Skip to content

Advanced Tagging

Benjamin Russell edited this page Jul 8, 2023 · 3 revisions

Advanced Tagging

The original TagLib project, TagLib# and this project all aim to provide an easy to use way to tag you media without having to worry about the details of the files or tagging formats you're using. Although node-taglib-sharp aims for maximum compatibility, there are some situations that just can't be handled with a single "tag" interface. If you're really serious about fine-tuning your music library, are writing software for someone who is, or just curious about the implementation of tagging formats, you'll eventually need to dig deeper than unified tagging.

One thing to keep in mind, each file format and tagging format is different. Although this guide does its best to describe patterns for interacting with tags, it may not cover every situation. The API Reference do a decent job of documenting specific methods, classes, etc.

Basic Structure

Let's start at the most basic: files have a Tag object and a Properties object.

The Tag object can either inherit directly from Tag and represent a single tag or inherit from CombinedTag and represent a collection of tags. CombinedTag objects can also contain nested CombinedTag objects.

The Properties object is essentially a collection of ICodec objects that describe the streams inside a file. For container files (eg, MKV, MPEG), there may be more than one codec object. For strictly audio files, there will usually be one codec object. In rare circumstances where the properties couldn't be reliably read, the collection of codecs may be empty. It's important to note properties are read-only on a file.

  • File
    • File.tag -> (Tag|CombinedTag)
      • File.tag.tags -> Tag[] *if CombinedTag
    • File.properties -> Properties
      • File.properties.codecs -> ICodec[]

A couple important things to remember:

  • A file can only contain one instance of a specific tag type
  • Files only support certain tag types, this is also enforced when adding tags in an advanced manner

"Sandwich" Files

"Sandwich" files are files that have tags at the beginning and/or end of the file. These tags are not defined as part of the specification of the file format and are considered "outside" the file. The term "sandwich" comes from the media part of the file being sandwiched by tags at the beginning and end of the file, like how slices of bread sandwich the meaty goodness of a sandwich. In TagLib#, this was called "NonContainer" files, presumably because container files were presumed to not have tags outside the file. Since this was found to be false with MPEG containers, the name was discarded for something that made more sense.

+-----------------+--------------------------------+---------+
| StartTags       | Media                          | EndTags |
+-----------------+--------------------------------+---------+

A sandwich file may have tags at the beginning of the file, end of the file or both. If the tag blocks were removed, the remaining media would be a perfectly acceptable media file on its own.

  • File
    • File.tag -> SandwichTag
      • File.tag.startTag -> StartTag
        • File.tag.startTag.tags -> Tag[]
      • File.tag.endTag -> EndTag
        • File.tag.endTag.tags -> Tag[]

You can control which end of a file a tag goes. This will be explained in more detail in the following section, but essentially: using File.getTag() or SandwichTag.createTag new tags are placed at the end of the file defined in the settings for the file type, using StartTag.createTag() or EndTag.createTag() new tags are placed directly in whichever tag you called.

Adding / Removing Tags

If your file type supports more than one type of tagging format, you can generally add and remove tags. There are a couple ways to accomplish this.

File.getTag

At the highest level, tags can be added or retrieved using File.getTag(tagType, create). Just specify a single tag type and whether or not to create the tag if it does not exist and you'll get a tag. Let's look at some examples.

In this first case, we have a WAV file that doesn't have any tags.

import {File, TagTypes, WavFileSettings} from "node-taglib-sharp";

WavFileSettings.defaultTags = TagTypes.None;
const file1 = File.createFromPath("path/to/my/file1.wav");
console.log(file1.tag.tagTypes);              // 0 -> TagTypes.None
// Note the file still has a tag object, even if the file is empty

// Getting a tag in an empty file with create=false returns undefined
let newTag1 = file1.getTag(TagTypes.Info, false);
console.log(newTag1);                         // [undefined]

// Getting a tag in an empty file with create=true creates the tag, adds
// it to the file in memory, and returns the new tag.
newTag1 = file1.getTag(TagTypes.MovieId, true);
console.log(newTag1.tagTypes);                // 128 -> TagTypes.MovieId
console.log(file.tag.tagTypes);               // 128 -> TagTypes.MovieId
console.log(file.tagTypes);                   // 128 -> TagTypes.MovieId

const movieIdTag = file1.getTag(TagTypes.MovieId, false);
console.log(movieIdTag.tagTypes);             // 128 -> TagTypes.MovieId
console.log(movieIdTag === newTag1);          // true

In this second case, we have an MP3 file that already has an ID3v2 tag in it.

import {File, TagTypes} from "node-taglib-sharp";

const file2 = File.createFromPath("path/to/my/file2.mp3");

// Getting a tag that already exists always returns the existing tag
const id3v2Tag = file2.getTag(TagTypes.Id3v2, true);
console.log(id3v2Tag.isEmpty());     // false

// Getting a tag that doesn't exist adds it to the file
const apeTag = file2.getTag(TagTypes.Ape, true);
console.log(apeTag.isEmpty());       // true
console.log(file.tagTypes);          // 12 -> TagTypes.Ape | TagTypes.Id3v2
console.log(file.tag.endTag.
// IMPORTANT NOTE: The new tag is empty!

You may have noticed in the first example, I set the default tags to TagTypes.None. It's important to remember that 1) loading a file will create whatever default tags are defined for the file type, and 2) creating new tags on sandwich files will create the tag at which ever end is preferred as defined in the settings for the file type.

CombinedTag.tags.push()? CombinedTag.tags.slice()???

Copying Existing Data Into New Tags

Clone this wiki locally