import 'dart:isolate'; import 'package:uuid/uuid.dart'; import 'package:venera/foundation/app.dart'; import 'package:venera/foundation/local.dart'; import 'package:venera/utils/file_type.dart'; import 'package:venera/utils/io.dart'; import 'package:zip_flutter/zip_flutter.dart'; class EpubData { final String title; final String author; final File cover; final Map> chapters; const EpubData({ required this.title, required this.author, required this.cover, required this.chapters, }); } Future createEpubComic( EpubData data, String cacheDir, String outFilePath) async { final workingDir = Directory(FilePath.join(cacheDir, 'epub')); if (workingDir.existsSync()) { workingDir.deleteSync(recursive: true); } workingDir.createSync(recursive: true); // mimetype workingDir.joinFile('mimetype').writeAsStringSync('application/epub+zip'); // META-INF Directory(FilePath.join(workingDir.path, 'META-INF')).createSync(); File(FilePath.join(workingDir.path, 'META-INF', 'container.xml')) .writeAsStringSync(''' '''); Directory(FilePath.join(workingDir.path, 'OEBPS')).createSync(); // copy images, create html files final imageDir = Directory(FilePath.join(workingDir.path, 'OEBPS', 'images')); imageDir.createSync(); final coverExt = data.cover.extension; final coverMime = FileType.fromExtension(coverExt).mime; imageDir .joinFile('cover.$coverExt') .writeAsBytesSync(data.cover.readAsBytesSync()); int imgIndex = 0; int chapterIndex = 0; var manifestStrBuilder = StringBuffer(); manifestStrBuilder.writeln( ' '); manifestStrBuilder.writeln( ' '); for (final chapter in data.chapters.keys) { var images = []; for (final image in data.chapters[chapter]!) { final ext = image.extension; imageDir .joinFile('img$imgIndex.$ext') .writeAsBytesSync(image.readAsBytesSync()); images.add('images/img$imgIndex.$ext'); var mime = FileType.fromExtension(ext).mime; manifestStrBuilder.writeln( ' '); imgIndex++; } var html = File(FilePath.join(workingDir.path, 'OEBPS', '$chapterIndex.html')); html.writeAsStringSync(''' $chapter

$chapter

${images.map((e) => ' $e').join('\n')}
'''); manifestStrBuilder.writeln( ' '); chapterIndex++; } // content.opf final contentOpf = File(FilePath.join(workingDir.path, 'content.opf')); final uuid = const Uuid().v4(); var spineStrBuilder = StringBuffer(); for (var i = 0; i < chapterIndex; i++) { var idRef = 'idref="chapter$i"'; spineStrBuilder.writeln(' '); } contentOpf.writeAsStringSync(''' ${data.title} ${data.author} urn:uuid:$uuid ${manifestStrBuilder.toString()} ${spineStrBuilder.toString()} '''); // toc.ncx final tocNcx = File(FilePath.join(workingDir.path, 'toc.ncx')); var navMapStrBuilder = StringBuffer(); var playOrder = 2; final chapterNames = data.chapters.keys.toList(); for (var i = 0; i < chapterIndex; i++) { navMapStrBuilder .writeln(' '); navMapStrBuilder.writeln( ' ${chapterNames[i]}'); navMapStrBuilder.writeln(' '); navMapStrBuilder.writeln(' '); playOrder++; } tocNcx.writeAsStringSync(''' ${data.title} ${navMapStrBuilder.toString()} '''); ZipFile.compressFolder(workingDir.path, outFilePath); workingDir.deleteSync(recursive: true); return File(outFilePath); } Future createEpubWithLocalComic( LocalComic comic, String outFilePath) async { var chapters = >{}; if (comic.chapters == null) { chapters[comic.title] = (await LocalManager().getImages(comic.id, comic.comicType, 0)) .map((e) => File(e)) .toList(); } else { for (var chapter in comic.downloadedChapters) { chapters[comic.chapters![chapter]!] = (await LocalManager().getImages(comic.id, comic.comicType, chapter)) .map((e) => File(e)) .toList(); } } var data = EpubData( title: comic.title, author: comic.subtitle, cover: comic.coverFile, chapters: chapters, ); final cacheDir = App.cachePath; return Isolate.run(() => overrideIO(() async { return createEpubComic(data, cacheDir, outFilePath); })); }