Merge pull request #226 from exislow/gui-enhancement

Gui enhancement
This commit is contained in:
exislow
2024-11-11 12:08:44 +01:00
committed by GitHub
11 changed files with 223 additions and 70 deletions
+1 -1
View File
@@ -171,7 +171,7 @@ profile = "black"
multi_line_output = 3 multi_line_output = 3
indent = 4 indent = 4
color_output = true color_output = true
known_third_party = ["Crypto", "PySide6", "coloredlogs", "dataclasses_json", "ffmpeg", "m3u8", "mutagen", "pathvalidate", "requests", "rich", "tidalapi", "toml", "typer"] known_third_party = ["Crypto", "PySide6", "coloredlogs", "constants", "dataclasses_json", "ffmpeg", "m3u8", "mutagen", "pathvalidate", "requests", "rich", "tidalapi", "toml", "typer"]
[tool.mypy] [tool.mypy]
# https://mypy.readthedocs.io/en/latest/config_file.html#using-a-pyproject-toml-file # https://mypy.readthedocs.io/en/latest/config_file.html#using-a-pyproject-toml-file
+4 -1
View File
@@ -1,3 +1,4 @@
import json
import os import os
import shutil import shutil
from collections.abc import Callable from collections.abc import Callable
@@ -31,7 +32,9 @@ class BaseConfig:
os.makedirs(self.path_base, exist_ok=True) os.makedirs(self.path_base, exist_ok=True)
with open(self.file_path, encoding="utf-8", mode="w") as f: with open(self.file_path, encoding="utf-8", mode="w") as f:
f.write(data_json) # Save it in a pretty format
obj_json_config = json.loads(data_json)
json.dump(obj_json_config, f, indent=4)
def set_option(self, key: str, value: Any) -> None: def set_option(self, key: str, value: Any) -> None:
value_old: Any = getattr(self.data, key) value_old: Any = getattr(self.data, key)
+1
View File
@@ -5,6 +5,7 @@ REQUESTS_TIMEOUT_SEC: int = 45
EXTENSION_LYRICS: str = ".lrc" EXTENSION_LYRICS: str = ".lrc"
UNIQUIFY_THRESHOLD: int = 99 UNIQUIFY_THRESHOLD: int = 99
FILENAME_SANITIZE_PLACEHOLDER: str = "_" FILENAME_SANITIZE_PLACEHOLDER: str = "_"
COVER_NAME: str = "cover.jpg"
class QualityVideo(StrEnum): class QualityVideo(StrEnum):
+2
View File
@@ -171,6 +171,8 @@ class DialogPreferences(QtWidgets.QDialog):
"download_delay", "download_delay",
"video_convert_mp4", "video_convert_mp4",
"extract_flac", "extract_flac",
"metadata_cover_embed",
"cover_album_file",
] ]
def gui_populate(self): def gui_populate(self):
+79 -15
View File
@@ -10,6 +10,7 @@ from uuid import uuid4
import ffmpeg import ffmpeg
import m3u8 import m3u8
import requests import requests
from constants import COVER_NAME
from requests.exceptions import HTTPError from requests.exceptions import HTTPError
from rich.progress import Progress, TaskID from rich.progress import Progress, TaskID
from tidalapi import Album, Mix, Playlist, Session, Track, UserPlaylist, Video from tidalapi import Album, Mix, Playlist, Session, Track, UserPlaylist, Video
@@ -189,6 +190,7 @@ class Download:
download_delay: bool = False, download_delay: bool = False,
quality_audio: Quality | None = None, quality_audio: Quality | None = None,
quality_video: QualityVideo | None = None, quality_video: QualityVideo | None = None,
is_parent_album: bool = False,
) -> (bool, str): ) -> (bool, str):
try: try:
if media_id and media_type: if media_id and media_type:
@@ -278,10 +280,13 @@ class Download:
tmp_path_file = self._extract_flac(tmp_path_file) tmp_path_file = self._extract_flac(tmp_path_file)
tmp_path_lyrics: pathlib.Path | None = None tmp_path_lyrics: pathlib.Path | None = None
tmp_path_cover: pathlib.Path | None = None
# Write metadata to file. # Write metadata to file.
if not isinstance(media, Video): if not isinstance(media, Video):
result_metadata, tmp_path_lyrics = self.metadata_write(media, tmp_path_file) result_metadata, tmp_path_lyrics, tmp_path_cover = self.metadata_write(
media, tmp_path_file, is_parent_album
)
# Move final file to the configured destination directory. # Move final file to the configured destination directory.
os.makedirs(os.path.dirname(path_media_dst), exist_ok=True) os.makedirs(os.path.dirname(path_media_dst), exist_ok=True)
@@ -289,7 +294,11 @@ class Download:
# Move lyrics file # Move lyrics file
if self.settings.data.lyrics_file and not isinstance(media, Video): if self.settings.data.lyrics_file and not isinstance(media, Video):
self._move_lyrics(path_media_dst, tmp_path_lyrics) self._move_lyrics(tmp_path_lyrics, path_media_dst)
# Move cover file
if self.settings.data.cover_album_file:
self._move_cover(tmp_path_cover, path_media_dst)
else: else:
self.fn_logger.debug(f"Download skipped, since file exists: '{path_media_dst}'") self.fn_logger.debug(f"Download skipped, since file exists: '{path_media_dst}'")
@@ -327,13 +336,13 @@ class Download:
return quality_old return quality_old
def _move_lyrics(self, file_media_dst: str, path_lyrics: pathlib.Path) -> bool: def _move_file(self, path_file_source: pathlib.Path, path_file_destination: str | pathlib.Path) -> bool:
result: bool result: bool
# Build tmp lyrics filename
# Check if the file was downloaded # Check if the file was downloaded
if path_lyrics and os.path.isfile(path_lyrics): if path_file_source and os.path.isfile(path_file_source):
# Move it. # Move it.
shutil.move(path_lyrics, os.path.splitext(file_media_dst)[0] + EXTENSION_LYRICS) shutil.move(path_file_source, path_file_destination)
result = True result = True
else: else:
@@ -341,20 +350,64 @@ class Download:
return result return result
def lyrics_write_file(self, dir_destination: pathlib.Path, lyrics: str) -> str: def _move_lyrics(self, path_lyrics: pathlib.Path, file_media_dst: str) -> bool:
# Build tmp lyrics filename
path_file_lyrics: str = os.path.splitext(file_media_dst)[0] + EXTENSION_LYRICS
result: bool = self._move_file(path_lyrics, path_file_lyrics)
return result
def _move_cover(self, path_cover: pathlib.Path, file_media_dst: str) -> bool:
# Build tmp lyrics filename
path_file_cover: pathlib.Path = pathlib.Path(file_media_dst).parent.absolute() / COVER_NAME
result: bool = self._move_file(path_cover, path_file_cover)
return result
def lyrics_to_file(self, dir_destination: pathlib.Path, lyrics: str) -> str:
return self.write_to_tmp_file(dir_destination, mode="x", content=lyrics)
def cover_to_file(self, dir_destination: pathlib.Path, image: bytes) -> str:
return self.write_to_tmp_file(dir_destination, mode="xb", content=image)
def write_to_tmp_file(self, dir_destination: pathlib.Path, mode: str, content: str | bytes) -> str:
result: str = dir_destination / str(uuid4()) result: str = dir_destination / str(uuid4())
encoding: str | None = "utf-8" if isinstance(content, str) else None
try: try:
with open(result, "x", encoding="utf-8") as f: with open(result, mode=mode, encoding=encoding) as f:
f.write(lyrics) f.write(content)
except: except:
result = "" result = ""
return result return result
def metadata_write(self, track: Track, path_media: str) -> (bool, pathlib.Path | None): @staticmethod
def cover_data(url: str = None, path_file: str = None) -> str | bytes:
result: str | bytes = ""
if url:
try:
result = requests.get(url, timeout=REQUESTS_TIMEOUT_SEC).content
except Exception as e:
# TODO: Implement propper logging.
print(e)
elif path_file:
try:
with open(path_file, "rb") as f:
result = f.read()
except OSError as e:
# TODO: Implement propper logging.
print(e)
return result
def metadata_write(
self, track: Track, path_media: str, is_parent_album: str
) -> (bool, pathlib.Path | None, pathlib.Path | None):
result: bool = False result: bool = False
path_lyrics: pathlib.Path | None = None path_lyrics: pathlib.Path | None = None
path_cover: pathlib.Path | None = None
release_date: str = ( release_date: str = (
track.album.available_release_date.strftime("%Y-%m-%d") track.album.available_release_date.strftime("%Y-%m-%d")
if track.album.available_release_date if track.album.available_release_date
@@ -363,6 +416,7 @@ class Download:
copy_right: str = track.copyright if hasattr(track, "copyright") and track.copyright else "" copy_right: str = track.copyright if hasattr(track, "copyright") and track.copyright else ""
isrc: str = track.isrc if hasattr(track, "isrc") and track.isrc else "" isrc: str = track.isrc if hasattr(track, "isrc") and track.isrc else ""
lyrics: str = "" lyrics: str = ""
cover_data: bytes = None
if self.settings.data.lyrics_embed or self.settings.data.lyrics_file: if self.settings.data.lyrics_embed or self.settings.data.lyrics_file:
# Try to retrieve lyrics. # Try to retrieve lyrics.
@@ -379,7 +433,14 @@ class Download:
print(f"Could not retrieve lyrics for `{name_builder_item(track)}`.") print(f"Could not retrieve lyrics for `{name_builder_item(track)}`.")
if lyrics and self.settings.data.lyrics_file: if lyrics and self.settings.data.lyrics_file:
path_lyrics = self.lyrics_write_file(pathlib.Path(path_media).parent, lyrics) path_lyrics = self.lyrics_to_file(pathlib.Path(path_media).parent, lyrics)
if self.settings.data.metadata_cover_embed or (self.settings.data.cover_album_file and is_parent_album):
url_cover = track.album.image(int(self.settings.data.metadata_cover_dimension))
cover_data = self.cover_data(url=url_cover)
if cover_data and self.settings.data.cover_album_file and is_parent_album:
path_cover = self.cover_to_file(pathlib.Path(path_media).parent, cover_data)
# `None` values are not allowed. # `None` values are not allowed.
m: Metadata = Metadata( m: Metadata = Metadata(
@@ -396,14 +457,14 @@ class Download:
totaltrack=track.album.num_tracks if track.album and track.album.num_tracks else 1, totaltrack=track.album.num_tracks if track.album and track.album.num_tracks else 1,
totaldisc=track.album.num_volumes if track.album and track.album.num_volumes else 1, totaldisc=track.album.num_volumes if track.album and track.album.num_volumes else 1,
discnumber=track.volume_num if track.volume_num else 1, discnumber=track.volume_num if track.volume_num else 1,
url_cover=track.album.image(int(self.settings.data.metadata_cover_dimension)), cover_data=cover_data if self.settings.data.metadata_cover_embed else None,
) )
m.save() m.save()
result = True result = True
return result, path_lyrics return result, path_lyrics, path_cover
def items( def items(
self, self,
@@ -451,16 +512,19 @@ class Download:
f"[green]List '{list_media_name}'", total=len(items), visible=progress_stdout f"[green]List '{list_media_name}'", total=len(items), visible=progress_stdout
) )
is_album: bool = isinstance(media, Album)
# Iterate through list items # Iterate through list items
while not self.progress.finished: while not self.progress.finished:
for media in items: for item_media in items:
# Download the item. # Download the item.
status_download, result_path_file = self.item( status_download, result_path_file = self.item(
media=media, media=item_media,
file_template=file_name_relative, file_template=file_name_relative,
quality_audio=quality_audio, quality_audio=quality_audio,
quality_video=quality_video, quality_video=quality_video,
download_delay=download_delay, download_delay=download_delay,
is_parent_album=is_album,
) )
# Advance progress bar. # Advance progress bar.
+64 -8
View File
@@ -31,7 +31,6 @@ from tidal_dl_ng.helper.tidal import (
search_results_all, search_results_all,
user_media_lists, user_media_lists,
) )
from tidal_dl_ng.metadata import Metadata
try: try:
import qdarktheme import qdarktheme
@@ -249,11 +248,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
tree.setColumnWidth(3, 150) tree.setColumnWidth(3, 150)
tree.setColumnWidth(4, 150) tree.setColumnWidth(4, 150)
header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
# Connect the contextmenu
tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
tree.customContextMenuRequested.connect(self.menu_context_tree_results)
def _init_tree_results_model(self, model: QtGui.QStandardItemModel) -> None: def _init_tree_results_model(self, model: QtGui.QStandardItemModel) -> None:
model.setColumnCount(7) labels_column: [str] = ["#", "obj", "Artist", "Title", "Album", "Duration", "Quality", "Date Added"]
model.setColumnCount(len(labels_column))
model.setRowCount(0) model.setRowCount(0)
model.setHorizontalHeaderLabels(["#", "obj", "Artist", "Title", "Album", "Duration", "Quality"]) model.setHorizontalHeaderLabels(labels_column)
def _init_tree_queue(self, tree: QtWidgets.QTableWidget): def _init_tree_queue(self, tree: QtWidgets.QTableWidget):
tree.setColumnHidden(1, True) tree.setColumnHidden(1, True)
@@ -359,7 +363,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.spinner.stop() self.spinner.stop()
self.spinner = None self.spinner = None
def menu_context_tree_lists(self, point): def menu_context_tree_lists(self, point: QtCore.QPoint):
# Infos about the node selected. # Infos about the node selected.
index = self.tr_lists_user.indexAt(point) index = self.tr_lists_user.indexAt(point)
@@ -370,12 +374,46 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# We build the menu. # We build the menu.
menu = QtWidgets.QMenu() menu = QtWidgets.QMenu()
menu.addAction("Download Playlist", lambda: self.thread_download_list_media(point)) menu.addAction("Download Playlist", lambda: self.thread_download_list_media(point))
menu.addAction("Copy Share URL", lambda: self.thread_copy_url_share(self.tr_lists_user, point))
menu.exec(self.tr_lists_user.mapToGlobal(point)) menu.exec(self.tr_lists_user.mapToGlobal(point))
def thread_download_list_media(self, point): def menu_context_tree_results(self, point: QtCore.QPoint):
# Infos about the node selected.
index = self.tr_results.indexAt(point)
# Do not open menu if something went wrong or a parent node is clicked.
if not index.isValid():
return
# We build the menu.
menu = QtWidgets.QMenu()
menu.addAction("Copy Share URL", lambda: self.thread_copy_url_share(self.tr_results, point))
menu.exec(self.tr_results.mapToGlobal(point))
def thread_download_list_media(self, point: QtCore.QPoint):
self.thread_it(self.on_download_list_media, point) self.thread_it(self.on_download_list_media, point)
self.thread_it(self.list_items_show_result, point=point)
def thread_copy_url_share(self, tree_target: QtWidgets.QTreeWidget, point: QtCore.QPoint):
self.thread_it(self.on_copy_url_share, tree_target, point)
def on_copy_url_share(self, tree_target: QtWidgets.QTreeWidget | QtWidgets.QTreeView, point: QtCore.QPoint = None):
if isinstance(tree_target, QtWidgets.QTreeWidget):
item: QtWidgets.QTreeWidgetItem = tree_target.itemAt(point)
media: Album | Artist | Mix | Playlist = get_user_list_media_item(item)
else:
index: QtCore.QModelIndex = tree_target.indexAt(point)
media: Track | Video | Album | Artist | Mix | Playlist = get_results_media_item(
index, self.proxy_tr_results, self.model_tr_results
)
clipboard = QtWidgets.QApplication.clipboard()
url_share = media.share_url if hasattr(media, "share_url") else "No share URL available."
clipboard.clear()
clipboard.setText(url_share)
def on_download_list_media(self, point: QtCore.QPoint = None): def on_download_list_media(self, point: QtCore.QPoint = None):
items: [QtWidgets.QTreeWidgetItem] items: [QtWidgets.QTreeWidgetItem]
@@ -444,6 +482,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
child_album: QtGui.QStandardItem = QtGui.QStandardItem(item.album) child_album: QtGui.QStandardItem = QtGui.QStandardItem(item.album)
child_duration: QtGui.QStandardItem = QtGui.QStandardItem(duration) child_duration: QtGui.QStandardItem = QtGui.QStandardItem(duration)
child_quality: QtGui.QStandardItem = QtGui.QStandardItem(item.quality) child_quality: QtGui.QStandardItem = QtGui.QStandardItem(item.quality)
child_date_added: QtGui.QStandardItem = QtGui.QStandardItem(item.date_user_added)
if isinstance(item.obj, Mix | Playlist | Album | Artist): if isinstance(item.obj, Mix | Playlist | Album | Artist):
# Add a disabled dummy child, so expansion arrow will appear. This Child will be replaced on expansion. # Add a disabled dummy child, so expansion arrow will appear. This Child will be replaced on expansion.
@@ -452,7 +491,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
child_dummy.setEnabled(False) child_dummy.setEnabled(False)
child_index.appendRow(child_dummy) child_index.appendRow(child_dummy)
return child_index, child_obj, child_artist, child_title, child_album, child_duration, child_quality return (
child_index,
child_obj,
child_artist,
child_title,
child_album,
child_duration,
child_quality,
child_date_added,
)
def on_tr_results_add_top_level_item(self, item_child: Sequence[QtGui.QStandardItem]): def on_tr_results_add_top_level_item(self, item_child: Sequence[QtGui.QStandardItem]):
self.model_tr_results.appendRow(item_child) self.model_tr_results.appendRow(item_child)
@@ -501,6 +549,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if isinstance(item, Track | Video | Album): if isinstance(item, Track | Video | Album):
explicit = " 🅴" if item.explicit else "" explicit = " 🅴" if item.explicit else ""
date_user_added: str = item.user_date_added.strftime("%Y-%m-%d_%H:%M") if item.user_date_added else ""
if isinstance(item, Track): if isinstance(item, Track):
result_item: ResultItem = ResultItem( result_item: ResultItem = ResultItem(
position=idx, position=idx,
@@ -511,6 +561,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
obj=item, obj=item,
quality=quality_audio_highest(item), quality=quality_audio_highest(item),
explicit=bool(item.explicit), explicit=bool(item.explicit),
date_user_added=date_user_added,
) )
result.append(result_item) result.append(result_item)
@@ -524,6 +575,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
obj=item, obj=item,
quality=item.video_quality, quality=item.video_quality,
explicit=bool(item.explicit), explicit=bool(item.explicit),
date_user_added=date_user_added,
) )
result.append(result_item) result.append(result_item)
@@ -537,6 +589,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
obj=item, obj=item,
quality="", quality="",
explicit=False, explicit=False,
date_user_added=date_user_added,
) )
result.append(result_item) result.append(result_item)
@@ -550,6 +603,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
obj=item, obj=item,
quality=quality_audio_highest(item), quality=quality_audio_highest(item),
explicit=bool(item.explicit), explicit=bool(item.explicit),
date_user_added=date_user_added,
) )
result.append(result_item) result.append(result_item)
@@ -564,6 +618,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
obj=item, obj=item,
quality="", quality="",
explicit=False, explicit=False,
date_user_added=date_user_added,
) )
result.append(result_item) result.append(result_item)
@@ -577,6 +632,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
obj=item, obj=item,
quality="", quality="",
explicit=False, explicit=False,
date_user_added=date_user_added,
) )
result.append(result_item) result.append(result_item)
@@ -757,7 +813,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if cover_url and self.cover_url_current != cover_url: if cover_url and self.cover_url_current != cover_url:
self.cover_url_current = cover_url self.cover_url_current = cover_url
data_cover: bytes = Metadata.cover_data(cover_url) data_cover: bytes = Download.cover_data(cover_url)
pixmap: QtGui.QPixmap = QtGui.QPixmap() pixmap: QtGui.QPixmap = QtGui.QPixmap()
pixmap.loadFromData(data_cover) pixmap.loadFromData(data_cover)
self.l_pm_cover.setPixmap(pixmap) self.l_pm_cover.setPixmap(pixmap)
+7 -33
View File
@@ -1,10 +1,7 @@
import mutagen import mutagen
import requests
from mutagen import flac, id3, mp4 from mutagen import flac, id3, mp4
from mutagen.id3 import APIC, TALB, TCOM, TCOP, TDRC, TIT2, TOPE, TPE1, TRCK, TSRC, USLT from mutagen.id3 import APIC, TALB, TCOM, TCOP, TDRC, TIT2, TOPE, TPE1, TRCK, TSRC, USLT
from tidal_dl_ng.constants import REQUESTS_TIMEOUT_SEC
class Metadata: class Metadata:
path_file: str path_file: str
@@ -22,7 +19,7 @@ class Metadata:
isrc: str isrc: str
lyrics: str lyrics: str
path_cover: str path_cover: str
url_cover: str cover_data: bytes
m: mutagen.mp4.MP4 | mutagen.mp4.MP4 | mutagen.flac.FLAC m: mutagen.mp4.MP4 | mutagen.mp4.MP4 | mutagen.flac.FLAC
def __init__( def __init__(
@@ -41,8 +38,7 @@ class Metadata:
albumartist: str = "", albumartist: str = "",
date: str = "", date: str = "",
lyrics: str = "", lyrics: str = "",
path_cover: str = "", cover_data: bytes = None,
url_cover: str = "",
): ):
self.path_file = path_file self.path_file = path_file
self.title = title self.title = title
@@ -58,27 +54,25 @@ class Metadata:
self.composer = composer self.composer = composer
self.isrc = isrc self.isrc = isrc
self.lyrics = lyrics self.lyrics = lyrics
self.path_cover = path_cover self.cover_data = cover_data
self.url_cover = url_cover
self.m: mutagen.mp4.MP4 | mutagen.flac.FLAC | mutagen.mp3.MP3 = mutagen.File(self.path_file) self.m: mutagen.mp4.MP4 | mutagen.flac.FLAC | mutagen.mp3.MP3 = mutagen.File(self.path_file)
def _cover(self) -> bool: def _cover(self) -> bool:
result: bool = False result: bool = False
data_cover: str | bytes = self.cover_data(url=self.url_cover, path_file=self.path_cover)
if data_cover: if self.cover_data:
if isinstance(self.m, mutagen.flac.FLAC): if isinstance(self.m, mutagen.flac.FLAC):
flac_cover = flac.Picture() flac_cover = flac.Picture()
flac_cover.type = id3.PictureType.COVER_FRONT flac_cover.type = id3.PictureType.COVER_FRONT
flac_cover.data = data_cover flac_cover.data = self.cover_data
flac_cover.mime = "image/jpeg" flac_cover.mime = "image/jpeg"
self.m.clear_pictures() self.m.clear_pictures()
self.m.add_picture(flac_cover) self.m.add_picture(flac_cover)
elif isinstance(self.m, mutagen.mp3.MP3): elif isinstance(self.m, mutagen.mp3.MP3):
self.m.tags.add(APIC(encoding=3, data=data_cover)) self.m.tags.add(APIC(encoding=3, data=self.cover_data))
elif isinstance(self.m, mutagen.mp4.MP4): elif isinstance(self.m, mutagen.mp4.MP4):
cover_mp4 = mp4.MP4Cover(data_cover) cover_mp4 = mp4.MP4Cover(self.cover_data)
self.m.tags["covr"] = [cover_mp4] self.m.tags["covr"] = [cover_mp4]
result = True result = True
@@ -142,23 +136,3 @@ class Metadata:
self.m.tags["\xa9wrt"] = ", ".join(self.composer) if self.composer else "" self.m.tags["\xa9wrt"] = ", ".join(self.composer) if self.composer else ""
self.m.tags["\xa9lyr"] = self.lyrics self.m.tags["\xa9lyr"] = self.lyrics
self.m.tags["isrc"] = self.isrc self.m.tags["isrc"] = self.isrc
@staticmethod
def cover_data(url: str = None, path_file: str = None) -> str | bytes:
result: str | bytes = ""
if url:
try:
result = requests.get(url, timeout=REQUESTS_TIMEOUT_SEC).content
except Exception as e:
# TODO: Implement propper logging.
print(e)
elif path_file:
try:
with open(path_file, "rb") as f:
result = f.read()
except OSError as e:
# TODO: Implement propper logging.
print(e)
return result
+4 -2
View File
@@ -10,8 +10,6 @@ from tidal_dl_ng.constants import CoverDimensions, QualityVideo, SkipExisting
@dataclass @dataclass
class Settings: class Settings:
skip_existing: SkipExisting = SkipExisting.Disabled skip_existing: SkipExisting = SkipExisting.Disabled
# TODO: Implement cover download to a separate file.
# album_cover_save: bool = True
lyrics_embed: bool = False lyrics_embed: bool = False
lyrics_file: bool = False lyrics_file: bool = False
# TODO: Implement API KEY selection. # TODO: Implement API KEY selection.
@@ -36,6 +34,8 @@ class Settings:
video_convert_mp4: bool = True video_convert_mp4: bool = True
path_binary_ffmpeg: str = "" path_binary_ffmpeg: str = ""
metadata_cover_dimension: CoverDimensions = CoverDimensions.Px320 metadata_cover_dimension: CoverDimensions = CoverDimensions.Px320
metadata_cover_embed: bool = True
cover_album_file: bool = True
extract_flac: bool = True extract_flac: bool = True
@@ -79,6 +79,8 @@ class HelpSettings:
metadata_cover_dimension: str = ( metadata_cover_dimension: str = (
"The dimensions of the cover image embedded into the track. Possible values: 320x320, 640x640x 1280x1280." "The dimensions of the cover image embedded into the track. Possible values: 320x320, 640x640x 1280x1280."
) )
metadata_cover_embed: str = "Embed album cover into file."
cover_album_file: str = "Save cover to 'cover.jpg', if an album is downloaded."
extract_flac: str = "Extract FLAC audio tracks from MP4 containers and save them as `*.flac` (uses FFmpeg)." extract_flac: str = "Extract FLAC audio tracks from MP4 containers and save them as `*.flac` (uses FFmpeg)."
+1
View File
@@ -28,6 +28,7 @@ class ResultItem:
obj: object obj: object
quality: str quality: str
explicit: bool explicit: bool
date_user_added: str
@dataclass @dataclass
+32 -8
View File
@@ -78,8 +78,8 @@ class Ui_DialogSettings:
self.lh_flags_2 = QHBoxLayout() self.lh_flags_2 = QHBoxLayout()
self.lh_flags_2.setObjectName("lh_flags_2") self.lh_flags_2.setObjectName("lh_flags_2")
self.lh_flag_lyrics_embed = QHBoxLayout() self.lv_flag_lyrics_embed = QVBoxLayout()
self.lh_flag_lyrics_embed.setObjectName("lh_flag_lyrics_embed") self.lv_flag_lyrics_embed.setObjectName("lv_flag_lyrics_embed")
self.cb_lyrics_embed = QCheckBox(self.gb_flags) self.cb_lyrics_embed = QCheckBox(self.gb_flags)
self.cb_lyrics_embed.setObjectName("cb_lyrics_embed") self.cb_lyrics_embed.setObjectName("cb_lyrics_embed")
sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
@@ -88,20 +88,20 @@ class Ui_DialogSettings:
sizePolicy2.setHeightForWidth(self.cb_lyrics_embed.sizePolicy().hasHeightForWidth()) sizePolicy2.setHeightForWidth(self.cb_lyrics_embed.sizePolicy().hasHeightForWidth())
self.cb_lyrics_embed.setSizePolicy(sizePolicy2) self.cb_lyrics_embed.setSizePolicy(sizePolicy2)
self.lh_flag_lyrics_embed.addWidget(self.cb_lyrics_embed) self.lv_flag_lyrics_embed.addWidget(self.cb_lyrics_embed)
self.lh_flags_2.addLayout(self.lh_flag_lyrics_embed) self.lh_flags_2.addLayout(self.lv_flag_lyrics_embed)
self.lh_flag_lyrics_file = QHBoxLayout() self.lv_flag_lyrics_file = QVBoxLayout()
self.lh_flag_lyrics_file.setObjectName("lh_flag_lyrics_file") self.lv_flag_lyrics_file.setObjectName("lv_flag_lyrics_file")
self.cb_lyrics_file = QCheckBox(self.gb_flags) self.cb_lyrics_file = QCheckBox(self.gb_flags)
self.cb_lyrics_file.setObjectName("cb_lyrics_file") self.cb_lyrics_file.setObjectName("cb_lyrics_file")
sizePolicy1.setHeightForWidth(self.cb_lyrics_file.sizePolicy().hasHeightForWidth()) sizePolicy1.setHeightForWidth(self.cb_lyrics_file.sizePolicy().hasHeightForWidth())
self.cb_lyrics_file.setSizePolicy(sizePolicy1) self.cb_lyrics_file.setSizePolicy(sizePolicy1)
self.lh_flag_lyrics_file.addWidget(self.cb_lyrics_file) self.lv_flag_lyrics_file.addWidget(self.cb_lyrics_file)
self.lh_flags_2.addLayout(self.lh_flag_lyrics_file) self.lh_flags_2.addLayout(self.lv_flag_lyrics_file)
self.lv_flags.addLayout(self.lh_flags_2) self.lv_flags.addLayout(self.lh_flags_2)
@@ -131,6 +131,28 @@ class Ui_DialogSettings:
self.lv_flags.addLayout(self.lh_flag_3) self.lv_flags.addLayout(self.lh_flag_3)
self.lh_flags_4 = QHBoxLayout()
self.lh_flags_4.setObjectName("lh_flags_4")
self.lv_flag_metadata_cover_embed = QVBoxLayout()
self.lv_flag_metadata_cover_embed.setObjectName("lv_flag_metadata_cover_embed")
self.cb_metadata_cover_embed = QCheckBox(self.gb_flags)
self.cb_metadata_cover_embed.setObjectName("cb_metadata_cover_embed")
self.lv_flag_metadata_cover_embed.addWidget(self.cb_metadata_cover_embed)
self.lh_flags_4.addLayout(self.lv_flag_metadata_cover_embed)
self.lv_flag_cover_album_file = QVBoxLayout()
self.lv_flag_cover_album_file.setObjectName("lv_flag_cover_album_file")
self.cb_cover_album_file = QCheckBox(self.gb_flags)
self.cb_cover_album_file.setObjectName("cb_cover_album_file")
self.lv_flag_cover_album_file.addWidget(self.cb_cover_album_file)
self.lh_flags_4.addLayout(self.lv_flag_cover_album_file)
self.lv_flags.addLayout(self.lh_flags_4)
self.lv_main.addWidget(self.gb_flags) self.lv_main.addWidget(self.gb_flags)
self.gb_choices = QGroupBox(DialogSettings) self.gb_choices = QGroupBox(DialogSettings)
@@ -508,6 +530,8 @@ class Ui_DialogSettings:
self.cb_lyrics_file.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None)) self.cb_lyrics_file.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
self.cb_download_delay.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None)) self.cb_download_delay.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
self.cb_extract_flac.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None)) self.cb_extract_flac.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
self.cb_metadata_cover_embed.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
self.cb_cover_album_file.setText(QCoreApplication.translate("DialogSettings", "CheckBox", None))
self.gb_choices.setTitle(QCoreApplication.translate("DialogSettings", "Choices", None)) self.gb_choices.setTitle(QCoreApplication.translate("DialogSettings", "Choices", None))
self.l_icon_skip_existing.setText(QCoreApplication.translate("DialogSettings", "TextLabel", None)) self.l_icon_skip_existing.setText(QCoreApplication.translate("DialogSettings", "TextLabel", None))
self.l_skip_existing.setText(QCoreApplication.translate("DialogSettings", "TextLabel", None)) self.l_skip_existing.setText(QCoreApplication.translate("DialogSettings", "TextLabel", None))
+28 -2
View File
@@ -108,7 +108,7 @@
<item> <item>
<layout class="QHBoxLayout" name="lh_flags_2"> <layout class="QHBoxLayout" name="lh_flags_2">
<item> <item>
<layout class="QHBoxLayout" name="lh_flag_lyrics_embed"> <layout class="QVBoxLayout" name="lv_flag_lyrics_embed">
<item> <item>
<widget class="QCheckBox" name="cb_lyrics_embed"> <widget class="QCheckBox" name="cb_lyrics_embed">
<property name="sizePolicy"> <property name="sizePolicy">
@@ -128,7 +128,7 @@
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="lh_flag_lyrics_file"> <layout class="QVBoxLayout" name="lv_flag_lyrics_file">
<item> <item>
<widget class="QCheckBox" name="cb_lyrics_file"> <widget class="QCheckBox" name="cb_lyrics_file">
<property name="sizePolicy"> <property name="sizePolicy">
@@ -184,6 +184,32 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="lh_flags_4">
<item>
<layout class="QVBoxLayout" name="lv_flag_metadata_cover_embed">
<item>
<widget class="QCheckBox" name="cb_metadata_cover_embed">
<property name="text">
<string>CheckBox</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="lv_flag_cover_album_file">
<item>
<widget class="QCheckBox" name="cb_cover_album_file">
<property name="text">
<string>CheckBox</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>