str(err),
exc_info=err if self.logger.isEnabledFor(10) else None,
)
+ # Yield to event loop to prevent blocking
+ await asyncio.sleep(0)
await asyncio.sleep(1)
async def _handle_select_plugin_source(
STREAMDETAILS_EXPIRATION: Final[int] = 60 * 15 # 15 minutes
+def align_audio_to_frame_boundary(audio_data: bytes, pcm_format: AudioFormat) -> bytes:
+ """Align audio data to frame boundaries by truncating incomplete frames.
+
+ :param audio_data: Raw PCM audio data to align.
+ :param pcm_format: AudioFormat of the audio data.
+ """
+ bytes_per_sample = pcm_format.bit_depth // 8
+ frame_size = bytes_per_sample * pcm_format.channels
+ valid_bytes = (len(audio_data) // frame_size) * frame_size
+ if valid_bytes != len(audio_data):
+ LOGGER.debug(
+ "Truncating %d bytes from audio buffer to align to frame boundary",
+ len(audio_data) - valid_bytes,
+ )
+ return audio_data[:valid_bytes]
+ return audio_data
+
+
async def crossfade_pcm_parts(
fade_in_part: bytes,
fade_out_part: bytes,
):
chunk_count += 1
await audio_buffer.put(chunk)
+ # Yield to event loop to prevent blocking warnings
+ await asyncio.sleep(0)
# Only set EOF if we completed successfully
await audio_buffer.set_eof()
except asyncio.CancelledError:
)
return b""
# Ensure frame alignment after resampling
- # Import inline to avoid circular dependency at module level
- from music_assistant.helpers.smart_fades import ( # noqa: PLC0415
- align_audio_to_frame_boundary,
- )
-
return align_audio_to_frame_boundary(stdout, output_format)
except Exception as err:
LOGGER.exception(
import shortuuid
from music_assistant.constants import VERBOSE_LOG_LEVEL
-from music_assistant.helpers.audio import crossfade_pcm_parts, strip_silence
+from music_assistant.helpers.audio import (
+ align_audio_to_frame_boundary,
+ crossfade_pcm_parts,
+ strip_silence,
+)
from music_assistant.helpers.process import communicate
from music_assistant.helpers.util import remove_file
from music_assistant.models.smart_fades import (
TIME_STRETCH_BPM_PERCENTAGE_THRESHOLD = 5.0
-def align_audio_to_frame_boundary(audio_data: bytes, pcm_format: AudioFormat) -> bytes:
- """Align audio data to frame boundaries by truncating incomplete frames."""
- bytes_per_sample = pcm_format.bit_depth // 8
- frame_size = bytes_per_sample * pcm_format.channels
- valid_bytes = (len(audio_data) // frame_size) * frame_size
- if valid_bytes != len(audio_data):
- logging.getLogger(__name__).debug(
- "Truncating %d bytes from audio buffer to align to frame boundary",
- len(audio_data) - valid_bytes,
- )
- return audio_data[:valid_bytes]
- return audio_data
-
-
class SmartFadesAnalyzer:
"""Smart fades analyzer that performs audio analysis."""
from __future__ import annotations
+import asyncio
import logging
from typing import TYPE_CHECKING, cast
logger.debug("End of stream reached")
break
logger.log(VERBOSE_LOG_LEVEL, line)
+ await asyncio.sleep(0) # Yield to event loop
# ensure we're cleaned up afterwards (this also logs the returncode)
logger.debug("CLIRaop stderr reader ended")