const form = document.querySelector("#inspect-form"); const urlInput = document.querySelector("#source-url"); const submitButton = document.querySelector("#inspect-button"); const errorNotice = document.querySelector("#error-notice"); const errorMessage = document.querySelector("#error-message"); const resultSection = document.querySelector("#result"); const heroSection = document.querySelector(".hero"); const newSourceButton = document.querySelector("#new-source"); const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); const fields = { title: document.querySelector("#result-title"), sourceLink: document.querySelector("#source-link"), thumbnail: document.querySelector("#thumbnail"), thumbnailShell: document.querySelector("#thumbnail-shell"), duration: document.querySelector("#duration"), extractor: document.querySelector("#extractor"), uploader: document.querySelector("#uploader"), uploadDate: document.querySelector("#upload-date"), topQuality: document.querySelector("#top-quality"), bestSpecs: document.querySelector("#best-specs"), compatibleOption: document.querySelector("#compatible-option"), compatibleSpecs: document.querySelector("#compatible-specs"), }; function displayValue(value, fallback = "Not available") { return value === null || value === undefined || value === "" ? fallback : String(value); } function formatDuration(seconds) { if (!Number.isFinite(seconds)) return "Unknown duration"; const total = Math.max(0, Math.round(seconds)); const hours = Math.floor(total / 3600); const minutes = Math.floor((total % 3600) / 60); const remainingSeconds = total % 60; const parts = hours > 0 ? [hours, minutes, remainingSeconds] : [minutes, remainingSeconds]; return parts.map((part) => String(part).padStart(2, "0")).join(":"); } function formatUploadDate(value) { if (!/^\d{8}$/.test(value || "")) return displayValue(value); return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`; } function resolutionLabel(format) { if (format.width && format.height) return `${format.width} × ${format.height}`; if (format.audio_codec && format.audio_codec !== "none") return "Audio only"; return "Unknown"; } function isVideo(format) { return format.video_codec && format.video_codec !== "none"; } function isAudio(format) { return format.audio_codec && format.audio_codec !== "none"; } function sortByVisualQuality(formats) { return [...formats].sort((left, right) => { return (right.height || 0) - (left.height || 0) || (right.fps || 0) - (left.fps || 0); }); } function codecFamily(codec) { if (!codec || codec === "none") return null; const normalized = codec.toLowerCase(); if (normalized.startsWith("avc") || normalized === "h264") return "H.264"; if (normalized.startsWith("av01")) return "AV1"; if (normalized.startsWith("vp9") || normalized.startsWith("vp09")) return "VP9"; if (normalized.startsWith("mp4a") || normalized === "aac") return "AAC"; if (normalized.startsWith("opus")) return "Opus"; return codec; } function addSpec(list, label, value) { const wrapper = document.createElement("div"); const term = document.createElement("dt"); const detail = document.createElement("dd"); term.textContent = label; detail.textContent = value; wrapper.append(term, detail); list.append(wrapper); } function renderOption(list, video, audio) { list.replaceChildren(); addSpec(list, "Quality", resolutionLabel(video)); if (video.fps) addSpec(list, "FPS", String(video.fps)); addSpec(list, "Video", displayValue(codecFamily(video.video_codec), "Unknown")); const audioCodec = isAudio(video) ? video.audio_codec : audio?.audio_codec; addSpec(list, "Audio", displayValue(codecFamily(audioCodec), "Unknown")); } function renderOutputOptions(formats) { const orderedVideo = sortByVisualQuality(formats.filter(isVideo)); const audioOnly = formats.filter((format) => !isVideo(format) && isAudio(format)); const bestVideo = orderedVideo[0]; const bestAudio = audioOnly.at(-1); if (!bestVideo) { fields.topQuality.textContent = "Audio only"; fields.bestSpecs.replaceChildren(); fields.compatibleOption.hidden = true; return; } fields.topQuality.textContent = resolutionLabel(bestVideo); renderOption(fields.bestSpecs, bestVideo, bestAudio); const compatibleVideo = orderedVideo.find((format) => { const videoCodec = codecFamily(format.video_codec); const audioCodec = codecFamily(format.audio_codec); return videoCodec === "H.264" && (!isAudio(format) || audioCodec === "AAC"); }); const compatibleAudio = audioOnly.findLast((format) => codecFamily(format.audio_codec) === "AAC"); if (compatibleVideo && (isAudio(compatibleVideo) || compatibleAudio)) { renderOption(fields.compatibleSpecs, compatibleVideo, compatibleAudio); fields.compatibleOption.hidden = false; } else { fields.compatibleOption.hidden = true; } } function renderResult(data) { fields.title.textContent = data.title; fields.sourceLink.href = data.source_url; fields.extractor.textContent = displayValue(data.extractor); fields.uploader.textContent = displayValue(data.uploader); fields.uploadDate.textContent = formatUploadDate(data.upload_date); fields.duration.textContent = formatDuration(data.duration); if (data.thumbnail) { fields.thumbnail.src = data.thumbnail; fields.thumbnail.alt = `Preview for ${data.title}`; fields.thumbnail.hidden = false; fields.thumbnailShell.classList.remove("no-image"); } else { fields.thumbnail.removeAttribute("src"); fields.thumbnail.alt = ""; fields.thumbnail.hidden = true; fields.thumbnailShell.classList.add("no-image"); } renderOutputOptions(data.formats); } function transitionDelay(milliseconds) { return new Promise((resolve) => { window.setTimeout(resolve, reducedMotion.matches ? 0 : milliseconds); }); } async function showResult(data) { renderResult(data); heroSection.classList.add("is-leaving"); await transitionDelay(180); heroSection.hidden = true; heroSection.classList.remove("is-leaving"); resultSection.hidden = false; window.scrollTo({ top: 0, behavior: "auto" }); window.requestAnimationFrame(() => resultSection.classList.add("is-visible")); } async function showInspector() { resultSection.classList.remove("is-visible"); resultSection.classList.add("is-leaving"); await transitionDelay(180); resultSection.hidden = true; resultSection.classList.remove("is-leaving"); heroSection.classList.add("is-leaving"); heroSection.hidden = false; window.requestAnimationFrame(() => { heroSection.classList.remove("is-leaving"); urlInput.focus(); }); } function setLoading(isLoading) { submitButton.disabled = isLoading; urlInput.disabled = isLoading; form.setAttribute("aria-busy", String(isLoading)); } form.addEventListener("submit", async (event) => { event.preventDefault(); errorNotice.hidden = true; resultSection.hidden = true; setLoading(true); try { const response = await fetch("/api/inspect", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url: urlInput.value.trim() }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.detail || "The source could not be inspected."); } await showResult(data); } catch (error) { errorMessage.textContent = error instanceof Error ? error.message : "Inspection failed."; errorNotice.hidden = false; } finally { setLoading(false); } }); newSourceButton.addEventListener("click", showInspector);