ID3-JSON
This project's goal is to provide an easy way to read and write ID3 tags with a consistent input and output. The existing tools I've found require a lot of work to parse their output and work in inconsistent ways, so I'm making another one.
The main client of this project is going to be a Vim plugin: https://github.com/AndrewRadev/id3.vim. That said, there's no particular reason not to use it for whatever. I'd like to keep the tool general-purpose, so if you're looking for some specific functionality, please open a github issue.
The project is backed by the excellent id3 crate built in Rust. Really, the tag parsing and writing logic is all there -- the code here mostly just translates the data to/from JSON (though I do make some assumptions, see Quirks below).
Installation
If you have the Rust toolchain installed, you can install it from crates.io:
$ cargo install id3-json
But you can also use the precompiled binary for your operating system from the releases tab in github: https://github.com/AndrewRadev/id3-json/releases:
Basic usage
Running the program with --help should provide a message along these lines.
id3-json 0.3.1
USAGE:
id3-json [FLAGS] <music-file.mp3>
FLAGS:
-r, --read Reads tags from the file and outputs them to STDOUT as JSON,
or writes them to the file given by --out-json.
If neither `read` nor `write` are given, will read by default.
-w, --write Write mode, expects a JSON on STDIN with valid tag values,
or reads the tags from the file given by --in-json.
If also given `read`, will print/write the resulting tags afterwards
--with-covers Also output cover images as base64-encoded data.
If not set, only cover metadata will be returned.
-i, --in-json <path/to.json>
File to read tags from. If not given, uses STDIN
-o, --out-json <path/to.json>
File to write tags to. If not given, uses STDOUT
--tag-version <ID3v2.{2,3,4}>
On write, sets the tags' version to 2.2, 2.3, or 2.4.
-V, --version Print version information
ARGS:
<music-file.mp3> Music file to read tags from or write tags to
The input to write to a tag should be a valid json with a "data" key pointing to a nested object with "title", "artist", etc as keys. The output is a similar JSON object with a "data" key with all of these fields set. Here's some example output, pretty-printed using the jq tool:
% id3-json tests/fixtures/attempt_1_no_cover.mp3 | jq .
{
"data": {
"album": "Echoes From The Past",
"artist": "Christiaan Bakker",
"comment": "http://www.jamendo.com Attribution 3.0 ",
"covers": [],
"date": null,
"genre": "(255)",
"title": "Elevator Music Attempt #1",
"track": null
},
"version": "ID3v2.4"
}
Here's how we can update the title and track number, and remove the genre. The tool will print the tags after the change (because of --read):
% echo '{ "data": {"title": "[updated]", "track": 1, "genre": null} }' | id3-json tests/fixtures/attempt_1_no_cover.mp3 --write --read | jq .
{
"data": {
"album": "Echoes From The Past",
"artist": "Christiaan Bakker",
"comment": "http://www.jamendo.com Attribution 3.0 ",
"covers": [],
"date": null,
"genre": null,
"title": "[updated]",
"track": 1
},
"version": "ID3v2.4"
}
Alternatively, you can read and write tags from/to JSON files instead of standard input and output. If we put our input tags in a JSON file called test_input.json:
We can then use the --in-json parameter for this input and optionally --out-json parameter to read the output:
% id3-json tests/fixtures/attempt_1_no_cover.mp3 -w -r --in-json test_input.json --out-json test_output.json
% jq . test_output.json
{
"data": {
"album": "Echoes From The Past",
"artist": "Christiaan Bakker",
"comment": "http://www.jamendo.com Attribution 3.0 ",
"covers": [],
"date": null,
"genre": "(255)",
"title": "[updated through file]",
"track": null
},
"version": "ID3v2.4"
}
For compatibility reasons, you can provide the field names without nesting them inside the "data" key.
Cover images
Cover image data is a bit tricky to transport, since it needs to be encoded in some way, and it can be large, which would get in the way of examining in a terminal. By default, the tool will output only metadata about images in the covers key:
% id3-json tests/fixtures/attempt_1.mp3 | jq .
{
"data": {
"album": "Echoes From The Past",
"artist": "Christiaan Bakker",
"comment": "http://www.jamendo.com Attribution 3.0 ",
"covers": [
{
"description": "",
"mime_type": "image/jpeg",
"size": 13707,
"type": "front"
}
],
"date": null,
"genre": "(255)",
"title": "Elevator Music Attempt #1",
"track": null
},
"version": "ID3v2.4"
}
If the same command is called with --with-covers, the objects in the covers array will contain a data key with the image bytes encoded in base64. In this example, the data has been truncated for readability:
% id3-json --with-covers tests/fixtures/attempt_1.mp3 | jq .
{
"data": {
"album": "Echoes From The Past",
"artist": "Christiaan Bakker",
"comment": "http://www.jamendo.com Attribution 3.0 ",
"covers": [
{
"data": "/9j/4AAQSkZJRgABAQEASABIAAD/2wBD[...]",
"description": "",
"mime_type": "image/jpeg",
"size": 13707,
"type": "front",
}
],
"date": null,
"genre": "(255)",
"title": "Elevator Music Attempt #1",
"track": null
},
"version": "ID3v2.4"
}
In general, you can embed many types of images into mp3s, but at this time, the tool supports only covers and "other". When writing, this can be specified in the type key: "front", "back", or "other". If the key is missing, "front" will be used. The mime_type will default to "image/jpeg", but you can provide a different image format, e.g. "image/png". The "description" can be whatever textual description you'd like. There is no need to specify the size when writing, since it will be calculated by the data, but you always need a "data" key. That's all the minimal example requires:
# Generate JSON with the base64-encoded image data, not wrapped:
BASE_64_DATA=
# Write the data in a JSON file:
# Apply the JSON to the file:
After doing this, you should be able to open the song in a music player and see the chosen cover attached. If you'd like a different way to embed images on the command-line without going through base64 encoding, try my other project, id3-image.
Quirks
The numbers given to the "year" field in set_year seem to be i32, but for simplicity, I assume years are going to be positive numbers.
If the tags are ID3v2.4, the tool will read and write the "date" field as "date recorded" (TDRL). See the relevant github issue for the conversation: https://github.com/AndrewRadev/id3-json/issues/1. Seems like both picard and easy tag use that field, which is why I chose it.
It's still a bit up in the air, I might end up dealing with both "date recorded" and "date released" as separate fields, though I can't help but wonder how many people care and would be happy to just have "date". For a tag that's not v2.4, it'll return "year" from the TYER tag instead.
It's possible to have multiple comments with a "description", "lang", and "text". See the frame::Comment structure for details. However, at least in my personal music library, it seems almost all mp3 files contain a single comment with "" for the description. Some of them have another one that's labeled as "ID3v1 comment".
For simplicity's sake I've decided to have id3-json read and write that one comment with a description of "". All other comments should be preserved, so if anything else reads them, it should still work as expected.
Potential future changes
For now, this interface works for me, but if other people need to use it without some of my design choices, a --raw or --full option could be implemented to just read and write the frames as-is with minimal processing and leave it to the client of the tool to decide how to manage them.
Batch processing is another direction I could take this, returning a JSON array with an entry for each filename (or an object with filenames as keys?) and, when writing, expecting a corresponding array/object.
A lot of other metadata could also be read/written, the specific fields I've chosen are just what I used to use from a different utility.
Music used for testing:
- Elevator Music Attempt 1 by Christian Bakker: https://www.jamendo.com/en/list/a98147/echoes-from-the-past
- The Masochism Tango by Tom Lehrer: https://tomlehrersongs.com/the-masochism-tango/