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
8 changes: 8 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
== 8.1.0-beta.5 2025-10-31

Improvements:
* Added audio channels configuration to built-in presets.

Fixes:
* Fixed MPEG-DASH timing issues by using avoid_negative_ts 'make_zero' instead of the asetpts filter.

== 8.1.0-beta.4 2025-10-10

Improvements:
Expand Down
44 changes: 44 additions & 0 deletions lib/ffmpeg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def ffmpeg_binary=(path)
end

@ffmpeg_binary = path
@ffmpeg_version = nil
end

# Get the path to the ffmpeg binary.
Expand All @@ -110,6 +111,27 @@ def ffmpeg_binary
@ffmpeg_binary ||= which('ffmpeg')
end

# Get the version of the ffmpeg binary.
#
# @return [String] The version string (e.g., "4.4.6", "8.0")
def ffmpeg_version
@ffmpeg_version ||= begin
stdout, = FFMPEG::IO.capture3(ffmpeg_binary, '-version')
stdout[/ffmpeg version (\d+\.\d+(?:\.\d+)?)/i, 1]
end
end

# Check if the ffmpeg version matches the given pattern.
#
# @param pattern [String, Regexp] The version pattern to match.
# @return [Boolean] True if the ffmpeg version matches the pattern, false otherwise.
def ffmpeg_version?(pattern)
return false unless ffmpeg_version
return pattern.match?(ffmpeg_version) if pattern.is_a?(Regexp)

ffmpeg_version.start_with?(pattern.to_s)
end

# Safely captures the standard output and the standard error of the ffmpeg command.
#
# @param args [Array<String>] The arguments to pass to ffmpeg.
Expand Down Expand Up @@ -198,6 +220,28 @@ def ffprobe_binary=(path)
end

@ffprobe_binary = path
@ffprobe_version = nil
end

# Get the version of the ffprobe binary.
#
# @return [String] The version string (e.g., "4.4.6", "8.0")
def ffprobe_version
@ffprobe_version ||= begin
stdout, = FFMPEG::IO.capture3(ffprobe_binary, '-version')
stdout[/ffprobe version (\d+\.\d+(?:\.\d+)?)/i, 1]
end
end

# Check if the ffprobe version matches the given pattern.
#
# @param pattern [String, Regexp] The version pattern to match.
# @return [Boolean] True if the ffprobe version matches the pattern, false otherwise.
def ffprobe_version?(pattern)
return false unless ffprobe_version
return pattern.match?(ffprobe_version) if pattern.is_a?(Regexp)

ffprobe_version.start_with?(pattern.to_s)
end

