Initial commit

This commit is contained in:
admin
2026-05-26 22:46:00 +02:00
commit 7e2c2ff89c
256 changed files with 51523 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
import { parseNfo, type NfoMetadata } from "./nfoParser";
/**
* Lenient JSON metadata parser. Accepts a wide range of common keys produced by
* scrapers (Kodi/Jellyfin/Stash/JavSP/etc.) and normalizes them into the same
* shape as `NfoMetadata` so the cover editor can pre-fill from a file or a
* pasted blob.
*/
export function parseMetaJson(raw: string): NfoMetadata | null {
let obj: unknown;
try {
obj = JSON.parse(raw);
} catch {
return null;
}
if (!obj || typeof obj !== "object") return null;
// Some scrapers wrap in {"data": {...}} or arrays.
const root = (obj as Record<string, unknown>);
const data = (root.data && typeof root.data === "object" ? root.data : root) as Record<string, unknown>;
const pick = (...keys: string[]): string | undefined => {
for (const k of keys) {
const v = data[k];
if (typeof v === "string" && v.trim()) return v.trim();
if (typeof v === "number" && Number.isFinite(v)) return String(v);
}
return undefined;
};
const pickArray = (...keys: string[]): string[] => {
for (const k of keys) {
const v = data[k];
if (Array.isArray(v)) {
return v
.map((x) => {
if (typeof x === "string") return x.trim();
if (x && typeof x === "object") {
const name = (x as Record<string, unknown>).name ?? (x as Record<string, unknown>).Name;
return typeof name === "string" ? name.trim() : "";
}
return "";
})
.filter(Boolean);
}
if (typeof v === "string" && v.trim()) {
return v.split(/[,、,]/).map((s) => s.trim()).filter(Boolean);
}
}
return [];
};
const runtimeRaw = pick("runtime", "runtimeMin", "runtime_min", "duration");
const runtimeMin = runtimeRaw ? parseInt(runtimeRaw, 10) : NaN;
const out: NfoMetadata = {
title: pick("title", "originaltitle", "originalTitle", "name"),
code: pick("code", "id", "num", "number", "javId", "jav_id"),
releaseDate: pick("releaseDate", "release_date", "premiered", "releasedate", "date", "year"),
runtimeMin: Number.isFinite(runtimeMin) ? runtimeMin : undefined,
director: pick("director"),
studio: pick("studio", "maker", "manufacturer"),
series: pick("series", "set"),
actresses: pickArray("actresses", "actors", "actor", "performers", "cast", "stars"),
genres: pickArray("genres", "genre", "tags", "categories"),
notes: pick("plot", "outline", "summary", "description", "notes"),
};
return strip(out);
}
/**
* Try JSON first, fall back to XML. Returns null if neither matches.
*/
export function parseMetaAny(raw: string): NfoMetadata | null {
const trimmed = raw.trim();
if (!trimmed) return null;
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
return parseMetaJson(trimmed);
}
return parseNfo(trimmed);
}
function strip(o: NfoMetadata): NfoMetadata {
const out: NfoMetadata = {};
for (const [k, v] of Object.entries(o)) {
if (v == null) continue;
if (Array.isArray(v) && v.length === 0) continue;
if (typeof v === "string" && v === "") continue;
(out as Record<string, unknown>)[k] = v;
}
return out;
}