Home

Awesome

tone

tone is a cross platform audio tagger and metadata editor to dump and modify metadata for a wide variety of formats, including mp3, m4b, flac and more. It has no dependencies and can be downloaded as single binary for Windows, macOS, Linux and other common platforms.

The code is written in pure C# and utilizes the awesome atldotnet library to provide full support for a wide variety of audio and metadata formats.

Features

The main purpose of tone is to tag m4b audio books for myself. It is planned as a successor to [m4b-tool].

Support me via GitHub sponsors

If you are using any of my projects and find them helpful, please consider donating to support me. I plan to use the money to support other open source projects or charitable purposes. Thank you!

<p align="center"> <a href="https://github.com/sponsors/sandreas"><img src="./assets/help.svg" width="300" alt="sponsor me and donate" style="margin:auto;"></a> </p>

TL;DR

dump tags

# show help
tone dump --help

# show all tags for single file (input.mp3)
tone dump input.mp3

# show title and artiest tag recursively for all files in directory with extension m4b in FFMETADATA format
tone dump audio-directory/ --include-extension m4b --format ffmetadata --include-property title --include-property artist

# show album only via json format and JSONPath query
tone dump "input.mp3" --format json --query "$.meta.album"

# show audio stream information via JSONPath query
tone dump "input.mp3" --format json --query "$.audio"

Redirecting output / executing subprocess

If you plan to redirect the output into a file or run tone in a subprocess, there was a breaking change in version 0.2.2, that will prevent invalid json output redirected into a file, e.g.:

tone dump --format json audio.mp3 > audio-metadata.json

To force behaviour of version 0.1.7 (not recommended), you can use the environment variable TONE_OUTPUT_REDIRECT=0, e.g.:

TONE_OUTPUT_REDIRECT=0 tone dump --format json audio.mp3 > audio-metadata.json

This environment variable can also be used for subprocess execution.

modify tags

IMPORTANT: Because metadata is modified in place without copying the file, changes are only safe on local block storage (like your hard disk, sd card, etc.). Modifying files on remote storages (like network shares) may break your files. Please make sure you have a backup.

# show help
tone tag --help

# change title tag
tone tag input.mp3 --meta-title "a title"

# change a custom field, auto-import covers nearby and show debug info on error (--dry-run simulation)
tone tag --debug --auto-import=covers --meta-additional-field "©st3=testing" input.m4b --dry-run

# recursively set tags genre, artist, series, part and title by path pattern (--dry-run simulation)
tone tag --auto-import=covers --auto-import=chapters --path-pattern="audiobooks/%g/%a/%s/%p - %n.m4b" --path-pattern="audiobooks/%g/%a/%z/%n.m4b" audiobooks/ --dry-run

# write your own custom JavaScript tagger and call this function with parameters to modify metadata on your own
tone tag "harry-potter-1.m4b" --taggers="musicbrainz" --script="musicbrainz.js" --script-tagger-parameter="e2310769-2e68-462f-b54f-25ac8e3f1a21"

Setup

tone is a terminal application and deployed as monolithic binary with no dependencies. This means, that downloading a single file from the releases page.

Linux / macOS


# linux-arm
wget https://github.com/sandreas/tone/releases/download/v0.2.2/tone-0.2.2-linux-arm.tar.gz

# linux-arm64
wget https://github.com/sandreas/tone/releases/download/v0.2.2/tone-0.2.2-linux-arm64.tar.gz

# linux-x64
wget https://github.com/sandreas/tone/releases/download/v0.2.2/tone-0.2.2-linux-x64.tar.gz

# macos (m1) - not working atm, see issue #6
wget https://github.com/sandreas/tone/releases/download/v0.2.2/tone-0.2.2-osx-arm64.tar.gz

# macos (intel)
wget https://github.com/sandreas/tone/releases/download/v0.2.2/tone-0.2.2-osx-x64.tar.gz

# untar 
tar xzf tone-*.tar.gz

# install to your $PATH
sudo mv tone*/tone /usr/local/bin/

# test if tone is usable
tone --help

Windows

# download for windows (powershell)
iwr -outf tone-0.2.2-win-x64.zip https://github.com/sandreas/tone/releases/download/v0.2.2/tone-0.2.2-win-x64.zip

# extract tone
Expand-Archive -LiteralPath tone-0.2.2-win-x64.zip -DestinationPath .

# test if tone is usable
.\tone --help

# open directory in windows explorer to manually put tone in your %PATH%, e.g. C:\Windows
start .

Docker

Since tone is a monolith, it is probably not necessary to run it via docker, but since it is convenient to have a possibility to copy tone in your own image, I published an official variant on dockerhub. Since it is a multiarch image, you can use it on arm6, arm7, aarch64, and x64 images.

