diff --git a/CHANGELOG.md b/CHANGELOG.md index 073b63c5..01abf0bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ### Added - New TryDecodeMfro function +- New mp4ff-subslister tool replacing mp4ff-wvttlister, but also supporting stpp ### Fixed - More robust check for mfro at the end of file +### Removed + +- mp4ff-wvttlister tool removed and replaced by mp4ff-subslister + ## [0.43.0] - 2024-04-04 ### Added diff --git a/README.md b/README.md index 7af3d6d3..bfc49898 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Some useful command line tools are available in `cmd`. 2. `mp4ff-pslister` extracts and displays SPS and PPS for AVC or HEVC in a mp4 or a bytestream (Annex B) file. Partial information is printed for HEVC. 3. `mp4ff-nallister` lists NALUs and picture types for video in progressive or fragmented file -4. `mp4ff-wvttlister` lists details of wvtt (WebVTT in ISOBMFF) samples +4. `mp4ff-subslister` lists details of wvtt or stpp (WebVTT or TTML in ISOBMFF) subtitle samples 5. `mp4ff-crop` shortens a progressive mp4 file to a specified duration 6. `mp4ff-encrypt` encrypts a fragmented file using cenc or cbcs Common Encryption scheme 7. `mp4ff-decrypt` decrypts a fragmented file encrypted using cenc or cbcs Common Encryption scheme diff --git a/cmd/mp4ff-wvttlister/main.go b/cmd/mp4ff-subslister/main.go similarity index 59% rename from cmd/mp4ff-wvttlister/main.go rename to cmd/mp4ff-subslister/main.go index 714c9320..9fd71f8e 100644 --- a/cmd/mp4ff-wvttlister/main.go +++ b/cmd/mp4ff-subslister/main.go @@ -1,4 +1,4 @@ -// mp4ff-wvttlister - list wvtt (WebVTT in ISOBMFF) samples +// mp4ff-subslister - list wvtt or stpp (WebVTT or TTML in ISOBMFF) samples package main import ( @@ -13,10 +13,11 @@ import ( "github.com/Eyevinn/mp4ff/mp4" ) -var usg = `Usage of mp4ff-wvttlister: +var usg = `Usage of mp4ff-subslister: -mp4ff-wvttlister lists and displays content of wvtt (WebVTT in ISOBMFF) samples. -Uses track with given non-zero track ID or first wvtt track found in an asset. +mp4ff-subslister lists and displays content of wvtt or stpp samples. +These corresponds to WebVTT or TTML subtitles in ISOBMFF files. +Uses track with given non-zero track ID or first subtitle track found in an asset. ` var usage = func() { @@ -35,7 +36,7 @@ func main() { flag.Parse() if *version { - fmt.Printf("mp4ff-wvttlister %s\n", mp4.GetVersion()) + fmt.Printf("mp4ff-subslister %s\n", mp4.GetVersion()) os.Exit(0) } @@ -95,22 +96,20 @@ func findTrack(moov *mp4.MoovBox, hdlrType string, trackID uint32) (*mp4.TrakBox return nil, fmt.Errorf("no matching track found") } -func parseProgressiveMp4(f *mp4.File, w io.Writer, trackID uint32, maxNrSamples int) error { - wvttTrak, err := findTrack(f.Moov, "text", trackID) - if err != nil { - return err - } - - stbl := wvttTrak.Mdia.Minf.Stbl - if stbl.Stsd.Wvtt == nil { - return fmt.Errorf("no wvtt track found") - } +type subtitleTrack struct { + variant string + trak *mp4.TrakBox +} - fmt.Fprintf(w, "Track %d, timescale = %d\n", wvttTrak.Tkhd.TrackID, wvttTrak.Mdia.Mdhd.Timescale) - err = stbl.Stsd.Wvtt.VttC.Info(os.Stdout, "", " ", " ") +func parseProgressiveMp4(f *mp4.File, w io.Writer, trackID uint32, maxNrSamples int) error { + subsTrak, err := findWvttTrack(f.Moov, w, trackID) if err != nil { - return err + subsTrak, err = findStppTrack(f.Moov, w, trackID) + if err != nil { + return fmt.Errorf("no subtitle track found: %w", err) + } } + stbl := subsTrak.trak.Mdia.Minf.Stbl nrSamples := stbl.Stsz.SampleNumber mdat := f.Mdat mdatPayloadStart := mdat.PayloadAbsoluteOffset() @@ -137,7 +136,12 @@ func parseProgressiveMp4(f *mp4.File, w io.Writer, trackID uint32, maxNrSamples // Next find sample bytes as slice in mdat offsetInMdatData := uint64(offset) - mdatPayloadStart sample := mdat.Data[offsetInMdatData : offsetInMdatData+uint64(size)] - err = printWvttSample(w, sample, sampleNr, decTime+uint64(cto), dur) + switch subsTrak.variant { + case "wvtt": + err = printWvttSample(w, sample, sampleNr, decTime+uint64(cto), dur) + case "stpp": + err = printStppSample(w, sample, sampleNr, decTime+uint64(cto), dur) + } if err != nil { return err } @@ -148,27 +152,65 @@ func parseProgressiveMp4(f *mp4.File, w io.Writer, trackID uint32, maxNrSamples return nil } -func parseFragmentedMp4(f *mp4.File, w io.Writer, trackID uint32, maxNrSamples int) error { - var wvttTrex *mp4.TrexBox - if f.Init != nil { // Print vttC header and timescale if moov-box is present - wvttTrak, err := findTrack(f.Init.Moov, "text", trackID) - if err != nil { - return err - } +func findWvttTrack(moov *mp4.MoovBox, w io.Writer, trackID uint32) (*subtitleTrack, error) { + subsTrak, err := findTrack(moov, "text", trackID) + if err != nil { + return nil, err + } - stbl := wvttTrak.Mdia.Minf.Stbl - if stbl.Stsd.Wvtt == nil { - return fmt.Errorf("no wvtt track found") - } + stbl := subsTrak.Mdia.Minf.Stbl + if stbl.Stsd.Wvtt == nil { + return nil, fmt.Errorf("no wvtt track found") + } + + fmt.Fprintf(w, "Track %d, timescale = %d\n", subsTrak.Tkhd.TrackID, subsTrak.Mdia.Mdhd.Timescale) + err = stbl.Stsd.Wvtt.VttC.Info(os.Stdout, "", " ", " ") + if err != nil { + return nil, err + } + return &subtitleTrack{ + variant: "wvtt", + trak: subsTrak, + }, nil +} + +func findStppTrack(moov *mp4.MoovBox, w io.Writer, trackID uint32) (*subtitleTrack, error) { + subsTrak, err := findTrack(moov, "subt", trackID) + if err != nil { + return nil, err + } + + stbl := subsTrak.Mdia.Minf.Stbl + if stbl.Stsd.Stpp == nil { + return nil, fmt.Errorf("no stpp track found") + } - fmt.Fprintf(w, "Track %d, timescale = %d\n", wvttTrak.Tkhd.TrackID, wvttTrak.Mdia.Mdhd.Timescale) - err = stbl.Stsd.Wvtt.VttC.Info(w, " ", "", " ") + fmt.Fprintf(w, "Track %d, timescale = %d\n", subsTrak.Tkhd.TrackID, subsTrak.Mdia.Mdhd.Timescale) + err = stbl.Stsd.Stpp.Info(w, "", " ", " ") + if err != nil { + return nil, err + } + return &subtitleTrack{ + variant: "stpp", + trak: subsTrak, + }, nil +} + +func parseFragmentedMp4(f *mp4.File, w io.Writer, trackID uint32, maxNrSamples int) error { + var subsTrex *mp4.TrexBox + var subsTrak *subtitleTrack + var err error + if f.Init != nil { // Print vttC header and timescale if moov-box is present + subsTrak, err = findWvttTrack(f.Moov, w, trackID) if err != nil { - return err + subsTrak, err = findStppTrack(f.Moov, w, trackID) + if err != nil { + return fmt.Errorf("no subtitle track found: %w", err) + } } for _, trex := range f.Init.Moov.Mvex.Trexs { - if trex.TrackID == wvttTrak.Tkhd.TrackID { - wvttTrex = trex + if trex.TrackID == subsTrak.trak.Tkhd.TrackID { + subsTrex = trex } } } @@ -183,7 +225,7 @@ func parseFragmentedMp4(f *mp4.File, w io.Writer, trackID uint32, maxNrSamples i tfraTime = entry.Time } } - fSamples, err := iFrag.GetFullSamples(wvttTrex) + fSamples, err := iFrag.GetFullSamples(subsTrex) if err != nil { return err } @@ -195,9 +237,28 @@ func parseFragmentedMp4(f *mp4.File, w io.Writer, trackID uint32, maxNrSamples i iSamples = append(iSamples, fSamples...) } } - var err error + if subsTrak == nil { + if len(iSamples) == 0 { + return fmt.Errorf("no subtitle samples found") + } + variant := "stpp" + if iSamples[0].Data[0] == 0 { // Only wvtt start with a length field. + variant = "wvtt" + } + + subsTrak = &subtitleTrack{ + variant: variant, + } + } for i, sample := range iSamples { - err = printWvttSample(w, sample.Data, i+1, sample.PresentationTime(), sample.Dur) + switch subsTrak.variant { + case "wvtt": + err = printWvttSample(w, sample.Data, i+1, sample.PresentationTime(), sample.Dur) + case "stpp": + err = printStppSample(w, sample.Data, i+1, sample.PresentationTime(), sample.Dur) + default: + return fmt.Errorf("unknown subtitle track type") + } if err != nil { return err @@ -232,3 +293,9 @@ func printWvttSample(w io.Writer, sample []byte, nr int, pts uint64, dur uint32) } return nil } + +func printStppSample(w io.Writer, sample []byte, nr int, pts uint64, dur uint32) error { + fmt.Fprintf(w, "Sample %d, pts=%d, dur=%d\n", nr, pts, dur) + _, err := w.Write(sample) + return err +} diff --git a/cmd/mp4ff-wvttlister/main_test.go b/cmd/mp4ff-subslister/main_test.go similarity index 54% rename from cmd/mp4ff-wvttlister/main_test.go rename to cmd/mp4ff-subslister/main_test.go index 7df407be..1097f4e6 100644 --- a/cmd/mp4ff-wvttlister/main_test.go +++ b/cmd/mp4ff-subslister/main_test.go @@ -7,9 +7,7 @@ import ( "testing" ) -var wantedSampleShort = `Track 1, timescale = 1000 -[vttC] size=14 - - config: "WEBVTT" +var wantedWvttShort = `Track 1, timescale = 1000 Sample 1, pts=0, dur=6640 [vttc] size=52 [sttg] size=18 @@ -67,7 +65,50 @@ var wantedMultiVttc = `Sample 1, pts=291054710760, dur=2560 - cueText: "Ouais ! Belle gosse ! Voici 2 M !" ` -func TestWvttLister(t *testing.T) { +var wantedStppCombined = `Track 1, timescale = 90000 + [stpp] size=43 + - dataReferenceIndex: 1 + - nameSpace: "http://www.w3.org/ns/ttml" + - schemaLocation: "" + - auxiliaryMimeTypes: "" +Sample 1, pts=0, dur=540000 + + + + + +