fix: ensure distroless compatibility
This commit is contained in:
@@ -69,12 +69,6 @@ AUDIO_FORMATS = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_ffmpeg_available():
|
|
||||||
"""Check if FFmpeg is installed and available."""
|
|
||||||
if which("ffmpeg") is None:
|
|
||||||
logger.error("FFmpeg is not installed or not in PATH. Audio conversion is unavailable.")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_output_path(input_path, format_name):
|
def get_output_path(input_path, format_name):
|
||||||
"""Get the output path with the new extension based on the format."""
|
"""Get the output path with the new extension based on the format."""
|
||||||
@@ -93,6 +87,7 @@ def get_output_path(input_path, format_name):
|
|||||||
|
|
||||||
return os.path.join(dir_name, new_file_name)
|
return os.path.join(dir_name, new_file_name)
|
||||||
|
|
||||||
|
|
||||||
def register_active_download(path):
|
def register_active_download(path):
|
||||||
"""
|
"""
|
||||||
Register a file as being actively downloaded.
|
Register a file as being actively downloaded.
|
||||||
@@ -134,8 +129,14 @@ def convert_audio(input_path, format_name=None, bitrate=None, register_func=None
|
|||||||
global unregister_active_download
|
global unregister_active_download
|
||||||
unregister_active_download = unregister_func
|
unregister_active_download = unregister_func
|
||||||
|
|
||||||
# If no format specified or FFmpeg not available, return the original path
|
# If no format specified, return the original path
|
||||||
if not format_name or not check_ffmpeg_available():
|
if not format_name:
|
||||||
|
return input_path
|
||||||
|
|
||||||
|
# Resolve ffmpeg path explicitly (distroless-safe)
|
||||||
|
ffmpeg_path = which("ffmpeg") or "/usr/local/bin/ffmpeg"
|
||||||
|
if not os.path.exists(ffmpeg_path):
|
||||||
|
logger.error(f"FFmpeg is not available (looked for '{ffmpeg_path}'). Audio conversion is unavailable.")
|
||||||
return input_path
|
return input_path
|
||||||
|
|
||||||
# Validate format and get format details
|
# Validate format and get format details
|
||||||
@@ -162,16 +163,9 @@ def convert_audio(input_path, format_name=None, bitrate=None, register_func=None
|
|||||||
|
|
||||||
# Skip conversion if the file is already in the target format and bitrate matches (or not applicable)
|
# Skip conversion if the file is already in the target format and bitrate matches (or not applicable)
|
||||||
if input_path.lower().endswith(format_details["extension"].lower()):
|
if input_path.lower().endswith(format_details["extension"].lower()):
|
||||||
# For lossless, or if effective_bitrate matches (or no specific bitrate needed for format)
|
|
||||||
if format_details["default_bitrate"] is None: # Lossless
|
if format_details["default_bitrate"] is None: # Lossless
|
||||||
logger.info(f"File {input_path} is already in {format_name_upper} (lossless) format. Skipping conversion.")
|
logger.info(f"File {input_path} is already in {format_name_upper} (lossless) format. Skipping conversion.")
|
||||||
return input_path
|
return input_path
|
||||||
# For lossy, if no specific bitrate was relevant (already handled by effective_bitrate logic)
|
|
||||||
# This condition might be redundant if we always convert to ensure bitrate.
|
|
||||||
# Let's assume for now, if it's already the right extension, we don't re-encode unless bitrate implies so.
|
|
||||||
# However, the original logic converted if bitrate was specified even for same extension.
|
|
||||||
# To maintain similar behavior: if a bitrate is effectively set for a lossy format, we proceed.
|
|
||||||
# If effective_bitrate is None (e.g. for FLAC, WAV), and extension matches, skip.
|
|
||||||
if not effective_bitrate and format_details["default_bitrate"] is not None:
|
if not effective_bitrate and format_details["default_bitrate"] is not None:
|
||||||
logger.info(f"File {input_path} is already in {format_name_upper} format with a suitable bitrate. Skipping conversion.")
|
logger.info(f"File {input_path} is already in {format_name_upper} format with a suitable bitrate. Skipping conversion.")
|
||||||
return input_path
|
return input_path
|
||||||
@@ -186,10 +180,10 @@ def convert_audio(input_path, format_name=None, bitrate=None, register_func=None
|
|||||||
register_active_download(temp_output)
|
register_active_download(temp_output)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmd = ["ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-i", input_path]
|
cmd = [ffmpeg_path, "-y", "-hide_banner", "-loglevel", "error", "-i", input_path]
|
||||||
|
|
||||||
# Add bitrate parameter for lossy formats if an effective_bitrate is set
|
# Add bitrate parameter for lossy formats if an effective_bitrate is set
|
||||||
if effective_bitrate and format_details["bitrates"]: # format_details["bitrates"] implies lossy
|
if effective_bitrate and format_details["bitrates"]: # lossy
|
||||||
cmd.extend(["-b:a", effective_bitrate])
|
cmd.extend(["-b:a", effective_bitrate])
|
||||||
|
|
||||||
# Add codec parameter
|
# Add codec parameter
|
||||||
@@ -234,6 +228,15 @@ def convert_audio(input_path, format_name=None, bitrate=None, register_func=None
|
|||||||
logger.info(f"Successfully converted to {format_name_upper}" + (f" at {effective_bitrate}" if effective_bitrate else ""))
|
logger.info(f"Successfully converted to {format_name_upper}" + (f" at {effective_bitrate}" if effective_bitrate else ""))
|
||||||
return output_path
|
return output_path
|
||||||
|
|
||||||
|
except FileNotFoundError as fnf:
|
||||||
|
logger.error(f"FFmpeg executable not found at '{ffmpeg_path}'. Conversion aborted.")
|
||||||
|
if exists(temp_output):
|
||||||
|
try:
|
||||||
|
os.remove(temp_output)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
unregister_active_download(temp_output)
|
||||||
|
return input_path
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error during audio conversion: {str(e)}")
|
logger.error(f"Error during audio conversion: {str(e)}")
|
||||||
# Clean up temp files
|
# Clean up temp files
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ from os import (
|
|||||||
system,
|
system,
|
||||||
replace as os_replace,
|
replace as os_replace,
|
||||||
)
|
)
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
from deezspot.models.download import (
|
from deezspot.models.download import (
|
||||||
Track,
|
Track,
|
||||||
Album,
|
Album,
|
||||||
@@ -263,8 +265,23 @@ class EASY_DW:
|
|||||||
try:
|
try:
|
||||||
# Step 1: First convert the OGG file to standard format (copy operation)
|
# Step 1: First convert the OGG file to standard format (copy operation)
|
||||||
# Output is og_song_path_for_ogg_output
|
# Output is og_song_path_for_ogg_output
|
||||||
ffmpeg_cmd = f'ffmpeg -y -hide_banner -loglevel error -i "{temp_filename}" -c:a copy "{og_song_path_for_ogg_output}"'
|
# Resolve ffmpeg path explicitly to avoid PATH issues in distroless
|
||||||
system(ffmpeg_cmd) # Creates/overwrites og_song_path_for_ogg_output
|
ffmpeg_path = shutil.which("ffmpeg") or "/usr/local/bin/ffmpeg"
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
ffmpeg_path, "-y", "-hide_banner", "-loglevel", "error",
|
||||||
|
"-i", temp_filename, "-c:a", "copy", og_song_path_for_ogg_output
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
check=False
|
||||||
|
)
|
||||||
|
if result.returncode != 0 or not os.path.exists(og_song_path_for_ogg_output):
|
||||||
|
raise RuntimeError(f"ffmpeg remux failed (rc={result.returncode}). stderr: {result.stderr.strip()}")
|
||||||
|
except FileNotFoundError as fnf:
|
||||||
|
raise RuntimeError(f"ffmpeg not found: attempted '{ffmpeg_path}'. Ensure it is present in PATH.") from fnf
|
||||||
|
|
||||||
# temp_filename has been processed. Unregister and remove it.
|
# temp_filename has been processed. Unregister and remove it.
|
||||||
# CURRENT_DOWNLOAD was temp_filename.
|
# CURRENT_DOWNLOAD was temp_filename.
|
||||||
@@ -1024,16 +1041,27 @@ def download_cli(preferences: Preferences) -> None:
|
|||||||
__not_interface = preferences.not_interface
|
__not_interface = preferences.not_interface
|
||||||
__quality_download = preferences.quality_download
|
__quality_download = preferences.quality_download
|
||||||
__recursive_download = preferences.recursive_download
|
__recursive_download = preferences.recursive_download
|
||||||
cmd = f"deez-dw.py -so spo -l \"{__link}\" "
|
# Build argv list instead of shell string (distroless-safe)
|
||||||
|
argv = ["deez-dw.py", "-so", "spo", "-l", __link]
|
||||||
if __output_dir:
|
if __output_dir:
|
||||||
cmd += f"-o {__output_dir} "
|
argv += ["-o", str(__output_dir)]
|
||||||
if __not_interface:
|
if __not_interface:
|
||||||
cmd += f"-g "
|
argv += ["-g"]
|
||||||
if __quality_download:
|
if __quality_download:
|
||||||
cmd += f"-q {__quality_download} "
|
argv += ["-q", str(__quality_download)]
|
||||||
if __recursive_download:
|
if __recursive_download:
|
||||||
cmd += f"-rd "
|
argv += ["-rd"]
|
||||||
system(cmd)
|
prog = shutil.which(argv[0])
|
||||||
|
if not prog:
|
||||||
|
logger.error("deez-dw.py CLI not found in PATH; cannot run download_cli in this environment.")
|
||||||
|
return
|
||||||
|
argv[0] = prog
|
||||||
|
try:
|
||||||
|
result = subprocess.run(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
logger.error(f"deez-dw.py exited with {result.returncode}: {result.stderr.strip()}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to execute deez-dw.py: {e}")
|
||||||
|
|
||||||
class DW_TRACK:
|
class DW_TRACK:
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|||||||
Reference in New Issue
Block a user