Compare commits

...

10 Commits

Author SHA1 Message Date
Rafael Moraes da6c84f3c0 Improve README formatting and config table 2025-08-13 21:15:18 -03:00
Rafael Moraes 636a227ba8 Bump version 2025-08-13 21:05:00 -03:00
Rafael Moraes 71643e04a3 Add checks for Apple Music subscription and restrictions 2025-08-13 21:04:47 -03:00
Rafael Moraes cd995ffcbd Refactor AppleMusicApi authentication and storefront logic 2025-08-13 21:04:39 -03:00
Rafael Moraes eab33bc02c Bump verision 2025-07-20 13:54:25 -03:00
Rafael Moraes ac0d9374fb Refactor get_remuxed_path for older Python compatibility 2025-07-17 16:27:08 -03:00
Rafael Moraes 7a72ecd301 Update project description 2025-07-09 13:37:55 -03:00
Rafael Moraes 2de68d5985 Update project description in pyproject.toml 2025-07-09 13:35:04 -03:00
Rafael Moraes 2e920b7306 Update README description for clarity 2025-07-09 13:31:51 -03:00
Rafael Moraes 8120e9e855 Remove unused import urlparse 2025-07-07 21:35:46 -03:00
7 changed files with 83 additions and 67 deletions
+50 -42
View File
@@ -1,8 +1,8 @@
# Glomaticos Apple Music Downloader
A Python CLI app for downloading Apple Music songs, music videos and post videos.
A command-line app for downloading Apple Music songs, music videos and post videos.
**Join our Discord Server:** https://discord.gg/aBjMEZ9tnq
**Join our Discord Server:** <https://discord.gg/aBjMEZ9tnq>
## Features
@@ -33,9 +33,11 @@ The following tools are optional but required for specific features. Add them to
## Installation
1. Install the package `gamdl` using pip
```bash
pip install gamdl
```
2. Set up the cookies file.
- Move the cookies file to the directory where youll run Gamdl and rename it to `cookies.txt`.
- Alternatively, specify the path to the cookies file using command-line arguments or the config file.
@@ -60,14 +62,19 @@ gamdl [OPTIONS] URLS...
### Examples
- Download a Song:
```bash
gamdl "https://music.apple.com/us/album/never-gonna-give-you-up-2022-remaster/1624945511?i=1624945512"
```
- Download an Album:
```bash
gamdl "https://music.apple.com/us/album/whenever-you-need-somebody-2022-remaster/1624945511"
```
- Download from an Artist:
```bash
gamdl "https://music.apple.com/us/artist/rick-astley/669771"
```
@@ -86,47 +93,48 @@ Gamdl can be configured by using the command-line arguments or the config file.
The config file is created automatically when you run Gamdl for the first time at `~/.gamdl/config.json` on Linux and `%USERPROFILE%\.gamdl\config.json` on Windows.
Config file values can be overridden using command-line arguments.
| Command-line argument / Config file key | Description | Default value |
| Command-line argument / Config file key | Description | Default value |
| --------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------- |
| `--disable-music-video-skip` / `disable_music_video_skip` | Don't skip downloading music videos in albums/playlists. | `false` |
| `--save-cover`, `-s` / `save_cover` | Save cover as a separate file. | `false` |
| `--overwrite` / `overwrite` | Overwrite existing files. | `false` |
| `--read-urls-as-txt`, `-r` / - | Interpret URLs as paths to text files containing URLs separated by newlines. | `false` |
| `--save-playlist` / `save_playlist` | Save a M3U8 playlist file when downloading a playlist. | `false` |
| `--synced-lyrics-only` / `synced_lyrics_only` | Download only the synced lyrics. | `false` |
| `--no-synced-lyrics` / `no_synced_lyrics` | Don't download the synced lyrics. | `false` |
| `--config-path` / - | Path to config file. | `<home>/.gamdl/config.json` |
| `--log-level` / `log_level` | Log level. | `INFO` |
| `--no-exceptions` / `no_exceptions` | Don't print exceptions. | `false` |
| `--cookies-path`, `-c` / `cookies_path` | Path to .txt cookies file. | `./cookies.txt` |
| `--language`, `-l` / `language` | Metadata language as an ISO-2A language code (don't always work for videos). | `en-US` |
| `--output-path`, `-o` / `output_path` | Path to output directory. | `./Apple Music` |
| `--temp-path` / `temp_path` | Path to temporary directory. | `./temp` |
| `--wvd-path` / `wvd_path` | Path to .wvd file. | `null` |
| `--nm3u8dlre-path` / `nm3u8dlre_path` | Path to N_m3u8DL-RE binary. | `N_m3u8DL-RE` |
| `--mp4decrypt-path` / `mp4decrypt_path` | Path to mp4decrypt binary. | `mp4decrypt` |
| `--ffmpeg-path` / `ffmpeg_path` | Path to FFmpeg binary. | `ffmpeg` |
| `--mp4box-path` / `mp4box_path` | Path to MP4Box binary. | `MP4Box` |
| `--download-mode` / `download_mode` | Download mode. | `ytdlp` |
| `--remux-mode` / `remux_mode` | Remux mode. | `ffmpeg` |
| `--cover-format` / `cover_format` | Cover format. | `jpg` |
| `--template-folder-album` / `template_folder_album` | Template folder for tracks that are part of an album. | `{album_artist}/{album}` |
| `--template-folder-compilation` / `template_folder_compilation` | Template folder for tracks that are part of a compilation album. | `Compilations/{album}` |
| `--template-file-single-disc` / `template_file_single_disc` | Template file for the tracks that are part of a single-disc album. | `{track:02d} {title}` |
| `--template-file-multi-disc` / `template_file_multi_disc` | Template file for the tracks that are part of a multi-disc album. | `{disc}-{track:02d} {title}` |
| `--template-folder-no-album` / `template_folder_no_album` | Template folder for the tracks that are not part of an album. | `{artist}/Unknown Album` |
| `--template-file-no-album` / `template_file_no_album` | Template file for the tracks that are not part of an album. | `{title}` |
| `--template-file-playlist` / `template_file_playlist` | Template file for the M3U8 playlist. | `Playlists/{playlist_title}` |
| `--template-date` / `template_date` | Date tag template. | `%Y-%m-%dT%H:%M:%SZ` |
| `--exclude-tags` / `exclude_tags` | Comma-separated tags to exclude. | `null` |
| `--cover-size` / `cover_size` | Cover size. | `1200` |
| `--truncate` / `truncate` | Maximum length of the file/folder names. | `null` |
| `--codec-song` / `codec_song` | Song codec. | `aac-legacy` |
| `--synced-lyrics-format` / `synced_lyrics_format` | Synced lyrics format. | `lrc` |
| `--codec-music-video` / `codec_music_video` | Music video codec. | `h264` |
| `--remux-format-music-video` / `remux_format_music_video` | Music video remux format. | `m4v` |
| `--quality-post` / `quality_post` | Post video quality. | `best` |
| `--no-config-file`, `-n` / - | Do not use a config file. | `false` |
| `--disable-music-video-skip` / `disable_music_video_skip` | Don't skip downloading music videos in albums/playlists. | `false` |
| `--save-cover`, `-s` / `save_cover` | Save cover as a separate file. | `false` |
| `--overwrite` / `overwrite` | Overwrite existing files. | `false` |
| `--read-urls-as-txt`, `-r` / - | Interpret URLs as paths to text files containing URLs separated by newlines. | `false` |
| `--save-playlist` / `save_playlist` | Save a M3U8 playlist file when downloading a playlist. | `false` |
| `--synced-lyrics-only` / `synced_lyrics_only` | Download only the synced lyrics. | `false` |
| `--no-synced-lyrics` / `no_synced_lyrics` | Don't download the synced lyrics. | `false` |
| `--config-path` / - | Path to config file. | `<home>/.gamdl/config.json` |
| `--log-level` / `log_level` | Log level. | `INFO` |
| `--no-exceptions` / `no_exceptions` | Don't print exceptions. | `false` |
| `--cookies-path`, `-c` / `cookies_path` | Path to .txt cookies file. | `./cookies.txt` |
| `--language`, `-l` / `language` | Metadata language as an ISO-2A language code (don't always work for videos). | `en-US` |
| `--output-path`, `-o` / `output_path` | Path to output directory. | `./Apple Music` |
| `--temp-path` / `temp_path` | Path to temporary directory. | `./temp` |
| `--wvd-path` / `wvd_path` | Path to .wvd file. | `null` |
| `--nm3u8dlre-path` / `nm3u8dlre_path` | Path to N_m3u8DL-RE binary. | `N_m3u8DL-RE` |
| `--mp4decrypt-path` / `mp4decrypt_path` | Path to mp4decrypt binary. | `mp4decrypt` |
| `--ffmpeg-path` / `ffmpeg_path` | Path to FFmpeg binary. | `ffmpeg` |
| `--mp4box-path` / `mp4box_path` | Path to MP4Box binary. | `MP4Box` |
| `--download-mode` / `download_mode` | Download mode. | `ytdlp` |
| `--remux-mode` / `remux_mode` | Remux mode. | `ffmpeg` |
| `--cover-format` / `cover_format` | Cover format. | `jpg` |
| `--template-folder-album` / `template_folder_album` | Template folder for tracks that are part of an album. | `{album_artist}/{album}` |
| `--template-folder-compilation` / `template_folder_compilation` | Template folder for tracks that are part of a compilation album. | `Compilations/{album}` |
| `--template-file-single-disc` / `template_file_single_disc` | Template file for the tracks that are part of a single-disc album. | `{track:02d} {title}` |
| `--template-file-multi-disc` / `template_file_multi_disc` | Template file for the tracks that are part of a multi-disc album. | `{disc}-{track:02d} {title}` |
| `--template-folder-no-album` / `template_folder_no_album` | Template folder for the tracks that are not part of an album. | `{artist}/Unknown Album` |
| `--template-file-no-album` / `template_file_no_album` | Template file for the tracks that are not part of an album. | `{title}` |
| `--template-file-playlist` / `template_file_playlist` | Template file for the M3U8 playlist. | `Playlists/{playlist_title}` |
| `--template-date` / `template_date` | Date tag template. | `%Y-%m-%dT%H:%M:%SZ` |
| `--exclude-tags` / `exclude_tags` | Comma-separated tags to exclude. | `null` |
| `--cover-size` / `cover_size` | Cover size. | `1200` |
| `--truncate` / `truncate` | Maximum length of the file/folder names. | `null` |
| `--codec-song` / `codec_song` | Song codec. | `aac-legacy` |
| `--synced-lyrics-format` / `synced_lyrics_format` | Synced lyrics format. | `lrc` |
| `--codec-music-video` / `codec_music_video` | Music video codec. | `h264` |
| `--remux-format-music-video` / `remux_format_music_video` | Music video remux format. | `m4v` |
| `--quality-post` / `quality_post` | Post video quality. | `best` |
| `--no-config-file`, `-n` / - | Do not use a config file. | `false` |
### Tags variables
+1 -1
View File
@@ -1 +1 @@
__version__ = "2.5"
__version__ = "2.5.2"
+18 -18
View File
@@ -59,9 +59,8 @@ class AppleMusicApi:
"Make sure you have exported the cookies from Apple Music webpage and are logged in "
"with an active subscription."
)
storefront = parse_cookie("itua")
return cls(
storefront=storefront,
storefront=None,
media_user_token=media_user_token,
language=language,
)
@@ -84,12 +83,6 @@ class AppleMusicApi:
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
}
)
if self.media_user_token:
self.session.cookies.update(
{
"media-user-token": self.media_user_token,
}
)
home_page = self.session.get(self.APPLE_MUSIC_HOMEPAGE_URL).text
index_js_uri = re.search(
r"/(assets/index-legacy-[^/]+\.js)",
@@ -101,8 +94,17 @@ class AppleMusicApi:
token = re.search('(?=eyJh)(.*?)(?=")', index_js_page).group(1)
self.session.headers.update({"authorization": f"Bearer {token}"})
self.session.params = {"l": self.language}
if not self.storefront:
self._fetch_storefront()
if self.media_user_token:
self.session.cookies.update(
{
"media-user-token": self.media_user_token,
}
)
self._set_account_info()
def _set_account_info(self):
self.account_info = self.get_account_info()
self.storefront = self.account_info["meta"]["subscription"]["storefront"]
def _check_amp_api_response(self, response: requests.Response):
try:
@@ -116,15 +118,13 @@ class AppleMusicApi:
):
raise_response_exception(response)
def _fetch_storefront(self):
self.storefront = self.get_user_storefront()["id"]
def get_user_storefront(
self,
) -> dict:
response = self.session.get(f"{self.AMP_API_URL}/v1/me/storefront")
def get_account_info(self, meta: str = "subscription") -> dict:
response = self.session.get(
f"{self.AMP_API_URL}/v1/me/account",
params={"meta": meta},
)
self._check_amp_api_response(response)
return response.json()["data"][0]
return response.json()
def get_artist(
self,
+11
View File
@@ -382,6 +382,17 @@ def main(
cookies_path,
language,
)
if not apple_music_api.account_info["meta"]["subscription"]["active"]:
logger.critical(
"No active Apple Music subscription found, you won't be able to download"
" anything"
)
return
if apple_music_api.account_info["data"][0]["attributes"].get("restrictions"):
logger.warning(
"Your account has content restrictions enabled, some content may not be"
" downloadable"
)
itunes_api = ItunesApi(
apple_music_api.storefront,
apple_music_api.language,
-1
View File
@@ -7,7 +7,6 @@ from pathlib import Path
import m3u8
from InquirerPy import inquirer
from InquirerPy.base.control import Choice
from urllib.parse import urlparse
from .constants import MUSIC_VIDEO_CODEC_MAP
from .downloader import Downloader
+2 -4
View File
@@ -309,10 +309,8 @@ class DownloaderSong:
return self.downloader.temp_path / f"{track_id}_decrypted.m4a"
def get_remuxed_path(self, track_id: str, file_format: MediaFileFormat) -> Path:
return (
self.downloader.temp_path
/ f"{track_id}_remuxed.{"m4a" if file_format == MediaFileFormat.M4A else "mp4"}"
)
file_suffix = "m4a" if file_format == MediaFileFormat.M4A else "mp4"
return self.downloader.temp_path / f"{track_id}_remuxed.{file_suffix}"
def fix_key_id(self, encrypted_path: Path):
count = 0
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "gamdl"
description = "A Python CLI app for downloading Apple Music songs, music videos and post videos."
description = "A command-line app for downloading Apple Music songs, music videos and post videos."
requires-python = ">=3.10"
authors = [{ name = "glomatico" }]
dependencies = [