| |
|
|
| const BASE = ""; |
|
|
| async function jpost(path, body) { |
| const r = await fetch(BASE + path, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify(body), |
| }); |
| if (!r.ok) throw new Error((await r.text()) || r.statusText); |
| return r.json(); |
| } |
|
|
| async function upload(path, file, fields = {}) { |
| const fd = new FormData(); |
| fd.append("file", file); |
| for (const [k, v] of Object.entries(fields)) if (v != null) fd.append(k, v); |
| const r = await fetch(BASE + path, { method: "POST", body: fd }); |
| if (!r.ok) throw new Error((await r.text()) || r.statusText); |
| return r.json(); |
| } |
|
|
| |
| |
| export function uploadWithProgress(path, file, fields = {}, onProgress) { |
| return new Promise((resolve, reject) => { |
| const fd = new FormData(); |
| fd.append("file", file); |
| for (const [k, v] of Object.entries(fields)) if (v != null) fd.append(k, v); |
| const xhr = new XMLHttpRequest(); |
| xhr.open("POST", BASE + path); |
| if (xhr.upload && onProgress) { |
| xhr.upload.onprogress = (e) => { |
| if (e.lengthComputable) onProgress(e.loaded, e.total); |
| }; |
| } |
| xhr.onload = () => { |
| if (xhr.status >= 200 && xhr.status < 300) { |
| try { resolve(JSON.parse(xhr.responseText)); } |
| catch { resolve({}); } |
| } else reject(new Error(xhr.responseText || xhr.statusText || `HTTP ${xhr.status}`)); |
| }; |
| xhr.onerror = () => reject(new Error("Lỗi mạng khi tải lên")); |
| xhr.onabort = () => reject(new Error("Đã huỷ tải lên")); |
| xhr.send(fd); |
| }); |
| } |
|
|
| |
| |
| export async function runPool(count, worker, limit = 4) { |
| let next = 0; |
| const run = async () => { |
| while (next < count) { |
| const i = next++; |
| await worker(i); |
| } |
| }; |
| await Promise.all(Array.from({ length: Math.min(limit, count) }, run)); |
| } |
|
|
| export const fileUrl = (u) => BASE + u; |
|
|
| export const api = { |
| job: (id) => fetch(BASE + `/api/jobs/${id}`).then((r) => r.json()), |
| listInputs: (sid) => fetch(BASE + `/api/sessions/${sid}/inputs`).then((r) => r.json()), |
| cancel: (id) => jpost(`/api/jobs/${id}/cancel`, {}), |
| dropSession: (sid) => fetch(BASE + `/api/sessions/${sid}`, { method: "DELETE" }).then((r) => r.json()), |
|
|
| |
| parseLinks: (raw) => jpost("/api/download/parse", { raw }), |
| startDownload: (urls, zip_output, username = "") => |
| jpost("/api/download/start", { urls, zip_output, username }), |
| push: (session, paths, target_tool) => |
| jpost("/api/download/push", { session, paths, target_tool }), |
|
|
| |
| cutUpload: (file, session) => upload("/api/cut/upload", file, { session }), |
| cutPreview: (req) => jpost("/api/cut/preview", req), |
| cutExecute: (req) => jpost("/api/cut/execute", req), |
| cutExecuteBatch: (req) => jpost("/api/cut/execute-batch", req), |
| |
| cutPushShuffle: (session, segments) => |
| jpost("/api/cut/push-shuffle", { session, segments }), |
| |
| cutSplitAudio: (session, file, username = "") => |
| jpost("/api/cut/split-audio", { session, file, username }), |
|
|
| |
| shuffleUpload: (file, segment, session) => |
| upload("/api/shuffle/upload", file, { segment, session }), |
| shuffleUploadAudio: (file, session) => |
| upload("/api/shuffle/upload-audio", file, { session }), |
| |
| shuffleUploadP: (file, segment, session, onProgress) => |
| uploadWithProgress("/api/shuffle/upload", file, { segment, session }, onProgress), |
| shuffleUploadAudioP: (file, session, onProgress) => |
| uploadWithProgress("/api/shuffle/upload-audio", file, { session }, onProgress), |
| shuffleSegments: (sid) => fetch(BASE + `/api/shuffle/segments/${sid}`).then((r) => r.json()), |
| shuffleEstimate: (req) => jpost("/api/shuffle/estimate", req), |
| shufflePlan: (req) => jpost("/api/shuffle/plan", req), |
| shuffleGenerate: (req) => jpost("/api/shuffle/generate", req), |
|
|
| |
| dubUpload: (file, session) => upload("/api/dub/upload", file, { session }), |
| dubVoiceSample: (file, session) => |
| upload("/api/dub/voice-sample", file, { session }), |
| dubVoices: (lang) => fetch(BASE + `/api/dub/voices?lang=${lang}`).then((r) => r.json()), |
| dubStart: (req) => jpost("/api/dub/start", req), |
| |
| dubPrepare: (req) => jpost("/api/dub/prepare", req), |
| dubSynthesize: (req) => jpost("/api/dub/synthesize", req), |
| dubTts: (req) => jpost("/api/dub/tts", req), |
| |
| dubSrtImport: (file, session) => upload("/api/dub/subtitle/import", file, { session }), |
| |
| subtitlePlan: (session, srt) => jpost("/api/dub/subtitle/plan", { session, srt }), |
| subtitleBurn: (req) => jpost("/api/dub/subtitle", req), |
| |
| subtitlePreviewFrame: (session, video, segments, mode, time) => |
| jpost("/api/dub/subtitle/preview-frame", { session, video, segments, mode, time }), |
|
|
| |
| driveStatus: (username) => fetch(BASE + `/api/drive/status?username=${encodeURIComponent(username)}`).then((r) => r.json()), |
| driveSetup: (username, folder_link) => jpost("/api/drive/setup", { username, folder_link }), |
| driveRemove: (username) => fetch(BASE + `/api/drive/setup?username=${encodeURIComponent(username)}`, { method: "DELETE" }).then((r) => r.json()), |
|
|
| |
| getHistory: (username) => fetch(BASE + `/api/history?username=${encodeURIComponent(username)}`).then((r) => r.json()), |
| }; |
|
|
| |
| |
| |
| |
| export function pollJob(id, onTick, interval = 1000, signal) { |
| return new Promise((resolve, reject) => { |
| let timer = null; |
| const stop = () => { if (timer) clearTimeout(timer); }; |
| if (signal) signal.addEventListener("abort", stop, { once: true }); |
| const tick = async () => { |
| if (signal && signal.aborted) return; |
| try { |
| const job = await api.job(id); |
| if (signal && signal.aborted) return; |
| onTick && onTick(job); |
| if (["done", "error", "cancelled"].includes(job.status)) { |
| resolve(job); |
| } else { |
| timer = setTimeout(tick, interval); |
| } |
| } catch (e) { |
| if (!(signal && signal.aborted)) reject(e); |
| } |
| }; |
| tick(); |
| }); |
| } |
|
|