2026-05-15 17:05:37 +07:00
|
|
|
import { existsSync, statSync } from "node:fs";
|
|
|
|
|
import path from "node:path";
|
|
|
|
|
import { AppError } from "../errors";
|
|
|
|
|
import type { ResolvedMediaSource } from "./mediaTypes";
|
|
|
|
|
|
|
|
|
|
export async function resolveMediaSource(
|
|
|
|
|
input: string,
|
|
|
|
|
): Promise<ResolvedMediaSource> {
|
|
|
|
|
const source = input.trim();
|
|
|
|
|
if (!source) {
|
|
|
|
|
throw new AppError("Media source is required", "MISSING_MEDIA_SOURCE", 400);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 17:11:26 +07:00
|
|
|
const urlSource = resolveUrlSource(source);
|
|
|
|
|
if (urlSource) return urlSource;
|
2026-05-15 17:05:37 +07:00
|
|
|
|
2026-05-15 17:11:26 +07:00
|
|
|
const localPath = path.resolve(source);
|
|
|
|
|
if (existsSync(localPath) && statSync(localPath).isFile()) {
|
2026-05-15 17:05:37 +07:00
|
|
|
return {
|
2026-05-15 17:11:26 +07:00
|
|
|
source: localPath,
|
|
|
|
|
title: path.basename(localPath),
|
2026-05-15 17:05:37 +07:00
|
|
|
kind: "local",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new AppError(
|
|
|
|
|
"Media source must be an HTTP(S) URL or existing local file",
|
|
|
|
|
"UNSUPPORTED_MEDIA_SOURCE",
|
|
|
|
|
400,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 17:11:26 +07:00
|
|
|
function resolveUrlSource(source: string): ResolvedMediaSource | null {
|
|
|
|
|
let url: URL;
|
|
|
|
|
try {
|
|
|
|
|
url = new URL(source);
|
|
|
|
|
} catch {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
source,
|
|
|
|
|
title: titleFromUrl(url),
|
|
|
|
|
kind: "url",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function titleFromUrl(url: URL): string {
|
2026-05-15 17:05:37 +07:00
|
|
|
const filename = decodeURIComponent(url.pathname.split("/").pop() || "");
|
2026-05-15 17:11:26 +07:00
|
|
|
return path.basename(filename) || url.hostname;
|
|
|
|
|
}
|