schema: add comic metadata and reading direction support

Add 14 new columns to media_items table for comprehensive comic and manga
metadata support, including reading direction fields and universal metadata
that applies to all media formats.

New Columns:
- Reading direction: manga_type (raw ComicInfo.xml field), reading_direction (computed)
- Universal series: series_count, volume (apply to ebooks, audiobooks, comics)
- Publisher info: imprint, age_rating (all formats)
- Comic-specific: story_arc, is_black_and_white, alternate_info, scan_information, summary
- Additional metadata: metadata_notes, community_rating, web_url

Constraints:
- manga_type CHECK: unknown, no, yes, yes_and_right_to_left
- reading_direction CHECK: auto, ltr, rtl, vertical

Indexes (8 new):
- idx_media_items_reading_direction, idx_media_items_manga_type
- idx_media_items_story_arc, idx_media_items_imprint
- idx_media_items_age_rating, idx_media_items_series_count, idx_media_items_volume
- idx_media_items_alternate_info_gin (GIN index for JSONB queries)

Documentation:
- Added COMMENT ON COLUMN for all 14 new fields
- Distinctions between comic-specific and universal fields clearly documented

This supports the ComicInfo.xml v2.0 standard with 29 fields and enables
proper reading direction detection for manga, webtoons, and Western comics.

