From 0d6f2a93a7c0bbf2dbac67f8531ff16dd65584df Mon Sep 17 00:00:00 2001 From: Justin Donofrio Date: Sat, 9 Nov 2024 10:10:43 -0500 Subject: [PATCH] fix ogg covers --- requirements.txt | 1 + src/onthespot/api/spotify.py | 7 +-- src/onthespot/downloader.py | 2 +- src/onthespot/gui/dl_progressbtn.py | 10 ++-- src/onthespot/gui/mainui.py | 6 +-- src/onthespot/parse_item.py | 2 +- src/onthespot/post_download.py | 83 +++++++++++++++++------------ 7 files changed, 64 insertions(+), 47 deletions(-) diff --git a/requirements.txt b/requirements.txt index 715a02d..b92ada9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ librespot +mutagen Pillow pyperclip PyQt6 diff --git a/src/onthespot/api/spotify.py b/src/onthespot/api/spotify.py index cce42cb..b73302a 100644 --- a/src/onthespot/api/spotify.py +++ b/src/onthespot/api/spotify.py @@ -138,6 +138,7 @@ def spotify_re_init_session(account): account['status'] = 'active' account['account_type'] = session.get_user_attribute("type") bitrate = "160k" + account_type = session.get_user_attribute("type") if account_type == "premium": bitrate = "320k" account['bitrate'] = bitrate @@ -256,7 +257,7 @@ def spotify_get_lyrics(token, item_id, item_type, metadata, filepath): if len(lyrics) <= 2: return False if config.get('use_lrc_file'): - with open(filepath[0:-len(config.get('media_format'))] + 'lrc', 'w', encoding='utf-8') as f: + with open(filepath + '.lrc', 'w', encoding='utf-8') as f: f.write(merged_lyrics) if config.get('embed_lyrics'): if item_type == "track": @@ -547,7 +548,7 @@ def spotify_get_episode_metadata(token, episode_id_str): episode_data = make_call(f"https://api.spotify.com/v1/episodes/{episode_id_str}", headers=headers) info = {} - languages = episode_data.get('languages', []) + languages = episode_data.get('languages', '') info['album_name'] = episode_data.get("show", {}).get("name", "") info['title'] = episode_data.get('name', "") @@ -558,7 +559,7 @@ def spotify_get_episode_metadata(token, episode_id_str): info['album_artists'] = conv_list_format([episode_data.get('show', {}).get('publisher', "")]) info['language'] = conv_list_format(languages) info['description'] = str(episode_data.get('description', "") if episode_data.get('description', "") != "" else "") - info['copyright'] = episode_data.get('show', {}).get('copyrights', []) + info['copyright'] = conv_list_format(episode_data.get('show', {}).get('copyrights', '')) info['length'] = str(episode_data.get('duration_ms', '')) info['explicit'] = episode_data.get('explicit', '') info['is_playable'] = episode_data.get('is_playable', '') diff --git a/src/onthespot/downloader.py b/src/onthespot/downloader.py index 31dedfb..757da6a 100644 --- a/src/onthespot/downloader.py +++ b/src/onthespot/downloader.py @@ -359,7 +359,7 @@ def run(self): os.remove(file_path) continue else: - time.sleep(1) + time.sleep(0.5) def stop(self): logger.info('Stopping Download Worker') diff --git a/src/onthespot/gui/dl_progressbtn.py b/src/onthespot/gui/dl_progressbtn.py index 08de9fb..017a142 100644 --- a/src/onthespot/gui/dl_progressbtn.py +++ b/src/onthespot/gui/dl_progressbtn.py @@ -10,9 +10,10 @@ class DownloadActionsButtons(QWidget): - def __init__(self, item_id, pbar, copy_btn, cancel_btn, retry_btn, open_btn, locate_btn, delete_btn, parent=None): + def __init__(self, item_id, item_metadata, pbar, copy_btn, cancel_btn, retry_btn, open_btn, locate_btn, delete_btn, parent=None): super(DownloadActionsButtons, self).__init__(parent) self.item_id = item_id + self.item_metadata = item_metadata layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) @@ -42,7 +43,7 @@ def __init__(self, item_id, pbar, copy_btn, cancel_btn, retry_btn, open_btn, loc self.setLayout(layout) def copy_link(self): - pyperclip.copy("FIX ME") + pyperclip.copy(self.item_metadata['item_url']) def cancel_item(self): download_queue[self.item_id]['item_status'] = "Cancelled" @@ -72,10 +73,7 @@ def delete_file(self): file_path = download_queue[self.item_id]['file_path'] file = os.path.abspath(file_path) os.remove(file) - self.item["gui"]["status_label"].setText(self.tr("Deleted")) - self.play_btn.hide() - self.save_btn.hide() - self.queue_btn.hide() + download_queue[self.item_id]["gui"]["status_label"].setText(self.tr("Deleted")) self.open_btn.hide() self.locate_btn.hide() self.delete_btn.hide() diff --git a/src/onthespot/gui/mainui.py b/src/onthespot/gui/mainui.py index 15f46ce..616a3e0 100644 --- a/src/onthespot/gui/mainui.py +++ b/src/onthespot/gui/mainui.py @@ -42,7 +42,7 @@ def run(self): logger.error(f"Unknown Exception for {item}: {str(e)}") pending[item_id] = item else: - time.sleep(4) + time.sleep(0.5) class MainWindow(QMainWindow): @@ -369,7 +369,7 @@ def add_item_to_download_list(self, item, item_metadata): if config.get("download_delete_btn"): delete_btn = QPushButton() #delete_btn.setText('Delete') - delete_icon = QIcon(os.path.join(config.app_root, 'resources', 'icons', 'delete.png')) + delete_icon = QIcon(os.path.join(config.app_root, 'resources', 'icons', 'trash.png')) delete_btn.setIcon(delete_icon) delete_btn.setToolTip(self.tr('Delete')) delete_btn.setMinimumHeight(30) @@ -392,7 +392,7 @@ def add_item_to_download_list(self, item, item_metadata): status_label = QLabel(self.tbl_dl_progress) status_label.setText(self.tr("Waiting")) - actions = DownloadActionsButtons(item['item_id'], pbar, copy_btn, cancel_btn, retry_btn, open_btn, locate_btn, delete_btn) + actions = DownloadActionsButtons(item['item_id'], item_metadata, pbar, copy_btn, cancel_btn, retry_btn, open_btn, locate_btn, delete_btn) rows = self.tbl_dl_progress.rowCount() self.tbl_dl_progress.insertRow(rows) diff --git a/src/onthespot/parse_item.py b/src/onthespot/parse_item.py index 440d545..21cf85d 100755 --- a/src/onthespot/parse_item.py +++ b/src/onthespot/parse_item.py @@ -221,4 +221,4 @@ def parsingworker(): parse_url(album_url) continue else: - time.sleep(4) + time.sleep(0.5) diff --git a/src/onthespot/post_download.py b/src/onthespot/post_download.py index 74fb105..8a6faaf 100644 --- a/src/onthespot/post_download.py +++ b/src/onthespot/post_download.py @@ -2,9 +2,10 @@ import subprocess from io import BytesIO import base64 -import time import requests from PIL import Image +from mutagen.flac import Picture +from mutagen.oggvorbis import OggVorbis from .otsconfig import config from .runtimedata import get_logger @@ -43,6 +44,10 @@ def convert_audio_format(filename, metadata, bitrate, default_format): command.append(param) # Append metadata + if config.get("embed_branding"): + branding = "Downloaded by OnTheSpot, https://github.com/justin025/onthespot" + command += ['-metadata', 'comment={}'.format(branding)] + for key in metadata.keys(): value = metadata[key] @@ -99,10 +104,7 @@ def convert_audio_format(filename, metadata, bitrate, default_format): command += ['-metadata', 'copyright={}'.format(value)] elif key == 'description' and config.get("embed_description"): - if filetype == '.mp3': - command += ['-metadata', 'COMM={}'.format(value)] - else: - command += ['-metadata', 'comment={}'.format(value)] + command += ['-metadata', 'COMM={}'.format(value)] elif key == 'language' and config.get("embed_language"): if filetype == '.mp3': @@ -195,8 +197,8 @@ def convert_audio_format(filename, metadata, bitrate, default_format): else: subprocess.check_call(command, shell=False) os.remove(temp_name) - else: - raise FileNotFoundError + # Delete + def set_music_thumbnail(filename, metadata): @@ -220,7 +222,7 @@ def set_music_thumbnail(filename, metadata): with open(image_path, 'wb') as cover: cover.write(buf.read()) - if config.get('embed_cover') and filetype != '.wav': + if config.get('embed_cover') and filetype not in ('.wav', '.ogg'): if os.path.isfile(temp_name): os.remove(temp_name) @@ -232,41 +234,56 @@ def set_music_thumbnail(filename, metadata): if int(os.environ.get('SHOW_FFMPEG_OUTPUT', 0)) == 0: command += ['-loglevel', 'error', '-hide_banner', '-nostats'] - if filetype == '.ogg': - #with open(image_path, "rb") as image_file: - # base64_image = base64.b64encode(image_file.read()).decode('utf-8') - # - # Argument list too long, downscale the image instead - - with Image.open(image_path) as img: - new_size = (250, 250) # 250 seems to be the max - img = img.resize(new_size, Image.Resampling.LANCZOS) - with BytesIO() as output: - img.save(output, format=config.get("album_cover_format")) - output.seek(0) - base64_image = base64.b64encode(output.read()).decode('utf-8') - - # METADATA_BLOCK_PICTURE is a better supported format but I don't know how to write it - command += [ - "-c", "copy", "-metadata", f"coverart={base64_image}", "-metadata", f"coverartmime=image/{config.get('album_cover_format')}" - ] - else: - command += [ - '-i', image_path, '-map', '0:a', '-map', '1:v', '-c', 'copy', '-disposition:v:0', 'attached_pic', - '-metadata:s:v', 'title=Cover', '-metadata:s:v', 'comment=Cover (front)' - ] + # Windows equivilant of argument list too long + #if filetype == '.ogg': + # #with open(image_path, "rb") as image_file: + # # base64_image = base64.b64encode(image_file.read()).decode('utf-8') + # # + # # Argument list too long, downscale the image instead + # + # with Image.open(image_path) as img: + # new_size = (250, 250) # 250 seems to be the max + # img = img.resize(new_size, Image.Resampling.LANCZOS) + # with BytesIO() as output: + # img.save(output, format=config.get("album_cover_format")) + # output.seek(0) + # base64_image = base64.b64encode(output.read()).decode('utf-8') + # + # # METADATA_BLOCK_PICTURE is a better supported format but I don't know how to write it + # command += [ + # "-c", "copy", "-metadata", f"coverart={base64_image}", "-metadata", f"coverartmime=image/{config.get('album_cover_format')}" + # ] + #else: + command += [ + '-i', image_path, '-map', '0:a', '-map', '1:v', '-c', 'copy', '-disposition:v:0', 'attached_pic', + '-metadata:s:v', 'title=Cover', '-metadata:s:v', 'comment=Cover (front), -id3v2_version 1' + ] command += [filename] logger.debug( f'Setting thumbnail with ffmpeg. Built commandline {command}' ) if os.name == 'nt': - # Wait for windows file lock to release - time.sleep(0.5) subprocess.check_call(command, shell=False, creationflags=subprocess.CREATE_NO_WINDOW) else: subprocess.check_call(command, shell=False) + if config.get('embed_cover') and filetype == '.ogg': + with open(image_path, 'rb') as image_file: + image_data = image_file.read() + tags = OggVorbis(filename) + picture = Picture() + picture.data = image_data + picture.type = 3 + picture.desc = "Cover" + picture.mime = f"image/{config.get('album_cover_format')}" + picture_data = picture.write() + encoded_data = base64.b64encode(picture_data) + vcomment_value = encoded_data.decode("ascii") + tags["metadata_block_picture"] = [vcomment_value] + tags.save() + + if os.path.exists(temp_name): os.remove(temp_name) if not config.get('save_album_cover'):