Chore: Simplify ffmpeg version check
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 16 Jan 2025 20:45:59 +0000 (21:45 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 16 Jan 2025 20:45:59 +0000 (21:45 +0100)
music_assistant/controllers/streams.py
music_assistant/helpers/audio.py
music_assistant/helpers/ffmpeg.py

index ebca6827607f620c0e80aceade9a9fa90a4bf5f8..ed22d50510ced59bd25818e2096e8ef618e17ed4 100644 (file)
@@ -50,7 +50,6 @@ from music_assistant.constants import (
 )
 from music_assistant.helpers.audio import LOGGER as AUDIO_LOGGER
 from music_assistant.helpers.audio import (
-    check_audio_support,
     crossfade_pcm_parts,
     get_chunksize,
     get_hls_substream,
@@ -61,7 +60,7 @@ from music_assistant.helpers.audio import (
     get_stream_details,
 )
 from music_assistant.helpers.ffmpeg import LOGGER as FFMPEG_LOGGER
-from music_assistant.helpers.ffmpeg import get_ffmpeg_stream
+from music_assistant.helpers.ffmpeg import check_ffmpeg_version, get_ffmpeg_stream
 from music_assistant.helpers.util import get_ip, get_ips, select_free_port, try_parse_bool
 from music_assistant.helpers.webserver import Webserver
 from music_assistant.models.core_controller import CoreController
@@ -216,25 +215,11 @@ class StreamsController(CoreController):
 
     async def setup(self, config: CoreConfig) -> None:
         """Async initialize of module."""
-        ffmpeg_present, libsoxr_support, version = await check_audio_support()
-        major_version = int("".join(char for char in version.split(".")[0] if not char.isalpha()))
-        if not ffmpeg_present:
-            self.logger.error("FFmpeg binary not found on your system, playback will NOT work!.")
-        elif major_version < 6:
-            self.logger.error("FFMpeg version is too old, you may run into playback issues.")
-        elif not libsoxr_support:
-            self.logger.warning(
-                "FFmpeg version found without libsoxr support, "
-                "highest quality audio not available. "
-            )
-        self.logger.info(
-            "Detected ffmpeg version %s %s",
-            version,
-            "with libsoxr support" if libsoxr_support else "",
-        )
         # copy log level to audio/ffmpeg loggers
         AUDIO_LOGGER.setLevel(self.logger.level)
         FFMPEG_LOGGER.setLevel(self.logger.level)
+        # perform check for ffmpeg version
+        await check_ffmpeg_version()
         # start the webserver
         self.publish_port = config.get_value(CONF_BIND_PORT)
         self.publish_ip = config.get_value(CONF_PUBLISH_IP)
index f6170da019bae049cc28e8a895cc81fc5fdc5165..5ce3b6b4f012a012f49dfef07339b8b7687490f6 100644 (file)
@@ -29,7 +29,6 @@ from music_assistant_models.errors import (
     MusicAssistantError,
     ProviderUnavailableError,
 )
-from music_assistant_models.helpers import set_global_cache_values
 from music_assistant_models.streamdetails import AudioFormat
 
 from music_assistant.constants import (
@@ -47,7 +46,7 @@ from music_assistant.helpers.util import clean_stream_title
 from .dsp import filter_to_ffmpeg_params
 from .ffmpeg import FFMpeg, get_ffmpeg_stream
 from .playlists import IsHLSPlaylist, PlaylistItem, fetch_playlist, parse_m3u
-from .process import AsyncProcess, check_output, communicate
+from .process import AsyncProcess, communicate
 from .util import TimedAsyncGenerator, create_tempfile, detect_charset
 
 if TYPE_CHECKING:
@@ -835,21 +834,6 @@ async def get_file_stream(
             yield data
 
 
-async def check_audio_support() -> tuple[bool, bool, str]:
-    """Check if ffmpeg is present (with/without libsoxr support)."""
-    # check for FFmpeg presence
-    returncode, output = await check_output("ffmpeg", "-version")
-    ffmpeg_present = returncode == 0 and "FFmpeg" in output.decode()
-
-    # use globals as in-memory cache
-    version = output.decode().split("ffmpeg version ")[1].split(" ")[0].split("-")[0]
-    libsoxr_support = "enable-libsoxr" in output.decode()
-    result = (ffmpeg_present, libsoxr_support, version)
-    # store in global cache for easy access by 'get_ffmpeg_args'
-    await set_global_cache_values({"ffmpeg_support": result})
-    return result
-
-
 async def get_preview_stream(
     mass: MusicAssistant,
     provider_instance_id_or_domain: str,
index 8ff24e84e73b19f2a140e404e2e8782bd929ed26..82ac1b511133471929aa64728e0cc96e4b8a3cba 100644 (file)
@@ -7,15 +7,15 @@ import logging
 import time
 from collections import deque
 from collections.abc import AsyncGenerator
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Final
 
 from music_assistant_models.enums import ContentType
 from music_assistant_models.errors import AudioError
-from music_assistant_models.helpers import get_global_cache_value
+from music_assistant_models.helpers import get_global_cache_value, set_global_cache_values
 
 from music_assistant.constants import VERBOSE_LOG_LEVEL
 
-from .process import AsyncProcess
+from .process import AsyncProcess, check_output
 from .util import TimedAsyncGenerator, close_async_generator
 
 if TYPE_CHECKING:
@@ -23,6 +23,7 @@ if TYPE_CHECKING:
 
 LOGGER = logging.getLogger("ffmpeg")
 MINIMAL_FFMPEG_VERSION = 6
+CACHE_ATTR_LIBSOXR_PRESENT: Final[str] = "libsoxr_present"
 
 
 class FFMpeg(AsyncProcess):
@@ -186,7 +187,7 @@ async def get_ffmpeg_stream(
             yield chunk
 
 
-def get_ffmpeg_args(  # noqa: PLR0915
+def get_ffmpeg_args(
     input_format: AudioFormat,
     output_format: AudioFormat,
     filter_params: list[str],
@@ -199,24 +200,6 @@ def get_ffmpeg_args(  # noqa: PLR0915
     """Collect all args to send to the ffmpeg process."""
     if extra_args is None:
         extra_args = []
-    ffmpeg_present, libsoxr_support, version = get_global_cache_value("ffmpeg_support")
-    if not ffmpeg_present:
-        msg = (
-            "FFmpeg binary is missing from system."
-            "Please install ffmpeg on your OS to enable playback."
-        )
-        raise AudioError(
-            msg,
-        )
-
-    major_version = int("".join(char for char in version.split(".")[0] if not char.isalpha()))
-    if major_version < MINIMAL_FFMPEG_VERSION:
-        msg = (
-            f"FFmpeg version {version} is not supported. "
-            f"Minimal version required is {MINIMAL_FFMPEG_VERSION}."
-        )
-        raise AudioError(msg)
-
     # generic args
     generic_args = [
         "ffmpeg",
@@ -311,8 +294,9 @@ def get_ffmpeg_args(  # noqa: PLR0915
         input_format.sample_rate != output_format.sample_rate
         or input_format.bit_depth > output_format.bit_depth
     ):
-        # prefer resampling with libsoxr due to its high quality (if its available)
-        # but skip libsoxr if loudnorm filter is present, due to this bug:
+        libsoxr_support = get_global_cache_value(CACHE_ATTR_LIBSOXR_PRESENT)
+        # prefer resampling with libsoxr due to its high quality
+        # but skip if loudnorm filter is present, due to this bug:
         # https://trac.ffmpeg.org/ticket/11323
         loudnorm_present = any("loudnorm" in f for f in filter_params)
         if libsoxr_support and not loudnorm_present:
@@ -334,3 +318,38 @@ def get_ffmpeg_args(  # noqa: PLR0915
         extra_args += ["-af", ",".join(filter_params)]
 
     return generic_args + input_args + extra_args + output_args
+
+
+async def check_ffmpeg_version() -> None:
+    """Check if ffmpeg is present (with libsoxr support)."""
+    # check for FFmpeg presence
+    returncode, output = await check_output("ffmpeg", "-version")
+    ffmpeg_present = returncode == 0 and "FFmpeg" in output.decode()
+
+    # use globals as in-memory cache
+    version = output.decode().split("ffmpeg version ")[1].split(" ")[0].split("-")[0]
+    libsoxr_support = "enable-libsoxr" in output.decode()
+    await set_global_cache_values({CACHE_ATTR_LIBSOXR_PRESENT: libsoxr_support})
+
+    if not ffmpeg_present:
+        msg = (
+            "FFmpeg binary is missing from system."
+            "Please install ffmpeg on your OS to enable playback."
+        )
+        raise AudioError(
+            msg,
+        )
+
+    major_version = int("".join(char for char in version.split(".")[0] if not char.isalpha()))
+    if major_version < MINIMAL_FFMPEG_VERSION:
+        msg = (
+            f"FFmpeg version {version} is not supported. "
+            f"Minimal version required is {MINIMAL_FFMPEG_VERSION}."
+        )
+        raise AudioError(msg)
+
+    LOGGER.info(
+        "Detected ffmpeg version %s %s",
+        version,
+        "with libsoxr support" if libsoxr_support else "",
+    )