mirror of
https://github.com/glomatico/gamdl.git
synced 2025-01-22 19:34:12 +00:00
.wvd file
This commit is contained in:
parent
92f4ed0a00
commit
a531bb4898
27
README.md
27
README.md
@ -1,7 +1,7 @@
|
|||||||
# Glomatico's ✨ Apple Music ✨ Downloader
|
# Glomatico's ✨ Apple Music ✨ Downloader
|
||||||
A Python script to download Apple Music songs/music videos/albums/playlists.
|
A Python script to download Apple Music songs/music videos/albums/playlists.
|
||||||
|
|
||||||
![Windows CMD usage example](https://i.imgur.com/6WeUCFh.png)
|
![Windows CMD usage example](https://i.imgur.com/18Azlg4.png)
|
||||||
|
|
||||||
This is a rework of https://github.com/loveyoursupport/AppleMusic-Downloader/tree/661a274d62586b521feec5a7de6bee0e230fdb7d.
|
This is a rework of https://github.com/loveyoursupport/AppleMusic-Downloader/tree/661a274d62586b521feec5a7de6bee0e230fdb7d.
|
||||||
|
|
||||||
@ -25,19 +25,26 @@ Some new features that I added:
|
|||||||
* You can get them from here:
|
* You can get them from here:
|
||||||
* MP4Box: https://gpac.wp.imt.fr/downloads/
|
* MP4Box: https://gpac.wp.imt.fr/downloads/
|
||||||
* mp4decrypt: https://www.bento4.com/downloads/
|
* mp4decrypt: https://www.bento4.com/downloads/
|
||||||
4. Export your Apple Music cookies as `cookies.txt` and put it on the same folder that you will run the script
|
4. Export your Apple Music cookies as `cookies.txt` to 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.
|
* 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 Device Keys (`device_client_id_blob` and `device_private_key` files) on the same folder that you will run the script
|
5. Put your Widevine Device file (.wvd) in 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
|
* You can use Dumper to dump your phone's L3 CDM: https://github.com/Diazole/dumper. Once you have the L3 CDM, you can use pywidevine to create the .wvd file from it.
|
||||||
* The generated `private_key.pem` and `client_id.bin` files should be renamed to `device_private_key` and `device_client_id_blob` respectively.
|
1. Install pywidevine with pip
|
||||||
|
```
|
||||||
|
pip install pywidevine pyyaml
|
||||||
|
```
|
||||||
|
2. Create the .wvd file
|
||||||
|
```
|
||||||
|
pywidevine create-device -t ANDROID -l 3 -k private_key.pem -c client_id.bin -o .
|
||||||
|
```
|
||||||
6. (optional) Add aria2c to your PATH for faster downloads
|
6. (optional) Add aria2c to your PATH for faster downloads
|
||||||
* You can get it from here: https://github.com/aria2/aria2/releases.
|
* You can get it from here: https://github.com/aria2/aria2/releases.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
```
|
```
|
||||||
usage: gamdl [-h] [-u [URLS_TXT]] [-d DEVICE_PATH] [-f FINAL_PATH] [-t TEMP_PATH] [-c COOKIES_LOCATION] [-m]
|
usage: gamdl [-h] [-u [URLS_TXT]] [-w WVD_LOCATION] [-f FINAL_PATH] [-t TEMP_PATH] [-c COOKIES_LOCATION] [-m] [-p]
|
||||||
[-p] [-n] [-s] [-e] [-y] [-v]
|
[-n] [-s] [-e] [-y] [-v]
|
||||||
[url ...]
|
[url ...]
|
||||||
|
|
||||||
Download Apple Music songs/music videos/albums/playlists
|
Download Apple Music songs/music videos/albums/playlists
|
||||||
|
|
||||||
@ -48,8 +55,8 @@ options:
|
|||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-u [URLS_TXT], --urls-txt [URLS_TXT]
|
-u [URLS_TXT], --urls-txt [URLS_TXT]
|
||||||
Read URLs from a text file (default: None)
|
Read URLs from a text file (default: None)
|
||||||
-d DEVICE_PATH, --device-path DEVICE_PATH
|
-w WVD_LOCATION, --wvd-location WVD_LOCATION
|
||||||
Widevine L3 device keys path (default: .)
|
.wvd file location (default: *.wvd)
|
||||||
-f FINAL_PATH, --final-path FINAL_PATH
|
-f FINAL_PATH, --final-path FINAL_PATH
|
||||||
Final Path (default: Apple Music)
|
Final Path (default: Apple Music)
|
||||||
-t TEMP_PATH, --temp-path TEMP_PATH
|
-t TEMP_PATH, --temp-path TEMP_PATH
|
||||||
|
@ -3,7 +3,7 @@ import argparse
|
|||||||
import traceback
|
import traceback
|
||||||
from .gamdl import Gamdl
|
from .gamdl import Gamdl
|
||||||
|
|
||||||
__version__ = '1.0'
|
__version__ = '1.1'
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -27,10 +27,10 @@ def main():
|
|||||||
nargs = '?'
|
nargs = '?'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-d',
|
'-w',
|
||||||
'--device-path',
|
'--wvd-location',
|
||||||
default = '.',
|
default = '*.wvd',
|
||||||
help = 'Widevine L3 device keys path'
|
help = '.wvd file location'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-f',
|
'-f',
|
||||||
@ -99,7 +99,7 @@ def main():
|
|||||||
with open(args.urls_txt, 'r', encoding = 'utf8') as f:
|
with open(args.urls_txt, 'r', encoding = 'utf8') as f:
|
||||||
args.url = f.read().splitlines()
|
args.url = f.read().splitlines()
|
||||||
dl = Gamdl(
|
dl = Gamdl(
|
||||||
args.device_path,
|
args.wvd_location,
|
||||||
args.cookies_location,
|
args.cookies_location,
|
||||||
args.disable_music_video_skip,
|
args.disable_music_video_skip,
|
||||||
args.prefer_hevc,
|
args.prefer_hevc,
|
||||||
@ -122,7 +122,7 @@ def main():
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
for i, url in enumerate(download_queue):
|
for i, url in enumerate(download_queue):
|
||||||
for j, track in enumerate(url):
|
for j, track in enumerate(url):
|
||||||
print(f'Downloading "{track["attributes"]["name"]}" (track {j + 1} from URL {i + 1})...')
|
print(f'Downloading "{track["attributes"]["name"]}" (track {j + 1}/{len(url)} from URL {i + 1}/{len(download_queue)})')
|
||||||
track_id = track['id']
|
track_id = track['id']
|
||||||
try:
|
try:
|
||||||
webplayback = dl.get_webplayback(track_id)
|
webplayback = dl.get_webplayback(track_id)
|
||||||
@ -165,8 +165,8 @@ def main():
|
|||||||
exit(1)
|
exit(1)
|
||||||
except:
|
except:
|
||||||
error_count += 1
|
error_count += 1
|
||||||
print(f'* Failed to download "{track["attributes"]["name"]}" (track {j + 1} from URL {i + 1}).')
|
print(f'Failed to download "{track["attributes"]["name"]}" (track {j + 1}/{len(url)} from URL {i + 1}/{len(download_queue)})')
|
||||||
if args.print_exceptions:
|
if args.print_exceptions:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
dl.cleanup()
|
dl.cleanup()
|
||||||
print(f'Done ({error_count} error(s)).')
|
print(f'Done ({error_count} error(s))')
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import glob
|
||||||
from http.cookiejar import MozillaCookieJar
|
from http.cookiejar import MozillaCookieJar
|
||||||
import re
|
import re
|
||||||
import base64
|
import base64
|
||||||
@ -19,23 +20,17 @@ from mutagen.mp4 import MP4, MP4Cover
|
|||||||
|
|
||||||
|
|
||||||
class Gamdl:
|
class Gamdl:
|
||||||
def __init__(self, device_path, cookies_location, disable_music_video_skip, prefer_hevc, temp_path, final_path, no_lrc, skip_cleanup):
|
def __init__(self, wvd_location, cookies_location, disable_music_video_skip, prefer_hevc, temp_path, final_path, no_lrc, skip_cleanup):
|
||||||
self.disable_music_video_skip = disable_music_video_skip
|
self.disable_music_video_skip = disable_music_video_skip
|
||||||
self.prefer_hevc = prefer_hevc
|
self.prefer_hevc = prefer_hevc
|
||||||
self.temp_path = Path(temp_path)
|
self.temp_path = Path(temp_path)
|
||||||
self.final_path = Path(final_path)
|
self.final_path = Path(final_path)
|
||||||
self.no_lrc = no_lrc
|
self.no_lrc = no_lrc
|
||||||
self.skip_cleanup = skip_cleanup
|
self.skip_cleanup = skip_cleanup
|
||||||
with open(Path(device_path) / 'device_client_id_blob', 'rb') as client_id, open(Path(device_path) / 'device_private_key', 'rb') as private_key:
|
wvd_location = glob.glob(wvd_location)
|
||||||
self.cdm = Cdm.from_device(
|
if not wvd_location:
|
||||||
Device(
|
raise Exception('.wvd file not found')
|
||||||
type_ = 'ANDROID',
|
self.cdm = Cdm.from_device(Device.load(Path(wvd_location[0])))
|
||||||
security_level = 3,
|
|
||||||
flags = None,
|
|
||||||
private_key = private_key.read(),
|
|
||||||
client_id = client_id.read()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.cdm_session = self.cdm.open()
|
self.cdm_session = self.cdm.open()
|
||||||
cookies = MozillaCookieJar(Path(cookies_location))
|
cookies = MozillaCookieJar(Path(cookies_location))
|
||||||
cookies.load(ignore_discard = True, ignore_expires = True)
|
cookies.load(ignore_discard = True, ignore_expires = True)
|
||||||
@ -393,12 +388,11 @@ class Gamdl:
|
|||||||
def make_final(self, final_location, fixed_location, tags):
|
def make_final(self, final_location, fixed_location, tags):
|
||||||
final_location.parent.mkdir(parents = True, exist_ok = True)
|
final_location.parent.mkdir(parents = True, exist_ok = True)
|
||||||
shutil.copy(fixed_location, final_location)
|
shutil.copy(fixed_location, final_location)
|
||||||
file = MP4(final_location).tags
|
file = MP4(final_location)
|
||||||
file.update(tags)
|
file.update(tags)
|
||||||
file.save(final_location)
|
file.save()
|
||||||
|
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
if self.temp_path.exists() and not self.skip_cleanup:
|
if self.temp_path.exists() and not self.skip_cleanup:
|
||||||
shutil.rmtree(self.temp_path)
|
shutil.rmtree(self.temp_path)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user