Read and write ID3 tags with machine-readable input and output

Overview

Build Status

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.2.0

USAGE:
    id3-json [FLAGS] <music-file.mp3>

FLAGS:
    -r, --read       Reads tags from the file and outputs them to STDOUT as 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.
                     If also given `read`, will print the resulting tags afterwards

        --tag-version <ID3v2.{2,3,4}>
                     On write, sets the tags' version to 2.2, 2.3, or 2.4.

    -V, --version    Prints 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 "title", "artist", etc as keys. The output will be a JSON object that has a "data" key and inside has all these fields set. Here's some example output, pretty-printed using the jq tool:

% 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 ",
    "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 '{ "title": "[updated]", "track": 1, "genre": null }' | id3-json tests/fixtures/attempt_1.mp3 --write --read | jq .
{
  "data": {
    "album": "Echoes From The Past",
    "artist": "Christiaan Bakker",
    "comment": "http://www.jamendo.com Attribution 3.0 ",
    "date": null,
    "genre": null,
    "title": "[updated]",
    "track": 1
  },
  "version": "ID3v2.4"
}

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: #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:

You might also like...
Convenience wrapper for cargo buildscript input/output

A convenience wrapper for cargo buildscript input/output. Why? The cargo buildscript API is (necessarily) stringly-typed.

Scriptable tool to read and write UEFI variables from EFI shell. View, save, edit and restore hidden UEFI (BIOS) Setup settings faster than with the OEM menu forms.
Scriptable tool to read and write UEFI variables from EFI shell. View, save, edit and restore hidden UEFI (BIOS) Setup settings faster than with the OEM menu forms.

UEFI Variable Tool (UVT) UEFI Variable Tool (UVT) is a command-line application that runs from the UEFI shell. It can be launched in seconds from any

A simple rust library to read and write Zip archives, which is also my pet project for learning Rust

rust-zip A simple rust library to read and write Zip archives, which is also my pet project for learning Rust. At the moment you can list the files in

Rust-based WebAssembly bindings to read and write Apache Parquet files

parquet-wasm WebAssembly bindings to read and write the Parquet format to Apache Arrow. This is designed to be used alongside a JavaScript Arrow imple

SQL database to read and write
SQL database to read and write "discord"

GlueSQL Discord Storage After discussing how CI testing will be managed, we plan to move it upstream. Precautions for use discord ToS https://discord.

ask.sh: AI terminal assistant that can read and write your terminal directly!
ask.sh: AI terminal assistant that can read and write your terminal directly!

ask.sh: AI terminal assistant that read from & write to your terminal ask.sh is an AI terminal assistant based on OpenAI APIs such as GPT-3.5/4! What'

Rust crate to extend io::Read & io::Write types with progress callbacks

progress-streams Rust crate to provide progress callbacks for types which implement io::Read or io::Write. Examples Reader extern crate progress_strea

Rust read/write support for well-known text (WKT)