# Safely captures the standard output and the standard error of the ffmpeg command.
Expand Down
13 changes: 12 additions & 1 deletion lib/ffmpeg/presets/aac.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def aac_128k(
metadata: nil,
threads: FFMPEG.threads,
audio_sample_rate: 48_000,
audio_channels: 2,
&
)
AAC.new(
Expand All @@ -21,6 +22,7 @@ def aac_128k(
metadata:,
threads:,
audio_sample_rate:,
audio_channels:,
audio_bit_rate: '128k',
&
)
Expand All @@ -32,6 +34,7 @@ def aac_192k(
metadata: nil,
threads: FFMPEG.threads,
audio_sample_rate: 48_000,
audio_channels: 2,
&
)
AAC.new(
Expand All @@ -40,6 +43,7 @@ def aac_192k(
metadata:,
threads:,
audio_sample_rate:,
audio_channels:,
audio_bit_rate: '192k',
&
)
Expand All @@ -51,6 +55,7 @@ def aac_320k(
metadata: nil,
threads: FFMPEG.threads,
audio_sample_rate: 48_000,
audio_channels: 2,
&
)
AAC.new(
Expand All @@ -59,6 +64,7 @@ def aac_320k(
metadata:,
threads:,
audio_sample_rate:,
audio_channels:,
audio_bit_rate: '320k',
&
)
Expand All @@ -67,12 +73,14 @@ def aac_320k(

# Preset to encode AAC audio files.
class AAC < Preset
attr_reader :threads, :audio_bit_rate, :audio_sample_rate
attr_reader :threads, :audio_bit_rate, :audio_sample_rate, :audio_channels

# @param name [String] The name of the preset.
# @param filename [String] The filename format of the output.
# @param metadata [Object] The metadata to associate with the preset.
# @param audio_bit_rate [String] The audio bit rate to use.
# @param audio_sample_rate [Integer] The audio sample rate to use.
# @param audio_channels [Integer, nil] The number of audio channels to use (nil to preserve source).
# @yield The block to execute to compose the command arguments.
def initialize(
name: nil,
Expand All @@ -81,11 +89,13 @@ def initialize(
threads: FFMPEG.threads,
audio_bit_rate: '128k',
audio_sample_rate: 48_000,
audio_channels: 2,
&
)
@threads = threads
@audio_bit_rate = audio_bit_rate
@audio_sample_rate = audio_sample_rate
@audio_channels = audio_channels
preset = self

super(name:, filename:, metadata:) do
Expand All @@ -101,6 +111,7 @@ def initialize(
map media.audio_mapping_id do
audio_bit_rate preset.audio_bit_rate
audio_sample_rate preset.audio_sample_rate
audio_channels preset.audio_channels if preset.audio_channels
end
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/ffmpeg/presets/dash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def initialize(
use_template 1
use_timeline 1
segment_duration preset.segment_duration
avoid_negative_ts 'make_zero'

muxing_flags 'frag_keyframe+empty_moov+default_base_moof'
map_chapters '-1'
Expand Down
13 changes: 12 additions & 1 deletion lib/ffmpeg/presets/dash/aac.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def aac_128k(
threads: FFMPEG.threads,
segment_duration: 4,
audio_sample_rate: 48_000,
audio_channels: 2,
&
)
AAC.new(
Expand All @@ -24,6 +25,7 @@ def aac_128k(
threads:,
segment_duration:,
audio_sample_rate:,
audio_channels:,
audio_bit_rate: '128k',
&
)
Expand All @@ -36,6 +38,7 @@ def aac_192k(
threads: FFMPEG.threads,
segment_duration: 4,
audio_sample_rate: 48_000,
audio_channels: 2,
&
)
AAC.new(
Expand All @@ -45,6 +48,7 @@ def aac_192k(
threads:,
segment_duration:,
audio_sample_rate:,
audio_channels:,
audio_bit_rate: '192k',
&
)
Expand All @@ -57,6 +61,7 @@ def aac_320k(
threads: FFMPEG.threads,
segment_duration: 4,
audio_sample_rate: 48_000,
audio_channels: 2,
&
)
AAC.new(
Expand All @@ -66,6 +71,7 @@ def aac_320k(
threads:,
segment_duration:,
audio_sample_rate:,
audio_channels:,
audio_bit_rate: '320k',
&
)
Expand All @@ -74,12 +80,14 @@ def aac_320k(

# Preset to encode DASH AAC audio files.
class AAC < DASH
attr_reader :audio_bit_rate, :audio_sample_rate
attr_reader :audio_bit_rate, :audio_sample_rate, :audio_channels

# @param name [String] The name of the preset.
# @param filename [String] The filename format of the output.
# @param metadata [Object] The metadata to associate with the preset.
# @param audio_bit_rate [String] The audio bit rate to use.
# @param audio_sample_rate [Integer] The audio sample rate to use.
# @param audio_channels [Integer, nil] The number of audio channels to use (nil to preserve source).
# @yield The block to execute to compose the command arguments.
def initialize(
name: nil,
Expand All @@ -89,10 +97,12 @@ def initialize(
segment_duration: 4,
audio_bit_rate: '128k',
audio_sample_rate: 48_000,
audio_channels: 2,
&
)
@audio_bit_rate = audio_bit_rate
@audio_sample_rate = audio_sample_rate
@audio_channels = audio_channels
preset = self

super(
Expand All @@ -111,6 +121,7 @@ def initialize(
map media.audio_mapping_id do
audio_bit_rate preset.audio_bit_rate
audio_sample_rate preset.audio_sample_rate
audio_channels preset.audio_channels if preset.audio_channels
end
end
end
Expand Down
3 changes: 1 addition & 2 deletions lib/ffmpeg/presets/dash/h264.rb
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,9 @@ def initialize(
end

map media.audio_mapping_id do
# Reset the audio stream's timestamps to start from 0.
filter Filter.new(:audio, 'asetpts', expr: 'PTS-STARTPTS')
audio_bit_rate h264_presets.first.audio_bit_rate
audio_sample_rate h264_presets.first.audio_sample_rate
audio_channels h264_presets.first.audio_channels if h264_presets.first.audio_channels
end
end
end
Expand Down
Loading