diff --git a/app/build.gradle b/app/build.gradle index 0e8c4aa3d..1cf1e1e41 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -124,10 +124,10 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" //androidX - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.core:core-ktx:1.3.2' + implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'androidx.core:core-ktx:1.5.0' implementation "androidx.activity:activity-ktx:1.2.3" - implementation "androidx.fragment:fragment-ktx:1.3.3" + implementation "androidx.fragment:fragment-ktx:1.3.5" implementation 'androidx.preference:preference-ktx:1.1.1' implementation "androidx.collection:collection-ktx:1.1.0" implementation 'androidx.constraintlayout:constraintlayout:2.0.4' diff --git a/app/src/main/java/io/legado/app/data/entities/Book.kt b/app/src/main/java/io/legado/app/data/entities/Book.kt index ba33ba78e..54c47b323 100644 --- a/app/src/main/java/io/legado/app/data/entities/Book.kt +++ b/app/src/main/java/io/legado/app/data/entities/Book.kt @@ -67,6 +67,9 @@ data class Book( fun isEpub(): Boolean { return originName.endsWith(".epub", true) } + fun isUmd(): Boolean { + return originName.endsWith(".umd", true) + } fun isOnLineTxt(): Boolean { return !isLocalBook() && type == 0 diff --git a/app/src/main/java/io/legado/app/help/BookHelp.kt b/app/src/main/java/io/legado/app/help/BookHelp.kt index 6379d4cc3..41dc7376e 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -190,7 +190,7 @@ object BookHelp { } fun getContent(book: Book, bookChapter: BookChapter): String? { - if (book.isLocalTxt()) { + if (book.isLocalTxt()||book.isUmd()) { return LocalBook.getContext(book, bookChapter) } else if (book.isEpub() && !hasContent(book, bookChapter)) { val string = LocalBook.getContext(book, bookChapter) @@ -203,7 +203,7 @@ object BookHelp { ).writeText(it) } return string - } else { + }else { val file = FileUtils.getFile( downloadDir, cacheFolderName, diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index 3aea6d748..30c9f65db 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -28,6 +28,8 @@ object LocalBook { fun getChapterList(book: Book): ArrayList { return if (book.isEpub()) { EpubFile.getChapterList(book) + }else if(book.isUmd()){ + UmdFile.getChapterList(book) } else { AnalyzeTxtFile().analyze(book) } @@ -36,6 +38,8 @@ object LocalBook { fun getContext(book: Book, chapter: BookChapter): String? { return if (book.isEpub()) { EpubFile.getContent(book, chapter) + }else if (book.isUmd()){ + UmdFile.getContent(book, chapter) } else { AnalyzeTxtFile.getContent(book, chapter) } @@ -121,13 +125,14 @@ object LocalBook { ) ) if (book.isEpub()) EpubFile.upBookInfo(book) + if (book.isUmd()) UmdFile.upBookInfo(book) appDb.bookDao.insert(book) return book } fun deleteBook(book: Book, deleteOriginal: Boolean) { kotlin.runCatching { - if (book.isLocalTxt()) { + if (book.isLocalTxt()||book.isUmd()) { val bookFile = FileUtils.getFile(cacheFolder, book.originName) bookFile.delete() } diff --git a/app/src/main/java/io/legado/app/model/localBook/UmdFile.kt b/app/src/main/java/io/legado/app/model/localBook/UmdFile.kt new file mode 100644 index 000000000..de6c1586f --- /dev/null +++ b/app/src/main/java/io/legado/app/model/localBook/UmdFile.kt @@ -0,0 +1,138 @@ +package io.legado.app.model.localBook + +import android.net.Uri +import android.util.Log +import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BookChapter + +import io.legado.app.utils.FileUtils +import io.legado.app.utils.MD5Utils +import io.legado.app.utils.externalFilesDir +import io.legado.app.utils.isContentScheme +import me.ag2s.umdlib.domain.UmdBook +import me.ag2s.umdlib.umd.UmdReader +import splitties.init.appCtx +import java.io.File +import java.io.InputStream +import java.util.ArrayList + +class UmdFile(var book: Book) { + companion object { + private var uFile: UmdFile? = null + + @Synchronized + private fun getUFile(book: Book): UmdFile { + + if (uFile == null || uFile?.book?.bookUrl != book.bookUrl) { + uFile = UmdFile(book) + return uFile!! + } + uFile?.book = book + return uFile!! + } + + @Synchronized + fun getChapterList(book: Book): ArrayList { + return getUFile(book).getChapterList() + } + + @Synchronized + fun getContent(book: Book, chapter: BookChapter): String? { + return getUFile(book).getContent(chapter) + } + + @Synchronized + fun getImage( + book: Book, + href: String + ): InputStream? { + return getUFile(book).getImage(href) + } + + + @Synchronized + fun upBookInfo(book: Book) { + return getUFile(book).upBookInfo() + } + } + + + + private var umdBook: UmdBook? = null + get() { + if (field != null) { + return field + } + field = readUmd() + return field + } + + + + init { + try { + umdBook?.let { + if (book.coverUrl.isNullOrEmpty()) { + book.coverUrl = FileUtils.getPath( + appCtx.externalFilesDir, + "covers", + "${MD5Utils.md5Encode16(book.bookUrl)}.jpg" + ) + } + if (!File(book.coverUrl!!).exists()) { + FileUtils.writeBytes(book.coverUrl!!,it.cover.coverData) + + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + private fun readUmd(): UmdBook? { + val input= if (book.bookUrl.isContentScheme()) { + val uri = Uri.parse(book.bookUrl) + appCtx.contentResolver.openInputStream(uri) + } else { + File(book.bookUrl).inputStream() + } + return UmdReader().read(input) + } + + private fun upBookInfo() { + if(umdBook==null){ + uFile = null + book.intro = "书籍导入异常" + }else{ + val hd= umdBook!!.header + book.name=hd.title; + book.author=hd.author; + book.kind=hd.bookType; + } + } + private fun getContent(chapter: BookChapter): String? { + return umdBook?.chapters?.getContentString(chapter.index) + } + + private fun getChapterList(): ArrayList { + val chapterList = ArrayList() + umdBook?.chapters?.titles?.forEachIndexed { index, _ -> + val title = umdBook!!.chapters.getTitle(index) + val chapter = BookChapter() + chapter.title=title; + chapter.index = index + chapter.bookUrl = book.bookUrl + chapter.url = index.toString(); + Log.d("UMD",chapter.url) + chapterList.add(chapter) + } + book.latestChapterTitle = chapterList.lastOrNull()?.title + book.totalChapterNum = chapterList.size + return chapterList + } + + private fun getImage(href: String): InputStream? { + TODO("Not yet implemented") + } + + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt index 0bb66218d..5d45d4795 100644 --- a/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt @@ -197,6 +197,7 @@ class ImportBookActivity : VMBaseActivity getTitles() { + return titles; + } + + private List titles = new ArrayList<>(); + public List contentLengths = new ArrayList<>(); + public ByteArrayOutputStream contents = new ByteArrayOutputStream(); + + public void addTitle(String s){ + titles.add(UmdUtils.stringToUnicodeBytes(s)); + } + public void addTitle(byte[] s){ + titles.add(s); + } + public void addContentLength(Integer integer){ + contentLengths.add(integer); + } + public int getContentLength(int index){ + return contentLengths.get(index); + } + + public byte[] getContent(int index) { + int st=contentLengths.get(index); + byte[] b=contents.toByteArray(); + int end=index+1 chunkRbList = new ArrayList(); + + while(startPos < allContents.length) { + left = allContents.length - startPos; + len = DEFAULT_CHUNK_INIT_SIZE < left ? DEFAULT_CHUNK_INIT_SIZE : left; + + bos.reset(); + DeflaterOutputStream zos = new DeflaterOutputStream(bos); + zos.write(allContents, startPos, len); + zos.close(); + byte[] chunk = bos.toByteArray(); + + byte[] rb = UmdUtils.genRandomBytes(4); + wos.writeByte('$'); + wos.writeBytes(rb); // 4 random + chunkRbList.add(rb); + wos.writeInt(chunk.length + 9); + wos.write(chunk); + + // end of each chunk + wos.writeBytes('#', 0xF1, 0, 0, 0x15); + wos.write(zero16); + + startPos += len; + chunkCnt++; + } + + // end of all chunks + wos.writeBytes('#', 0x81, 0, 0x01, 0x09); + wos.writeBytes(0, 0, 0, 0); //random numbers + wos.write('$'); + wos.writeBytes(0, 0, 0, 0); //random numbers + wos.writeInt(chunkCnt * 4 + 9); + for (int i = chunkCnt - 1; i >= 0; i--) { + // random. They are as the same as random numbers in the begin of each chunk + // use desc order to output these random + wos.writeBytes(chunkRbList.get(i)); + } + } + + public void addChapter(String title, String content) { + titles.add(UmdUtils.stringToUnicodeBytes(title)); + byte[] b = UmdUtils.stringToUnicodeBytes(content); + contentLengths.add(b.length); + try { + contents.write(b); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void addFile(File f, String title) throws IOException { + byte[] temp = UmdUtils.readFile(f); + String s = new String(temp); + addChapter(title, s); + } + + public void addFile(File f) throws IOException { + String s = f.getName(); + int idx = s.lastIndexOf('.'); + if (idx >= 0) { + s = s.substring(0, idx); + } + addFile(f, s); + } + + public void clearChapters() { + titles.clear(); + contentLengths.clear(); + contents.reset(); + } + + public int getTotalContentLen() { + return TotalContentLen; + } + + public void setTotalContentLen(int totalContentLen) { + TotalContentLen = totalContentLen; + } +} diff --git a/epublib/src/main/java/me/ag2s/umdlib/domain/UmdCover.java b/epublib/src/main/java/me/ag2s/umdlib/domain/UmdCover.java new file mode 100644 index 000000000..b3c155300 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/umdlib/domain/UmdCover.java @@ -0,0 +1,96 @@ +package me.ag2s.umdlib.domain; + + +import java.io.File; +import java.io.IOException; + +import me.ag2s.umdlib.tool.UmdUtils; +import me.ag2s.umdlib.tool.WrapOutputStream; + + +/** + * This is the cover part of the UMD file. + *

