Merge pull request #1093 from ag2s20150909/master

添加umd格式支持
pull/1094/head
kunfei 3 years ago committed by GitHub
commit 7b35a0dc84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      app/build.gradle
  2. 3
      app/src/main/java/io/legado/app/data/entities/Book.kt
  3. 2
      app/src/main/java/io/legado/app/help/BookHelp.kt
  4. 7
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  5. 138
      app/src/main/java/io/legado/app/model/localBook/UmdFile.kt
  6. 2
      app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt
  7. 81
      epublib/src/main/java/me/ag2s/umdlib/domain/UmdBook.java
  8. 207
      epublib/src/main/java/me/ag2s/umdlib/domain/UmdChapters.java
  9. 96
      epublib/src/main/java/me/ag2s/umdlib/domain/UmdCover.java
  10. 20
      epublib/src/main/java/me/ag2s/umdlib/domain/UmdEnd.java
  11. 162
      epublib/src/main/java/me/ag2s/umdlib/domain/UmdHeader.java
  12. 124
      epublib/src/main/java/me/ag2s/umdlib/tool/StreamReader.java
  13. 159
      epublib/src/main/java/me/ag2s/umdlib/tool/UmdUtils.java
  14. 91
      epublib/src/main/java/me/ag2s/umdlib/tool/WrapOutputStream.java
  15. 222
      epublib/src/main/java/me/ag2s/umdlib/umd/UmdReader.java

@ -124,10 +124,10 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
//androidX //androidX
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.5.0'
implementation "androidx.activity:activity-ktx:1.2.3" 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.preference:preference-ktx:1.1.1'
implementation "androidx.collection:collection-ktx:1.1.0" implementation "androidx.collection:collection-ktx:1.1.0"
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'

