melchor629 / node-flac-bindings

Nodejs bindings to libFLAC
ISC License
17 stars 1 forks source link

Support for foreign metadata RIFF chunks #49

Open kmturley opened 11 months ago

kmturley commented 11 months ago

Wave files can contain unofficial metadata, such as Sampler Chunk - "smpl": https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl

These are used for audio looping players and samplers avoiding to loading multiple samples.

I have one such file here: https://github.com/studiorack/basic-harmonica/blob/bf42d5bab7470cc201e3c4b6dee7925b19db6bff/samples/harmonica_1.wav

and a flac file converted using the official flac command line tool: flac harmonica_1.wav --keep-foreign-metadata

When inspecting with exiftool it shows the RIFF metadata was copied:

$ exiftool harmonica_1.flac -G1

[RIFF]          Encoding                        : Microsoft PCM
[RIFF]          Num Channels                    : 1
[RIFF]          Sample Rate                     : 48000
[RIFF]          Avg Bytes Per Sec               : 96000
[RIFF]          Bits Per Sample                 : 16
[RIFF]          Manufacturer                    : 0
[RIFF]          Product                         : 0
[RIFF]          Sample Period                   : 20833
[RIFF]          MIDI Unity Note                 : 64
[RIFF]          MIDI Pitch Fraction             : 0
[RIFF]          SMPTE Format                    : none
[RIFF]          SMPTE Offset                    : 00:00:00:00
[RIFF]          Num Sample Loops                : 1
[RIFF]          Sampler Data Len                : 0
[RIFF]          Sampler Data                    : (Binary data 20 bytes, use -b option to extract)
[RIFF]          Unshifted Note                  : 64
[RIFF]          Fine Tune                       : 0
[RIFF]          Gain                            : 0
[RIFF]          Low Note                        : 0
[RIFF]          High Note                       : 127
[RIFF]          Low Velocity                    : 0
[RIFF]          High Velocity                   : 127
[RIFF]          Acidizer Flags                  : One shot
[RIFF]          Root Note                       : High C
[RIFF]          Beats                           : 2
[RIFF]          Meter                           : 4/4
[RIFF]          Tempo                           : 0
[RIFF]          Comment                         : Recorded on 7/10/2022 in Edison.
[RIFF]          Software                        : FL Studio 20
[Composite]     Duration                        : 0.87 s

I have tried all the metadata types in your bindings, but none appear to support the RIFF chunks:

new flac.FileEncoder({
  file: output || 'out.flac',
  compressionLevel: 9,
  metadata: [new metadata.ApplicationMetadata(), new metadata.PaddingMetadata(), new metadata.SeekTableMetadata(), new metadata.UnknownMetadata(), new metadata.VorbisCommentMetadata()]
})

How can I get this to work? Happy to submit a PR

Using flac-bindings v2.7.2 as I want my library to have backwards compatibility for CommonJS.

melchor629 commented 11 months ago

Hello! This is the first time I see this feature of the flac tool, and I never read about it before. I will try my best to help you.

First, what you want is something you can accomplish using the available APIs (either through streams or using the metadata API level 1 or level 2). I know the metadata APIs are a bit complicated so that's why added the metadata array for the stream encoders (FileEncoder and StreamEncoder). I will focus on this API.

The metadata property in the constructor just tells the encoder to write those flac metadata blocks into the output stream/file. In your example, you are adding several empty metadata blocks to the output file (not sure if libflac sees this and removes them or something).

For that case, the idea is to read the RIFF chunks beforehand, store each chunk in an array of buffers (the whole chunk) and then write them as application metadata with id riff (see this). The only thing I don't know for sure if it just needs to be metadata block for all chunks or one metadata block for each chunk. My guess goes to the one for each.

Imagine that you have a library that reads all RIFF chunks somehow using an async iterator, and puts the raw chunk in a property called raw. The idea would look like this:

// read riff chunks
const pathToTheFile = '...'
const riffChunks = []
for await (const riffChunk of readRiffChunks(pathToTheFile)) {
  const riffApplicationMetadata = new flac.metadata.ApplicationMetadata()
  riffApplicationMetadata.id = 'riff'
  riffApplicationMetadata.data = riffChunk.raw
  riffChunks.push(riffApplicationMetadata)
}

// create encoder with the metadata block
const flacEncoder = new flac.FileEncoder({
  file: output || 'out.flac',
  compressionLevel: 9,
  metadata: riffChunks,
})

Note that I could not test that properly, don't have anything to try this fine. This "code" gives you something like this: out.flac hex

If you would like to inspect further, I made a quick CodeSandbox https://codesandbox.io/p/sandbox/lively-bash-gw9jnh?file=%2Findex.js%3A76%2C38

I could not find any npm package that I liked to use for this sample (I have to say I just spend 10 minutes looking) so I made the code to read the chunks manually. Is not that complicated I have to say hehe. I hope this helps you find the solution to your issue.

On the other hand, I don't quite like the idea to have this kind of code around, which is not from flac API directly. I see the utility of it but I don't many people using it for their daily use. If you would like to keep this for your project, for me it's fine.

Anyway, I also don't mind to have it anyways if the code does not use any external dependency. Like an utils package inside the library 🤔 I would accept the PR. The only issue is I don't have time to maintain two versions for the library (2.x and 3.x), so it will only go to 3.x, sorry for that tho.

Thanks for raising the issue :)

kmturley commented 11 months ago

Thanks for your thoughtful comment and useful advice/examples.

Comparing the two files you generated in your codebox:

exiftool ./test/out.flac -G1
Screenshot 2023-10-22 at 3 47 07 PM

They appear to be different metadata. I will spend some time on this over the next few days and then report back my findings!

melchor629 commented 11 months ago

Hello again! At least something is being read with that tool! That's something hehe

Good luck with the investigations. I am looking forward to your results. If you need some help, I may be able to assist you.

Also, take a look at flac's source code, something I did not think of yesterday to check: https://github.com/xiph/flac/blob/master/src/flac/foreign_metadata.c#L210 Maybe you see something useful in it.