Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Newdevlatest #32

Open
wants to merge 26 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
86892a2
Upgrade dependencies
SergeShkurko Oct 23, 2021
6f9e995
Squashed commit of the following:
SergeShkurko Oct 23, 2021
ddd441e
Update example
SergeShkurko Oct 23, 2021
677cb57
Added epub v3 support
SergeShkurko Oct 23, 2021
e1dfe2c
Support split epub chapters
jasmeet0817 Apr 18, 2024
0df2de8
Fix merge issues
jasmeet0817 Apr 18, 2024
4b13a57
Formatting fix
jasmeet0817 Apr 18, 2024
0fc3d66
Merge branch 'ScerIO:dev' into dev
jasmeet0817 Nov 6, 2024
9279e17
Fix a bug in split epub chapters
jasmeet0817 Nov 9, 2024
c3b997c
Check if the book has chapters split into files before merging splits
jasmeet0817 Nov 10, 2024
3b0b996
Decode file name exception should not cause exception
jasmeet0817 Nov 27, 2024
b5e78bd
Deterministic hash in epubx
jasmeet0817 Jul 7, 2024
d1f2f8c
Merge with latest
jasmeet0817 Nov 27, 2024
f62b4ce
Epub v3 fixes
jasmeet0817 Nov 28, 2024
bb865c5
Compress images
jasmeet0817 Nov 28, 2024
9b7f0f5
If you can't compress, don't
jasmeet0817 Nov 28, 2024
9af6224
Epub v3 nav fix
jasmeet0817 Nov 29, 2024
cbd96e6
Yet another tocEntryPath fix
jasmeet0817 Nov 29, 2024
ac39e06
A better Book cover reader
jasmeet0817 Nov 29, 2024
f6f13bf
25% quality for images
jasmeet0817 Dec 9, 2024
b478875
Adaptive immage compression based on book size
jasmeet0817 Dec 23, 2024
bd87b3a
Export book_size
jasmeet0817 Dec 29, 2024
039ec51
Don't throw exception if files are not found
jasmeet0817 Dec 29, 2024
8a5b931
No more image compression logic
jasmeet0817 Dec 29, 2024
6c19fe6
Cleanup
jasmeet0817 Dec 29, 2024
2891f17
cleanup
jasmeet0817 Dec 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .flutter-plugins
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# This is a generated file; do not edit or check into version control.
flutter_image_compress=/home/jesse/.pub-cache/hosted/pub.dev/flutter_image_compress-2.3.0/
flutter_image_compress_common=/home/jesse/.pub-cache/hosted/pub.dev/flutter_image_compress_common-1.0.5/
flutter_image_compress_macos=/home/jesse/.pub-cache/hosted/pub.dev/flutter_image_compress_macos-1.0.2/
flutter_image_compress_ohos=/home/jesse/.pub-cache/hosted/pub.dev/flutter_image_compress_ohos-0.0.3/
flutter_image_compress_web=/home/jesse/.pub-cache/hosted/pub.dev/flutter_image_compress_web-0.1.4+1/
1 change: 1 addition & 0 deletions .flutter-plugins-dependencies
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_image_compress_common","path":"/home/jesse/.pub-cache/hosted/pub.dev/flutter_image_compress_common-1.0.5/","native_build":true,"dependencies":[]}],"android":[{"name":"flutter_image_compress_common","path":"/home/jesse/.pub-cache/hosted/pub.dev/flutter_image_compress_common-1.0.5/","native_build":true,"dependencies":[]}],"macos":[{"name":"flutter_image_compress_macos","path":"/home/jesse/.pub-cache/hosted/pub.dev/flutter_image_compress_macos-1.0.2/","native_build":true,"dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"flutter_image_compress_web","path":"/home/jesse/.pub-cache/hosted/pub.dev/flutter_image_compress_web-0.1.4+1/","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_image_compress","dependencies":["flutter_image_compress_common","flutter_image_compress_web","flutter_image_compress_macos","flutter_image_compress_ohos"]},{"name":"flutter_image_compress_common","dependencies":[]},{"name":"flutter_image_compress_macos","dependencies":[]},{"name":"flutter_image_compress_ohos","dependencies":[]},{"name":"flutter_image_compress_web","dependencies":[]}],"date_created":"2024-12-23 10:21:31.746835","version":"3.24.3","swift_package_manager_enabled":false}
24 changes: 12 additions & 12 deletions lib/src/epub_reader.dart
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
import 'dart:async';

