Class: FFMPEG::Media

Inherits:
Object
  • Object
show all
Defined in:
lib/ffmpeg/media.rb

Overview

Represents a media file and provides access to its metadata

Examples:

Basic usage

media = FFMPEG::Media.new("/path/to/video.mp4")
media.duration     # => 120.5
media.resolution   # => "1920x1080"
media.video_codec  # => "h264"
media.audio_codec  # => "aac"

Checking properties

media.valid?       # => true
media.video?       # => true
media.audio?       # => true
media.hd?          # => true

Transcoding

media.transcode("/path/to/output.webm", video_codec: "libvpx-vp9")

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ Media

Create a new Media instance

Parameters:

  • path (String) —

    path to the media file

Raises:



41
42
43
44
45
# File 'lib/ffmpeg/media.rb', line 41

def initialize(path)
  @path = File.expand_path(path)
  validate_file!
  probe!
end

Instance Attribute Details

#format ⇒ Hash (readonly)

Returns format information.

Returns:

  • (Hash) —

    format information



35
36
37
# File 'lib/ffmpeg/media.rb', line 35

def format
  @format
end

#path ⇒ String (readonly)

Returns path to the media file.

Returns:

  • (String) —

    path to the media file



26
27
28
# File 'lib/ffmpeg/media.rb', line 26

def path
  @path
end

#raw ⇒ Hash (readonly)

Returns raw ffprobe output.

Returns:

  • (Hash) —

    raw ffprobe output



29
30
31
# File 'lib/ffmpeg/media.rb', line 29

def raw
  @raw
end

#streams ⇒ Array<Stream> (readonly)

Returns all streams in the media.

Returns:

  • (Array<Stream>) —

    all streams in the media



32
33
34
# File 'lib/ffmpeg/media.rb', line 32

def streams
  @streams
end

Instance Method Details

#audio ⇒ Stream?

Returns primary audio stream.

Returns:

  • (Stream, nil) —

    primary audio stream



118
119
120
# File 'lib/ffmpeg/media.rb', line 118

def audio
  audio_streams.find(&:default) || audio_streams.first
end

#audio? ⇒ Boolean

Returns true if media has audio.

Returns:

  • (Boolean) —

    true if media has audio



128
129
130
# File 'lib/ffmpeg/media.rb', line 128

def audio?
  audio_streams.any?
end

#audio_bit_rate ⇒ Integer?

Returns audio bit rate.

Returns:

  • (Integer, nil) —

    audio bit rate



217
218
219
# File 'lib/ffmpeg/media.rb', line 217

def audio_bit_rate
  audio&.bit_rate
end

#audio_channels ⇒ Integer?

Returns number of audio channels.

Returns:

  • (Integer, nil) —

    number of audio channels



207
208
209
# File 'lib/ffmpeg/media.rb', line 207

def audio_channels
  audio&.channels
end

#audio_codec ⇒ String?

Returns audio codec name.

Returns:

  • (String, nil) —

    audio codec name



197
198
199
# File 'lib/ffmpeg/media.rb', line 197

def audio_codec
  audio&.codec
end

#audio_streams ⇒ Array<Stream>

Returns all audio streams.

Returns:

  • (Array<Stream>) —

    all audio streams



103
104
105
# File 'lib/ffmpeg/media.rb', line 103

def audio_streams
  @audio_streams ||= streams.select(&:audio?)
end

#bit_rate ⇒ Integer?

Returns bit rate in bits/sec.

Returns:

  • (Integer, nil) —

    bit rate in bits/sec



58
59
60
# File 'lib/ffmpeg/media.rb', line 58

def bit_rate
  format["bit_rate"]&.to_i
end

#channel_layout ⇒ String?

Returns audio channel layout.

Returns:

  • (String, nil) —

    audio channel layout



212
213
214
# File 'lib/ffmpeg/media.rb', line 212

def channel_layout
  audio&.channel_layout
end

#creation_time ⇒ Time?

Returns creation time if available.

Returns:

  • (Time, nil) —

    creation time if available



88
89
90
91
92
93
# File 'lib/ffmpeg/media.rb', line 88

def creation_time
  time_str = tags["creation_time"]
  Time.parse(time_str) if time_str
rescue ArgumentError
  nil
end

#detect_scenes(threshold: 0.3) ⇒ Array<Hash>

Detect scene changes in the video

Parameters:

  • threshold (Float) (defaults to: 0.3) —

    scene detection threshold (0.0-1.0)

Returns:

  • (Array<Hash>) —

    array of scene change points



266
267
268
269
# File 'lib/ffmpeg/media.rb', line 266

def detect_scenes(threshold: 0.3)
  detector = SceneDetector.new(self)
  detector.detect(threshold: threshold)
end

#duration ⇒ Float?

Returns duration in seconds.

Returns:

  • (Float, nil) —

    duration in seconds



53
54
55
# File 'lib/ffmpeg/media.rb', line 53

def duration
  format["duration"]&.to_f
end

#extract_keyframes(output_dir:, interval: nil, count: nil, timestamps: nil) ⇒ Array<String>

