This repository has been archived on 2026-06-15. You can view files and clone it, but cannot push or open issues or pull requests.
Files
MediaLore/src/lib/movie-metadata.ts
Garret Patti bd028a7a5d
All checks were successful
Build and Push Docker Image / build (push) Successful in 56s
scan fixes
2026-04-20 20:31:18 -04:00

104 lines
3.2 KiB
TypeScript

import fs from 'fs'
import path from 'path'
import type { Library } from '@/types'
import { getDb } from './db'
import { resolveLibraryRoot } from './libraries'
import { parseMovieNfo } from './nfo'
/**
* Import NFO metadata for Movie items in a library.
* - Reads .nfo file matching each movie file
* - If importMetadataOnly=false: skip items that already have metadata (title/year/plot/genres)
* - If importMetadataOnly=true: update all items regardless of existing metadata
*/
export async function importMovieMetadata(
library: Library,
importMetadataOnly: boolean = false
): Promise<{ imported: number; skipped: number }> {
const db = getDb()
const libraryRoot = resolveLibraryRoot(library)
let imported = 0
let skipped = 0
// Get all movies in the library
const movies = db
.prepare(
`SELECT item_key, file_path, title, year, plot, genres FROM media_items
WHERE library_id = ? AND item_type = 'movie' AND file_path IS NOT NULL`
)
.all(library.id) as Array<{ item_key: string; file_path: string; title: string | null; year: number | null; plot: string | null; genres: string | null }>
const updateItem = db.prepare(`
UPDATE media_items SET title = @title, year = @year, plot = @plot, genres = @genres
WHERE item_key = @item_key
`)
const BATCH_SIZE = 50
for (let i = 0; i < movies.length; i += BATCH_SIZE) {
const batch = movies.slice(i, i + BATCH_SIZE)
db.transaction(() => {
for (const item of batch) {
// Check if we should skip this item
if (!importMetadataOnly && hasMetadata(item)) {
skipped++
continue
}
const videoPath = path.join(libraryRoot, item.file_path)
const dir = path.dirname(videoPath)
const baseNameWithoutExt = path.basename(videoPath, path.extname(videoPath))
const nfoPath = path.join(dir, `${baseNameWithoutExt}.nfo`)
try {
if (fs.existsSync(nfoPath)) {
const nfoData = parseMovieNfo(nfoPath)
if (nfoData) {
updateItem.run({
item_key: item.item_key,
title: nfoData.title ?? item.title,
year: nfoData.year ?? item.year,
plot: nfoData.plot ?? item.plot,
genres: nfoData.genres.length > 0 ? JSON.stringify(nfoData.genres) : item.genres,
})
imported++
} else {
skipped++
}
} else {
skipped++
}
} catch {
skipped++
}
}
})()
await new Promise<void>((r) => setImmediate(r))
}
console.log(
`[movie-metadata] Imported metadata for ${imported} movies in "${library.name}" (${importMetadataOnly ? 'full' : 'incremental'})`
)
return { imported, skipped }
}
/**
* Check if a media item already has metadata populated.
* Returns true if ANY of: title, year, plot, or genres are populated.
*/
function hasMetadata(item: {
title: string | null
year: number | null
plot: string | null
genres: string | null
}): boolean {
if (item.title) return true
if (item.year) return true
if (item.plot) return true
if (item.genres) return true
return false
}