@@ -2,6 +2,7 @@ package src
2
2
3
3
import (
4
4
"bufio"
5
+ "errors"
5
6
"fmt"
6
7
"log"
7
8
"os/exec"
@@ -88,11 +89,17 @@ type KeyframeKey struct {
88
89
}
89
90
90
91
func (s * MetadataService ) GetKeyframes (info * MediaInfo , isVideo bool , idx uint32 ) (* Keyframe , error ) {
92
+ info .lock .Lock ()
93
+ var ret * Keyframe
91
94
if isVideo && info .Videos [idx ].Keyframes != nil {
92
- return info .Videos [idx ].Keyframes , nil
95
+ ret = info .Videos [idx ].Keyframes
93
96
}
94
97
if ! isVideo && info .Audios [idx ].Keyframes != nil {
95
- return info .Audios [idx ].Keyframes , nil
98
+ ret = info .Audios [idx ].Keyframes
99
+ }
100
+ info .lock .Unlock ()
101
+ if ret != nil {
102
+ return ret , nil
96
103
}
97
104
98
105
get_running , set := s .keyframeLock .Start (KeyframeKey {
@@ -110,6 +117,14 @@ func (s *MetadataService) GetKeyframes(info *MediaInfo, isVideo bool, idx uint32
110
117
}
111
118
kf .info .ready .Add (1 )
112
119
120
+ info .lock .Lock ()
121
+ if isVideo {
122
+ info .Videos [idx ].Keyframes = kf
123
+ } else {
124
+ info .Audios [idx ].Keyframes = kf
125
+ }
126
+ info .lock .Unlock ()
127
+
113
128
go func () {
114
129
var table string
115
130
var err error
@@ -122,7 +137,7 @@ func (s *MetadataService) GetKeyframes(info *MediaInfo, isVideo bool, idx uint32
122
137
}
123
138
124
139
if err != nil {
125
- log .Printf ("Couldn't retrive keyframes for %s %s %d: %v" , info .Path , table , idx , err )
140
+ log .Printf ("Couldn't retrieve keyframes for %s %s %d: %v" , info .Path , table , idx , err )
126
141
return
127
142
}
128
143
@@ -235,16 +250,89 @@ func getVideoKeyframes(path string, video_idx uint32, kf *Keyframe) error {
235
250
return nil
236
251
}
237
252
253
+ const DummyKeyframeDuration = float64 (4 )
254
+
238
255
// we can pretty much cut audio at any point so no need to get specific frames, just cut every 4s
239
256
func getAudioKeyframes (info * MediaInfo , audio_idx uint32 , kf * Keyframe ) error {
240
- dummyKeyframeDuration := float64 (4 )
241
- segmentCount := int ((float64 (info .Duration ) / dummyKeyframeDuration ) + 1 )
242
- kf .Keyframes = make ([]float64 , segmentCount )
243
- for segmentIndex := 0 ; segmentIndex < segmentCount ; segmentIndex += 1 {
244
- kf .Keyframes [segmentIndex ] = float64 (segmentIndex ) * dummyKeyframeDuration
257
+ defer printExecTime ("ffprobe keyframe analysis for %s audio n%d" , info .Path , audio_idx )()
258
+ // Format's duration CAN be different than audio's duration. To make sure we do not
259
+ // miss a segment or make one more, we need to check the audio's duration.
260
+ //
261
+ // Since fetching the duration requires reading packets and is SLOW, we start by generating
262
+ // keyframes until a reasonably safe point of the file (if the format has a 20min duration, audio
263
+ // probably has a close duration).
264
+ // You can read why duration retrieval is slow on the comment below.
265
+ safe_duration := info .Duration - 20
266
+ segment_count := int ((safe_duration / DummyKeyframeDuration ) + 1 )
267
+ if segment_count > 0 {
268
+ kf .Keyframes = make ([]float64 , segment_count )
269
+ for i := 0 ; i < segment_count ; i += 1 {
270
+ kf .Keyframes [i ] = float64 (i ) * DummyKeyframeDuration
271
+ }
272
+ kf .info .ready .Done ()
273
+ } else {
274
+ segment_count = 0
275
+ }
276
+
277
+ // Some formats DO NOT contain a duration metadata, we need to manually fetch it
278
+ // from the packets.
279
+ //
280
+ // We could use the same command to retrieve all packets and know when we can cut PRECISELY
281
+ // but since packets always contain only a few ms we don't need this precision.
282
+ cmd := exec .Command (
283
+ "ffprobe" ,
284
+ "-select_streams" , fmt .Sprintf ("a:%d" , audio_idx ),
285
+ "-show_entries" , "packet=pts_time" ,
286
+ // some avi files don't have pts, we use this to ask ffmpeg to generate them (it uses the dts under the hood)
287
+ "-fflags" , "+genpts" ,
288
+ // We use a read_interval LARGER than the file (at least we estimate)
289
+ // This allows us to only decode the LAST packets
290
+ "-read_intervals" , fmt .Sprintf ("%f" , info .Duration + 10_000 ),
291
+ "-of" , "csv=print_section=0" ,
292
+ info .Path ,
293
+ )
294
+ stdout , err := cmd .StdoutPipe ()
295
+ if err != nil {
296
+ return err
297
+ }
298
+ err = cmd .Start ()
299
+ if err != nil {
300
+ return err
301
+ }
302
+
303
+ scanner := bufio .NewScanner (stdout )
304
+ var duration float64
305
+ for scanner .Scan () {
306
+ pts := scanner .Text ()
307
+ if pts == "" || pts == "N/A" {
308
+ continue
309
+ }
310
+
311
+ duration , err = strconv .ParseFloat (pts , 64 )
312
+ if err != nil {
313
+ return err
314
+ }
315
+
316
+ }
317
+ if err := scanner .Err (); err != nil {
318
+ return err
319
+ }
320
+ if duration <= 0 {
321
+ return errors .New ("could not find audio's duration" )
322
+ }
323
+
324
+ new_seg_count := int ((duration / DummyKeyframeDuration ) + 1 )
325
+ if new_seg_count > segment_count {
326
+ new_segments := make ([]float64 , new_seg_count - segment_count )
327
+ for i := segment_count ; i < new_seg_count ; i += 1 {
328
+ new_segments [i - segment_count ] = float64 (i ) * DummyKeyframeDuration
329
+ }
330
+ kf .add (new_segments )
331
+ if segment_count == 0 {
332
+ kf .info .ready .Done ()
333
+ }
245
334
}
246
335
247
336
kf .IsDone = true
248
- kf .info .ready .Done ()
249
337
return nil
250
338
}
0 commit comments