Skip to content

Using metadata APIs with the bindings

Melchor Garau Madrigal edited this page Apr 2, 2019 · 6 revisions

First, selecting the API

There's three types of metadata to manipulate metadata on flacs files with the FLAC API:

And then, common to all, the metadata API that manipulates the objects itself, not the files.

What offers the metadata0 API? It's an easy way to only access to the StreamInfo, VorbisComment, CueSheet and Pictures metadata blocks.

What offers the metadata1 API? It's the easy way to read and write all metadata blocks from a file using an Iterator. Is quite efficient and uses less memory than metadata2 but is slow.

What offers the metadata2 API? A complete way to read and write all metadata blocks from a file using an Iterator. Reads all blocks into memory and operates in memory until save. Is faster than level 1 doing operations.

See this link for a more precise information about the three APIs.

There is an example at the end of this guide. I recommend you first to read the guide and then see the example.

Note The bindings tries to bind everything, but there are some functions that cannot be bound because of the arguments they need (are difficult to implement or impossible). Pay attention to the documentation of the bindings (available on this Wiki).

Note 2 The documentation is not really a fully documentation, only tries to show what is bound and the differences between the C API and the node bindings. You will always find links to the original C API documentation that will explain better than me how the API works.

Note 3 See how the C structs and the Javascript objects are mapped, keep it in a separate page to see it while you read this guide.

Metadata0

You can get metadata blocks from the file quickly with that:

const flac = require('flac-bindings');
let streamInfo = flac.api.metadata0.getStreaminfo('a file.flac');
let tags = flac.api.metadata0.getTags('a file.flac');
let cuesheet = flac.api.metadata0.getCuesheet('a file.flac');
let picture = flac.api.metadata0.getPicture('a file.flac', -1, null, null, -1, -1, -1, -1);

Easy as that.

Metadata1

With this iterator, things get interesting. First you must create an instance of the iterator:

let it = flac.SimpleIterator();

After that, you must initialize the iterator with a file and some options:

it.init('a file.flac', isReadOnly, shouldPreserveStats);

Note Is a good practice to check whether this functions return true. That means everything is okay. If something goes wrong you can use SimpleIterator.status() to know what happen.

To see how it works the last two parameters, you shold see the flac documentation.

If everything goes right, you can now iterate over the metadata blocks and manipulate them:

it.next(); //Advances its pointer to the next block
it.prev(); //Backs its pointer to the previous block
let blockType = it.getBlockType(it); //Gets the block type of the block to which the iterator is pointing at
let block = it.getBlock(it); //Gets the block to which the iterator is pointing at

You can even use for(... of ...) to iterate over:

for(let block of it) {
    console.log(block);
}

You can use flac.api.metadata.MetadataTypeString to get a string representation of the type of the metadata block.

The block you get is yours (according to the FLAC API). So you can modify it using the metadata API or modifing the attributes of the block directly. But you must delete it when it is not needed anymore with the method seen in Metadata0.

To store any changes of a metadata block, you must call:

it.set_block(block, true);

The true value is to indicate the flac API to use padding blocks if available to be as efficient as he can.

You can insert blocks created using the metadata API (click the link after read this section, if you want) in two ways:

let newBlock = new flac.metadata.VorbisCommentMetadata();
 ... //Make some stuff using the metadata API and/or directly to the newBlock object
it.setBlock(newBlock, true); //Replaces the current block (at which the iterator points)
it.insertBlockAfter(newBlock, true); //Inserts the now block after the current one, then the iterator will point to the inserted block

Last thing is how to delete a block:

it.deleteBlock(true); //Deletes the current block (at which the iterator points).

Note You cannot delete the STREAMINFO block!

This replaces the current block into a PADDING block (because of that true at the end). If you pass a false, the block will be deleted and will rewrite all the flac file.

And thats all for the Metadata1 API.

Metadata2

Let's get done with metadata2. Before start coding, you must understand that in this level there are two objects to play with: the chain and the iterator. The chain is where you open the file and you write to the file, and also you can consolidate the padding blocks. The iterator is like the level 1 (metadata1) and you already know how to use it. In this API, everything you do is not saved directly to the file, is all in memory, so you must ensure to save the changes and to not modify the flac file by other ways while using this API.

Well, let's start creating the chain and initializing it with a file:

let chain = flac.Chain();
chain.read('a file.flac'); //Opens the file, reads all blocks into the memory and closes the file

Note Is a good practice to check whether this functions return true. That means everything is okay. If something goes wrong you can use Chain.status() to know what happen.

If everything goes ok, you can now create the iterator. But optionally you can reorder and merge the padding blocks to optimize them (Chain.sortPadding() and Chain.mergePadding()). Let's see how to create and initialize the iterator:

let it = new Iterator(); //Creates a new level2 iterator
it.init(chain); //Initializes it with the chain
//or simply
let it = chain.createIterator(); // :)

To move the iterator fowards and backwards:

it.next(); //Moves the pointer to the next block
it.prev(); //Moves the pointer to the previous block
let blockType = it.getBlockType(); //Gets the block type of the current block
let block = it.getBlock(it); //Gets the current block

Note In this metadata API the blocks you get are owned by the chain, that means that if you modify them, the changes will be reflected directly to the chain and you don't need to save it back into the iterator, as in metadata1 does.

You can even use the for(... of ...) to iterate over the blocks:

for(let block of it) { ... }
for(let block of chain.createIterator()) { ... }