wkt Rust read/write support for well-known text (WKT). License Licensed under either of Apache License, Version 2.0 (LICENSE-APACHE or http://www.apac

Rust read/write support for GPS Exchange Format (GPX)

gpx gpx is a library for reading and writing GPX (GPS Exchange Format) files. It uses the primitives provided by geo-types to allow for storage of GPS

Rust read/write support for GPS Exchange Format (GPX)

gpx gpx is a library for reading and writing GPX (GPS Exchange Format) files. It uses the primitives provided by geo-types to allow for storage of GPS

Rust read/write support for well-known text (WKT)

wkt Rust read/write support for well-known text (WKT). License Licensed under either of Apache License, Version 2.0 (LICENSE-APACHE or http://www.apac

UEFI command-line tool for read/write access of variables

UEFI Command Line Tool for Reading/Writing UEFI Variables This tool is a rewritten version of the modded grub with setup_var commands, enhanced with m

A Rust program/library to write a Hello World message to the standard output.

hello-world Description hello-world is a Rust program and library that prints the line Hello, world! to the console. Why was this created? This progra

Text Expression Runner – Readable and easy to use text expressions
Text Expression Runner – Readable and easy to use text expressions

ter - Text Expression Runner ter is a cli to run text expressions and perform basic text operations such as filtering, ignoring and replacing on the c

CLI Tool for tagging and organizing files by tags.

wutag 🔱 🏷️ CLI tool for tagging and organizing files by tags. Install If you use arch Linux and have AUR repositories set up you can use your favour

A common library and set of test cases for transforming OSM tags to lane specifications

osm2lanes See discussion for context. This repo is currently just for starting this experiment. No license chosen yet. Structure data tests.json—tests

Verbump - A simple utility written in rust to bump and manage git semantic version tags.

Verbump - A simple utility written in rust to bump and manage git semantic version tags.

An efficient pictures manager based on custom tags and file system organization.

PicturesManager An efficient pictures manager based on custom tags and file system organization. Developed with Tauri (web app) with a Rust backend an

Generate easy to remember sentences that acts as human readable UUIDs 🥳

uuid-readable-rs Easy to remember unique sentences acting as UUID Generate easy to remember sentences that acts as human readable UUIDs. Built on UUID

Comments
  • year is shown as null in ID3v2.4 tagged file

    year is shown as null in ID3v2.4 tagged file

    id3-json -r 10.\ the\ masochism\ tango.mp3 | jq . outputs this:

    {
      "data": {
        "album": "An Evening Wasted With Tom Lehrer",
        "artist": "Tom Lehrer",
        "comment": null,
        "genre": "Comedy",
        "title": "The Masochism Tango",
        "track": 10,
        "year": null
      }
    }
    

    while picard shows this:

    screenshot picard showing the date as 1990-04-12

    It happens with this file: testfile.zip (it's in the public domain)

    % ./id3-json -V
    id3-json 0.1.2
    

    I'm not very familiar with id3 tags, but the issue appears to be that v2.4 replaced the year tags (TORY and TYER) with time tags (TDOR and TDRL). Maybe they should be used if the year tags are not found?

    opened by tastytea 13
Owner
Andrew Radev
"Nooooo, you can't keep solving your problems by writing Vim plugins, use a real programming language" Haha vimscript go brrrrr
Andrew Radev
Converts cargo check (and clippy) JSON output to the GitHub Action error format

cargo-action-fmt Takes JSON-formatted cargo check (and cargo clippy) output and formats it for GitHub actions. Examples This tool can be used with a v

Oliver Gould 8 Oct 12, 2022
Rust libraries and tools to help with interoperability and testing of serialization formats based on Serde.

The repository zefchain/serde-reflection is based on Facebook's repository novifinancial/serde-reflection. We are now maintaining the project here and

Zefchain Labs 46 Dec 22, 2022
Jsonptr - Data structures and logic for resolving, assigning, and deleting by JSON Pointers

jsonptr - JSON Pointers for Rust Data structures and logic for resolving, assigning, and deleting by JSON Pointers (RFC 6901). Usage Resolve JSON Poin

Chance 38 Aug 28, 2022
A fast and simple command-line tool for common operations over JSON-lines files

rjp: Rapid JSON-lines processor A fast and simple command-line tool for common operations over JSON-lines files, such as: converting to and from text

Ales Tamchyna 3 Jul 8, 2022
Esri JSON struct definitions and serde integration.

serde_esri Esri JSON parsing library. This crate provides representations of Esri JSON objects with serde::Deserialize and serde::Serialize trait impl

Josiah Parry 5 Nov 23, 2023
Read input lines as byte slices for high efficiency

bytelines This library provides an easy way to read in input lines as byte slices for high efficiency. It's basically lines from the standard library,

Isaac Whitfield 53 Sep 24, 2022
TestDrive automatically scrapes input/output data from BOJ(Baekjoon Online Judge) and runs tests for your executable binary file!

?? TestDrive What does it do? TestDrive automatically scrapes input/output data from BOJ(Baekjoon Online Judge) and runs tests for your executable bin

Hyeonseok Jung 3 Mar 5, 2022
A tool to deserialize data from an input encoding, transform it and serialize it back into an output encoding.

dts A simple tool to deserialize data from an input encoding, transform it and serialize it back into an output encoding. Requires rust >= 1.56.0. Ins

null 11 Dec 14, 2022
Grimsby is an Erlang Port written in Rust that can close its standard input while retaining standard output (and error)

Grimsby An Erlang Port provides the basic mechanism for communication from Erlang with the external world. From the Ports and Port Drivers: Erlang Ref

Peter Morgan 5 May 29, 2023
Rust library for program synthesis of string transformations from input-output examples 🔮

Synox implements program synthesis of string transformations from input-output examples. Perhaps the most well-known use of string program synthesis in end-user programs is the Flash Fill feature in Excel. These string transformations are learned from input-output examples.

Anish Athalye 21 Apr 27, 2022