-
-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pypi
- Loading branch information
Showing
25 changed files
with
340 additions
and
5,507 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,5 @@ | ||
/* | ||
__pycache__/ | ||
!gamdl.py | ||
!song_genres.py | ||
!music_video_genres.py | ||
!storefront_ids.py | ||
!pywidevine | ||
__pycache__ | ||
!gamdl | ||
!requirements.txt | ||
!.gitignore | ||
device_client_id_blob | ||
device_private_key | ||
!pyproject.toml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,54 +1,83 @@ | ||
# Glomatico's ✨ Apple Music ✨ Downloader | ||
A Python script to download Apple Music songs/music videos/albums/playlists. | ||
|
||
![Windows CMD usage example](https://i.imgur.com/byjqmGF.png) | ||
![Windows CMD usage example](https://i.imgur.com/6WeUCFh.png) | ||
|
||
This is a rework of https://github.com/loveyoursupport/AppleMusic-Downloader/tree/661a274d62586b521feec5a7de6bee0e230fdb7d. | ||
|
||
Some new features that I added: | ||
- MP4Box for muxing | ||
- Tags for music videos | ||
- Multiple URLs input | ||
- iTunes folder structure | ||
- Embedded lyrics and .lrc file | ||
- Auto set region | ||
- Playlist support | ||
- And much more! | ||
* MP4Box for muxing | ||
* Tags for music videos | ||
* Multiple URLs input | ||
* iTunes folder structure | ||
* Embedded lyrics and .lrc file | ||
* Auto set region | ||
* Playlist support | ||
* And much more! | ||
|
||
## Setup | ||
1. Install Python 3.8 or higher | ||
2. Install the required packages using pip: | ||
1. Install Python 3.7 or newer | ||
2. Install gamdl with pip | ||
``` | ||
pip install -r requirements.txt | ||
pip install gamdl | ||
``` | ||
3. Add MP4Box and mp4decrypt to your PATH | ||
* You can get them from here: | ||
* MP4Box: https://gpac.wp.imt.fr/downloads/ | ||
* mp4decrypt: https://www.bento4.com/downloads/ | ||
4. Export your Apple Music cookies as `cookies.txt` and put it in the same folder as the script | ||
4. Export your Apple Music cookies as `cookies.txt` and put it on the same folder that you will run the script | ||
* You can export your cookies by using this Google Chrome extension on Apple Music website: https://chrome.google.com/webstore/detail/open-cookiestxt/gdocmgbfkjnnpapoeobnolbbkoibbcif. Make sure to be logged in. | ||
5. Put your L3 Widevine Keys (`device_client_id_blob` and `device_private_key` files) on `./pywidevine/L3/cdm/devices/android_generic` folder | ||
* You can get your L3 Widevine Keys by using Dumper: https://github.com/Diazole/dumper | ||
5. Put your L3 Widevine Device Keys (`device_client_id_blob` and `device_private_key` files) on the same folder that you will run the script | ||
* You can get your L3 Widevine Device Keys by using Dumper: https://github.com/Diazole/dumper | ||
* The generated `private_key.pem` and `client_id.bin` files should be renamed to `device_private_key` and `device_client_id_blob` respectively. | ||
6. (optional) Add aria2c to your PATH for faster downloads | ||
* You can get it from here: https://aria2.github.io/. | ||
* You can get it from here: https://github.com/aria2/aria2/releases. | ||
## Usage | ||
``` | ||
python gamdl.py [OPTIONS] [URLS] | ||
``` | ||
Tracks are saved in `./Apple Music` by default, but the directory can be changed using `--final-path` argument. | ||
usage: gamdl [-h] [-u [URLS_TXT]] [-d DEVICE_PATH] [-f FINAL_PATH] [-t TEMP_PATH] [-c COOKIES_LOCATION] [-m] | ||
[-p] [-n] [-s] [-e] [-y] [-v] | ||
[url ...] | ||
|
||
Download Apple Music songs/music videos/albums/playlists | ||
|
||
Use `--help` argument to see all available options. | ||
positional arguments: | ||
url Apple Music song/music video/album/playlist URL(s) (default: None) | ||
|
||
options: | ||
-h, --help show this help message and exit | ||
-u [URLS_TXT], --urls-txt [URLS_TXT] | ||
Read URLs from a text file (default: None) | ||
-d DEVICE_PATH, --device-path DEVICE_PATH | ||
Widevine L3 device keys path (default: .) | ||
-f FINAL_PATH, --final-path FINAL_PATH | ||
Final Path (default: Apple Music) | ||
-t TEMP_PATH, --temp-path TEMP_PATH | ||
Temp Path (default: temp) | ||
-c COOKIES_LOCATION, --cookies-location COOKIES_LOCATION | ||
Cookies location (default: cookies.txt) | ||
-m, --disable-music-video-skip | ||
Disable music video skip on playlists/albums (default: False) | ||
-p, --prefer-hevc Prefer HEVC over AVC (default: False) | ||
-n, --no-lrc Don't create .lrc file (default: False) | ||
-s, --skip-cleanup Skip cleanup (default: False) | ||
-e, --print-exceptions | ||
Print execeptions (default: False) | ||
-y, --print-video-playlist | ||
Print Video M3U8 Playlist (default: False) | ||
-v, --version show program's version number and exit | ||
``` | ||
## Songs/Music Videos quality | ||
* Songs: | ||
* AAC 256kbps M4A | ||
* 256kbps AAC | ||
* Music Videos (varies depending on the video): | ||
* HEVC 4K 12~20mbps M4V / AAC 256kbps (achieved by using `--prefer-hevc` argument) | ||
* AVC 1080p 6.5~10mbps M4V / AAC 256kbps | ||
* AVC 720p 4mbps M4V / AAC 256kbps | ||
* AVC 480p 1.5mbps M4V / AAC 256kbps | ||
* AVC 360p 1mbps M4V / AAC 256kbps | ||
* 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 1.5mbps / AAC 256kbps | ||
* 360p AVC 1mbps / AAC 256kbps | ||
Some videos may include EIA-608 captions. | ||
Some videos may include EIA-608 closed captions. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import shutil | ||
import argparse | ||
import traceback | ||
from .gamdl import Gamdl | ||
|
||
__version__ = '1.0' | ||
|
||
|
||
def main(): | ||
if not shutil.which('mp4decrypt'): | ||
raise Exception('mp4decrypt is not on PATH') | ||
if not shutil.which('MP4Box'): | ||
raise Exception('MP4Box is not on PATH') | ||
parser = argparse.ArgumentParser( | ||
description = 'Download Apple Music songs/music videos/albums/playlists', | ||
formatter_class = argparse.ArgumentDefaultsHelpFormatter | ||
) | ||
parser.add_argument( | ||
'url', | ||
help = 'Apple Music song/music video/album/playlist URL(s)', | ||
nargs = '*' | ||
) | ||
parser.add_argument( | ||
'-u', | ||
'--urls-txt', | ||
help = 'Read URLs from a text file', | ||
nargs = '?' | ||
) | ||
parser.add_argument( | ||
'-d', | ||
'--device-path', | ||
default = '.', | ||
help = 'Widevine L3 device keys path' | ||
) | ||
parser.add_argument( | ||
'-f', | ||
'--final-path', | ||
default = 'Apple Music', | ||
help = 'Final Path' | ||
) | ||
parser.add_argument( | ||
'-t', | ||
'--temp-path', | ||
default = 'temp', | ||
help = 'Temp Path' | ||
) | ||
parser.add_argument( | ||
'-c', | ||
'--cookies-location', | ||
default = 'cookies.txt', | ||
help = 'Cookies location' | ||
) | ||
parser.add_argument( | ||
'-m', | ||
'--disable-music-video-skip', | ||
action = 'store_true', | ||
help = 'Disable music video skip on playlists/albums' | ||
) | ||
parser.add_argument( | ||
'-p', | ||
'--prefer-hevc', | ||
action = 'store_true', | ||
help = 'Prefer HEVC over AVC' | ||
) | ||
parser.add_argument( | ||
'-n', | ||
'--no-lrc', | ||
action = 'store_true', | ||
help = "Don't create .lrc file" | ||
) | ||
parser.add_argument( | ||
'-s', | ||
'--skip-cleanup', | ||
action = 'store_true', | ||
help = 'Skip cleanup' | ||
) | ||
parser.add_argument( | ||
'-e', | ||
'--print-exceptions', | ||
action = 'store_true', | ||
help = 'Print execeptions' | ||
) | ||
parser.add_argument( | ||
'-y', | ||
'--print-video-playlist', | ||
action = 'store_true', | ||
help = 'Print Video M3U8 Playlist' | ||
) | ||
parser.add_argument( | ||
'-v', | ||
'--version', | ||
action = 'version', | ||
version = f'%(prog)s {__version__}' | ||
) | ||
args = parser.parse_args() | ||
if not args.url and not args.urls_txt: | ||
parser.error('you must specify an url or a text file using -u/--urls-txt') | ||
if args.urls_txt: | ||
with open(args.urls_txt, 'r', encoding = 'utf8') as f: | ||
args.url = f.read().splitlines() | ||
dl = Gamdl( | ||
args.device_path, | ||
args.cookies_location, | ||
args.disable_music_video_skip, | ||
args.prefer_hevc, | ||
args.temp_path, | ||
args.final_path, | ||
args.no_lrc, | ||
args.skip_cleanup | ||
) | ||
error_count = 0 | ||
download_queue = [] | ||
for i, url in enumerate(args.url): | ||
try: | ||
download_queue.append(dl.get_download_queue(url.strip())) | ||
except KeyboardInterrupt: | ||
exit(1) | ||
except: | ||
error_count += 1 | ||
print(f'* Failed to check URL {i + 1}.') | ||
if args.print_exceptions: | ||
traceback.print_exc() | ||
for i, url in enumerate(download_queue): | ||
for j, track in enumerate(url): | ||
print(f'Downloading "{track["attributes"]["name"]}" (track {j + 1} from URL {i + 1})...') | ||
track_id = track['id'] | ||
try: | ||
webplayback = dl.get_webplayback(track_id) | ||
if track['type'] == 'music-videos': | ||
playlist = dl.get_playlist_music_video(webplayback) | ||
if args.print_video_playlist: | ||
print(playlist.dumps()) | ||
stream_url_audio = dl.get_stream_url_music_video_audio(playlist) | ||
decryption_keys_audio = dl.get_decryption_keys_music_video(stream_url_audio, track_id) | ||
encrypted_location_audio = dl.get_encrypted_location_audio(track_id) | ||
dl.download(encrypted_location_audio, stream_url_audio) | ||
decrypted_location_audio = dl.get_decrypted_location_audio(track_id) | ||
dl.decrypt(encrypted_location_audio, decrypted_location_audio, decryption_keys_audio) | ||
stream_url_video = dl.get_stream_url_music_video_video(playlist) | ||
decryption_keys_video = dl.get_decryption_keys_music_video(stream_url_video, track_id) | ||
encrypted_location_video = dl.get_encrypted_location_video(track_id) | ||
dl.download(encrypted_location_video, stream_url_video) | ||
decrypted_location_video = dl.get_decrypted_location_video(track_id) | ||
dl.decrypt(encrypted_location_video, decrypted_location_video, decryption_keys_video) | ||
tags = dl.get_tags_music_video(track['attributes']['url'].split('/')[-1]) | ||
fixed_location = dl.get_fixed_location(track_id, '.m4v') | ||
final_location = dl.get_final_location('.m4v', tags) | ||
dl.fixup_music_video(decrypted_location_audio, decrypted_location_video, fixed_location) | ||
dl.make_final(final_location, fixed_location, tags) | ||
else: | ||
stream_url = dl.get_stream_url_song(webplayback) | ||
decryption_keys = dl.get_decryption_keys_song(stream_url, track_id) | ||
encrypted_location = dl.get_encrypted_location_audio(track_id) | ||
dl.download(encrypted_location, stream_url) | ||
decrypted_location = dl.get_decrypted_location_audio(track_id) | ||
dl.decrypt(encrypted_location, decrypted_location, decryption_keys) | ||
unsynced_lyrics, synced_lyrics = dl.get_lyrics(track_id) | ||
tags = dl.get_tags_song(webplayback, unsynced_lyrics, track['attributes']['genreNames'][0]) | ||
fixed_location = dl.get_fixed_location(track_id, '.m4a') | ||
final_location = dl.get_final_location('.m4a', tags) | ||
dl.fixup_song(decrypted_location, fixed_location) | ||
dl.make_final(final_location, fixed_location, tags) | ||
dl.make_lrc(final_location, synced_lyrics) | ||
except KeyboardInterrupt: | ||
exit(1) | ||
except: | ||
error_count += 1 | ||
print(f'* Failed to download "{track["attributes"]["name"]}" (track {j + 1} from URL {i + 1}).') | ||
if args.print_exceptions: | ||
traceback.print_exc() | ||
dl.cleanup() | ||
print(f'Done ({error_count} error(s)).') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import gamdl | ||
|
||
if __name__ == "__main__": | ||
gamdl.main() |
Oops, something went wrong.