@ -67,6 +67,9 @@ data class Book(
fun isEpub(): Boolean { fun isEpub(): Boolean {
return originName.endsWith(".epub", true) return originName.endsWith(".epub", true)
} }
fun isUmd(): Boolean {
return originName.endsWith(".umd", true)
}
fun isOnLineTxt(): Boolean { fun isOnLineTxt(): Boolean {
return !isLocalBook() && type == 0 return !isLocalBook() && type == 0

@ -190,7 +190,7 @@ object BookHelp {
} }
fun getContent(book: Book, bookChapter: BookChapter): String? { fun getContent(book: Book, bookChapter: BookChapter): String? {
if (book.isLocalTxt()) { if (book.isLocalTxt()||book.isUmd()) {
return LocalBook.getContext(book, bookChapter) return LocalBook.getContext(book, bookChapter)
} else if (book.isEpub() && !hasContent(book, bookChapter)) { } else if (book.isEpub() && !hasContent(book, bookChapter)) {
val string = LocalBook.getContext(book, bookChapter) val string = LocalBook.getContext(book, bookChapter)

@ -28,6 +28,8 @@ object LocalBook {
fun getChapterList(book: Book): ArrayList<BookChapter> { fun getChapterList(book: Book): ArrayList<BookChapter> {
return if (book.isEpub()) { return if (book.isEpub()) {
EpubFile.getChapterList(book) EpubFile.getChapterList(book)
}else if(book.isUmd()){
UmdFile.getChapterList(book)
} else { } else {
AnalyzeTxtFile().analyze(book) AnalyzeTxtFile().analyze(book)
} }
@ -36,6 +38,8 @@ object LocalBook {
fun getContext(book: Book, chapter: BookChapter): String? { fun getContext(book: Book, chapter: BookChapter): String? {
return if (book.isEpub()) { return if (book.isEpub()) {
EpubFile.getContent(book, chapter) EpubFile.getContent(book, chapter)
}else if (book.isUmd()){
UmdFile.getContent(book, chapter)
} else { } else {
AnalyzeTxtFile.getContent(book, chapter) AnalyzeTxtFile.getContent(book, chapter)
} }
@ -121,13 +125,14 @@ object LocalBook {
) )
) )
if (book.isEpub()) EpubFile.upBookInfo(book) if (book.isEpub()) EpubFile.upBookInfo(book)
if (book.isUmd()) UmdFile.upBookInfo(book)
appDb.bookDao.insert(book) appDb.bookDao.insert(book)
return book return book
} }
fun deleteBook(book: Book, deleteOriginal: Boolean) { fun deleteBook(book: Book, deleteOriginal: Boolean) {
kotlin.runCatching { kotlin.runCatching {
if (book.isLocalTxt()) { if (book.isLocalTxt()||book.isUmd()) {
val bookFile = FileUtils.getFile(cacheFolder, book.originName) val bookFile = FileUtils.getFile(cacheFolder, book.originName)
bookFile.delete() bookFile.delete()
} }

@ -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<BookChapter> {
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<BookChapter> {
val chapterList = ArrayList<BookChapter>()
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")
}
}

@ -197,6 +197,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
} else if (!item.isDir } else if (!item.isDir
&& !item.name.endsWith(".txt", true) && !item.name.endsWith(".txt", true)
&& !item.name.endsWith(".epub", true) && !item.name.endsWith(".epub", true)
&& !item.name.endsWith(".umd", true)
) { ) {
docList.removeAt(i) docList.removeAt(i)
} }
@ -226,6 +227,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
) )
} else if (it.name.endsWith(".txt", true) } else if (it.name.endsWith(".txt", true)
|| it.name.endsWith(".epub", true) || it.name.endsWith(".epub", true)
|| it.name.endsWith(".umd", true)
) { ) {
docList.add( docList.add(
DocItem( DocItem(

@ -0,0 +1,81 @@
package me.ag2s.umdlib.domain;
import java.io.IOException;
import java.io.OutputStream;
import me.ag2s.umdlib.tool.WrapOutputStream;
public class UmdBook {
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
private int num;
/** Header Part of UMD book */
private UmdHeader header = new UmdHeader();
/**
* Detail chapters Part of UMD book
* (include Titles & Contents of each chapter)
*/
private UmdChapters chapters = new UmdChapters();
/** Cover Part of UMD book (for example, and JPEG file) */
private UmdCover cover = new UmdCover();
/** End Part of UMD book */
private UmdEnd end = new UmdEnd();
/**
* Build the UMD file.
* @param os
* @throws IOException
*/
public void buildUmd(OutputStream os) throws IOException {
WrapOutputStream wos = new WrapOutputStream(os);
header.buildHeader(wos);
chapters.buildChapters(wos);
cover.buildCover(wos);
end.buildEnd(wos);
}
public UmdHeader getHeader() {
return header;
}
public void setHeader(UmdHeader header) {
this.header = header;
}
public UmdChapters getChapters() {
return chapters;
}
public void setChapters(UmdChapters chapters) {
this.chapters = chapters;
}
public UmdCover getCover() {
return cover;
}
public void setCover(UmdCover cover) {
this.cover = cover;
}
public UmdEnd getEnd() {
return end;
}
public void setEnd(UmdEnd end) {
this.end = end;
}
}

@ -0,0 +1,207 @@
package me.ag2s.umdlib.domain;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.DeflaterOutputStream;
import me.ag2s.umdlib.tool.UmdUtils;
import me.ag2s.umdlib.tool.WrapOutputStream;
/**
* It includes all titles and contents of each chapter in the UMD file.
* And the content has been compressed by zlib.
*
* @author Ray Liang (liangguanhui@qq.com)
* 2009-12-20
*/
public class UmdChapters {
private static final int DEFAULT_CHUNK_INIT_SIZE = 32768;
private int TotalContentLen;
public List<byte[]> getTitles() {
return titles;
}
private List<byte[]> titles = new ArrayList<>();
public List<Integer> 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<contentLengths.size()?contentLengths.get(index+1): getTotalContentLen();
System.out.println("总长度:"+contents.size());
System.out.println("起始值:"+st);
System.out.println("结束值:"+end);
byte[] bAr=new byte[end-st];
System.arraycopy(b,st,bAr,0,bAr.length);
return bAr;
}
public String getContentString(int index) {
return UmdUtils.unicodeBytesToString(getContent(index)).replace((char) 0x2029, '\n');
}
public String getTitle(int index){
return UmdUtils.unicodeBytesToString(titles.get(index));
}
public void buildChapters(WrapOutputStream wos) throws IOException {
writeChaptersHead(wos);
writeChaptersContentOffset(wos);
writeChaptersTitles(wos);
writeChaptersChunks(wos);
}
private void writeChaptersHead(WrapOutputStream wos) throws IOException {
wos.writeBytes('#', 0x0b, 0, 0, 0x09);
wos.writeInt(contents.size());
}
private void writeChaptersContentOffset(WrapOutputStream wos) throws IOException {
wos.writeBytes('#', 0x83, 0, 0, 0x09);
byte[] rb = UmdUtils.genRandomBytes(4);
wos.writeBytes(rb); //random numbers
wos.write('$');
wos.writeBytes(rb); //random numbers
wos.writeInt(contentLengths.size() * 4 + 9); // about the count of chapters
int offset = 0;
for (Integer n : contentLengths) {
wos.writeInt(offset);
offset += n;
}
}
private void writeChaptersTitles(WrapOutputStream wos) throws IOException {
wos.writeBytes('#', 0x84, 0, 0x01, 0x09);
byte[] rb = UmdUtils.genRandomBytes(4);
wos.writeBytes(rb); //random numbers
wos.write('$');
wos.writeBytes(rb); //random numbers
int totalTitlesLen = 0;
for (byte[] t : titles) {
totalTitlesLen += t.length;
}
// about the length of the titles
wos.writeInt(totalTitlesLen + titles.size() + 9);
for (byte[] t : titles) {
wos.writeByte(t.length);
wos.write(t);
}
}
private void writeChaptersChunks(WrapOutputStream wos) throws IOException {
byte[] allContents = contents.toByteArray();
byte[] zero16 = new byte[16];
Arrays.fill(zero16, 0, zero16.length, (byte) 0);
// write each package of content
int startPos = 0;
int len = 0;
int left = 0;
int chunkCnt = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream(DEFAULT_CHUNK_INIT_SIZE + 256);
List<byte[]> chunkRbList = new ArrayList<byte[]>();
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;
}
}

@ -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.
* <P>
* NOTICE: if the "coverData" is empty, it will be skipped when building UMD file.
* </P>
* There are 3 ways to load the image data:
* <ol>
* <li>new constructor function of UmdCover.</li>
* <li>use UmdCover.load function.</li>
* <li>use UmdCover.initDefaultCover, it will generate a simple image with text.</li>
* </ol>
* @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("<EFBFBD><EFBFBD><EFBFBD><EFBFBD>", 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;
}
}

@ -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);
}
}

@ -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 + '\'' +
'}';
}
}

@ -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;
}
}

@ -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<s.length;i++){
a=bytes[i*2+1];
b=bytes[i*2];
c=(a&0xff)<<8|(b&0xff);
if(c<0){
c+=0xffff;
}
char[] c1=Character.toChars(c);
sb.append(c1);
}
return sb.toString();
}
/**
* 将byte[]转化成Hex形式
* @param bArr byte[]
* @return 目标HEX字符串
*/
public static String toHex(byte[] bArr){
StringBuilder sb = new StringBuilder(bArr.length);
String sTmp;
for (int i = 0; i < bArr.length; i++) {
sTmp = Integer.toHexString(0xFF & bArr[i]);
if (sTmp.length() < 2)
sb.append(0);
sb.append(sTmp.toUpperCase());
}
return sb.toString();
}
/**
* 解压缩zip的byte[]
* @param compress zippered byte[]
* @return decompressed byte[]
* @throws Exception 解码时失败时
*/
public static byte[] decompress(byte[] compress) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(compress);
InflaterInputStream iis = new InflaterInputStream(bais);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int c = 0;
byte[] buf = new byte[BUFFER_SIZE];
while (true) {
c = iis.read(buf);
if (c == EOF)
break;
baos.write(buf, 0, c);
}
baos.flush();
return baos.toByteArray();
}
public static void saveFile(File f, byte[] content) throws IOException {
FileOutputStream fos = new FileOutputStream(f);
try {
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write(content);
bos.flush();
} finally {
fos.close();
}
}
public static byte[] readFile(File f) throws IOException {
FileInputStream fis = new FileInputStream(f);
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedInputStream bis = new BufferedInputStream(fis);
int ch;
while ((ch = bis.read()) >= 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;
}
}

@ -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;
}
}

@ -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 +
'}';
}
}
Loading…
Cancel
Save