If you want to add or replace a block, first you must create and modify it with the metadata API or directly modifying the attributes, and then you can use this three methods:

it.insertAfter(block); //Inserts after the current block, the iterator will point into this new block
it.insertBefore(block); //Inserts before the current block, the iterator will point into this new block
it.setBlock(block); //Replaces the current block with a new one

Note The blocks, once added/replaced into the iterator, will be propiety of the chain. That means that you don't need to delete them, and any changes you do on them will be reflected directly in the chain. Note 2 You cannot insert STREAMINFO blocks. You cannot insert before the STREAMINFO block, this one will always be the first. You can only replace a STREAMINFO block with another STREAMINFO block. You cannot delete the STREAMINFO block.

To delete a block:

it.delete_block(true); //Deletes the block that the iterator points at. The iterator points to the padding block created, or just the previous one

The true value is to indicate the flac API to replace with a padding block. If false is provided, then it will be deleted and the iterator will point to the previous block. Note Any references to the block deleted will be invalid, so make sure that you don't use them anymore, otherwise node could crash or do wierd things.

Once done that, you can always reorder and/or merge padding blocks again:

chain.sortPadding();
chain.mergePadding();

Note Calling one or both methods will invalidate any iterator. Ensure that you don't have any iterator created before calling any of these methods.

And finally you can save all changes:

chain.write(usePadding, shouldPreserveStats);

To see how it works the last two parameters, you shold see the flac documentation.

Note All references to blocks that were owned by the chain will become invalid. Ensure to not use them anymore or node could crash.

Metadata

In general, most of the attributes of the C structs can be modified directly without the need of any function. But there's some others that they need to be modified using a function (a C setter). The mapping between C and Javascript tries to limitate the modification of these attributes when is told to not modify directly (I recommend you to see how the objects are mapped).

First, the objects must be created using the API, you cannot simply create a JS Object and pass it to the API, it won't let you go. When creating the object, you must tell the kind of it (see the possible types)

let block = new flac.api.metadata.VorbisCommentMetadata(); //Creates a new metadata block of type VorbisComment

The objects can be cloned:

let clonedBlock = block.clone();

To get things clear, when you read modify the object directly, it means:

let block = new flac.api.metadata.CueSheetMetadata();
block.mediaCatalogNumber = "ANUMBERFORTHECATALOG123";
block.leadIn = 120;
block.isCd = true;
  ...

It means access to the object directly and modify the attributes directly, because it is possible in almost all attributes. Remember, all changes done here will be reflected in the C struct counterpart, so you (nor the bindings internally) don't need to call any special methods to make effective this changes.

The below sections is to get clear with the functions of the metadata API, to know what you can do to modify the attributes that cannot be modified directly.

Application metadata objects

According to the API, you cannot modify the data attribute directly, you must use a function. Well, in the JS, you can do it, internally the function is called for you:

applicationBlock.data = newData; //data must be a buffer

SeekTable metadata objects

To add, delete or modify points in the seek table, you must use the following functions:

Read carefully how they work (on the C documentation) and how to use it (on the Wiki documentation).

VorbisComment metadata objects

A VorbisComment is a list of entries and a vendor entry. You cannot modify the entries directly, but the vendor string is possible to be modified directly (in JS).

To insert, delete, resize, replace the entries list, here is a list of functions available:

CueSheet metadata objects

In a CueSheet object, you can modify everything except the list of CueSheet tracks. In a CueSheet track, you can modify everything except the indices list.

To add, replace and delete tracks to the list you can use:

To add, replace and delete indices in the indices list of a track, first you must create a CueSheet track, modify what you want (and can) and then store the track in the CueSheet object, and then use:

At last, you can check if the CueSheet metadata object is valid with:

Picture metadata objects

The picture metadata attributes can be modified directly. The corresponding function in the C API will be called if needed.

You can check if the picture object is valid or not with:

An example

This is an example of how to use (a bit) the metadata1 API with the metadata API to iterate over the blocks of a FLAC file, append an Application block and modify the VorbisComment block. Is not a very complete example, but maybe it helps you a bit:

const flac = require('./index'); //You must use here require('flac-bindings'); This file is a test of mine
const fs = require('fs');
const Buffer = require('buffer').Buffer;

(cbk => {
    //Copy the original, only because this is a test, I don't really want to modify the file
    let rs = fs.createReadStream('original-file.flac');
    rs.pipe(fs.createWriteStream('test.flac'));
    rs.on('end', cbk);
})(() => {
    //When it is done copying, it will call this block of code
    let it = flac.api.SimpleIterator();
    it.init("test.flac", false, false);
    console.log(flac.api.SimpleIterator.SimpleIteratorStatusString[it.status()]);

    do {
        console.log(flac.api.format.MetadataTypeString[it.getBlockType()]);
        console.log(it.getBlock());
        console.log("");
    } while(it.next());

    it.prev();
    let obj = new flac.api.metadata.ApplicationMetadata();
    console.log(obj);
    obj.data = Buffer.alloc(16, 0xAF);
    it.insertBlockAfter(obj, true);

    it.prev();
    it.prev();

    obj = it.getBlock();
    console.log(obj.insertComment(obj.comments.length - 1, "DESCRIPTION=Maqui note"));
    console.log(it.setBlock(obj, true));
    console.log(obj); //You will notice here that the modifications to the block are visible in the console.log :)
});