From c12a109d4b7c609570bf3e6e41d11f1309822ebb Mon Sep 17 00:00:00 2001 From: Elijah Lopez Date: Tue, 9 Apr 2024 19:37:45 -0400 Subject: [PATCH] fix: upgrade PIL --- CHANGELOG.txt | 4 + build_files/TkinterDnD2/TkinterDnD.py | 7 +- build_files/mc_version_info.txt | 9 +-- build_files/mcu_version_info.txt | 1 - build_files/onedir.spec | 8 +- build_files/portable.spec | 7 +- build_files/setup_script.iss | 2 +- build_files/updater.spec | 11 +-- requirements.txt | 2 +- src/meta.py | 3 +- src/music_caster.py | 18 ++--- src/utils.py | 108 +++++++++++++------------- 12 files changed, 80 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 0c1c3bcb..1f79522c 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,5 +1,9 @@ Music Caster by Elijah Lopez Changelog +5.18.1 +- [Fix] Security update +- [Fix] Play AIFF from explorer + 5.18.0 - [Feat] Local support for AIFF? diff --git a/build_files/TkinterDnD2/TkinterDnD.py b/build_files/TkinterDnD2/TkinterDnD.py index d7a8f467..b11e033a 100644 --- a/build_files/TkinterDnD2/TkinterDnD.py +++ b/build_files/TkinterDnD2/TkinterDnD.py @@ -24,10 +24,8 @@ Tk window and all its descendants.""" try: - # noinspection PyPep8Naming - import Tkinter as tkinter - # noinspection PyPep8Naming - import Tix as tix + import Tkinter as tkinter # type: ignore + import Tix as tix # type: ignore except ImportError: import tkinter from tkinter import tix @@ -73,7 +71,6 @@ class DnDEvent: pass -# noinspection PyUnresolvedReferences,PyPep8Naming class DnDWrapper: """Internal class.""" # some of the percent substitutions need to be enclosed in braces diff --git a/build_files/mc_version_info.txt b/build_files/mc_version_info.txt index 6017ef03..fec2f2da 100644 --- a/build_files/mc_version_info.txt +++ b/build_files/mc_version_info.txt @@ -1,10 +1,9 @@ # UTF-8 # For more details about fixed file info 'ffi' see: http://msdn.microsoft.com/en-us/library/ms646997.aspx -# noinspection PyUnresolvedReferences VSVersionInfo( ffi=FixedFileInfo( - prodvers=(5, 18, 0, 0), - filevers=(5, 18, 0, 0), + prodvers=(5, 18, 1, 0), + filevers=(5, 18, 1, 0), # Contains a bitmask that specifies the valid bits 'flags'r mask=0x17, # Contains a bitmask that specifies the Boolean attributes of the file. @@ -28,12 +27,12 @@ VSVersionInfo( '000004b0', [StringStruct('CompanyName', 'Elijah Lopez'), StringStruct('FileDescription', 'Music Caster'), - StringStruct('FileVersion', '5.18.0.0'), + StringStruct('FileVersion', '5.18.1.0'), StringStruct('InternalName', 'Music Caster'), StringStruct('LegalCopyright', 'Copyright (c) 2019 - 2024, Elijah Lopez'), StringStruct('OriginalFilename', 'Music Caster.exe'), StringStruct('ProductName', 'Music Caster'), - StringStruct('ProductVersion', '5.18.0.0')]) + StringStruct('ProductVersion', '5.18.1.0')]) ]), VarFileInfo([VarStruct('Translation', [0, 1200])]) ] diff --git a/build_files/mcu_version_info.txt b/build_files/mcu_version_info.txt index a6e38243..70a5a7c4 100644 --- a/build_files/mcu_version_info.txt +++ b/build_files/mcu_version_info.txt @@ -1,5 +1,4 @@ # UTF-8 -# noinspection PyUnresolvedReferences VSVersionInfo( ffi=FixedFileInfo( prodvers=(2, 3, 0, 0), diff --git a/build_files/onedir.spec b/build_files/onedir.spec index aca3f176..f415dfcc 100644 --- a/build_files/onedir.spec +++ b/build_files/onedir.spec @@ -1,15 +1,11 @@ # -*- mode: python ; coding: utf-8 -*- import os -from glob import iglob -# noinspection PyPackageRequirements from PyInstaller.building.api import PYZ, EXE, COLLECT -# noinspection PyPackageRequirements -from PyInstaller.building.build_main import Analysis, Tree -# noinspection PyPackageRequirements +from PyInstaller.building.build_main import Analysis, Tree # type: ignore from PyInstaller.config import CONF import platform -CONF['distpath'] = './src/dist' +CONF['distpath'] = './src/dist' # type: ignore block_cipher = None # CONF['workpath'] = './build' # TODO: test on MAC OSX diff --git a/build_files/portable.spec b/build_files/portable.spec index 2dbd0dbf..b74a8952 100644 --- a/build_files/portable.spec +++ b/build_files/portable.spec @@ -1,14 +1,11 @@ # -*- mode: python ; coding: utf-8 -*- import os -# noinspection PyPackageRequirements from PyInstaller.building.api import PYZ, EXE -# noinspection PyPackageRequirements -from PyInstaller.building.build_main import Analysis, Tree -# noinspection PyPackageRequirements +from PyInstaller.building.build_main import Analysis, Tree # type: ignore from PyInstaller.config import CONF import platform -CONF['distpath'] = './src/dist' +CONF['distpath'] = './src/dist' # type: ignore # CONF['workpath'] = './build' block_cipher = None a = Analysis([f'{os.getcwd()}/src/music_caster.py'], diff --git a/build_files/setup_script.iss b/build_files/setup_script.iss index 1d6df4b6..51a4d0a4 100644 --- a/build_files/setup_script.iss +++ b/build_files/setup_script.iss @@ -1,5 +1,5 @@ #define MyAppName "Music Caster" -#define MyAppVersion "5.18.0" +#define MyAppVersion "5.18.1" #define MyAppPublisher "Elijah Lopez" #define MyAppURL "https://elijahlopez.ca/software#music-caster" #define MyAppExeName "Music Caster.exe" diff --git a/build_files/updater.spec b/build_files/updater.spec index 1ab38324..f213aa02 100644 --- a/build_files/updater.spec +++ b/build_files/updater.spec @@ -1,18 +1,14 @@ # -*- mode: python ; coding: utf-8 -*- import os import sys -# noinspection PyPackageRequirements from PyInstaller.building.api import PYZ, EXE -# noinspection PyPackageRequirements from PyInstaller.building.build_main import Analysis -# noinspection PyPackageRequirements from PyInstaller.config import CONF -CONF['distpath'] = './dist' -CONF['workpath'] = './_build' +CONF['distpath'] = './dist' # type: ignore +CONF['workpath'] = './_build' # type: ignore block_cipher = None -# noinspection PyTypeChecker -sys.modules['FixTk'] = None +sys.modules['FixTk'] = None # type: ignore a = Analysis([f'{os.getcwd()}/updater.py'], pathex=[os.getcwd()], binaries=[], @@ -46,7 +42,6 @@ exe = EXE(pyz, icon=os.path.abspath('resources/Updater.ico'), version='mcu_version_info.txt') # ONLY USE FOR DEBUGGING -# noinspection PyUnresolvedReferences # coll = COLLECT(exe, # a.binaries - TOC([('libcrypto-1_1.dll', None, None)]), # a.zipfiles, diff --git a/requirements.txt b/requirements.txt index 14ba8627..d5b9a3f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ pgi; sys_platform == 'linux' testresources; sys_platform == 'linux' ujson~=5.5 mutagen~=1.45 -Pillow~=10.2.0 +Pillow~=10.3.0 PyChromecast~=14.0 zeroconf~=0.37 pynput~=1.4.5 diff --git a/src/meta.py b/src/meta.py index e9d729da..81c0f759 100644 --- a/src/meta.py +++ b/src/meta.py @@ -1,4 +1,4 @@ -VERSION = latest_version = '5.18.0' +VERSION = latest_version = '5.18.1' UPDATE_MESSAGE = """ [NEW] Better Error Capturing [MSG] Language translators wanted @@ -55,6 +55,7 @@ # re-define AUDIO_EXTS AUDIO_EXTS = {f'.{ext}' for ext in AUDIO_EXTS} AUDIO_EXTS.add('.m3u') +AUDIO_HANDLER_EXTS = ('mp3', 'flac', 'm4a', 'aac', 'ogg', 'opus', 'aiff', 'wma', 'wav', 'mpeg', 'm3u', 'm3u8') FONT_NORMAL = 'Segoe UI', 11 FONT_SMALL = 'Segoe UI', 10 diff --git a/src/music_caster.py b/src/music_caster.py index c161674f..2be0d1c8 100644 --- a/src/music_caster.py +++ b/src/music_caster.py @@ -1,19 +1,15 @@ +# flake8: noqa: E402 from meta import * import time start_time = time.monotonic() -# noinspection PyUnresolvedReferences from contextlib import suppress from itertools import islice -# noinspection PyUnresolvedReferences import io import multiprocessing as mp import os -# noinspection PyUnresolvedReferences import platform import threading -# noinspection PyUnresolvedReferences -from subprocess import Popen, PIPE, DEVNULL -# noinspection PyUnresolvedReferences +from subprocess import Popen, PIPE, DEVNULL # noqa import re import sys from shared import is_already_running @@ -158,8 +154,7 @@ def activate_instance(port=2001, default_timeout=0.5, to_port=2004): import concurrent.futures from collections import deque from collections.abc import Iterable - # noinspection PyUnresolvedReferences - import encodings.idna # DO NOT REMOVE + import encodings.idna # noqa # DO NOT REMOVE from functools import cmp_to_key import hashlib from copy import deepcopy @@ -183,10 +178,10 @@ def activate_instance(port=2001, default_timeout=0.5, to_port=2004): from audio_player import AudioPlayer from utils import * + from modules.resolution_switcher import fmt_res, get_all_resolutions, set_resolution, get_all_refresh_rates, get_initial_res, is_plugged_in, get_initial_dpi_scale get_initial_dpi_scale() from gui import MainWindow, MiniPlayerWindow, focus_window import PySimpleGUI as Sg - from modules.resolution_switcher import set_resolution, get_all_refresh_rates, get_initial_res, is_plugged_in from modules.db import DatabaseConnection, init_db # 0.5 seconds gone to 3rd party imports @@ -241,8 +236,7 @@ def activate_instance(port=2001, default_timeout=0.5, to_port=2004): settings_file_lock = threading.Lock() last_play_command = settings_last_modified = 0 update_last_checked = time.time() # check every hour - # noinspection PyTypeChecker - cast: Chromecast = None + cast: Chromecast = None # type: ignore all_tracks, url_metadata, all_tracks_sorted = {}, {}, [] tray_playlists = [t('Playlists Tab')] CHECK_MARK = '✓' @@ -1070,7 +1064,6 @@ def api_get_dz(): range_header = {'Range': request.headers.get('Range', 'bytes=0-')} r = requests.get(file_url, headers=range_header, stream=True) start_bytes = int(range_header['Range'].split('=', 1)[1].split('-', 1)[0]) - # noinspection PyProtectedMember blowfish_key = metadata['bf_key'] iv = b'\x00\x01\x02\x03\x04\x05\x06\x07' @@ -1553,7 +1546,6 @@ def ydl_get_metadata(item, duration_helper=True): metadata['art'] = item['thumbnail'] return metadata - # noinspection PyTypeChecker def get_url_metadata(url, fetch_art=True) -> list: # TODO: move to utils.py and add parameter url_metadata_cache """ diff --git a/src/utils.py b/src/utils.py index a7fca0bb..1d8a7453 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,3 +1,4 @@ +# flake8: noqa: E402 import audioop import base64 from contextlib import suppress @@ -29,36 +30,34 @@ from zipfile import ZipFile import tarfile import shutil - +# local imports from b64_images import * from base64 import b64encode, b64decode - # 3rd party imports import deemix.utils.localpaths as __lp import webbrowser __lp.musicdata = '/dz' import mutagen -from mutagen import MutagenError +from mutagen._util import MutagenError +import mutagen._file from mutagen.aac import AAC from mutagen.oggopus import OggOpus from mutagen.oggvorbis import OggVorbis import mutagen.flac -# noinspection PyProtectedMember -from mutagen.id3 import ID3NoHeaderError -# noinspection PyProtectedMember +import mutagen.id3 +from mutagen.id3._util import ID3NoHeaderError from mutagen.mp3 import HeaderNotFoundError, EasyMP3, MP3 from mutagen.mp4 import MP4, MP4Cover from mutagen.wave import WAVE import pyaudio from pychromecast import CastInfo import pypresence -import pyqrcode from PIL import Image, ImageFile, ImageDraw, ImageFont, UnidentifiedImageError import requests from wavinfo import WavInfoReader, WavInfoEOFError # until mutagen supports .wav from youtube_comment_downloader import YoutubeCommentDownloader -from modules.resolution_switcher import fmt_res, get_all_resolutions, get_initial_dpi_scale from meta import * +from meta import AUDIO_HANDLER_EXTS # CONSTANTS @@ -270,7 +269,6 @@ def __init__(self, cast_info_or_none=None): def id(self): return str(self.__device.uuid) if isinstance(self.__device, CastInfo) else None - # noinspection PyPep8Naming @classmethod def LOCAL_DEVICE(cls): return t('Local device') @@ -413,19 +411,19 @@ def get_audio_length(file_path) -> int: try: if file_path.casefold().endswith('.wav'): a = WavInfoReader(file_path) - length = a.data.frame_count / a.fmt.sample_rate + length = a.data.frame_count / a.fmt.sample_rate # type:ignore elif file_path.casefold().endswith('.wma'): try: - audio_info = mutagen.File(file_path).info + audio_info = mutagen.File(file_path).info # type:ignore length = audio_info.length except AttributeError: audio_info = AAC(file_path).info length = audio_info.length elif file_path.casefold().endswith('.opus'): - audio_info = mutagen.File(file_path).info + audio_info = mutagen.File(file_path).info # type:ignore length = audio_info.length else: - audio_info = mutagen.File(file_path).info + audio_info = mutagen.File(file_path).info # type:ignore length = audio_info.length return length except (AttributeError, HeaderNotFoundError, MutagenError, WavInfoEOFError, StopIteration) as e: @@ -442,7 +440,7 @@ def valid_audio_file(uri) -> bool: def set_metadata(file_path: str, metadata: dict): ext = os.path.splitext(file_path)[1].casefold() - audio = mutagen.File(file_path) + audio: mutagen._file.FileType = mutagen.File(file_path) # type: ignore title = metadata['title'] artists = metadata['artist'].split(', ') if ', ' in metadata['artist'] else metadata['artist'].split(',') album = metadata['album'] @@ -455,32 +453,36 @@ def set_metadata(file_path: str, metadata: dict): if '/' not in track_place: tracks = max(1, int(track_place)) track_place = f'{track_place}/{tracks}' - if isinstance(audio, (MP3, mutagen.wave.WAVE)) or ext in {'.mp3', '.wav'}: + if isinstance(audio, (MP3, WAVE)) or ext in {'.mp3', '.wav'}: if title: - audio['TIT2'] = mutagen.id3.TIT2(text=metadata['title']) + audio['TIT2'] = mutagen.id3._frames.TIT2(text=metadata['title']) if artists: - audio['TPE1'] = mutagen.id3.TPE1(text=artists) - audio['TPE2'] = mutagen.id3.TPE1(text=artists[0]) # album artist - audio['TCMP'] = mutagen.id3.TCMP(text=track_number) - audio['TRCK'] = mutagen.id3.TRCK(text=track_place) - audio['TPOS'] = mutagen.id3.TPOS(text=track_place) + audio['TPE1'] = mutagen.id3._frames.TPE1(text=artists) + audio['TPE2'] = mutagen.id3._frames.TPE1(text=artists[0]) # album artist + audio['TCMP'] = mutagen.id3._frames.TCMP(text=track_number) + audio['TRCK'] = mutagen.id3._frames.TRCK(text=track_place) + audio['TPOS'] = mutagen.id3._frames.TPOS(text=track_place) if album: - audio['TALB'] = mutagen.id3.TALB(text=album) + audio['TALB'] = mutagen.id3._frames.TALB(text=album) # audio['TDRC'] = mutagen.id3.TDRC(text=metadata['year']) # audio['TCON'] = mutagen.id3.TCON(text=metadata['genre']) # audio['TPUB'] = mutagen.id3.TPUB(text=metadata['publisher']) - audio['TXXX:RATING'] = mutagen.id3.TXXX(text=rating, desc='RATING') - audio['TXXX:ITUNESADVISORY'] = mutagen.id3.TXXX(text=rating, desc='ITUNESADVISORY') + audio['TXXX:RATING'] = mutagen.id3._frames.TXXX(text=rating, desc='RATING') + audio['TXXX:ITUNESADVISORY'] = mutagen.id3._frames.TXXX(text=rating, desc='ITUNESADVISORY') if metadata['art'] is not None: img_data = b64decode(metadata['art']) - audio['APIC:'] = mutagen.id3.APIC(encoding=0, mime=metadata['mime'], type=3, data=img_data) + audio['APIC:'] = mutagen.id3._frames.APIC(encoding=0, mime=metadata['mime'], type=3, data=img_data) else: # remove all album art for k in tuple(audio.keys()): - if 'APIC:' in k: audio.pop(k) + if 'APIC:' in k: + audio.pop(k) elif isinstance(audio, MP4): - if title: audio['©nam'] = [title] - if artists: audio['©ART'] = artists - if album: audio['©alb'] = [album] + if title: + audio['©nam'] = [title] + if artists: + audio['©ART'] = artists + if album: + audio['©alb'] = [album] audio['trkn'] = [tuple((int(x) for x in track_place.split('/')))] audio['rtng'] = [int(rating)] if metadata['art'] is not None: @@ -490,9 +492,12 @@ def set_metadata(file_path: str, metadata: dict): elif 'covr' in audio: del audio['covr'] elif isinstance(audio, (OggOpus, OggVorbis)): - if title: audio['title'] = [title] - if artists: audio['artist'] = artists - if album: audio['album'] = [album] + if title: + audio['title'] = [title] + if artists: + audio['artist'] = artists + if album: + audio['album'] = [album] audio['rtng'] = [rating] audio['trkn'] = track_place if metadata['art'] is not None: @@ -503,12 +508,12 @@ def set_metadata(file_path: str, metadata: dict): audio.pop('APIC:', None) audio.pop('metadata_block_picture', None) else: # FLAC? - if title: audio['TITLE'] = title - if artists: audio['ARTIST'] = artists - if album: audio['ALBUM'] = album - audio['TRACKNUMBER'] = track_number - audio['TRACKTOTAL'] = track_place.split('/')[1] - audio['ITUNESADVISORY'] = rating + if title: audio['TITLE'] = title # type: ignore + if artists: audio['ARTIST'] = artists # type: ignore + if album: audio['ALBUM'] = album # type: ignore + audio['TRACKNUMBER'] = track_number # type: ignore + audio['TRACKTOTAL'] = track_place.split('/')[1] # type: ignore + audio['ITUNESADVISORY'] = rating # type: ignore if metadata['art'] is not None: if ext == '.flac': img_data = b64decode(metadata['art']) @@ -516,20 +521,19 @@ def set_metadata(file_path: str, metadata: dict): pic.mime = metadata['mime'] pic.data = img_data pic.type = 3 - # noinspection PyUnresolvedReferences - audio.clear_pictures() - audio.add_picture(pic) + audio.clear_pictures() # type: ignore + audio.add_picture(pic) # type: ignore else: - audio['APIC:'] = metadata['art'] - audio['mime'] = metadata['mime'] + audio['APIC:'] = metadata['art'] # type: ignore + audio['mime'] = metadata['mime'] # type: ignore else: if ext == '.flac': - # noinspection PyUnresolvedReferences - audio.clear_pictures() + audio.clear_pictures() # type: ignore else: # remove all album art for k in tuple(audio.keys()): - if 'APIC:' in k: audio.pop(k) + if 'APIC:' in k: + audio.pop(k) audio.save() @@ -755,7 +759,6 @@ def ydl_extract_info(url, quiet=False): raise IOError from e -# noinspection PyTypeChecker @lru_cache(maxsize=1) def get_yt_id(url, ignore_playlist=False): query = urlparse(url) @@ -853,10 +856,11 @@ def add_reg_handlers(path_to_exe, add_folder_context=True): wr.SetValueEx(key, None, 0, wr.REG_SZ, f'"{path_to_exe}" -n --shell "%1"') # set file handlers - for ext in {'mp3', 'flac', 'm4a', 'aac', 'ogg', 'opus', 'wma', 'wav', 'mpeg', 'm3u', 'm3u8'}: + for ext in AUDIO_HANDLER_EXTS: key_path = fr'{classes_path}\.{ext}' try: # check if key exists - with wr.OpenKeyEx(wr.HKEY_CURRENT_USER, key_path, 0, read_access) as _: pass + with wr.OpenKeyEx(wr.HKEY_CURRENT_USER, key_path, 0, read_access) as _: + pass except (WindowsError, FileNotFoundError): # create key for extension if it does not exist with MC as the default program with wr.CreateKeyEx(wr.HKEY_CURRENT_USER, key_path, 0, write_access) as key: @@ -864,7 +868,6 @@ def add_reg_handlers(path_to_exe, add_folder_context=True): wr.SetValueEx(key, None, 0, wr.REG_SZ, mc_file) # add to Open With (prompts user to set default program when they try playing a file) with wr.CreateKeyEx(wr.HKEY_CURRENT_USER, fr'{key_path}\\OpenWithProgids', 0, write_access) as key: - # noinspection PyTypeChecker wr.SetValueEx(key, mc_file, 0, wr.REG_NONE, b'') # type needs to be bytes play_folder_key_path = fr'{classes_path}\Directory\shell\MusicCasterPlayFolder' @@ -1005,8 +1008,7 @@ def get_proxies(add_local=True): scraped_proxies = set() soup = BeautifulSoup(response.text, 'lxml') table = soup.find('table') - # noinspection PyUnresolvedReferences - for row in table.find_all('tr'): + for row in table.find_all('tr'): # type: ignore count = 0 proxy = '' try: @@ -1340,12 +1342,10 @@ def truncate_title(title): # TKDnD -# noinspection PyProtectedMember def drop_target_register(widget, *dndtypes): widget.tk.call('tkdnd::drop_target', 'register', widget._w, dndtypes) -# noinspection PyProtectedMember def dnd_bind(widget, sequence=None, func=None, add=None, need_cleanup=True): """Internal function.""" what = ('bind', widget._w)