Skip to content

[WIP] Apple Music lossless downloader#4

Open
julientregoat wants to merge 17 commits intomainfrom
stream-extractor
Open

[WIP] Apple Music lossless downloader#4
julientregoat wants to merge 17 commits intomainfrom
stream-extractor

Conversation

@julientregoat
Copy link
Owner

@julientregoat julientregoat commented Jan 25, 2026

TODO before merge:

  • Refactor setup and wrapper scripts for reusable separate flows for remote wrapper. The current flow works for local; it needs to be split for building and launching the wrapper remotely (x86_64), then building/running the downloader locally. Clarify whether the remote wrapper must be started on the remote host or can be started from a local device. Keep the local flow for others who use this.
  • Test the downloader with multiple tracks on a remote x86_64 instance of the wrapper (local Apple Silicon works with single-track downloads but crashes with albums/too many tracks).
  • Test downloader with multiple tracks on a remote non-Apple Silicon arm64 instance of the wrapper to see if Apple Silicon or local is the arm64 issue.
  • determine if wrapper auth is required on ALL architectures (wrapper readme indicates it may be) and update docs if needed
  • Identify code and README in this PR that can be refactored, simplified, reorganized; lots of fixes were made in getting this to work, so old fixes may be able to be removed, or verbose scripts may be able to be simplified. Also clarify multi-track download support with remote x86 and arm64 wrappers once the above test TODO items are completed.
  • update PR description

Summary

This PR adds Apple Music lossless downloader support to the scripts repo. It uses Docker for both the wrapper (decryption server) and the downloader (zhaarey/apple-music-downloader).

What's included

  • setup.sh – Installs MP4Box, clones the wrapper repo, downloads the latest prebuilt binary (arm64 or x86_64), builds the wrapper Docker image, and pulls the downloader image.
  • wrapper.sh – Manages the wrapper container (start/stop/restart/status, login). Uses a short-lived login container for 2FA, then a long-lived server container with -H only so restarts use the cached session.
  • download_apple_music.sh – Downloads ALAC from Apple Music URLs. Checks wrapper reachability, optionally auto-starts wrapper, runs the downloader in Docker.
  • wrapper_utils.sh – Shared architecture detection (arm64 vs x86_64).
  • check_format.sh – Checks ALAC availability for URLs without downloading.
  • README – Quick start, configuration, troubleshooting (decryption crashes on Apple Silicon, device limit), and a new Remote wrapper (x86_64) section for running the wrapper remotely when the local arm64 wrapper fails during decrypt.

Platform notes

  • Apple Silicon (arm64): Credentials required; 2FA on first login. Native arm64 wrapper; downloader runs x86_64 via Rosetta. Local Apple Silicon works with single-track (song) downloads but the wrapper crashes with albums or too many tracks during decryption ("connection reset by peer"); see README for workarounds (song URLs, remote wrapper) and the remote-wrapper option.
  • x86_64: Standard local wrapper + downloader flow.

- Add apple-music-downloader directory with scripts and documentation
- download_apple_music.sh: Download ALAC lossless audio from Apple Music URLs
  - Supports --auto-wrapper flag for automatic wrapper management
  - Uses Docker for apple-music-downloader component
  - Requires wrapper (decryption server) to be running
- wrapper.sh: Manage wrapper service (start/stop/restart/status)
- setup.sh: Verify dependencies and pull Docker images
- README.md: Comprehensive documentation with references to upstream projects
- .env.template: Configuration template for credentials and settings
- Update main README.md to reference new downloader
- Update .gitignore to exclude .env and wrapper runtime files

Add ALAC availability check and format documentation improvements

- Add check_format.sh script to check available formats before downloading
- Update download_apple_music.sh to check for ALAC availability and error if not available
- Clarify Dolby Atmos is lossy (EC3 codec) on Apple Music, not lossless TrueHD
- Add detailed format explanations (aac-lc, MV, Dolby Atmos) in README
- Update .env.template with clearer explanations of optional credentials
- Prevent accidental downloads of lossy formats by enforcing ALAC-only downloads

Simplify wrapper binary download logic

Remove version tracking complexity - always download latest binary
and let Docker's build cache handle rebuild detection automatically

Switch wrapper to Docker container management

- Refactor wrapper.sh to manage Docker container instead of local binary
- Update README to document Docker-based wrapper setup
- Update .gitignore to ignore wrapper-repo/ instead of wrapper binary
- Clarify that .env file is optional with sensible defaults

Consolidate configuration variables at top of setup.sh

