- Add `movies` type: per-movie folders with video files, poster/backdrop images, and optional Jellyfin NFO metadata (title, year, plot, rating, genres, runtime). Grid view with 2:3 poster art, detail modal with play and two-click delete of the movie folder. - Add `tv` type: Series -> Season -> Episode hierarchy with lazy loading at each level. Reads tvshow.nfo and episodedetails NFO files for metadata. Episode grid with video thumbnails, streams via existing video player. Delete is limited to the entire series folder to avoid breaking Jellyfin. - Add fast-xml-parser dependency for Kodi/Jellyfin NFO parsing (lib/nfo.ts) - Migrate existing DB to expand the libraries CHECK constraint to include the two new types; migration is idempotent and preserves existing data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
74 lines
2.0 KiB
TypeScript
74 lines
2.0 KiB
TypeScript
import path from 'path'
|
|
import fs from 'fs'
|
|
import Database from 'better-sqlite3'
|
|
|
|
const CONFIG_PATH = process.env.CONFIG_PATH ?? process.cwd()
|
|
const DB_PATH = path.resolve(CONFIG_PATH, 'medialore.db')
|
|
|
|
let _db: Database.Database | null = null
|
|
|
|
export function getDb(): Database.Database {
|
|
if (_db) return _db
|
|
_db = new Database(DB_PATH)
|
|
_db.pragma('journal_mode = WAL')
|
|
_db.pragma('foreign_keys = ON')
|
|
initDb(_db)
|
|
return _db
|
|
}
|
|
|
|
function initDb(db: Database.Database): void {
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS tag_categories (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL UNIQUE
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS tags (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
category_id TEXT NOT NULL REFERENCES tag_categories(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS tags_name_category ON tags(name, category_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS media_tags (
|
|
media_key TEXT NOT NULL,
|
|
tag_id TEXT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (media_key, tag_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS libraries (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
path TEXT NOT NULL,
|
|
type TEXT NOT NULL CHECK(type IN ('games', 'mixed', 'movies', 'tv')),
|
|
cover_ext TEXT NULL
|
|
);
|
|
`)
|
|
|
|
migrateLibrariesType(db)
|
|
}
|
|
|
|
function migrateLibrariesType(db: Database.Database): void {
|
|
const row = db
|
|
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='libraries'")
|
|
.get() as { sql: string } | undefined
|
|
|
|
if (row && !row.sql.includes("'movies'")) {
|
|
db.exec(`
|
|
BEGIN TRANSACTION;
|
|
CREATE TABLE libraries_new (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
path TEXT NOT NULL,
|
|
type TEXT NOT NULL CHECK(type IN ('games', 'mixed', 'movies', 'tv')),
|
|
cover_ext TEXT NULL
|
|
);
|
|
INSERT INTO libraries_new SELECT * FROM libraries;
|
|
DROP TABLE libraries;
|
|
ALTER TABLE libraries_new RENAME TO libraries;
|
|
COMMIT;
|
|
`)
|
|
}
|
|
}
|