import 'package:archive/archive.dart';
import 'package:epubx/epubx.dart';

import 'entities/epub_book.dart';
import 'entities/epub_byte_content_file.dart';
import 'entities/epub_chapter.dart';
import 'entities/epub_content.dart';
import 'entities/epub_content_file.dart';
import 'entities/epub_text_content_file.dart';
import 'readers/content_reader.dart';
import 'readers/schema_reader.dart';
import 'ref_entities/epub_book_ref.dart';
import 'ref_entities/epub_byte_content_file_ref.dart';
import 'ref_entities/epub_chapter_ref.dart';
import 'ref_entities/epub_content_file_ref.dart';
import 'ref_entities/epub_content_ref.dart';
import 'ref_entities/epub_text_content_file_ref.dart';
import 'schema/opf/epub_metadata_creator.dart';

/// A class that provides the primary interface to read Epub files.
///
Expand Down Expand Up @@ -119,8 +111,12 @@ class EpubReader {

await Future.forEach(contentRef.AllFiles!.keys, (dynamic key) async {
if (!result.AllFiles!.containsKey(key)) {
result.AllFiles![key] =
await readByteContentFile(contentRef.AllFiles![key]!);
try {
result.AllFiles![key] =
await readByteContentFile(contentRef.AllFiles![key]!);
} catch (FileNotFoundException) {
// Do nothing, let the file be missing.
}
}
});

Expand All @@ -147,7 +143,11 @@ class EpubReader {
Map<String, EpubByteContentFileRef> byteContentFileRefs) async {
var result = <String, EpubByteContentFile>{};
await Future.forEach(byteContentFileRefs.keys, (dynamic key) async {
result[key] = await readByteContentFile(byteContentFileRefs[key]!);
try {
result[key] = await readByteContentFile(byteContentFileRefs[key]!);
} catch (FileNotFoundException) {
// Do nothing, let the file be missing.
}
});
return result;
}
Expand Down
62 changes: 45 additions & 17 deletions lib/src/readers/book_cover_reader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,73 @@ import 'dart:typed_data';

import 'package:collection/collection.dart' show IterableExtension;
import 'package:image/image.dart' as images;
import 'package:logger/logger.dart';

import '../ref_entities/epub_book_ref.dart';
import '../ref_entities/epub_byte_content_file_ref.dart';
import '../schema/opf/epub_manifest_item.dart';
import '../schema/opf/epub_metadata_meta.dart';

class BookCoverReader {
static final logger = Logger();

static Future<images.Image?> readBookCover(EpubBookRef bookRef) async {
var metaItems = bookRef.Schema!.Package!.Metadata!.MetaItems;
if (metaItems == null || metaItems.isEmpty) return null;

var coverMetaItem = metaItems.firstWhereOrNull(
(EpubMetadataMeta metaItem) =>
metaItem.Name != null && metaItem.Name!.toLowerCase() == 'cover');
if (coverMetaItem == null) return null;
if (coverMetaItem.Content == null || coverMetaItem.Content!.isEmpty) {
throw Exception(
'Incorrect EPUB metadata: cover item content is missing.');
var coverManifestId;
var coverManifestSearchId = 'cover';
if (coverMetaItem != null &&
coverMetaItem.Content != null &&
coverMetaItem.Content!.isNotEmpty) {
coverManifestId = coverMetaItem.Content!.toLowerCase();
} else {
logger.e('Cover id is not in manifest.');
}

var coverManifestItem = bookRef.Schema!.Package!.Manifest!.Items!
.firstWhereOrNull((EpubManifestItem manifestItem) =>
manifestItem.Id!.toLowerCase() ==
coverMetaItem.Content!.toLowerCase());
var coverManifestItem;
if (coverManifestId != null) {
coverManifestItem = bookRef.Schema!.Package!.Manifest!.Items!
.firstWhereOrNull((EpubManifestItem manifestItem) =>
manifestItem.Id!.toLowerCase() == coverManifestId);
}
// If manifest item with cover id is not found, search for item with text "cover" in id
if (coverManifestItem == null) {
throw Exception(
'Incorrect EPUB manifest: item with ID = \"${coverMetaItem.Content}\" is missing.');
var coverManifestItems = bookRef.Schema!.Package!.Manifest!.Items!
.where((EpubManifestItem manifestItem) =>
manifestItem.Id!.toLowerCase().contains(coverManifestSearchId))
.toList();
if (coverManifestItems.length == 1) {
coverManifestItem = coverManifestItems.first;
} else {
coverManifestItem = coverManifestItems.firstWhereOrNull(
(EpubManifestItem manifestItem) =>
manifestItem.MediaType?.contains('image') ?? false);
}
}
if (coverManifestItem == null) {
logger.e(
'Incorrect EPUB manifest: item with ID = \"$coverManifestId\" is missing.');
return null;
}

EpubByteContentFileRef? coverImageContentFileRef;
if (!bookRef.Content!.Images!.containsKey(coverManifestItem.Href)) {
throw Exception(
logger.e(
'Incorrect EPUB manifest: item with href = \"${coverManifestItem.Href}\" is missing.');
return null;
}

coverImageContentFileRef = bookRef.Content!.Images![coverManifestItem.Href];
var coverImageContent =
await coverImageContentFileRef!.readContentAsBytes();
var retval = images.decodeImage(Uint8List.fromList(coverImageContent));
return retval;
try {
var coverImageContent =
await coverImageContentFileRef!.readContentAsBytes();
var retval = images.decodeImage(Uint8List.fromList(coverImageContent));
return retval;
} catch (e) {
logger.e('Error reading cover image content: $e');
return null;
}
}
}
111 changes: 90 additions & 21 deletions lib/src/readers/chapter_reader.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:epubx/src/utils/file_name_decoder.dart';

import '../ref_entities/epub_book_ref.dart';
import '../ref_entities/epub_chapter_ref.dart';
import '../ref_entities/epub_text_content_file_ref.dart';
Expand All @@ -8,18 +10,26 @@ class ChapterReader {
if (bookRef.Schema!.Navigation == null) {
return <EpubChapterRef>[];
}
return getChaptersImpl(
bookRef, bookRef.Schema!.Navigation!.NavMap!.Points!);
var navigationPoints = bookRef.Schema!.Navigation!.NavMap!.Points!;
var navigationFileNames = getAllNavigationFileNames(navigationPoints);
var unmappedChapters = getUnmappedChapters(bookRef, navigationFileNames);
var hasChapterSplittingIntoFiles =
hasChapterSplittingInFiles(navigationFileNames);
return getChaptersImpl(bookRef, navigationPoints, unmappedChapters,
hasChapterSplittingIntoFiles);
}

static List<EpubChapterRef> getChaptersImpl(
EpubBookRef bookRef, List<EpubNavigationPoint> navigationPoints) {
EpubBookRef bookRef,
List<EpubNavigationPoint> navigationPoints,
List<String> unmappedChapters,
bool hasChapterSplittingIntoFiles,
) {
var result = <EpubChapterRef>[];
// navigationPoints.forEach((EpubNavigationPoint navigationPoint) {
for (var navigationPoint in navigationPoints){
for (var navigationPoint in navigationPoints) {
String? contentFileName;
String? anchor;
if (navigationPoint.Content?.Source ==null) continue;
if (navigationPoint.Content?.Source == null) continue;
var contentSourceAnchorCharIndex =
navigationPoint.Content!.Source!.indexOf('#');
if (contentSourceAnchorCharIndex == -1) {
Expand All @@ -31,7 +41,7 @@ class ChapterReader {
anchor = navigationPoint.Content!.Source!
.substring(contentSourceAnchorCharIndex + 1);
}
contentFileName = Uri.decodeFull(contentFileName!);
contentFileName = decodeFileName(contentFileName!);
EpubTextContentFileRef? htmlContentFileRef;
if (!bookRef.Content!.Html!.containsKey(contentFileName)) {
throw Exception(
Expand All @@ -43,23 +53,82 @@ class ChapterReader {
chapterRef.ContentFileName = contentFileName;
chapterRef.Anchor = anchor;
chapterRef.Title = navigationPoint.NavigationLabels!.first.Text;
chapterRef.SubChapters =
getChaptersImpl(bookRef, navigationPoint.ChildNavigationPoints!);
if(chapterRef.ContentFileName!.contains('_split_')) {
var fileNamePart = chapterRef.ContentFileName!.split('_split_')[0];
for (var fileName in bookRef.Content!.Html!.keys) {
if(fileName.contains(fileNamePart)) {
if (fileName == contentFileName) {
continue;
}
chapterRef.otherTextContentFileRefs.add(bookRef.Content!.Html![fileName]!);
chapterRef.OtherContentFileNames.add(fileName);
}
}
chapterRef.SubChapters = getChaptersImpl(
bookRef,
navigationPoint.ChildNavigationPoints!,
unmappedChapters,
hasChapterSplittingIntoFiles);
if (hasChapterSplittingIntoFiles) {
addSplitChaptersToRef(bookRef, chapterRef, unmappedChapters);
}

result.add(chapterRef);
};
}
;
return result;
}

static List<String> getAllNavigationFileNames(
List<EpubNavigationPoint> points) {
var result = <String>[];
for (var point in points) {
if (point.Content?.Source != null) {
result.add(point.Content!.Source!);
}
result
.addAll(getAllNavigationFileNames(point.ChildNavigationPoints ?? []));
}
return result;
}

/// Sometimes chapters are split into multiple files,
/// but the split files are not listed in the navigation.
/// We need to find these files and add them to the chapter as [OtherContentFileNames].
static List<String> getUnmappedChapters(
EpubBookRef bookRef, List<String> navigationFileNames) {
var allFileNames = Set<String>.from(bookRef.Content!.Html!.keys);
return allFileNames.difference(navigationFileNames.toSet()).toList();
}

/// This checks if the chapters are split into multiple files by
/// 1. Checking if the file names contain "_split_".
/// 2. Two chapters listed in the navigation file should not have the same file name part before "_split_".
static bool hasChapterSplittingInFiles(List<String> navigationFileNames) {
var uniqueFileNameParts = <String>{};
for (var fileName in navigationFileNames) {
if (fileName.contains('_split_')) {
var baseName = fileName.split('_split_')[0];
if (uniqueFileNameParts.contains(baseName)) {
return false;
}
uniqueFileNameParts.add(baseName);
}
}
return uniqueFileNameParts.isNotEmpty;
}

static void addSplitChaptersToRef(
EpubBookRef bookRef,
EpubChapterRef chapterRef,
List<String> unmappedChapters,
) {
if (!chapterRef.ContentFileName!.contains('_split_')) {
return;
}

var baseName = chapterRef.ContentFileName!.split('_split_')[0];
var addedChapters = <String>[]; // List to store items for removal

for (var fileName in unmappedChapters) {
if (fileName.contains(baseName) &&
fileName != chapterRef.ContentFileName) {
chapterRef.otherTextContentFileRefs
.add(bookRef.Content!.Html![fileName]!);
chapterRef.OtherContentFileNames.add(fileName);
addedChapters.add(fileName); // Add to removal list
}
}
unmappedChapters
.removeWhere((fileName) => addedChapters.contains(fileName));
}
}
6 changes: 4 additions & 2 deletions lib/src/readers/content_reader.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:epubx/src/utils/file_name_decoder.dart';

import '../entities/epub_content_type.dart';
import '../ref_entities/epub_book_ref.dart';
import '../ref_entities/epub_byte_content_file_ref.dart';
Expand Down Expand Up @@ -30,7 +32,7 @@ class ContentReader {
case EpubContentType.DTBOOK_NCX:
var epubTextContentFile = EpubTextContentFileRef(bookRef);
{
epubTextContentFile.FileName = Uri.decodeFull(fileName!);
epubTextContentFile.FileName = decodeFileName(fileName!);
epubTextContentFile.ContentMimeType = contentMimeType;
epubTextContentFile.ContentType = contentType;
}
Expand Down Expand Up @@ -62,7 +64,7 @@ class ContentReader {
default:
var epubByteContentFile = EpubByteContentFileRef(bookRef);
{
epubByteContentFile.FileName = Uri.decodeFull(fileName!);
epubByteContentFile.FileName = decodeFileName(fileName!);
epubByteContentFile.ContentMimeType = contentMimeType;
epubByteContentFile.ContentType = contentType;
}
Expand Down
Loading