+ * NOTICE: if the "coverData" is empty, it will be skipped when building UMD file. + *

+ * There are 3 ways to load the image data: + *
    + *
  1. new constructor function of UmdCover.
  2. + *
  3. use UmdCover.load function.
  4. + *
  5. use UmdCover.initDefaultCover, it will generate a simple image with text.
  6. + *
+ * @author Ray Liang (liangguanhui@qq.com) + * 2009-12-20 + */ +public class UmdCover { + + private static int DEFAULT_COVER_WIDTH = 120; + private static int DEFAULT_COVER_HEIGHT = 160; + + private byte[] coverData; + + public UmdCover() { + } + + public UmdCover(byte[] coverData) { + this.coverData = coverData; + } + + public void load(File f) throws IOException { + this.coverData = UmdUtils.readFile(f); + } + + public void load(String fileName) throws IOException { + load(new File(fileName)); + } + + public void initDefaultCover(String title) throws IOException { +// BufferedImage img = new BufferedImage(DEFAULT_COVER_WIDTH, DEFAULT_COVER_HEIGHT, BufferedImage.TYPE_INT_RGB); +// Graphics g = img.getGraphics(); +// g.setColor(Color.BLACK); +// g.fillRect(0, 0, img.getWidth(), img.getHeight()); +// g.setColor(Color.WHITE); +// g.setFont(new Font("����", Font.PLAIN, 12)); +// +// FontMetrics fm = g.getFontMetrics(); +// int ascent = fm.getAscent(); +// int descent = fm.getDescent(); +// int strWidth = fm.stringWidth(title); +// int x = (img.getWidth() - strWidth) / 2; +// int y = (img.getHeight() - ascent - descent) / 2; +// g.drawString(title, x, y); +// g.dispose(); +// +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(baos); +// JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(img); +// param.setQuality(0.5f, false); +// encoder.setJPEGEncodeParam(param); +// encoder.encode(img); +// +// coverData = baos.toByteArray(); + } + + public void buildCover(WrapOutputStream wos) throws IOException { + if (coverData == null || coverData.length == 0) { + return; + } + wos.writeBytes('#', 0x82, 0, 0x01, 0x0A, 0x01); + byte[] rb = UmdUtils.genRandomBytes(4); + wos.writeBytes(rb); //random numbers + wos.write('$'); + wos.writeBytes(rb); //random numbers + wos.writeInt(coverData.length + 9); + wos.write(coverData); + } + + public byte[] getCoverData() { + return coverData; + } + + public void setCoverData(byte[] coverData) { + this.coverData = coverData; + } + +} diff --git a/epublib/src/main/java/me/ag2s/umdlib/domain/UmdEnd.java b/epublib/src/main/java/me/ag2s/umdlib/domain/UmdEnd.java new file mode 100644 index 000000000..129712f49 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/umdlib/domain/UmdEnd.java @@ -0,0 +1,20 @@ +package me.ag2s.umdlib.domain; + +import java.io.IOException; + +import me.ag2s.umdlib.tool.WrapOutputStream; + +/** + * End part of UMD book, nothing to be special + * + * @author Ray Liang (liangguanhui@qq.com) + * 2009-12-20 + */ +public class UmdEnd { + + public void buildEnd(WrapOutputStream wos) throws IOException { + wos.writeBytes('#', 0x0C, 0, 0x01, 0x09); + wos.writeInt(wos.getWritten() + 4); + } + +} diff --git a/epublib/src/main/java/me/ag2s/umdlib/domain/UmdHeader.java b/epublib/src/main/java/me/ag2s/umdlib/domain/UmdHeader.java new file mode 100644 index 000000000..389ea388f --- /dev/null +++ b/epublib/src/main/java/me/ag2s/umdlib/domain/UmdHeader.java @@ -0,0 +1,162 @@ +package me.ag2s.umdlib.domain; + + +import java.io.IOException; + +import me.ag2s.umdlib.tool.UmdUtils; +import me.ag2s.umdlib.tool.WrapOutputStream; + +/** + * Header of UMD file. + * It includes a lot of properties of header. + * All the properties are String type. + * + * @author Ray Liang (liangguanhui@qq.com) + * 2009-12-20 + */ +public class UmdHeader { + public byte getUmdType() { + return umdType; + } + + public void setUmdType(byte umdType) { + this.umdType = umdType; + } + + private byte umdType; + private String title; + + private String author; + + private String year; + + private String month; + + private String day; + + private String bookType; + + private String bookMan; + + private String shopKeeper; + private final static byte B_type_umd = (byte) 0x01; + private final static byte B_type_title = (byte) 0x02; + private final static byte B_type_author = (byte) 0x03; + private final static byte B_type_year = (byte) 0x04; + private final static byte B_type_month = (byte) 0x05; + private final static byte B_type_day = (byte) 0x06; + private final static byte B_type_bookType = (byte) 0x07; + private final static byte B_type_bookMan = (byte) 0x08; + private final static byte B_type_shopKeeper = (byte) 0x09; + + public void buildHeader(WrapOutputStream wos) throws IOException { + wos.writeBytes(0x89, 0x9b, 0x9a, 0xde); // UMD file type flags + wos.writeByte('#'); + wos.writeBytes(0x01, 0x00, 0x00, 0x08); // Unknown + wos.writeByte(0x01); //0x01 is text type; while 0x02 is Image type. + wos.writeBytes(UmdUtils.genRandomBytes(2)); //random number + + // start properties output + buildType(wos, B_type_title, getTitle()); + buildType(wos, B_type_author, getAuthor()); + buildType(wos, B_type_year, getYear()); + buildType(wos, B_type_month, getMonth()); + buildType(wos, B_type_day, getDay()); + buildType(wos, B_type_bookType, getBookType()); + buildType(wos, B_type_bookMan, getBookMan()); + buildType(wos, B_type_shopKeeper, getShopKeeper()); + } + + public void buildType(WrapOutputStream wos, byte type, String content) throws IOException { + if (content == null || content.length() == 0) { + return; + } + + wos.writeBytes('#', type, 0, 0); + + byte[] temp = UmdUtils.stringToUnicodeBytes(content); + wos.writeByte(temp.length + 5); + wos.write(temp); + } + + + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getBookMan() { + return bookMan; + } + + public void setBookMan(String bookMan) { + this.bookMan = bookMan; + } + + public String getShopKeeper() { + return shopKeeper; + } + + public void setShopKeeper(String shopKeeper) { + this.shopKeeper = shopKeeper; + } + + public String getYear() { + return year; + } + + public void setYear(String year) { + this.year = year; + } + + public String getMonth() { + return month; + } + + public void setMonth(String month) { + this.month = month; + } + + public String getDay() { + return day; + } + + public void setDay(String day) { + this.day = day; + } + + public String getBookType() { + return bookType; + } + + public void setBookType(String bookType) { + this.bookType = bookType; + } + + @Override + public String toString() { + return "UmdHeader{" + + "umdType=" + umdType + + ", title='" + title + '\'' + + ", author='" + author + '\'' + + ", year='" + year + '\'' + + ", month='" + month + '\'' + + ", day='" + day + '\'' + + ", bookType='" + bookType + '\'' + + ", bookMan='" + bookMan + '\'' + + ", shopKeeper='" + shopKeeper + '\'' + + '}'; + } +} diff --git a/epublib/src/main/java/me/ag2s/umdlib/tool/StreamReader.java b/epublib/src/main/java/me/ag2s/umdlib/tool/StreamReader.java new file mode 100644 index 000000000..663e73f6b --- /dev/null +++ b/epublib/src/main/java/me/ag2s/umdlib/tool/StreamReader.java @@ -0,0 +1,124 @@ +package me.ag2s.umdlib.tool; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class StreamReader { + private InputStream is; + + public long getOffset() { + return offset; + } + + public void setOffset(long offset) { + this.offset = offset; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + private long offset; + private long size; + + private void incCount(int value) { + int temp = (int) (offset + value); + if (temp < 0) { + temp = Integer.MAX_VALUE; + } + offset = temp; + } + public StreamReader(InputStream inputStream) throws IOException { + this.is=inputStream; + //this.size=inputStream.getChannel().size(); + } + + public short readUint8() throws IOException { + byte[] b=new byte[1]; + is.read(b); + incCount(1); + return (short) ((b[0] & 0xFF)); + + } + + public byte readByte() throws IOException { + byte[] b=new byte[1]; + is.read(b); + incCount(1); + return b[0]; + } + public byte[] readBytes(int len) throws IOException { + if (len<1){ + System.out.println(len); + throw new IllegalArgumentException("Length must > 0: " + len); + } + byte[] b=new byte[len]; + is.read(b); + incCount(len); + return b; + } + public String readHex(int len) throws IOException { + if (len<1){ + System.out.println(len); + throw new IllegalArgumentException("Length must > 0: " + len); + } + byte[] b=new byte[len]; + is.read(b); + incCount(len); + return UmdUtils.toHex(b); + } + + public short readShort() throws IOException { + byte[] b=new byte[2]; + is.read(b); + incCount(2); + short x = (short) (((b[0] & 0xFF) << 8) | ((b[1] & 0xFF) << 0)); + return x; + } + public short readShortLe() throws IOException { + byte[] b=new byte[2]; + is.read(b); + incCount(2); + short x = (short) (((b[1] & 0xFF) << 8) | ((b[0] & 0xFF) << 0)); + return x; + } + public int readInt() throws IOException { + byte[] b=new byte[4]; + is.read(b); + incCount(4); + int x = ((b[0] & 0xFF) << 24) | ((b[1] & 0xFF) << 16) | + ((b[2] & 0xFF) << 8) | ((b[3] & 0xFF) << 0); + return x; + } + public int readIntLe() throws IOException { + byte[] b=new byte[4]; + is.read(b); + incCount(4); + int x = ((b[3] & 0xFF) << 24) | ((b[2] & 0xFF) << 16) | + ((b[1] & 0xFF) << 8) | ((b[0] & 0xFF) << 0); + return x; + } + public void skip(int len) throws IOException { + readBytes(len); + } + + + public byte[] read(byte[] b) throws IOException { + is.read(b); + incCount(b.length); + return b; + } + + public byte[] read(byte[] b, int off, int len) throws IOException { + is.read(b, off, len); + incCount(len); + return b; + } + + +} diff --git a/epublib/src/main/java/me/ag2s/umdlib/tool/UmdUtils.java b/epublib/src/main/java/me/ag2s/umdlib/tool/UmdUtils.java new file mode 100644 index 000000000..2fa804d58 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/umdlib/tool/UmdUtils.java @@ -0,0 +1,159 @@ + +package me.ag2s.umdlib.tool; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Random; +import java.util.zip.InflaterInputStream; + + +public class UmdUtils { + + private static final int EOF = -1; + private static final int BUFFER_SIZE = 8 * 1024; + + + /** + * 将字符串编码成Unicode形式的byte[] + * @param s 要编码的字符串 + * @return 编码好的byte[] + */ + public static byte[] stringToUnicodeBytes(String s) { + if (s == null) { + throw new NullPointerException(); + } + + int len = s.length(); + byte[] ret = new byte[len * 2]; + int a, b, c; + for (int i = 0; i < len; i++) { + c = s.charAt(i); + a = c >> 8; + b = c & 0xFF; + if (a < 0) { + a += 0xFF; + } + if (b < 0) { + b += 0xFF; + } + ret[i * 2] = (byte) b; + ret[i * 2 + 1] = (byte) a; + } + return ret; + } + + /** + * 将编码成Unicode形式的byte[]解码成原始字符串 + * @param bytes 编码成Unicode形式的byte[] + * @return 原始字符串 + */ + public static String unicodeBytesToString(byte[] bytes){ + char[] s=new char[bytes.length/2]; + StringBuilder sb=new StringBuilder(); + int a,b,c; + for(int i=0;i= 0) { + baos.write(ch); + } + baos.flush(); + return baos.toByteArray(); + } finally { + fis.close(); + } + } + + private static Random random = new Random(); + + public static byte[] genRandomBytes(int len) { + if (len <= 0) { + throw new IllegalArgumentException("Length must > 0: " + len); + } + byte[] ret = new byte[len]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (byte) random.nextInt(256); + } + return ret; + } + +} diff --git a/epublib/src/main/java/me/ag2s/umdlib/tool/WrapOutputStream.java b/epublib/src/main/java/me/ag2s/umdlib/tool/WrapOutputStream.java new file mode 100644 index 000000000..80ec1982e --- /dev/null +++ b/epublib/src/main/java/me/ag2s/umdlib/tool/WrapOutputStream.java @@ -0,0 +1,91 @@ +package me.ag2s.umdlib.tool; + +import java.io.IOException; +import java.io.OutputStream; + +public class WrapOutputStream extends OutputStream { + + private OutputStream os; + private int written; + + public WrapOutputStream(OutputStream os) { + this.os = os; + } + + private void incCount(int value) { + int temp = written + value; + if (temp < 0) { + temp = Integer.MAX_VALUE; + } + written = temp; + } + + // it is different from the writeInt of DataOutputStream + public void writeInt(int v) throws IOException { + os.write((v >>> 0) & 0xFF); + os.write((v >>> 8) & 0xFF); + os.write((v >>> 16) & 0xFF); + os.write((v >>> 24) & 0xFF); + incCount(4); + } + + public void writeByte(byte b) throws IOException { + write(b); + } + + public void writeByte(int n) throws IOException { + write(n); + } + + public void writeBytes(byte ... bytes) throws IOException { + write(bytes); + } + + public void writeBytes(int ... vals) throws IOException { + for (int v : vals) { + write(v); + } + } + + public void write(byte[] b, int off, int len) throws IOException { + os.write(b, off, len); + incCount(len); + } + + public void write(byte[] b) throws IOException { + os.write(b); + incCount(b.length); + } + + public void write(int b) throws IOException { + os.write(b); + incCount(1); + } + + ///////////////////////////////////////////////// + + public void close() throws IOException { + os.close(); + } + + public void flush() throws IOException { + os.flush(); + } + + public boolean equals(Object obj) { + return os.equals(obj); + } + + public int hashCode() { + return os.hashCode(); + } + + public String toString() { + return os.toString(); + } + + public int getWritten() { + return written; + } + +} diff --git a/epublib/src/main/java/me/ag2s/umdlib/umd/UmdReader.java b/epublib/src/main/java/me/ag2s/umdlib/umd/UmdReader.java new file mode 100644 index 000000000..804973905 --- /dev/null +++ b/epublib/src/main/java/me/ag2s/umdlib/umd/UmdReader.java @@ -0,0 +1,222 @@ +package me.ag2s.umdlib.umd; + +import java.io.IOException; +import java.io.InputStream; + + +import me.ag2s.umdlib.domain.UmdBook; +import me.ag2s.umdlib.domain.UmdCover; +import me.ag2s.umdlib.domain.UmdHeader; +import me.ag2s.umdlib.tool.StreamReader; +import me.ag2s.umdlib.tool.UmdUtils; + +/** + * UMD格式的电子书解析 + * 格式规范参考: + * http://blog.sina.com.cn/s/blog_7c8dc2d501018o5d.html + * http://blog.sina.com.cn/s/blog_7c8dc2d501018o5l.html + * + */ + +public class UmdReader { + UmdBook book; + InputStream inputStream; + int _AdditionalCheckNumber; + int _TotalContentLen; + boolean end = false; + + + public synchronized UmdBook read(InputStream inputStream) throws Exception { + + book = new UmdBook(); + this.inputStream=inputStream; + StreamReader reader = new StreamReader(inputStream); + UmdHeader umdHeader = new UmdHeader(); + book.setHeader(umdHeader); + if (reader.readIntLe() != 0xde9a9b89) { + throw new IOException("Wrong header"); + } + short num1 = -1; + byte ch = reader.readByte(); + while (ch == 35) { + //int num2=reader.readByte(); + short segType = reader.readShortLe(); + byte segFlag = reader.readByte(); + short len = (short) (reader.readUint8() - 5); + + System.out.println("块标识:" + segType); + //short length1 = reader.readByte(); + ReadSection(segType, segFlag, len, reader, umdHeader); + + if ((int) segType == 241 || (int) segType == 10) { + segType = num1; + } + for (ch = reader.readByte(); ch == 36; ch = reader.readByte()) { + //int num3 = reader.readByte(); + System.out.println(ch); + int additionalCheckNumber = reader.readIntLe(); + int length2 = (reader.readIntLe() - 9); + ReadAdditionalSection(segType, additionalCheckNumber, length2, reader); + } + num1 = segType; + + } + System.out.println(book.getHeader().toString()); + return book; + + } + + private void ReadAdditionalSection(short segType, int additionalCheckNumber, int length, StreamReader reader) throws Exception { + switch (segType) { + case 14: + //this._TotalImageList.Add((object) Image.FromStream((Stream) new MemoryStream(reader.ReadBytes((int) length)))); + break; + case 15: + //this._TotalImageList.Add((object) Image.FromStream((Stream) new MemoryStream(reader.ReadBytes((int) length)))); + break; + case 129: + reader.readBytes(length); + break; + case 130: + //byte[] covers = reader.readBytes(length); + book.setCover(new UmdCover(reader.readBytes(length))); + //this._Book.Cover = BitmapImage.FromStream((Stream) new MemoryStream(reader.ReadBytes((int) length))); + break; + case 131: + System.out.println(length / 4); + book.setNum(length / 4); + for (int i = 0; i < length / 4; ++i) { + book.getChapters().addContentLength(reader.readIntLe()); + } + break; + case 132: + //System.out.println(length/4); + System.out.println(_AdditionalCheckNumber); + System.out.println(additionalCheckNumber); + if (this._AdditionalCheckNumber != additionalCheckNumber) { + System.out.println(length); + book.getChapters().contents.write(UmdUtils.decompress(reader.readBytes(length))); + book.getChapters().contents.flush(); + break; + } else { + for (int i = 0; i < book.getNum(); i++) { + short len = reader.readUint8(); + byte[] title = reader.readBytes(len); + //System.out.println(UmdUtils.unicodeBytesToString(title)); + book.getChapters().addTitle(title); + } + } + + + break; + default: + /*Console.WriteLine("未知内容"); + Console.WriteLine("Seg Type = " + (object) segType); + Console.WriteLine("Seg Len = " + (object) length); + Console.WriteLine("content = " + (object) reader.ReadBytes((int) length));*/ + break; + } + } + + public void ReadSection(short segType, byte segFlag, short length, StreamReader reader, UmdHeader header) throws IOException { + switch (segType) { + case 1://umd文件头 DCTS_CMD_ID_VERSION + header.setUmdType(reader.readByte()); + reader.readBytes(2);//Random 2 + System.out.println("UMD文件类型:" + header.getUmdType()); + break; + case 2://文件标题 DCTS_CMD_ID_TITLE + header.setTitle(UmdUtils.unicodeBytesToString(reader.readBytes(length))); + System.out.println("文件标题:" + header.getTitle()); + break; + case 3://作者 + header.setAuthor(UmdUtils.unicodeBytesToString(reader.readBytes(length))); + System.out.println("作者:" + header.getAuthor()); + break; + case 4://年 + header.setYear(UmdUtils.unicodeBytesToString(reader.readBytes(length))); + System.out.println("年:" + header.getYear()); + break; + case 5://月 + header.setMonth(UmdUtils.unicodeBytesToString(reader.readBytes(length))); + System.out.println("月:" + header.getMonth()); + break; + case 6://日 + header.setDay(UmdUtils.unicodeBytesToString(reader.readBytes(length))); + System.out.println("日:" + header.getDay()); + break; + case 7://小说类型 + header.setBookType(UmdUtils.unicodeBytesToString(reader.readBytes(length))); + System.out.println("小说类型:" + header.getBookType()); + break; + case 8://出版商 + header.setBookMan(UmdUtils.unicodeBytesToString(reader.readBytes(length))); + System.out.println("出版商:" + header.getBookMan()); + break; + case 9:// 零售商 + header.setShopKeeper(UmdUtils.unicodeBytesToString(reader.readBytes(length))); + System.out.println("零售商:" + header.getShopKeeper()); + break; + case 10://CONTENT ID + System.out.println("CONTENT ID:" + reader.readHex(length)); + break; + case 11: + //内容长度 DCTS_CMD_ID_FILE_LENGTH + _TotalContentLen = reader.readIntLe(); + book.getChapters().setTotalContentLen(_TotalContentLen); + System.out.println("内容长度:" + _TotalContentLen); + break; + case 12://UMD文件结束 + end = true; + int num2 = reader.readIntLe(); + System.out.println("整个文件长度" + num2); + break; + case 13: + break; + case 14: + int num3 = (int) reader.readByte(); + break; + case 15: + reader.readBytes(length); + break; + case 129://正文 + case 131://章节偏移 + _AdditionalCheckNumber = reader.readIntLe(); + System.out.println("章节偏移:" + _AdditionalCheckNumber); + break; + case 132://章节标题,正文 + _AdditionalCheckNumber = reader.readIntLe(); + System.out.println("章节标题,正文:" + _AdditionalCheckNumber); + break; + case 130://封面(jpg) + int num4 = (int) reader.readByte(); + _AdditionalCheckNumber = reader.readIntLe(); + break; + case 135://页面偏移(Page Offset) + reader.readUint8();//fontSize 一字节 字体大小 + reader.readUint8();//screenWidth 屏幕宽度 + reader.readBytes(4);//BlockRandom 指向一个页面偏移数据块 + break; + case 240://CDS KEY + break; + case 241://许可证(LICENCE KEY) + //System.out.println("整个文件长度" + length); + System.out.println("许可证(LICENCE KEY):" + reader.readHex(16)); + break; + default: + if (length > 0) { + byte[] numArray = reader.readBytes(length); + } + + + } + } + + + @Override + public String toString() { + return "UmdReader{" + + "book=" + book + + '}'; + } +}