Move all static configuration variables to the top of the script
for better organization and consistency
- Update setup.sh to handle zip file downloads and extraction
- Add x86_64 fallback for Apple Silicon (arm64) when arm64 binary unavailable
- Add --platform linux/amd64 flag to docker run commands for Apple Silicon compatibility
- Update README with architecture-specific notes and troubleshooting
- Document zip extraction and Rosetta 2 usage

Reorganize download output structure and improve format check

- Format check is already integrated into download script (no separate docker run for check_format.sh)
- Reorganize files after download: ALAC/[Artist]/[Release] -> Apple Music Downloads/[Artist - Release Name]
- Flatten directory structure for easier browsing
- Update README with new output directory structure

Add support for multiple URLs in download script

- Accept multiple URLs as arguments (one to many)
- Check all URLs in a single docker run (--debug mode)
- Download all URLs in a single docker run (more efficient)
- Reorganize all downloaded files after completion
- Update help text and README with multiple URL examples
- Reduces docker runs from 2N (N URLs) to 2 (one check, one download)

Add quality controls and output directory flag

- Add --output-dir flag to specify download directory (replaces env var)
- Add --alac-max flag to override maximum sample rate
- Auto-detect bit depth and set sample rate limits:
  * 16-bit: max 44.1 kHz (CD quality)
  * 24-bit: max 48 kHz (high-res)
- Remove APPLE_MUSIC_OUTPUT_DIR from .env.template
- Update README with new flags and quality documentation
- Bit depth is determined by Apple Music (not configurable)

Refactor format checking logic and remove unnecessary wrapper checks

- Move all format checking and validation logic to check_format.sh
  - Make check_format.sh both a standalone script and a sourceable library
  - download_apple_music.sh now sources check_format.sh to use shared function
  - Eliminates code duplication (~155 lines removed from download script)
  - check_format.sh supports multiple URLs and validates all formats

- Remove wrapper checks from check_format.sh standalone execution
  - Format checking with --debug only queries metadata, doesn't need wrapper
  - Makes format checks faster by skipping unnecessary checks
  - Wrapper is only needed for actual downloading/decryption

- Fix dependency structure
  - download_apple_music.sh now sources wrapper_utils.sh directly
  - check_format.sh no longer sources wrapper_utils.sh (doesn't need it)
  - Each script now sources only what it needs directly

- Clean up check_format.sh
  - Remove unused WRAPPER_HOST, WRAPPER_PORT variables
  - Remove unused check_wrapper() function
  - Add comments explaining why wrapper isn't needed for format checks

Add platform compatibility note and fix wrapper startup

- Add note about Apple Silicon vs x86_64 compatibility in README
- Remove error output suppression in wrapper.sh to see actual errors
- Add --platform linux/amd64 flag to wrapper container for Apple Silicon

Simplify validation: remove bit depth/sample rate pairing constraint

- Remove pairing constraint between bit depth and sample rate
- Validation now only checks: minimum 16-bit, minimum 44.1 kHz
- Default max sample rates: 44.1 kHz for 16-bit, 48 kHz for 24-bit
- --max-sample-rate flag supports 44100, 48000, 96000, 192000 Hz
- Update README to reflect simplified validation rules
- Fixes issue where 24-bit/44.1 kHz was incorrectly rejected

Improve download script resilience for long-running downloads

- Move reorganize_files function definition early so cleanup can use it
- Reorganize existing ALAC files at script start (handles interrupted downloads)
- Add reorganization to cleanup function for error/timeout cases
- Improve error messages and handling for interrupted downloads
- Add note to README about long downloads and automatic reorganization
- Make reorganize_files idempotent and more robust

Add named container for downloader to prevent concurrent downloads

Extract shared architecture detection and update documentation

- Create detect_wrapper_architecture.sh for shared architecture detection
- Update setup.sh and wrapper.sh to use shared detection script
- Update README to reflect native arm64 wrapper support
- Document platform-specific Docker image naming
- Clarify wrapper runs natively on Apple Silicon (no emulation)

Fix downloader image pull for Apple Silicon

- Use --platform linux/amd64 when pulling downloader image on arm64
- Make downloader pull failure non-fatal (download script will pull when needed)
- Clarify that downloader uses Rosetta 2 on Apple Silicon
- Remove check_wrapper_restarting() and show_restart_error() functions
  - Restart detection was not reliably catching restart loops
  - Simplifies codebase by removing complex detection logic

- Simplify wrapper.sh start logic
  - Just checks if container is running after brief wait
  - No complex restart detection

- Remove restart checks from download_apple_music.sh and check_format.sh
  - Wrapper issues will be evident from downloader connection failures
  - Connection errors (e.g., 'dial tcp 127.0.0.1:10020: connect: connection refused')
    clearly indicate wrapper problems

- Update documentation
  - Note that wrapper issues show up as connection failures in downloader logs
  - Users can check wrapper logs directly: docker logs apple-music-wrapper

Note: This removes the restart detection that was added in a previous commit,
as it proved unreliable in practice.

Consolidate wrapper utilities and clarify architecture information

- Merge detect_wrapper_architecture.sh into wrapper_utils.sh
  - All wrapper-related utilities now in one place
  - Architecture detection auto-runs when sourced
  - Maintains same functionality with cleaner structure

- Default to x86_64 on Apple Silicon (no flag needed)
  - Removed FORCE_WRAPPER_X86_64 flag (now default behavior)
  - USE_WRAPPER_ARM64=0 explicitly set in .env.template
  - Better user experience - works out of the box

- Show clear architecture information in logs
  - Show both system architecture and wrapper/downloader architecture
  - Make it clear when using x86_64 as a workaround on Apple Silicon
  - Add architecture info to setup.sh, wrapper.sh, and download_apple_music.sh
  - Format: 'System: arm64 (Apple Silicon) | Wrapper: x86_64 (Rosetta 2)'
  - Format: 'System: arm64 (Apple Silicon) | Downloader: x86_64 (no arm64 build available - using Rosetta 2)'

- Update documentation
  - Reflect consolidation of architecture detection
  - Update .env.template comments
  - Clarify default behavior on Apple Silicon

Remove Dockerfile patching and default to native arm64 on Apple Silicon

The wrapper binary is compiled with Android NDK and links against Android
Apple Music app libraries - this is by design for decryption. Running x86_64
Android NDK binaries on Apple Silicon requires QEMU emulation (not Rosetta 2)
which crashes with segmentation faults. Native arm64 is now the default.

- Remove Dockerfile patching code (tzdata/env vars didn't help)
- Default to arm64 on Apple Silicon (native, no emulation)
- Change env var from USE_WRAPPER_ARM64 to USE_WRAPPER_X86_64
- Update documentation and messaging throughout

Fix docker build to use repo's Dockerfile instead of Dockerfile.simple

Create simple Dockerfile for prebuilt binaries (repo's Dockerfile is incomplete)

Use repo's Dockerfile and clone arm64 branch on Apple Silicon

- Remove all custom Dockerfile code (it doesn't work)
- Clone arm64 branch on Apple Silicon (has complete Dockerfile with entrypoint.sh)
- Clone main branch on x86_64
- Use repo's Dockerfile directly without modifications

Remove explicit --platform from docker commands

The correct branch (arm64 or main) is now cloned based on system architecture,
and the Dockerfile handles platform configuration internally. No need to
override with --platform flag.

- Remove --platform from docker build in setup.sh
- Remove --platform from docker run in wrapper.sh
- Remove unused DOCKER_PLATFORM variable from wrapper_utils.sh
- Update README and comments accordingly
…c use)

Fix Apple Silicon support: require credentials and add 2FA workflow

The arm64 wrapper binary requires login credentials to stay running.
This adds credential validation on Apple Silicon, 2FA support via file
input, and removes the broken x86_64 option that crashed with QEMU.

Also fixes Dockerfile restoration after extracting release zip to ensure
the correct (main branch) Dockerfile is used with arm64 shared libraries.

Use cached session instead of forcing re-authentication on every start

The wrapper docs show a two-step process: login once with -L flag, then
run without -L to use cached session. We were passing -L on every start,
causing 2FA prompts on restarts and potential instability during decryption.

Now checks for accounts.sqlitedb to determine if session is cached.
Added 'login' command to force re-authentication when needed.

Add interactive 2FA input when wrapper requests code

Fix restart 2FA loop: separate login container from server

The server container was started with -L (login) on first run. When it
crashed during decryption, Docker restarted it with the same args, so
it tried to re-authenticate and asked for 2FA again.

Now use a short-lived login container (-L -F) for auth + 2FA only, then
stop it. Start the long-lived server with -H only. Restarts use cached
session and never re-auth.

Troubleshooting: decryption crash (OOM check), device limit

- Decryption crash: add docker inspect / ps commands to check exit 137 (OOM)
- New section "You've reached your device limit": Apple Music concurrent
  device limit, how to free devices, use cached session to avoid re-auth
@julientregoat julientregoat changed the title [WIP] Apple Music lossless downloader (wrapper + downloader) [WIP] Apple Music lossless downloader Jan 25, 2026
…ion fails

- Platform Compatibility + Apple Silicon troubleshooting: local works with
  single-track, crashes with albums/too many; link to Decryption fails
- download_apple_music.sh: warn when arm64 that album/multi-track may crash
- wrapper.sh: note on arm64 start about single-track vs album

## Quality

**ALAC (lossless) is the default and highest quality format.** The downloader automatically selects ALAC if available, falling back to other formats only if ALAC is not available for a specific track.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incorrect, ONLY alac lossless allowed

```

**Options:**
- `--auto-wrapper` - Automatically start wrapper if not running, and stop it if this script started it (after download completes)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FIXME remove this functionality, and flag + envvar used to toggle it

echo ""

# Note about Apple Silicon requirements
if [[ "$(uname -m)" == "arm64" ]] || [[ "$(uname -m)" == "aarch64" ]]; then
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicates wrapper arch detection logic; can this be moved later in the file and use those envvars?

WRAPPER_IMAGE="${WRAPPER_IMAGE_BASE}-${WRAPPER_IMAGE_SUFFIX}"

# Show clear architecture information
SYSTEM_ARCH=$(uname -m)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicates wrapper utils arch detection logic i think? same as top of file does

…ame -m calls

- Moved wrapper_utils.sh sourcing earlier in setup.sh (before first architecture check)
- Replaced all uname -m calls with WRAPPER_ARCH variable from wrapper_utils.sh
- Simplified architecture checks from checking both arm64/aarch64 to using WRAPPER_ARCH
- Centralizes architecture detection logic in wrapper_utils.sh
- All files now source wrapper_utils.sh before using WRAPPER_ARCH
- Update README.md to clarify that scripts error out if ALAC is not available
- Remove incorrect mentions of fallback behavior to other formats
- Update format descriptions (aac-lc, Dolby Atmos, other formats) to state they are not used
- Update comment in download_apple_music.sh to clarify no fallback behavior
- Remove --auto-wrapper flag and APPLE_MUSIC_AUTO_WRAPPER env var
- Simplify wrapper check to only error if not running (no auto-start)
- Remove wrapper shutdown logic at end of script
- Update .env.template to remove APPLE_MUSIC_AUTO_WRAPPER
- Update README.md to remove all auto-wrapper references
- Treat wrapper as long-lived service that should be started manually
- Rename wrapper_utils.sh to utils.sh for generic architecture detection
- Simplify to only export SYSTEM_ARCH (remove wrapper-specific variables)
- Update all scripts to use SYSTEM_ARCH and compose wrapper values when needed
- Make credentials required on all architectures (not just Apple Silicon)
- Remove dead fallback logic and unnecessary checks
- Simplify release fetching: remove non-zip fallback, inline URLs
- Remove redundant logging and architecture-specific branches
- Clean up variable naming: prefix wrapper-specific vars with WRAPPER_
- Update documentation to reflect credentials always required
- Add generic container utilities: container_is_running(), container_exists(), cleanup_container()
- Add generic network utility: check_port()
- Replace duplicated container checks in wrapper.sh and download_apple_music.sh
- Remove wrapper-specific is_running() function in favor of generic container_is_running()
- Remove wrapper-specific check_wrapper() function in favor of generic check_port()
- Reduce code duplication and improve maintainability
- Add DOWNLOADER_IMAGE constant, load_env(), and require_docker() to utils.sh
- Remove duplicate .env loading blocks from all scripts (now use load_env)
- Remove duplicate DOWNLOADER_IMAGE definitions (now exported from utils.sh)
- Remove duplicate Docker checks (now use require_docker)
- Consolidate duplicate help text in download_apple_music.sh into show_usage()
- Fix bug: remove `local` keyword outside function in check_format.sh
- Reduce README from 479 to 191 lines (60% reduction)
- Remove remote wrapper section (unimplemented functionality)
- Consolidate redundant mentions of credentials, Apple Silicon warnings, ALAC defaults
- Add back useful context: format explanations, related projects usage, media-user-token details
- Simplify code comments while preserving important architectural explanations
- Restore function usage hints in utils.sh and two-phase login explanation in wrapper.sh
- Extract login logic from wrapper.sh into authenticate_wrapper() function
- Simplify reorganize_files() using shorter conditional syntax
- Combine regex patterns in check_format.sh format parsing
- Simplify output directory assignment using parameter expansion
- Use print0 and explicit loop to process each file exactly once
- Add sort to ensure consistent ordering when finding extracted binary
- Skip wrapper.zip explicitly to avoid deleting file we're about to download
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant