Skip to content

Commit

Permalink
Merge pull request #325 from Eyevinn/feat-add-sidx
Browse files Browse the repository at this point in the history
New example add-side with advanced options.
  • Loading branch information
tobbee committed Apr 19, 2024
2 parents cde96fd + 7a6a5fa commit cfd8b12
Show file tree
Hide file tree
Showing 14 changed files with 489 additions and 16 deletions.
16 changes: 13 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,28 @@
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

## [Unreleased]

### Added

- New TryDecodeMfro function
- New mp4ff-subslister tool replacing mp4ff-wvttlister, but also supporting stpp
- New `TryDecodeMfro` function
- New `mp4ff-subslister` tool replacing `mp4ff-wvttlister`. It supports `wvtt` and `stpp`
- `File.UpdateSidx()` to update or add a top level sidx box for a fragmented file
- `mp4.DecStartSegmentOnMoof` flag to make the Decoder interpret every moof as
a new segment start, unless styp, sidx, or mfra boxes give that information.
- New example `add-sidx` shows how on can add a top-level `sidx` box to a fragmented file.
It further has the option to remove unused encryption boxes, and to interpret each
moof box as starting a new segment.
- New method `MoovBox.IsEncrypted()` checks if an encrypted codec is signaled

### Fixed

- More robust check for mfro at the end of file
- GetTrex() return value
- Can now write PIFF `uuid` box that has previously been read
- Does now avoid the second parsing of `senc` box if the file is ot encrypted as seen in moov box.

### Removed

Expand Down
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
all: test check coverage build

.PHONY: build
build: mp4ff-crop mp4ff-decrypt mp4ff-encrypt mp4ff-info mp4ff-nallister mp4ff-pslister mp4ff-wvttlister examples
build: mp4ff-crop mp4ff-decrypt mp4ff-encrypt mp4ff-info mp4ff-nallister mp4ff-pslister mp4ff-subslister examples

.PHONY: prepare
prepare:
go mod vendor

mp4ff-crop mp4ff-decrypt mp4ff-encrypt mp4ff-info mp4ff-nallister mp4ff-pslister mp4ff-wvttlister:
mp4ff-crop mp4ff-decrypt mp4ff-encrypt mp4ff-info mp4ff-nallister mp4ff-pslister mp4ff-subslister:
go build -ldflags "-X github.com/Eyevinn/mp4ff/mp4.commitVersion=$$(git describe --tags HEAD) -X github.com/Eyevinn/mp4ff/mp4.commitDate=$$(git log -1 --format=%ct)" -o out/$@ ./cmd/$@/main.go

.PHONY: examples
Expand All @@ -21,6 +21,10 @@ initcreator multitrack resegmenter segmenter:
test: prepare
go test ./...

.PHONY: testsum
testsum: prepare
gotestsum

.PHONY: coverage
coverage:
# Ignore (allow) packages without any tests
Expand Down
127 changes: 127 additions & 0 deletions examples/add-sidx/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// add-sidx adds a top-level sidx box describing the segments of a fragmented files.
//
// Segments are identified by styp boxes if they exist, otherwise by
// the start of moof or emsg boxes.
package main

import (
"flag"
"fmt"
"io"
"log"
"os"
"strings"

"github.com/Eyevinn/mp4ff/mp4"
)

var usg = `Usage of add-sidx:
add-sidx adds a top-level sidx box to a fragmented file provided it does not exist.
If styp boxes are present, they signal new segments. It is possible to interpret
every moof box as the start of a new segment, by specifying the "-startSegOnMoof" option.
One can further remove unused encryption boxes with the "-removeEnc" option.
`

var usage = func() {
parts := strings.Split(os.Args[0], "/")
name := parts[len(parts)-1]
fmt.Fprintln(os.Stderr, usg)
fmt.Fprintf(os.Stderr, "%s [options] <inFile> <outFile>\n", name)
flag.PrintDefaults()
}

func main() {
removeEncBoxes := flag.Bool("removeEnc", false, "Remove unused encryption boxes")
usePTO := flag.Bool("nzEPT", false, "Use non-zero earliestPresentationTime")
segOnMoof := flag.Bool("startSegOnMoof", false, "Start a new segment on every moof")
version := flag.Bool("version", false, "Get mp4ff version")

flag.Parse()

if *version {
fmt.Printf("add-sidx %s\n", mp4.GetVersion())
os.Exit(0)
}
flag.Parse()

if *version {
fmt.Printf("add-sidx %s\n", mp4.GetVersion())
os.Exit(0)
}

args := flag.Args()
if len(args) != 2 {
fmt.Fprintf(os.Stderr, "must specify infile and outfile\n")
usage()
os.Exit(1)
}

inFilePath := flag.Arg(0)
outFilePath := flag.Arg(1)

ifd, err := os.Open(inFilePath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
usage()
os.Exit(1)
}
defer ifd.Close()
ofd, err := os.Create(outFilePath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
usage()
os.Exit(1)
}
defer ofd.Close()
err = run(ifd, ofd, *usePTO, *removeEncBoxes, *segOnMoof)
if err != nil {
log.Fatal(err)
}
}

