Self-hosted service
miraclx/freyr-js avatar
miraclx/freyr-js

freyr: taking a streaming URL and leaving you an m4a library

A tool for downloading songs from music streaming services like Spotify and Apple Music.

2,358 stars155 forksJavaScriptApache-2.0

At a glance

What is it?
freyr reads metadata from Spotify, Apple Music or Deezer, finds the audio somewhere else, and writes tagged AAC files into a structured folder. The interesting engineering is in the matching step, not the download.
Who is it for?
freyr is built around a specific bet: that you can identify a track reliably enough from its metadata to find the right audio somewhere else, and that a properly tagged local file is worth more than a streaming subscription. The bet holds up better than expected for well-known catalog music, where title, artist, album and duration agree across sources.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 7, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The streaming service supplies metadata, not audio

The single most important thing to understand about freyr is which half of the pipeline comes from where. The streaming service URL you provide is used to extract metadata: title, album, artist, and the rest. The audio itself is found by querying other sources, which the README names as YouTube among others.

The README lays out five steps. Extract track metadata from the streaming service. Query sources and classify the results to find the best sounding, most accurate audio, then download it in raw format. Encode to Apple AAC in an `.m4a` file at a default bitrate of 320kbps. Embed all the metadata and album art. And organize the files into a structured library.

That ordering explains why the project supports three services with different metadata quality. Spotify, Apple Music, and Deezer differ not in what you can search but in what you can label afterwards. If you are deciding which URL to paste in, look at the table below first, because a field missing at step one cannot be recovered at step four.

It also explains the shape of the risk. The streaming service is a metadata oracle that happens to be authenticated, and the audio is a best match found by search. Those are different trust models, and a tool that conflates them would be much easier to write and much harder to trust.

Composer and genre are the fields you will lose

The README publishes a metadata availability table across the three services, and it is short enough to read at a glance and useful enough to drive your choice. Title, artist, album, track number, disk number, release date, rating, album artist, ISRC, label, copyright, and cover art are available from all three. Composer and genre are the two rows marked as unavailable from Spotify, and available from both Apple Music and Deezer.

For a library you intend to browse with a tag-aware player, that is a real difference. Losing composer means classical and score-based collections lose the one field that distinguishes a work from a recording of it. Losing genre means any genre-based view or smart playlist has nothing to group on.

The rest of the table is uniform in a way that suggests deliberate design rather than coincidence. ISRC, the international standard recording code, is available everywhere, and that is the field that actually identifies a recording across sources. A tool built around matching can lean on ISRC when title and artist strings disagree, and the fact that freyr can embed it into the output means the identifier survives into your local library and can be used again later.

The release notes show this area getting specific attention: v0.10.3 added ISRC-style sort metadata being embedded in the output file, and separately fixed the total disc number count, which is the kind of detail that only matters to someone who has already noticed it was wrong.

Classification is the part that decides whether it works

Step two is where the project is doing real work: it classifies results from its queries to pick the best sounding, most accurate audio. The README does not describe the classifier, and the repository tree does not obviously expose it, which means this is the part of the system you will have to judge by its output rather than by reading about it.

What you can tell from the release history is which failure modes have been real. v0.10.3 fixed the YouTube Music logic for sourcing tracks. v0.10.2 added support for non-latin letters in source search, and made failure to acquire an audio source handled gracefully instead of taking the run down. The second of those is the more important change. A downloader that aborts on one unmatchable track is unusable on a playlist of fifty, because you cannot tell which ones it got.

Non-latin search support deserves more than a mention, because it is where catalogue coverage and matching quality intersect. A transliterated or romanized query finds different results than a query in the original script, and if your library includes music outside the English-language mainstream you will notice the difference between what you asked for and what came back.

The failure mode to watch for is a confident wrong match rather than an error. A track that resolves to a live version, a cover, or a remix will be tagged completely correctly, because every piece of metadata came from the streaming service and the only substitution was the audio.

What the package actually depends on

The `package.json` describes a Node package named `freyr`, version 0.10.3, published as an ES module with a single export pointing at `src/freyr.js` and a `freyr` binary pointing at `cli.js`. The engine requirement is Node 16 or later, and the license is Apache-2.0.

The dependency list explains most of the pipeline. `@ffmpeg/core` and `@ffmpeg/ffmpeg` handle the encode step. `@miraclx/spotify-web-api-node` and `@yujinakayama/apple-music` are the metadata clients, one of them a fork maintained by the same author. `libxget` and `youtube-dl-exec` are in the tree for the sourcing step. Then there is the ordinary Node surface: `commander` for the CLI, `conf` for configuration, `express` with `cors` and `cookie-parser`, `got` for HTTP, `node-cache`, `file-type`, `filenamify`, `async`, `bluebird`, and `mkdirp`.

Two things stand out. First, the project carries its own Apple Music client as a maintained fork, and v0.10.2's changelog contains an entry for updating the Apple Music access token. Authentication against a streaming service is the part of this pipeline most likely to break without notice, and a project that patches around it by shipping a fork is telling you something real about the maintenance burden.

