Files
siftlode/frontend/src/lib/downloadFailure.ts
T
peter 14598ec320 feat(downloads): ad-hoc clips cut straight from a YouTube video or a Plex item
POST /api/downloads/clip takes a source (a YouTube id or a Plex rating key), a range of at most
ten minutes and, for Plex, the audio track being heard. The clip becomes a per-user asset in the
library like an editor clip, shareable as soon as it is ready. A YouTube clip fetches only its
range (yt-dlp download_ranges: 3-7 s whatever the video's length); a Plex clip reads the library
file in place. Both then go through one re-encode: frame-exact at the marks, H.264 at most 1080p
with HDR tonemapped to SDR, stereo AAC, no chapter track, .mp4.

Job kinds are a JobKind StrEnum now, and Re-download refuses every kind but a download.
2026-10-10 02:31:38 +02:00

41 lines
2.2 KiB
TypeScript

import type { DownloadJob, DownloadJobKind } from "./api";
// The i18n keys that explain a failed download, most specific first — the list form `t()` takes, so
// the first key a locale has wins. A failure-catalog entry (`error_detail`, e.g. `facebook.login_wall`)
// names what went wrong on that site and what to do; the category (`error_code`) is the generic
// sentence, and the fallback when an older client meets a detail it has no copy for; a category it has
// no copy for falls back to the unknown one, so the row never prints a key. A job that is not a
// download (an upload's conversion, a clip) may word a category its own way, ahead of the shared
// sentence — "this download was stopped" is not what happened to it — and its own unknown sentence
// comes before the download's. One helper for the queue row's headline and the failure window, so
// the two can never explain the same job differently.
export function failCopyKeys(
job: Pick<DownloadJob, "error_code" | "error_detail" | "job_kind">,
part: "short" | "body",
): string[] {
// A clip from YouTube or Plex is worded as the editor's clip: both are "making the clip".
const kind = job.job_kind === "clip" ? "edit" : (job.job_kind ?? "download");
const category = (code: string) => [
...(kind === "download" ? [] : [`downloads.errors.fail.${kind}.${code}.${part}`]),
`downloads.errors.fail.cat.${code}.${part}`,
];
const code = job.error_code || "unknown";
return [
...(job.error_detail ? [`downloads.errors.fail.catalog.${job.error_detail}.${part}`] : []),
...category(code),
...(code === "unknown" ? [] : category("unknown")),
];
}
const TITLE_KEYS: Record<DownloadJobKind, string> = {
download: "downloads.errors.fail.title",
upload: "downloads.errors.fail.titleUpload", // its failure is a conversion, not a download
edit: "downloads.errors.fail.titleEdit", // a clip being cut
clip: "downloads.errors.fail.titleEdit", // a clip being cut, from YouTube or Plex
};
/** The title over a failed job's explanation, worded for what the job was making. */
export function failTitleKey(job: Pick<DownloadJob, "job_kind">): string {
return TITLE_KEYS[job.job_kind ?? "download"];
}