Skip to content

Commit

Permalink
Merge pull request #343 from Eyevinn/subs-lister
Browse files Browse the repository at this point in the history
change: mp4ff-wvttlister replacd by mp4ff-subslister
  • Loading branch information
tobbee committed Apr 6, 2024
2 parents 37aee0a + bee5d1c commit cde96fd
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 45 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
143 changes: 105 additions & 38 deletions cmd/mp4ff-wvttlister/main.go → cmd/mp4ff-subslister/main.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -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() {
Expand All @@ -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)
}

Expand Down Expand Up @@ -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()
Expand All @@ -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
}
Expand All @@ -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
}
}
}
Expand All @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -67,20 +65,67 @@ var wantedMultiVttc = `Sample 1, pts=291054710760, dur=2560
- cueText: "<c.white.bg_black>Ouais ! Belle gosse ! Voici 2 M !</c>"
`

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
<?xml version="1.0" encoding="UTF-8"?>
<tt xmlns="http://www.w3.org/ns/ttml" xmlns:tts="http://www.w3.org/ns/ttml#styling" xml:lang="eng" ` +
`xmlns:ttp="http://www.w3.org/ns/ttml#parameter" xmlns:ttm="http://www.w3.org/ns/ttml#metadata" ` +
`xmlns:ebuttm="urn:ebu:tt:metadata" xmlns:ebutts="urn:ebu:tt:style" xml:space="default" ` +
`ttp:timeBase="media" ttp:cellResolution="32 15">
<head>
<metadata/>
<styling>
<style xml:id="default" tts:fontStyle="normal" tts:fontFamily="sansSerif" tts:fontSize="100%" ` +
`tts:lineHeight="normal" tts:textAlign="center" ebutts:linePadding="0.5c"/>
<style xml:id="white_black" tts:backgroundColor="black" tts:color="white"/>
</styling>
<layout>
<region xml:id="ttx_11" tts:origin="10% 84%" tts:extent="80% 15%" tts:overflow="visible"/>
<region xml:id="ttx_9" tts:origin="10% 70%" tts:extent="80% 15%" tts:overflow="visible"/>
</layout>
</head>
<body style="default">
<div>
<p begin="00:00:02.520" end="00:00:04.120" region="ttx_9" tts:textAlign="right">
<span style="white_black">-Pourquoi ?</span>
</p>
<p begin="00:00:02.520" end="00:00:04.120" region="ttx_11" tts:textAlign="center">
<span style="white_black">-J'ai...</span>
</p>
<p begin="00:00:04.520" end="00:00:06.600" region="ttx_9" tts:textAlign="center">
<span style="white_black">J'ai un tas de trucs à faire.</span>
</p>
<p begin="00:00:04.520" end="00:00:06.600" region="ttx_11" tts:textAlign="center">
<span style="white_black">-Non !</span>
</p>
</div>
</body>
</tt>
`

func TestSubsLister(t *testing.T) {

testCases := []struct {
testFile string
wanted string
}{
{
testFile: "testdata/sample_short.ismt",
wanted: wantedSampleShort,
wanted: wantedWvttShort,
},
{
testFile: "testdata/multi_vttc.mp4",
wanted: wantedMultiVttc,
},
{
testFile: "testdata/stpp_combined.mp4",
wanted: wantedStppCombined,
},
}

for _, tc := range testCases {
Expand All @@ -106,6 +151,9 @@ func TestWvttLister(t *testing.T) {
t.Errorf("line %d: got: %q\n wanted %q", i, gotLines[i], wantedLines[i])
}
}
if got != tc.wanted {
t.Errorf("got: %q\n wanted %q", got, tc.wanted)
}
})
}
}
Binary file added cmd/mp4ff-subslister/testdata/stpp_combined.mp4
Binary file not shown.

0 comments on commit cde96fd

Please sign in to comment.