func run(in io.Reader, out io.Writer, nonZeroEPT, removeEncBoxes, segOnMoof bool) error {
var flags mp4.DecFileFlags
if segOnMoof {
flags |= mp4.DecStartOnMoof
}
mp4Root, err := mp4.DecodeFile(in, mp4.WithDecodeFlags(flags))
if err != nil {
return err
}
fmt.Printf("creating sidx with %d segment(s)\n", len(mp4Root.Segments))

if removeEncBoxes {
removeEncryptionBoxes(mp4Root)
}

addIfNotExists := true
err = mp4Root.UpdateSidx(addIfNotExists, nonZeroEPT)
if err != nil {
return fmt.Errorf("addSidx failed: %w", err)
}

err = mp4Root.Encode(out)
if err != nil {
return fmt.Errorf("failed to encode output file: %w", err)
}
return nil
}

func removeEncryptionBoxes(inFile *mp4.File) {
for _, seg := range inFile.Segments {
for _, frag := range seg.Fragments {
bytesRemoved := uint64(0)
for _, traf := range frag.Moof.Trafs {
bytesRemoved += traf.RemoveEncryptionBoxes()
}
for _, traf := range frag.Moof.Trafs {
for _, trun := range traf.Truns {
trun.DataOffset -= int32(bytesRemoved)
}
}
}
}
}
93 changes: 93 additions & 0 deletions examples/add-sidx/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package main

import (
"bytes"
"os"
"testing"

"github.com/Eyevinn/mp4ff/mp4"
)

func TestAddSidx(t *testing.T) {
inPath := "testdata/clear_with_enc_boxes.mp4"
testCases := []struct {
desc string
inPath string
removeEnc bool
segOnMoof bool
wantedNrSegs uint32
wantedSize uint32
wantedFirstDur uint32
}{
{
desc: "sidx, enc boxes, 1 segment",
inPath: inPath,
removeEnc: false,
segOnMoof: false,
wantedNrSegs: 1,
wantedFirstDur: 2 * 144144,
},
{
desc: "sidx, enc boxes, many segments",
inPath: inPath,
removeEnc: false,
segOnMoof: true,
wantedNrSegs: 2,
wantedFirstDur: 144144,
},
{
desc: "sidx, no enc boxes, many segments",
inPath: inPath,
removeEnc: true,
segOnMoof: true,
wantedNrSegs: 2,
wantedFirstDur: 144144,
},
{
desc: "normal file with styp",
inPath: "../resegmenter/testdata/testV300.mp4",
removeEnc: false,
segOnMoof: false,
wantedNrSegs: 4,
wantedFirstDur: 180000,
},
}

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
in, err := os.Open(tc.inPath)
if err != nil {
t.Error(err)
}
out := bytes.Buffer{}
err = run(in, &out, false, tc.removeEnc, tc.segOnMoof)
if err != nil {
return
}
decOut, err := mp4.DecodeFile(&out)
if err != nil {
t.Error()
}
if decOut.Sidx == nil {
t.Error("no sidx box")
}
sidxEntries := decOut.Sidx.SidxRefs
gotNrEntries := len(sidxEntries)
if gotNrEntries != int(tc.wantedNrSegs) {
t.Errorf("got %d sidx entries instead of %d", gotNrEntries, tc.wantedNrSegs)
}
if sidxEntries[0].SubSegmentDuration != tc.wantedFirstDur {
t.Errorf("got first duration %d instead of %d", sidxEntries[0].SubSegmentDuration, tc.wantedFirstDur)
}
if tc.removeEnc {
for _, seg := range decOut.Segments {
for _, frag := range seg.Fragments {
if frag.Moof.Traf.Senc != nil {
t.Error("senc is still present in fragment")
}
}
}
}
})
}
}
Binary file not shown.
10 changes: 7 additions & 3 deletions mp4/boxsr.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,17 +225,21 @@ LoopBoxes:
moof := box.(*MoofBox)
for _, traf := range moof.Trafs {
if ok, parsed := traf.ContainsSencBox(); ok && !parsed {
isEncrypted := true
defaultIVSize := byte(0) // Should get this from tenc in sinf
if f.Moov != nil {
trackID := traf.Tfhd.TrackID
isEncrypted = f.Moov.IsEncrypted(trackID)
sinf := f.Moov.GetSinf(trackID)
if sinf != nil && sinf.Schi != nil && sinf.Schi.Tenc != nil {
defaultIVSize = sinf.Schi.Tenc.DefaultPerSampleIVSize
}
}
err = traf.ParseReadSenc(defaultIVSize, moof.StartPos)
if err != nil {
return nil, err
if isEncrypted {
err = traf.ParseReadSenc(defaultIVSize, moof.StartPos)
if err != nil {
return nil, err
}
}
}
}
Expand Down
Loading

0 comments on commit cfd8b12

Please sign in to comment.