From 4f170c399f816f8817f1b3c88b097c458f9d6962 Mon Sep 17 00:00:00 2001 From: bajankristof Date: Thu, 8 May 2025 20:09:00 +0200 Subject: [PATCH 1/3] chore: release 7.0.0 --- CHANGELOG | 4 ++++ lib/ffmpeg/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9897497..8d6daad 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +== 7.0.0 2025-05-08 + +(No changes since the last beta.) + == 7.0.0-beta.14 2025-04-25 Improvements: diff --git a/lib/ffmpeg/version.rb b/lib/ffmpeg/version.rb index a2de057..b7e7d01 100644 --- a/lib/ffmpeg/version.rb +++ b/lib/ffmpeg/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module FFMPEG - VERSION = '7.0.0-beta.14' + VERSION = '7.0.0' end From cdad933ee220c5946e677cca78019aea186c9ad4 Mon Sep 17 00:00:00 2001 From: bajankristof Date: Thu, 8 May 2025 20:39:56 +0200 Subject: [PATCH 2/3] feat: add new modular way to compose command arguments Refs: ARC-10735 --- lib/ffmpeg.rb | 3 + .../command_args/color_space_injection.rb | 30 +++++++++ lib/ffmpeg/command_args/composable.rb | 56 ++++++++++++++++ lib/ffmpeg/command_args/network_streaming.rb | 26 ++++++++ lib/ffmpeg/raw_command_args.rb | 38 +++++++++++ .../color_space_injection_spec.rb | 65 +++++++++++++++++++ .../command_args/network_streaming_spec.rb | 47 ++++++++++++++ spec/ffmpeg/raw_command_args_spec.rb | 49 ++++++++++++++ 8 files changed, 314 insertions(+) create mode 100644 lib/ffmpeg/command_args/color_space_injection.rb create mode 100644 lib/ffmpeg/command_args/composable.rb create mode 100644 lib/ffmpeg/command_args/network_streaming.rb create mode 100644 spec/ffmpeg/command_args/color_space_injection_spec.rb create mode 100644 spec/ffmpeg/command_args/network_streaming_spec.rb diff --git a/lib/ffmpeg.rb b/lib/ffmpeg.rb index ae125e4..c8e276e 100644 --- a/lib/ffmpeg.rb +++ b/lib/ffmpeg.rb @@ -6,6 +6,9 @@ require 'shellwords' require_relative 'ffmpeg/command_args' +require_relative 'ffmpeg/command_args/color_space_injection' +require_relative 'ffmpeg/command_args/composable' +require_relative 'ffmpeg/command_args/network_streaming' require_relative 'ffmpeg/errors' require_relative 'ffmpeg/filter' require_relative 'ffmpeg/filters/format' diff --git a/lib/ffmpeg/command_args/color_space_injection.rb b/lib/ffmpeg/command_args/color_space_injection.rb new file mode 100644 index 0000000..ebf8bd3 --- /dev/null +++ b/lib/ffmpeg/command_args/color_space_injection.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require_relative 'composable' + +module FFMPEG + class CommandArgs + # The ColorSpaceInjection composable contains logic for injecting + # color space metadata into video streams by taking a wild guess at the + # color space (uses bt709 for H.264 and HEVC). + # This composable is best used as an input argument composer. + # See https://trac.ffmpeg.org/ticket/11020 for more information. + module ColorSpaceInjection + include FFMPEG::CommandArgs::Composable + + compose do + next unless media.video_streams? + next unless %w[h264 hevc].include?(media.video_codec_name) + next unless media.color_space == 'reserved' + + bitstream_filter FFMPEG::Filter.new( + :video, + "#{media.video_codec_name}_metadata", + colour_primaries: 1, + transfer_characteristics: 1, + matrix_coefficients: 1 + ) + end + end + end +end diff --git a/lib/ffmpeg/command_args/composable.rb b/lib/ffmpeg/command_args/composable.rb new file mode 100644 index 0000000..d20a204 --- /dev/null +++ b/lib/ffmpeg/command_args/composable.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'securerandom' + +require_relative '../command_args' + +module FFMPEG + class CommandArgs + # The Composable module allows for composing command arguments in a modular way. + module Composable + module ClassMethods # rubocop:disable Style/Documentation + attr_reader :blocks + + # Defines a block of code that can be composed into command arguments. + # Multiple blocks can be defined with different names, + # and they can be used to compose command arguments in a modular way. + # + # @param name [Object] The name of the block. + # @param block [Proc] The block of code to be executed in context of the command arguments. + # @return [self] + # + # @example + # module MyCommandArgs + # include FFMPEG::CommandArgs::Composable + # + # compose :h264 do + # video_codec_name 'libx264' + # end + # + # compose :aac do + # audio_codec_name 'aac' + # end + # end + # + # args = FFMPEG::RawCommandArgs.compose do + # use MyCommandArgs, only: %i[h264] + # end + # args.to_s # "-c:v libx264" + def compose(name = SecureRandom.hex(4), &block) + return unless block_given? + + @blocks ||= {} + @blocks[name] = block + + self + end + end + + class << self + def included(base) + base.extend(ClassMethods) + end + end + end + end +end diff --git a/lib/ffmpeg/command_args/network_streaming.rb b/lib/ffmpeg/command_args/network_streaming.rb new file mode 100644 index 0000000..9596683 --- /dev/null +++ b/lib/ffmpeg/command_args/network_streaming.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative 'composable' + +module FFMPEG + class CommandArgs + # The NetworkStreaming composable contains some defaults + # for network streaming operations. + # This composable is best used as an input argument composer. + module NetworkStreaming + include FFMPEG::CommandArgs::Composable + + compose do + next unless media.remote? + + reconnect 1 + reconnect_at_eof 1 + reconnect_streamed 1 + reconnect_delay_max 30 + reconnect_on_network_error 1 + reconnect_on_http_error '500,502,503,504' + reconnect_on_timeout 1 + end + end + end +end diff --git a/lib/ffmpeg/raw_command_args.rb b/lib/ffmpeg/raw_command_args.rb index 17ddd81..1b49792 100644 --- a/lib/ffmpeg/raw_command_args.rb +++ b/lib/ffmpeg/raw_command_args.rb @@ -96,6 +96,44 @@ def to_a @args end + # Adds a composable to the command arguments. + # + # @param composable [Module] The composable to add. + # @param only [Array] The names of the blocks to include. + # @param except [Array] The names of the blocks to exclude. + # @return [self] + # + # @example + # module MyCommandArgs + # include FFMPEG::CommandArgs::Composable + # + # compose :h264 do + # video_codec_name 'libx264' + # end + # + # compose :aac do + # audio_codec_name 'aac' + # end + # end + # + # args = FFMPEG::RawCommandArgs.compose do + # use MyCommandArgs, only: %i[h264] + # end + # args.to_s # "-c:v libx264" + def use(composable, only: nil, except: nil) + only = [only].compact unless only.is_a?(Array) + except = [except].compact unless except.is_a?(Array) + + composable.blocks&.each do |name, block| + next if !only.empty? && !only.include?(name) + next if !except.empty? && except.include?(name) + + instance_exec(&block) + end + + self + end + # ==================== # # === COMMON UTILS === # # ==================== # diff --git a/spec/ffmpeg/command_args/color_space_injection_spec.rb b/spec/ffmpeg/command_args/color_space_injection_spec.rb new file mode 100644 index 0000000..286be83 --- /dev/null +++ b/spec/ffmpeg/command_args/color_space_injection_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require_relative '../../spec_helper' + +module FFMPEG + class CommandArgs + describe ColorSpaceInjection do + let(:media) { instance_double(FFMPEG::Media) } + + subject(:args) do + CommandArgs.compose(media) do + use ColorSpaceInjection + end.to_a + end + + context 'when the media has no video streams' do + before { allow(media).to receive(:video_streams?).and_return(false) } + + it 'does not apply color space injection arguments' do + expect(args).to be_empty + end + end + + context 'when the media video codec is not H.264 or HEVC' do + before do + allow(media).to receive(:video_streams?).and_return(true) + allow(media).to receive(:video_codec_name).and_return('vp9') + end + + it 'does not apply color space injection arguments' do + expect(args).to be_empty + end + end + + context 'when the media color space is not reserved' do + before do + allow(media).to receive(:video_streams?).and_return(true) + allow(media).to receive(:video_codec_name).and_return('h264') + allow(media).to receive(:color_space).and_return('bt709') + end + + it 'does not apply color space injection arguments' do + expect(args).to be_empty + end + end + + context 'when the media meets all conditions for color space injection' do + before do + allow(media).to receive(:video_streams?).and_return(true) + allow(media).to receive(:video_codec_name).and_return('h264') + allow(media).to receive(:color_space).and_return('reserved') + end + + it 'applies color space injection arguments' do + expect(args).to eq( + %w[ + -bsf:v + h264_metadata=colour_primaries=1:transfer_characteristics=1:matrix_coefficients=1 + ] + ) + end + end + end + end +end diff --git a/spec/ffmpeg/command_args/network_streaming_spec.rb b/spec/ffmpeg/command_args/network_streaming_spec.rb new file mode 100644 index 0000000..02f7f16 --- /dev/null +++ b/spec/ffmpeg/command_args/network_streaming_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative '../../spec_helper' + +module FFMPEG + class CommandArgs + describe NetworkStreaming do + let(:path) { fixture_media_file('napoleon.mp3', remote: true) } + let(:media) { FFMPEG::Media.new(path) } + + subject(:args) do + CommandArgs.compose(media) do + use NetworkStreaming + end.to_a + end + + before { start_web_server } + after { stop_web_server } + + context 'when the media is not remote' do + let(:path) { fixture_media_file('napoleon.mp3') } + + it 'does not apply network streaming arguments' do + expect(args).to be_empty + end + end + + context 'when the media is remote' do + it 'applies network streaming arguments' do + expect(args).to( + eq( + %w[ + -reconnect 1 + -reconnect_at_eof 1 + -reconnect_streamed 1 + -reconnect_delay_max 30 + -reconnect_on_network_error 1 + -reconnect_on_http_error 500,502,503,504 + -reconnect_on_timeout 1 + ] + ) + ) + end + end + end + end +end diff --git a/spec/ffmpeg/raw_command_args_spec.rb b/spec/ffmpeg/raw_command_args_spec.rb index 37aff1d..50151e9 100644 --- a/spec/ffmpeg/raw_command_args_spec.rb +++ b/spec/ffmpeg/raw_command_args_spec.rb @@ -6,6 +6,55 @@ module FFMPEG describe RawCommandArgs do subject { RawCommandArgs.new } + describe '#use' do + let(:composable) do + Module.new do + include FFMPEG::CommandArgs::Composable + + compose :foo do + foo 1 + end + + compose :bar do + bar 1 + end + end + end + + it 'uses a composable to generate arguments in a modular way' do + subject.use composable + expect(subject.to_a).to eq(%w[-foo 1 -bar 1]) + end + + context 'when given a non-array `only` keyword argument' do + it 'uses the composable to generate arguments with the specified only keyword' do + subject.use composable, only: :foo + expect(subject.to_a).to eq(%w[-foo 1]) + end + end + + context 'when given an array `only` keyword argument' do + it 'uses the composable to generate arguments with the specified only keyword' do + subject.use composable, only: %i[foo bar] + expect(subject.to_a).to eq(%w[-foo 1 -bar 1]) + end + end + + context 'when given a non-array `except` keyword argument' do + it 'uses the composable to generate arguments with the specified except keyword' do + subject.use composable, except: :foo + expect(subject.to_a).to eq(%w[-bar 1]) + end + end + + context 'when given an array `except` keyword argument' do + it 'uses the composable to generate arguments with the specified except keyword' do + subject.use composable, except: %i[foo bar] + expect(subject.to_a).to eq([]) + end + end + end + describe '#arg' do it 'adds the argument' do subject.arg('foo', 'bar') From 657e5deaa16e593e2a38d3a929fc8fe148423fc3 Mon Sep 17 00:00:00 2001 From: bajankristof Date: Thu, 8 May 2025 20:41:27 +0200 Subject: [PATCH 3/3] chore: update version to 7.1.0 and document changes --- CHANGELOG | 7 ++++++- lib/ffmpeg/version.rb | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8d6daad..c3d67cd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,9 @@ -== 7.0.0 2025-05-08 +== 7.1.0 2025-05-09 + +Improvements: +* Added new modular way of composing ffmpeg commands using the `FFMPEG::CommandArgs::Composable` module. + +== 7.0.0 2025-05-09 (No changes since the last beta.) diff --git a/lib/ffmpeg/version.rb b/lib/ffmpeg/version.rb index b7e7d01..f14bab0 100644 --- a/lib/ffmpeg/version.rb +++ b/lib/ffmpeg/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module FFMPEG - VERSION = '7.0.0' + VERSION = '7.1.0' end