diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt index e01100bfd..52feb9489 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt @@ -239,7 +239,7 @@ class CacheActivity : VMBaseActivity() adapter.getItem(exportPosition)?.let { book -> Snackbar.make(binding.titleBar, R.string.exporting, Snackbar.LENGTH_INDEFINITE) .show() - viewModel.export(path, book) { + viewModel.exportEPUB(path, book) { binding.titleBar.snackbar(it) } } diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt index d420372c6..e60a7d3d0 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -1,8 +1,13 @@ package io.legado.app.ui.book.cache import android.app.Application +import android.graphics.Bitmap +import android.graphics.drawable.Drawable import android.net.Uri import androidx.documentfile.provider.DocumentFile +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition import io.legado.app.R import io.legado.app.base.BaseViewModel import io.legado.app.constant.AppPattern @@ -13,7 +18,12 @@ import io.legado.app.help.BookHelp import io.legado.app.help.ContentProcessor import io.legado.app.help.storage.BookWebDav import io.legado.app.utils.* +import me.ag2s.epublib.domain.* +import me.ag2s.epublib.epub.EpubWriter +import me.ag2s.epublib.util.ResourceUtil +import java.io.ByteArrayOutputStream import java.io.File +import java.io.FileOutputStream import java.nio.charset.Charset @@ -127,4 +137,130 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } return srcList } + //////////////////Start EPUB + /** + * 导出Epub + */ + fun exportEPUB(path: String, book: Book, finally: (msg: String) -> Unit) { + execute { + if (path.isContentScheme()) { + val uri = Uri.parse(path) + DocumentFile.fromTreeUri(context, uri)?.let { + exportEpub(it, book) + } + } else { + exportEpub(FileUtils.createFolderIfNotExist(path), book) + } + }.onError { + finally(it.localizedMessage ?: "ERROR") + }.onSuccess { + finally(context.getString(R.string.success)) + } + } + + @Suppress("BlockingMethodInNonBlockingContext") + private suspend fun exportEpub(doc: DocumentFile, book: Book) { + val filename = "${book.name} by ${book.author}.epub" + DocumentUtils.delete(doc, filename) + val epubBook = EpubBook() + epubBook.version = "2.0" + //set metadata + setEpubMetadata(book,epubBook) + //set cover + setCover(book, epubBook) + + //set css + epubBook.resources.add( + Resource( + "h1 {color: blue;}p {text-indent:2em;}".encodeToByteArray(), + "css/style.css" + ) + ) + //设置正文 + setEpubContent(book, epubBook) + + DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc -> + context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs -> + EpubWriter().write(epubBook, bookOs) + } + + } + } + + private suspend fun exportEpub(file: File, book: Book) { + val filename = "${book.name} by ${book.author}.epub" + val epubBook = EpubBook() + epubBook.version = "2.0" + //set metadata + setEpubMetadata(book,epubBook) + //set cover + setCover(book, epubBook) + + //set css + epubBook.resources.add( + Resource( + "h1 {color: blue;}p {text-indent:2em;}".encodeToByteArray(), + "css/style.css" + ) + ) + val bookPath = FileUtils.getPath(file, filename) + val bookFile = FileUtils.createFileWithReplace(bookPath) + //设置正文 + setEpubContent(book, epubBook) + EpubWriter().write(epubBook, FileOutputStream(bookFile)) + } + private fun setCover(book: Book, epubBook: EpubBook) { + + Glide.with(context) + .asBitmap() + .load(book.coverUrl) + .into(object : CustomTarget(){ + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + val stream = ByteArrayOutputStream() + resource.compress(Bitmap.CompressFormat.JPEG, 100, stream) + val byteArray: ByteArray = stream.toByteArray() + resource.recycle() + epubBook.coverImage= Resource(byteArray,"cover.jpg") + } + + override fun onLoadCleared(placeholder: Drawable?) { + + } + + }) + } + + private fun setEpubContent(book: Book, epubBook: EpubBook) { + val useReplace = AppConfig.exportUseReplace + val contentProcessor = ContentProcessor(book.name, book.origin) + appDb.bookChapterDao.getChapterList(book.bookUrl).forEach { chapter -> + BookHelp.getContent(book, chapter).let { content -> + val content1 = contentProcessor + .getContent(book, chapter.title, content ?: "null", false, useReplace) + .joinToString("\n") + .replace(chapter.title,"") + + epubBook.addSection( + chapter.title, + ResourceUtil.createHTMLResource(chapter.title, content1) + ) + } + } + } + + private fun setEpubMetadata(book: Book,epubBook: EpubBook) { + val metadata = Metadata() + metadata.titles.add(book.name)//书籍的名称 + metadata.authors.add(Author(book.author))//书籍的作者 + metadata.language = "zh"//数据的语言 + metadata.dates.add(Date())//数据的创建日期 + metadata.publishers.add("Legado APP")//数据的创建者 + metadata.descriptions.add(book.getDisplayIntro())//书籍的简介 + //metadata.subjects.add("")//书籍的主题,在静读天下里面有使用这个分类书籍 + epubBook.metadata=metadata + } + + //////end of EPUB + + } \ No newline at end of file diff --git a/epublib/src/main/java/me/ag2s/epublib/domain/Date.java b/epublib/src/main/java/me/ag2s/epublib/domain/Date.java index b72990bca..8e25aba5d 100644 --- a/epublib/src/main/java/me/ag2s/epublib/domain/Date.java +++ b/epublib/src/main/java/me/ag2s/epublib/domain/Date.java @@ -1,11 +1,11 @@ package me.ag2s.epublib.domain; -import me.ag2s.epublib.epub.PackageDocumentBase; - import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.Locale; +import me.ag2s.epublib.epub.PackageDocumentBase; + /** * A Date used by the book's metadata. *

@@ -48,6 +48,10 @@ public class Date implements Serializable { private Event event; private String dateString; + public Date() { + this(new java.util.Date(), Event.CREATION); + } + public Date(java.util.Date date) { this(date, (Event) null); } diff --git a/epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java b/epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java index ec742c0b0..96212f253 100644 --- a/epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java +++ b/epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java @@ -27,7 +27,12 @@ import me.ag2s.epublib.epub.EpubProcessorSupport; * @author paul */ public class ResourceUtil { - + /** + * 快速创建HTML类型的Resource + * @param title 章节的标题 + * @param string 章节的正文 + * @return 返回Resource + */ public static Resource createHTMLResource(String title, String string) { String html = createHtml(title, string); MediaType mediaTypeProperty = MediaTypes.XHTML; @@ -35,6 +40,14 @@ public class ResourceUtil { return new Resource(data, mediaTypeProperty); } + /** + * 快速创建HTML类型的Resource + * @param title 章节的标题 + * @param string 章节的正文 + * @param href Resource的href + * @return 返回Resource + */ + @SuppressWarnings("unused") public static Resource createHTMLResource(String title, String string, String href) { String html = createHtml(title, string); @@ -65,6 +78,13 @@ public class ResourceUtil { return html; } + /** + * 快速从File创建Resource + * @param file File + * @return + * @throws IOException + */ + @SuppressWarnings("unused") public static Resource createResource(File file) throws IOException { if (file == null) { @@ -77,7 +97,7 @@ public class ResourceUtil { /** - * Creates a resource with as contents a html page with the given title. + * 创建一个只带标题的HTMl类型的Resource,常用于封面页,大卷页 * * @param title v * @param href v