Skip to content

Commit 288957d

Browse files
committed
feat: introduce inline remux when there's no output path provided
1 parent e9208d9 commit 288957d

2 files changed

Lines changed: 144 additions & 5 deletions

File tree

lib/ffmpeg/media.rb

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,22 +85,42 @@ def initialize(path, *ffprobe_args, load: true, autoload: true)
8585
# extraction, it falls back to extracting raw streams and re-muxing with
8686
# a corrected frame rate.
8787
#
88-
# @param output_path [String, Pathname] The output path for the remuxed file.
88+
# @param output_path [String, Pathname, nil] The output path for the remuxed file.
89+
# Tries an inline replacement for nil value.
8990
# @param timeout [Integer, nil] Timeout in seconds for each ffmpeg command.
9091
# @yield [report] Reports from the ffmpeg command (see FFMPEG::Reporters).
9192
# @return [FFMPEG::Transcoder::Status]
92-
def remux(output_path, timeout: nil, &block)
93-
Remuxer.new(timeout:).process(self, output_path, &block)
93+
def remux(output_path = nil, timeout: nil, &block)
94+
return Remuxer.new(timeout:).process(self, output_path, &block) if output_path
95+
raise ArgumentError if remote?
96+
97+
Dir.mktmpdir do |tmpdir|
98+
output_path = File.join(tmpdir, File.basename(@path))
99+
100+
status = Remuxer.new(timeout:).process(self, output_path, &block)
101+
102+
if status.success?
103+
File.unlink @path
104+
File.mv output_path, @path
105+
if @loaded
106+
@loaded = false
107+
load!
108+
end
109+
end
110+
111+
status
112+
end
94113
end
95114

96115
# Remuxes the media file to the given output path via stream copy,
97116
# raising an error if the remux fails.
98117
#
99-
# @param output_path [String, Pathname] The output path for the remuxed file.
118+
# @param output_path [String, Pathname, nil] The output path for the remuxed file.
119+
# Tries an inline replacement for nil value.
100120
# @param timeout [Integer, nil] Timeout in seconds for each ffmpeg command.
101121
# @yield [report] Reports from the ffmpeg command (see FFMPEG::Reporters).
102122
# @return [FFMPEG::Transcoder::Status]
103-
def remux!(output_path, timeout: nil, &block)
123+
def remux!(output_path = nil, timeout: nil, &block)
104124
remux(output_path, timeout:, &block).assert!
105125
end
106126

spec/ffmpeg/media_spec.rb

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,125 @@ module FFMPEG
594594
end
595595
end
596596

597+
describe '#remux' do
598+
context 'with an output_path' do
599+
let(:output_path) { tmp_file(ext: 'mp4') }
600+
let(:remuxer) { instance_double(Remuxer) }
601+
let(:status) { instance_double(Transcoder::Status) }
602+
603+
before do
604+
allow(Remuxer).to receive(:new).and_return(remuxer)
605+
allow(remuxer).to receive(:process).and_return(status)
606+
end
607+
608+
it 'delegates to a new Remuxer with the given output path' do
609+
expect(Remuxer).to receive(:new).with(timeout: nil).and_return(remuxer)
610+
expect(remuxer).to receive(:process).with(subject, output_path).and_return(status)
611+
expect(subject.remux(output_path)).to be(status)
612+
end
613+
614+
it 'passes the timeout option to the Remuxer' do
615+
timeout = rand(999)
616+
expect(Remuxer).to receive(:new).with(timeout: timeout).and_return(remuxer)
617+
expect(remuxer).to receive(:process).with(subject, output_path).and_return(status)
618+
subject.remux(output_path, timeout: timeout)
619+
end
620+
end
621+
622+
context 'without an output_path' do
623+
context 'when the media is remote' do
624+
let(:path) { fixture_media_file('landscape@4k60.mp4', remote: true) }
625+
626+
it 'raises ArgumentError' do
627+
expect { subject.remux }.to raise_error(ArgumentError)
628+
end
629+
end
630+
631+
context 'when the media is local' do
632+
let(:path) do
633+
dst = tmp_file(ext: 'mp4')
634+
FileUtils.cp(fixture_media_file('widescreen-no-audio.mp4'), dst)
635+
dst
636+
end
637+
638+
let(:remuxer) { instance_double(Remuxer) }
639+
640+
before do
641+
allow(Remuxer).to receive(:new).and_return(remuxer)
642+
end
643+
644+
context 'when the remux succeeds' do
645+
let(:remux_status) { instance_double(Transcoder::Status, success?: true) }
646+
647+
before do
648+
allow(remuxer).to receive(:process).and_return(remux_status)
649+
allow(File).to receive(:unlink)
650+
allow(File).to receive(:mv)
651+
end
652+
653+
it 'unlinks the original file and moves the remuxed output in its place' do
654+
expect(File).to receive(:unlink).with(path)
655+
expect(File).to receive(:mv).with(an_instance_of(String), path)
656+
subject.remux
657+
end
658+
659+
it 'reloads the media metadata' do
660+
subject # force initialization before setting expectation
661+
expect(subject).to receive(:load!).once.and_call_original
662+
subject.remux
663+
end
664+
665+
it 'returns the status' do
666+
expect(subject.remux).to be(remux_status)
667+
end
668+
end
669+
670+
context 'when the remux fails' do
671+
let(:remux_status) { instance_double(Transcoder::Status, success?: false) }
672+
673+
before do
674+
allow(remuxer).to receive(:process).and_return(remux_status)
675+
end
676+
677+
it 'does not modify the original file' do
678+
expect(File).not_to receive(:unlink)
679+
expect(File).not_to receive(:mv)
680+
subject.remux
681+
end
682+
683+
it 'returns the status' do
684+
expect(subject.remux).to be(remux_status)
685+
end
686+
end
687+
end
688+
end
689+
end
690+
691+
describe '#remux!' do
692+
context 'with an output_path' do
693+
it 'calls assert! on the result of #remux' do
694+
output_path = tmp_file(ext: 'mp4')
695+
status = instance_double(Transcoder::Status)
696+
697+
expect(subject).to receive(:remux).with(output_path, timeout: nil).and_return(status)
698+
expect(status).to receive(:assert!).and_return(status)
699+
700+
subject.remux!(output_path)
701+
end
702+
end
703+
704+
context 'without an output_path' do
705+
it 'calls assert! on the result of #remux without output_path' do
706+
status = instance_double(Transcoder::Status)
707+
708+
expect(subject).to receive(:remux).with(nil, timeout: nil).and_return(status)
709+
expect(status).to receive(:assert!).and_return(status)
710+
711+
subject.remux!
712+
end
713+
end
714+
end
715+
597716
describe '#ffmpeg_execute' do
598717
it 'executes a ffmpeg command with the media as input' do
599718
reports = []

0 commit comments

Comments
 (0)