94 lines
3.1 KiB
TypeScript
94 lines
3.1 KiB
TypeScript
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;
|
|
}
|