DEFAULT_SYNC_INTERVAL = 12 * 60 # default sync interval in minutes
CONF_SYNC_INTERVAL = "sync_interval"
CONF_DELETED_PROVIDERS = "deleted_providers"
-DB_SCHEMA_VERSION: Final[int] = 27
+DB_SCHEMA_VERSION: Final[int] = 29
CACHE_CATEGORY_LAST_SYNC: Final[int] = 9
CACHE_CATEGORY_SEARCH_RESULTS: Final[int] = 10
await db.execute(full_query)
await db.commit()
+ if prev_version <= 29:
+ # Smart fades analyses were previously computed on silence-stripped audio,
+ # so beat timestamps are misaligned with the unstripped buffers now passed
+ # to the crossfade mixer. Truncate the table so all analyses are re-computed.
+ await self._database.execute(f"DELETE FROM {DB_TABLE_SMART_FADES_ANALYSIS}")
+
# save changes
await self._database.commit()
try:
# Execute the enhanced smart fade with full buffer
- 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)",
- )
+ _, raw_crossfade_output, stderr = await communicate(args, fade_in_part)
if raw_crossfade_output:
return raw_crossfade_output
bpm=self.fade_out_analysis.bpm,
)
- # 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:
from typing import TYPE_CHECKING
from music_assistant.controllers.streams.smart_fades.fades import (
- SMART_CROSSFADE_DURATION,
SmartCrossFade,
SmartFade,
StandardCrossFade,
# but just to be sure...
return fade_out_part + fade_in_part
- # strip silence from end of audio of fade_out_part
- fade_out_part = await strip_silence(
- self.streams.mass,
- fade_out_part,
- pcm_format=pcm_format,
- reverse=True,
- )
- # Ensure frame alignment after silence stripping
- fade_out_part = align_audio_to_frame_boundary(fade_out_part, pcm_format)
-
- # strip silence from begin of audio of fade_in_part
- fade_in_part = await strip_silence(
- self.streams.mass,
- fade_in_part,
- pcm_format=pcm_format,
- reverse=False,
- )
- # 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:
+ # strip silence from end of audio of fade_out_part
+ fade_out_part = await strip_silence(
+ self.streams.mass,
+ fade_out_part,
+ pcm_format=pcm_format,
+ reverse=True,
+ )
+ # Ensure frame alignment after silence stripping
+ fade_out_part = align_audio_to_frame_boundary(fade_out_part, pcm_format)
+ # strip silence from begin of audio of fade_in_part
+ fade_in_part = await strip_silence(
+ self.streams.mass,
+ fade_in_part,
+ pcm_format=pcm_format,
+ reverse=False,
+ )
+ # Ensure frame alignment after silence stripping
+ fade_in_part = align_audio_to_frame_boundary(fade_in_part, pcm_format)
smart_fade: SmartFade = StandardCrossFade(
logger=self.logger,
crossfade_duration=standard_crossfade_duration,