n10v / id3v2

🎵 ID3 decoding and encoding library for Go
https://pkg.go.dev/github.com/bogem/id3v2/v2
MIT License
334 stars 50 forks source link

support CHAP frames #62

Closed takaishi closed 2 years ago

takaishi commented 3 years ago

Support to add and parse CHAP frames according to https://id3.org/id3v2-chapters-1.0 . Similar PullRequest already opened (https://github.com/bogem/id3v2/pull/40), but it looks like doesn't proceed. I respect it's commit and update in this PullRequest.

takaishi commented 3 years ago

@bogem Hello, could you review this PR?

beautifulentropy commented 3 years ago

@bogem Hello, could you review this PR?

Just passing by; excellent work on this!

takaishi commented 3 years ago

@bogem Hello, I added commit. Could you review again?

n10v commented 3 years ago

Also one important thing: did you test the MP3 file with written CHAPs via id3v2 in a major media player (e.g. Apple Music, WinAmp)? Does a player recognise such CHAPs? If yes, please send me a screenshot 🙏

takaishi commented 2 years ago

@bogem I'm so sorry for late, I added commits for your comment. And I added results of test chapters written by id3v2 package:

Also one important thing: did you test the MP3 file with written CHAPs via id3v2 in a major media player (e.g. Apple Music, WinAmp)? Does a player recognise such CHAPs? If yes, please send me a screenshot 🙏

I tried test in a Apple Music or QuickTime Player, but they may not support chapters. I don't know major player supports chapter, so I tested by 2 software ( Forecast and IINA ).

First, I wrote chapters to test.mp3 by following code:

Code to write 3 chapters to copied file from testdata/test.mp3 ```go package main import ( "fmt" "github.com/bogem/id3v2" "os" "time" ) func main() { if err := _main(); err != nil { fmt.Errorf(err.Error()) os.Exit(1) } } func _main() error { fmt.Println("AAA") f, err := os.Open("./test_copied.mp3") if err != nil { return err } defer f.Close() tag, err := id3v2.Open(f.Name(), id3v2.Options{Parse: true}) if tag == nil || err != nil { return fmt.Errorf("error while opening mp3 file: ", err) } defer tag.Close() cfs := []id3v2.ChapterFrame{ { ElementID: "0", StartTime: 0, EndTime: time.Duration(60000 * 1000000), StartOffset: 0, EndOffset: 0, Title: &id3v2.TextFrame{ Text: "chap0", Encoding: id3v2.EncodingUTF8, }, Description: &id3v2.TextFrame{ Text: "description0", Encoding: id3v2.EncodingUTF8, }, }, { ElementID: "1", StartTime: time.Duration(60000 * 1000000), EndTime: time.Duration(120000 * 1000000), StartOffset: 0, EndOffset: 0, Title: &id3v2.TextFrame{ Text: "chap1", Encoding: id3v2.EncodingUTF8, }, Description: &id3v2.TextFrame{ Text: "description1", Encoding: id3v2.EncodingUTF8, }, }, { ElementID: "2", StartTime: time.Duration(120000 * 1000000), EndTime: time.Duration(180000 * 1000000), StartOffset: 0, EndOffset: 0, Title: &id3v2.TextFrame{ Text: "chap2", Encoding: id3v2.EncodingUTF8, }, Description: &id3v2.TextFrame{ Text: "description2", Encoding: id3v2.EncodingUTF8, }, }, } for _, cf := range cfs { tag.AddChapterFrame(cf) } if err := tag.Save(); err != nil { return err } return nil } ```

Second, I opened mp3 file by Forecast that is chapter editor of mp3. It has 3 chapters with same start time, duration and title.

image

Finally, I played mp3 file by IINA because IINA supports chapters. In following gif movie, I select chapter 1 and 2, player jumps selected point (Sorry menu text is japanese)

Nov-21-2021 11-43-03

beautifulentropy commented 2 years ago

Super excited for this. Thanks for all the hard work!

n10v commented 2 years ago

Released in v2.1.2

umputun commented 8 months ago

@takaishi - I have tried to add chapters, and it worked the same way as you show in Forecast screenshot above. The issue (well, at least I think it is an issue) is that players don't show chapters. For example, Quick View doesn't show any. I think the reason can be seen on your screenshot - as you can see, the third column ("Include in chapters list") is unselected for each chapter. I have the same mp3 tagged with Python's eyed3, and all the chapters are marked as included and showed just fine by any player I have tried.

I'm not an expert in mp3 tags, but this may be due to a missing CTOC frame. I have tried to add it manually, like this, but it didn't change the result. Any bright ideas or suggestions on how to fix it?

    // create a CTOC frame manually
    ctocFrame := u.createCTOCFrame(chapters)
    tag.AddFrame(tag.CommonID("CTOC"), ctocFrame)

....

func (u *Upload) createCTOCFrame(chapters []chapter) *id3v2.UnknownFrame {
    var frameBody bytes.Buffer
    frameBody.WriteByte(0x03)                // write flags (e.g., 0x03 for top-level and ordered chapters)
    frameBody.WriteByte(byte(len(chapters))) // write the number of child elements

    // append child element IDs (chapter IDs)
    for i, _ := range chapters {
        elementID := fmt.Sprintf("%d", i+1)
        frameBody.WriteString(elementID)
        frameBody.WriteByte(0x00) // Null separator for IDs
    }

    // create and return an UnknownFrame with the constructed body
    return &id3v2.UnknownFrame{Body: frameBody.Bytes()}
}