Skip to content

Commit

Permalink
Add fLaC metadata tag PERFORMER (just for pianists for now) (#36)
Browse files Browse the repository at this point in the history
* Changes to track downloading:

Tidal reports 'Mix Engineer' instead of 'Mixer', so change in track.py: self.mixer: Optional[Tuple[str]] = self.get_contributors('Mix Engineer')
Add support for the 'PERFORMER' tag in .flac files: for now, just 'piano' as returned by track.Track.get_credits()
Change the order of tracks' ensuring that the audio stream is before video stream: copy to temp file THEN execute FFmpeg
Remove a couple unused attributes from PlaylistsEndpointResponseJSON
  • Loading branch information
ebb-earl-co committed Jan 7, 2024
1 parent f6805b8 commit 9277658
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 26 deletions.
11 changes: 2 additions & 9 deletions tidal_wave/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,10 @@ def __post_init__(self):
self.composer: Optional[Tuple[str]] = self.get_contributors("Composer")
self.engineer: Optional[Tuple[str]] = self.get_contributors("Engineer")
self.lyricist: Optional[Tuple[str]] = self.get_contributors("Lyricist")
self.mixer: Optional[Tuple[str]] = self.get_contributors("Mixer")
self.mixer: Optional[Tuple[str]] = self.get_contributors("Mix Engineer")
self.producer: Optional[Tuple[str]] = self.get_contributors("Producer")
self.remixer: Optional[Tuple[str]] = self.get_contributors("Remixer")
self.piano: Optional[Tuple[str]] = self.get_contributors("Piano")


@dataclass(frozen=True)
Expand Down Expand Up @@ -496,19 +497,11 @@ class PlaylistsEndpointResponseJSON(dataclass_wizard.JSONWizard):
number_of_tracks: int
number_of_videos: int
description: str
last_updated: Annotated[
datetime, dataclass_wizard.Pattern("%Y-%m-%dT%H:%M:%S.%f%z")
]
created: Annotated[datetime, dataclass_wizard.Pattern("%Y-%m-%dT%H:%M:%S.%f%z")]
type: str
public_playlist: bool
url: str
image: str # UUID v4
popularity: int
square_image: str # UUID v4
last_item_added_at: Annotated[
datetime, dataclass_wizard.Pattern("%Y-%m-%dT%H:%M:%S.%f%z")
]


class TidalResource:
Expand Down
50 changes: 33 additions & 17 deletions tidal_wave/track.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ def craft_tags(self):
tags[tag_map["track_peak_amplitude"]] = f"{self.metadata.peak}"
tags[tag_map["track_replay_gain"]] = f"{self.metadata.replay_gain}"
# credits
for tag in {"composer", "lyricist", "mixer", "producer", "remixer"}:
for tag in {"composer", "engineer", "lyricist", "mixer", "producer", "remixer"}:
try:
_credits_tag = ";".join(getattr(self.credits, tag))
except (TypeError, AttributeError): # NoneType problems
Expand All @@ -343,12 +343,21 @@ def craft_tags(self):
else:
tags[tag_map["lyrics"]] = _lyrics

# track and disk
if self.codec == "flac":
# track and disk
tags["DISCTOTAL"] = f"{self.metadata.volume_number}"
tags["DISC"] = f"{self.album.number_of_volumes}"
tags["TRACKTOTAL"] = f"{self.album.number_of_tracks}"
tags["TRACKNUMBER"] = f"{self.metadata.track_number}"
# instrument-specific
## piano
try:
piano_credits: List[str] = [f"{pc} (piano)" for pc in self.credits.piano]
except (TypeError, AttributeError): # NoneType problems
pass
else:
tags["PERFORMER"] = piano_credits

elif self.codec == "m4a":
# Have to convert to bytes the values of the tags starting with '----'
for k, v in tags.copy().items():
Expand Down Expand Up @@ -381,24 +390,31 @@ def set_tags(self):
self.mutagen["covr"] = [
MP4Cover(self.cover_path.read_bytes(), imageformat=MP4Cover.FORMAT_JPEG)
]
elif self.codec == "mka":
# FFmpeg chokes here with
# [matroska @ 0x5eb6a424f840] No wav codec tag found for codec none
# so DON'T attempt to add a cover image, and DON'T run the
# FFmpeg to put streams in order
self.mutagen.save()
return

self.mutagen.save()
# Make sure audio track comes first because of
# less-sophisticated audio players
with temporary_file(suffix=".mka") as tf:
cmd: List[str] = shlex.split(
f"""ffmpeg -hide_banner -loglevel quiet -y -i "{str(self.outfile.absolute())}"
-map 0:a:0 -map 0:v:0 -c copy "{tf.name}" """
)
subprocess.run(cmd)
shutil.copyfile(tf.name, str(self.outfile.absolute()))
# less-sophisticated audio players that only
# recognize the first stream
if self.codec == "flac":
with temporary_file(suffix=".mka") as tf:
shutil.move(str(self.outfile.absolute()), tf.name)
cmd: List[str] = shlex.split(
f"""ffmpeg -hide_banner -loglevel quiet -y -i "{tf.name}"
-map 0:a:0 -map 0:v:0 -c:a copy -c:v copy
-metadata:s:v title='Album cover' -metadata:s:v comment='Cover (front)'
-disposition:v attached_pic "{str(self.outfile.absolute())}" """
)
subprocess.run(cmd)
elif self.codec == "m4a":
with temporary_file(suffix=".mka") as tf:
shutil.move(str(self.outfile.absolute()), tf.name)
cmd: List[str] = shlex.split(
f"""ffmpeg -hide_banner -loglevel quiet -y -i "{tf.name}"
-map 0:a:0 -map 0:v:0 -c:a copy -c:v copy
"{str(self.outfile.absolute())}" """
)
subprocess.run(cmd)


def get(
self,
Expand Down

0 comments on commit 9277658

Please sign in to comment.