Add ComicInfo.xml to cbz file. Close #333

This commit is contained in:
2025-04-21 20:03:43 +08:00
parent ea99e87afb
commit 7bdab7ade7
3 changed files with 72 additions and 20 deletions

View File

@@ -306,7 +306,8 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
}); });
} else { } else {
// prevent dirty data // 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(); comic.read();
} }
}, },
@@ -444,7 +445,10 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
var fileName = ""; var fileName = "";
// For each comic, export it to a file // For each comic, export it to a file
for (var comic in comics) { 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); await export(comic, fileName);
current++; current++;
if (comics.length > 1) { if (comics.length > 1) {

View File

@@ -112,7 +112,7 @@ abstract class CBZ {
var ext = e.path.split('.').last; var ext = e.path.split('.').last;
return !['jpg', 'jpeg', 'png', 'webp', 'gif', 'jpe'].contains(ext); return !['jpg', 'jpeg', 'png', 'webp', 'gif', 'jpe'].contains(ext);
}); });
if(files.isEmpty) { if (files.isEmpty) {
cache.deleteSync(recursive: true); cache.deleteSync(recursive: true);
throw Exception('No images found in the archive'); throw Exception('No images found in the archive');
} }
@@ -141,8 +141,7 @@ abstract class CBZ {
FilePath.join(LocalManager().path, sanitizeFileName(metaData.title)), FilePath.join(LocalManager().path, sanitizeFileName(metaData.title)),
); );
dest.createSync(); dest.createSync();
coverFile.copyMem( coverFile.copyMem(FilePath.join(dest.path, 'cover.${coverFile.extension}'));
FilePath.join(dest.path, 'cover.${coverFile.extension}'));
if (metaData.chapters == null) { if (metaData.chapters == null) {
for (var i = 0; i < files.length; i++) { for (var i = 0; i < files.length; i++) {
var src = files[i]; var src = files[i];
@@ -233,17 +232,19 @@ abstract class CBZ {
} }
} }
var cover = comic.coverFile; var cover = comic.coverFile;
await cover await cover.copyMem(
.copyMem(FilePath.join(cache.path, 'cover.${cover.path.split('.').last}')); 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( await File(FilePath.join(cache.path, 'metadata.json')).writeAsString(
jsonEncode( jsonEncode(metaData),
ComicMetaData( );
title: comic.title, await File(FilePath.join(cache.path, 'ComicInfo.xml')).writeAsString(
author: comic.subtitle, _buildComicInfoXml(metaData),
tags: comic.tags,
chapters: chapters,
).toJson(),
),
); );
var cbz = File(outFilePath); var cbz = File(outFilePath);
if (cbz.existsSync()) cbz.deleteSync(); if (cbz.existsSync()) cbz.deleteSync();
@@ -252,7 +253,54 @@ abstract class CBZ {
return cbz; return cbz;
} }
static String _buildComicInfoXml(ComicMetaData data) {
final buffer = StringBuffer();
buffer.writeln('<?xml version="1.0" encoding="utf-8"?>');
buffer.writeln('<ComicInfo xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">');
buffer.writeln(' <Title>${_escapeXml(data.title)}</Title>');
buffer.writeln(' <Series>${_escapeXml(data.title)}</Series>');
if (data.author.isNotEmpty) {
buffer.writeln(' <Writer>${_escapeXml(data.author)}</Writer>');
}
if (data.tags.isNotEmpty) {
var tags = data.tags;
if (tags.length > 5) {
tags = tags.sublist(0, 5);
}
buffer.writeln(' <Genre>${_escapeXml(tags.join(', '))}</Genre>');
}
if (data.chapters != null && data.chapters!.isNotEmpty) {
final chaptersInfo = data.chapters!.map((chapter) =>
'${_escapeXml(chapter.title)}: ${chapter.start}-${chapter.end}'
).join('; ');
buffer.writeln(' <Notes>Chapters: $chaptersInfo</Notes>');
}
buffer.writeln(' <Manga>Unknown</Manga>');
buffer.writeln(' <BlackAndWhite>Unknown</BlackAndWhite>');
final now = DateTime.now();
buffer.writeln(' <Year>${now.year}</Year>');
buffer.writeln('</ComicInfo>');
return buffer.toString();
}
static String _escapeXml(String text) {
return text
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&apos;');
}
static _compress(String src, String dst) async { static _compress(String src, String dst) async {
await ZipFile.compressFolderAsync(src, dst, 4); await ZipFile.compressFolderAsync(src, dst, 4);
} }
} }

View File

@@ -135,12 +135,12 @@ String sanitizeFileName(String fileName, {String? dir, int? maxLength}) {
if (fileName.endsWith('.')) { if (fileName.endsWith('.')) {
fileName = fileName.substring(0, fileName.length - 1); fileName = fileName.substring(0, fileName.length - 1);
} }
var maxLength = 255; var length = maxLength ?? 255;
if (dir != null) { if (dir != null) {
if (!dir.endsWith('/') && !dir.endsWith('\\')) { if (!dir.endsWith('/') && !dir.endsWith('\\')) {
dir = "$dir/"; dir = "$dir/";
} }
maxLength -= dir.length; length -= dir.length;
} }
final invalidChars = RegExp(r'[<>:"/\\|?*]'); final invalidChars = RegExp(r'[<>:"/\\|?*]');
final sanitizedFileName = fileName.replaceAll(invalidChars, ' '); final sanitizedFileName = fileName.replaceAll(invalidChars, ' ');
@@ -148,11 +148,11 @@ String sanitizeFileName(String fileName, {String? dir, int? maxLength}) {
if (trimmedFileName.isEmpty) { if (trimmedFileName.isEmpty) {
throw Exception('Invalid File Name: Empty length.'); throw Exception('Invalid File Name: Empty length.');
} }
if (maxLength <= 0) { if (length <= 0) {
throw Exception('Invalid File Name: Max length is less than 0.'); throw Exception('Invalid File Name: Max length is less than 0.');
} }
if (trimmedFileName.length > maxLength) { if (trimmedFileName.length > length) {
trimmedFileName = trimmedFileName.substring(0, maxLength); trimmedFileName = trimmedFileName.substring(0, length);
} }
return trimmedFileName; return trimmedFileName;
} }