Second, `filenamify` and the `country-data` dependency point at a filesystem concern that is easy to underestimate. A library of tracks by artists with non-latin names, characters that are illegal on some filesystems, and names that differ only by normalization form is a real problem, and the presence of a dedicated sanitizing dependency is a sign the author hit it.

AtomicParsley, and why the Docker build compiles it

Metadata embedding needs a tagger, and freyr uses AtomicParsley, which the README lists as a requirement at version 20230114 or later. On a normal install you install it from your package manager or drop the binary somewhere on your PATH. Inside the container, the build compiles it from source, which is the most interesting thing in the `Dockerfile`.

The multi-stage image starts from a Node alpine image to install production dependencies with yarn, then moves to a Go image to install `node-prune` and strip the development modules from `node_modules`. It then clones AtomicParsley at a specific commit hash:

code
RUN go install github.com/tj/node-prune@1159d4c \
  && node-prune --include '*.map' /freyr/node_modules \
  && node-prune /freyr/node_modules \
  && git clone --branch 20230114.175602.21bde60 --depth 1 https://github.com/miraclx/atomicparsley /atomicparsley \

The comment above that line explains why: the upstream pull request for the fix they need has not been merged and no release has been cut. So the image pins an unmerged branch of a fork. That is a reasonable engineering decision for a one-off dependency and a poor one to inherit unknowingly, because the pin is to a commit in someone else's repository that can be garbage collected.

The final stage is a plain alpine image with bash, nodejs, and python3, cleaned of `.pyc` and `.whl` files, with AtomicParsley copied to `/bin`. The image creates an unprivileged `freyr` user, symlinks the CLI into `/bin`, declares `/data` as a volume, and sets the entrypoint to the shell wrapper `freyr.sh` with `--help` as the default command. Running as a non-root user with a single writable volume is a sensible shape for something that downloads a lot of files.

The README also requires Python, at version 3.2 or later, which is an oddly low floor and a reminder that this pipeline has accumulated dependencies over years.

Maintenance signals, and what the release history says about scope

The last three releases are v0.10.1 on 2023-08-08, v0.10.2 on 2024-01-01, and v0.10.3 on 2024-01-14. The last push was on 2026-09-24, which is well within recent activity, so the tree is moving while the tagged releases have not moved much since January 2024.

Read the release notes as a description of the project's real maintenance surface. Apple Music authentication was automated in v0.10.3, after being handled by hand earlier. YouTube Music sourcing was fixed. Deezer gained copyright encoding, and Spotify was changed to encode copyright in the `℗ {YEAR} {LABEL}` format specifically to match Apple Music. Non-explicit Spotify tracks are now tagged `Inoffensive`, and sort metadata keys such as `sonm`, `soar`, and `soal` are embedded in the output file.

Every one of those is a detail about matching a streaming service's conventions rather than about downloading audio. The consistent lesson is that the hard part of this tool is agreeing with each service about how a track should be labelled, because that agreement is what makes a local library feel native instead of assembled.

The FAQ and issue tracker in the repository are the place to look before committing to it. This is a tool whose value depends on external services behaving as expected, and none of those services owe it stability.

Editorial conclusion

freyr is built around a specific bet: that you can identify a track reliably enough from its metadata to find the right audio somewhere else, and that a properly tagged local file is worth more than a streaming subscription. The bet holds up better than expected for well-known catalog music, where title, artist, album and duration agree across sources. It gets harder with obscure releases, live recordings, and regional catalogue differences, where a wrong match produces a correctly tagged wrong song. Read the metadata availability table before trusting a batch, remember that the tool is not downloading from the streaming service, and decide for yourself whether sourcing audio from public video sites sits inside your own terms of use.

Frequently asked questions

Which streaming services does freyr support?

Spotify, Apple Music, and Deezer. The README's metadata table shows all three supply title, artist, album, track and disc number, release date, rating, album artist, ISRC, label, copyright, and cover art, while Spotify is the one service without composer or genre.

What audio format does freyr produce?

Apple AAC in an `.m4a` container at a default bitrate of 320kbps, with metadata and album art embedded and the files organized into a structured library. The npm package is published as `freyr` and installs a `freyr` command.

Does freyr download audio from Spotify or Apple Music?

No. The streaming service is used to extract track metadata, and the audio itself is found by querying other sources, which the README names as YouTube among others, then classified before download. This distinction matters for how you think about what the tool depends on.

What do I need to install freyr manually?

Node 16 or later, Python, and AtomicParsley at version 20230114 or later for tag embedding. The README recommends Docker instead, partly because the container build handles AtomicParsley and the production dependency pruning for you.

Official sources

  1. License: Apache-2.0
  2. miraclx/freyr-js on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/miraclx-freyr-js.svg)](https://hysenlabs.com/projects/miraclx-freyr-js)