)
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,
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
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)
MusicAssistantError,
ProviderUnavailableError,
)
-from music_assistant_models.helpers import set_global_cache_values
from music_assistant_models.streamdetails import AudioFormat
from music_assistant.constants import (
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:
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,
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:
LOGGER = logging.getLogger("ffmpeg")
MINIMAL_FFMPEG_VERSION = 6
+CACHE_ATTR_LIBSOXR_PRESENT: Final[str] = "libsoxr_present"
class FFMpeg(AsyncProcess):
yield chunk
-def get_ffmpeg_args( # noqa: PLR0915
+def get_ffmpeg_args(
input_format: AudioFormat,
output_format: AudioFormat,
filter_params: list[str],
"""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",
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:
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 "",
+ )