From c337b46bef4482d73691416f9fe06021cd1707e0 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 19 Apr 2026 14:27:24 -0400 Subject: [PATCH] chore: remove completed MANGA_EPUB_IMPLEMENTATION doc The manga/EPUB implementation plan has been fully executed. Remove the tracking document since all described features are now in place. --- MANGA_EPUB_IMPLEMENTATION.md | 1051 ---------------------------------- 1 file changed, 1051 deletions(-) delete mode 100644 MANGA_EPUB_IMPLEMENTATION.md diff --git a/MANGA_EPUB_IMPLEMENTATION.md b/MANGA_EPUB_IMPLEMENTATION.md deleted file mode 100644 index b44040e..0000000 --- a/MANGA_EPUB_IMPLEMENTATION.md +++ /dev/null @@ -1,1051 +0,0 @@ - - - -# Manga EPUB and Panel Detection Implementation - -**Status:** Ready to implement -**Created:** April 12, 2026 -**Purpose:** Enable manga EPUBs in manga library, filter by fixed-layout, and implement universal panel detection - ---- - -## Table of Contents - -1. [Phase 1: Database Schema Changes](#phase-1-database-schema-changes) -2. [Phase 2: Database Query Additions](#phase-2-database-query-additions) -3. [Phase 3: Scanner Service Enhancements](#phase-3-scanner-service-enhancements) -4. [Phase 4: New Processing Issues Handler](#phase-4-new-processing-issues-handler) -5. [Phase 5: Reader Handler Updates](#phase-5-reader-handler-updates) -6. [Phase 6: Route Registration](#phase-6-route-registration) -7. [Phase 7: Frontend Templates](#phase-7-frontend-templates) -8. [Phase 8: Update Reader Template](#phase-8-update-reader-template) -9. [Phase 9: Update Reader JavaScript](#phase-9-update-reader-javascript) -10. [Implementation Order](#implementation-order) - ---- - -## Phase 1: Database Schema Changes - -### File: `database/schema/schema.sql` - -**Location:** Add at end of file (after line 1253) - -**Changes to add:** - -```sql --- ============================================================================ --- ENHANCED MANGA/COMIC FORMAT DETECTION AND PROCESSING ISSUE TRACKING --- ============================================================================ - --- Allow EPUB in manga library -UPDATE library_types -SET allowed_extensions = array_append(allowed_extensions, '.epub') -WHERE name = 'manga'; - --- Function to detect fixed-layout EPUBs from OPF content -CREATE OR REPLACE FUNCTION detect_fixed_layout_epub(opf_content TEXT) -RETURNS BOOLEAN AS $$ -BEGIN - -- Check for pre-paginated metadata - IF opf_content LIKE '%rendition:layout">pre-paginated<%' THEN - RETURN TRUE; - END IF; - - IF opf_content LIKE '%rendition:layout="pre-paginated"%' THEN - RETURN TRUE; - END IF; - - -- Check for RTL page progression (manga indicator) - IF opf_content LIKE '%page-progression-direction="rtl"%' THEN - RETURN TRUE; - END IF; - - -- Check for image-heavy content (count tags) - -- Threshold of 50 images suggests manga/comic vs novel - IF (SELECT COUNT(*) FROM regexp_matches(opf_content, ']+>', 'g')) > 50 THEN - RETURN TRUE; - END IF; - - RETURN FALSE; -END; -$$ LANGUAGE plpgsql; - --- Table to track items that can't be processed in their library -CREATE TABLE IF NOT EXISTS processing_issues ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, - library_id UUID NOT NULL REFERENCES libraries(id) ON DELETE CASCADE, - issue_type VARCHAR(50) NOT NULL, - issue_description TEXT NOT NULL, - severity VARCHAR(20) NOT NULL DEFAULT 'warning', - resolved BOOLEAN DEFAULT FALSE, - resolved_at TIMESTAMP WITH TIME ZONE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - UNIQUE(media_item_id, issue_type) -); - --- Indexes for querying problem items -CREATE INDEX IF NOT EXISTS idx_processing_issues_library - ON processing_issues(library_id, resolved); -CREATE INDEX IF NOT EXISTS idx_processing_issues_severity - ON processing_issues(severity, resolved); - --- Add comment for documentation -COMMENT ON TABLE processing_issues IS 'Tracks media items that cannot be properly processed in their assigned library due to format mismatches or other issues'; -``` - -**Implementation:** Run this SQL directly in PostgreSQL or add to migration file - ---- - -## Phase 2: Database Query Additions - -### File: `internal/database/queries/queries.sql` - -**Location:** Add at end of file (after line 2018) - -**Changes to add:** - -```sql --- ============================================================================ --- PROCESSING ISSUES QUERIES --- ============================================================================ - --- name: CreateProcessingIssue :one -INSERT INTO processing_issues (media_item_id, library_id, issue_type, issue_description, severity) -VALUES ($1, $2, $3, $4, $5) -ON CONFLICT (media_item_id, issue_type) -DO UPDATE SET issue_description = EXCLUDED.issue_description, - severity = EXCLUDED.severity, - resolved = false, - resolved_at = NULL -RETURNING *; - --- name: ListProcessingIssuesByLibrary :many -SELECT - pi.id, - pi.media_item_id, - pi.issue_type, - pi.issue_description, - pi.severity, - pi.resolved, - pi.resolved_at, - pi.created_at, - mi.title, - mi.file_path, - mi.format_group, - lt.name as library_type_name -FROM processing_issues pi -JOIN media_items mi ON pi.media_item_id = mi.id -JOIN libraries l ON pi.library_id = l.id -JOIN library_types lt ON l.library_type_id = lt.id -WHERE pi.library_id = $1 - AND pi.resolved = false -ORDER BY - CASE pi.severity - WHEN 'error' THEN 1 - WHEN 'warning' THEN 2 - WHEN 'info' THEN 3 - END, - pi.created_at DESC; - --- name: GetProcessingIssueStats :one -SELECT - COUNT(*) FILTER (WHERE severity = 'error' AND resolved = false) as error_count, - COUNT(*) FILTER (WHERE severity = 'warning' AND resolved = false) as warning_count, - COUNT(*) FILTER (WHERE severity = 'info' AND resolved = false) as info_count -FROM processing_issues -WHERE library_id = $1; - --- name: ResolveProcessingIssue :one -UPDATE processing_issues -SET resolved = true, - resolved_at = NOW() -WHERE id = $1 - AND media_item_id = $2 -RETURNING *; - --- name: DeleteProcessingIssue :one -DELETE FROM processing_issues -WHERE id = $1 -RETURNING *; - --- ============================================================================ --- LIBRARY WITH TYPE INFO QUERIES --- ============================================================================ - --- name: GetLibraryWithType :one -SELECT - l.*, - lt.name as type_name, - lt.description as type_description, - lt.allowed_extensions -FROM libraries l -JOIN library_types lt ON l.library_type_id = lt.id -WHERE l.id = $1; -``` - -**Next step:** Run `sqlc generate` in `internal/database/` directory - ---- - -## Phase 3: Scanner Service Enhancements - -### File: `internal/services/media_scanner.go` - -#### Change 1: Add Fixed-Layout Detection Function - -**Location:** After `extractEPUBMetadata` function (after line 1182) - -**Add this new function:** - -```go -// 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, ` - - - - Processing Issues - Bookhoard - - - - @Header(user, "/admin/libraries/"+libraryID) - -
-
-
-
-

