From 7bdab7ade72649ac40b5d15be1b922ea3fb4d74b Mon Sep 17 00:00:00 2001 From: nyne Date: Mon, 21 Apr 2025 20:03:43 +0800 Subject: [PATCH] Add ComicInfo.xml to cbz file. Close #333 --- lib/pages/local_comics_page.dart | 8 +++- lib/utils/cbz.dart | 74 ++++++++++++++++++++++++++------ lib/utils/io.dart | 10 ++--- 3 files changed, 72 insertions(+), 20 deletions(-) diff --git a/lib/pages/local_comics_page.dart b/lib/pages/local_comics_page.dart index e30c64a..22b4dd9 100644 --- a/lib/pages/local_comics_page.dart +++ b/lib/pages/local_comics_page.dart @@ -306,7 +306,8 @@ class _LocalComicsPageState extends State { }); } else { // prevent dirty data - var comic = LocalManager().find(c.id, ComicType.fromKey(c.sourceKey))!; + var comic = + LocalManager().find(c.id, ComicType.fromKey(c.sourceKey))!; comic.read(); } }, @@ -444,7 +445,10 @@ class _LocalComicsPageState extends State { var fileName = ""; // For each comic, export it to a file for (var comic in comics) { - fileName = FilePath.join(cacheDir, sanitizeFileName(comic.title) + ext); + fileName = FilePath.join( + cacheDir, + sanitizeFileName(comic.title, maxLength: 100) + ext, + ); await export(comic, fileName); current++; if (comics.length > 1) { diff --git a/lib/utils/cbz.dart b/lib/utils/cbz.dart index 4710421..3e4b25b 100644 --- a/lib/utils/cbz.dart +++ b/lib/utils/cbz.dart @@ -112,7 +112,7 @@ abstract class CBZ { var ext = e.path.split('.').last; return !['jpg', 'jpeg', 'png', 'webp', 'gif', 'jpe'].contains(ext); }); - if(files.isEmpty) { + if (files.isEmpty) { cache.deleteSync(recursive: true); throw Exception('No images found in the archive'); } @@ -141,8 +141,7 @@ abstract class CBZ { FilePath.join(LocalManager().path, sanitizeFileName(metaData.title)), ); dest.createSync(); - coverFile.copyMem( - FilePath.join(dest.path, 'cover.${coverFile.extension}')); + coverFile.copyMem(FilePath.join(dest.path, 'cover.${coverFile.extension}')); if (metaData.chapters == null) { for (var i = 0; i < files.length; i++) { var src = files[i]; @@ -233,17 +232,19 @@ abstract class CBZ { } } var cover = comic.coverFile; - await cover - .copyMem(FilePath.join(cache.path, 'cover.${cover.path.split('.').last}')); + await cover.copyMem( + FilePath.join(cache.path, 'cover.${cover.path.split('.').last}')); + final metaData = ComicMetaData( + title: comic.title, + author: comic.subtitle, + tags: comic.tags, + chapters: chapters, + ); await File(FilePath.join(cache.path, 'metadata.json')).writeAsString( - jsonEncode( - ComicMetaData( - title: comic.title, - author: comic.subtitle, - tags: comic.tags, - chapters: chapters, - ).toJson(), - ), + jsonEncode(metaData), + ); + await File(FilePath.join(cache.path, 'ComicInfo.xml')).writeAsString( + _buildComicInfoXml(metaData), ); var cbz = File(outFilePath); if (cbz.existsSync()) cbz.deleteSync(); @@ -252,7 +253,54 @@ abstract class CBZ { return cbz; } + static String _buildComicInfoXml(ComicMetaData data) { + final buffer = StringBuffer(); + buffer.writeln(''); + buffer.writeln(''); + + buffer.writeln(' ${_escapeXml(data.title)}'); + buffer.writeln(' ${_escapeXml(data.title)}'); + + if (data.author.isNotEmpty) { + buffer.writeln(' ${_escapeXml(data.author)}'); + } + + if (data.tags.isNotEmpty) { + var tags = data.tags; + if (tags.length > 5) { + tags = tags.sublist(0, 5); + } + buffer.writeln(' ${_escapeXml(tags.join(', '))}'); + } + + if (data.chapters != null && data.chapters!.isNotEmpty) { + final chaptersInfo = data.chapters!.map((chapter) => + '${_escapeXml(chapter.title)}: ${chapter.start}-${chapter.end}' + ).join('; '); + buffer.writeln(' Chapters: $chaptersInfo'); + } + + buffer.writeln(' Unknown'); + buffer.writeln(' Unknown'); + + final now = DateTime.now(); + buffer.writeln(' ${now.year}'); + + buffer.writeln(''); + return buffer.toString(); + } + + static String _escapeXml(String text) { + return text + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); + } + static _compress(String src, String dst) async { await ZipFile.compressFolderAsync(src, dst, 4); } } + diff --git a/lib/utils/io.dart b/lib/utils/io.dart index 288b5a1..3746c97 100644 --- a/lib/utils/io.dart +++ b/lib/utils/io.dart @@ -135,12 +135,12 @@ String sanitizeFileName(String fileName, {String? dir, int? maxLength}) { if (fileName.endsWith('.')) { fileName = fileName.substring(0, fileName.length - 1); } - var maxLength = 255; + var length = maxLength ?? 255; if (dir != null) { if (!dir.endsWith('/') && !dir.endsWith('\\')) { dir = "$dir/"; } - maxLength -= dir.length; + length -= dir.length; } final invalidChars = RegExp(r'[<>:"/\\|?*]'); final sanitizedFileName = fileName.replaceAll(invalidChars, ' '); @@ -148,11 +148,11 @@ String sanitizeFileName(String fileName, {String? dir, int? maxLength}) { if (trimmedFileName.isEmpty) { throw Exception('Invalid File Name: Empty length.'); } - if (maxLength <= 0) { + if (length <= 0) { throw Exception('Invalid File Name: Max length is less than 0.'); } - if (trimmedFileName.length > maxLength) { - trimmedFileName = trimmedFileName.substring(0, maxLength); + if (trimmedFileName.length > length) { + trimmedFileName = trimmedFileName.substring(0, length); } return trimmedFileName; }