114 lines
3.3 KiB
TypeScript
114 lines
3.3 KiB
TypeScript
import fs from 'fs'
|
|
import path from 'path'
|
|
import type { Movie } from '@/types'
|
|
import { getDb } from './db'
|
|
import { HIDDEN_FILES, VIDEO_EXTENSIONS, fileApiUrl, thumbnailApiUrl, findFile } from './media-utils'
|
|
|
|
function findVideoFile(dir: string): string | null {
|
|
let entries: string[]
|
|
try {
|
|
entries = fs.readdirSync(dir)
|
|
} catch {
|
|
return null
|
|
}
|
|
return entries.find(
|
|
(e) => !HIDDEN_FILES.test(e) && VIDEO_EXTENSIONS.has(path.extname(e).toLowerCase())
|
|
) ?? null
|
|
}
|
|
|
|
export function findNfoFile(dir: string, dirName: string): string | null {
|
|
// Try {dirName}.nfo first, then movie.nfo, then any .nfo
|
|
const candidates = [`${dirName}.nfo`, 'movie.nfo']
|
|
let entries: string[]
|
|
try {
|
|
entries = fs.readdirSync(dir)
|
|
} catch {
|
|
return null
|
|
}
|
|
for (const candidate of candidates) {
|
|
const match = entries.find((e) => e.toLowerCase() === candidate.toLowerCase())
|
|
if (match) return match
|
|
}
|
|
return entries.find((e) => path.extname(e).toLowerCase() === '.nfo') ?? null
|
|
}
|
|
|
|
export function scanMoviesLibrary(libraryRoot: string, libraryId: string): Movie[] {
|
|
let dirs: string[]
|
|
try {
|
|
dirs = fs
|
|
.readdirSync(libraryRoot, { withFileTypes: true })
|
|
.filter((d) => d.isDirectory() && !HIDDEN_FILES.test(d.name))
|
|
.map((d) => d.name)
|
|
} catch {
|
|
return []
|
|
}
|
|
|
|
const movies: Movie[] = []
|
|
|
|
for (const dirName of dirs) {
|
|
const moviePath = path.join(libraryRoot, dirName)
|
|
|
|
const videoFile = findVideoFile(moviePath)
|
|
if (!videoFile) continue
|
|
|
|
const posterFile = findFile(moviePath, /^(poster|cover|folder)$/i)
|
|
const backdropFile = findFile(moviePath, /^(backdrop|fanart|background)$/i)
|
|
|
|
const id = encodeURIComponent(dirName)
|
|
const videoRelPath = path.join(dirName, videoFile)
|
|
|
|
movies.push({
|
|
id,
|
|
title: dirName,
|
|
year: null,
|
|
plot: null,
|
|
rating: null,
|
|
genres: [],
|
|
runtime: null,
|
|
posterUrl: posterFile
|
|
? thumbnailApiUrl(libraryId, path.join(dirName, posterFile))
|
|
: null,
|
|
backdropUrl: backdropFile
|
|
? fileApiUrl(libraryId, path.join(dirName, backdropFile))
|
|
: null,
|
|
videoPath: videoRelPath,
|
|
})
|
|
}
|
|
|
|
return movies.sort((a, b) => a.title.localeCompare(b.title))
|
|
}
|
|
|
|
export function moviesFromDb(libraryId: string): Movie[] {
|
|
const db = getDb()
|
|
const rows = db
|
|
.prepare(`SELECT * FROM media_items WHERE library_id = ? AND item_type = 'movie' ORDER BY title`)
|
|
.all(libraryId) as Array<{
|
|
item_key: string
|
|
title: string | null
|
|
year: number | null
|
|
plot: string | null
|
|
genres: string | null
|
|
metadata: string | null
|
|
file_path: string | null
|
|
}>
|
|
|
|
return rows.map((row) => {
|
|
const meta = row.metadata ? JSON.parse(row.metadata) : {}
|
|
const idPart = row.item_key.split(':movie:')[1] ?? row.item_key
|
|
return {
|
|
id: idPart,
|
|
item_key: row.item_key,
|
|
title: row.title ?? decodeURIComponent(idPart),
|
|
year: row.year ?? null,
|
|
plot: row.plot ?? null,
|
|
rating: meta.rating ?? null,
|
|
genres: row.genres ? JSON.parse(row.genres) : [],
|
|
runtime: meta.runtime ?? null,
|
|
posterUrl: meta.posterUrl ?? null,
|
|
backdropUrl: meta.backdropUrl ?? null,
|
|
videoPath: row.file_path ?? '',
|
|
manuallyEdited: meta.manuallyEdited === true,
|
|
}
|
|
})
|
|
}
|