docker pull sandreas/tone:v0.2.2

Or to use tone in your custom Dockerfile:

# Dockerfile
FROM sandreas/tone:v0.2.2 as tone
# ...
COPY --from=tone /usr/local/bin/tone /usr/local/bin/

Reserved fields and supported formats

tone already supports some common input and output formats for metadata, as well as a tone specific one (ToneJson). Moreover tone also uses some reserved metadata fields to overcome issues when storing specific information.

Reserved metadata fields

The namespace ----:com.pilabor.tone as well as the following fields are reserved for tone in mp4 / m4a / m4b based file formats:

ToneJson format

The ToneJson format is specific for tone, can contain all supported metadata (including binary images) and looks similar to this example...

Example

{
  "audio": {
    "bitrate": 320,
    "format": "MPEG Audio (Layer III)",
    "formatShort": "MPEG",
    "sampleRate": 44100.0,
    "duration": 255920.0,
    "channels": {
      "count": 2,
      "description": "Joint Stereo"
    },
    "frames": {
      "offset": 20749,
      "length": 10236864
    },
    "metaFormat": [
      "id3V24"
    ]
  },
  "meta": {
    "album": "Back in Black",
    "albumArtist": "AC/DC",
    "artist": "AC/DC",
    "discNumber": 1,
    "discTotal": 1,
    "encodedBy": "LAME 3.99.5",
    "genre": "Hard Rock",
    "itunesCompilation": "no",
    "publisher": "Atlantic",
    "recordingDate": "1986-01-01T00:00:00",
    "sortArtist": "AC/DC",
    "title": "Back in Black",
    "trackNumber": 6,
    "trackTotal": 10,
    "embeddedPictures": [
      {
        "type": 2,
        "code": 3,
        "mimetype": "image/jpeg",
        "data": "/9j/4AAQSkZJRgA...9k="
      }
    ],
    "additionalFields": {
      "grP1": "5",
      "tmed": "CD",
      "tlan": "eng",
      "tipl": "arranger",
      "tdor": "1980-07-25",
      "script": "Latn",
      "artist Credit": "AC/DC",
      "albumartistsort": "AC/DC",
      "catalognumber": "16018-2",
      "album Artist Credit": "AC/DC",
      "musicBrainz Album Type": "album",
      "replaygaiN_ALBUM_GAIN": "-8.43 dB",
      "replaygaiN_ALBUM_PEAK": "1.064363",
      "replaygaiN_TRACK_GAIN": "-8.38 dB",
      "replaygaiN_TRACK_PEAK": "1.051585",
      "musicBrainz Album Status": "Official",
      "musicBrainz Album Release Country": "DE",
      "acoustid Id": "8b379144-9a9d-4fc1-897a-a7c0771f8ebb",
      "musicBrainz Album Id": "fdabb997-b984-4097-bd3b-89fafd5e2e75",
      "ufid": "http://musicbrainz.org\u0000ef71afb6-5e51-41df-999b-9e7c7306063a",
      "musicBrainz Artist Id": "66c662b6-6e2f-4930-8610-912e24c63ed1",
      "musicBrainz Album Artist Id": "66c662b6-6e2f-4930-8610-912e24c63ed1",
      "musicBrainz Release Group Id": "d3bc1a64-7561-3787-b680-0003aa50f8f1",
      "musicBrainz Release Track Id": "cf05ab29-27c7-47ed-9450-9f4de676cded",
      "acoustid Fingerprint": "AQADtE...oIIYgRUChBhABIEeWAA0AQR4hSDg",
      "iTunNORM": " 00001AE7 00001AE7 00004340 00004340 00000000 00000000 0000869A 0000869A 00000000 00000000"
    }
  },
  "file": {
    "size": 10257613,
    "created": "2019-06-12T18:50:37.5527895+02:00",
    "modified": "2019-06-12T18:50:37.5527895+02:00",
    "accessed": "2023-02-14T09:21:29.2261032+01:00",
    "path": "music/album/AC_DC/Back in Black",
    "name": "06 - Back in Black.mp3"
  }
}

ChptFmtNative format (also CHPT_FMT_NATIVE)

The ChptFmtNative format was initially used in mp4v2, but never fully specified. However, there is a loose spec here.

Example

