diff --git a/data/transmux.ts b/data/transmux.ts new file mode 100644 index 0000000000..fb7b0a84e9 Binary files /dev/null and b/data/transmux.ts differ diff --git a/ffmpeg/api_test.go b/ffmpeg/api_test.go index a731ddfa26..be66982dbe 100644 --- a/ffmpeg/api_test.go +++ b/ffmpeg/api_test.go @@ -1291,15 +1291,19 @@ func TestTranscoder_OutputFPS(t *testing.T) { } func TestTranscoderAPI_ClipInvalidConfig(t *testing.T) { + run, dir := setupTest(t) + cmd := ` + cp "$1"/../transcoder/test.ts .` + run(cmd) + defer os.RemoveAll(dir) tc := NewTranscoder() defer tc.StopTranscoder() - in := &TranscodeOptionsIn{} + in := &TranscodeOptionsIn{Fname: fmt.Sprintf("%s/test.ts", dir)} out := []TranscodeOptions{{ Oname: "-", VideoEncoder: ComponentOptions{Name: "drop"}, From: time.Second, }} - _, err := tc.Transcode(in, out) if err == nil || err != ErrTranscoderClipConfig { t.Errorf("Expected '%s', got %v", ErrTranscoderClipConfig, err) diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go index ca4ebcfc27..0c2115f3b3 100755 --- a/ffmpeg/ffmpeg.go +++ b/ffmpeg/ffmpeg.go @@ -554,12 +554,10 @@ func (l *CodingSizeLimit) Clamp(p *VideoProfile, format MediaFormatInfo) error { adjustedHeight.H = clamp(h, l.HeightMin, l.HeightMax) adjustedHeight.W = format.ScaledWidth(adjustedHeight.H) if adjustedWidth.Valid(l) { - glog.Infof("[valid] profile %dx%d input=%dx%d accepted %dx%d\n", w, h, format.Width, format.Height, adjustedWidth.W, adjustedWidth.H) p.Resolution = fmt.Sprintf("%dx%d", adjustedWidth.W, adjustedWidth.H) return nil } if adjustedHeight.Valid(l) { - glog.Infof("[valid] profile %dx%d input=%dx%d accepted %dx%d\n", w, h, format.Width, format.Height, adjustedHeight.W, adjustedHeight.H) p.Resolution = fmt.Sprintf("%dx%d", adjustedHeight.W, adjustedHeight.H) return nil } @@ -836,20 +834,6 @@ func destroyCOutputParams(params []C.output_params) { } func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions) (*TranscodeResults, error) { - // here we require input size and aspect ratio - status, format, err := GetCodecInfo(input.Fname) - if err != nil { - return nil, err - } - if status == CodecStatusOk { - // We dont return error in case status != CodecStatusOk because proper error would be returned later in the logic. - // Like 'TranscoderInvalidVideo' or `No such file or directory` would be replaced by error we specify here. - err = ensureEncoderLimits(ps, format) - if err != nil { - return nil, err - } - } - t.mu.Lock() defer t.mu.Unlock() if t.stopped || t.handle == nil { @@ -858,6 +842,32 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions) if input == nil { return nil, ErrTranscoderInp } + // don't read metadata for pipe input, because it can't seek back and av_find_input_format in the decoder will fail + if !strings.HasPrefix(strings.ToLower(input.Fname), "pipe:") { + status, format, err := GetCodecInfo(input.Fname) + if err != nil { + return nil, err + } + if status == CodecStatusOk { + // We don't return error in case status != CodecStatusOk because proper error would be returned later in the logic. + // Like 'TranscoderInvalidVideo' or `No such file or directory` would be replaced by error we specify here. + // here we require input size and aspect ratio + err = ensureEncoderLimits(ps, format) + if err != nil { + return nil, err + } + } + if !t.started { + // NeedsBypass is state where video is present in container & without any frames + videoMissing := status == CodecStatusNeedsBypass || format.Vcodec == "" + if videoMissing { + // Audio-only segment, fail fast right here as we cannot handle them nicely + return nil, ErrTranscoderVid + } + // Stream is either OK or completely broken, let the transcoder handle it + t.started = true + } + } hw_type, err := accelDeviceType(input.Accel) if err != nil { return nil, err @@ -877,18 +887,6 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions) if input.Transmuxing { t.started = true } - if !t.started { - status, format, _ := GetCodecInfo(input.Fname) - // NeedsBypass is state where video is present in container & vithout any frames - videoMissing := status == CodecStatusNeedsBypass || format.Vcodec == "" - if videoMissing { - // Audio-only segment, fail fast right here as we cannot handle them nicely - return nil, ErrTranscoderVid - } - // Stream is either OK or completely broken, let the transcoder handle it - t.started = true - } - // Output configuration params, finalizer, err := createCOutputParams(input, ps) // This prevents C memory leaks diff --git a/ffmpeg/transmuxer_test.go b/ffmpeg/transmuxer_test.go index ae28917db6..340ebc8a9b 100644 --- a/ffmpeg/transmuxer_test.go +++ b/ffmpeg/transmuxer_test.go @@ -2,10 +2,54 @@ package ffmpeg import ( "fmt" + "io" "os" "testing" ) +func TestTransmuxer_Pipe(t *testing.T) { + run, dir := setupTest(t) + + run("cp \"$1\"/../data/transmux.ts .") + ir, iw, err := os.Pipe() + fname := fmt.Sprintf("%s/transmux.ts", dir) + _, err = os.Stat(fname) + if err != nil { + t.Fatal(err) + return + } + var bytesWritten int64 + go func(iw *os.File) { + defer iw.Close() + f, _ := os.Open(fname) + b, _ := io.Copy(iw, f) + bytesWritten += b + }(iw) + fpipe := fmt.Sprintf("pipe:%d", ir.Fd()) + oname := fmt.Sprintf("%s/test_out.ts", dir) + in := &TranscodeOptionsIn{ + Fname: fpipe, + Transmuxing: true, + } + tc := NewTranscoder() + out := []TranscodeOptions{ + { + Oname: oname, + VideoEncoder: ComponentOptions{ + Name: "copy", + }, + AudioEncoder: ComponentOptions{ + Name: "copy", + }, + Profile: VideoProfile{Format: FormatNone}, + }, + } + _, err = tc.Transcode(in, out) + if err != nil { + t.Fatal(err) + } +} + func TestTransmuxer_Join(t *testing.T) { run, dir := setupTest(t) defer os.RemoveAll(dir)