Part of Phase 1: Database Schema Changes
Implementation: IMPLEMENTATION_PLAN_MERGE_METADATA_READING_DIRECTION.md
This commit is contained in:
2026-03-29 19:11:48 -04:00
parent 1b632a1b6e
commit 2ffd8e032b
+94 -1
View File
@@ -133,7 +133,40 @@ CREATE TABLE IF NOT EXISTS media_items (
entitlement_id VARCHAR(255) UNIQUE,
revision_number INTEGER DEFAULT 1,
kobo_content_id VARCHAR(255),
kobo_metadata JSONB
kobo_metadata JSONB,
-- Manga and comic reading direction support
-- Stores raw Manga field from ComicInfo.xml
manga_type VARCHAR(30) DEFAULT 'unknown',
reading_direction VARCHAR(20) DEFAULT 'auto',
CHECK (manga_type IN ('unknown', 'no', 'yes', 'yes_and_right_to_left')),
CHECK (reading_direction IN ('auto', 'ltr', 'rtl', 'vertical')),
-- Universal series information (applies to ALL formats: ebooks, audiobooks, comics)
series_count INTEGER, -- Total items in series (from ComicInfo Count field, or book series count)
volume INTEGER, -- Volume/omnibus number for collected editions
-- Universal publisher and classification (applies to ALL formats)
imprint VARCHAR(255), -- Publisher imprint (e.g., Vertigo, HarperCollinsEpic)
age_rating VARCHAR(20), -- Age rating: Everyone, Teen, Mature, Adult (applies to all formats)
web_url VARCHAR(500), -- URL to info page (Goodreads, ComicVine, MangaUpdates, Audible, etc.)
-- Comic-specific fields
story_arc VARCHAR(255), -- Story arc name (e.g., "The Dark Phoenix Saga", "Civil War")
is_black_and_white BOOLEAN, -- Black and white flag (mostly comics, some illustrated books)
-- Additional metadata (applies to all formats)
metadata_notes TEXT, -- Notes from metadata files (ComicInfo.xml, EPUB, PDF) - distinct from user notes
community_rating DECIMAL(3,1), -- Pre-existing community rating from metadata (0.0-10.0) - distinct from user ratings
-- Alternate series information (JSONB for flexible schema - comic-specific)
alternate_info JSONB, -- Stores AlternateSeries, AlternateNumber, AlternateCount
-- Example: {"alternate_series": "Ultimate X-Men", "alternate_number": 1, "alternate_count": 12}
-- Scan and publication metadata (comic-specific)
scan_information TEXT, -- Scan information (scanner group, resolution, etc.)
-- Summary (distinct from description - may merge with Calibre description)
summary TEXT -- Summary from ComicInfo.xml (may be merged with description from Calibre)
);
-- Add GIN indexes for fast array searches
@@ -387,6 +420,46 @@ CREATE INDEX IF NOT EXISTS idx_media_items_copyright_year ON media_items(copyrig
CREATE INDEX IF NOT EXISTS idx_media_items_page_count ON media_items(page_count);
CREATE INDEX IF NOT EXISTS idx_media_items_series ON media_items(series);
-- Index for filtering by reading direction (for manga/comic libraries)
CREATE INDEX IF NOT EXISTS idx_media_items_reading_direction
ON media_items(reading_direction)
WHERE reading_direction IS NOT NULL;
-- Index for filtering by story arc (comic-specific)
CREATE INDEX IF NOT EXISTS idx_media_items_story_arc
ON media_items(story_arc)
WHERE story_arc IS NOT NULL;
-- Index for filtering by imprint (universal - all formats)
CREATE INDEX IF NOT EXISTS idx_media_items_imprint
ON media_items(imprint)
WHERE imprint IS NOT NULL;
-- Index for filtering by age rating (universal - all formats)
CREATE INDEX IF NOT EXISTS idx_media_items_age_rating
ON media_items(age_rating)
WHERE age_rating IS NOT NULL;
-- Index for filtering by manga type (comic-specific)
CREATE INDEX IF NOT EXISTS idx_media_items_manga_type
ON media_items(manga_type)
WHERE manga_type IS NOT NULL;
-- Index for filtering by series count (universal - all formats)
CREATE INDEX IF NOT EXISTS idx_media_items_series_count
ON media_items(series_count)
WHERE series_count IS NOT NULL;
-- Index for filtering by volume (universal - all formats)
CREATE INDEX IF NOT EXISTS idx_media_items_volume
ON media_items(volume)
WHERE volume IS NOT NULL;
-- GIN index for alternate_info JSONB queries (comic-specific)
CREATE INDEX IF NOT EXISTS idx_media_items_alternate_info_gin
ON media_items USING GIN (alternate_info)
WHERE alternate_info IS NOT NULL;
-- Progress and ratings indexes
CREATE INDEX IF NOT EXISTS idx_reading_progress_media_item_id ON reading_progress(media_item_id);
CREATE INDEX IF NOT EXISTS idx_reading_progress_user_id ON reading_progress(user_id);
@@ -477,6 +550,26 @@ COMMENT ON COLUMN media_items.goodreads_id IS 'Goodreads book identifier for int
COMMENT ON COLUMN media_items.openlibrary_id IS 'Open Library identifier for integration';
COMMENT ON COLUMN media_items.google_books_id IS 'Google Books identifier for integration';
-- Comic-specific fields
COMMENT ON COLUMN media_items.manga_type IS 'Raw Manga field from ComicInfo.xml: unknown, no, yes, yes_and_right_to_left';
COMMENT ON COLUMN media_items.reading_direction IS 'Computed reading direction: auto, ltr (left-to-right), rtl (right-to-left), vertical (webtoons/manhwa)';
COMMENT ON COLUMN media_items.story_arc IS 'Story arc name for grouping related issues (e.g., "The Dark Phoenix Saga", "Civil War")';
COMMENT ON COLUMN media_items.is_black_and_white IS 'Black and white comic flag from ComicInfo.xml';
COMMENT ON COLUMN media_items.alternate_info IS 'Alternate series information as JSONB: {alternate_series, alternate_number, alternate_count}';
COMMENT ON COLUMN media_items.scan_information IS 'Scan information from ComicInfo.xml (scanner group, resolution, etc.)';
-- Universal fields (apply to ebooks, audiobooks, comics)
COMMENT ON COLUMN media_items.series_count IS 'Total items in series (from ComicInfo.xml Count field, or book series count)';
COMMENT ON COLUMN media_items.volume IS 'Volume/omnibus number for collected editions';
COMMENT ON COLUMN media_items.imprint IS 'Publisher imprint/subdivision (e.g., Vertigo, HarperCollinsEpic, DC Black Label)';
COMMENT ON COLUMN media_items.age_rating IS 'Age rating from metadata: Everyone, Teen, Mature, Adult (applies to all formats)';
COMMENT ON COLUMN media_items.web_url IS 'URL to info page (Goodreads, ComicVine, MangaUpdates, Audible, etc.)';
-- Additional metadata (applies to all formats)
COMMENT ON COLUMN media_items.metadata_notes IS 'Notes from metadata files (ComicInfo.xml, EPUB, PDF) - distinct from user notes in media_notes table';
COMMENT ON COLUMN media_items.community_rating IS 'Pre-existing community rating from metadata files (scale 0.0-10.0) - distinct from user ratings in media_ratings table';
COMMENT ON COLUMN media_items.summary IS 'Summary from ComicInfo.xml (may be merged with description from Calibre)';
-- Library Type File Extensions Notes:
-- - Ebooks: .epub, .pdf, .mobi, .azw, .azw3, .txt, .rtf, .doc, .docx, .lit, .fb2, .pdb
-- - Comics: .cbz, .cbr, .cb7, .cbt, .pdf