## artist: Cœur de pirate
## album: Blonde
##
## total-duration: 00:38:37.034
##
00:00:00.000 Lève les voiles
00:01:12.709 Adieu
00:03:40.346 Danse et danse
00:06:50.775 Golden Baby
00:09:57.772 Ava
00:13:14.657 Loin d'ici
00:15:58.494 Les amours dévouées
00:18:26.443 Place de la république
00:22:37.664 Cap diamant
00:25:20.925 Verseau
00:29:14.722 Saint-Laurent
00:32:29.519 La petite mort
00:36:19.140 Hôtel amour

ffmetadata format

The ffmetadata format was designed for ffmpeg, a versatile media encoder and it is specified here.

Example

;FFMETADATA1
title=Back in Black
artist=AC/DC
track=6/10
album=Back in Black
disc=1/1
date=1986
genre=Hard Rock
TBPM=0
compilation=0
TMED=CD
language=eng
album_artist=AC/DC
artist-sort=AC/DC
publisher=Atlantic
TIPL=arranger
TDOR=1980-07-25
encoded_by=LAME 3.99.5
Script=Latn
Artist Credit=AC/DC
ALBUMARTISTSORT=AC/DC
CATALOGNUMBER=16018-2

Commands

The features of tone are divided by commands. You can dump information or tag a file and so on. To do so, run

tone <command> <parameters>

Example:

tone dump "my-audio-file.mp3"

global options

There are some global options, that can be used to change the behaviour of the file iterator. These options apply for all commands:

dump - show audio metadata

The dump command can be used to show metadata for a wide variety of audio files. You can either specify a single file or a directory, which will be traversed recursively. Several output --format options are supported. By default a terminal user interface library is used, but it is also possible to use json or ffmetadata.

Options reference

tone dump --help           
USAGE:
    tone dump [input] [OPTIONS]

EXAMPLES:
    tone dump --help
    tone dump input.mp3
    tone dump audio-directory/ --include-extension m4b --format ffmetadata --include-property title --include-property artist

ARGUMENTS:
    [input]    Input files or folders

OPTIONS:
    -h, --help                 Prints help information
        --debug                                       
        --force                                       
        --include-extension                           
        --order-by                                    
        --limit                                       
        --include-property                            
        --format                                      
        --query 

tag - modify audio metadata

The tag command can be used to modify audio metadata. Besides using predefined parameters like --meta-album it is also possible to add or modify custom fields via --meta-additional-field, e.g. --meta-additional-field "©st3=testing" as well as pictures or chapters.

IMPORTANT: tone is meant to be used on local block storage and may cause unwanted side effects when trying to modify tags on network or other remote storage. Please ensure you have a backup or copy files locally to change metadata. See #52 for details.

The --taggers option

The --taggers option allows you to specify a custom set or a different order of internal taggers (NOT input formats), which are gonna be applied. In most cases changing the order of the taggers does not make a huge difference, but fully understanding this option requires a bit of technical knowledge. Let's go through a use case to see what you can do with it.

Note: Internal taggers are applied in a sane order by default and not meant for beginners. Most of the time you don't need to change the order and this usually is for very specific experts use cases. So if you don't fully understand this option, just leave it as is.

Use case: re-tag sorttitle / sortalbum The following taggers are relevant for this use case:

Usually, the remove tagger is applied at last. If you provide --meta-remove-property=sorttitle, this ensures an existing value will really be removed after all taggers have been applied. The m4bfillup tagger will automatically generate sorttitle / sortalbum from movementname, movement and title / album if AND ONLY IF the current value is empty.

So if you change the movementname (e.g. Harray Potter to Harry Potter because of a typo), sorttitle / sortalbum will not be updated, because these fields already have a value. If you remove the sorttitle / sortalbum, it will not be auto-updated but only removed, since remove is applied after m4bfillup.

This can be solved by reordering the taggers:

tone tag harry-potter-1.m4b --taggers="remove,m4bfillup" --meta-movement-name="Harry Potter" --meta-remove-property="sortalbum" --meta-remove-property="sorttitle"

As you see, most of the time, you only care about one special tagger to be applied first or last. This is why tone has an option to add all remaining taggers to the list using a *:

tone tag harry-potter-1.m4b --taggers="remove,*" --meta-movement-name="Harry Potter" --meta-remove-property="sortalbum" --meta-remove-property="sorttitle"

The following taggers are available at the moment (names can be applied case-insensitive):

Equate The equate tagger can be used to set a field by referencing another, e.g. when you would like to set the AlbumArtist equal to the Artist, you could use --meta-equate "artist,albumartiest".

The --meta-equate works like this:

