(url, { title, stems } = {})
| 367 | // re-download + re-separate a track whose backend audio was swept. Takes over |
| 368 | // the studio like a normal import. Returns the new job id, or null on failure. |
| 369 | export async function importFromUrl(url, { title, stems } = {}) { |
| 370 | if (!url || url.startsWith("local:")) return null; // local files can't auto-restore |
| 371 | reset(); |
| 372 | setSubmitProcessing(true); |
| 373 | setWaveformLoading(true, ""); |
| 374 | const stemSel = stems?.length ? stems : [...selectedStems]; |
| 375 | |
| 376 | let jobId; |
| 377 | try { |
| 378 | const res = await fetch("/api/jobs", { |
| 379 | method: "POST", |
| 380 | headers: { "Content-Type": "application/json" }, |
| 381 | body: JSON.stringify({ url, stems: stemSel }), |
| 382 | }); |
| 383 | const data = await res.json(); |
| 384 | if (!res.ok) throw new Error(data.detail || res.statusText); |
| 385 | jobId = data.job_id; |
| 386 | } catch (err) { |
| 387 | showError(`Failed to restore track: ${err.message}`); |
| 388 | setSubmitProcessing(false); |
| 389 | return null; |
| 390 | } |
| 391 | |
| 392 | setCurrentJobId(jobId); |
| 393 | jobSources.set(jobId, url); |
| 394 | // Merges into the existing library entry by sourceUrl (replaceTrackId), |
| 395 | // preserving its folder placement; status updates as SSE frames arrive. |
| 396 | addTrackToLibrary({ |
| 397 | id: jobId, |
| 398 | title: title || url || "Processing track", |
| 399 | channel: "Processing", |
| 400 | thumb: "", |
| 401 | stems: stemSel, |
| 402 | selectedStems: stemSel, |
| 403 | audioStems: [], |
| 404 | status: "processing", |
| 405 | bpm: null, |
| 406 | key: null, |
| 407 | scale: null, |
| 408 | keyConfidence: null, |
| 409 | lufs: null, |
| 410 | peakDb: null, |
| 411 | sourceUrl: url, |
| 412 | }); |
| 413 | setCurrentTrack(jobId); |
| 414 | |
| 415 | jobBox.classList.add("hidden"); |
| 416 | jobCancelBtn.classList.add("hidden"); |
| 417 | startPhraseRotation("queued"); |
| 418 | lastStatus = "queued"; |
| 419 | connectEvents(jobId); |
| 420 | return jobId; |
| 421 | } |
| 422 | |
| 423 | export function wireJobForm() { |
| 424 | jobCancelBtn.addEventListener("click", cancelCurrentJob); |
no test coverage detected