Processing Issues

-

Items that couldn't be processed in this library

-
- - ← Back to Library - -
-
- - if stats.ErrorCount > 0 || stats.WarningCount > 0 || stats.InfoCount > 0 { - -
- if stats.ErrorCount > 0 { -
-

Errors

-

{ stats.ErrorCount }

-
- } - if stats.WarningCount > 0 { -
-

Warnings

-

{ stats.WarningCount }

-
- } - if stats.InfoCount > 0 { -
-

Info

-

{ stats.InfoCount }

-
- } -
- } - - if len(issues) == 0 { -
-

No processing issues found for this library.

-
- } else { - -
- for _, issue := range issues { -
-
-
-

{ issue.Title }

-

{ issue.IssueDescription }

-
-

Type: { issue.IssueType }

-

Format: { issue.FormatGroup }

-

File: { issue.FilePath }

-

Library: { issue.LibraryTypeName }

-
-
-
- - { issue.Severity } - -
-
-
- if issue.Severity == "warning" || issue.Severity == "info" { - - } -
-
- } -
- } -
- - -} -``` - ---- - -## Phase 8: Update Reader Template - -### File: `templates/reader.templ` - -**Location:** Update the `x-init` attribute to include panel detection config - -**Find this code:** - -```templ - -``` - -**Replace with:** - -```templ - -``` - ---- - -## Phase 9: Update Reader JavaScript - -### File: `web/src/reader/reader.ts` - -**Location:** Replace entire file content - -**Replace with:** - -```typescript -import "foliate-js/view.js"; -import { Alpine } from "../alpine"; - -document.addEventListener("alpine:init", () => { - Alpine.data("readerShell", () => ({ - enablePanelDetection: false, - libraryType: "", - formatGroup: "", - mangaType: "", - readingDirection: "", - panelDetector: null, - - initReader(config: any) { - this.enablePanelDetection = config.enablePanelDetection; - this.libraryType = config.libraryType; - this.formatGroup = config.formatGroup; - this.mangaType = config.mangaType; - this.readingDirection = config.readingDirection; - - console.log("Reader initialized with:", { - panelDetection: this.enablePanelDetection, - library: this.libraryType, - format: this.formatGroup, - }); - - // Only load panel detection if enabled - if (this.enablePanelDetection) { - this.loadPanelDetection(); - } - }, - - async loadPanelDetection() { - try { - // Dynamic import to only load when needed - const { PanelDetector } = await import("foliate-js/panel-detection.js"); - this.panelDetector = new PanelDetector(); - console.log("Panel detection loaded successfully"); - } catch (error) { - console.error("Failed to load panel detection:", error); - } - }, - - nextPage() { - const view = document.querySelector("#reader-view"); - // @ts-ignore - foliate custom element - view?.next?.(); - }, - - previousPage() { - const view = document.querySelector("#reader-view"); - // @ts-ignore - foliate custom element - view?.prev?.(); - }, - })); - - Alpine.start(); -}); -``` - ---- - -## Implementation Order - -### Step 1: Database Changes - -```bash -# Run schema changes -psql -U your_user -d bookhoard -f database/schema/schema.sql - -# Generate database code -cd internal/database -sqlc generate - -# Verify new queries were generated -ls -la queries.sql.go -``` - -### Step 2: Backend Code Changes - -```bash -# Add scanner enhancements -# Edit internal/services/media_scanner.go - -# Add processing issues handler -# Create internal/handlers/processing_issues.go - -# Update reader handler -# Edit internal/handlers/reader.go - -# Update routes -# Edit cmd/server/main.go -``` - -### Step 3: Frontend Changes - -```bash -# Create processing issues template -# Create templates/admin_processing_issues.templ - -# Update reader template -# Edit templates/reader.templ - -# Update reader JavaScript -# Edit web/src/reader/reader.ts - -# Regenerate templates -cd templates -templ generate - -# Build frontend -cd ../web -npm run build:ts -``` - -### Step 4: Testing - -```bash -# Restart server -go run cmd/server/main.go - -# Test 1: Scan manga directory with EPUBs -# Check that fixed-layout EPUBs are detected correctly - -# Test 2: Verify processing issues are created -# Check database: SELECT * FROM processing_issues; - -# Test 3: Test reader with different library types -# - Manga library with CBZ → panel detection enabled -# - Manga library with fixed-layout EPUB → panel detection enabled -# - Ebooks library with PDF → panel detection disabled -# - Comics library with PDF → panel detection enabled - -# Test 4: Check processing issues UI -# Visit: /admin/libraries/{libraryId}/issues/list -``` - -### Step 5: Verification - -```bash -# Check database changes -\dt processing_issues -\d+ processing_issues -SELECT * FROM library_types WHERE name = 'manga'; - -# Verify scanner behavior -grep -n "DetectFixedLayoutEPUB" internal/services/media_scanner.go - -# Verify handler is registered -grep -n "processingIssuesHandler" cmd/server/main.go - -# Test API endpoints -curl http://localhost:8765/api/libraries/{libraryId}/issues/list -curl http://localhost:8765/api/libraries/{libraryId}/issues/stats -``` - ---- - -## Testing Checklist - -- [ ] Manga library accepts `.epub` files -- [ ] Fixed-layout EPUBs detected correctly -- [ ] Reflowable EPUBs in manga library create processing issues -- [ ] Processing issues API endpoints work -- [ ] Processing issues UI displays correctly -- [ ] Reader enables panel detection for manga library -- [ ] Reader enables panel detection for comics library -- [ ] Reader disables panel detection for ebooks library -- [ ] PDF ebooks don't get panel detection -- [ ] PDF comics get panel detection - ---- - -## Rollback Plan - -If issues occur: - -```bash -# 1. Revert database changes -psql -U your_user -d bookhoard -c "DROP TABLE IF EXISTS processing_issues CASCADE;" -psql -U your_user -d bookhoard -c "UPDATE library_types SET allowed_extensions = ARRAY['.cbz','.cbr','.png','.jpg','.jpeg','.gif','.bmp','.webp'] WHERE name = 'manga';" - -# 2. Remove code changes -git checkout HEAD -- internal/services/media_scanner.go -git checkout HEAD -- internal/handlers/reader.go -rm internal/handlers/processing_issues.go -git checkout HEAD -- cmd/server/main.go - -# 3. Regenerate database code -cd internal/database && sqlc generate - -# 4. Regenerate templates -cd templates && templ generate - -# 5. Rebuild frontend -cd web && npm run build:ts - -# 6. Restart server -``` - ---- - -## Notes - -- All changes follow existing code patterns -- No breaking changes to existing functionality -- Processing issues are warnings only, don't block scanning -- Panel detection is determined by library type + format group -- PDF panel detection works for comics but not ebooks -- Fixed-layout detection uses multiple heuristics for reliability