-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add ChapterFrame type for CHAP frames * fix to parse CHAP frame * update logic * support old golang * use `Description` called in spec * refactor: put together identical code * fix: should write title and description * refine test - test with TIT2 and TIT3 pattern - improve error message * if tag version is 4, syncSafe is enable * need test that has non-zero time and offset * use `else if` * add name * make more simple * better to use lowercase letter * remove comment * Update chapter_frame_test.go More clarity message Co-authored-by: Albert Nigmatzianov <[email protected]> * Update chapter_frame_test.go More clarity message Co-authored-by: Albert Nigmatzianov <[email protected]> * Update chapter_frame_test.go More clarity message Co-authored-by: Albert Nigmatzianov <[email protected]> * Update chapter_frame_test.go More clarity message Co-authored-by: Albert Nigmatzianov <[email protected]> * Update chapter_frame_test.go More clarity message Co-authored-by: Albert Nigmatzianov <[email protected]> * Update chapter_frame_test.go More clarity message Co-authored-by: Albert Nigmatzianov <[email protected]> * Update chapter_frame_test.go More clarity message Co-authored-by: Albert Nigmatzianov <[email protected]> * use tt.fields.* as StartTime or EndTime Co-authored-by: cnt0 <[email protected]> Co-authored-by: Albert Nigmatzianov <[email protected]>
- Loading branch information
1 parent
ca3c642
commit 033cd25
Showing
12 changed files
with
326 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
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 *TextFrame | ||
Description *TextFrame | ||
} | ||
|
||
func (cf ChapterFrame) Size() int { | ||
size := encodedSize(cf.ElementID, EncodingISO) + | ||
1 + // trailing zero after ElementID | ||
4 + 4 + 4 + 4 // (Start, End) (Time, Offset) | ||
if cf.Title != nil { | ||
size = size + | ||
frameHeaderSize + // Title frame header size | ||
cf.Title.Size() | ||
} | ||
if cf.Description != nil { | ||
size = size + | ||
frameHeaderSize + // Description frame header size | ||
cf.Description.Size() | ||
} | ||
return size | ||
} | ||
|
||
func (cf ChapterFrame) UniqueIdentifier() string { | ||
return cf.ElementID | ||
} | ||
|
||
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) | ||
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) | ||
|
||
if cf.Title != nil { | ||
writeFrame(bw, "TIT2", *cf.Title, true) | ||
} | ||
|
||
if cf.Description != nil { | ||
writeFrame(bw, "TIT3", *cf.Description, true) | ||
} | ||
}) | ||
} | ||
|
||
func parseChapterFrame(br *bufReader, version byte) (Framer, error) { | ||
elementID := br.ReadText(EncodingISO) | ||
synchSafe := version == 4 | ||
var startTime uint32 | ||
var startOffset uint32 | ||
var endTime uint32 | ||
var endOffset uint32 | ||
|
||
if err := binary.Read(br, binary.BigEndian, &startTime); err != nil { | ||
return nil, err | ||
} | ||
if err := binary.Read(br, binary.BigEndian, &endTime); err != nil { | ||
return nil, err | ||
} | ||
if err := binary.Read(br, binary.BigEndian, &startOffset); err != nil { | ||
return nil, err | ||
} | ||
if err := binary.Read(br, binary.BigEndian, &endOffset); err != nil { | ||
return nil, err | ||
} | ||
|
||
var title TextFrame | ||
var description TextFrame | ||
|
||
// borrowed from parse.go | ||
buf := getByteSlice(32 * 1024) | ||
defer putByteSlice(buf) | ||
|
||
for { | ||
header, err := parseFrameHeader(buf, br, synchSafe) | ||
if err == io.EOF || err == errBlankFrame || err == ErrInvalidSizeFormat { | ||
break | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
id, bodySize := header.ID, header.BodySize | ||
if id == "TIT2" || id == "TIT3" { | ||
bodyRd := getLimitedReader(br, bodySize) | ||
br := newBufReader(bodyRd) | ||
frame, err := parseTextFrame(br) | ||
if err != nil { | ||
putLimitedReader(bodyRd) | ||
return nil, err | ||
} | ||
if id == "TIT2" { | ||
title = frame.(TextFrame) | ||
} else if id == "TIT3" { | ||
description = frame.(TextFrame) | ||
} | ||
|
||
putLimitedReader(bodyRd) | ||
} | ||
} | ||
|
||
cf := ChapterFrame{ | ||
ElementID: string(elementID), | ||
// StartTime is given in milliseconds, so we should convert it to nanoseconds | ||
// for time.Duration | ||
StartTime: time.Duration(int64(startTime) * nanosInMillis), | ||
EndTime: time.Duration(int64(endTime) * nanosInMillis), | ||
StartOffset: startOffset, | ||
EndOffset: endOffset, | ||
Title: &title, | ||
Description: &description, | ||
} | ||
return cf, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
package id3v2 | ||
|
||
import ( | ||
"io" | ||
"io/ioutil" | ||
"log" | ||
"os" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func prepareTestFile() (*os.File, error) { | ||
src, err := os.Open("./testdata/test.mp3") | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer src.Close() | ||
|
||
tmpFile, err := ioutil.TempFile("", "chapter_test") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
_, err = io.Copy(tmpFile, src) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return tmpFile, nil | ||
} | ||
|
||
func TestAddChapterFrame(t *testing.T) { | ||
type fields struct { | ||
ElementID string | ||
StartTime time.Duration | ||
EndTime time.Duration | ||
StartOffset uint32 | ||
EndOffset uint32 | ||
Title *TextFrame | ||
Description *TextFrame | ||
} | ||
tests := []struct { | ||
name string | ||
fields fields | ||
}{ | ||
{ | ||
name: "element id only", | ||
fields: fields{ | ||
ElementID: "chap0", | ||
StartTime: 0, | ||
EndTime: time.Duration(1000 * nanosInMillis), | ||
StartOffset: 0, | ||
EndOffset: 0, | ||
}, | ||
}, | ||
{ | ||
name: "with title", | ||
fields: fields{ | ||
ElementID: "chap0", | ||
StartTime: 0, | ||
EndTime: time.Duration(1000 * nanosInMillis), | ||
StartOffset: 0, | ||
EndOffset: 0, | ||
Title: &TextFrame{ | ||
Encoding: EncodingUTF8, | ||
Text: "chapter 0", | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "with description", | ||
fields: fields{ | ||
ElementID: "chap0", | ||
StartTime: 0, | ||
EndTime: time.Duration(1000 * nanosInMillis), | ||
StartOffset: 0, | ||
EndOffset: 0, | ||
Description: &TextFrame{ | ||
Encoding: EncodingUTF8, | ||
Text: "chapter 0", | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "with title and description", | ||
fields: fields{ | ||
ElementID: "chap0", | ||
StartTime: 0, | ||
EndTime: time.Duration(1000 * nanosInMillis), | ||
StartOffset: 0, | ||
EndOffset: 0, | ||
Title: &TextFrame{ | ||
Encoding: EncodingUTF8, | ||
Text: "chapter 0 title", | ||
}, | ||
Description: &TextFrame{ | ||
Encoding: EncodingUTF8, | ||
Text: "chapter 0 description", | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "non-zero time and offset", | ||
fields: fields{ | ||
ElementID: "chap0", | ||
StartTime: time.Duration(1000 * nanosInMillis), | ||
EndTime: time.Duration(1000 * nanosInMillis), | ||
StartOffset: 10, | ||
EndOffset: 10, | ||
}, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
tmpFile, err := prepareTestFile() | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
defer os.Remove(tmpFile.Name()) | ||
|
||
tag, err := Open(tmpFile.Name(), Options{Parse: true}) | ||
if tag == nil || err != nil { | ||
log.Fatal("Error while opening mp3 file: ", err) | ||
} | ||
|
||
cf := ChapterFrame{ | ||
ElementID: tt.fields.ElementID, | ||
StartTime: tt.fields.StartTime, | ||
EndTime: tt.fields.EndTime, | ||
StartOffset: tt.fields.StartOffset, | ||
EndOffset: tt.fields.EndOffset, | ||
Title: tt.fields.Title, | ||
Description: tt.fields.Description, | ||
} | ||
tag.AddChapterFrame(cf) | ||
|
||
if err := tag.Save(); err != nil { | ||
t.Error(err) | ||
} | ||
tag.Close() | ||
|
||
tag, err = Open(tmpFile.Name(), Options{Parse: true}) | ||
if tag == nil || err != nil { | ||
log.Fatal("Error while opening mp3 file: ", err) | ||
} | ||
frame := tag.GetLastFrame("CHAP").(ChapterFrame) | ||
if frame.ElementID != tt.fields.ElementID { | ||
t.Errorf("Expected element ID: %s, but got %s", tt.fields.ElementID, frame.ElementID) | ||
} | ||
if tt.fields.Title != nil && frame.Title.Text != tt.fields.Title.Text { | ||
t.Errorf("Expected title: %s, but got %s", tt.fields.Title.Text, frame.Title) | ||
} | ||
if tt.fields.Description != nil && frame.Description.Text != tt.fields.Description.Text { | ||
t.Errorf("Expected description: %s, but got %s", tt.fields.Description.Text, frame.Description.Text) | ||
} | ||
if frame.StartTime != tt.fields.StartTime { | ||
t.Errorf("Expected start time: %s, but got %s", tt.fields.StartTime, frame.StartTime) | ||
} | ||
if frame.EndTime != tt.fields.EndTime { | ||
t.Errorf("Expected end time: %s, but got %s", tt.fields.EndTime, frame.EndTime) | ||
} | ||
if frame.StartOffset != tt.fields.StartOffset { | ||
t.Errorf("Expected start offset: %d, but got %d", tt.fields.StartOffset, frame.StartOffset) | ||
} | ||
if frame.EndOffset != tt.fields.EndOffset { | ||
t.Errorf("Expected end offset: %d, but got %d", tt.fields.EndOffset, frame.EndOffset) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.