fix: fixed issues related to missing wvd
This commit is contained in:
@@ -4,6 +4,6 @@ PASSWORD=
|
|||||||
# Host port to expose the app on.
|
# Host port to expose the app on.
|
||||||
PORT=5000
|
PORT=5000
|
||||||
|
|
||||||
# Host paths for persistent data.
|
# Host paths for persistent data (used by docker-compose volumes only).
|
||||||
DOWNLOADS_DIR=./downloads
|
HOST_DOWNLOADS_DIR=./downloads
|
||||||
CONFIG_DIR=./config
|
HOST_CONFIG_DIR=./config
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
|
/config/
|
||||||
/downloads/
|
/downloads/
|
||||||
.env
|
.env
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ FROM python:3.12-slim
|
|||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
|
aria2 \
|
||||||
git \
|
git \
|
||||||
curl \
|
curl \
|
||||||
unzip \
|
unzip \
|
||||||
|
|||||||
63
app.py
63
app.py
@@ -42,6 +42,7 @@ def require_login():
|
|||||||
DOWNLOADS_DIR = Path(os.environ.get("DOWNLOADS_DIR", "/downloads"))
|
DOWNLOADS_DIR = Path(os.environ.get("DOWNLOADS_DIR", "/downloads"))
|
||||||
COOKIES_PATH = Path(os.environ.get("COOKIES_PATH", "/config/cookies.txt"))
|
COOKIES_PATH = Path(os.environ.get("COOKIES_PATH", "/config/cookies.txt"))
|
||||||
CONFIG_DIR = Path(os.environ.get("CONFIG_DIR", "/config"))
|
CONFIG_DIR = Path(os.environ.get("CONFIG_DIR", "/config"))
|
||||||
|
WVD_PATH = Path(os.environ.get("WVD_PATH", "/config/device.wvd"))
|
||||||
TEMP_DIR = Path("/tmp/votify")
|
TEMP_DIR = Path("/tmp/votify")
|
||||||
|
|
||||||
DOWNLOADS_DIR.mkdir(parents=True, exist_ok=True)
|
DOWNLOADS_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
@@ -93,6 +94,8 @@ def run_download(job_id: str, urls: list[str], options: dict):
|
|||||||
cmd.extend(["--cookies-path", str(COOKIES_PATH)])
|
cmd.extend(["--cookies-path", str(COOKIES_PATH)])
|
||||||
cmd.extend(["--output-path", str(DOWNLOADS_DIR)])
|
cmd.extend(["--output-path", str(DOWNLOADS_DIR)])
|
||||||
cmd.extend(["--temp-path", str(TEMP_DIR)])
|
cmd.extend(["--temp-path", str(TEMP_DIR)])
|
||||||
|
if WVD_PATH.exists():
|
||||||
|
cmd.extend(["--wvd-path", str(WVD_PATH)])
|
||||||
|
|
||||||
quality = options.get("audio_quality", "aac-medium")
|
quality = options.get("audio_quality", "aac-medium")
|
||||||
if quality:
|
if quality:
|
||||||
@@ -149,8 +152,14 @@ def run_download(job_id: str, urls: list[str], options: dict):
|
|||||||
text=True,
|
text=True,
|
||||||
bufsize=1,
|
bufsize=1,
|
||||||
)
|
)
|
||||||
|
with jobs_lock:
|
||||||
|
jobs[job_id]["process"] = process
|
||||||
|
|
||||||
output_lines = []
|
output_lines = []
|
||||||
for line in process.stdout:
|
for line in process.stdout:
|
||||||
|
with jobs_lock:
|
||||||
|
if jobs[job_id]["status"] == "cancelled":
|
||||||
|
break
|
||||||
line = line.rstrip("\n")
|
line = line.rstrip("\n")
|
||||||
output_lines.append(line)
|
output_lines.append(line)
|
||||||
with jobs_lock:
|
with jobs_lock:
|
||||||
@@ -158,12 +167,18 @@ def run_download(job_id: str, urls: list[str], options: dict):
|
|||||||
|
|
||||||
process.wait()
|
process.wait()
|
||||||
|
|
||||||
if process.returncode == 0 and want_mp3:
|
|
||||||
convert_to_mp3(job_id, files_before)
|
|
||||||
|
|
||||||
with jobs_lock:
|
with jobs_lock:
|
||||||
jobs[job_id]["status"] = "completed" if process.returncode == 0 else "failed"
|
cancelled = jobs[job_id]["status"] == "cancelled"
|
||||||
jobs[job_id]["return_code"] = process.returncode
|
|
||||||
|
if cancelled:
|
||||||
|
with jobs_lock:
|
||||||
|
jobs[job_id]["output"] = jobs[job_id].get("output", []) + ["[cancelled] Job was cancelled by user."]
|
||||||
|
else:
|
||||||
|
if process.returncode == 0 and want_mp3:
|
||||||
|
convert_to_mp3(job_id, files_before)
|
||||||
|
with jobs_lock:
|
||||||
|
jobs[job_id]["status"] = "completed" if process.returncode == 0 else "failed"
|
||||||
|
jobs[job_id]["return_code"] = process.returncode
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
with jobs_lock:
|
with jobs_lock:
|
||||||
jobs[job_id]["status"] = "failed"
|
jobs[job_id]["status"] = "failed"
|
||||||
@@ -244,10 +259,14 @@ def start_download():
|
|||||||
return jsonify({"job_id": job_id})
|
return jsonify({"job_id": job_id})
|
||||||
|
|
||||||
|
|
||||||
|
def job_to_dict(job):
|
||||||
|
return {k: v for k, v in job.items() if k != "process"}
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/jobs")
|
@app.route("/api/jobs")
|
||||||
def list_jobs():
|
def list_jobs():
|
||||||
with jobs_lock:
|
with jobs_lock:
|
||||||
return jsonify(list(jobs.values()))
|
return jsonify([job_to_dict(j) for j in jobs.values()])
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/jobs/<job_id>")
|
@app.route("/api/jobs/<job_id>")
|
||||||
@@ -256,7 +275,22 @@ def get_job(job_id):
|
|||||||
job = jobs.get(job_id)
|
job = jobs.get(job_id)
|
||||||
if not job:
|
if not job:
|
||||||
return jsonify({"error": "Job not found"}), 404
|
return jsonify({"error": "Job not found"}), 404
|
||||||
return jsonify(job)
|
return jsonify(job_to_dict(job))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/jobs/<job_id>/cancel", methods=["POST"])
|
||||||
|
def cancel_job(job_id):
|
||||||
|
with jobs_lock:
|
||||||
|
job = jobs.get(job_id)
|
||||||
|
if not job:
|
||||||
|
return jsonify({"error": "Job not found"}), 404
|
||||||
|
if job["status"] != "running":
|
||||||
|
return jsonify({"error": "Job is not running"}), 400
|
||||||
|
job["status"] = "cancelled"
|
||||||
|
proc = job.get("process")
|
||||||
|
if proc:
|
||||||
|
proc.terminate()
|
||||||
|
return jsonify({"ok": True})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/jobs/<job_id>", methods=["DELETE"])
|
@app.route("/api/jobs/<job_id>", methods=["DELETE"])
|
||||||
@@ -355,5 +389,20 @@ def upload_cookies():
|
|||||||
return jsonify({"ok": True})
|
return jsonify({"ok": True})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/wvd", methods=["GET"])
|
||||||
|
def check_wvd():
|
||||||
|
return jsonify({"exists": WVD_PATH.exists()})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/wvd", methods=["POST"])
|
||||||
|
def upload_wvd():
|
||||||
|
if "file" not in request.files:
|
||||||
|
return jsonify({"error": "No file uploaded"}), 400
|
||||||
|
file = request.files["file"]
|
||||||
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
file.save(WVD_PATH)
|
||||||
|
return jsonify({"ok": True})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=5000, debug=False)
|
app.run(host="0.0.0.0", port=5000, debug=False)
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
# Netscape HTTP Cookie File
|
|
||||||
# https://curl.haxx.se/rfc/cookie_spec.html
|
|
||||||
# This is a generated file! Do not edit.
|
|
||||||
|
|
||||||
.spotify.com TRUE / TRUE 1804370105 sp_t 5090c304-1e4d-4352-aea1-16fe4ae9207c
|
|
||||||
.spotify.com TRUE / TRUE 1772920453 sp_new 1
|
|
||||||
.spotify.com TRUE / TRUE 1772920453 sp_landing https%3A%2F%2Fopen.spotify.com%2F
|
|
||||||
.spotify.com TRUE / TRUE 1772920453 sp_landingref https%3A%2F%2Fwww.bing.com%2F
|
|
||||||
.spotify.com TRUE / FALSE 1804370056 OptanonAlertBoxClosed 2026-03-06T21:54:16.012Z
|
|
||||||
.spotify.com TRUE / FALSE 1804370056 eupubconsent-v2 CQgpG1gQgpG1gAcABBENCVFgAP_AAEOAAAYgJnABxC4URAFAaSIyAJIgMAAUgABAQAAQAAIBAAABCBgEQAQAkAAgBACABAACGAAAIAAAAAAACAAAAEAAAIAAJADAAAAEIAAAIAAAABAAAAAAAAAgEAAAAAAAgAAEAAAAiAAAAJIAEEAAAAAAAAAAIAAAAAAAAACAAAAAAAAAAQAAQCgAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAQTOgUQAKAAsACoAFwAPAAgABIACgAGQANIAeAB6AD8AJwAXgA_ACcAFcAMoAc8A7gDvAH4AQgAiYBFgCSwFeAV8A4gB7YD9gP4Ah2BKoErALYAXYAvMBiwDGQGTAMsAgKBGYCZwARSAkAAsACoAIIAZABoADwAPwAygBzgDvAH4ARYAkoB7QEOgLYAXmAywCZxQAOABcAEgAnAB3AHbAYsAyY.f_gACHAAAAAA.IJnABxC4URAFAaSIyAJIgMAAUgABAQAAQAAIBAAABCBgEQAQAkAAgBACABAACGAAAIAAAAAAACAAAAEAAAIAAJADAAAAEIAAAIAAAADAAAAAAAAAgEAAAAAAAgAAEAABAiAAAAJIAEEAAAAAAAAAAIAAAAAAAAACAAAAAAAAAAQAAQCgAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAQAAA
|
|
||||||
.spotify.com TRUE / TRUE 1772835856 _cs_mk_ga 0.3535578042278994_1772834056026
|
|
||||||
open.spotify.com FALSE / FALSE 0 sss 1
|
|
||||||
.spotify.com TRUE / FALSE 1772920505 _gid GA1.2.1328783107.1772834056
|
|
||||||
.spotify.com TRUE / TRUE 1804370106 sp_adid ff3f915f-9589-4b4c-a755-2e1431a4cd63
|
|
||||||
.spotify.com TRUE / TRUE 1772920461 sp_m nl
|
|
||||||
.spotify.com TRUE / FALSE 1807394104 _ga_S35RN5WNT2 GS2.1.s1772834061$o1$g1$t1772834103$j18$l0$h0
|
|
||||||
.spotify.com TRUE / TRUE 1774043704 sp_dc AQAER3Lqcs1oCTqLdVMsph_dzJBd6ywwfIuFsTycqxoUrvj77I7KgQWTdnkOFZrw51GUURjj-wFXyAziah0ljXB3YSvXcugyoaz-N2ryPxh4e78XsDeJpJtPOGs9bncokmLK6W2RLbHFeTwcSawXf6LM6JixmRrH_f35MI0chLo-GbGxle9Jqf1-FmTGYt8dSXh-ryX2575p-IhWIg
|
|
||||||
.spotify.com TRUE / TRUE 1778018105 sp_gaid 0088fca431da2488813d802a0cd113719429847133955a017c6d57
|
|
||||||
.spotify.com TRUE / FALSE 1807394106 _ga GA1.1.139841860.1772834056
|
|
||||||
.spotify.com TRUE / FALSE 1807394106 _ga_ZWG1NSHWD8 GS2.1.s1772834056$o1$g1$t1772834105$j11$l0$h0
|
|
||||||
.spotify.com TRUE / FALSE 1804370105 OptanonConsent isGpcEnabled=0&datestamp=Fri+Mar+06+2026+22%3A55%3A05+GMT%2B0100+(Central+European+Standard+Time)&version=202601.2.0&browserGpcFlag=0&isIABGlobal=false&hosts=&consentId=14d166da-7df6-43b2-bc25-f18987870a49&interactionCount=1&isAnonUser=1&prevHadToken=0&landingPath=NotLandingPage&groups=s00%3A1%2Cf00%3A1%2Cm00%3A1%2Ct00%3A1%2Cf11%3A1%2CID01%3A1%2Ci00%3A1%2CV2STACK3%3A1%2CV2STACK11%3A1%2CV2STACK20%3A1%2Cm03%3A1&intType=1&crTime=1772834056251&geolocation=NL%3BZH&AwaitingReconsent=false
|
|
||||||
.spotify.com TRUE / FALSE 1807394106 _ga_BMC5VGR8YS GS2.2.s1772834056$o1$g1$t1772834105$j11$l0$h0
|
|
||||||
@@ -7,6 +7,6 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
volumes:
|
volumes:
|
||||||
- ${DOWNLOADS_DIR:-./downloads}:/downloads
|
- ${HOST_DOWNLOADS_DIR:-./downloads}:/downloads
|
||||||
- ${CONFIG_DIR:-./config}:/config
|
- ${HOST_CONFIG_DIR:-./config}:/config
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
.status-running { background: #1a3a2a; color: var(--accent); }
|
.status-running { background: #1a3a2a; color: var(--accent); }
|
||||||
.status-completed { background: #1a3a1a; color: #4caf50; }
|
.status-completed { background: #1a3a1a; color: #4caf50; }
|
||||||
.status-failed { background: #3a1a1a; color: var(--danger); }
|
.status-failed { background: #3a1a1a; color: var(--danger); }
|
||||||
|
.status-cancelled { background: #3a3a1a; color: var(--warning); }
|
||||||
|
|
||||||
.job-card { background: var(--surface); border-radius: var(--radius); padding: 16px; margin-bottom: 12px; }
|
.job-card { background: var(--surface); border-radius: var(--radius); padding: 16px; margin-bottom: 12px; }
|
||||||
.job-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
.job-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
||||||
@@ -218,6 +219,18 @@
|
|||||||
<br><br>
|
<br><br>
|
||||||
<button class="btn btn-sm btn-secondary" onclick="uploadCookies()">Upload</button>
|
<button class="btn btn-sm btn-secondary" onclick="uploadCookies()">Upload</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h2>Widevine Device</h2>
|
||||||
|
<div class="cookie-status" id="wvd-status">
|
||||||
|
<div class="dot dot-red"></div>
|
||||||
|
<span>Checking...</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<label>Upload device.wvd (required for AAC quality)</label>
|
||||||
|
<input type="file" id="wvd-file" accept=".wvd" style="margin-top:8px">
|
||||||
|
<br><br>
|
||||||
|
<button class="btn btn-sm btn-secondary" onclick="uploadWvd()">Upload</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -235,7 +248,7 @@
|
|||||||
|
|
||||||
if (name === 'jobs') loadJobs();
|
if (name === 'jobs') loadJobs();
|
||||||
if (name === 'files') loadFiles("");
|
if (name === 'files') loadFiles("");
|
||||||
if (name === 'settings') checkCookies();
|
if (name === 'settings') { checkCookies(); checkWvd(); }
|
||||||
|
|
||||||
if (jobPollInterval) clearInterval(jobPollInterval);
|
if (jobPollInterval) clearInterval(jobPollInterval);
|
||||||
if (name === 'jobs') jobPollInterval = setInterval(loadJobs, 3000);
|
if (name === 'jobs') jobPollInterval = setInterval(loadJobs, 3000);
|
||||||
@@ -358,6 +371,7 @@
|
|||||||
${progressHtml}
|
${progressHtml}
|
||||||
${logHtml}
|
${logHtml}
|
||||||
<div class="job-actions">
|
<div class="job-actions">
|
||||||
|
${job.status === 'running' ? `<button class="btn btn-sm btn-danger" onclick="cancelJob('${job.id}')">Cancel</button>` : ''}
|
||||||
${job.status !== 'running' ? `<button class="btn btn-sm btn-danger" onclick="deleteJob('${job.id}')">Remove</button>` : ''}
|
${job.status !== 'running' ? `<button class="btn btn-sm btn-danger" onclick="deleteJob('${job.id}')">Remove</button>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -375,6 +389,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function cancelJob(id) {
|
||||||
|
await fetch('/api/jobs/' + id + '/cancel', { method: 'POST' });
|
||||||
|
loadJobs();
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteJob(id) {
|
async function deleteJob(id) {
|
||||||
expandedJobs.delete(id);
|
expandedJobs.delete(id);
|
||||||
await fetch('/api/jobs/' + id, { method: 'DELETE' });
|
await fetch('/api/jobs/' + id, { method: 'DELETE' });
|
||||||
@@ -485,6 +504,40 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkWvd() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/wvd');
|
||||||
|
const data = await res.json();
|
||||||
|
const el = document.getElementById('wvd-status');
|
||||||
|
if (data.exists) {
|
||||||
|
el.innerHTML = '<div class="dot dot-green"></div><span>device.wvd found</span>';
|
||||||
|
} else {
|
||||||
|
el.innerHTML = '<div class="dot dot-red"></div><span>device.wvd not found</span>';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadWvd() {
|
||||||
|
const file = document.getElementById('wvd-file').files[0];
|
||||||
|
if (!file) { alert('Select a file first'); return; }
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('file', file);
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/wvd', { method: 'POST', body: form });
|
||||||
|
if (res.ok) {
|
||||||
|
alert('WVD file uploaded successfully');
|
||||||
|
checkWvd();
|
||||||
|
} else {
|
||||||
|
const data = await res.json();
|
||||||
|
alert(data.error || 'Upload failed');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert('Error: ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatSize(bytes) {
|
function formatSize(bytes) {
|
||||||
if (!bytes) return '';
|
if (!bytes) return '';
|
||||||
const units = ['B', 'KB', 'MB', 'GB'];
|
const units = ['B', 'KB', 'MB', 'GB'];
|
||||||
@@ -531,6 +584,7 @@
|
|||||||
|
|
||||||
loadSettings();
|
loadSettings();
|
||||||
checkCookies();
|
checkCookies();
|
||||||
|
checkWvd();
|
||||||
|
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('/sw.js')
|
navigator.serviceWorker.register('/sw.js')
|
||||||
|
|||||||
Reference in New Issue
Block a user