fix: fixed issues related to missing wvd
This commit is contained in:
@@ -4,6 +4,6 @@ PASSWORD=
|
||||
# Host port to expose the app on.
|
||||
PORT=5000
|
||||
|
||||
# Host paths for persistent data.
|
||||
DOWNLOADS_DIR=./downloads
|
||||
CONFIG_DIR=./config
|
||||
# Host paths for persistent data (used by docker-compose volumes only).
|
||||
HOST_DOWNLOADS_DIR=./downloads
|
||||
HOST_CONFIG_DIR=./config
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/config/
|
||||
/downloads/
|
||||
.env
|
||||
@@ -2,6 +2,7 @@ FROM python:3.12-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ffmpeg \
|
||||
aria2 \
|
||||
git \
|
||||
curl \
|
||||
unzip \
|
||||
|
||||
63
app.py
63
app.py
@@ -42,6 +42,7 @@ def require_login():
|
||||
DOWNLOADS_DIR = Path(os.environ.get("DOWNLOADS_DIR", "/downloads"))
|
||||
COOKIES_PATH = Path(os.environ.get("COOKIES_PATH", "/config/cookies.txt"))
|
||||
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")
|
||||
|
||||
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(["--output-path", str(DOWNLOADS_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")
|
||||
if quality:
|
||||
@@ -149,8 +152,14 @@ def run_download(job_id: str, urls: list[str], options: dict):
|
||||
text=True,
|
||||
bufsize=1,
|
||||
)
|
||||
with jobs_lock:
|
||||
jobs[job_id]["process"] = process
|
||||
|
||||
output_lines = []
|
||||
for line in process.stdout:
|
||||
with jobs_lock:
|
||||
if jobs[job_id]["status"] == "cancelled":
|
||||
break
|
||||
line = line.rstrip("\n")
|
||||
output_lines.append(line)
|
||||
with jobs_lock:
|
||||
@@ -158,12 +167,18 @@ def run_download(job_id: str, urls: list[str], options: dict):
|
||||
|
||||
process.wait()
|
||||
|
||||
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
|
||||
cancelled = jobs[job_id]["status"] == "cancelled"
|
||||
|
||||
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:
|
||||
with jobs_lock:
|
||||
jobs[job_id]["status"] = "failed"
|
||||
@@ -244,10 +259,14 @@ def start_download():
|
||||
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")
|
||||
def list_jobs():
|
||||
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>")
|
||||
@@ -256,7 +275,22 @@ def get_job(job_id):
|
||||
job = jobs.get(job_id)
|
||||
if not job:
|
||||
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"])
|
||||
@@ -355,5 +389,20 @@ def upload_cookies():
|
||||
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__":
|
||||
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
|
||||
volumes:
|
||||
- ${DOWNLOADS_DIR:-./downloads}:/downloads
|
||||
- ${CONFIG_DIR:-./config}:/config
|
||||
- ${HOST_DOWNLOADS_DIR:-./downloads}:/downloads
|
||||
- ${HOST_CONFIG_DIR:-./config}:/config
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
.status-running { background: #1a3a2a; color: var(--accent); }
|
||||
.status-completed { background: #1a3a1a; color: #4caf50; }
|
||||
.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-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
||||
@@ -218,6 +219,18 @@
|
||||
<br><br>
|
||||
<button class="btn btn-sm btn-secondary" onclick="uploadCookies()">Upload</button>
|
||||
</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>
|
||||
|
||||
@@ -235,7 +248,7 @@
|
||||
|
||||
if (name === 'jobs') loadJobs();
|
||||
if (name === 'files') loadFiles("");
|
||||
if (name === 'settings') checkCookies();
|
||||
if (name === 'settings') { checkCookies(); checkWvd(); }
|
||||
|
||||
if (jobPollInterval) clearInterval(jobPollInterval);
|
||||
if (name === 'jobs') jobPollInterval = setInterval(loadJobs, 3000);
|
||||
@@ -358,6 +371,7 @@
|
||||
${progressHtml}
|
||||
${logHtml}
|
||||
<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>` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -375,6 +389,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelJob(id) {
|
||||
await fetch('/api/jobs/' + id + '/cancel', { method: 'POST' });
|
||||
loadJobs();
|
||||
}
|
||||
|
||||
async function deleteJob(id) {
|
||||
expandedJobs.delete(id);
|
||||
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) {
|
||||
if (!bytes) return '';
|
||||
const units = ['B', 'KB', 'MB', 'GB'];
|
||||
@@ -531,6 +584,7 @@
|
||||
|
||||
loadSettings();
|
||||
checkCookies();
|
||||
checkWvd();
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
|
||||
Reference in New Issue
Block a user