add support for reading and writing CHAP and CTOC frames

master
joshua stein 2017-05-25 15:42:52 -05:00
rodzic 0168d962f1
commit 16949dffd5
4 zmienionych plików z 402 dodań i 3 usunięć

BIN
chaptest.mp3 100644

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -13,7 +13,8 @@ import (
)
const (
testFile = "test.mp3"
testFile = "test.mp3"
chapterTestFile = "chaptest.mp3"
)
func TestOpen(t *testing.T) {
@ -227,3 +228,74 @@ func TestUTF16CommPanic(t *testing.T) {
file.Close()
}
}
func TestTOC(t *testing.T) {
file, err := Open(chapterTestFile)
if err != nil {
t.Errorf("unable to open chapter file")
}
_toc := file.Frame("CTOC")
if _toc == nil {
t.Errorf("failed finding CTOC")
}
toc := _toc.(*v2.TOCFrame)
if toc.Element != "toc" {
t.Errorf("toc element %q", toc.Element)
}
if !toc.TopLevel {
t.Errorf("not top level")
}
if !toc.Ordered {
t.Errorf("not ordered")
}
if toc.ChildElements[0] != "chp0" {
t.Errorf("got %q", toc.ChildElements[0])
}
if toc.ChildElements[len(toc.ChildElements)-1] != "chp8" {
t.Errorf("got %q", toc.ChildElements[len(toc.ChildElements)-1])
}
file.Close()
}
func TestChapters(t *testing.T) {
file, err := Open(chapterTestFile)
if err != nil {
t.Errorf("unable to open chapter file")
}
_chaps := file.Frames("CHAP")
if _chaps == nil {
t.Errorf("failed finding CHAP")
}
var chaps [](*v2.ChapterFrame)
for _, c := range _chaps {
cc := c.(*v2.ChapterFrame)
chaps = append(chaps, cc)
}
if chaps[0].Title() != "Intro" {
t.Errorf("got %q", chaps[0].Title)
}
if chaps[8].Title() != "Get a free account!" {
t.Errorf("got %q", chaps[8].Title)
}
if !chaps[0].UseTime {
t.Errorf("should be using time")
}
if chaps[0].EndTime != 15000 {
t.Errorf("end time is %q", chaps[0].EndTime)
}
file.Close()
}

Wyświetl plik