Extract keyframes from the video

Parameters:

  • output_dir (String) —

    directory for extracted frames

  • interval (Float, nil) (defaults to: nil) —

    interval between frames in seconds

  • count (Integer, nil) (defaults to: nil) —

    number of frames to extract

  • timestamps (Array<Float>, nil) (defaults to: nil) —

    specific timestamps

Returns:

  • (Array<String>) —

    paths to extracted frames



279
280
281
282
283
284
285
286
287
288
289
# File 'lib/ffmpeg/media.rb', line 279

def extract_keyframes(output_dir:, interval: nil, count: nil, timestamps: nil)
  extractor = KeyframeExtractor.new(self)

  if timestamps
    extractor.extract_at_timestamps(timestamps, output_dir: output_dir)
  elsif count
    extractor.extract_count(count, output_dir: output_dir)
  else
    extractor.extract_at_intervals(interval: interval || 5.0, output_dir: output_dir)
  end
end

#format_long_name ⇒ String?

Returns format long name.

Returns:

  • (String, nil) —

    format long name



73
74
75
# File 'lib/ffmpeg/media.rb', line 73

def format_long_name
  format["format_long_name"]
end

#format_name ⇒ String?

Returns container format name.

Returns:

  • (String, nil) —

    container format name



68
69
70
# File 'lib/ffmpeg/media.rb', line 68

def format_name
  format["format_name"]
end

#frame_count ⇒ Integer?

Returns approximate total frame count.

Returns:

  • (Integer, nil) —

    approximate total frame count



224
225
226
227
228
# File 'lib/ffmpeg/media.rb', line 224

def frame_count
  return nil unless duration && frame_rate

  (duration * frame_rate).round
end

#frame_rate ⇒ Float?

Returns video frame rate.

Returns:

  • (Float, nil) —

    video frame rate



155
156
157
# File 'lib/ffmpeg/media.rb', line 155

def frame_rate
  video&.frame_rate
end

#frame_to_timestamp(frame_number) ⇒ Float?

Calculate timestamp for a specific frame

Parameters:

  • frame_number (Integer) —

    frame number (0-indexed)

Returns:

  • (Float, nil) —

    timestamp in seconds



233
234
235
236
237
# File 'lib/ffmpeg/media.rb', line 233

def frame_to_timestamp(frame_number)
  return nil unless frame_rate && frame_rate.positive?

  frame_number.to_f / frame_rate
end

#hd? ⇒ Boolean

Returns true if video is HD (720p+).

Returns:

  • (Boolean) —

    true if video is HD (720p+)



185
186
187
# File 'lib/ffmpeg/media.rb', line 185

def hd?
  video&.hd? || false
end

#height ⇒ Integer?

Returns video height.

Returns:

  • (Integer, nil) —

    video height



140
141
142
# File 'lib/ffmpeg/media.rb', line 140

def height
  video&.height
end

#landscape? ⇒ Boolean

Returns true if video is landscape.

Returns:

  • (Boolean) —

    true if video is landscape



180
181
182
# File 'lib/ffmpeg/media.rb', line 180

def landscape?
  video&.landscape? || false
end

#pixel_format ⇒ String?

Returns pixel format.

Returns:

  • (String, nil) —

    pixel format



170
171
172
# File 'lib/ffmpeg/media.rb', line 170

def pixel_format
  video&.pixel_format
end

#portrait? ⇒ Boolean

Returns true if video is portrait.

Returns:

  • (Boolean) —

    true if video is portrait



175
176
177
# File 'lib/ffmpeg/media.rb', line 175

def portrait?
  video&.portrait? || false
end

#resolution ⇒ String?

Returns resolution as "WIDTHxHEIGHT".

Returns:

  • (String, nil) —

    resolution as "WIDTHxHEIGHT"



145
146
147
# File 'lib/ffmpeg/media.rb', line 145

def resolution
  video&.resolution
end

#rotation ⇒ Integer?

Returns video rotation in degrees.

Returns:

  • (Integer, nil) —

    video rotation in degrees



165
166
167
# File 'lib/ffmpeg/media.rb', line 165

def rotation
  video&.rotation
end

#sample_rate ⇒ Integer?

Returns audio sample rate.

Returns:

  • (Integer, nil) —

    audio sample rate



202
203
204
# File 'lib/ffmpeg/media.rb', line 202

def sample_rate
  audio&.sample_rate
end

#screenshot(timestamp, output_path, resolution: nil) ⇒ String

Take a screenshot at a specific timestamp

Parameters:

  • timestamp (Float) —

    timestamp in seconds

  • output_path (String) —

    path for output image

  • resolution (String, nil) (defaults to: nil) —

    output resolution (e.g., "640x480")

Returns:

  • (String) —

    path to the screenshot



298
299
300
301
# File 'lib/ffmpeg/media.rb', line 298

def screenshot(timestamp, output_path, resolution: nil)
  extractor = KeyframeExtractor.new(self)
  extractor.extract_at_timestamps([timestamp], output_dir: File.dirname(output_path)).first
end

#size ⇒ Integer?

Returns file size in bytes.

Returns:

  • (Integer, nil) —

    file size in bytes



63
64
65
# File 'lib/ffmpeg/media.rb', line 63

def size
  format["size"]&.to_i || File.size(path)
end

#subtitle_streams ⇒ Array<Stream>

Returns all subtitle streams.

Returns:

  • (Array<Stream>) —

    all subtitle streams



108
109
110
# File 'lib/ffmpeg/media.rb', line 108

def subtitle_streams
  @subtitle_streams ||= streams.select(&:subtitle?)
end

#tags ⇒ Hash

Returns format tags (title, artist, etc.).

Returns:

  • (Hash) —

    format tags (title, artist, etc.)



78
79
80
# File 'lib/ffmpeg/media.rb', line 78

def tags
  format["tags"] || {}
end

#timestamp_to_frame(timestamp) ⇒ Integer?

Calculate frame number for a specific timestamp

Parameters:

  • timestamp (Float) —

    timestamp in seconds

Returns:

  • (Integer, nil) —

    frame number (0-indexed)



242
243
244
245
246
# File 'lib/ffmpeg/media.rb', line 242

def timestamp_to_frame(timestamp)
  return nil unless frame_rate

  (timestamp.to_f * frame_rate).round
end

#title ⇒ String?

Returns media title from tags.

Returns:

  • (String, nil) —

    media title from tags



83
84
85
# File 'lib/ffmpeg/media.rb', line 83

def title
  tags["title"]
end

#to_h ⇒ Hash

Returns serializable representation.

Returns:

  • (Hash) —

    serializable representation



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/ffmpeg/media.rb', line 313

def to_h
  {
    path: path,
    duration: duration,
    size: size,
    format: format_name,
    video: video ? {
      codec: video_codec,
      resolution: resolution,
      frame_rate: frame_rate,
      bit_rate: video_bit_rate
    } : nil,
    audio: audio ? {
      codec: audio_codec,
      sample_rate: sample_rate,
      channels: audio_channels,
      bit_rate: audio_bit_rate
    } : nil,
    streams: streams.map(&:to_s)
  }
end

#to_s ⇒ String

Returns human-readable description.

Returns:

  • (String) —

    human-readable description



304
305
306
307
308
309
310
# File 'lib/ffmpeg/media.rb', line 304

def to_s
  parts = ["Media: #{File.basename(path)}"]
  parts << "Duration: #{format_duration(duration)}" if duration
  parts << "Resolution: #{resolution}" if resolution
  parts << "Codecs: #{video_codec}/#{audio_codec}" if video_codec || audio_codec
  parts.join(" | ")
end

#transcode(output_path, **options) {|Float| ... } ⇒ Media

Transcode media to a new file

Parameters:

  • output_path (String) —

    path to output file

  • options (Hash) —

    transcoding options

Yields:

  • (Float) —

    progress percentage (0-100)

Returns:

  • (Media) —

    the transcoded media

Raises:



256
257
258
259
# File 'lib/ffmpeg/media.rb', line 256

def transcode(output_path, **options, &block)
  transcoder = Transcoder.new(self, output_path, **options)
  transcoder.run(&block)
end

#uhd? ⇒ Boolean

Returns true if video is 4K (2160p+).

Returns:

  • (Boolean) —

    true if video is 4K (2160p+)



190
191
192
# File 'lib/ffmpeg/media.rb', line 190

def uhd?
  video&.uhd? || false
end

#valid? ⇒ Boolean

Returns true if media was successfully parsed.

Returns:

  • (Boolean) —

    true if media was successfully parsed



48
49
50
# File 'lib/ffmpeg/media.rb', line 48

def valid?
  @valid
end

#video ⇒ Stream?

Returns primary video stream.

Returns:

  • (Stream, nil) —

    primary video stream



113
114
115
# File 'lib/ffmpeg/media.rb', line 113

def video
  video_streams.find(&:default) || video_streams.first
end

#video? ⇒ Boolean

Returns true if media has video.

Returns:

  • (Boolean) —

    true if media has video



123
124
125
# File 'lib/ffmpeg/media.rb', line 123

def video?
  video_streams.any?
end

#video_bit_rate ⇒ Integer?

Returns video bit rate.

Returns:

  • (Integer, nil) —

    video bit rate



160
161
162
# File 'lib/ffmpeg/media.rb', line 160

def video_bit_rate
  video&.bit_rate
end

#video_codec ⇒ String?

Returns video codec name.

Returns:

  • (String, nil) —

    video codec name



150
151
152
# File 'lib/ffmpeg/media.rb', line 150

def video_codec
  video&.codec
end

#video_streams ⇒ Array<Stream>

Returns all video streams.

Returns:

  • (Array<Stream>) —

    all video streams



98
99
100
# File 'lib/ffmpeg/media.rb', line 98

def video_streams
  @video_streams ||= streams.select(&:video?)
end

#width ⇒ Integer?

Returns video width.

Returns:

  • (Integer, nil) —

    video width



135
136
137
# File 'lib/ffmpeg/media.rb', line 135

def width
  video&.width
end