mirror of
https://github.com/oskvr37/tiddl.git
synced 2026-06-13 04:05:08 +03:00
✨ add metadata and covers
This commit is contained in:
@@ -10,6 +10,7 @@ from ..ctx import Context, passContext
|
||||
from tiddl.download import downloadTrackStream
|
||||
from tiddl.models import TrackArg, ARG_TO_QUALITY, Track, PlaylistTrack, Album
|
||||
from tiddl.utils import formatTrack, trackExists
|
||||
from tiddl.metadata import addMetadata, Cover
|
||||
|
||||
|
||||
@click.command("download")
|
||||
@@ -33,7 +34,7 @@ def DownloadCommand(
|
||||
|
||||
api = ctx.obj.getApi()
|
||||
|
||||
def downloadTrack(track: Track, file_name: str) -> None:
|
||||
def downloadTrack(track: Track, file_name: str, cover_data=b"") -> None:
|
||||
if not track.allowStreaming:
|
||||
click.echo(
|
||||
f"{click.style('✖', 'yellow')} Track {click.style(file_name, 'yellow')} does not allow streaming"
|
||||
@@ -66,12 +67,21 @@ def DownloadCommand(
|
||||
with full_path.open("wb") as f:
|
||||
f.write(stream_data)
|
||||
|
||||
# TODO: add track credits fetching to fill more metadata
|
||||
|
||||
if not cover_data and track.album.cover:
|
||||
cover_data = Cover(track.album.cover).content
|
||||
|
||||
addMetadata(full_path, track, cover_data)
|
||||
|
||||
def downloadAlbum(album: Album):
|
||||
click.echo(f"★ Album {album.title}")
|
||||
|
||||
# TODO: fetch all items
|
||||
album_items = api.getAlbumItems(album.id, limit=100)
|
||||
|
||||
cover_data = Cover(album.cover).content if album.cover else b""
|
||||
|
||||
for item in album_items.items:
|
||||
if isinstance(item.item, Track):
|
||||
track = item.item
|
||||
@@ -82,7 +92,7 @@ def DownloadCommand(
|
||||
album_artist=album.artist.name,
|
||||
)
|
||||
|
||||
downloadTrack(track=track, file_name=file_name)
|
||||
downloadTrack(track=track, file_name=file_name, cover_data=cover_data)
|
||||
|
||||
for resource in ctx.obj.resources:
|
||||
match resource.type:
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from mutagen.flac import FLAC as MutagenFLAC, Picture
|
||||
from mutagen.easymp4 import EasyMP4 as MutagenEasyMP4
|
||||
from mutagen.mp4 import MP4Cover, MP4 as MutagenMP4
|
||||
|
||||
from tiddl.models import Track
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def addMetadata(track_path: Path, track: Track, cover_data=b""):
|
||||
extension = track_path.suffix
|
||||
|
||||
if extension == ".flac":
|
||||
metadata = MutagenFLAC(track_path)
|
||||
if cover_data:
|
||||
picture = Picture()
|
||||
picture.data = cover_data
|
||||
picture.mime = "image/jpeg"
|
||||
metadata.add_picture(picture)
|
||||
elif extension == ".m4a":
|
||||
if cover_data:
|
||||
metadata = MutagenMP4(track_path)
|
||||
metadata["covr"] = [MP4Cover(cover_data, imageformat=MP4Cover.FORMAT_JPEG)]
|
||||
metadata.save(track_path)
|
||||
metadata = MutagenEasyMP4(track_path)
|
||||
else:
|
||||
raise ValueError(f"Unknown file extension: {extension}")
|
||||
|
||||
new_metadata: dict[str, str] = {
|
||||
"title": track.title,
|
||||
"trackNumber": str(track.trackNumber),
|
||||
"discnumber": str(track.volumeNumber),
|
||||
"copyright": track.copyright,
|
||||
"albumartist": track.artist.name if track.artist else "",
|
||||
"artist": ";".join([artist.name.strip() for artist in track.artists]),
|
||||
"album": track.album.title,
|
||||
"date": str(track.streamStartDate) if track.streamStartDate else "",
|
||||
}
|
||||
|
||||
metadata.update(new_metadata)
|
||||
|
||||
try:
|
||||
metadata.save(track_path)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to set metadata for {extension}: {e}")
|
||||
|
||||
|
||||
class Cover:
|
||||
def __init__(self, uid: str, size=1280) -> None:
|
||||
if size > 1280:
|
||||
logger.warning(
|
||||
f"can not set cover size higher than 1280 (user set: {size})"
|
||||
)
|
||||
size = 1280
|
||||
|
||||
self.uid = uid
|
||||
|
||||
formatted_uid = uid.replace("-", "/")
|
||||
self.url = (
|
||||
f"https://resources.tidal.com/images/{formatted_uid}/{size}x{size}.jpg"
|
||||
)
|
||||
|
||||
logger.debug((self.uid, self.url))
|
||||
|
||||
self.content = self._get()
|
||||
|
||||
def _get(self) -> bytes:
|
||||
req = requests.get(self.url)
|
||||
|
||||
if req.status_code != 200:
|
||||
logger.error(f"could not download cover. ({req.status_code}) {self.url}")
|
||||
return b""
|
||||
|
||||
logger.debug(f"got cover: {self.uid}")
|
||||
|
||||
return req.content
|
||||
|
||||
def save(self, directory_path: Path):
|
||||
if not self.content:
|
||||
logger.error("cover file content is empty")
|
||||
return
|
||||
|
||||
file = directory_path / "cover.jpg"
|
||||
|
||||
if file.exists():
|
||||
logger.debug(f"cover already exists ({file})")
|
||||
return
|
||||
|
||||
try:
|
||||
with file.open("wb") as f:
|
||||
f.write(self.content)
|
||||
|
||||
except FileNotFoundError as e:
|
||||
logger.error(f"could not save cover. {file} -> {e}")
|
||||
Reference in New Issue
Block a user