Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FLAC Encoder, metadata support #14

Closed
varyoo opened this issue Jul 19, 2016 · 11 comments
Closed

FLAC Encoder, metadata support #14

varyoo opened this issue Jul 19, 2016 · 11 comments
Milestone

Comments

@varyoo
Copy link

varyoo commented Jul 19, 2016

I want to update some metadata blocks (Vorbis comments) and re-encode the Stream.
Is it planned? If not, any hints on how to do it (within the current API design)?

I love how close this library is to the FLAC specifications, I enjoyed using it, thank you.

mewmew added a commit that referenced this issue Jul 20, 2016
Support for encoding metadata has been added.

Proper support for encoding audio samples is yet to be
implemented. For now, simply copy the original encoded
audio data from the source FLAC file.

The API mirrors that of image.Encode.

Updates #14.
@mewmew
Copy link
Member

mewmew commented Jul 20, 2016

Hi @varyoo!

I want to update some metadata blocks (Vorbis comments) and re-encode the Stream.
Is it planned? If not, any hints on how to do it (within the current API design)?
I love how close this library is to the FLAC specifications, I enjoyed using it, thank you.

I'm really glad to hear you've enjoyed playing with the library so far! My brother and I had a lot of fun developing it.

The intention is to support both encoding and decoding, and to provide high-level access to these features through the Azul3D audio interface. As of commit 306014a, preliminary encoding support has been added, using an API which mirrors image.Encode.

// Encode writes the FLAC audio stream to w.
func Encode(w io.Writer, stream *Stream) error

Encoding of metadata blocks is now supported. Proper support for encoding audio samples is yet to be implemented. For now, the encoded samples of the original FLAC stream are copied verbatim to the output stream.

For an example of how to update metadata blocks, please take a look at the following code hosted at play.golang.org, which decodes the testdata/love.flac file, updates the vendor and adds a tag to the Vorbis comment metadata block.

Add custom Vorbis comment

P.S. Anyone who wishes to tackle implementing proper support for encoding audio samples is welcome to do so :)

mewmew added a commit that referenced this issue Jul 20, 2016
@varyoo
Copy link
Author

varyoo commented Jul 20, 2016

I'm amazed, thanks for support!

Vorbis comments changes seems to be working. But I don't get how the length written in the block header can follow tags changes.

From what I understand:

  • It is not possible to add or remove blocks, (I tried removing padding blocks). Prehaps []*meta.Block should be wrapped in a type with add and remove methods?
  • All blocks are re-encoded from the memory, regardless if changes were made.

Are we going further? Did I get something wrong? What is your point of view on these points?

@karlek
Copy link
Contributor

karlek commented Jul 21, 2016

@varyoo
We haven't made an API for removing metadata blocks yet. However it is possible, even though it's quite ugly.

It involves linear search and so called slice tricks.

// First you have to find the block you want to remove, for instance padding as you mentioned earlier.
index := -1 
for i, block := range stream.Blocks {
    if block.Type == meta.TypePadding {
        index = i
        break
    }
}
// Then delete the specific index in the slice.
if index != -1 {
    stream.Blocks = append(stream.Blocks[:index], stream.Blocks[index+1:]...)
}

Link for play.golang.org

@mewmew
Copy link
Member

mewmew commented Jul 21, 2016

But I don't get how the length written in the block header can follow tags changes.

Thanks for pointing this out. It's a bug and should be fixed.

@mewmew
Copy link
Member

mewmew commented Jul 21, 2016

It is not possible to add or remove blocks, (I tried removing padding blocks). Prehaps []*meta.Block should be wrapped in a type with add and remove methods?

We haven't made an API for removing metadata blocks yet. However it is possible, even though it's quite ugly.

I would like to propose that we treat the FLAC stream, metadata block and frame data structures in a similar fashion as one would treat an AST. They will basically contain the semantically relevant information required to recreate the a FLAC file containing the same metadata and audio samples. These data structures may be arbitrarily manipulated by the user prior to encoding, and even created afresh from memory (possibly produced by hand, procedurally generated, or generated by tools such as Go-fuzz). Keeping with the idea that we may not foresee all possible use cases, and cannot create an API which would satisfy all future needs, we should instead focus on a separation of concern, and keep these data structures clean. To facilitate manipulation of metadata (and audio samples), a set of utility packages may be written, which could provide convenience functions for adding and removing metadata blocks (basically using append to add, and re-slicing to remove metadata blocks as suggested in #14 (comment)). This metautil package (or whatever it may be called), could also include functions which facilitate common use-cases, such as adding, modifying and removing comments from Vorbis comment metadata blocks.

For reference, the idea of separation the data structures from a set of utility packages which manipulate said data structures is also being evaluated by the dot project: mewspring/dot#1 (comment)

@varyoo, @karlek What do you think?

@varyoo
Copy link
Author

varyoo commented Jul 22, 2016

We haven't made an API for removing metadata blocks yet. However it is possible, even though it's quite ugly.

Yes, but, for example meta.Header have a IsLast bool field, if I remove the last Block, the Block slice is corrupted.

To facilitate manipulation of metadata (and audio samples), a set of utility packages may be written, which could provide convenience functions for adding and removing metadata blocks

Isn't it procedural vs OOP?

@mewmew
Copy link
Member

mewmew commented Jul 22, 2016

Yes, but, for example meta.Header have a IsLast bool field, if I remove the last Block, the Block slice is corrupted.

Good catch. Will have to think about how to solve this in a clean manner.

@mewmew
Copy link
Member

mewmew commented Jul 22, 2016

Yes, but, for example meta.Header have a IsLast bool field, if I remove the last Block, the Block slice is corrupted.

This has been fixed as of commit c6cd3f7.

@mewmew
Copy link
Member

mewmew commented Jul 22, 2016

The following example hosted at play.golang.org utilizes a metadata utility package to remove padding metadata blocks, add Vorbis comments and append a custom metadata block. Providing the relevant extract below.

func updateMetadata(stream *flac.Stream) {
    // Remove padding metadata blocks.
    metautil.RemoveBlockType(stream, meta.TypePadding)

    // Add metadata comments.
    comment := metautil.NewCommentBlock("foobar")
    comment.Add("testing", "hello world :)")
    metautil.AppendBlockBody(stream, comment.Body)

    // Add a custom metadata block.
    block := &meta.Block{
        Header: meta.Header{
            Type: meta.TypeApplication,
        },
        Body: &meta.Application{
            ID:   0xDEADBEEF,
            Data: []byte("Arbitrary data.\n"),
        },
    }
    metautil.AppendBlock(stream, block)
}

Note that the metautil package is a proof of concept to experiment with how an API may look, which separates the data structures of the FLAC stream, the metadata blocks and the audio frames, from manipulation of said data structures.

@varyoo Feel free to suggest possible improvements, or fork the package to play on your own as you may have a concrete use-case in mind :)

