Smart fades: Add debug logs to investigate sometimes skipping crossfades.
authorMarvin Schenkel <marvinschenkel@gmail.com>
Tue, 24 Feb 2026 14:13:11 +0000 (15:13 +0100)
committerMarvin Schenkel <marvinschenkel@gmail.com>
Tue, 24 Feb 2026 14:13:11 +0000 (15:13 +0100)
music_assistant/controllers/streams/smart_fades/fades.py
music_assistant/controllers/streams/smart_fades/mixer.py

index 88dc11111a6787844c5664fed510d665bf8e50bc..ebc2ba6c6c35e9ef290a1c2735333c30b751f3e1 100644 (file)
@@ -135,7 +135,30 @@ class SmartFade(ABC):
 
         try:
             # Execute the enhanced smart fade with full buffer
-            _, raw_crossfade_output, stderr = await communicate(args, fade_in_part)
+            returncode, raw_crossfade_output, stderr = await communicate(args, fade_in_part)
+
+            expected_min_output = (
+                len(fade_out_part) + len(fade_in_part) - int(pcm_format.pcm_sample_size * 10)
+            )  # rough minimum: both inputs minus ~10s overlap
+            self.logger.debug(
+                "FFmpeg smartfade result: returncode=%d%s, "
+                "output=%.2fs (%d bytes), fadeout_input=%.2fs, fadein_input=%.2fs%s, "
+                "stderr=%s",
+                returncode,
+                " *** NONZERO - crossfade likely FAILED or produced partial output!"
+                if returncode != 0
+                else "",
+                len(raw_crossfade_output) / pcm_format.pcm_sample_size
+                if raw_crossfade_output
+                else 0,
+                len(raw_crossfade_output) if raw_crossfade_output else 0,
+                len(fade_out_part) / pcm_format.pcm_sample_size,
+                len(fade_in_part) / pcm_format.pcm_sample_size,
+                f" *** OUTPUT SUSPICIOUSLY SMALL (expected >={expected_min_output} bytes)"
+                if raw_crossfade_output and len(raw_crossfade_output) < expected_min_output
+                else "",
+                stderr.decode().strip() if stderr else "(empty)",
+            )
 
             if raw_crossfade_output:
                 return raw_crossfade_output
@@ -221,6 +244,24 @@ class SmartCrossFade(SmartFade):
             )
 
         # Check if we would have enough audio after beat alignment for the crossfade
+        if fadein_start_pos:
+            required = fadein_start_pos + crossfade_duration
+            self.logger.debug(
+                "Trim validation: fadein_start=%.2fs + xfade=%.2fs"
+                " = %.2fs needed. Checked against constant=%ds"
+                " (pass=%s). NOTE: if actual fade_in buffer is"
+                " shorter than %ds after silence stripping,"
+                " FFmpeg acrossfade WILL fail (only %.2fs would"
+                " remain, need %.2fs)",
+                fadein_start_pos,
+                crossfade_duration,
+                required,
+                SMART_CROSSFADE_DURATION,
+                required <= SMART_CROSSFADE_DURATION,
+                SMART_CROSSFADE_DURATION,
+                SMART_CROSSFADE_DURATION - required,
+                crossfade_duration,
+            )
         if fadein_start_pos and fadein_start_pos + crossfade_duration <= SMART_CROSSFADE_DURATION:
             self.filters.append(TrimFilter(logger=self.logger, fadein_start_pos=fadein_start_pos))
         else:
index 240fb523e8f12365df24caa3ae0550b626b66b56..1b25170a3f5da36222a92e989f70e17ee711f0b7 100644 (file)
@@ -5,6 +5,7 @@ from __future__ import annotations
 from typing import TYPE_CHECKING
 
 from music_assistant.controllers.streams.smart_fades.fades import (
+    SMART_CROSSFADE_DURATION,
     SmartCrossFade,
     SmartFade,
     StandardCrossFade,
@@ -70,6 +71,22 @@ class SmartFadesMixer:
         )
         # Ensure frame alignment after silence stripping
         fade_in_part = align_audio_to_frame_boundary(fade_in_part, pcm_format)
+        fadeout_duration = len(fade_out_part) / pcm_format.pcm_sample_size
+        fadein_duration = len(fade_in_part) / pcm_format.pcm_sample_size
+        fadeout_stripped = SMART_CROSSFADE_DURATION - fadeout_duration
+        fadein_stripped = SMART_CROSSFADE_DURATION - fadein_duration
+        self.logger.debug(
+            "Buffer durations after silence stripping: "
+            "fade_out=%.2fs (%.2fs stripped), fade_in=%.2fs (%.2fs stripped)%s",
+            fadeout_duration,
+            fadeout_stripped,
+            fadein_duration,
+            fadein_stripped,
+            " *** WARNING: fade_in significantly shorter than"
+            f" SMART_CROSSFADE_DURATION ({SMART_CROSSFADE_DURATION}s)!"
+            if fadein_stripped > 2.0
+            else "",
+        )
         if mode == SmartFadesMode.STANDARD_CROSSFADE:
             smart_fade: SmartFade = StandardCrossFade(
                 logger=self.logger,