Skip to content

Commit

Permalink
Feat: export epub
Browse files Browse the repository at this point in the history
  • Loading branch information
honjow committed Oct 11, 2021
1 parent 7f4b5e0 commit be944d4
Show file tree
Hide file tree
Showing 10 changed files with 312 additions and 6 deletions.
6 changes: 6 additions & 0 deletions assets/templates/epub/META-INF/container.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="OEBPS/metadata.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>
13 changes: 13 additions & 0 deletions assets/templates/epub/OEBPS/content/index.html.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>{{ title }}</title>
<link href="resources/index_0.css" type="text/css" charset="UTF-8" rel="stylesheet"/>
</head>
<body>
<div>
{# <p><img src="resources/{{ fileName }}" width="{{ width }}" height="{{ height }}"/></p> #}
<p><img src="resources/{{ fileName }}" /></p>
</div>
</body>
</html>
36 changes: 36 additions & 0 deletions assets/templates/epub/OEBPS/content/resources/index_0.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
body {
text-align: left;
text-indent: 0em;
margin-top: 0;
margin-bottom: 0;
margin-left: 0;
margin-right: 0;
Padding-top: 0;
Padding-bottom: 0;
Padding-left: 0;
Padding-right: 0;
}
p {
text-align: left;
text-indent: 0em;
margin-top: 0;
margin-bottom: 0;
margin-left: 0;
margin-right: 0;
Padding-top: 0;
Padding-bottom: 0;
Padding-left: 0;
Padding-right: 0;
}
div {
text-align: left;
text-indent: 0em;
margin-top: 0;
margin-bottom: 0;
margin-left: 0;
margin-right: 0;
Padding-top: 0;
Padding-bottom: 0;
Padding-left: 0;
Padding-right: 0;
}
33 changes: 33 additions & 0 deletions assets/templates/epub/OEBPS/metadata.xml.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="calibre_id">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xmlns:Txt2ePub="http://no722.cocolog-nifty.com/blog/metadata">
<dc:title>{{ title }}</dc:title>
<dc:creator opf:role="aut" opf:file-as="e-hentai.org">e-hentai.org</dc:creator>
{# <dc:contributor opf:role="bkp" opf:file-as="ChainLP">ChainLP (0.0.40.17) [http://no722.cocolog-nifty.com/blog/]</dc:contributor> #}
{# <dc:date>2021-07-30T05:37:00+00:00</dc:date> #}
<dc:publisher>e-hentai.org</dc:publisher>
<dc:language>{{ language }}</dc:language>
{# <dc:identifier opf:scheme="CHAINLP">254ce15d-dfdf-4fa0-bba0-9dbd232c7854</dc:identifier> #}
<meta content="true" name="fixed-layout"/>
<meta content="comic" name="book-type"/>
<meta name="Txt2ePub:series_index" content="1"/>
<meta name="calibre:title_sort" content="{{ title }}"/>
<meta name="cover" content="cover"/>
</metadata>
<manifest>
{%- for fileName in fileNameList %}
<item id="index{{ fileName.withoutExtension }}" href="content/index_{{ fileName.withoutExtension }}.xhtml" media-type="application/xhtml+xml"/>
{%- endfor %}
{%- for fileName in fileNameList %}
<item id="img_{{ fileName.withoutExtension }}" href="content/resources/{{ fileName.name }}" media-type="image/{{ fileName.extension }}"/>
{%- endfor %}
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/>
<item id="idcss" href="content/resources/index_0.css" media-type="text/css"/>
<item id="cover" href="content/resources/{{ coverName }}" media-type="image/{{ coverExtension }}"/>
</manifest>
<spine>
{%- for fileName in fileNameList %}
<itemref idref="index{{ fileName.withoutExtension }}"/>
{%- endfor %}
</spine>
</package>
14 changes: 14 additions & 0 deletions assets/templates/epub/OEBPS/toc.ncx.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="jp">
<head>
<meta name="dtb:uid" content="{{ uid }}"/>
<meta name="dtb:depth" content="1"/>
<meta name="dtb:generator" content="Txt2ePub"/>
<meta name="dtb:totalPageCount" content="0"/>
<meta name="dtb:maxPageNumber" content="0"/>
</head>
<docTitle><text>Table of Contents</text></docTitle>
<navMap>
</navMap>
</ncx>
1 change: 1 addition & 0 deletions assets/templates/epub/mimetype
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
application/epub+zip
127 changes: 127 additions & 0 deletions lib/common/epub/epub_builder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import 'dart:io';

import 'package:fehviewer/store/floor/entity/gallery_task.dart';
import 'package:fehviewer/utils/logger.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:jinja/jinja.dart';
import 'package:path/path.dart' as path;

import '../global.dart';

Future<String> buildEpub(GalleryTask task, {String? tempPath}) async {
final _tempPath = tempPath ?? path.join(Global.tempPath, 'export_epub_temp');

Directory _tempDir = Directory(_tempPath);
if (_tempDir.existsSync()) {
_tempDir.deleteSync(recursive: true);
}
_tempDir.createSync(recursive: true);

final metaPath = path.join(_tempPath, 'META-INF');
_creatDir(metaPath);

// OEBPS 目录创建
final oebpsPath = path.join(_tempPath, 'OEBPS');
_creatDir(oebpsPath);

final contentPath = path.join(oebpsPath, 'content');
_creatDir(contentPath);

final resourcesPath = path.join(contentPath, 'resources');
_creatDir(resourcesPath);

// mimetype
final fileMimetype = File(path.join(_tempPath, 'mimetype'));
fileMimetype.writeAsStringSync('application/epub+zip\n');

// css 创建
final cssText = await rootBundle
.loadString('assets/templates/epub/OEBPS/content/resources/index_0.css');
final fileCss = File(path.join(resourcesPath, 'index_0.css'));
fileCss.writeAsStringSync(cssText);

// container
final containerText = await rootBundle
.loadString('assets/templates/epub/META-INF/container.xml');
final fileContainer = File(path.join(metaPath, 'container.xml'));
fileContainer.writeAsStringSync(containerText);

// 图片复制到 resourcesPath
final _galleryDir = Directory(task.dirPath!);
final _fileList = _galleryDir.listSync();
for (final _file in _fileList) {
if ((await FileSystemEntity.type(_file.path)) ==
FileSystemEntityType.file) {
final srcFile = File(_file.path);
srcFile.copySync(path.join(resourcesPath, path.basename(srcFile.path)));
}
}

// 模板操作
final environment = Environment();

// index 模板内容读取
final indexTemplateText = await rootBundle
.loadString('assets/templates/epub/OEBPS/content/index.html.tpl');
final indexTemplate = environment.fromString(indexTemplateText);

// metadata 模板内容读取
final metadataTemplateText = await rootBundle
.loadString('assets/templates/epub/OEBPS/metadata.xml.tpl');
final metadataTemplate = environment.fromString(metadataTemplateText);

// toc 模板内容读取
final tocTemplateText =
await rootBundle.loadString('assets/templates/epub/OEBPS/toc.ncx.tpl');
final tocTemplate = environment.fromString(tocTemplateText);

final _fileNameList = _fileList
.map(
(e) => {
'withoutExtension': path.basenameWithoutExtension(e.path),
'name': path.basename(e.path),
'extension': path.extension(e.path) == '.jpg'
? 'jpeg'
: path.extension(e.path).split('.').last.toLowerCase(),
},
)
.toList()
..sort((a, b) => a['name']!.compareTo(b['name']!));

// 写入index
for (final _imgFile in _fileNameList) {
final _xhtml = indexTemplate.render(
fileName: _imgFile['name'],
);
final _xhtmlFile = File(
path.join(contentPath, 'index_${_imgFile['withoutExtension']}.xhtml'));
_xhtmlFile.createSync(recursive: true);
_xhtmlFile.writeAsStringSync('$_xhtml');
}

// 写入 metadata.opf
final metadata = metadataTemplate.render(
title: task.title,
fileNameList: _fileNameList,
coverName: _fileNameList.first['name'],
coverExtension: _fileNameList.first['extension'],
);
final metadataFile = File(path.join(oebpsPath, 'metadata.opf'));
metadataFile.createSync();
metadataFile.writeAsStringSync('$metadata');

// 写入 toc.ncx
final toc = tocTemplate.render();
final tocFile = File(path.join(oebpsPath, 'toc.ncx'));
tocFile.createSync();
tocFile.writeAsStringSync('$toc');

return _tempPath;
}

void _creatDir(String path) {
final _dir = Directory(path);
if (!_dir.existsSync()) {
_dir.createSync(recursive: true);
}
}
2 changes: 1 addition & 1 deletion lib/pages/item/download_gallery_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Future<int?> syncReadProgress(
Future<int?> _showSyncDialog() async {
return await showCupertinoDialog<int?>(
context: context,
barrierDismissible: true,
// barrierDismissible: true,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text('${L10n.of(context).sync_read_progress}...'),
Expand Down
82 changes: 77 additions & 5 deletions lib/pages/tab/controller/download_view_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:fehviewer/common/controller/archiver_download_controller.dart';
import 'package:fehviewer/common/controller/download_controller.dart';
import 'package:fehviewer/common/epub/epub_builder.dart';
import 'package:fehviewer/common/global.dart';
import 'package:fehviewer/common/isolate_download/download_manager.dart';
import 'package:fehviewer/generated/l10n.dart';
Expand All @@ -24,6 +25,8 @@ enum DownloadType {
archiver,
}

typedef FunctionExport = Future Function();

const String idDownloadGalleryView = 'DownloadGalleryView';
const String idDownloadArchiverView = 'DownloadArchiverView';
const String idDownloadGalleryItem = 'DownloadGalleryItem';
Expand Down Expand Up @@ -242,7 +245,8 @@ class DownloadViewController extends GetxController {
onPressed: () {
logger.d('导出zip');
Get.back();
_exportZip(task);

_exportZip(context, task);
},
child: const Text(
'ZIP',
Expand All @@ -253,7 +257,7 @@ class DownloadViewController extends GetxController {
onPressed: () {
logger.d('导出epub');
Get.back();
_exportEpub(task);
_exportEpub(context, task);
},
child: const Text(
'EPUB',
Expand All @@ -264,13 +268,13 @@ class DownloadViewController extends GetxController {
});
}

Future<String?> _exportZip(GalleryTask task) async {
Future<String?> _exportZip(BuildContext context, GalleryTask task) async {
logger.d('export zip , dir path ${task.dirPath}');
if (task.dirPath == null) {
return null;
}

final _zipPath = await _compZip(task);
final _zipPath = await exportGallery(context, () => _compZip(task));

if (_zipPath != null) {
Share.shareFiles([_zipPath]);
Expand Down Expand Up @@ -307,7 +311,33 @@ class DownloadViewController extends GetxController {
return _zipPath;
}

Future<String?> _exportEpub(GalleryTask task) async {}
Future<String?> _exportEpub(BuildContext context, GalleryTask task) async {
logger.d('export epub , dir path ${task.dirPath}');
if (task.dirPath == null) {
return null;
}

final _exportFilePath =
await exportGallery(context, () => _buildEpub(task));

if (_exportFilePath != null) {
Share.shareFiles([_exportFilePath]);
}
}

Future<String?> _buildEpub(GalleryTask task) async {
final _tempPath = await buildEpub(task);

// 打包epub文件
final encoder = ZipFileEncoder();
final _epubPath =
path.join(Global.tempPath, 'epub', '${task.gid}_${task.title}.epub');
encoder.create(_epubPath);
encoder.addDirectory(Directory(_tempPath), includeDirName: false);
encoder.close();

return _epubPath;
}

// gallery 暂停任务
void pauseGalleryDownload(int? gid) {
Expand All @@ -321,3 +351,45 @@ class DownloadViewController extends GetxController {
update(['${idDownloadGalleryItem}_$gid']);
}
}

Future<String?> exportGallery(
BuildContext context,
FunctionExport funExport, {
FunctionExport? funcCancel,
}) async {
Future<String?> _showExportDialog() async {
return await showCupertinoDialog<String?>(
context: context,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text(L10n.of(context).processing),
content: GetBuilder<DownloadViewController>(
initState: (state) async {
final _path = await funExport();
Get.back(result: _path);
},
builder: (logic) {
return const CupertinoActivityIndicator(radius: 16);
},
).paddingOnly(top: 10.0),
actions: [
CupertinoDialogAction(
child: Text(
L10n.of(context).cancel,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: CupertinoColors.destructiveRed),
),
onPressed: () {
funcCancel?.call();
Get.back();
},
),
],
);
},
);
}

return await _showExportDialog();
}
4 changes: 4 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,7 @@ flutter:

assets:
- assets/
- assets/templates/epub/META-INF/
- assets/templates/epub/OEBPS/
- assets/templates/epub/OEBPS/content/
- assets/templates/epub/OEBPS/content/resources/

0 comments on commit be944d4

Please sign in to comment.