Skip to content

Commit 7dcceab

Browse files
committed
Segment accurate clipping
1 parent ef418da commit 7dcceab

File tree

1 file changed

+1
-110
lines changed

1 file changed

+1
-110
lines changed

clients/manifest.go

Lines changed: 1 addition & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@ import (
66
"fmt"
77
"io"
88
"net/url"
9-
"os"
109
"path"
11-
"path/filepath"
1210
"sort"
13-
"strconv"
1411
"strings"
1512
"sync"
1613
"time"
@@ -357,124 +354,18 @@ func ClipInputManifest(requestID, sourceURL, clipTargetUrl string, startTimeUnix
357354
return nil, fmt.Errorf("error clipping: failed to download original manifest: %w", err)
358355
}
359356

360-
// Generate the absolute path URLS for segmens from the manifest's relative path
361-
// TODO: optimize later and only get absolute path URLs for the start/end segments
362-
sourceSegmentURLs, err := GetSourceSegmentURLs(sourceURL, origManifest)
363-
if err != nil {
364-
return nil, fmt.Errorf("error clipping: failed to get segment urls: %w", err)
365-
}
366-
367357
// Convert start/end time specified in UNIX time (milliseconds) to seconds wrt the first segment
368358
startTime, endTime, err := video.ConvertUnixMillisToSeconds(requestID, origManifest.Segments[0], startTimeUnixMillis, endTimeUnixMillis)
369359
if err != nil {
370360
return nil, fmt.Errorf("error clipping: failed to get start/end time offsets in seconds: %w", err)
371361
}
372362

373363
// Find the segments at the clipping start/end timestamp boundaries
374-
segs, clipsegs, err := video.ClipManifest(requestID, &origManifest, startTime, endTime)
364+
segs, _, err := video.ClipManifest(requestID, &origManifest, startTime, endTime)
375365
if err != nil {
376366
return nil, fmt.Errorf("error clipping: failed to get start/end segments: %w", err)
377367
}
378368

379-
// Only the first and last segments should be clipped.
380-
// And segs can be a single segment (if start/end times fall within the same segment)
381-
// or it can span several segments startng from start-time and spanning to end-time
382-
var segsToClip []*m3u8.MediaSegment
383-
if len(segs) == 1 {
384-
segsToClip = []*m3u8.MediaSegment{segs[0]}
385-
} else {
386-
segsToClip = []*m3u8.MediaSegment{segs[0], segs[len(segs)-1]}
387-
}
388-
// Create temp local storage dir to hold all clipping related files to upload later
389-
clipStorageDir, err := os.MkdirTemp(os.TempDir(), "clip_stage_")
390-
if err != nil {
391-
return nil, fmt.Errorf("error clipping: failed to create temp clipping storage dir: %w", err)
392-
}
393-
defer os.RemoveAll(clipStorageDir) // nolint:errcheck
394-
395-
// Download start/end segments and clip
396-
for i, v := range segsToClip {
397-
// Create temp local file to store the segments:
398-
clipSegmentFileName := filepath.Join(clipStorageDir, requestID+"_"+strconv.FormatUint(v.SeqId, 10)+".ts")
399-
defer os.Remove(clipSegmentFileName)
400-
clipSegmentFile, err := os.Create(clipSegmentFileName)
401-
if err != nil {
402-
return nil, err
403-
}
404-
defer clipSegmentFile.Close()
405-
406-
// Download the segment from OS and write to the temp local file
407-
segmentURL := sourceSegmentURLs[v.SeqId].URL
408-
dStorage := NewDStorageDownload()
409-
err = backoff.Retry(func() error {
410-
rc, err := GetFile(context.Background(), requestID, segmentURL.String(), dStorage)
411-
if err != nil {
412-
return fmt.Errorf("error clipping: failed to download segment %d: %w", v.SeqId, err)
413-
}
414-
defer rc.Close()
415-
416-
// Write the segment data to the temp local file
417-
_, err = io.Copy(clipSegmentFile, rc)
418-
if err != nil {
419-
return fmt.Errorf("error clipping: failed to write segment %d: %w", v.SeqId, err)
420-
}
421-
return nil
422-
}, DownloadRetryBackoff())
423-
if err != nil {
424-
return nil, fmt.Errorf("error clipping: failed to download or write segments to local temp storage: %w", err)
425-
}
426-
427-
// Locally clip (i.e re-encode + clip) those relevant segments at the specified start/end timestamps
428-
clippedSegmentFileName := filepath.Join(clipStorageDir, requestID+"_"+strconv.FormatUint(v.SeqId, 10)+"_clip.ts")
429-
if len(segs) == 1 {
430-
// If start/end times fall within same segment, then clip just that single segment
431-
duration := endTime - startTime
432-
err = video.ClipSegment(requestID, clipSegmentFileName, clippedSegmentFileName, clipsegs[0].ClipOffsetSecs, clipsegs[0].ClipOffsetSecs+duration)
433-
if err != nil {
434-
return nil, fmt.Errorf("error clipping: failed to clip segment %d: %w", v.SeqId, err)
435-
}
436-
} else {
437-
// If start/end times fall within different segments, then clip segment from start-time to end of segment
438-
// or clip from beginning of segment to end-time.
439-
if i == 0 {
440-
err = video.ClipSegment(requestID, clipSegmentFileName, clippedSegmentFileName, clipsegs[0].ClipOffsetSecs, -1)
441-
} else {
442-
err = video.ClipSegment(requestID, clipSegmentFileName, clippedSegmentFileName, -1, clipsegs[1].ClipOffsetSecs)
443-
}
444-
if err != nil {
445-
return nil, fmt.Errorf("error clipping: failed to clip segment %d: %w", v.SeqId, err)
446-
}
447-
}
448-
449-
// Upload clipped segment to OS
450-
clippedSegmentFile, err := os.Open(clippedSegmentFileName)
451-
if err != nil {
452-
return nil, fmt.Errorf("error clipping: failed to open clipped segment %d: %w", v.SeqId, err)
453-
}
454-
defer clippedSegmentFile.Close() // nolint:errcheck
455-
456-
clippedSegmentOSFilename := "clip_" + strconv.FormatUint(v.SeqId, 10) + ".ts"
457-
err = UploadToOSURL(clipTargetUrl, clippedSegmentOSFilename, clippedSegmentFile, MaxCopyFileDuration)
458-
if err != nil {
459-
return nil, fmt.Errorf("error clipping: failed to upload clipped segment %d: %w", v.SeqId, err)
460-
}
461-
462-
// Get duration of clipped segment(s) to use in the clipped manifest
463-
p := video.Probe{}
464-
clipSegProbe, err := p.ProbeFile(requestID, clippedSegmentFileName)
465-
if err != nil {
466-
return nil, fmt.Errorf("error clipping: failed to probe file: %w", err)
467-
}
468-
vidTrack, err := clipSegProbe.GetTrack(video.TrackTypeVideo)
469-
if err != nil {
470-
return nil, fmt.Errorf("error clipping: unknown duration of clipped segment: %w", err)
471-
}
472-
// Overwrite segs with new uri/duration. Note that these are pointers
473-
// so the start/end segments in original segs slice are directly modified
474-
v.Duration = vidTrack.DurationSec
475-
v.URI = clippedSegmentOSFilename
476-
}
477-
478369
// Generate the new clipped manifest
479370
clippedPlaylist, err := CreateClippedPlaylist(origManifest, segs)
480371
if err != nil {

0 commit comments

Comments
 (0)