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
|
FROM debian:stable-slim AS ffmpeg
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
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/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
RUN case "$TARGETARCH" in \
|
RUN set -euo pipefail; \
|
||||||
amd64) FFMPEG_PKG=ffmpeg-master-latest-linux64-gpl.tar.xz ;; \
|
case "$TARGETARCH" in \
|
||||||
arm64) FFMPEG_PKG=ffmpeg-master-latest-linuxarm64-gpl.tar.xz ;; \
|
amd64) ARCH_SUFFIX=linux64 ;; \
|
||||||
|
arm64) ARCH_SUFFIX=linuxarm64 ;; \
|
||||||
*) echo "Unsupported arch: $TARGETARCH" && exit 1 ;; \
|
*) echo "Unsupported arch: $TARGETARCH" && exit 1 ;; \
|
||||||
esac && \
|
esac; \
|
||||||
curl -fsSL -o /tmp/ffmpeg.tar.xz https://github.com/BtbN/FFmpeg-Builds/releases/latest/download/${FFMPEG_PKG} && \
|
ASSET_URL=$(curl -fsSL https://api.github.com/repos/BtbN/FFmpeg-Builds/releases/latest \
|
||||||
tar -xJf /tmp/ffmpeg.tar.xz -C /tmp && \
|
| 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
|
mv /tmp/ffmpeg-* /ffmpeg
|
||||||
|
|
||||||
# Stage 4: Prepare world-writable runtime directories
|
# Stage 4: Prepare world-writable runtime directories
|
||||||
FROM busybox:1.36.1-musl AS runtime-dirs
|
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 \
|
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
|
&& chmod -R 0777 /artifact
|
||||||
|
|
||||||
# Stage 5: Final application image (distroless)
|
# Stage 5: Final application image (distroless)
|
||||||
@@ -44,6 +53,9 @@ WORKDIR /app
|
|||||||
# Ensure Python finds vendored site-packages and unbuffered output
|
# Ensure Python finds vendored site-packages and unbuffered output
|
||||||
ENV PYTHONPATH=/python
|
ENV PYTHONPATH=/python
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV PYTHONUTF8=1
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
ENV LC_ALL=C.UTF-8
|
||||||
|
|
||||||
# Copy application code
|
# Copy application code
|
||||||
COPY --chown=65532:65532 . .
|
COPY --chown=65532:65532 . .
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
fastapi==0.116.1
|
fastapi==0.116.1
|
||||||
uvicorn[standard]==0.35.0
|
uvicorn[standard]==0.35.0
|
||||||
celery==5.5.3
|
celery==5.5.3
|
||||||
deezspot-spotizerr==2.7.4
|
deezspot-spotizerr==2.7.6
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
bcrypt==4.2.1
|
bcrypt==4.2.1
|
||||||
PyJWT==2.10.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(
|
dl.download_albumspo(
|
||||||
link_album=url, # Spotify URL
|
link_album=url, # Spotify URL
|
||||||
output_dir="./downloads",
|
output_dir="/app/downloads",
|
||||||
quality_download=quality, # Deezer quality
|
quality_download=quality, # Deezer quality
|
||||||
recursive_quality=recursive_quality,
|
recursive_quality=recursive_quality,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
@@ -159,7 +159,7 @@ def download_album(
|
|||||||
)
|
)
|
||||||
spo.download_album(
|
spo.download_album(
|
||||||
link_album=url, # Spotify URL
|
link_album=url, # Spotify URL
|
||||||
output_dir="./downloads",
|
output_dir="/app/downloads",
|
||||||
quality_download=fall_quality, # Spotify quality
|
quality_download=fall_quality, # Spotify quality
|
||||||
recursive_quality=recursive_quality,
|
recursive_quality=recursive_quality,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
@@ -216,7 +216,7 @@ def download_album(
|
|||||||
)
|
)
|
||||||
spo.download_album(
|
spo.download_album(
|
||||||
link_album=url,
|
link_album=url,
|
||||||
output_dir="./downloads",
|
output_dir="/app/downloads",
|
||||||
quality_download=quality,
|
quality_download=quality,
|
||||||
recursive_quality=recursive_quality,
|
recursive_quality=recursive_quality,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
@@ -260,7 +260,7 @@ def download_album(
|
|||||||
)
|
)
|
||||||
dl.download_albumdee( # Deezer URL, download via Deezer
|
dl.download_albumdee( # Deezer URL, download via Deezer
|
||||||
link_album=url,
|
link_album=url,
|
||||||
output_dir="./downloads",
|
output_dir="/app/downloads",
|
||||||
quality_download=quality,
|
quality_download=quality,
|
||||||
recursive_quality=recursive_quality,
|
recursive_quality=recursive_quality,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import subprocess
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
import sys
|
||||||
|
|
||||||
# Import Celery task utilities
|
# Import Celery task utilities
|
||||||
from .celery_config import get_config_params, MAX_CONCURRENT_DL
|
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.
|
# %h is replaced by celery with the actual hostname.
|
||||||
hostname = f"worker_{worker_name_suffix}@%h"
|
hostname = f"worker_{worker_name_suffix}@%h"
|
||||||
command = [
|
command = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
"celery",
|
"celery",
|
||||||
"-A",
|
"-A",
|
||||||
self.app_name,
|
self.app_name,
|
||||||
@@ -73,11 +76,14 @@ class CeleryManager:
|
|||||||
log_method = logger.info # Default log method
|
log_method = logger.info # Default log method
|
||||||
|
|
||||||
if error: # This is a stderr stream
|
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
|
log_method = logger.error
|
||||||
elif " - WARNING - " in line_stripped:
|
elif " - WARNING - " in line_stripped:
|
||||||
log_method = logger.warning
|
log_method = logger.warning
|
||||||
|
|
||||||
log_method(f"{log_prefix}: {line_stripped}")
|
log_method(f"{log_prefix}: {line_stripped}")
|
||||||
elif (
|
elif (
|
||||||
self.stop_event.is_set()
|
self.stop_event.is_set()
|
||||||
@@ -151,7 +157,7 @@ class CeleryManager:
|
|||||||
queues="utility_tasks,default", # Listen to utility and default
|
queues="utility_tasks,default", # Listen to utility and default
|
||||||
concurrency=5, # Increased concurrency for SSE updates and utility tasks
|
concurrency=5, # Increased concurrency for SSE updates and utility tasks
|
||||||
worker_name_suffix="utw", # Utility Worker
|
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(
|
logger.info(
|
||||||
f"Starting Celery Utility Worker with command: {' '.join(utility_cmd)}"
|
f"Starting Celery Utility Worker with command: {' '.join(utility_cmd)}"
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ def download_playlist(
|
|||||||
)
|
)
|
||||||
dl.download_playlistspo(
|
dl.download_playlistspo(
|
||||||
link_playlist=url, # Spotify URL
|
link_playlist=url, # Spotify URL
|
||||||
output_dir="./downloads",
|
output_dir="/app/downloads",
|
||||||
quality_download=quality, # Deezer quality
|
quality_download=quality, # Deezer quality
|
||||||
recursive_quality=recursive_quality,
|
recursive_quality=recursive_quality,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
@@ -161,7 +161,7 @@ def download_playlist(
|
|||||||
)
|
)
|
||||||
spo.download_playlist(
|
spo.download_playlist(
|
||||||
link_playlist=url, # Spotify URL
|
link_playlist=url, # Spotify URL
|
||||||
output_dir="./downloads",
|
output_dir="/app/downloads",
|
||||||
quality_download=fall_quality, # Spotify quality
|
quality_download=fall_quality, # Spotify quality
|
||||||
recursive_quality=recursive_quality,
|
recursive_quality=recursive_quality,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
@@ -224,7 +224,7 @@ def download_playlist(
|
|||||||
)
|
)
|
||||||
spo.download_playlist(
|
spo.download_playlist(
|
||||||
link_playlist=url,
|
link_playlist=url,
|
||||||
output_dir="./downloads",
|
output_dir="/app/downloads",
|
||||||
quality_download=quality,
|
quality_download=quality,
|
||||||
recursive_quality=recursive_quality,
|
recursive_quality=recursive_quality,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
@@ -268,7 +268,7 @@ def download_playlist(
|
|||||||
)
|
)
|
||||||
dl.download_playlistdee( # Deezer URL, download via Deezer
|
dl.download_playlistdee( # Deezer URL, download via Deezer
|
||||||
link_playlist=url,
|
link_playlist=url,
|
||||||
output_dir="./downloads",
|
output_dir="/app/downloads",
|
||||||
quality_download=quality,
|
quality_download=quality,
|
||||||
recursive_quality=recursive_quality, # Usually False for playlists to get individual track qualities
|
recursive_quality=recursive_quality, # Usually False for playlists to get individual track qualities
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ def download_track(
|
|||||||
# download_trackspo means: Spotify URL, download via Deezer
|
# download_trackspo means: Spotify URL, download via Deezer
|
||||||
dl.download_trackspo(
|
dl.download_trackspo(
|
||||||
link_track=url, # Spotify URL
|
link_track=url, # Spotify URL
|
||||||
output_dir="./downloads",
|
output_dir="/app/downloads",
|
||||||
quality_download=quality, # Deezer quality
|
quality_download=quality, # Deezer quality
|
||||||
recursive_quality=recursive_quality,
|
recursive_quality=recursive_quality,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
@@ -153,7 +153,7 @@ def download_track(
|
|||||||
)
|
)
|
||||||
spo.download_track(
|
spo.download_track(
|
||||||
link_track=url, # Spotify URL
|
link_track=url, # Spotify URL
|
||||||
output_dir="./downloads",
|
output_dir="/app/downloads",
|
||||||
quality_download=fall_quality, # Spotify quality
|
quality_download=fall_quality, # Spotify quality
|
||||||
recursive_quality=recursive_quality,
|
recursive_quality=recursive_quality,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
@@ -169,7 +169,7 @@ def download_track(
|
|||||||
convert_to=convert_to,
|
convert_to=convert_to,
|
||||||
bitrate=bitrate,
|
bitrate=bitrate,
|
||||||
artist_separator=artist_separator,
|
artist_separator=artist_separator,
|
||||||
real_time_multiplier=real_time_multiplier,
|
spotify_metadata=spotify_metadata,
|
||||||
pad_number_width=pad_number_width,
|
pad_number_width=pad_number_width,
|
||||||
)
|
)
|
||||||
print(
|
print(
|
||||||
@@ -211,7 +211,7 @@ def download_track(
|
|||||||
)
|
)
|
||||||
spo.download_track(
|
spo.download_track(
|
||||||
link_track=url,
|
link_track=url,
|
||||||
output_dir="./downloads",
|
output_dir="/app/downloads",
|
||||||
quality_download=quality,
|
quality_download=quality,
|
||||||
recursive_quality=recursive_quality,
|
recursive_quality=recursive_quality,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
@@ -254,7 +254,7 @@ def download_track(
|
|||||||
)
|
)
|
||||||
dl.download_trackdee( # Deezer URL, download via Deezer
|
dl.download_trackdee( # Deezer URL, download via Deezer
|
||||||
link_track=url,
|
link_track=url,
|
||||||
output_dir="./downloads",
|
output_dir="/app/downloads",
|
||||||
quality_download=quality,
|
quality_download=quality,
|
||||||
recursive_quality=recursive_quality,
|
recursive_quality=recursive_quality,
|
||||||
recursive_download=False,
|
recursive_download=False,
|
||||||
|
|||||||
@@ -1098,7 +1098,7 @@ def update_playlist_m3u_file(playlist_spotify_id: str):
|
|||||||
# Get configuration settings
|
# Get configuration settings
|
||||||
|
|
||||||
output_dir = (
|
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
|
# Get all tracks for the playlist
|
||||||
@@ -1125,14 +1125,14 @@ def update_playlist_m3u_file(playlist_spotify_id: str):
|
|||||||
skipped_missing_final_path = 0
|
skipped_missing_final_path = 0
|
||||||
|
|
||||||
for track in tracks:
|
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")
|
final_path = track.get("final_path")
|
||||||
if not final_path:
|
if not final_path:
|
||||||
skipped_missing_final_path += 1
|
skipped_missing_final_path += 1
|
||||||
continue
|
continue
|
||||||
normalized = str(final_path).replace("\\", "/")
|
normalized = str(final_path).replace("\\", "/")
|
||||||
if normalized.startswith("./downloads/"):
|
if normalized.startswith("/app/downloads/"):
|
||||||
relative_path = normalized.replace("./downloads/", "../", 1)
|
relative_path = normalized.replace("/app/downloads/", "../", 1)
|
||||||
elif "/downloads/" in normalized.lower():
|
elif "/downloads/" in normalized.lower():
|
||||||
idx = normalized.lower().rfind("/downloads/")
|
idx = normalized.lower().rfind("/downloads/")
|
||||||
relative_path = "../" + normalized[idx + len("/downloads/") :]
|
relative_path = "../" + normalized[idx + len("/downloads/") :]
|
||||||
|
|||||||
Reference in New Issue
Block a user