fix: Distroless ffmpeg support
This commit is contained in:
26
Dockerfile
26
Dockerfile
@@ -18,20 +18,29 @@ RUN uv pip install --target /python -r requirements.txt
|
||||
FROM debian:stable-slim AS ffmpeg
|
||||
ARG TARGETARCH
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl xz-utils \
|
||||
ca-certificates curl xz-utils jq \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN case "$TARGETARCH" in \
|
||||
amd64) FFMPEG_PKG=ffmpeg-master-latest-linux64-gpl.tar.xz ;; \
|
||||
arm64) FFMPEG_PKG=ffmpeg-master-latest-linuxarm64-gpl.tar.xz ;; \
|
||||
RUN set -euo pipefail; \
|
||||
case "$TARGETARCH" in \
|
||||
amd64) ARCH_SUFFIX=linux64 ;; \
|
||||
arm64) ARCH_SUFFIX=linuxarm64 ;; \
|
||||
*) echo "Unsupported arch: $TARGETARCH" && exit 1 ;; \
|
||||
esac && \
|
||||
curl -fsSL -o /tmp/ffmpeg.tar.xz https://github.com/BtbN/FFmpeg-Builds/releases/latest/download/${FFMPEG_PKG} && \
|
||||
tar -xJf /tmp/ffmpeg.tar.xz -C /tmp && \
|
||||
esac; \
|
||||
ASSET_URL=$(curl -fsSL https://api.github.com/repos/BtbN/FFmpeg-Builds/releases/latest \
|
||||
| jq -r ".assets[] | select(.name | endswith(\"${ARCH_SUFFIX}-gpl.tar.xz\")) | .browser_download_url" \
|
||||
| head -n1); \
|
||||
if [ -z "$ASSET_URL" ]; then \
|
||||
echo "Failed to resolve FFmpeg asset for arch ${ARCH_SUFFIX}" && exit 1; \
|
||||
fi; \
|
||||
echo "Fetching FFmpeg from: $ASSET_URL"; \
|
||||
curl -fsSL -o /tmp/ffmpeg.tar.xz "$ASSET_URL"; \
|
||||
tar -xJf /tmp/ffmpeg.tar.xz -C /tmp; \
|
||||
mv /tmp/ffmpeg-* /ffmpeg
|
||||
|
||||
# Stage 4: Prepare world-writable runtime directories
|
||||
FROM busybox:1.36.1-musl AS runtime-dirs
|
||||
RUN mkdir -p /artifact/downloads /artifact/data/config /artifact/data/creds /artifact/data/watch /artifact/data/history /artifact/logs/tasks \
|
||||
&& touch /artifact/.cache \
|
||||
&& chmod -R 0777 /artifact
|
||||
|
||||
# Stage 5: Final application image (distroless)
|
||||
@@ -44,6 +53,9 @@ WORKDIR /app
|
||||
# Ensure Python finds vendored site-packages and unbuffered output
|
||||
ENV PYTHONPATH=/python
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV PYTHONUTF8=1
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
|
||||
# Copy application code
|
||||
COPY --chown=65532:65532 . .
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
fastapi==0.116.1
|
||||
uvicorn[standard]==0.35.0
|
||||
celery==5.5.3
|
||||
deezspot-spotizerr==2.7.4
|
||||
deezspot-spotizerr==2.7.6
|
||||
httpx==0.28.1
|
||||
bcrypt==4.2.1
|
||||
PyJWT==2.10.1
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -101,7 +101,7 @@ def download_album(
|
||||
)
|
||||
dl.download_albumspo(
|
||||
link_album=url, # Spotify URL
|
||||
output_dir="./downloads",
|
||||
output_dir="/app/downloads",
|
||||
quality_download=quality, # Deezer quality
|
||||
recursive_quality=recursive_quality,
|
||||
recursive_download=False,
|
||||
@@ -159,7 +159,7 @@ def download_album(
|
||||
)
|
||||
spo.download_album(
|
||||
link_album=url, # Spotify URL
|
||||
output_dir="./downloads",
|
||||
output_dir="/app/downloads",
|
||||
quality_download=fall_quality, # Spotify quality
|
||||
recursive_quality=recursive_quality,
|
||||
recursive_download=False,
|
||||
@@ -216,7 +216,7 @@ def download_album(
|
||||
)
|
||||
spo.download_album(
|
||||
link_album=url,
|
||||
output_dir="./downloads",
|
||||
output_dir="/app/downloads",
|
||||
quality_download=quality,
|
||||
recursive_quality=recursive_quality,
|
||||
recursive_download=False,
|
||||
@@ -260,7 +260,7 @@ def download_album(
|
||||
)
|
||||
dl.download_albumdee( # Deezer URL, download via Deezer
|
||||
link_album=url,
|
||||
output_dir="./downloads",
|
||||
output_dir="/app/downloads",
|
||||
quality_download=quality,
|
||||
recursive_quality=recursive_quality,
|
||||
recursive_download=False,
|
||||
|
||||
@@ -2,6 +2,7 @@ import subprocess
|
||||
import logging
|
||||
import time
|
||||
import threading
|
||||
import sys
|
||||
|
||||
# Import Celery task utilities
|
||||
from .celery_config import get_config_params, MAX_CONCURRENT_DL
|
||||
@@ -46,6 +47,8 @@ class CeleryManager:
|
||||
# %h is replaced by celery with the actual hostname.
|
||||
hostname = f"worker_{worker_name_suffix}@%h"
|
||||
command = [
|
||||
sys.executable,
|
||||
"-m",
|
||||
"celery",
|
||||
"-A",
|
||||
self.app_name,
|
||||
@@ -73,11 +76,14 @@ class CeleryManager:
|
||||
log_method = logger.info # Default log method
|
||||
|
||||
if error: # This is a stderr stream
|
||||
if " - ERROR - " in line_stripped or " - CRITICAL - " in line_stripped:
|
||||
if (
|
||||
" - ERROR - " in line_stripped
|
||||
or " - CRITICAL - " in line_stripped
|
||||
):
|
||||
log_method = logger.error
|
||||
elif " - WARNING - " in line_stripped:
|
||||
log_method = logger.warning
|
||||
|
||||
|
||||
log_method(f"{log_prefix}: {line_stripped}")
|
||||
elif (
|
||||
self.stop_event.is_set()
|
||||
@@ -151,7 +157,7 @@ class CeleryManager:
|
||||
queues="utility_tasks,default", # Listen to utility and default
|
||||
concurrency=5, # Increased concurrency for SSE updates and utility tasks
|
||||
worker_name_suffix="utw", # Utility Worker
|
||||
log_level="ERROR" # Reduce log verbosity for utility worker (only errors)
|
||||
log_level="ERROR", # Reduce log verbosity for utility worker (only errors)
|
||||
)
|
||||
logger.info(
|
||||
f"Starting Celery Utility Worker with command: {' '.join(utility_cmd)}"
|
||||
|
||||
@@ -98,7 +98,7 @@ def download_playlist(
|
||||
)
|
||||
dl.download_playlistspo(
|
||||
link_playlist=url, # Spotify URL
|
||||
output_dir="./downloads",
|
||||
output_dir="/app/downloads",
|
||||
quality_download=quality, # Deezer quality
|
||||
recursive_quality=recursive_quality,
|
||||
recursive_download=False,
|
||||
@@ -161,7 +161,7 @@ def download_playlist(
|
||||
)
|
||||
spo.download_playlist(
|
||||
link_playlist=url, # Spotify URL
|
||||
output_dir="./downloads",
|
||||
output_dir="/app/downloads",
|
||||
quality_download=fall_quality, # Spotify quality
|
||||
recursive_quality=recursive_quality,
|
||||
recursive_download=False,
|
||||
@@ -224,7 +224,7 @@ def download_playlist(
|
||||
)
|
||||
spo.download_playlist(
|
||||
link_playlist=url,
|
||||
output_dir="./downloads",
|
||||
output_dir="/app/downloads",
|
||||
quality_download=quality,
|
||||
recursive_quality=recursive_quality,
|
||||
recursive_download=False,
|
||||
@@ -268,7 +268,7 @@ def download_playlist(
|
||||
)
|
||||
dl.download_playlistdee( # Deezer URL, download via Deezer
|
||||
link_playlist=url,
|
||||
output_dir="./downloads",
|
||||
output_dir="/app/downloads",
|
||||
quality_download=quality,
|
||||
recursive_quality=recursive_quality, # Usually False for playlists to get individual track qualities
|
||||
recursive_download=False,
|
||||
|
||||
@@ -94,7 +94,7 @@ def download_track(
|
||||
# download_trackspo means: Spotify URL, download via Deezer
|
||||
dl.download_trackspo(
|
||||
link_track=url, # Spotify URL
|
||||
output_dir="./downloads",
|
||||
output_dir="/app/downloads",
|
||||
quality_download=quality, # Deezer quality
|
||||
recursive_quality=recursive_quality,
|
||||
recursive_download=False,
|
||||
@@ -153,7 +153,7 @@ def download_track(
|
||||
)
|
||||
spo.download_track(
|
||||
link_track=url, # Spotify URL
|
||||
output_dir="./downloads",
|
||||
output_dir="/app/downloads",
|
||||
quality_download=fall_quality, # Spotify quality
|
||||
recursive_quality=recursive_quality,
|
||||
recursive_download=False,
|
||||
@@ -169,7 +169,7 @@ def download_track(
|
||||
convert_to=convert_to,
|
||||
bitrate=bitrate,
|
||||
artist_separator=artist_separator,
|
||||
real_time_multiplier=real_time_multiplier,
|
||||
spotify_metadata=spotify_metadata,
|
||||
pad_number_width=pad_number_width,
|
||||
)
|
||||
print(
|
||||
@@ -211,7 +211,7 @@ def download_track(
|
||||
)
|
||||
spo.download_track(
|
||||
link_track=url,
|
||||
output_dir="./downloads",
|
||||
output_dir="/app/downloads",
|
||||
quality_download=quality,
|
||||
recursive_quality=recursive_quality,
|
||||
recursive_download=False,
|
||||
@@ -254,7 +254,7 @@ def download_track(
|
||||
)
|
||||
dl.download_trackdee( # Deezer URL, download via Deezer
|
||||
link_track=url,
|
||||
output_dir="./downloads",
|
||||
output_dir="/app/downloads",
|
||||
quality_download=quality,
|
||||
recursive_quality=recursive_quality,
|
||||
recursive_download=False,
|
||||
|
||||
@@ -1098,7 +1098,7 @@ def update_playlist_m3u_file(playlist_spotify_id: str):
|
||||
# Get configuration settings
|
||||
|
||||
output_dir = (
|
||||
"./downloads" # This matches the output_dir used in download functions
|
||||
"/app/downloads" # This matches the output_dir used in download functions
|
||||
)
|
||||
|
||||
# Get all tracks for the playlist
|
||||
@@ -1125,14 +1125,14 @@ def update_playlist_m3u_file(playlist_spotify_id: str):
|
||||
skipped_missing_final_path = 0
|
||||
|
||||
for track in tracks:
|
||||
# Use final_path from deezspot summary and convert from ./downloads to ../ relative path
|
||||
# Use final_path from deezspot summary and convert from /app/downloads to ../ relative path
|
||||
final_path = track.get("final_path")
|
||||
if not final_path:
|
||||
skipped_missing_final_path += 1
|
||||
continue
|
||||
normalized = str(final_path).replace("\\", "/")
|
||||
if normalized.startswith("./downloads/"):
|
||||
relative_path = normalized.replace("./downloads/", "../", 1)
|
||||
if normalized.startswith("/app/downloads/"):
|
||||
relative_path = normalized.replace("/app/downloads/", "../", 1)
|
||||
elif "/downloads/" in normalized.lower():
|
||||
idx = normalized.lower().rfind("/downloads/")
|
||||
relative_path = "../" + normalized[idx + len("/downloads/") :]
|
||||
|
||||
Reference in New Issue
Block a user