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

Eyevinn / mp4ff / 19964749626

05 Dec 2025 01:40PM UTC coverage: 81.538% (+0.04%) from 81.502%
19964749626

push

github

tobbee
chore: remove Github action for mac-os with Go 1.16

17198 of 21092 relevant lines covered (81.54%)

0.9 hits per line

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

76.24
/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
                outTrak := init.AddEmptyTrack(tr.timeScale, tr.trackType, tr.lang)
1✔
111
                tr.trackID = outTrak.Tkhd.TrackID
1✔
112
                inStsd := tr.inTrak.Mdia.Minf.Stbl.Stsd
1✔
113
                outStsd := outTrak.Mdia.Minf.Stbl.Stsd
1✔
114
                switch tr.trackType {
1✔
115
                case "audio":
1✔
116
                        if inStsd.Mp4a != nil {
2✔
117
                                outStsd.AddChild(inStsd.Mp4a)
1✔
118
                        } else if inStsd.AC3 != nil {
1✔
119
                                outStsd.AddChild(inStsd.AC3)
×
120
                        } else if inStsd.EC3 != nil {
×
121
                                outStsd.AddChild(inStsd.EC3)
×
122
                        }
×
123
                case "video":
1✔
124
                        if inStsd.AvcX != nil {
2✔
125
                                outStsd.AddChild(inStsd.AvcX)
1✔
126
                        } else if inStsd.HvcX != nil {
1✔
127
                                outStsd.AddChild(inStsd.HvcX)
×
128
                        }
×
129
                default:
×
130
                        return nil, fmt.Errorf("unsupported tracktype: %s", tr.trackType)
×
131
                }
132
        }
133

134
        return init, nil
1✔
135
}
136

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

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

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

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

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

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

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

230
// TranslateSampleFlagsForFragment - translate sample flags from stss and sdtp to what is needed in trun
231
func TranslateSampleFlagsForFragment(stbl *mp4.StblBox, sampleNr uint32) (flags uint32) {
1✔
232
        var sampleFlags mp4.SampleFlags
1✔
233
        if stbl.Stss != nil {
2✔
234
                isSync := stbl.Stss.IsSyncSample(uint32(sampleNr))
1✔
235
                sampleFlags.SampleIsNonSync = !isSync
1✔
236
                if isSync {
2✔
237
                        sampleFlags.SampleDependsOn = 2 //2 == does not depend on others (I-picture). May be overridden by sdtp entry
1✔
238
                }
1✔
239
        }
240
        if stbl.Sdtp != nil {
1✔
241
                entry := stbl.Sdtp.Entries[uint32(sampleNr)-1] // table starts at 0, but sampleNr is one-based
×
242
                sampleFlags.IsLeading = entry.IsLeading()
×
243
                sampleFlags.SampleDependsOn = entry.SampleDependsOn()
×
244
                sampleFlags.SampleHasRedundancy = entry.SampleHasRedundancy()
×
245
                sampleFlags.SampleIsDependedOn = entry.SampleIsDependedOn()
×
246
        }
×
247
        return sampleFlags.Encode()
1✔
248
}
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