@@ -195,28 +195,36 @@ func (e *Encoder) SetSize(size string) int64 {
195195// If you want to control the context, use GetVideoContext().
196196func (e * Encoder ) GetVideo (input , title string ) (string , io.ReadCloser , error ) {
197197 ctx := context .Background ()
198- var cancel func ()
198+
199+ var cancel context.CancelFunc
199200
200201 if e .config .Time > 0 {
201202 ctx , cancel = context .WithTimeout (ctx , time .Second * time .Duration (e .config .Time + 1 ))
202203 }
203204
204- return e .getVideoContext (ctx , cancel , input , title )
205+ cmdStr , stream , err := e .GetVideoContext (ctx , input , title )
206+ if err != nil {
207+ if cancel != nil {
208+ cancel ()
209+ }
210+
211+ return cmdStr , nil , err
212+ }
213+
214+ if cancel == nil {
215+ return cmdStr , stream , nil
216+ }
217+
218+ return cmdStr , & cancelReadCloser {ReadCloser : stream , cancel : cancel }, nil
205219}
206220
207221// GetVideoContext retreives video from an input and returns an io.ReadCloser to consume the output.
208222// Input must be an RTSP URL. Title is encoded into the video as the "movie title."
209223// Returns command used for diagnostics, io.ReadCloser and error or nil.
210224// Use the context to add a timeout value (max run duration) to the ffmpeg command.
225+ //
226+ //nolint:contextcheck // caller-provided context is accepted and used for command execution.
211227func (e * Encoder ) GetVideoContext (ctx context.Context , input , title string ) (string , io.ReadCloser , error ) {
212- return e .getVideoContext (ctx , nil , input , title )
213- }
214-
215- func (e * Encoder ) getVideoContext (
216- ctx context.Context ,
217- parentCancel context.CancelFunc ,
218- input , title string ,
219- ) (string , io.ReadCloser , error ) {
220228 if input == "" {
221229 return "" , nil , ErrInvalidInput
222230 }
@@ -233,35 +241,30 @@ func (e *Encoder) getVideoContext(
233241 stdoutpipe , err := cmd .StdoutPipe ()
234242 if err != nil {
235243 cmdCancel ()
236- if parentCancel != nil {
237- parentCancel ()
238- }
239244
240245 return cmdStr , nil , fmt .Errorf ("subcommand failed: %w" , err )
241246 }
242247
243248 err = cmd .Start ()
244249 if err != nil {
245250 _ = stdoutpipe .Close ()
251+
246252 cmdCancel ()
247- if parentCancel != nil {
248- parentCancel ()
249- }
250253
251254 return cmdStr , nil , withStderr ("run failed" , err , stderr .String ())
252255 }
253256
254257 done := make (chan error , 1 )
258+
255259 go func () {
256260 done <- cmd .Wait ()
257261 }()
258262
259263 return cmdStr , & streamResult {
260- out : stdoutpipe ,
261- done : done ,
262- cmdCancel : cmdCancel ,
263- parentCancel : parentCancel ,
264- stderr : stderr ,
264+ out : stdoutpipe ,
265+ done : done ,
266+ cmdCancel : cmdCancel ,
267+ stderr : stderr ,
265268 }, nil
266269}
267270
@@ -305,7 +308,9 @@ func (e *Encoder) SaveVideoContext(
305308
306309 cmdStr , cmd := e .getVideoHandle (ctx , input , output , title )
307310 stderr := newTailBuffer (defaultStderrTail )
311+
308312 var stdout bytes.Buffer
313+
309314 cmd .Stdout = & stdout
310315 cmd .Stderr = stderr
311316
@@ -433,27 +438,37 @@ func (e *Encoder) getVideoHandle(ctx context.Context, input, output, title strin
433438
434439// streamResult is our custom io.ReadCloser that also cleans up the command and context.
435440type streamResult struct {
436- out io.ReadCloser
437- done <- chan error
438- cmdCancel context.CancelFunc
439- parentCancel context.CancelFunc
440- stderr * tailBuffer
441- closeOnce sync.Once
442- closeErr error
441+ out io.ReadCloser
442+ done <- chan error
443+ cmdCancel context.CancelFunc
444+ stderr * tailBuffer
445+ closeOnce sync.Once
446+ closeErr error
443447}
444448
445- func (s * streamResult ) Read (p []byte ) (int , error ) {
446- return s .out .Read (p )
449+ func (s * streamResult ) Read (data []byte ) (int , error ) {
450+ bytesRead , err := s .out .Read (data )
451+ if err == nil {
452+ return bytesRead , nil
453+ }
454+
455+ if errors .Is (err , io .EOF ) {
456+ return bytesRead , io .EOF
457+ }
458+
459+ if err != nil {
460+ return bytesRead , fmt .Errorf ("read stream: %w" , err )
461+ }
462+
463+ return bytesRead , nil
447464}
448465
449466func (s * streamResult ) Close () error {
450467 s .closeOnce .Do (func () {
451468 s .cmdCancel ()
452- if s .parentCancel != nil {
453- s .parentCancel ()
454- }
455469
456470 _ = s .out .Close ()
471+
457472 waitErr := <- s .done
458473 if waitErr != nil && ! errors .Is (waitErr , context .Canceled ) {
459474 s .closeErr = withStderr ("run failed" , waitErr , s .stderr .String ())
@@ -468,29 +483,29 @@ type tailBuffer struct {
468483 max int
469484}
470485
471- func newTailBuffer (max int ) * tailBuffer {
472- return & tailBuffer {max : max }
486+ func newTailBuffer (limit int ) * tailBuffer {
487+ return & tailBuffer {max : limit }
473488}
474489
475- func (t * tailBuffer ) Write (p []byte ) (int , error ) {
490+ func (t * tailBuffer ) Write (data []byte ) (int , error ) {
476491 if t .max <= 0 {
477- return len (p ), nil
492+ return len (data ), nil
478493 }
479494
480- if len (p ) >= t .max {
481- t .buf = append (t .buf [:0 ], p [len (p )- t .max :]... )
495+ if len (data ) >= t .max {
496+ t .buf = append (t .buf [:0 ], data [len (data )- t .max :]... )
482497
483- return len (p ), nil
498+ return len (data ), nil
484499 }
485500
486- need := len (t .buf ) + len (p ) - t .max
501+ need := len (t .buf ) + len (data ) - t .max
487502 if need > 0 {
488503 t .buf = append (t .buf [:0 ], t .buf [need :]... )
489504 }
490505
491- t .buf = append (t .buf , p ... )
506+ t .buf = append (t .buf , data ... )
492507
493- return len (p ), nil
508+ return len (data ), nil
494509}
495510
496511func (t * tailBuffer ) String () string {
@@ -504,3 +519,25 @@ func withStderr(prefix string, err error, stderr string) error {
504519
505520 return fmt .Errorf ("%s: %w: %s" , prefix , err , stderr )
506521}
522+
523+ type cancelReadCloser struct {
524+ io.ReadCloser
525+
526+ cancel context.CancelFunc
527+ closeOnce sync.Once
528+ }
529+
530+ func (c * cancelReadCloser ) Close () error {
531+ var err error
532+
533+ c .closeOnce .Do (func () {
534+ err = c .ReadCloser .Close ()
535+ c .cancel ()
536+ })
537+
538+ if err != nil {
539+ return fmt .Errorf ("close stream: %w" , err )
540+ }
541+
542+ return nil
543+ }
0 commit comments