Skip to content

Commit

Permalink
Merge pull request #49 from Arthi-chaud/v2
Browse files Browse the repository at this point in the history
Add Chapter Support
  • Loading branch information
vansante authored May 31, 2024
2 parents a306a4c + dd61fb4 commit e5dd79a
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 23 deletions.
Binary file modified assets/test.mp4
Binary file not shown.
2 changes: 2 additions & 0 deletions ffprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func ProbeURL(ctx context.Context, fileURL string, extraFFProbeOptions ...string
"-print_format", "json",
"-show_format",
"-show_streams",
"-show_chapters",
}, extraFFProbeOptions...)

// Add the file argument
Expand All @@ -47,6 +48,7 @@ func ProbeReader(ctx context.Context, reader io.Reader, extraFFProbeOptions ...s
"-print_format", "json",
"-show_format",
"-show_streams",
"-show_chapters",
}, extraFFProbeOptions...)

// Add the file from stdin argument
Expand Down
69 changes: 48 additions & 21 deletions ffprobe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,33 @@ func Test_ProbeReader_Error(t *testing.T) {
}

func validateData(t *testing.T, data *ProbeData) {
validateStreams(t, data)
// Check some Tags
const testMajorBrand = "isom"
if data.Format.Tags.MajorBrand != testMajorBrand {
t.Errorf("MajorBrand format tag is not %s", testMajorBrand)
}

if val, err := data.Format.TagList.GetString("major_brand"); err != nil {
t.Errorf("retrieving major_brand tag errors: %v", err)
} else if val != testMajorBrand {
t.Errorf("MajorBrand format tag is not %s", testMajorBrand)
}

// test Format.Duration
duration := data.Format.Duration()
if duration.Seconds() != 5.312 {
t.Errorf("this video is 5.312s.")
}
// test Format.StartTime
startTime := data.Format.StartTime()
if startTime != time.Duration(0) {
t.Errorf("this video starts at 0s.")
}
validateChapters(t, data)
}

func validateStreams(t *testing.T, data *ProbeData) {
// test ProbeData.GetStream
stream := data.StreamType(StreamVideo)
if len(stream) != 1 {
Expand Down Expand Up @@ -144,7 +171,8 @@ func validateData(t *testing.T, data *ProbeData) {
}

stream = data.StreamType(StreamData)
if len(stream) != 0 {
// We expect at least one data stream, since there are chapters
if len(stream) == 0 {
t.Errorf("It does not have a data stream.")
}

Expand All @@ -154,31 +182,30 @@ func validateData(t *testing.T, data *ProbeData) {
}

stream = data.StreamType(StreamAny)
if len(stream) != 2 {
t.Errorf("It should have two streams.")
if len(stream) != 3 {
t.Errorf("It should have three streams.")
}
}

// Check some Tags
const testMajorBrand = "isom"
if data.Format.Tags.MajorBrand != testMajorBrand {
t.Errorf("MajorBrand format tag is not %s", testMajorBrand)
func validateChapters(t *testing.T, data *ProbeData) {
chapters := data.Chapters
if chapters == nil {
t.Error("Chapters List was nil")
return
}

if val, err := data.Format.TagList.GetString("major_brand"); err != nil {
t.Errorf("retrieving major_brand tag errors: %v", err)
} else if val != testMajorBrand {
t.Errorf("MajorBrand format tag is not %s", testMajorBrand)
if len(chapters) != 3 {
t.Errorf("Expected 3 chapters. Got %d", len(chapters))
return
}

// test Format.Duration
duration := data.Format.Duration()
if duration.Seconds() != 5.312 {
t.Errorf("this video is 5.312s.")
chapterToTest := chapters[1]
if chapterToTest.Title() != "Middle" {
t.Errorf("Bad Chapter Name. Got %s", chapterToTest.Title())
}
// test Format.StartTime
startTime := data.Format.StartTime()
if startTime != time.Duration(0) {
t.Errorf("this video starts at 0s.")
if chapterToTest.StartTimeSeconds != 2.0 {
t.Errorf("Bad Chapter Start Time. Got %f", chapterToTest.StartTimeSeconds)
}
if chapterToTest.EndTimeSeconds != 4.0 {
t.Errorf("Bad Chapter End Time. Got %f", chapterToTest.EndTimeSeconds)
}
}

Expand Down
30 changes: 28 additions & 2 deletions probedata.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ const (

// ProbeData is the root json data structure returned by an ffprobe.
type ProbeData struct {
Streams []*Stream `json:"streams"`
Format *Format `json:"format"`
Streams []*Stream `json:"streams"`
Format *Format `json:"format"`
Chapters []*Chapter `json:"chapters"`
}

// Format is a json data structure to represent formats
Expand Down Expand Up @@ -102,6 +103,31 @@ type StreamDisposition struct {
AttachedPic int `json:"attached_pic"`
}

// Chapters is a json data structure to represent chapters.
type Chapter struct {
ID int `json:"id"`
TimeBase string `json:"time_base"`
StartTimeSeconds float64 `json:"start_time,string"`
EndTimeSeconds float64 `json:"end_time,string"`
TagList Tags `json:"tags"`
}

// StartTime returns the start time of the chapter as a time.Duration
func (c *Chapter) StartTime() time.Duration {
return time.Duration(c.StartTimeSeconds * float64(time.Second))
}

// EndTime returns the end timestamp of the chapter as a time.Duration
func (c *Chapter) EndTime() time.Duration {
return time.Duration(c.EndTimeSeconds * float64(time.Second))
}

// Name returns the value of the "title" tag of the chapter
func (c *Chapter) Title() string {
title, _ := c.TagList.GetString("title")
return title
}

// StartTime returns the start time of the media file as a time.Duration
func (f *Format) StartTime() (duration time.Duration) {
return time.Duration(f.StartTimeSeconds * float64(time.Second))
Expand Down

0 comments on commit e5dd79a

Please sign in to comment.