sandreas / 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.
https://pilabor.com
Apache License 2.0
410 stars 17 forks source link

Issue when enhancing `musicbrainz.js` script to incorporate chapters #60

Closed datenzar closed 6 months ago

datenzar commented 6 months ago

Hi,

I'm trying to get a single m4b file to be enhanced by chapter tag information based on MusicBrainz.

The JavaScript-enhancement looks quite promising, but currently I'm stuck (I guess with the deserialization of the chapters' list).

Here's the enhanced code:

// musicbrainz-chapters.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);
  }

// Code Addition: Start
  if ("media" in result) {
    // metadata.AdditionalFields["Media"] = result.media;
    console.log("new media:", result.media);

    if ("tracks" in result.media[0]) {
      var chapterList = [];
      // metadata.AdditionalFields["Media"] = result.media[0].tracks;
      console.log("tracks:", result.media[0].tracks);
      var start = 0;
      for (var i = 0; i < result.media[0].tracks.length; i++) {
        var track = result.media[0].tracks[i];
        var title = track.title;
        var length = track.length;

        // approach A
        // chapterList.push(tone.CreateChapter(title, start, length));

        // approach B
        chapterList.push({
          start,
          title,
          length,
          // optional
          subtitle: "",
        });

        start += length;
      }
      // FIXME: this is not working
      // Error: Object reference not set to an instance of an object.
      metadata.Chapters = chapterList;
    }
  }
// Code Addition: End
}

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

When calling tone via tone tag "harry-potter-1.m4b" --taggers="musicbrainz" --script="musicbrainz-chapters.js" --script-tagger-parameter="e2310769-2e68-462f-b54f-25ac8e3f1a21" I get an Error: Object reference not set to an instance of an object. message. I already tried multiple approaches (see commented code) but without success.

I'm not sure if I'm doing it wrong or if the functionality isn't implemented yet.

sandreas commented 6 months ago

@datenzar

You were close... Unfortunately, the metadata.Chapters cannot be fed with POJOs (JavaScript Objects) or just overwritten with a default JavaScript array. It's a C# IEnumerable containing C# Chapter Objects.

Here is the corrected example:

// musicbrainz-chapters.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);
  }

// Code Addition: Start
  if ("media" in result) {
    // metadata.AdditionalFields["Media"] = result.media;
    console.log("new media:", result.media);

    if ("tracks" in result.media[0]) {
      // metadata.AdditionalFields["Media"] = result.media[0].tracks;
      // remove existing chapters
      metadata.Chapters.Clear();
      var start = 0;
      for (var i = 0; i < result.media[0].tracks.length; i++) {
        var track = result.media[0].tracks[i];
        var title = track.title;
        var length = track.length;

        // Add new ones by using the tone API "CreateChapter"
        metadata.Chapters.Add(tone.CreateChapter(title, start, length));

        start += length;
      }
    }
  }
// Code Addition: End
}

// register your function name as tagger
tone.RegisterTagger("musicbrainz");
datenzar commented 6 months ago

Thanks a lot @sandreas!