@ -4,6 +4,8 @@
package v2
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/mikkyang/id3-go/encodedbytes"
@ -383,11 +385,11 @@ func (f DescTextFrame) Bytes() []byte {
return bytes
}
if err = wr.WriteString(f.description, f.encoding); err != nil {
if err = wr.WriteNullTermString(f.description, f.encoding); err != nil {
return bytes
}
if err = wr.WriteString(f.text, f.encoding); err != nil {
if err = wr.WriteNullTermString(f.text, f.encoding); err != nil {
return bytes
}
@ -583,3 +585,317 @@ func (f ImageFrame) Bytes() []byte {
return bytes
}
// ChapterFrame represents chapter frames
type ChapterFrame struct {
FrameHead
Element string
StartTime uint32
EndTime uint32
StartByte uint32
EndByte uint32
UseTime bool
titleFrame Framer
linkFrame Framer
}
func NewChapterFrame(ft FrameType, element string, startTime uint32, endTime uint32, startByte uint32, endByte uint32, useTime bool, title string, link string, linkTitle string) *ChapterFrame {
var titleFrame Framer
var linkFrame Framer
if title != "" {
ft := V23FrameTypeMap["TIT2"]
titleFrame = NewTextFrame(ft, title)
}
if link != "" {
ft := V23FrameTypeMap["WXXX"]
linkFrame = NewDescTextFrame(ft, linkTitle, link)
}
head := FrameHead{
FrameType: ft,
}
cf := &ChapterFrame{head, element, startTime, endTime, startByte, endByte, useTime, titleFrame, linkFrame}
cf.size = uint32(len(cf.Bytes()))
return cf
}
func ParseChapterFrame(head FrameHead, data []byte) Framer {
var err error
var d []byte
var empty uint32
f := new(ChapterFrame)
f.FrameHead = head
rd := encodedbytes.NewReader(data)
// http://id3.org/id3v2-chapters-1.0
empty = binary.BigEndian.Uint32([]byte{0xff, 0xff, 0xff, 0xff})
if f.Element, err = rd.ReadNullTermString(encodedbytes.NativeEncoding); err != nil {
return nil
}
if d, err = rd.ReadNumBytes(encodedbytes.BytesPerInt); err != nil {
return nil
}
f.StartTime = binary.BigEndian.Uint32(d)
if d, err = rd.ReadNumBytes(encodedbytes.BytesPerInt); err != nil {
return nil
}
f.EndTime = binary.BigEndian.Uint32(d)
if d, err = rd.ReadNumBytes(encodedbytes.BytesPerInt); err != nil {
return nil
}
f.StartByte = binary.BigEndian.Uint32(d)
if d, err = rd.ReadNumBytes(encodedbytes.BytesPerInt); err != nil {
return nil
}
f.EndByte = binary.BigEndian.Uint32(d)
if f.StartTime == empty && f.EndTime == empty {
f.StartTime = 0
f.EndTime = 0
f.UseTime = false
} else if f.StartByte == empty && f.EndByte == empty {
f.StartByte = 0
f.EndByte = 0
f.UseTime = true
} else {
return nil
}
f.size = uint32(len(f.Element) + 1 + (4 * 4))
if d, err = rd.ReadRest(); err != nil {
return nil
}
// individual TIT2 labels will be subframes which are just normal frames
// but contained within the CHAP frame's size
if d != nil {
var frame Framer
dsize := len(d)
pos := 0
for pos < dsize {
reader := bytes.NewReader(d[pos:])
if frame = ParseV23Frame(reader); frame == nil {
break
}
switch frame.Id() {
case "TIT1", "TIT2", "TIT3":
f.titleFrame = frame
case "WXXX":
f.linkFrame = frame
}
fsize := int(frame.Size()) + FrameHeaderSize
pos += fsize
f.size += uint32(fsize)
}
}
return f
}
func (f ChapterFrame) String() string {
if f.UseTime {
return fmt.Sprintf("chapter: %d ms to %d ms: %v", f.StartTime, f.EndTime, f.Title())
} else {
return fmt.Sprintf("chapter: byte %d to %d: %v", f.StartByte, f.EndByte, f.Title())
}
}
func (f ChapterFrame) Link() string {
if f.linkFrame != nil {
return f.linkFrame.(*DescTextFrame).Text()
}
return ""
}
func (f ChapterFrame) Title() string {
if f.titleFrame != nil {
return f.titleFrame.(*TextFrame).String()
}
return ""
}
func (f *ChapterFrame) Bytes() []byte {
f.size = uint32(8 + len(f.Element))
var titleBytes []byte
if f.titleFrame != nil {
titleBytes = V23Bytes(f.titleFrame)
f.size += uint32(len(titleBytes)) + FrameHeaderSize
}
var linkBytes []byte
if f.linkFrame != nil {
linkBytes = V23Bytes(f.linkFrame)
f.size += uint32(len(linkBytes)) + FrameHeaderSize
}
bs := make([]byte, f.size)
wr := encodedbytes.NewWriter(bs)
if err := wr.WriteNullTermString(f.Element, encodedbytes.NativeEncoding); err != nil {
return bs
}
b4 := make([]byte, 4)
if f.UseTime {
binary.BigEndian.PutUint32(b4, f.StartTime)
if _, err := wr.Write(b4); err != nil {
return bs
}
binary.BigEndian.PutUint32(b4, f.EndTime)
if _, err := wr.Write(b4); err != nil {
return bs
}
if _, err := wr.Write(bytes.Repeat([]byte{0xff}, 8)); err != nil {
return bs
}
} else {
if _, err := wr.Write(bytes.Repeat([]byte{0xff}, 8)); err != nil {
return bs
}
binary.BigEndian.PutUint32(b4, f.StartByte)
if _, err := wr.Write(b4); err != nil {
return bs
}
binary.BigEndian.PutUint32(b4, f.EndByte)
if _, err := wr.Write(b4); err != nil {
return bs
}
}
if f.titleFrame != nil {
wr.Write(titleBytes)
}
if f.linkFrame != nil {
wr.Write(linkBytes)
}
return bs
}
// TOCFrame represents Table of Contents frames
type TOCFrame struct {
FrameHead
Element string
TopLevel bool
Ordered bool
ChildElements []string
}
func NewTOCFrame(ft FrameType, element string, topLevel bool, ordered bool, childElements []string) *TOCFrame {
head := FrameHead{
FrameType: ft,
}
tf := &TOCFrame{head, element, topLevel, ordered, childElements}
tf.size = uint32(len(tf.Bytes()))
return tf
}
func ParseTOCFrame(head FrameHead, data []byte) Framer {
var err error
f := new(TOCFrame)
f.FrameHead = head
rd := encodedbytes.NewReader(data)
if f.Element, err = rd.ReadNullTermString(encodedbytes.NativeEncoding); err != nil {
return nil
}
f.size = uint32(len(f.Element) + 1)
b, err := rd.ReadByte()
if err != nil {
return nil
}
f.Ordered = (b&(1<<0) != 0)
f.TopLevel = (b&(1<<1) != 0)
b, err = rd.ReadByte()
if err != nil {
return nil
}
f.size += 2
for i := 0; i < int(b); i++ {
s, err := rd.ReadNullTermString(encodedbytes.NativeEncoding)
if err != nil {
return nil
}
f.size += uint32(len(s) + 1)
f.ChildElements = append(f.ChildElements, s)
}
return f
}
func (f *TOCFrame) SetChildElements(elements []string) {
f.ChildElements = elements
old := int(f.size)
now := len(f.Bytes())
f.changeSize(now - old)
}
func (f TOCFrame) String() string {
return fmt.Sprintf("<TOC %v>", f.ChildElements)
}
func (f *TOCFrame) Bytes() []byte {
var err error
size := uint32(len(f.Element) + 1 + 2 + 1)
for _, e := range f.ChildElements {
size += uint32(len(e) + 1)
}
bs := make([]byte, size)
wr := encodedbytes.NewWriter(bs)
if err := wr.WriteNullTermString(f.Element, encodedbytes.NativeEncoding); err != nil {
return bs
}
flags := 0
if f.Ordered {
flags |= (1 << 0)
}
if f.TopLevel {
flags |= (1 << 1)
}
if err = wr.WriteByte(byte(flags)); err != nil {
return bs
}
if err = wr.WriteByte(byte(len(f.ChildElements))); err != nil {
return bs
}
for _, e := range f.ChildElements {
if err := wr.WriteNullTermString(e, encodedbytes.NativeEncoding); err != nil {
return bs
}
}
return bs
}

Wyświetl plik

@ -42,8 +42,10 @@ var (
V23FrameTypeMap = map[string]FrameType{
"AENC": FrameType{id: "AENC", description: "Audio encryption", constructor: ParseDataFrame},
"APIC": FrameType{id: "APIC", description: "Attached picture", constructor: ParseImageFrame},
"CHAP": FrameType{id: "CHAP", description: "Chapter frame", constructor: nil},
"COMM": FrameType{id: "COMM", description: "Comments", constructor: ParseUnsynchTextFrame},
"COMR": FrameType{id: "COMR", description: "Commercial frame", constructor: ParseDataFrame},
"CTOC": FrameType{id: "CTOC", description: "Chapter table of contents", constructor: nil},
"ENCR": FrameType{id: "ENCR", description: "Encryption method registration", constructor: ParseDataFrame},
"EQUA": FrameType{id: "EQUA", description: "Equalization", constructor: ParseDataFrame},
"ETCO": FrameType{id: "ETCO", description: "Event timing codes", constructor: ParseDataFrame},
@ -147,6 +149,15 @@ func ParseV23Frame(reader io.Reader) Framer {
return nil
}
// can't reference these from the table or they will cause an
// initialization loop
switch id {
case "CHAP":
t.constructor = ParseChapterFrame
case "CTOC":
t.constructor = ParseTOCFrame
}
return t.constructor(h, frameData)
}