mirror of
https://github.com/home-assistant/core.git
synced 2026-09-26 09:23:17 -04:00
Write standard ID3 frames for TTS tags (#182819)
This commit is contained in:
@@ -18,7 +18,7 @@ from typing import Any, Final, Protocol
|
||||
|
||||
from aiohttp import web
|
||||
import mutagen
|
||||
from mutagen.id3 import ID3, TextFrame as ID3Text
|
||||
from mutagen.id3 import TALB, TIT2, TPE1, Encoding
|
||||
import probatio
|
||||
from propcache.api import cached_property
|
||||
|
||||
@@ -1195,23 +1195,9 @@ class SpeechManager:
|
||||
if tts_file is not None:
|
||||
if not tts_file.tags:
|
||||
tts_file.add_tags()
|
||||
if isinstance(tts_file.tags, ID3):
|
||||
tts_file["artist"] = ID3Text(
|
||||
encoding=3,
|
||||
text=artist, # type: ignore[no-untyped-call]
|
||||
)
|
||||
tts_file["album"] = ID3Text(
|
||||
encoding=3,
|
||||
text=album, # type: ignore[no-untyped-call]
|
||||
)
|
||||
tts_file["title"] = ID3Text(
|
||||
encoding=3,
|
||||
text=message, # type: ignore[no-untyped-call]
|
||||
)
|
||||
else:
|
||||
tts_file["artist"] = artist
|
||||
tts_file["album"] = album
|
||||
tts_file["title"] = message
|
||||
tts_file.tags.add(TPE1(encoding=Encoding.UTF8, text=artist)) # type: ignore[no-untyped-call]
|
||||
tts_file.tags.add(TALB(encoding=Encoding.UTF8, text=album)) # type: ignore[no-untyped-call]
|
||||
tts_file.tags.add(TIT2(encoding=Encoding.UTF8, text=message)) # type: ignore[no-untyped-call]
|
||||
data_bytes.seek(0)
|
||||
tts_file.save(data_bytes)
|
||||
except mutagen.MutagenError as err:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -11,6 +11,8 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
import wave
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import mutagen
|
||||
from mutagen.id3 import TIT2, TPE1, Encoding
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import ffmpeg, tts
|
||||
@@ -2327,3 +2329,111 @@ def test_write_tags_keeps_single_id3_tag() -> None:
|
||||
|
||||
assert tagged.startswith(b"ID3")
|
||||
assert tagged.count(b"ID3") == 1
|
||||
|
||||
|
||||
def test_write_tags_sets_standard_id3_frames() -> None:
|
||||
"""Test tagging audio carrying only the encoder frame sets standard frames."""
|
||||
data = load_fixture_bytes("tagged.mp3", DOMAIN)
|
||||
assert list(mutagen.File(io.BytesIO(data)).tags) == ["TSSE"]
|
||||
|
||||
tagged = ORIG_WRITE_TAGS(
|
||||
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3",
|
||||
data,
|
||||
"Test",
|
||||
"There is someone at the door.",
|
||||
"en",
|
||||
None,
|
||||
)
|
||||
|
||||
tags = mutagen.File(io.BytesIO(tagged)).tags
|
||||
assert tags["TPE1"].text == ["en"]
|
||||
assert tags["TALB"].text == ["Test"]
|
||||
assert tags["TIT2"].text == ["There is someone at the door."]
|
||||
assert "TSSE" in tags
|
||||
|
||||
|
||||
def test_write_tags_adds_tag_to_untagged_audio() -> None:
|
||||
"""Test audio arriving without an ID3 tag gets one holding the frames."""
|
||||
data = load_fixture_bytes("untagged.mp3", DOMAIN)
|
||||
assert not data.startswith(b"ID3")
|
||||
|
||||
tagged = ORIG_WRITE_TAGS(
|
||||
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3",
|
||||
data,
|
||||
"Test",
|
||||
"There is someone at the door.",
|
||||
"en",
|
||||
None,
|
||||
)
|
||||
|
||||
assert tagged.startswith(b"ID3")
|
||||
assert tagged.count(b"ID3") == 1
|
||||
tags = mutagen.File(io.BytesIO(tagged)).tags
|
||||
assert tags["TPE1"].text == ["en"]
|
||||
assert tags["TALB"].text == ["Test"]
|
||||
assert tags["TIT2"].text == ["There is someone at the door."]
|
||||
|
||||
|
||||
def test_write_tags_overwrites_id3v1_metadata() -> None:
|
||||
"""Test audio arriving with only an ID3v1 trailer gets the frames rewritten."""
|
||||
data = load_fixture_bytes("id3v1.mp3", DOMAIN)
|
||||
assert not data.startswith(b"ID3")
|
||||
assert data[-128:-125] == b"TAG"
|
||||
|
||||
tagged = ORIG_WRITE_TAGS(
|
||||
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3",
|
||||
data,
|
||||
"Test",
|
||||
"There is someone at the door.",
|
||||
"en",
|
||||
None,
|
||||
)
|
||||
|
||||
assert tagged.startswith(b"ID3")
|
||||
tags = mutagen.File(io.BytesIO(tagged)).tags
|
||||
assert tags["TPE1"].text == ["en"]
|
||||
assert tags["TALB"].text == ["Test"]
|
||||
assert tags["TIT2"].text == ["There is someone at the door."]
|
||||
|
||||
|
||||
def test_write_tags_replaces_existing_frames() -> None:
|
||||
"""Test frames carried through conversion from the provider are replaced."""
|
||||
data = load_fixture_bytes("untagged.mp3", DOMAIN)
|
||||
source = io.BytesIO(data)
|
||||
source.name = "source.mp3"
|
||||
source_file = mutagen.File(source)
|
||||
source_file.add_tags()
|
||||
source_file.tags.add(TIT2(encoding=Encoding.UTF8, text="Provider title"))
|
||||
source_file.tags.add(TPE1(encoding=Encoding.UTF8, text="Provider artist"))
|
||||
source.seek(0)
|
||||
source_file.save(source)
|
||||
|
||||
tagged = ORIG_WRITE_TAGS(
|
||||
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3",
|
||||
source.getvalue(),
|
||||
"Test",
|
||||
"There is someone at the door.",
|
||||
"en",
|
||||
None,
|
||||
)
|
||||
|
||||
tags = mutagen.File(io.BytesIO(tagged)).tags
|
||||
assert tags["TIT2"].text == ["There is someone at the door."]
|
||||
assert tags["TPE1"].text == ["en"]
|
||||
|
||||
|
||||
def test_write_tags_uses_voice_as_artist() -> None:
|
||||
"""Test the voice option replaces the language as the artist frame."""
|
||||
data = load_fixture_bytes("tagged.mp3", DOMAIN)
|
||||
|
||||
tagged = ORIG_WRITE_TAGS(
|
||||
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en-us_-_test.mp3",
|
||||
data,
|
||||
"Test",
|
||||
"There is someone at the door.",
|
||||
"en",
|
||||
{"voice": "JennyNeural"},
|
||||
)
|
||||
|
||||
tags = mutagen.File(io.BytesIO(tagged)).tags
|
||||
assert tags["TPE1"].text == ["JennyNeural"]
|
||||
|
||||
Reference in New Issue
Block a user