fix: MP4Box encapsulation error

with_status
WorldObservationLog 4 months ago
parent 2d7cd0e897
commit fd03f074a9
  1. 18
      src/mp4.py
  2. 5
      src/rip.py

@ -156,7 +156,7 @@ def encapsulate(song_info: SongInfo, decrypted_media: bytes, atmos_convent: bool
f.write(str(nhml_xml)) f.write(str(nhml_xml))
subprocess.run(f"gpac -i {nhml_name.absolute()} nhmlr -o {song_name.absolute()}", subprocess.run(f"gpac -i {nhml_name.absolute()} nhmlr -o {song_name.absolute()}",
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
subprocess.run(f'mp4box -ipod {song_name.absolute()}', subprocess.run(f'mp4box -brand "M4A " -ab "M4A " -ab "mp42" {song_name.absolute()}',
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
with open(song_name.absolute(), "rb") as f: with open(song_name.absolute(), "rb") as f:
final_song = f.read() final_song = f.read()
@ -188,3 +188,19 @@ def write_metadata(song: bytes, metadata: SongMetadata, embed_metadata: list[str
embed_song = f.read() embed_song = f.read()
tmp_dir.cleanup() tmp_dir.cleanup()
return embed_song return embed_song
# There are suspected errors in M4A files encapsulated by MP4Box and GPAC,
# causing some applications to be unable to correctly process Metadata (such as Android.media, Salt Music)
# Using FFMPEG re-encapsulating solves this problem
def fix_encapsulate(song: bytes) -> bytes:
tmp_dir = TemporaryDirectory()
name = uuid.uuid4().hex
song_name = Path(tmp_dir.name) / Path(f"{name}.m4a")
new_song_name = Path(tmp_dir.name) / Path(f"{name}_fixed.m4a")
with open(song_name.absolute(), "wb") as f:
f.write(song)
subprocess.run(["ffmpeg", "-y", "-i", song_name.absolute(), "-fflags", "+bitexact", "-c:a", "copy", "-c:v", "copy",
new_song_name.absolute()], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
with open(new_song_name.absolute(), "rb") as f:
return f.read()

@ -11,7 +11,7 @@ from src.adb import Device
from src.decrypt import decrypt from src.decrypt import decrypt
from src.metadata import SongMetadata from src.metadata import SongMetadata
from src.models import PlaylistInfo from src.models import PlaylistInfo
from src.mp4 import extract_media, extract_song, encapsulate, write_metadata from src.mp4 import extract_media, extract_song, encapsulate, write_metadata, fix_encapsulate
from src.save import save from src.save import save
from src.types import GlobalAuthParams, Codec from src.types import GlobalAuthParams, Codec
from src.url import Song, Album, URLType, Artist, Playlist from src.url import Song, Album, URLType, Artist, Playlist
@ -73,7 +73,8 @@ async def rip_song(song: Song, auth_params: GlobalAuthParams, codec: str, config
decrypted_song = await decrypt(song_info, keys, song_data, device) decrypted_song = await decrypt(song_info, keys, song_data, device)
song = encapsulate(song_info, decrypted_song, config.download.atmosConventToM4a) song = encapsulate(song_info, decrypted_song, config.download.atmosConventToM4a)
if not if_raw_atmos(codec, config.download.atmosConventToM4a): if not if_raw_atmos(codec, config.download.atmosConventToM4a):
song = write_metadata(song, song_metadata, config.metadata.embedMetadata, config.download.coverFormat) metadata_song = write_metadata(song, song_metadata, config.metadata.embedMetadata, config.download.coverFormat)
song = fix_encapsulate(metadata_song)
filename = save(song, codec, song_metadata, config.download, playlist) filename = save(song, codec, song_metadata, config.download, playlist)
logger.info(f"Song {song_metadata.artist} - {song_metadata.title} saved!") logger.info(f"Song {song_metadata.artist} - {song_metadata.title} saved!")
if config.download.afterDownloaded: if config.download.afterDownloaded:

Loading…
Cancel
Save