diff --git a/internal/services/media_scanner.go b/internal/services/media_scanner.go
index 5a4399d..ce66543 100644
--- a/internal/services/media_scanner.go
+++ b/internal/services/media_scanner.go
@@ -1068,7 +1068,15 @@ func (s *MediaScanner) extractMetadata(path string) (*MediaMetadata, error) {
case ".epub":
metadata, err := s.extractEPUBMetadata(path)
if err != nil {
- return metadata, err
+ // Enhanced format detection for EPUBs
+ isFixedLayout, detectErr := s.DetectFixedLayoutEPUB(path)
+ if detectErr == nil && isFixedLayout {
+ // Override format group for manga EPUBs
+ metadata.FileFormats = []*FormatInfo{{
+ FormatType: "fixed_layout",
+ FilePath: path,
+ MimeType: mimeType,
+ }}
}
// Try to extract embedded cover
coverPath, err := s.extractEPUBCover(path)
@@ -1181,6 +1189,165 @@ func (s *MediaScanner) extractEPUBMetadata(path string) (*MediaMetadata, error)
return metadata, nil
}
+// DetectFixedLayoutEPUB checks if EPUB has fixed-layout (manga) characteristics
+// by examining the OPF file for rendition metadata and content indicators
+func (s *MediaScanner) DetectFixedLayoutEPUB(epubPath string) (bool, error) {
+ // Open EPUB ZIP file
+ r, err := zip.OpenReader(epubPath)
+ if err != nil {
+ return false, fmt.Errorf("failed to open EPUB: %w", err)
+ }
+ defer r.Close()
+
+ // Find and read OPF file
+ var opfFile *zip.File
+ for _, f := range r.File {
+ if strings.HasSuffix(f.Name, ".opf") {
+ opfFile = f
+ break
+ }
+ // Also check in META-INF directory
+ if strings.Contains(f.Name, "META-INF/") && strings.HasSuffix(f.Name, ".opf") {
+ opfFile = f
+ break
+ }
+ }
+
+ if opfFile == nil {
+ return false, fmt.Errorf("OPF file not found in EPUB")
+ }
+
+ // Read OPF content
+ rc, err := opfFile.Open()
+ if err != nil {
+ return false, fmt.Errorf("failed to open OPF: %w", err)
+ }
+ defer rc.Close()
+
+ opfContent, err := io.ReadAll(rc)
+ if err != nil {
+ return false, fmt.Errorf("failed to read OPF: %w", err)
+ }
+
+ // Check for fixed-layout indicators
+ opfString := string(opfContent)
+
+ // Check 1: rendition:layout = pre-paginated (EPUB 3 fixed layout)
+ if strings.Contains(opfString, `rendition:layout">pre-paginated<`) ||
+ strings.Contains(opfString, `rendition:layout="pre-paginated"`) {
+ return true, nil
+ }
+
+ // Check 2: RTL page progression (manga indicator)
+ if strings.Contains(opfString, `page-progression-direction="rtl"`) {
+ return true, nil
+ }
+
+ // Check 3: Image-heavy content (count
tags)
+ // Threshold of 50 images suggests manga/comic vs novel
+ imgCount := strings.Count(opfString, `
50 {
+ return true, nil
+ }
+
+ // Check 4: Manga subject tag
+ lowerOPF := strings.ToLower(opfString)
+ if strings.Contains(lowerOPF, `