Input FLAC stream metadata

$ metaflac --list love.flac
love.flac:METADATA block #0
love.flac:  type: 0 (STREAMINFO)
love.flac:  is last: false
love.flac:  length: 34
love.flac:  minimum blocksize: 4096 samples
love.flac:  maximum blocksize: 4096 samples
love.flac:  minimum framesize: 14 bytes
love.flac:  maximum framesize: 4926 bytes
love.flac:  sample_rate: 44100 Hz
love.flac:  channels: 2
love.flac:  bits-per-sample: 16
love.flac:  total samples: 40900
love.flac:  MD5 signature: bdf6f7d31f77cb696a02b2192d192a89
love.flac:METADATA block #1
love.flac:  type: 3 (SEEKTABLE)
love.flac:  is last: false
love.flac:  length: 18
love.flac:  seek points: 1
love.flac:    point 0: sample_number=0, stream_offset=0, frame_samples=4096
love.flac:METADATA block #2
love.flac:  type: 4 (VORBIS_COMMENT)
love.flac:  is last: false
love.flac:  length: 40
love.flac:  vendor string: reference libFLAC 1.3.1 20141125
love.flac:  comments: 0
love.flac:METADATA block #3
love.flac:  type: 1 (PADDING)
love.flac:  is last: true
love.flac:  length: 8192

Output FLAC stream metadata

$ metaflac --list foo.flac
foo.flac:METADATA block #0
foo.flac:  type: 0 (STREAMINFO)
foo.flac:  is last: false
foo.flac:  length: 34
foo.flac:  minimum blocksize: 4096 samples
foo.flac:  maximum blocksize: 4096 samples
foo.flac:  minimum framesize: 14 bytes
foo.flac:  maximum framesize: 4926 bytes
foo.flac:  sample_rate: 44100 Hz
foo.flac:  channels: 2
foo.flac:  bits-per-sample: 16
foo.flac:  total samples: 40900
foo.flac:  MD5 signature: bdf6f7d31f77cb696a02b2192d192a89
foo.flac:METADATA block #1
foo.flac:  type: 3 (SEEKTABLE)
foo.flac:  is last: false
foo.flac:  length: 18
foo.flac:  seek points: 1
foo.flac:    point 0: sample_number=0, stream_offset=0, frame_samples=4096
foo.flac:METADATA block #2
foo.flac:  type: 4 (VORBIS_COMMENT)
foo.flac:  is last: false
foo.flac:  length: 40
foo.flac:  vendor string: reference libFLAC 1.3.1 20141125
foo.flac:  comments: 0
foo.flac:METADATA block #3
foo.flac:  type: 4 (VORBIS_COMMENT)
foo.flac:  is last: false
foo.flac:  length: 40
foo.flac:  vendor string: foobar
foo.flac:  comments: 1
foo.flac:    comment[0]: testing=hello world :)
foo.flac:METADATA block #4
foo.flac:  type: 2 (APPLICATION)
foo.flac:  is last: true
foo.flac:  length: 20
foo.flac:  application ID: deadbeef
foo.flac:  data contents:
Arbitrary data.

@varyoo
Copy link
Author

varyoo commented Jul 24, 2016

I get the idea now, it means that stream.Encode should check if everything is compliant with the FLAC specifications, so it never produce a corrupted file.

This is what I needed, I understand this package is part of a bigger project, thank you.

@mewmew
Copy link
Member

mewmew commented Jul 25, 2016

This is what I needed, I understand this package is part of a bigger project, thank you.

Lovely to hear. When you are happy with the implementation, would you mind closing this issue? We can open a dedicated issue for the future ambition of implementing encoding support for audio samples. This issue will be dedicated to metadata blocks.

@mewmew mewmew changed the title FLAC Encoder FLAC Encoder, metadata support Jul 25, 2016
@mewmew mewmew added this to the v1.1 milestone Jul 25, 2016
@karlek karlek closed this as completed Sep 13, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants