docker: alpine, autodownload

This commit is contained in:
imagede
2026-03-29 21:17:10 +02:00
parent 25bf7db167
commit 414750bfd8
4 changed files with 34 additions and 57 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
app/__pycache__ app/__pycache__
downloads/

View File

@@ -1,9 +1,7 @@
FROM python:3.12-slim FROM python:3.12-alpine
# Install ffmpeg (required for merging video+audio streams and MP3 conversion) # Install ffmpeg and dcron (lightweight cron daemon for Alpine)
RUN apt-get update && \ RUN apk add --no-cache ffmpeg dcron
apt-get install -y --no-install-recommends ffmpeg && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app WORKDIR /app
@@ -17,10 +15,10 @@ COPY app/ .
# Downloads are stored here (mount a host volume to persist them) # Downloads are stored here (mount a host volume to persist them)
RUN mkdir -p /downloads RUN mkdir -p /downloads
# Setup Cronjob # Setup Cronjob (fails)
RUN apt-get update && apt-get install -y cron && \ RUN echo "0 4 * * * pip install --upgrade yt-dlp >> /var/log/yt-dlp-upgrade.log 2>&1" | crontab -
echo "0 4 * * * pip install --upgrade yt-dlp >> /var/log/yt-dlp-upgrade.log 2>&1" | crontab -
EXPOSE 8080 EXPOSE 8080
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"] # Launch
CMD uvicorn main:app --host 0.0.0.0 --port 8080

View File

@@ -96,6 +96,7 @@
border: none; border: none;
border-radius: 8px; border-radius: 8px;
padding: 11px 20px; padding: 11px 20px;
margin: 1px;
font-size: 0.9rem; font-size: 0.9rem;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
@@ -290,15 +291,7 @@
<body> <body>
<header> <header>
<div class="logo"> <h1>hier könnte ihre werbung stehen</h1>
<svg aria-label="yt-dlp downloader" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="8" fill="#e53935"/>
<path d="M10 13l8 5-8 5V13z" fill="white"/>
<path d="M20 13h6M20 18h6M20 23h6" stroke="white" stroke-width="2" stroke-linecap="round"/>
</svg>
<h1>yt-dlp Downloader</h1>
</div>
<p class="subtitle">Download videos &amp; audio from YouTube and 1000+ sites</p>
</header> </header>
<div class="card"> <div class="card">
@@ -315,28 +308,10 @@
</div> </div>
</div> </div>
<div class="options-row"> <div style="margin-top: 16px; display: flex;">
<div class="option-group"> <button class="btn" id="dl-btn-video" onclick="startDownload(true)" style="width:50%">Download Video</button>
<label class="opt-label" for="format-select">Format</label>
<select id="format-select" onchange="onFormatChange()">
<option value="video">Video (MP4)</option>
<option value="audio">Audio (MP3)</option>
</select>
</div>
<div class="option-group" id="quality-group">
<label class="opt-label" for="quality-select">Quality</label>
<select id="quality-select">
<option value="best">Best available</option>
<option value="1080">1080p</option>
<option value="720">720p</option>
<option value="480">480p</option>
<option value="360">360p</option>
</select>
</div>
</div>
<div style="margin-top:16px;"> <button class="btn" id="dl-btn-audio" onclick="startDownload(false)" style="width:50%">Download Audio</button>
<button class="btn" id="dl-btn" onclick="startDownload()" style="width:100%">Download</button>
</div> </div>
</div> </div>
@@ -359,12 +334,6 @@
return n + ' views'; return n + ' views';
} }
function onFormatChange() {
const fmt = document.getElementById('format-select').value;
document.getElementById('quality-group').style.opacity = fmt === 'audio' ? '0.4' : '1';
document.getElementById('quality-select').disabled = fmt === 'audio';
}
async function fetchInfo() { async function fetchInfo() {
const url = document.getElementById('url-input').value.trim(); const url = document.getElementById('url-input').value.trim();
if (!url) return; if (!url) return;
@@ -390,15 +359,19 @@
} }
} }
async function startDownload() { async function startDownload(isVideo) {
const url = document.getElementById('url-input').value.trim(); const url = document.getElementById('url-input').value.trim();
if (!url) { alert('Please enter a URL first.'); return; } if (!url) { alert('Please enter a URL first.'); return; }
const format = document.getElementById('format-select').value; const format = isVideo ? "video" : "audio";
const quality = document.getElementById('quality-select').value; const quality = "best";
const btn = document.getElementById('dl-btn'); const btnv = document.getElementById('dl-btn-video');
btn.disabled = true; btnv.disabled = true;
btn.innerHTML = '<span class="spinner"></span>Starting…'; btnv.innerHTML = '<span class="spinner"></span>Starting…';
const btna = document.getElementById('dl-btn-audio');
btna.disabled = true;
btna.innerHTML = '<span class="spinner"></span>Starting…';
try { try {
const res = await fetch('/api/download', { const res = await fetch('/api/download', {
@@ -413,8 +386,11 @@
} catch (e) { } catch (e) {
alert('Failed to start download: ' + e.message); alert('Failed to start download: ' + e.message);
} finally { } finally {
btn.disabled = false; btna.disabled = false;
btn.textContent = 'Download'; btna.textContent = 'Download Audio';
btnv.disabled = false;
btnv.textContent = 'Download Video';
} }
} }
@@ -482,7 +458,8 @@
} else if (job.status === 'processing') { } else if (job.status === 'processing') {
meta.textContent = 'Post-processing (merging / converting)…'; meta.textContent = 'Post-processing (merging / converting)…';
} else if (job.status === 'done') { } else if (job.status === 'done') {
meta.textContent = 'Complete · ' + job.filename.split('_').slice(1).join('_'); const goodname = job.filename.split('_').slice(1).join('_');
meta.textContent = 'Complete · ' + goodname;
if (!actions.querySelector('a')) { if (!actions.querySelector('a')) {
const a = document.createElement('a'); const a = document.createElement('a');
a.href = '/api/file/' + job.id; a.href = '/api/file/' + job.id;
@@ -490,8 +467,9 @@
a.style.fontSize = '0.82rem'; a.style.fontSize = '0.82rem';
a.style.padding = '7px 14px'; a.style.padding = '7px 14px';
a.textContent = '⬇ Download File'; a.textContent = '⬇ Download File';
a.download = ''; a.download = goodname;
actions.appendChild(a); actions.appendChild(a);
window.location.href = '/api/file/' + job.id;
} }
} else if (job.status === 'error') { } else if (job.status === 'error') {
meta.textContent = ''; meta.textContent = '';
@@ -502,7 +480,7 @@
// Allow pressing Enter in the URL field // Allow pressing Enter in the URL field
document.getElementById('url-input').addEventListener('keydown', (e) => { document.getElementById('url-input').addEventListener('keydown', (e) => {
if (e.key === 'Enter') startDownload(); if (e.key === 'Enter') startDownload(true);
}); });
</script> </script>
</body> </body>