Skip to content
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
== 8.4.3 2026-04-02

Fixes:
* Correctly detect 3gp and 3g2 file extensions (and some others).

== 8.4.2 2026-04-01

Fixes:
Expand Down
48 changes: 43 additions & 5 deletions lib/ffmpeg/media.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,50 @@ def initialize(message, output)
when /\Adash\b/ then '.mpd'
when /\bhls\b/ then '.m3u8'
when /\bmpegts(raw)?\b/ then '.ts'
when /\bmpegvideo\b/ then '.mpg'
when /\blive_flv\b/ then '.flv'
when /\basf_o\b/ then '.asf'
when /\bmpeg(video\b|\z)/ then '.mpg'
when /\blive_flv\b/ then '.flv'
when /\basf\b/
if video?
'.wmv'
elsif audio?
'.wma'
else
'.asf'
end
when /\b(mov|mp4)\b/
case major_brand
when nil, /\Aqt\b/i then '.mov'
when /\Am4a\b/i then '.m4a'
when /\Am4v\b/i then '.m4v'
when /\Am4s\b/i then '.m4s'
else '.mp4'
when /\A3g2/i then '.3g2'
when /\A3gp/i then '.3gp'
when /\Af4v\b/i then '.f4v'
when /\Af4p\b/i then '.f4p'
when /\Af4a\b/i then '.f4a'
when /\Af4b\b/i then '.f4b'
when /\Aavi[fs]\b/i then '.avif'
else '.mp4'
end
when /\bmatroska\b/
if streams
.select { _1.video? || _1.audio? }
.select(&:av?)
.reject(&:attached_pic?)
.all? { WEBM_CODEC_NAMES.include?(_1.codec_name) }
'.webm'
else
'.mkv'
end
when /\bogg\b/
if video_streams?
'.ogv'
elsif include_codec_name?('opus')
'.opus'
elsif include_codec_name?('speex')
'.spx'
else
'.ogg'
end
else
muxer =
format_name
Expand Down Expand Up @@ -238,6 +262,20 @@ def local?
@valid
end

# Whether the media contains any of the specified codec names.
#
# @return [Boolean]
autoload def include_codec_name?(*codec_names)
self.codec_names.any? { codec_names.include?(_1) }
end

# Returns the set of all codec names used in the media's streams.
#
# @return [Set<String>]
autoload def codec_names
@codec_names ||= streams.map(&:codec_name).compact.to_set
end

# Returns the major brand of the media (if any).
autoload def major_brand
tags&.fetch(:major_brand, nil)&.to_s&.strip
Expand Down
7 changes: 7 additions & 0 deletions lib/ffmpeg/stream.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ def audio?
codec_type == :audio
end

# Whether the stream is an audio or video stream.
#
# @return [Boolean]
def av?
video? || audio?
end

# Whether the stream is marked as default.
#
# @return [Boolean]
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 = '8.4.2'
VERSION = '8.4.3'
end
26 changes: 26 additions & 0 deletions spec/ffmpeg/stream_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,32 @@ module FFMPEG
end
end

describe '#av?' do
context 'when the codec type is video' do
let(:metadata) { { codec_type: 'video' } }

it 'returns true' do
expect(subject.av?).to be(true)
end
end

context 'when the codec type is audio' do
let(:metadata) { { codec_type: 'audio' } }

it 'returns true' do
expect(subject.av?).to be(true)
end
end

context 'when the codec type is not video or audio' do
let(:metadata) { { codec_type: 'subtitle' } }

it 'returns false' do
expect(subject.av?).to be(false)
end
end
end

describe '#default?' do
context 'when marked as default' do
let(:metadata) { { disposition: { default: 1 } } }
Expand Down
Loading