player.volume_control = config.get_value(CONF_VOLUME_CONTROL)
player.mute_control = config.get_value(CONF_MUTE_CONTROL)
- async def _play_announcement( # noqa: PLR0915
+ async def _play_announcement(
self,
player: Player,
announcement: PlayerMedia,
await self.wait_for_state(player, PlayerState.PLAYING, 10, minimal_time=0.1)
# wait for the player to stop playing
if not announcement.duration:
- media_info = await async_parse_tags(announcement.custom_data["url"])
- announcement.duration = media_info.duration or 60
- media_info.duration += 2
+ media_info = await async_parse_tags(
+ announcement.custom_data["url"], require_duration=True
+ )
+ announcement.duration = media_info.duration
await self.wait_for_state(
player,
PlayerState.IDLE,
extra_args: list[str] | None = None,
chunk_size: int | None = None,
extra_input_args: list[str] | None = None,
- collect_log_history: bool = False,
- loglevel: str = "info",
) -> AsyncGenerator[bytes, None]:
"""
Get the ffmpeg audio stream as async generator.
filter_params=filter_params,
extra_args=extra_args,
extra_input_args=extra_input_args,
- collect_log_history=collect_log_history,
- loglevel=loglevel,
) as ffmpeg_proc:
# read final chunks from stdout
iterator = ffmpeg_proc.iter_chunked(chunk_size) if chunk_size else ffmpeg_proc.iter_any()
"-f",
output_format.content_type.value,
]
- elif input_format == output_format:
+ elif input_format == output_format and not extra_args:
# passthrough
if output_format.content_type in (
ContentType.MP4,
return self.tags.get(key, default)
-async def async_parse_tags(input_file: str, file_size: int | None = None) -> AudioTags:
+async def async_parse_tags(
+ input_file: str, file_size: int | None = None, require_duration: bool = False
+) -> AudioTags:
"""Parse tags from a media file (or URL). Async friendly."""
- return await asyncio.to_thread(parse_tags, input_file, file_size)
+ return await asyncio.to_thread(parse_tags, input_file, file_size, require_duration)
-def parse_tags(input_file: str, file_size: int | None = None) -> AudioTags:
+def parse_tags(
+ input_file: str, file_size: int | None = None, require_duration: bool = False
+) -> AudioTags:
"""
Parse tags from a media file (or URL). NOT Async friendly.
if not tags.duration and tags.raw.get("format", {}).get("duration"):
tags.duration = float(tags.raw["format"]["duration"])
+ if not tags.duration and require_duration:
+ tags.duration = get_file_duration(input_file)
+
# we parse all (basic) tags for all file formats using ffmpeg
# but we also try to extract some extra tags for local files using mutagen
if not input_file.startswith("http") and os.path.isfile(input_file):
raise InvalidDataError(msg) from err
+def get_file_duration(input_file: str) -> float:
+ """
+ Parse file/stream duration from an audio file using ffmpeg.
+
+ NOT Async friendly.
+ """
+ args = (
+ "ffmpeg",
+ "-hide_banner",
+ "-loglevel",
+ "info",
+ "-i",
+ input_file,
+ "-f",
+ "null",
+ "-",
+ )
+ try:
+ res = subprocess.check_output(args, stderr=subprocess.STDOUT).decode() # noqa: S603
+ # extract duration from ffmpeg output
+ duration_str = res.split("time=")[-1].split(" ")[0].strip()
+ duration_parts = duration_str.split(":")
+ duration = 0
+ for part in duration_parts:
+ duration = duration * 60 + float(part)
+ return duration
+ except Exception as err:
+ error_msg = f"Unable to retrieve duration for {input_file}"
+ raise InvalidDataError(error_msg) from err
+
+
def parse_tags_mutagen(input_file: str) -> dict[str, Any]:
"""
Parse tags from an audio file using Mutagen.
)
# Wait until the announcement is finished playing
# This is helpful for people who want to play announcements in a sequence
- media_info = await async_parse_tags(announcement.uri)
+ media_info = await async_parse_tags(announcement.uri, require_duration=True)
duration = media_info.duration or 5
await asyncio.sleep(duration)
self.logger.debug(
# Wait until the announcement is finished playing
# This is helpful for people who want to play announcements in a sequence
# yeah we can also setup a subscription on the sonos player for this, but this is easier
- media_info = await async_parse_tags(announcement.uri)
+ media_info = await async_parse_tags(announcement.uri, require_duration=True)
duration = media_info.duration or 10
await asyncio.sleep(duration)