147 lines
4.0 KiB
JavaScript
147 lines
4.0 KiB
JavaScript
const form = document.querySelector("#convert-form");
|
|
const urlInput = document.querySelector("#url");
|
|
const formatSelect = document.querySelector("#format");
|
|
const qualityField = document.querySelector("#quality-field");
|
|
const qualitySelect = document.querySelector("#quality");
|
|
const convertButton = document.querySelector("#convert-button");
|
|
const metadataPanel = document.querySelector("#metadata");
|
|
const thumbnail = document.querySelector("#thumbnail");
|
|
const videoTitle = document.querySelector("#video-title");
|
|
const videoChannel = document.querySelector("#video-channel");
|
|
const videoDuration = document.querySelector("#video-duration");
|
|
const statusDot = document.querySelector("#status-dot");
|
|
const statusText = document.querySelector("#status-text");
|
|
const errorText = document.querySelector("#error-text");
|
|
const downloadLink = document.querySelector("#download-link");
|
|
|
|
let pollTimer = null;
|
|
|
|
formatSelect.addEventListener("change", () => {
|
|
qualityField.classList.toggle("hidden", formatSelect.value !== "mp3");
|
|
});
|
|
|
|
form.addEventListener("submit", async (event) => {
|
|
event.preventDefault();
|
|
clearPolling();
|
|
resetDownload();
|
|
setBusy(true);
|
|
setStatus("active", "Validating URL");
|
|
|
|
const payload = {
|
|
url: urlInput.value.trim(),
|
|
format: formatSelect.value,
|
|
quality: qualitySelect.value,
|
|
};
|
|
|
|
try {
|
|
setStatus("active", "Fetching video info");
|
|
const info = await postJson("/api/info", { url: payload.url });
|
|
renderMetadata(info);
|
|
|
|
setStatus("active", "Queued");
|
|
const { job_id: jobId } = await postJson("/api/convert", payload);
|
|
pollStatus(jobId);
|
|
} catch (error) {
|
|
showError(error.message);
|
|
setBusy(false);
|
|
}
|
|
});
|
|
|
|
async function pollStatus(jobId) {
|
|
try {
|
|
const status = await getJson(`/api/status/${jobId}`);
|
|
|
|
if (status.metadata) {
|
|
renderMetadata(status.metadata);
|
|
}
|
|
|
|
if (status.status === "complete") {
|
|
setStatus("done", "Ready to download");
|
|
downloadLink.href = status.download_url;
|
|
downloadLink.classList.remove("hidden");
|
|
setBusy(false);
|
|
return;
|
|
}
|
|
|
|
if (status.status === "error") {
|
|
showError(status.error || "Conversion failed.");
|
|
setBusy(false);
|
|
return;
|
|
}
|
|
|
|
setStatus("active", status.message || "Working");
|
|
pollTimer = window.setTimeout(() => pollStatus(jobId), 1200);
|
|
} catch (error) {
|
|
showError(error.message);
|
|
setBusy(false);
|
|
}
|
|
}
|
|
|
|
async function postJson(url, body) {
|
|
const response = await fetch(url, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
});
|
|
return parseJsonResponse(response);
|
|
}
|
|
|
|
async function getJson(url) {
|
|
const response = await fetch(url);
|
|
return parseJsonResponse(response);
|
|
}
|
|
|
|
async function parseJsonResponse(response) {
|
|
const data = await response.json().catch(() => ({}));
|
|
if (!response.ok) {
|
|
throw new Error(data.detail || "Request failed.");
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function renderMetadata(info) {
|
|
videoTitle.textContent = info.title || "Untitled YouTube video";
|
|
videoChannel.textContent = info.channel || "Unknown channel";
|
|
videoDuration.textContent = info.duration ? `Duration ${info.duration}` : "Duration unknown";
|
|
|
|
if (info.thumbnail) {
|
|
thumbnail.src = info.thumbnail;
|
|
thumbnail.classList.remove("hidden");
|
|
} else {
|
|
thumbnail.removeAttribute("src");
|
|
thumbnail.classList.add("hidden");
|
|
}
|
|
|
|
metadataPanel.classList.remove("hidden");
|
|
}
|
|
|
|
function setStatus(kind, message) {
|
|
statusDot.className = `status-dot ${kind}`;
|
|
statusText.textContent = message;
|
|
errorText.classList.add("hidden");
|
|
errorText.textContent = "";
|
|
}
|
|
|
|
function showError(message) {
|
|
setStatus("error", "Error");
|
|
errorText.textContent = message;
|
|
errorText.classList.remove("hidden");
|
|
}
|
|
|
|
function resetDownload() {
|
|
downloadLink.classList.add("hidden");
|
|
downloadLink.removeAttribute("href");
|
|
}
|
|
|
|
function setBusy(isBusy) {
|
|
convertButton.disabled = isBusy;
|
|
convertButton.textContent = isBusy ? "Converting..." : "Convert";
|
|
}
|
|
|
|
function clearPolling() {
|
|
if (pollTimer) {
|
|
window.clearTimeout(pollTimer);
|
|
pollTimer = null;
|
|
}
|
|
}
|