diff --git a/README.md b/README.md index d8aa297..0f0a1b7 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,9 @@ Some new features that I added: ## Usage ``` -usage: gamdl [-h] [-u [URLS_TXT]] [-w WVD_LOCATION] [-f FINAL_PATH] [-t TEMP_PATH] [-c COOKIES_LOCATION] [-m] [-p] - [-o] [-n] [-s] [-e] [-i] [-v] - [url ...] +usage: gamdl [-h] [-u [URLS_TXT]] [-w WVD_LOCATION] [-f FINAL_PATH] [-t TEMP_PATH] [-c COOKIES_LOCATION] [-m] + [-p] [-a] [-o] [-n] [-s] [-e] [-i] [-v] + [url ...] Download Apple Music songs/music videos/albums/playlists @@ -66,6 +66,7 @@ options: -m, --disable-music-video-skip Disable music video skip on playlists/albums (default: False) -p, --prefer-hevc Prefer HEVC over AVC (default: False) + -a, --heaac Download songs/music videos with HE-AAC instead of AAC (default: False) -o, --overwrite Overwrite existing files (default: False) -n, --no-lrc Don't create .lrc file (default: False) -s, --skip-cleanup Skip cleanup (default: False) @@ -78,15 +79,15 @@ options: ## Songs/Music Videos quality * Songs: - * 256kbps AAC + * 256kbps AAC / HE-AAC 64kbps * Music Videos (varies depending on the video): - * 4K HEVC 20mbps / AAC 256kbps - * 4K HEVC 12mbps / AAC 256kbps - * 1080p AVC 10mbps / AAC 256kbps - * 1080p AVC 6.5bps / AAC 256kbps - * 720p AVC 4mbps / AAC 256kbps - * 480p AVC 2mbps / AAC 256kbps - * 480p AVC 1.5mbps / AAC 256kbps - * 360p AVC 1mbps / AAC 256kbps + * 4K HEVC 20mbps, AAC 256kbps / HE-AAC 64kbps + * 4K HEVC 12mbps, AAC 256kbps / HE-AAC 64kbps + * 1080p AVC 10mbps, AAC 256kbps / HE-AAC 64kbps + * 1080p AVC 6.5bps, AAC 256kbps / HE-AAC 64kbps + * 720p AVC 4mbps, AAC 256kbps / HE-AAC 64kbps + * 576p AVC 2mbps, AAC 256kbps / HE-AAC 64kbps + * 480p AVC 1.5mbps, AAC 256kbps / HE-AAC 64kbps + * 360p AVC 1mbps, AAC 256kbps / HE-AAC 64kbps Some videos may include EIA-608 closed captions. diff --git a/gamdl/__init__.py b/gamdl/__init__.py index 6c41e72..bfac68b 100644 --- a/gamdl/__init__.py +++ b/gamdl/__init__.py @@ -3,7 +3,7 @@ import traceback from .gamdl import Gamdl -__version__ = '1.6' +__version__ = '1.8' def main(): @@ -13,18 +13,18 @@ def main(): raise Exception('MP4Box is not on PATH') parser = argparse.ArgumentParser( description = 'Download Apple Music songs/music videos/albums/playlists', - formatter_class = argparse.ArgumentDefaultsHelpFormatter + formatter_class = argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( 'url', help = 'Apple Music song/music video/album/playlist URL(s)', - nargs = '*' + nargs = '*', ) parser.add_argument( '-u', '--urls-txt', help = 'Read URLs from a text file', - nargs = '?' + nargs = '?', ) parser.add_argument( '-w', @@ -36,67 +36,73 @@ def main(): '-f', '--final-path', default = 'Apple Music', - help = 'Final Path' + help = 'Final Path', ) parser.add_argument( '-t', '--temp-path', default = 'temp', - help = 'Temp Path' + help = 'Temp Path', ) parser.add_argument( '-c', '--cookies-location', default = 'cookies.txt', - help = 'Cookies location' + help = 'Cookies location', ) parser.add_argument( '-m', '--disable-music-video-skip', action = 'store_true', - help = 'Disable music video skip on playlists/albums' + help = 'Disable music video skip on playlists/albums', ) parser.add_argument( '-p', '--prefer-hevc', action = 'store_true', - help = 'Prefer HEVC over AVC' + help = 'Prefer HEVC over AVC', + ) + parser.add_argument( + '-a', + '--heaac', + action = 'store_true', + help = 'Download songs/music videos with HE-AAC instead of AAC', ) parser.add_argument( '-o', '--overwrite', action = 'store_true', - help = 'Overwrite existing files' + help = 'Overwrite existing files', ) parser.add_argument( '-n', '--no-lrc', action = 'store_true', - help = "Don't create .lrc file" + help = "Don't create .lrc file", ) parser.add_argument( '-s', '--skip-cleanup', action = 'store_true', - help = 'Skip cleanup' + help = 'Skip cleanup', ) parser.add_argument( '-e', '--print-exceptions', action = 'store_true', - help = 'Print execeptions' + help = 'Print execeptions', ) parser.add_argument( '-i', '--print-video-m3u8-url', action = 'store_true', - help = 'Print Video M3U8 URL' + help = 'Print Video M3U8 URL', ) parser.add_argument( '-v', '--version', action = 'version', - version = f'%(prog)s {__version__}' + version = f'%(prog)s {__version__}', ) args = parser.parse_args() if not args.url and not args.urls_txt: @@ -109,11 +115,12 @@ def main(): args.cookies_location, args.disable_music_video_skip, args.prefer_hevc, + args.heaac, args.temp_path, args.final_path, args.no_lrc, args.overwrite, - args.skip_cleanup + args.skip_cleanup, ) error_count = 0 download_queue = [] diff --git a/gamdl/gamdl.py b/gamdl/gamdl.py index 91f330d..60f00fa 100644 --- a/gamdl/gamdl.py +++ b/gamdl/gamdl.py @@ -17,9 +17,15 @@ class Gamdl: - def __init__(self, wvd_location, cookies_location, disable_music_video_skip, prefer_hevc, temp_path, final_path, no_lrc, overwrite, skip_cleanup): + def __init__(self, wvd_location, cookies_location, disable_music_video_skip, prefer_hevc, heaac, temp_path, final_path, no_lrc, overwrite, skip_cleanup): self.disable_music_video_skip = disable_music_video_skip self.prefer_hevc = prefer_hevc + if heaac: + self.song_audio_quality = '32:ctrp64' + self.music_video_audio_quality = 'audio-HE-stereo-64' + else: + self.song_audio_quality = '28:ctrp256' + self.music_video_audio_quality = 'audio-stereo-256' self.temp_path = Path(temp_path) self.final_path = Path(final_path) self.no_lrc = no_lrc @@ -31,7 +37,7 @@ def __init__(self, wvd_location, cookies_location, disable_music_video_skip, pre self.cdm = Cdm.from_device(Device.load(Path(wvd_location[0]))) self.cdm_session = self.cdm.open() cookies = MozillaCookieJar(Path(cookies_location)) - cookies.load(ignore_discard = True, ignore_expires = True) + cookies.load(ignore_discard=True, ignore_expires=True) self.session = requests.Session() self.session.cookies.update(cookies) self.session.headers.update({ @@ -47,7 +53,7 @@ def __init__(self, wvd_location, cookies_location, disable_music_video_skip, pre 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-site', - 'origin': 'https://beta.music.apple.com' + 'origin': 'https://beta.music.apple.com', }) web_page = self.session.get('https://beta.music.apple.com').text index_js_uri = re.search('(?<=index\.)(.*?)(?=\.js")', web_page).group(1) @@ -81,14 +87,14 @@ def get_webplayback(self, track_id): 'https://play.itunes.apple.com/WebObjects/MZPlay.woa/wa/webPlayback', json = { 'salableAdamId': track_id, - 'language': 'en-US' + 'language': 'en-US', } ).json()["songList"][0] return response def get_stream_url_song(self, webplayback): - return next(i for i in webplayback["assets"] if i["flavor"] == "28:ctrp256")['URL'] + return next(i for i in webplayback["assets"] if i["flavor"] == self.song_audio_quality)['URL'] def get_stream_url_music_video(self, webplayback): @@ -102,7 +108,7 @@ def get_stream_url_music_video(self, webplayback): stream_url_video = playlist['formats'][-1]['url'] else: stream_url_video = [i['url'] for i in playlist['formats'] if i['vcodec'] is not None and 'avc1' in i['vcodec']][-1] - stream_url_audio = next(i['url'] for i in playlist['formats'] if 'audio-stereo-256' in i['format_id']) + stream_url_audio = next(i['url'] for i in playlist['formats'] if self.music_video_audio_quality in i['format_id']) return stream_url_video, stream_url_audio @@ -138,7 +144,7 @@ def download(self, encrypted_location, stream_url): 'allow_unplayable_formats': True, 'fixup': 'never', 'overwrites': self.overwrite, - 'external_downloader': 'aria2c' + 'external_downloader': 'aria2c', }) as ydl: ydl.download(stream_url) @@ -152,7 +158,7 @@ def get_license_b64(self, challenge, track_uri, track_id): 'uri': track_uri, 'adamId': track_id, 'isLibrary': False, - 'user-initiated': True + 'user-initiated': True, } ).json()['license'] @@ -186,9 +192,9 @@ def decrypt(self, encrypted_location, decrypted_location, decryption_keys): encrypted_location, '--key', decryption_keys, - decrypted_location + decrypted_location, ], - check = True + check=True ) @@ -232,8 +238,8 @@ def get_cover(self, url): def get_tags_song(self, webplayback, unsynced_lyrics): - metadata = next(i for i in webplayback["assets"] if i["flavor"] == "28:ctrp256")['metadata'] - cover_url = next(i for i in webplayback["assets"] if i["flavor"] == "28:ctrp256")['artworkURL'] + metadata = next(i for i in webplayback["assets"] if i["flavor"] == self.song_audio_quality)['metadata'] + cover_url = next(i for i in webplayback["assets"] if i["flavor"] == self.song_audio_quality)['artworkURL'] tags = { '\xa9nam': [metadata['itemName']], '\xa9gen': [metadata['genre']], @@ -254,7 +260,7 @@ def get_tags_song(self, webplayback, unsynced_lyrics): 'disk': [(metadata['discNumber'], metadata['discCount'])], 'trkn': [(metadata['trackNumber'], metadata['trackCount'])], 'covr': [MP4Cover(self.get_cover(cover_url), MP4Cover.FORMAT_JPEG)], - 'stik': [1] + 'stik': [1], } if 'copyright' in metadata: tags['cprt'] = [metadata['copyright']] @@ -286,7 +292,7 @@ def get_tags_music_video(self, track_id): 'cnID': [metadata[0]["trackId"]], 'geID': [int(extra_metadata['genres'][0]['genreId'])], 'sfID': [int(self.storefront.split('-')[0])], - 'covr': [MP4Cover(self.get_cover(metadata[0]["artworkUrl30"].replace('30x30bb.jpg', '600x600bb.jpg')), MP4Cover.FORMAT_JPEG)] + 'covr': [MP4Cover(self.get_cover(metadata[0]["artworkUrl30"].replace('30x30bb.jpg', '600x600bb.jpg')), MP4Cover.FORMAT_JPEG)], } if 'copyright' in extra_metadata: tags['cprt'] = [extra_metadata['copyright']] @@ -361,9 +367,9 @@ def fixup_music_video(self, decrypted_location_audio, decrypted_location_video, '-itags', 'artist=placeholder', '-new', - fixed_location + fixed_location, ], - check = True + check=True ) @@ -377,9 +383,9 @@ def fixup_song(self, decrypted_location, fixed_location): '-itags', 'artist=placeholder', '-new', - fixed_location + fixed_location, ], - check = True + check=True )