• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Eyevinn / mp4ff / 19457364131

18 Nov 2025 07:07AM UTC coverage: 81.496% (-0.005%) from 81.501%
19457364131

Pull #466

github

vtpl1
AddEmptyTrack returns reference to newly added track
Pull Request #466: AddEmptyTrack returns reference to newly added track

11 of 11 new or added lines in 3 files covered. (100.0%)

12 existing lines in 1 file now uncovered.

17203 of 21109 relevant lines covered (81.5%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.5
/examples/segmenter/segmenter.go
1
package main
2

3
import (
4
        "fmt"
5
        "io"
6

7
        "github.com/Eyevinn/mp4ff/mp4"
8
)
9

10
// Segmenter - segment the progressive inFIle
11
type Segmenter struct {
12
        inFile *mp4.File
13
        tracks []*Track
14
        nrSegs int //  target number of segments
15
}
16

17
// Track - media track defined by inTrak
18
type Track struct {
19
        trackType string
20
        inTrak    *mp4.TrakBox
21
        timeScale uint32
22
        trackID   uint32 // trackID in segmented output
23
        lang      string
24
        segments  []sampleInterval
25
}
26

27
// NewSegmenter - create a Segmenter from inFile and fill in track information
28
func NewSegmenter(inFile *mp4.File) (*Segmenter, error) {
1✔
29
        if inFile.IsFragmented() {
1✔
30
                return nil, fmt.Errorf("segmented input file not supported")
×
31
        }
×
32
        s := Segmenter{inFile: inFile}
1✔
33
        traks := inFile.Moov.Traks
1✔
34
        for _, trak := range traks {
2✔
35
                track := &Track{trackType: "", lang: ""}
1✔
36
                switch hdlrType := trak.Mdia.Hdlr.HandlerType; hdlrType {
1✔
37
                case "vide":
1✔
38
                        track.trackType = "video"
1✔
39
                case "soun":
1✔
40
                        track.trackType = "audio"
1✔
41
                default:
×
42
                        return nil, fmt.Errorf("hdlr type %q not supported", hdlrType)
×
43
                }
44
                track.lang = trak.Mdia.Mdhd.GetLanguage()
1✔
45
                if trak.Mdia.Elng != nil {
1✔
46
                        track.lang = trak.Mdia.Elng.Language
×
47
                }
×
48
                track.inTrak = trak
1✔
49
                track.timeScale = trak.Mdia.Mdhd.Timescale
1✔
50
                s.tracks = append(s.tracks, track)
1✔
51
        }
52
        return &s, nil
1✔
53
}
54

55
// SetTargetSegmentation - set segment start points
56
func (s *Segmenter) SetTargetSegmentation(syncTimescale uint32, segStarts []syncPoint) error {
1✔
57
        var err error
1✔
58
        for i := range s.tracks {
2✔
59
                s.tracks[i].segments, err = getSegmentIntervals(syncTimescale, segStarts, s.tracks[i].inTrak)
1✔
60
                if err != nil {
1✔
61
                        return err
×
62
                }
×
63
        }
64
        s.nrSegs = len(segStarts)
1✔
65
        return nil
1✔
66
}
67

68
// MakeInitSegments - initialized and return init segments for all the tracks
69
func (s *Segmenter) MakeInitSegments() ([]*mp4.InitSegment, error) {
1✔
70
        var inits []*mp4.InitSegment
1✔
71
        for _, tr := range s.tracks {
2✔
72
                init := mp4.CreateEmptyInit()
1✔
73
                init.Moov.Mvhd.Timescale = s.inFile.Moov.Mvhd.Timescale
1✔
74
                inMovieDuration := s.inFile.Moov.Mvhd.Duration
1✔
75
                init.Moov.Mvex.AddChild(&mp4.MehdBox{FragmentDuration: int64(inMovieDuration)})
1✔
76
                outTrak := init.AddEmptyTrack(tr.timeScale, tr.trackType, tr.lang)
1✔
77
                tr.trackID = outTrak.Tkhd.TrackID
1✔
78

1✔
79
                inStsd := tr.inTrak.Mdia.Minf.Stbl.Stsd
1✔
80
                outStsd := outTrak.Mdia.Minf.Stbl.Stsd
1✔
81
                switch tr.trackType {
1✔
82
                case "audio":
1✔
83
                        if inStsd.Mp4a != nil {
2✔
84
                                outStsd.AddChild(inStsd.Mp4a)
1✔
85
                        } else if inStsd.AC3 != nil {
1✔
86
                                outStsd.AddChild(inStsd.AC3)
×
87
                        } else if inStsd.EC3 != nil {
×
88
                                outStsd.AddChild(inStsd.EC3)
×
89
                        }
×
90
                case "video":
1✔
91
                        if inStsd.AvcX != nil {
2✔
92
                                outStsd.AddChild(inStsd.AvcX)
1✔
93
                        } else if inStsd.HvcX != nil {
1✔
94
                                outStsd.AddChild(inStsd.HvcX)
×
95
                        }
×
96
                default:
×
97
                        return nil, fmt.Errorf("unsupported tracktype: %s", tr.trackType)
×
98
                }
99
                inits = append(inits, init)
1✔
100
        }
101
        return inits, nil
1✔
102
}
103

104
// MakeMuxedInitSegment - initialized and return one init segments for all the tracks
105
func (s *Segmenter) MakeMuxedInitSegment() (*mp4.InitSegment, error) {
1✔
106
        init := mp4.CreateEmptyInit()
1✔
107
        inMovieDuration := s.inFile.Moov.Mvhd.Duration
1✔
108
        init.Moov.Mvex.AddChild(&mp4.MehdBox{FragmentDuration: int64(inMovieDuration)})
1✔
109
        for _, tr := range s.tracks {
2✔
110
                init.AddEmptyTrack(tr.timeScale, tr.trackType, tr.lang)
1✔
111
                // outTrak := init.Moov.Traks[len(init.Moov.Traks)-1]
1✔
112
                outTrak := init.AddEmptyTrack(tr.timeScale, tr.trackType, tr.lang)
1✔
113
                tr.trackID = outTrak.Tkhd.TrackID
1✔
114
                inStsd := tr.inTrak.Mdia.Minf.Stbl.Stsd
1✔
115
                outStsd := outTrak.Mdia.Minf.Stbl.Stsd
1✔
116
                switch tr.trackType {
1✔
117
                case "audio":
1✔
118
                        if inStsd.Mp4a != nil {
2✔
119
                                outStsd.AddChild(inStsd.Mp4a)
1✔
120
                        } else if inStsd.AC3 != nil {
1✔
121
                                outStsd.AddChild(inStsd.AC3)
×
122
                        } else if inStsd.EC3 != nil {
×
UNCOV
123
                                outStsd.AddChild(inStsd.EC3)
×
UNCOV
124
                        }
×
125
                case "video":
1✔
126
                        if inStsd.AvcX != nil {
2✔
127
                                outStsd.AddChild(inStsd.AvcX)
1✔
128
                        } else if inStsd.HvcX != nil {
1✔
129
                                outStsd.AddChild(inStsd.HvcX)
×
130
                        }
×
UNCOV
131
                default:
×
UNCOV
132
                        return nil, fmt.Errorf("unsupported tracktype: %s", tr.trackType)
×
133
                }
134
        }
135

136
        return init, nil
1✔
137
}
138

139
// GetFullSamplesForInterval - get slice of fullsamples with numbers startSampleNr to endSampleNr (inclusive)
140
func (s *Segmenter) GetFullSamplesForInterval(mp4f *mp4.File, tr *Track, startSampleNr, endSampleNr uint32,
141
        rs io.ReadSeeker) ([]mp4.FullSample, error) {
1✔
142
        stbl := tr.inTrak.Mdia.Minf.Stbl
1✔
143
        samples := make([]mp4.FullSample, 0, endSampleNr-startSampleNr+1)
1✔
144
        mdat := mp4f.Mdat
1✔
145
        mdatPayloadStart := mdat.PayloadAbsoluteOffset()
1✔
146
        for sampleNr := startSampleNr; sampleNr <= endSampleNr; sampleNr++ {
2✔
147
                chunkNr, sampleNrAtChunkStart, err := stbl.Stsc.ChunkNrFromSampleNr(int(sampleNr))
1✔
148
                if err != nil {
1✔
UNCOV
149
                        return nil, err
×
UNCOV
150
                }
×
151
                var offset int64
1✔
152
                if stbl.Stco != nil {
2✔
153
                        offset = int64(stbl.Stco.ChunkOffset[chunkNr-1])
1✔
154
                } else if stbl.Co64 != nil {
1✔
UNCOV
155
                        offset = int64(stbl.Co64.ChunkOffset[chunkNr-1])
×
UNCOV
156
                }
×
157

158
                for sNr := sampleNrAtChunkStart; sNr < int(sampleNr); sNr++ {
2✔
159
                        offset += int64(stbl.Stsz.GetSampleSize(sNr))
1✔
160
                }
1✔
161
                size := stbl.Stsz.GetSampleSize(int(sampleNr))
1✔
162
                decTime, dur := stbl.Stts.GetDecodeTime(sampleNr)
1✔
163
                var cto int32 = 0
1✔
164
                if stbl.Ctts != nil {
2✔
165
                        cto = stbl.Ctts.GetCompositionTimeOffset(sampleNr)
1✔
166
                }
1✔
167
                var sampleData []byte
1✔
168
                // Next find bytes as slice in mdat
1✔
169
                if mdat.GetLazyDataSize() > 0 {
1✔
170
                        _, err := rs.Seek(offset, io.SeekStart)
×
171
                        if err != nil {
×
172
                                return nil, err
×
173
                        }
×
174
                        sampleData = make([]byte, size)
×
175
                        _, err = io.ReadFull(rs, sampleData)
×
176
                        if err != nil {
×
UNCOV
177
                                return nil, err
×
UNCOV
178
                        }
×
179
                } else {
1✔
180
                        offsetInMdatData := uint64(offset) - mdatPayloadStart
1✔
181
                        sampleData = mdat.Data[offsetInMdatData : offsetInMdatData+uint64(size)]
1✔
182
                }
1✔
183

184
                //presTime := uint64(int64(decTime) + int64(cto))
185
                //One can either segment on presentationTime or DecodeTime
186
                //presTimeMs := presTime * 1000 / uint64(tr.timeScale)
187
                sc := mp4.FullSample{
1✔
188
                        Sample: mp4.Sample{
1✔
189
                                Flags:                 TranslateSampleFlagsForFragment(stbl, sampleNr),
1✔
190
                                Size:                  size,
1✔
191
                                Dur:                   dur,
1✔
192
                                CompositionTimeOffset: cto,
1✔
193
                        },
1✔
194
                        DecodeTime: decTime,
1✔
195
                        Data:       sampleData,
1✔
196
                }
1✔
197

1✔
198
                //fmt.Printf("Sample %d times %d %d, sync %v, offset %d, size %d\n", sampleNr, decTime, cto, isSync, offset, size)
1✔
199
                samples = append(samples, sc)
1✔
200
        }
201
        return samples, nil
1✔
202
}
203

204
// GetSamplesForInterval - get slice of samples with numbers startSampleNr to endSampleNr (inclusive)
205
func (s *Segmenter) GetSamplesForInterval(mp4f *mp4.File, trak *mp4.TrakBox, startSampleNr, endSampleNr uint32) ([]mp4.Sample, error) {
1✔
206
        stbl := trak.Mdia.Minf.Stbl
1✔
207
        samples := make([]mp4.Sample, 0, endSampleNr-startSampleNr+1)
1✔
208
        for sampleNr := startSampleNr; sampleNr <= endSampleNr; sampleNr++ {
2✔
209
                size := stbl.Stsz.GetSampleSize(int(sampleNr))
1✔
210
                dur := stbl.Stts.GetDur(sampleNr)
1✔
211
                var cto int32 = 0
1✔
212
                if stbl.Ctts != nil {
2✔
213
                        cto = stbl.Ctts.GetCompositionTimeOffset(sampleNr)
1✔
214
                }
1✔
215

216
                //presTime := uint64(int64(decTime) + int64(cto))
217
                //One can either segment on presentationTime or DecodeTime
218
                //presTimeMs := presTime * 1000 / uint64(trak.timeScale)
219
                sc := mp4.Sample{
1✔
220
                        Flags:                 TranslateSampleFlagsForFragment(stbl, sampleNr),
1✔
221
                        Size:                  size,
1✔
222
                        Dur:                   dur,
1✔
223
                        CompositionTimeOffset: cto,
1✔
224
                }
1✔
225

1✔
226
                //fmt.Printf("Sample %d times %d %d, sync %v, offset %d, size %d\n", sampleNr, decTime, cto, isSync, offset, size)
1✔
227
                samples = append(samples, sc)
1✔
228
        }
229
        return samples, nil
1✔
230
}
231

232
// TranslateSampleFlagsForFragment - translate sample flags from stss and sdtp to what is needed in trun
233
func TranslateSampleFlagsForFragment(stbl *mp4.StblBox, sampleNr uint32) (flags uint32) {
1✔
234
        var sampleFlags mp4.SampleFlags
1✔
235
        if stbl.Stss != nil {
2✔
236
                isSync := stbl.Stss.IsSyncSample(uint32(sampleNr))
1✔
237
                sampleFlags.SampleIsNonSync = !isSync
1✔
238
                if isSync {
2✔
239
                        sampleFlags.SampleDependsOn = 2 //2 == does not depend on others (I-picture). May be overridden by sdtp entry
1✔
240
                }
1✔
241
        }
242
        if stbl.Sdtp != nil {
1✔
243
                entry := stbl.Sdtp.Entries[uint32(sampleNr)-1] // table starts at 0, but sampleNr is one-based
×
244
                sampleFlags.IsLeading = entry.IsLeading()
×
245
                sampleFlags.SampleDependsOn = entry.SampleDependsOn()
×
246
                sampleFlags.SampleHasRedundancy = entry.SampleHasRedundancy()
×
UNCOV
247
                sampleFlags.SampleIsDependedOn = entry.SampleIsDependedOn()
×
UNCOV
248
        }
×
249
        return sampleFlags.Encode()
1✔
250
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc