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
1 change: 1 addition & 0 deletions .github/workflows/ci.lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [ 'main' ]
pull_request:
branches: [ 'main' ]
workflow_dispatch:

permissions:
contents: read
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [ 'main' ]
pull_request:
branches: [ 'main' ]
workflow_dispatch:

permissions:
contents: read
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
== 7.0.0-beta.12 2025-04-15

Breaking Changes:
* The `display_aspect_ratio` and `sample_aspect_ratio` methods have been renamed to
`raw_display_aspect_ratio` and `raw_sample_aspect_ratio` respectively on both
the Media and Stream classes.
* The `calculated_aspect_ratio` and `calculated_pixel_aspect_ratio` methods have been
renamed to `display_aspect_ratio` and `sample_aspect_ratio` respectively on both
the Media and Stream classes.
* Rolled back zlib scale changes (they caused more errors than what they resolved).

Improvements:
* Added more getters for color information to the Media class.
* Added more options to the scale filter.
* Greatly simplified the MPEG-DASH H.264 preset.
* Added support for stream specifiers in many RawCommandArgs methods.

== 7.0.0-beta.11 2025-04-10

Improvements:
Expand Down
81 changes: 34 additions & 47 deletions lib/ffmpeg/filters/scale.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ module FFMPEG
module Filters # rubocop:disable Style/Documentation
class << self
def scale(
zlib: false,
width: nil,
height: nil,
algorithm: nil,
Expand All @@ -19,10 +18,11 @@ def scale(
in_color_transfer: nil,
out_color_transfer: nil,
in_chroma_location: nil,
out_chroma_location: nil
out_chroma_location: nil,
force_original_aspect_ratio: nil,
force_divisible_by: nil
)
Scale.new(
zlib:,
width:,
height:,
algorithm:,
Expand All @@ -35,12 +35,14 @@ def scale(
in_color_transfer:,
out_color_transfer:,
in_chroma_location:,
out_chroma_location:
out_chroma_location:,
force_original_aspect_ratio:,
force_divisible_by:
)
end
end

# The Scale class uses the scale (or zscale) filter
# The Scale class uses the scale filter
# to resize a multimedia stream.
class Scale < Filter
NEAREST_DIMENSION = -1
Expand All @@ -54,6 +56,7 @@ class << self
# @param media [FFMPEG::Media] The media to fit.
# @param max_width [Numeric] The maximum width to fit.
# @param max_height [Numeric] The maximum height to fit.
# @param kwargs [Hash] Additional options for the scale filter.
# @return [FFMPEG::Filters::Scale] The scale filter.
def contained(media, max_width: nil, max_height: nil, **kwargs)
unless media.is_a?(FFMPEG::Media)
Expand Down Expand Up @@ -83,7 +86,7 @@ def contained(media, max_width: nil, max_height: nil, **kwargs)

if width.negative? || height.negative?
new(width:, height:, **kwargs)
elsif media.calculated_aspect_ratio > Rational(width, height)
elsif media.display_aspect_ratio > Rational(width, height)
new(width:, height: -2, **kwargs)
else
new(width: -2, height:, **kwargs)
Expand All @@ -96,10 +99,10 @@ def contained(media, max_width: nil, max_height: nil, **kwargs)
:in_color_range, :out_color_range,
:in_color_primaries, :out_color_primaries,
:in_color_transfer, :out_color_transfer,
:in_chroma_location, :out_chroma_location
:in_chroma_location, :out_chroma_location,
:force_original_aspect_ratio, :force_divisible_by

def initialize(
zlib: false,
width: nil,
height: nil,
algorithm: nil,
Expand All @@ -112,7 +115,9 @@ def initialize(
in_color_transfer: nil,
out_color_transfer: nil,
in_chroma_location: nil,
out_chroma_location: nil
out_chroma_location: nil,
force_original_aspect_ratio: nil,
force_divisible_by: nil
)
if !width.nil? && !width.is_a?(Numeric) && !width.is_a?(String)
raise ArgumentError, "Unknown width format #{width.class}, expected #{Numeric} or #{String}"
Expand All @@ -135,50 +140,32 @@ def initialize(
@out_color_transfer = out_color_transfer
@in_chroma_location = in_chroma_location
@out_chroma_location = out_chroma_location
@force_original_aspect_ratio = force_original_aspect_ratio
@force_divisible_by = force_divisible_by

super(:video, zlib ? 'zscale' : 'scale')
end

def zlib?
@name.start_with?('z')
super(:video, 'scale')
end

protected

def format_kwargs
if zlib?
super(
w: @width,
h: @height,
f: @algorithm,
min: @in_color_space,
m: @out_color_space,
rin: @in_color_range,
r: @out_color_range,
pin: @in_color_primaries,
p: @out_color_primaries,
tin: @in_color_transfer,
t: @out_color_transfer,
cin: @in_chroma_location,
c: @out_chroma_location
)
else
super(
w: @width,
h: @height,
flags: @algorithm && [@algorithm],
in_color_matrix: @in_color_space,
out_color_matrix: @out_color_space,
in_range: @in_color_range,
out_range: @out_color_range,
in_primaries: @in_color_primaries,
out_primaries: @out_color_primaries,
in_transfer: @in_color_transfer,
out_transfer: @out_color_transfer,
in_chroma_loc: @in_chroma_location,
out_chroma_loc: @out_chroma_location
)
end
super(
w: @width,
h: @height,
flags: @algorithm && [@algorithm],
in_color_matrix: @in_color_space,
out_color_matrix: @out_color_space,
in_range: @in_color_range,
out_range: @out_color_range,
in_primaries: @in_color_primaries,
out_primaries: @out_color_primaries,
in_transfer: @in_color_transfer,
out_transfer: @out_color_transfer,
in_chroma_loc: @in_chroma_location,
out_chroma_loc: @out_chroma_location,
force_original_aspect_ratio: @force_original_aspect_ratio,
force_divisible_by: @force_divisible_by
)
end
end
end
Expand Down
32 changes: 23 additions & 9 deletions lib/ffmpeg/media.rb
Original file line number Diff line number Diff line change
Expand Up @@ -270,25 +270,25 @@ def local?
default_video_stream&.display_aspect_ratio
end

# Returns the sample aspect ratio of the default video stream (if any).
# Returns the raw display aspect ratio of the default video stream (if any).
#
# @return [String, nil]
autoload def sample_aspect_ratio
default_video_stream&.sample_aspect_ratio
autoload def raw_display_aspect_ratio
default_video_stream&.raw_display_aspect_ratio
end

# Returns the calculated aspect ratio of the default video stream (if any).
# Returns the sample aspect ratio of the default video stream (if any).
#
# @return [String, nil]
autoload def calculated_aspect_ratio
default_video_stream&.calculated_aspect_ratio
autoload def sample_aspect_ratio
default_video_stream&.sample_aspect_ratio
end

# Returns the calculated pixel aspect ratio of the default video stream (if any).
# Returns the raw sample aspect ratio of the default video stream (if any).
#
# @return [String, nil]
autoload def calculated_pixel_aspect_ratio
default_video_stream&.calculated_pixel_aspect_ratio
autoload def raw_sample_aspect_ratio
default_video_stream&.raw_sample_aspect_ratio
end

# Returns the pixel format of the default video stream (if any).
Expand All @@ -312,6 +312,20 @@ def local?
default_video_stream&.color_space
end

# Returns the color primaries of the default video stream (if any).
#
# @return [String, nil]
autoload def color_primaries
default_video_stream&.color_primaries
end

# Returns the color transfer of the default video stream (if any).
#
# @return [String, nil]
autoload def color_transfer
default_video_stream&.color_transfer
end

# Returns the frame rate (avg_frame_rate) of the default video stream (if any).
#
# @return [Float, nil]
Expand Down
2 changes: 2 additions & 0 deletions lib/ffmpeg/presets/dash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ def initialize(
super(name:, filename:, metadata:) do
threads preset.threads if preset.threads
format_name 'dash'
use_template 1
use_timeline 1
segment_duration preset.segment_duration

muxing_flags 'frag_keyframe+empty_moov+default_base_moof'
Expand Down
47 changes: 11 additions & 36 deletions lib/ffmpeg/presets/dash/h264.rb
Original file line number Diff line number Diff line change
Expand Up @@ -278,45 +278,20 @@ def initialize(
h264_presets = preset.usable_h264_presets(media)

if media.video_streams?
# Split the default video stream into multiple streams,
# one for each usable H.264 preset (e.g.: [v:0]split=2[v0][v1]).
split_filter =
Filters
.split(h264_presets.length)
.with_input_link!(media.video_mapping_id)
.with_output_links!(*h264_presets.each_with_index.map { |_, index| "v#{index}" })

# Scale the split video streams to the desired resolutions
# and frame rates (e.g.: [v0]scale=640:360,fps=30[v0out]).
# We also apply the desired pixel format to the video stream,
# as well as set the display aspect ratio to the calculated aspect ratio
# to resolve potential issues with different aspect ratios.
stream_filter_graphs =
h264_presets.each_with_index.map do |h264_preset, index|
fps_filter = Filters.fps(adjusted_frame_rate(h264_preset.frame_rate))
format_filter = h264_preset.format_filter
scale_filter = h264_preset.scale_filter(media)
dar_filter = Filters.set_dar(media.calculated_aspect_ratio) if media.calculated_aspect_ratio

stream_filters = [fps_filter, format_filter, scale_filter, dar_filter].compact
stream_filters.first.with_input_link!("v#{index}")
stream_filters.last.with_output_link!("v#{index}out")

Filter.join(*stream_filters)
end

# Apply the generated filter complex to the output.
filter_complex split_filter, *stream_filter_graphs

# Force keyframes at the specified interval.
force_key_frames "expr:gte(t,n_forced*#{preset.keyframe_interval})"

# Map the scaled video streams with the desired H.264 parameters.
# Use the default video stream for all representations.
h264_presets.each_with_index do |h264_preset, index|
map "[v#{index}out]" do
map media.video_mapping_id do
frame_rate = adjusted_frame_rate(h264_preset.frame_rate)
filters Filters.fps(frame_rate),
h264_preset.format_filter,
h264_preset.scale_filter(media),
h264_preset.dar_filter(media),
stream_index: index
video_preset h264_preset.video_preset, stream_index: index
video_profile h264_preset.video_profile, stream_index: index
constant_rate_factor h264_preset.constant_rate_factor, stream_id: "v:#{index}"
constant_rate_factor h264_preset.constant_rate_factor, stream_type: 'v', stream_index: index
min_keyframe_interval preset.keyframe_interval * frame_rate, stream_index: index
max_keyframe_interval preset.keyframe_interval * frame_rate, stream_index: index
end
end
end
Expand Down
Loading