From 5cdc82a1932ad892a46e5d9aa9bebcf6a8946f42 Mon Sep 17 00:00:00 2001 From: cnt0 <4948391+cnt0@users.noreply.github.com> Date: Fri, 8 Feb 2019 20:04:46 +0500 Subject: [PATCH] add ChapterFrame type for CHAP frames --- chapter_frame.go | 116 +++++++++++++++++++++++++++++++++++++++++++++++ common_ids.go | 5 +- sequence.go | 2 + tag.go | 5 ++ 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 chapter_frame.go diff --git a/chapter_frame.go b/chapter_frame.go new file mode 100644 index 0000000..5181b55 --- /dev/null +++ b/chapter_frame.go @@ -0,0 +1,116 @@ +package id3v2 + +import ( + "encoding/binary" + "io" + "time" +) + +const ( + nanosInMillis = 1000000 + IgnoredOffset = 0xFFFFFFFF +) + +// ChapterFrame is used to work with CHAP frames +// according to spec from http://id3.org/id3v2-chapters-1.0 +// This implementation only supports single TIT2 subframe (Title field). +// All other subframes are ignored. +// If StartOffset or EndOffset == id3v2.IgnoredOffset, then it should be ignored +// and StartTime or EndTime should be utilized +type ChapterFrame struct { + ElementID string + StartTime time.Duration + EndTime time.Duration + StartOffset uint32 + EndOffset uint32 + Title string +} + +func (cf ChapterFrame) Size() int { + titleFrame := TextFrame{ + Encoding: EncodingUTF8, + Text: cf.Title, + } + return encodedSize(cf.ElementID, EncodingISO) + + 1 + // trailing zero after ElementID + 4 + 4 + 4 + 4 + // (Start, End) (Time, Offset) + frameHeaderSize + // Title frame header size + titleFrame.Size() +} + +func (cf ChapterFrame) WriteTo(w io.Writer) (n int64, err error) { + return useBufWriter(w, func(bw *bufWriter) { + bw.EncodeAndWriteText(cf.ElementID, EncodingISO) + bw.WriteByte(0) + // nanoseconds => milliseconds + binary.Write(bw, binary.BigEndian, int32(cf.StartTime/nanosInMillis)) + binary.Write(bw, binary.BigEndian, int32(cf.EndTime/nanosInMillis)) + + binary.Write(bw, binary.BigEndian, cf.StartOffset) + binary.Write(bw, binary.BigEndian, cf.EndOffset) + + titleFrame := TextFrame{ + Encoding: EncodingUTF8, + Text: cf.Title, + } + writeFrame(bw, "TIT2", titleFrame, true) + }) +} + +func parseChapterFrame(br *bufReader) (Framer, error) { + ElementID := br.ReadText(EncodingISO) + chapterTime := make([]int32, 2) + for i := range chapterTime { + if err := binary.Read(br, binary.BigEndian, &chapterTime[i]); err != nil { + return nil, err + } + } + chapterOffset := make([]uint32, 2) + for i := range chapterOffset { + if err := binary.Read(br, binary.BigEndian, &chapterOffset[i]); err != nil { + return nil, err + } + } + var title string + + // borrowed from parse.go + buf := getByteSlice(32 * 1024) + defer putByteSlice(buf) + for { + // no way to determine whether this should be true or not + // this is likely should be fixed + header, err := parseFrameHeader(buf, br, true) + if err == io.EOF || err == errBlankFrame || err == ErrInvalidSizeFormat { + break + } + if err != nil { + return nil, err + } + id, bodySize := header.ID, header.BodySize + if id == "TIT2" { + bodyRd := getLimitedReader(br, bodySize) + br2 := newBufReader(bodyRd) + frame, err := parseTextFrame(br2) + if err != nil { + putLimitedReader(bodyRd) + return nil, err + } + title = frame.(TextFrame).Text + + putLimitedReader(bodyRd) + break + } + } + + cf := ChapterFrame{ + ElementID: string(ElementID), + // StartTime is given in milliseconds, so we should convert it to nanoseconds + // for time.Duration + StartTime: time.Duration(chapterTime[0] * nanosInMillis), + EndTime: time.Duration(chapterTime[1] * nanosInMillis), + StartOffset: chapterOffset[0], + EndOffset: chapterOffset[1], + Title: title, + } + return cf, nil +} diff --git a/common_ids.go b/common_ids.go index 1677b7a..cfa0a60 100644 --- a/common_ids.go +++ b/common_ids.go @@ -8,6 +8,7 @@ package id3v2 var ( V23CommonIDs = map[string]string{ "Attached picture": "APIC", + "Chapters": "CHAP", "Comments": "COMM", "Album/Movie/Show title": "TALB", "BPM": "TBPM", @@ -59,6 +60,7 @@ var ( V24CommonIDs = map[string]string{ "Attached picture": "APIC", + "Chapters": "CHAP", "Comments": "COMM", "Album/Movie/Show title": "TALB", "BPM": "TBPM", @@ -133,6 +135,7 @@ var ( // } var parsers = map[string]func(*bufReader) (Framer, error){ "APIC": parsePictureFrame, + "CHAP": parseChapterFrame, "COMM": parseCommentFrame, "TXXX": parseUserDefinedTextFrame, "UFID": parseUFIDFrame, @@ -143,7 +146,7 @@ var parsers = map[string]func(*bufReader) (Framer, error){ // be added to sequence. func mustFrameBeInSequence(id string) bool { switch id { - case "APIC", "COMM", "TXXX", "USLT": + case "APIC", "CHAP", "COMM", "TXXX", "USLT": return true } return false diff --git a/sequence.go b/sequence.go index 2abc942..26794c7 100644 --- a/sequence.go +++ b/sequence.go @@ -27,6 +27,8 @@ func (s *sequence) AddFrame(f Framer) { id = uslf.Language + uslf.ContentDescriptor } else if udtf, ok := f.(UserDefinedTextFrame); ok { id = udtf.Description + } else if cf, ok := f.(ChapterFrame); ok { + id = cf.ElementID } else { panic("sequence: unknown type of Framer") } diff --git a/tag.go b/tag.go index de0418c..a15a590 100644 --- a/tag.go +++ b/tag.go @@ -51,6 +51,11 @@ func (tag *Tag) AddAttachedPicture(pf PictureFrame) { tag.AddFrame(tag.CommonID("Attached picture"), pf) } +// AddChapterFrame adds the chapter frame to tag. +func (tag *Tag) AddChapterFrame(cf ChapterFrame) { + tag.AddFrame(tag.CommonID("Chapters"), cf) +} + // AddCommentFrame adds the comment frame to tag. func (tag *Tag) AddCommentFrame(cf CommentFrame) { tag.AddFrame(tag.CommonID("Comments"), cf)