mirror of
https://github.com/zhaarey/AppleMusicDecrypt.git
synced 2025-10-23 15:11:06 +00:00
fix: format code
This commit is contained in:
37
README.md
37
README.md
@@ -1,10 +1,13 @@
|
|||||||
# AppleMusicDecrypt
|
# AppleMusicDecrypt
|
||||||
|
|
||||||
Apple Music decryption tool, based on [zhaarey/apple-music-alac-atmos-downloader](https://github.com/zhaarey/apple-music-alac-atmos-downloader)
|
Apple Music decryption tool, based
|
||||||
|
on [zhaarey/apple-music-alac-atmos-downloader](https://github.com/zhaarey/apple-music-alac-atmos-downloader)
|
||||||
|
|
||||||
**WARNING: This project is currently in an extremely early stage, and there are still a large number of undiscovered bugs and unfinished features. USE IT WITH CAUTION.**
|
**WARNING: This project is currently in an extremely early stage, and there are still a large number of undiscovered
|
||||||
|
bugs and unfinished features. USE IT WITH CAUTION.**
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Download song/album with default codec (alac)
|
# Download song/album with default codec (alac)
|
||||||
download https://music.apple.com/jp/album/nameless-name-single/1688539265
|
download https://music.apple.com/jp/album/nameless-name-single/1688539265
|
||||||
@@ -22,22 +25,33 @@ download https://music.apple.com/jp/song/caribbean-blue/339592231 -c aac
|
|||||||
- `aac-downmix (audio-stereo-downmix)`
|
- `aac-downmix (audio-stereo-downmix)`
|
||||||
|
|
||||||
# Support Link
|
# Support Link
|
||||||
- Apple Music Song Share Link (https://music.apple.com/jp/album/%E5%90%8D%E3%82%82%E3%81%AA%E3%81%8D%E4%BD%95%E3%82%82%E3%81%8B%E3%82%82/1688539265?i=1688539274)
|
|
||||||
|
- Apple Music Song Share
|
||||||
|
Link (https://music.apple.com/jp/album/%E5%90%8D%E3%82%82%E3%81%AA%E3%81%8D%E4%BD%95%E3%82%82%E3%81%8B%E3%82%82/1688539265?i=1688539274)
|
||||||
- Apple Music Album Share Link (https://music.apple.com/jp/album/nameless-name-single/1688539265)
|
- Apple Music Album Share Link (https://music.apple.com/jp/album/nameless-name-single/1688539265)
|
||||||
- Apple Music Song Link (https://music.apple.com/jp/song/caribbean-blue/339592231)
|
- Apple Music Song Link (https://music.apple.com/jp/song/caribbean-blue/339592231)
|
||||||
|
|
||||||
# Deploy
|
# Deploy
|
||||||
|
|
||||||
## Prepare Local Environment
|
## Prepare Local Environment
|
||||||
|
|
||||||
1. Install [GPAC](https://gpac.io/downloads/gpac-nightly-builds/)
|
1. Install [GPAC](https://gpac.io/downloads/gpac-nightly-builds/)
|
||||||
2. Download [Bento4 MP4Tools](https://www.bento4.com/downloads/) and add the executable files to the environment variables
|
2. Download [Bento4 MP4Tools](https://www.bento4.com/downloads/) and add the executable files to the environment
|
||||||
|
variables
|
||||||
3. Run `gpac -version`, `mp4box -version`, `mp4extract`, `mp4edit` and make sure all the commands run fine
|
3. Run `gpac -version`, `mp4box -version`, `mp4extract`, `mp4edit` and make sure all the commands run fine
|
||||||
|
|
||||||
## Prepare Android Environment
|
## Prepare Android Environment
|
||||||
|
|
||||||
### For WSA (Recommend):
|
### For WSA (Recommend):
|
||||||
|
|
||||||
1. Install Apple Music (3.6.0-beta) and login
|
1. Install Apple Music (3.6.0-beta) and login
|
||||||
2. Play a song in Apple Music
|
2. Play a song in Apple Music
|
||||||
3. Install WSA from [LSPosed/MagiskOnWSALocal](https://github.com/LSPosed/MagiskOnWSALocal). Choose the version that includes Magisk but not GApps
|
3. Install WSA from [LSPosed/MagiskOnWSALocal](https://github.com/LSPosed/MagiskOnWSALocal). Choose the version that
|
||||||
4. Install following Magisk modules: [magisk-frida](https://github.com/ViRb3/magisk-frida), [sqlite3-magisk-module](https://github.com/rojenzaman/sqlite3-magisk-module)
|
includes Magisk but not GApps
|
||||||
|
4. Install following Magisk
|
||||||
|
modules: [magisk-frida](https://github.com/ViRb3/magisk-frida), [sqlite3-magisk-module](https://github.com/rojenzaman/sqlite3-magisk-module)
|
||||||
5. Edit `config.toml`
|
5. Edit `config.toml`
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[devices]]
|
[[devices]]
|
||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
@@ -46,11 +60,14 @@ agentPort = 10020
|
|||||||
fridaPath = "/system/bin/frida-server"
|
fridaPath = "/system/bin/frida-server"
|
||||||
suMethod = "su -c"
|
suMethod = "su -c"
|
||||||
```
|
```
|
||||||
|
|
||||||
### For Google Android Emulator
|
### For Google Android Emulator
|
||||||
|
|
||||||
1. Install Apple Music (3.6.0-beta) and login
|
1. Install Apple Music (3.6.0-beta) and login
|
||||||
2. Play a song in Apple Music
|
2. Play a song in Apple Music
|
||||||
3. Manually install Frida and start frida-server in background
|
3. Manually install Frida and start frida-server in background
|
||||||
4. Edit `config.toml`
|
4. Edit `config.toml`
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[devices]]
|
[[devices]]
|
||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
@@ -59,10 +76,16 @@ agentPort = 10020
|
|||||||
fridaPath = "/data/local/tmp/frida-server-16.2.1-android-x86_64" # Replace this value to your frida-server path!
|
fridaPath = "/data/local/tmp/frida-server-16.2.1-android-x86_64" # Replace this value to your frida-server path!
|
||||||
suMethod = "su 0"
|
suMethod = "su 0"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run Script
|
## Run Script
|
||||||
|
|
||||||
### Use pre-built script (For Windows)
|
### Use pre-built script (For Windows)
|
||||||
Download latest build from [Actions](https://github.com/WorldObservationLog/AppleMusicDecrypt/actions) (need login your GitHub account). Unzip it, and run `main.exe`
|
|
||||||
|
Download latest build from [Actions](https://github.com/WorldObservationLog/AppleMusicDecrypt/actions) (need login your
|
||||||
|
GitHub account). Unzip it, and run `main.exe`
|
||||||
|
|
||||||
### Manually Run
|
### Manually Run
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/WorldObservationLog/AppleMusicDecrypt.git
|
git clone https://github.com/WorldObservationLog/AppleMusicDecrypt.git
|
||||||
cd AppleMusicDecrypt
|
cd AppleMusicDecrypt
|
||||||
|
|||||||
1
main.py
1
main.py
@@ -2,7 +2,6 @@ import asyncio
|
|||||||
|
|
||||||
from src.cmd import NewInteractiveShell
|
from src.cmd import NewInteractiveShell
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
cmd = NewInteractiveShell(loop)
|
cmd = NewInteractiveShell(loop)
|
||||||
|
|||||||
@@ -100,7 +100,8 @@ class Device:
|
|||||||
def _get_dsid(self) -> str:
|
def _get_dsid(self) -> str:
|
||||||
logger.debug("getting dsid")
|
logger.debug("getting dsid")
|
||||||
dsid = self._execute_command(
|
dsid = self._execute_command(
|
||||||
"sqlite3 /data/data/com.apple.android.music/files/mpl_db/cookies.sqlitedb \"select value from cookies where name='X-Dsid';\"", True)
|
"sqlite3 /data/data/com.apple.android.music/files/mpl_db/cookies.sqlitedb \"select value from cookies where name='X-Dsid';\"",
|
||||||
|
True)
|
||||||
if not dsid:
|
if not dsid:
|
||||||
raise FailedGetAuthParamException
|
raise FailedGetAuthParamException
|
||||||
return dsid.strip()
|
return dsid.strip()
|
||||||
@@ -108,7 +109,8 @@ class Device:
|
|||||||
def _get_account_token(self, dsid: str) -> str:
|
def _get_account_token(self, dsid: str) -> str:
|
||||||
logger.debug("getting account token")
|
logger.debug("getting account token")
|
||||||
account_token = self._execute_command(
|
account_token = self._execute_command(
|
||||||
f"sqlite3 /data/data/com.apple.android.music/files/mpl_db/cookies.sqlitedb \"select value from cookies where name='mz_at_ssl-{dsid}';\"", True)
|
f"sqlite3 /data/data/com.apple.android.music/files/mpl_db/cookies.sqlitedb \"select value from cookies where name='mz_at_ssl-{dsid}';\"",
|
||||||
|
True)
|
||||||
if not account_token:
|
if not account_token:
|
||||||
raise FailedGetAuthParamException
|
raise FailedGetAuthParamException
|
||||||
return account_token.strip()
|
return account_token.strip()
|
||||||
@@ -124,7 +126,8 @@ class Device:
|
|||||||
def _get_storefront(self) -> str | None:
|
def _get_storefront(self) -> str | None:
|
||||||
logger.debug("getting storefront")
|
logger.debug("getting storefront")
|
||||||
storefront_id = self._execute_command(
|
storefront_id = self._execute_command(
|
||||||
"sqlite3 /data/data/com.apple.android.music/files/mpl_db/accounts.sqlitedb \"select storeFront from account;\"", True)
|
"sqlite3 /data/data/com.apple.android.music/files/mpl_db/accounts.sqlitedb \"select storeFront from account;\"",
|
||||||
|
True)
|
||||||
if not storefront_id:
|
if not storefront_id:
|
||||||
raise FailedGetAuthParamException
|
raise FailedGetAuthParamException
|
||||||
with open("assets/storefront_ids.json") as f:
|
with open("assets/storefront_ids.json") as f:
|
||||||
|
|||||||
21
src/api.py
21
src/api.py
@@ -5,9 +5,8 @@ from ssl import SSLError
|
|||||||
import httpcore
|
import httpcore
|
||||||
import httpx
|
import httpx
|
||||||
import regex
|
import regex
|
||||||
|
|
||||||
from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log
|
||||||
|
|
||||||
from src.models import *
|
from src.models import *
|
||||||
|
|
||||||
@@ -18,7 +17,8 @@ user_agent_itunes = "iTunes/12.11.3 (Windows; Microsoft Windows 10 x64 Professio
|
|||||||
user_agent_app = "Music/5.7 Android/10 model/Pixel6GR1YH build/1234 (dt:66)"
|
user_agent_app = "Music/5.7 Android/10 model/Pixel6GR1YH build/1234 (dt:66)"
|
||||||
|
|
||||||
|
|
||||||
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)), stop=stop_after_attempt(5),
|
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)),
|
||||||
|
stop=stop_after_attempt(5),
|
||||||
before_sleep=before_sleep_log(logger, logging.WARN))
|
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||||
async def get_token():
|
async def get_token():
|
||||||
req = await client.get("https://beta.music.apple.com")
|
req = await client.get("https://beta.music.apple.com")
|
||||||
@@ -28,14 +28,16 @@ async def get_token():
|
|||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)), stop=stop_after_attempt(5),
|
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)),
|
||||||
|
stop=stop_after_attempt(5),
|
||||||
before_sleep=before_sleep_log(logger, logging.WARN))
|
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||||
async def download_song(url: str) -> bytes:
|
async def download_song(url: str) -> bytes:
|
||||||
async with lock:
|
async with lock:
|
||||||
return (await client.get(url)).content
|
return (await client.get(url)).content
|
||||||
|
|
||||||
|
|
||||||
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)), stop=stop_after_attempt(5),
|
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)),
|
||||||
|
stop=stop_after_attempt(5),
|
||||||
before_sleep=before_sleep_log(logger, logging.WARN))
|
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||||
async def get_meta(album_id: str, token: str, storefront: str):
|
async def get_meta(album_id: str, token: str, storefront: str):
|
||||||
if "pl." in album_id:
|
if "pl." in album_id:
|
||||||
@@ -69,7 +71,8 @@ async def get_meta(album_id: str, token: str, storefront: str):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)), stop=stop_after_attempt(5),
|
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)),
|
||||||
|
stop=stop_after_attempt(5),
|
||||||
before_sleep=before_sleep_log(logger, logging.WARN))
|
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||||
async def get_cover(url: str, cover_format: str):
|
async def get_cover(url: str, cover_format: str):
|
||||||
formatted_url = regex.sub('bb.jpg', f'bb.{cover_format}', url)
|
formatted_url = regex.sub('bb.jpg', f'bb.{cover_format}', url)
|
||||||
@@ -78,7 +81,8 @@ async def get_cover(url: str, cover_format: str):
|
|||||||
return req.content
|
return req.content
|
||||||
|
|
||||||
|
|
||||||
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)), stop=stop_after_attempt(5),
|
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)),
|
||||||
|
stop=stop_after_attempt(5),
|
||||||
before_sleep=before_sleep_log(logger, logging.WARN))
|
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||||
async def get_info_from_adam(adam_id: str, token: str, storefront: str):
|
async def get_info_from_adam(adam_id: str, token: str, storefront: str):
|
||||||
req = await client.get(f"https://amp-api.music.apple.com/v1/catalog/{storefront}/songs/{adam_id}",
|
req = await client.get(f"https://amp-api.music.apple.com/v1/catalog/{storefront}/songs/{adam_id}",
|
||||||
@@ -92,7 +96,8 @@ async def get_info_from_adam(adam_id: str, token: str, storefront: str):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)), stop=stop_after_attempt(5),
|
@retry(retry=retry_if_exception_type((httpx.TimeoutException, httpcore.ConnectError, SSLError, FileNotFoundError)),
|
||||||
|
stop=stop_after_attempt(5),
|
||||||
before_sleep=before_sleep_log(logger, logging.WARN))
|
before_sleep=before_sleep_log(logger, logging.WARN))
|
||||||
async def get_song_lyrics(song_id: str, storefront: str, token: str, dsid: str, account_token: str) -> str:
|
async def get_song_lyrics(song_id: str, storefront: str, token: str, dsid: str, account_token: str) -> str:
|
||||||
req = await client.get(f"https://amp-api.music.apple.com/v1/catalog/{storefront}/songs/{song_id}/lyrics",
|
req = await client.get(f"https://amp-api.music.apple.com/v1/catalog/{storefront}/songs/{song_id}/lyrics",
|
||||||
|
|||||||
11
src/cmd.py
11
src/cmd.py
@@ -35,7 +35,8 @@ class NewInteractiveShell:
|
|||||||
download_parser = subparser.add_parser("download")
|
download_parser = subparser.add_parser("download")
|
||||||
download_parser.add_argument("url", type=str)
|
download_parser.add_argument("url", type=str)
|
||||||
download_parser.add_argument("-c", "--codec",
|
download_parser.add_argument("-c", "--codec",
|
||||||
choices=["alac", "ec3", "aac", "aac-binaural", "aac-downmix", "ac3"], default="alac")
|
choices=["alac", "ec3", "aac", "aac-binaural", "aac-downmix", "ac3"],
|
||||||
|
default="alac")
|
||||||
download_parser.add_argument("-f", "--force", type=bool, default=False)
|
download_parser.add_argument("-f", "--force", type=bool, default=False)
|
||||||
subparser.add_parser("exit")
|
subparser.add_parser("exit")
|
||||||
|
|
||||||
@@ -79,12 +80,14 @@ class NewInteractiveShell:
|
|||||||
available_device: Device = random.choice(devices)
|
available_device: Device = random.choice(devices)
|
||||||
else:
|
else:
|
||||||
available_device: Device = random.choice(available_devices)
|
available_device: Device = random.choice(available_devices)
|
||||||
global_auth_param = GlobalAuthParams.from_auth_params_and_token(available_device.get_auth_params(), self.anonymous_access_token)
|
global_auth_param = GlobalAuthParams.from_auth_params_and_token(available_device.get_auth_params(),
|
||||||
|
self.anonymous_access_token)
|
||||||
match url.type:
|
match url.type:
|
||||||
case URLType.Song:
|
case URLType.Song:
|
||||||
self.loop.create_task(rip_song(url, global_auth_param, codec, self.config, available_device, force_download))
|
task = self.loop.create_task(
|
||||||
|
rip_song(url, global_auth_param, codec, self.config, available_device, force_download))
|
||||||
case URLType.Album:
|
case URLType.Album:
|
||||||
self.loop.create_task(rip_album(url, global_auth_param, codec, self.config, available_device))
|
task = self.loop.create_task(rip_album(url, global_auth_param, codec, self.config, available_device))
|
||||||
|
|
||||||
async def handle_command(self):
|
async def handle_command(self):
|
||||||
session = PromptSession("> ")
|
session = PromptSession("> ")
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from prompt_toolkit.shortcuts import ProgressBar
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from tenacity import retry, retry_if_exception_type, stop_after_attempt, before_sleep_log
|
|
||||||
|
|
||||||
from src.adb import Device
|
from src.adb import Device
|
||||||
from src.exceptions import DecryptException
|
from src.exceptions import DecryptException
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from src.models.album_meta import AlbumMeta
|
from src.models.album_meta import AlbumMeta
|
||||||
from src.models.playlist_meta import PlaylistMeta
|
from src.models.playlist_meta import PlaylistMeta
|
||||||
from src.models.tracks_meta import TracksMeta
|
|
||||||
from src.models.song_data import SongData
|
from src.models.song_data import SongData
|
||||||
from src.models.song_lyrics import SongLyrics
|
from src.models.song_lyrics import SongLyrics
|
||||||
|
from src.models.tracks_meta import TracksMeta
|
||||||
|
|||||||
12
src/mp4.py
12
src/mp4.py
@@ -12,7 +12,14 @@ from bs4 import BeautifulSoup
|
|||||||
from src.exceptions import CodecNotFoundException
|
from src.exceptions import CodecNotFoundException
|
||||||
from src.metadata import SongMetadata
|
from src.metadata import SongMetadata
|
||||||
from src.types import *
|
from src.types import *
|
||||||
from src.utils import find_best_codec
|
from src.utils import find_best_codec, get_codec_from_codec_id
|
||||||
|
|
||||||
|
|
||||||
|
async def get_available_codecs(m3u8_url: str) -> Tuple[list[str], list[str]]:
|
||||||
|
parsed_m3u8 = m3u8.load(m3u8_url)
|
||||||
|
codec_ids = [playlist.stream_info.audio for playlist in parsed_m3u8.playlists]
|
||||||
|
codecs = [get_codec_from_codec_id(codec_id) for codec_id in codec_ids]
|
||||||
|
return codecs, codec_ids
|
||||||
|
|
||||||
|
|
||||||
async def extract_media(m3u8_url: str, codec: str) -> Tuple[str, list[str], str]:
|
async def extract_media(m3u8_url: str, codec: str) -> Tuple[str, list[str], str]:
|
||||||
@@ -161,7 +168,8 @@ def write_metadata(song: bytes, metadata: SongMetadata, embed_metadata: list[str
|
|||||||
with open(cover_path.absolute(), "wb") as f:
|
with open(cover_path.absolute(), "wb") as f:
|
||||||
f.write(metadata.cover)
|
f.write(metadata.cover)
|
||||||
subprocess.run(["mp4box", "-time", "0", "-mtime", "0", "-keep-utc", "-name", f"1={metadata.title}", "-itags",
|
subprocess.run(["mp4box", "-time", "0", "-mtime", "0", "-keep-utc", "-name", f"1={metadata.title}", "-itags",
|
||||||
":".join(["tool=\"\"", f"cover={absolute_cover_path}", metadata.to_itags_params(embed_metadata, cover_format)]),
|
":".join(["tool=\"\"", f"cover={absolute_cover_path}",
|
||||||
|
metadata.to_itags_params(embed_metadata, cover_format)]),
|
||||||
song_name.absolute()], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
song_name.absolute()], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
with open(song_name.absolute(), "rb") as f:
|
with open(song_name.absolute(), "rb") as f:
|
||||||
embed_song = f.read()
|
embed_song = f.read()
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ from src.mp4 import extract_media, extract_song, encapsulate, write_metadata
|
|||||||
from src.save import save
|
from src.save import save
|
||||||
from src.types import GlobalAuthParams, Codec
|
from src.types import GlobalAuthParams, Codec
|
||||||
from src.url import Song, Album, URLType
|
from src.url import Song, Album, URLType
|
||||||
from src.utils import check_song_exists
|
|
||||||
|
|
||||||
|
|
||||||
@logger.catch
|
|
||||||
async def rip_song(song: Song, auth_params: GlobalAuthParams, codec: str, config: Config, device: Device,
|
async def rip_song(song: Song, auth_params: GlobalAuthParams, codec: str, config: Config, device: Device,
|
||||||
force_save: bool = False):
|
force_save: bool = False):
|
||||||
logger.debug(f"Task of song id {song.id} was created")
|
logger.debug(f"Task of song id {song.id} was created")
|
||||||
@@ -52,7 +50,8 @@ async def rip_album(album: Album, auth_params: GlobalAuthParams, codec: str, con
|
|||||||
for track in album_info.data[0].relationships.tracks.data:
|
for track in album_info.data[0].relationships.tracks.data:
|
||||||
song = Song(id=track.id, storefront=album.storefront, url="", type=URLType.Song)
|
song = Song(id=track.id, storefront=album.storefront, url="", type=URLType.Song)
|
||||||
tg.create_task(rip_song(song, auth_params, codec, config, device, force_save))
|
tg.create_task(rip_song(song, auth_params, codec, config, device, force_save))
|
||||||
logger.info(f"Album: {album_info.data[0].attributes.artistName} - {album_info.data[0].attributes.name} finished ripping")
|
logger.info(
|
||||||
|
f"Album: {album_info.data[0].attributes.artistName} - {album_info.data[0].attributes.name} finished ripping")
|
||||||
|
|
||||||
|
|
||||||
async def rip_playlist():
|
async def rip_playlist():
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from bs4 import BeautifulSoup
|
|||||||
|
|
||||||
from src.config import Download
|
from src.config import Download
|
||||||
from src.exceptions import NotTimeSyncedLyricsException
|
from src.exceptions import NotTimeSyncedLyricsException
|
||||||
|
|
||||||
from src.types import *
|
from src.types import *
|
||||||
|
|
||||||
|
|
||||||
@@ -117,3 +116,11 @@ def check_song_exists(metadata, config: Download, codec: str):
|
|||||||
|
|
||||||
def get_valid_filename(filename: str):
|
def get_valid_filename(filename: str):
|
||||||
return "".join(i for i in filename if i not in r"\/:*?<>|")
|
return "".join(i for i in filename if i not in r"\/:*?<>|")
|
||||||
|
|
||||||
|
|
||||||
|
def get_codec_from_codec_id(codec_id: str) -> str:
|
||||||
|
codecs = [Codec.AC3, Codec.EC3, Codec.AAC, Codec.ALAC, Codec.AAC_BINAURAL, Codec.AAC_DOWNMIX]
|
||||||
|
for codec in codecs:
|
||||||
|
if regex.match(CodecRegex.get_pattern_by_codec(codec), codec_id):
|
||||||
|
return codec
|
||||||
|
return ""
|
||||||
|
|||||||
Reference in New Issue
Block a user