diff --git a/clients/input_copy.go b/clients/input_copy.go index 2137ac718..932455da5 100644 --- a/clients/input_copy.go +++ b/clients/input_copy.go @@ -72,6 +72,18 @@ func (s *InputCopy) CopyInputToS3(requestID string, inputFile, osTransferURL *ur log.Log(requestID, "probe succeeded", "source", inputFile.String(), "dest", osTransferURL.String()) videoTrack, err := inputFileProbe.GetTrack(video.TrackTypeVideo) hasVideoTrack := err == nil + // verify the duration of the video track and don't process if we can't determine duration + if hasVideoTrack && videoTrack.DurationSec == 0 { + duration := 0.0 + if IsHLSInput(inputFile) { + duration = getVideoTrackDuration(requestID, signedURL) + } + if duration == 0.0 { + log.Log(requestID, "input file duration is 0 or cannot be determined") + } else { + videoTrack.DurationSec = duration + } + } if hasVideoTrack { log.Log(requestID, "probed video track:", "container", inputFileProbe.Format, "codec", videoTrack.Codec, "bitrate", videoTrack.Bitrate, "duration", videoTrack.DurationSec, "w", videoTrack.Width, "h", videoTrack.Height, "pix-format", videoTrack.PixelFormat, "FPS", videoTrack.FPS) } @@ -88,6 +100,15 @@ func (s *InputCopy) CopyInputToS3(requestID string, inputFile, osTransferURL *ur return inputFileProbe, signedURL, nil } +func getVideoTrackDuration(requestID, manifestUrl string) float64 { + manifest, err := DownloadRenditionManifest(requestID, manifestUrl) + if err != nil { + return 0 + } + manifestDuration, _ := video.GetTotalDurationAndSegments(&manifest) + return manifestDuration +} + func getSignedURL(osTransferURL *url.URL) (string, error) { // check if plain https is accessible, if not then the bucket must be private and we need to generate a signed url // in most cases signed urls work fine as input but in the edge case where we have to fall back to mediaconvert diff --git a/video/clip.go b/video/clip.go index 94f0714f4..f0aeb5cc1 100644 --- a/video/clip.go +++ b/video/clip.go @@ -15,7 +15,7 @@ func formatTime(seconds float64) string { return timeObj.Format("15:04:05") } -func getTotalDurationAndSegments(manifest *m3u8.MediaPlaylist) (float64, uint64) { +func GetTotalDurationAndSegments(manifest *m3u8.MediaPlaylist) (float64, uint64) { if manifest == nil { return 0.0, 0 } @@ -60,7 +60,7 @@ func ClipManifest(requestID string, manifest *m3u8.MediaPlaylist, startTime, end var startSegIdx, endSegIdx uint64 var err error - manifestDuration, manifestSegments := getTotalDurationAndSegments(manifest) + manifestDuration, manifestSegments := GetTotalDurationAndSegments(manifest) // Find the segment index that correlates with the specified startTime // but error out it exceeds the manifest's duration. diff --git a/video/clip_test.go b/video/clip_test.go index ab3a8bc84..ba51ae0a4 100644 --- a/video/clip_test.go +++ b/video/clip_test.go @@ -49,6 +49,45 @@ const manifestC = `#EXTM3U 3.ts #EXT-X-ENDLIST` +// an example of a manifest that ffprobe fails on when +// trying to determine the duration. +const manifestD = `#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-PLAYLIST-TYPE:EVENT +#EXT-X-TARGETDURATION:11.0000000000 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PROGRAM-DATE-TIME:2023-08-28T10:17:20.948Z +#EXTINF:10.634, +source/0.ts +#EXT-X-PROGRAM-DATE-TIME:2023-08-28T10:17:31.582Z +#EXTINF:10.000, +source/1.ts +#EXT-X-PROGRAM-DATE-TIME:2023-08-28T10:17:41.582Z +#EXTINF:10.000, +source/63744.ts +#EXT-X-PROGRAM-DATE-TIME:2023-09-05T00:13:30.682Z +#EXTINF:10.000, +source/63745.ts +` + +func TestManifestDurationCalculation(t *testing.T) { + sourceManifestB, _, err := m3u8.DecodeFrom(strings.NewReader(manifestB), true) + require.NoError(t, err) + plB := sourceManifestB.(*m3u8.MediaPlaylist) + + dur, segs := GetTotalDurationAndSegments(plB) + require.Equal(t, 18.78, dur) + require.Equal(t, uint64(4), segs) + + sourceManifestD, _, err := m3u8.DecodeFrom(strings.NewReader(manifestD), true) + require.NoError(t, err) + plD := sourceManifestD.(*m3u8.MediaPlaylist) + + dur, segs = GetTotalDurationAndSegments(plD) + require.Equal(t, 40.634, dur) + require.Equal(t, uint64(4), segs) +} + func TestClippingFailsWhenInvalidManifestIsUsed(t *testing.T) { sourceManifestC, _, err := m3u8.DecodeFrom(strings.NewReader(manifestC), true)