Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
== 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.)

== 7.0.0-beta.14 2025-04-25

Improvements:
Expand Down
3 changes: 3 additions & 0 deletions lib/ffmpeg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
30 changes: 30 additions & 0 deletions lib/ffmpeg/command_args/color_space_injection.rb
Original file line number Diff line number Diff line change
@@ -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
56 changes: 56 additions & 0 deletions lib/ffmpeg/command_args/composable.rb
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions lib/ffmpeg/command_args/network_streaming.rb
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions lib/ffmpeg/raw_command_args.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 === #
# ==================== #
Expand Down
2 changes: 1 addition & 1 deletion lib/ffmpeg/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module FFMPEG
VERSION = '7.0.0-beta.14'
VERSION = '7.1.0'
end
65 changes: 65 additions & 0 deletions spec/ffmpeg/command_args/color_space_injection_spec.rb
Original file line number Diff line number Diff line change
@@ -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
47 changes: 47 additions & 0 deletions spec/ffmpeg/command_args/network_streaming_spec.rb
Original file line number Diff line number Diff line change
@@ -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
49 changes: 49 additions & 0 deletions spec/ffmpeg/raw_command_args_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Loading