44package ffmpeg
55
66import (
7- "bytes "
7+ "context "
88 "fmt"
99 "io"
1010 "os/exec"
1111 "path/filepath"
1212 "strconv"
1313 "strings"
14+ "time"
1415)
1516
1617// Default, Maximum and Minimum Values for encoder configuration. Change these if your needs differ.
@@ -173,7 +174,7 @@ func (e *Encoder) SetSize(size string) int64 {
173174
174175// getVideoHandle is a helper function that creates and returns an ffmpeg command.
175176// This is used by higher level function to cobble together an input stream.
176- func (e * Encoder ) getVideoHandle (input , output , title string ) (string , * exec.Cmd ) {
177+ func (e * Encoder ) getVideoHandle (ctx context. Context , input , output , title string ) (string , * exec.Cmd ) {
177178 if title == "" {
178179 title = filepath .Base (output )
179180 }
@@ -220,18 +221,38 @@ func (e *Encoder) getVideoHandle(input, output, title string) (string, *exec.Cmd
220221
221222 arg = append (arg , output ) // save file path goes last.
222223
223- return strings .Join (arg , " " ), exec .Command ( arg [0 ], arg [1 :]... ) //nolint:Gosec
224+ return strings .Join (arg , " " ), exec .CommandContext ( ctx , arg [0 ], arg [1 :]... ) //nolint:Gosec
224225}
225226
226227// GetVideo retreives video from an input and returns an io.ReadCloser to consume the output.
227228// Input must be an RTSP URL. Title is encoded into the video as the "movie title."
228229// Returns command used, io.ReadCloser and error or nil.
230+ // This will automatically create a context with a timeout equal to the time duration requested plus 1 second.
231+ // If no time duration is requested the context has no timeout.
232+ // If you want to control the context, use GetVideoContext().
229233func (e * Encoder ) GetVideo (input , title string ) (string , io.ReadCloser , error ) {
234+ ctx := context .Background ()
235+
236+ if e .config .Time > 0 {
237+ var cancel func ()
238+
239+ ctx , cancel = context .WithTimeout (ctx , time .Second * time .Duration (e .config .Time + 1 ))
240+ defer cancel ()
241+ }
242+
243+ return e .GetVideoContext (ctx , input , title )
244+ }
245+
246+ // GetVideoContext retreives video from an input and returns an io.ReadCloser to consume the output.
247+ // Input must be an RTSP URL. Title is encoded into the video as the "movie title."
248+ // Returns command used, io.ReadCloser and error or nil.
249+ // Use the context to add a timeout value (max run duration) to the ffmpeg command.
250+ func (e * Encoder ) GetVideoContext (ctx context.Context , input , title string ) (string , io.ReadCloser , error ) {
230251 if input == "" {
231252 return "" , nil , ErrInvalidInput
232253 }
233254
234- cmdStr , cmd := e .getVideoHandle (input , "-" , title )
255+ cmdStr , cmd := e .getVideoHandle (ctx , input , "-" , title )
235256
236257 stdoutpipe , err := cmd .StdoutPipe ()
237258 if err != nil {
@@ -248,26 +269,42 @@ func (e *Encoder) GetVideo(input, title string) (string, io.ReadCloser, error) {
248269// SaveVideo saves a video snippet to a file.
249270// Input must be an RTSP URL and output must be a file path. It will be overwritten.
250271// Returns command used, command output and error or nil.
272+ // This will automatically create a context with a timeout equal to the time duration requested plus 1 second.
273+ // If no time duration is requested the context has no timeout.
274+ // If you want to control the context, use SaveVideoContext().
251275func (e * Encoder ) SaveVideo (input , output , title string ) (string , string , error ) {
276+ ctx := context .Background ()
277+
278+ if e .config .Time > 0 {
279+ var cancel func ()
280+
281+ ctx , cancel = context .WithTimeout (ctx , time .Second * time .Duration (e .config .Time + 1 ))
282+ defer cancel ()
283+ }
284+
285+ return e .SaveVideoContext (ctx , input , output , title )
286+ }
287+
288+ // SaveVideoContext saves a video snippet to a file using a provided context.
289+ // Input must be an RTSP URL and output must be a file path. It will be overwritten.
290+ // Returns command used, command output and error or nil.
291+ // Use the context to add a timeout value (max run duration) to the ffmpeg command.
292+ func (e * Encoder ) SaveVideoContext (ctx context.Context , input , output , title string ) (string , string , error ) {
252293 if input == "" {
253294 return "" , "" , ErrInvalidInput
254295 } else if output == "" || output == "-" {
255296 return "" , "" , ErrInvalidOutput
256297 }
257298
258- cmdStr , cmd := e .getVideoHandle (input , output , title )
299+ cmdStr , cmd := e .getVideoHandle (ctx , input , output , title )
259300 // log.Println(cmdStr) // DEBUG
260301
261- var out bytes.Buffer
262- cmd .Stderr , cmd .Stdout = & out , & out
263-
264- if err := cmd .Start (); err != nil {
265- return cmdStr , strings .TrimSpace (out .String ()), fmt .Errorf ("subcommand failed: %w" , err )
266- } else if err := cmd .Wait (); err != nil {
267- return cmdStr , strings .TrimSpace (out .String ()), fmt .Errorf ("subcommand failed: %w" , err )
302+ out , err := cmd .CombinedOutput ()
303+ if err != nil {
304+ return cmdStr , string (out ), fmt .Errorf ("subcommand failed: %w" , err )
268305 }
269306
270- return cmdStr , strings . TrimSpace (out . String () ), nil
307+ return cmdStr , string (out ), nil
271308}
272309
273310// fixValues makes sure video request values are sane.
0 commit comments