Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change: mp4ff-wvttlister replacd by mp4ff-subslister #343

Merged
merged 1 commit into from
Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Loading