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

New example add-sidx with advanced options. #325

Merged
merged 2 commits into from
Apr 19, 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
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
Loading