163 lines
6.3 KiB
TypeScript

#!/usr/bin/env tsx
/**
* One-time import script to populate the database from the existing library
*
* Usage:
* pnpm import
* # or
* tsx scripts/import.ts
*/
import Database from 'better-sqlite3';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { existsSync, mkdirSync } from 'fs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DATA_DIR = process.env.DATA_DIR || join(__dirname, '../../../data');
const DB_PATH = process.env.DATABASE_URL || join(DATA_DIR, 'library.db');
// Ensure data directory exists
if (!existsSync(DATA_DIR)) {
mkdirSync(DATA_DIR, { recursive: true });
}
console.log('╔════════════════════════════════════════════╗');
console.log('║ MEDIA LIBRARY IMPORT SCRIPT ║');
console.log('╠════════════════════════════════════════════╣');
console.log('║ This will scan the library and populate ║');
console.log('║ the database with video metadata. ║');
console.log('╚════════════════════════════════════════════╝');
console.log('');
// Step 0: Initialize database schema
console.log('Step 0: Initializing database schema...');
console.log(` Database: ${DB_PATH}`);
const sqlite = new Database(DB_PATH);
sqlite.pragma('journal_mode = WAL');
sqlite.exec(`
CREATE TABLE IF NOT EXISTS videos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
path TEXT NOT NULL UNIQUE,
filename TEXT NOT NULL,
studio TEXT,
performer TEXT,
title TEXT,
duration_seconds INTEGER,
quality TEXT,
orientation TEXT CHECK(orientation IN ('H', 'V')),
has_audio INTEGER DEFAULT 1,
file_size INTEGER,
file_hash TEXT,
last_validated INTEGER,
is_valid INTEGER DEFAULT 1,
thumbnail_path TEXT,
created_at INTEGER DEFAULT (unixepoch()),
tags TEXT
);
CREATE INDEX IF NOT EXISTS idx_orientation ON videos(orientation);
CREATE INDEX IF NOT EXISTS idx_studio ON videos(studio);
CREATE INDEX IF NOT EXISTS idx_is_valid ON videos(is_valid);
CREATE TABLE IF NOT EXISTS compilations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
filename TEXT NOT NULL,
path TEXT,
duration_seconds INTEGER,
video_ids TEXT,
settings TEXT,
created_at INTEGER DEFAULT (unixepoch())
);
CREATE TABLE IF NOT EXISTS jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL CHECK(type IN ('compilation', 'scan', 'organize')),
status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'running', 'completed', 'failed')),
progress INTEGER DEFAULT 0,
log TEXT,
params TEXT,
started_at INTEGER,
completed_at INTEGER,
created_at INTEGER DEFAULT (unixepoch())
);
`);
sqlite.close();
console.log(' Schema initialized successfully!\n');
// Now import the scanner (which will use the existing db)
const { scanLibrary, scanCompilations } = await import('../src/services/scanner.js');
const startTime = Date.now();
try {
// Step 1: Scan compilations from playback directory
console.log('Step 1: Scanning compilations...\n');
const compilationResult = await scanCompilations((progress) => {
process.stdout.write(`\r [${progress.processed}/${progress.total}] ${progress.current.slice(0, 50).padEnd(50)}`);
});
console.log('\n');
console.log(` Compilations imported: ${compilationResult.imported}`);
console.log(` Compilations skipped: ${compilationResult.skipped}`);
console.log(` Compilations errors: ${compilationResult.errors}`);
console.log(` Compilations removed: ${compilationResult.removed}`);
console.log('');
// Step 2: Scan full video library for compilation source material
console.log('Step 2: Scanning full video library for compilation source...\n');
const result = await scanLibrary(
(progress) => {
const percent = Math.floor((progress.processed / progress.total) * 100);
const bar = '█'.repeat(Math.floor(percent / 2.5)) + '░'.repeat(40 - Math.floor(percent / 2.5));
process.stdout.write(`\r[${bar}] ${percent}% (${progress.processed}/${progress.total}) ${progress.current.slice(0, 40).padEnd(40)}`);
},
{
skipExisting: true,
validateFiles: false, // Skip validation for speed
scanSource: 'library', // Scan full library for compilation source
}
);
// Step 3: Also scan public directory for public gallery display
console.log('\n\nStep 3: Scanning public directory for gallery display...\n');
const publicResult = await scanLibrary(
(progress) => {
const percent = Math.floor((progress.processed / progress.total) * 100);
const bar = '█'.repeat(Math.floor(percent / 2.5)) + '░'.repeat(40 - Math.floor(percent / 2.5));
process.stdout.write(`\r[${bar}] ${percent}% (${progress.processed}/${progress.total}) ${progress.current.slice(0, 40).padEnd(40)}`);
},
{
skipExisting: true,
validateFiles: false,
scanSource: 'public', // Scan public directory
}
);
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
console.log('\n');
console.log('════════════════════════════════════════════');
console.log(' COMPLETE ');
console.log('════════════════════════════════════════════');
console.log(' Library (compilation source):');
console.log(` Imported: ${result.imported}`);
console.log(` Skipped: ${result.skipped}`);
console.log(` Errors: ${result.errors}`);
console.log(` Removed: ${result.removed}`);
console.log('');
console.log(' Public (gallery display):');
console.log(` Imported: ${publicResult.imported}`);
console.log(` Skipped: ${publicResult.skipped}`);
console.log(` Errors: ${publicResult.errors}`);
console.log(` Removed: ${publicResult.removed}`);
console.log('');
console.log(` Time: ${elapsed}s`);
console.log('════════════════════════════════════════════');
} catch (err) {
console.error('\nImport failed:', err);
process.exit(1);
}