<details> <summary>Field reference</summary> ``` Album AlbumArtist Artist Bpm ChaptersTableDescription Composer Comment Conductor Copyright Description DiscNumber DiscTotal EncodedBy EncoderSettings EncodingTool Genre Group ItunesCompilation ItunesMediaType ItunesPlayGap LongDescription Lyrics Part Movement MovementName Narrator OriginalAlbum OriginalArtist Popularity Publisher PublishingDate PurchaseDate RecordingDate SortTitle SortAlbum SortArtist SortAlbumArtist SortComposer Subtitle Title TrackNumber TrackTotal ``` </details>

Options reference

tone tag --help                                                                                                                                                                     
USAGE:
    tone tag [input] [OPTIONS]

EXAMPLES:
    tone tag --help
    tone tag input.mp3 --meta-title "a title"
    tone tag --debug --auto-import=covers --meta-additional-field ©st3=testing input.m4b --dry-run
    tone tag --auto-import=covers --auto-import=chapters --path-pattern="audiobooks/%g/%a/%s/%p - %n.m4b" --path-pattern="audiobooks/%g/%a/%z/%n.m4b" audiobooks/ --dry-run
    tone tag input.mp3 --script musicbrainz.js --script-tagger-parameter e2310769-2e68-462f-b54f-25ac8e3f1a21

ARGUMENTS:
    [input]    Input files or folders

OPTIONS:
    -h, --help                               Prints help information
        --debug                                                     
        --force                                                     
        --include-extension                                         
        --order-by                                                  
        --limit                                                     
    -y, --assume-yes                                                
        --dry-run                                                   
        --taggers                                                   
        --script                                                    
        --script-tagger-parameter                                   
        --prepend-movement-to-description                           
        --meta-artist                                               
        --meta-album                                                
        --meta-album-artist                                         
        --meta-bpm                                                  
        --meta-chapters-table-description                           
        --meta-comment                                              
        --meta-composer                                             
        --meta-conductor                                            
        --meta-copyright                                            
        --meta-description                                          
        --meta-disc-number                                          
        --meta-disc-total                                           
        --meta-encoded-by                                           
        --meta-encoder-settings                                     
        --meta-encoding-tool                                        
        --meta-genre                                                
        --meta-group                                                
        --meta-itunes-compilation                                   
        --meta-itunes-media-type                                    
        --meta-itunes-play-gap                                      
        --meta-long-description                                     
        --meta-part                                                 
        --meta-movement                                             
        --meta-movement-name                                        
        --meta-narrator                                             
        --meta-original-album                                       
        --meta-original-artist                                      
        --meta-popularity                                           
        --meta-publisher                                            
        --meta-publishing-date                                      
        --meta-purchase-date                                        
        --meta-recording-date                                       
        --meta-sort-album                                           
        --meta-sort-album-artist                                    
        --meta-sort-artist                                          
        --meta-sort-composer                                        
        --meta-sort-title                                           
        --meta-subtitle                                             
        --meta-title                                                
        --meta-track-number                                         
        --meta-track-total                                          
        --meta-additional-field                                     
        --auto-import                                               
        --meta-chapters-file                                        
        --meta-cover-file                                           
        --meta-tone-json-file                                       
    -p, --path-pattern                                              
        --path-pattern-extension                                    
        --meta-equate                                               
        --meta-remove-additional-field                              
        --meta-remove-property

filename to tag via --path-pattern / -p

It is possible to use the tag subcommand with multiple --path-pattern arguments to read metadata from path names. Please note:

short hands

All short hands are configured to match non-slash (/) or part numbers ([0-9-.IVXLCDM]+).

Custom scripted taggers (experimental)

With tone v0.0.4 it is possible to use scripted taggers. Long story short: You can now use JavaScript to hook into the tagging mechanism and write your own extensions for tone.

Note: script support is limited to a specific subset of JavaScript and does not support every feature that is supported in modern browsers. If you would like to know more, take a look at jint

create a javascript file

Lets say you would like to consume an external API to set some tags, in our example we use http://musicbrainz.org to tag the audiobook Harry Potter and the Philosophers Stone :

// musicbrainz.js
function musicbrainz(metadata, parameters) {
  // e2310769-2e68-462f-b54f-25ac8e3f1a21
  var id = parameters.length > 0 ? parameters[0] : null;
  if(id === null) {
    console.log("Please provide a valid musicbrainz release id to use this tagger");
    return;
  }
  var url = "http://musicbrainz.org/ws/2/release/" + id + "?inc=recordings&fmt=json";
  console.log("fetching url:", url);
  
  // User-Agent header is required for musicbrainz to provide a response
  var json = tone.Fetch(url, {
      headers: {
        'User-Agent': 'Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4'
      }
  });
  // you could also read a text file in the base path of the audio file
  // json = tone.ReadTextFile(metadata.BasePath + "/musicbrainz.json");
  
  var result = JSON.parse(json);
  metadata.Title = result.title;
  console.log("new title:", result.title);

  if('barcode' in result) {
    metadata.AdditionalFields["ISBN"] = result.barcode;
    console.log("new barcode:", result.barcode);
  }
}

