Class: RVideo::Inspector

Inherits:
Object
  • Object
show all
Defined in:
lib/rvideo/inspector.rb

Overview

To inspect a video or audio file, initialize an Inspector object.

file = RVideo::Inspector.new(options_hash)

Inspector accepts three options: file, raw_response, and ffmpeg_binary. Either raw_response or file is required; ffmpeg binary is optional.

:file is a path to a file to be inspected.

:raw_response is the full output of "ffmpeg -i [file]". If the :raw_response option is used, RVideo will not actually inspect a file; it will simply parse the provided response. This is useful if your application has already collected the ffmpeg -i response, and you don't want to call it again.

:ffmpeg_binary is an optional argument that specifies the path to the ffmpeg binary to be used. If a path is not explicitly declared, RVideo will assume that ffmpeg exists in the Unix path. Type "which ffmpeg" to check if ffmpeg is installed and exists in your operating system's path.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Inspector

Returns a new instance of Inspector.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/rvideo/inspector.rb', line 26

def initialize(options = {})
  if not (options[:raw_response] or options[:file])
    raise ArgumentError, "Must supply either an input file or a pregenerated response"
  end
  
  if options[:raw_response]
    initialize_with_raw_response(options[:raw_response])
  elsif options[:file]
    initialize_with_file(options[:file], options[:ffmpeg_binary])
  end

   = /(Input \#.*)\n.+\n\Z/m.match(@raw_response)
  
  if /Unknown format/i.match(@raw_response) || .nil?
    @unknown_format = true
  elsif /Duration: N\/A/im.match(@raw_response)
    # in this case, we can at least still get the container type
    @unreadable_file = true
    @raw_metadata = [1]
  else
    @raw_metadata = [1]
  end
end

Instance Attribute Details

#ffmpeg_binary ⇒ Object

Returns the value of attribute ffmpeg_binary.



24
25
26
# File 'lib/rvideo/inspector.rb', line 24

def ffmpeg_binary
  @ffmpeg_binary
end

#filename ⇒ Object (readonly)

Returns the value of attribute filename.



22
23
24
# File 'lib/rvideo/inspector.rb', line 22

def filename
  @filename
end

#full_filename ⇒ Object (readonly)

Returns the value of attribute full_filename.



22
23
24
# File 'lib/rvideo/inspector.rb', line 22

def full_filename
  @full_filename
end

#path ⇒ Object (readonly)

Returns the value of attribute path.



22
23
24
# File 'lib/rvideo/inspector.rb', line 22

def path
  @path
end

#raw_metadata ⇒ Object (readonly)

Returns the value of attribute raw_metadata.



22
23
24
# File 'lib/rvideo/inspector.rb', line 22

def 
  @raw_metadata
end

#raw_response ⇒ Object (readonly)

Returns the value of attribute raw_response.



22
23
24
# File 'lib/rvideo/inspector.rb', line 22

def raw_response
  @raw_response
end

Instance Method Details

#audio? ⇒ Boolean

Does the file have an audio stream?

Returns:

  • (Boolean)


100
101
102
# File 'lib/rvideo/inspector.rb', line 100

def audio?
  not audio_match.nil?
end

#audio_bit_rate ⇒ Object



283
284
285
286
# File 'lib/rvideo/inspector.rb', line 283

def audio_bit_rate
  return nil unless audio?
  audio_match[7].to_i
end

#audio_bit_rate_units ⇒ Object



288
289
290
291
# File 'lib/rvideo/inspector.rb', line 288

def audio_bit_rate_units
  return nil unless audio?
  audio_match[8]
end

#audio_bit_rate_with_units ⇒ Object



293
294
295
# File 'lib/rvideo/inspector.rb', line 293

def audio_bit_rate_with_units
  "#{audio_bit_rate} #{audio_bit_rate_units}"
end

#audio_channels ⇒ Object



354
355
356
357
358
359
360
361
362
363
# File 'lib/rvideo/inspector.rb', line 354

def audio_channels
  return nil unless audio?

  case audio_match[5]
  when "mono"   then 1
  when "stereo" then 2
  else
    raise RuntimeError, "Unknown number of channels: #{audio_channels}"
  end
end

#audio_channels_string ⇒ Object

The channels used in the audio stream.

Examples:

"stereo"
"mono"
"5:1"


349
350
351
352
# File 'lib/rvideo/inspector.rb', line 349

def audio_channels_string
  return nil unless audio?
  audio_match[5]
end

#audio_codec ⇒ Object

The audio codec used.

Example:

"aac"


310
311
312
313
# File 'lib/rvideo/inspector.rb', line 310

def audio_codec
  return nil unless audio?
  audio_match[2]
end

#audio_sample_bit_depth ⇒ Object

This should almost always return 16, as the vast majority of audio is 16 bit.



367
368
369
370
# File 'lib/rvideo/inspector.rb', line 367

def audio_sample_bit_depth
  return nil unless audio?
  audio_match[6].to_i
end

#audio_sample_rate ⇒ Object

The sampling rate of the audio stream.

Example:

44100


321
322
323
324
# File 'lib/rvideo/inspector.rb', line 321

def audio_sample_rate
  return nil unless audio?
  audio_match[3].to_i
end

#audio_sample_rate_units ⇒ Object Also known as: audio_sample_units

The units used for the sampling rate. May always be Hz.

Example:

"Hz"


332
333
334
335
# File 'lib/rvideo/inspector.rb', line 332

def audio_sample_rate_units
  return nil unless audio?
  audio_match[4]
end

#audio_sample_rate_with_units ⇒ Object



338
339
340
# File 'lib/rvideo/inspector.rb', line 338

def audio_sample_rate_with_units
  "#{audio_sample_rate} #{audio_sample_rate_units}"
end

#audio_stream ⇒ Object



297
298
299
300
301
302
# File 'lib/rvideo/inspector.rb', line 297

def audio_stream
  return nil unless valid?
  
  match = /\n\s*Stream.*Audio:.*\n/.match(@raw_response)
  match[0].strip if match
end

#audio_stream_id ⇒ Object

The ID of the audio stream (useful for troubleshooting).

Example:

#0.1


377
378
379
380
# File 'lib/rvideo/inspector.rb', line 377

def audio_stream_id
  return nil unless audio?
  audio_match[1]
end

#bitrate ⇒ Object

The bitrate of the movie.

Example:

3132



263
264
265
266
# File 'lib/rvideo/inspector.rb', line 263

def bitrate
  return nil unless valid?
  bitrate_match[1].to_i
end

#bitrate_units ⇒ Object

The bitrate units used. In practice, this may always be kb/s.

Example:

"kb/s"


274
275
276
277
# File 'lib/rvideo/inspector.rb', line 274

def bitrate_units
  return nil unless valid?
  bitrate_match[2]
end

#bitrate_with_units ⇒ Object



279
280
281
# File 'lib/rvideo/inspector.rb', line 279

def bitrate_with_units
  "#{bitrate} #{bitrate_units}"
end

#calculate_time(timecode) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/rvideo/inspector.rb', line 143

def calculate_time(timecode)
  m = /\A([0-9\.\,]*)(s|f|%)?\Z/.match(timecode)
  if m.nil? or m[1].nil? or m[1].empty?
    raise TranscoderError::ParameterError, "Invalid timecode for frame capture: #{timecode}. Must be a number, optionally followed by s, f, or %."
  end
  
  case m[2]
  when "s", nil
    t = m[1].to_f
  when "f"
    t = m[1].to_f / fps.to_f
  when "%"
    # milliseconds / 1000 * percent / 100 
    t = (duration.to_i / 1000.0) * (m[1].to_f / 100.0)
  else
    raise TranscoderError::ParameterError, "Invalid timecode for frame capture: #{timecode}. Must be a number, optionally followed by s, f, or p."
  end
  
  if (t * 1000) > duration
    calculate_time("99%")
  else
    t
  end
end

#capture_frame(timecode, output_file = nil) ⇒ Object

Take a screengrab of a movie. Requires an input file and a time parameter, and optionally takes an output filename. If no output filename is specfied, constructs one.

Three types of time parameters are accepted - percentage (e.g. 3%), time in seconds (e.g. 60 seconds), and raw frame (e.g. 37). Will raise an exception if the time in seconds or the frame are out of the bounds of the input file.

Types:

37s (37 seconds)
37f (frame 37)
37% (37 percent)
37  (default to seconds)

If a time is outside of the duration of the file, it will choose a frame at the 99% mark.

Example:

t = RVideo::Transcoder.new('path/to/input_file.mp4')
t.capture_frame('10%') # => '/path/to/screenshot/input-10p.jpg'


131
132
133
134
135
136
137
138
139
140
141
# File 'lib/rvideo/inspector.rb', line 131

def capture_frame(timecode, output_file = nil)
  t = calculate_time(timecode)
  output_file ||= "#{TEMP_PATH}/#{File.basename(@full_filename, ".*")}-#{timecode.gsub("%","p")}.jpg"
  command = "ffmpeg -i #{@full_filename.shell_quoted} -ss #{t} -t 00:00:01 -r 1 -vframes 1 -f image2 #{output_file.shell_quoted}"
  
  RVideo.logger.info("\nCreating Screenshot: #{command}\n")
  frame_result = `#{command} 2>&1`
  RVideo.logger.info("\nScreenshot results: #{frame_result}")
  
  output_file
end

#codec_time_base ⇒ Object



471
472
473
474
# File 'lib/rvideo/inspector.rb', line 471

def codec_time_base
  return nil unless video?
  video_match[11]
end

#container ⇒ Object

Returns the container format for the file. Instead of returning a single format, this may return a string of related formats.

Examples:

"avi"

"mov,mp4,m4a,3gp,3g2,mj2"


224
225
226
227
# File 'lib/rvideo/inspector.rb', line 224

def container
  return nil if @unknown_format
  /Input \#\d+\,\s*(\S+),\s*from/.match(@raw_metadata)[1]
end

#display_aspect_ratio ⇒ Object



449
450
451
452
# File 'lib/rvideo/inspector.rb', line 449

def display_aspect_ratio
  return nil unless video?
  video_match[8]
end

#duration ⇒ Object

The duration of the movie in milliseconds, as an integer.

Example:

24400         # 24.4 seconds

Note that the precision of the duration is in tenths of a second, not thousandths, but milliseconds are a more standard unit of time than deciseconds.



250
251
252
253
254
255
# File 'lib/rvideo/inspector.rb', line 250

def duration
  return nil unless valid?
  
  units = raw_duration.split(":")
  (units[0].to_i * 60 * 60 * 1000) + (units[1].to_i * 60 * 1000) + (units[2].to_f * 1000).to_i
end

#ffmpeg_build ⇒ Object

Returns the build description for ffmpeg.

Example:

built on Apr 15 2006 04:58:19, gcc: 4.0.1 (Apple Computer, Inc. build
5250)


211
212
213
# File 'lib/rvideo/inspector.rb', line 211

def ffmpeg_build
  /(\n\s*)(built on.*)(\n)/.match(@raw_response)[2]
end

#ffmpeg_configuration ⇒ Object

Returns the configuration options used to build ffmpeg.

Example:

--enable-mp3lame --enable-gpl --disable-ffplay --disable-ffserver
--enable-a52 --enable-xvid


187
188
189
# File 'lib/rvideo/inspector.rb', line 187

def ffmpeg_configuration 
  /(\s*configuration:)(.*)\n/.match(@raw_response)[2].strip
end

#ffmpeg_libav ⇒ Object

Returns the versions of libavutil, libavcodec, and libavformat used by ffmpeg.

Example:

libavutil version: 49.0.0
libavcodec version: 51.9.0
libavformat version: 50.4.0


200
201
202
# File 'lib/rvideo/inspector.rb', line 200

def ffmpeg_libav
  /^(\s*lib.*\n)+/.match(@raw_response)[0].split("\n").each {|l| l.strip! }
end

#ffmpeg_version ⇒ Object

Returns the version of ffmpeg used, In practice, this may or may not be useful.

Examples:

SVN-r6399
CVS


176
177
178
# File 'lib/rvideo/inspector.rb', line 176

def ffmpeg_version
  @ffmpeg_version = @raw_response.split("\n").first.split("version").last.split(",").first.strip
end

#fps ⇒ Object Also known as: framerate

The frame rate of the video in frames per second

Example:

"29.97"


460
461
462
463
# File 'lib/rvideo/inspector.rb', line 460

def fps
  return nil unless video?
  video_match[2] or video_match[9]
end

#height ⇒ Object

The height of the video in pixels.



428
429
430
431
# File 'lib/rvideo/inspector.rb', line 428

def height
  return nil unless video?
  video_match[6].to_i
end

#initialize_with_file(file, ffmpeg_binary = nil) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/rvideo/inspector.rb', line 54

def initialize_with_file(file, ffmpeg_binary = nil)
  if ffmpeg_binary
    @ffmpeg_binary = ffmpeg_binary
    if not FileTest.exist?(@ffmpeg_binary)
      raise "ffmpeg could not be found (trying #{@ffmpeg_binary})" 
    end
  else
    # assume it is in the unix path
    if not FileTest.exist?(`which ffmpeg`.chomp)
      raise "ffmpeg could not be found (expected ffmpeg to be found in the Unix path)"
    end
    @ffmpeg_binary = "ffmpeg"
  end
  
  if not FileTest.exist?(file.gsub('"',''))
    raise TranscoderError::InputFileNotFound, "File not found (#{file})"
  end
  
  @full_filename = file
  @filename      = File.basename(@full_filename)
  @path          = File.dirname(@full_filename)
  
  @raw_response = `#{@ffmpeg_binary} -i #{@full_filename.shell_quoted} 2>&1`
end

#initialize_with_raw_response(raw_response) ⇒ Object



50
51
52
# File 'lib/rvideo/inspector.rb', line 50

def initialize_with_raw_response(raw_response)
  @raw_response = raw_response
end

#invalid? ⇒ Boolean

Returns false if the file can be read successfully. Returns false otherwise.

Returns:

  • (Boolean)


85
86
87
# File 'lib/rvideo/inspector.rb', line 85

def invalid?
  not valid?
end

#pixel_aspect_ratio ⇒ Object



444
445
446
447
# File 'lib/rvideo/inspector.rb', line 444

def pixel_aspect_ratio
  return nil unless video?
  video_match[7]
end

#raw_duration ⇒ Object

The duration of the movie, as a string.

Example:

"00:00:24.4"  # 24.4 seconds


235
236
237
238
# File 'lib/rvideo/inspector.rb', line 235

def raw_duration
  return nil unless valid?
  /Duration:\s*([0-9\:\.]+),/.match(@raw_metadata)[1]
end

#resolution ⇒ Object

width x height, as a string.

Examples:

320x240
1280x720


439
440
441
442
# File 'lib/rvideo/inspector.rb', line 439

def resolution
  return nil unless video?
  "#{width}x#{height}"
end

#time_base ⇒ Object



466
467
468
469
# File 'lib/rvideo/inspector.rb', line 466

def time_base
  return nil unless video?
  video_match[10]
end

#unknown_format? ⇒ Boolean

True if the format is not understood ("Unknown Format")

Returns:

  • (Boolean)


90
91
92
# File 'lib/rvideo/inspector.rb', line 90

def unknown_format?
  @unknown_format ? true : false
end

#unreadable_file? ⇒ Boolean

True if the file is not readable ("Duration: N/A, bitrate: N/A")

Returns:

  • (Boolean)


95
96
97
# File 'lib/rvideo/inspector.rb', line 95

def unreadable_file?
  @unreadable_file ? true : false
end

#valid? ⇒ Boolean

Returns true if the file can be read successfully. Returns false otherwise.

Returns:

  • (Boolean)


80
81
82
# File 'lib/rvideo/inspector.rb', line 80

def valid?
  not (@unknown_format or @unreadable_file)
end

#video? ⇒ Boolean

Does the file have a video stream?

Returns:

  • (Boolean)


105
106
107
# File 'lib/rvideo/inspector.rb', line 105

def video?
  not video_match.nil?
end

#video_codec ⇒ Object

The video codec used.

Example:

"mpeg4"


405
406
407
408
# File 'lib/rvideo/inspector.rb', line 405

def video_codec
  return nil unless video?
  video_match[3]
end

#video_colorspace ⇒ Object

The colorspace of the video stream.

Example:

"yuv420p"


416
417
418
419
# File 'lib/rvideo/inspector.rb', line 416

def video_colorspace
  return nil unless video?
  video_match[4]
end

#video_stream ⇒ Object



382
383
384
385
386
387
# File 'lib/rvideo/inspector.rb', line 382

def video_stream
  return nil unless valid?
  
  match = /\n\s*Stream.*Video:.*\n/.match(@raw_response)
  match[0].strip unless match.nil?
end

#video_stream_id ⇒ Object

The ID of the video stream (useful for troubleshooting).

Example:

#0.0


394
395
396
397
# File 'lib/rvideo/inspector.rb', line 394

def video_stream_id
  return nil unless video?
  video_match[1]
end

#width ⇒ Object

The width of the video in pixels.



422
423
424
425
# File 'lib/rvideo/inspector.rb', line 422

def width
  return nil unless video?
  video_match[5].to_i
end