// register your function name as tagger
tone.RegisterTagger("musicbrainz");
run your tagger

Now you can use the --script parameter to load your custom JavaScript and furthermore the --script-tagger-parameter to provide the parameters array used in the tagger function. If you would like to prevent the default tone taggers to be applied, you can also limit the them to your scripted one via --taggers=musicbrainz.

tone tag "harry-potter-1.m4b" --taggers="musicbrainz" --script="musicbrainz.js" --script-tagger-parameter="e2310769-2e68-462f-b54f-25ac8e3f1a21"
Tagger API

To get an overview of fields, that can be accessed or modified via the metadata object, you should take a look at the IMetadata interface. Not all of them are primitive types, but there are API at least some helper methods to overcome this problem (more are planned):

MethodDescriptionNotes
tone.RegisterTagger(string functionName):voidRegisters a custom tagger function with functionName-
tone.Fetch(string url [, object? options]):stringFetches remote url contents using options inspired by original fetch APIOnly a small subset of options is implemented (mainly method, body and headers)
tone.Download(string url, string destinationPath [, object options]):boolDownloads a remote <url> to <destinationFile> using options inspired by original fetch APIReturns true on success, false on error<br/>Directories will be created recursively<br/>Files are not overwritten by default
tone.ReadTextFile(string path):stringReads a text file completely as string-
tone.WriteTextFile(string path, string content):voidWrites text to a file (create file if not exists, overwrite contents)-
tone.AppendTextFile(string path, string content):voidAppends text to a file (create file if not exists, append contents)-
tone.LimitByteLength(string message, int maxLength):stringLimites text to byte length (not char length)-
tone.CreateDateTime(string dateString):DateTimeCreates a DateTime value from stringe.g. for metadata.PublishingDate
tone.CreateTimeSpan(number milliseconds):TimeSpanCreates a TimeSpan value from stringe.g. for metadata.TotalDuration
tone.CreatePicture(string path):PictureInfoCreates a PictureInfo value from a path (refer to Download)for metadata.EmbeddedPictures
tone.CreateChapter(string title, number startMs, number lengthMs [, PictureInfo picture, string subtitle, string uniqueID]):ChapterInfoCreates a ChapterInfofor metadata.Chapters

Development

Setup environment

To build tone, you need the dotnet SDK with at least version 6.0. After this you check out the code via git and that's it.

# check dotnet version > 6.0
dotnet --version

# clone git repository
git clone https://github.com/sandreas/tone.git

# change into main solution
cd tone

# restore nuget packages
dotnet restore

# build solution
dotnet build

First test and using an IDE

Run tone without params to test your environment

# change to project tone/tone
cd tone 

# run the project
dotnet run

# output should be something like:
# USAGE:
#     tone [OPTIONS] <COMMAND>
# ...

If this works, you can now open the tone.sln file in the main directory with your favorite IDE (e.g. Visual Studio, JetBrains Rider or Visual Studio Code)

Publish a binary

You can now also publish tone as single binary. Before you build an executable binary, you have to choose a valid runtime identifier (RID) for the operating system and the architecture you would like to build for. Valid RID values are for example win-x64, linux-x64, osx-x64 and so on

Refer to the official RID catalog and please ensure, your RID is supported by the according dotnet version (older versions may not support modern runtime ids)

The most common variants are probably these:

# windows (x64)
dotnet publish tone/tone.csproj --runtime "win-x64" --framework net6.0 -c Release -p:PublishSingleFile=true --self-contained true -p:PublishReadyToRun=true -p:PublishTrimmed=true -o "dist/tone"

# macOS (x64)
dotnet publish tone/tone.csproj --runtime "osx-x64" --framework net6.0 -c Release -p:PublishSingleFile=true --self-contained true -p:PublishReadyToRun=true -p:PublishTrimmed=true -o "dist/tone"

# macOS (arm64)
dotnet publish tone/tone.csproj --runtime "osx-arm64" --framework net6.0 -c Release -p:PublishSingleFile=true --self-contained true -p:PublishReadyToRun=true -p:PublishTrimmed=true -o "dist/tone"

# linux (x64)
dotnet publish tone/tone.csproj --runtime "linux-x64" --framework net6.0 -c Release -p:PublishSingleFile=true --self-contained true -p:PublishReadyToRun=true -p:PublishTrimmed=true -o "dist/tone"

known issues

The following issues are known, part of an external library and already reported: