Class: RVideo::Inspector

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Inspector

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.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/rvideo/inspector.rb', line 30

def initialize(options = {})
  if options[:raw_response]
    @raw_response = options[:raw_response]
  elsif options[:file]
    if options[:ffmpeg_binary]
      @ffmpeg_binary = options[:ffmpeg_binary]
      raise RuntimeError, "ffmpeg could not be found (trying #{@ffmpeg_binary})" unless FileTest.exist?(@ffmpeg_binary)
    else
      # assume it is in the unix path
      raise RuntimeError, 'ffmpeg could not be found (expected ffmpeg to be found in the Unix path)' unless FileTest.exist?(`which ffmpeg`.chomp)
      @ffmpeg_binary = "ffmpeg"
    end

    file = options[:file]
    @filename = File.basename(file)
    @path = File.dirname(file)
    @full_filename = file
    raise TranscoderError::InputFileNotFound, "File not found (#{@full_filename})" unless FileTest.exist?(@full_filename)
    @raw_response = `#{@ffmpeg_binary} -i #{Shellwords.shellescape @full_filename} 2>&1`
  else
    raise ArgumentError, "Must supply either an input file or a pregenerated response" if options[:raw_response].nil? and file.nil?
  end

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

Instance Attribute Details

#ffmpeg_binaryObject

Returns the value of attribute ffmpeg_binary.



6
7
8
# File 'lib/rvideo/inspector.rb', line 6

def ffmpeg_binary
  @ffmpeg_binary
end

#filenameObject (readonly)

Returns the value of attribute filename.



4
5
6
# File 'lib/rvideo/inspector.rb', line 4

def filename
  @filename
end

#full_filenameObject (readonly)

Returns the value of attribute full_filename.



4
5
6
# File 'lib/rvideo/inspector.rb', line 4

def full_filename
  @full_filename
end

#pathObject (readonly)

Returns the value of attribute path.



4
5
6
# File 'lib/rvideo/inspector.rb', line 4

def path
  @path
end

#raw_metadataObject (readonly)

Returns the value of attribute raw_metadata.



4
5
6
# File 'lib/rvideo/inspector.rb', line 4

def 
  @raw_metadata
end

#raw_responseObject (readonly)

Returns the value of attribute raw_response.



4
5
6
# File 'lib/rvideo/inspector.rb', line 4

def raw_response
  @raw_response
end

Instance Method Details

#audio?Boolean

Does the file have an audio stream?

Returns:

  • (Boolean)


114
115
116
117
118
119
120
# File 'lib/rvideo/inspector.rb', line 114

def audio?
  if audio_match.nil?
    false
  else
    true
  end
end

#audio_bit_rateObject

:nodoc:



341
342
343
# File 'lib/rvideo/inspector.rb', line 341

def audio_bit_rate # :nodoc:
  nil
end

#audio_channelsObject



410
411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/rvideo/inspector.rb', line 410

def audio_channels
  return nil unless audio?

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

#audio_channels_stringObject

The channels used in the audio stream.

Examples:

"stereo"
"mono"
"5:1"


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

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

#audio_codecObject

The audio codec used.

Example:

"aac"


361
362
363
364
365
# File 'lib/rvideo/inspector.rb', line 361

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

#audio_sample_rateObject

The sampling rate of the audio stream.

Example:

44100


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

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

#audio_sample_unitsObject

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

Example:

"Hz"


389
390
391
392
393
# File 'lib/rvideo/inspector.rb', line 389

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

#audio_streamObject



345
346
347
348
349
350
351
# File 'lib/rvideo/inspector.rb', line 345

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

#audio_stream_idObject

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

Example:

#0.1


432
433
434
435
436
# File 'lib/rvideo/inspector.rb', line 432

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

#bitrateObject

The bitrate of the movie.

Example:

3132


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

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

#bitrate_unitsObject

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

Example:

"kb/s"


335
336
337
338
339
# File 'lib/rvideo/inspector.rb', line 335

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

#calculate_time(timecode) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/rvideo/inspector.rb', line 184

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, dimension = 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'


153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/rvideo/inspector.rb', line 153

def capture_frame(timecode, output_file = nil, dimension=nil)
  t = calculate_time(timecode)
  unless output_file
    output_file = "#{TEMP_PATH}/#{File.basename(@full_filename, ".*")}-#{timecode.gsub("%","p")}.jpg"
  end
  # do the work
  # mplayer $input_file$ -ss $start_time$ -frames 1 -vo jpeg -o $output_file$
  # ffmpeg -i $input_file$ -v nopb -ss $start_time$ -b $bitrate$ -an -vframes 1 -y $output_file$
  dimension_option=""
  dimension_option="-s #{dimension}" unless dimension.nil?
  command = "ffmpeg -i #{@full_filename} -ss #{t} -t 00:00:01 -r 1 -vframes 1 #{dimension_option} -f image2 #{output_file}"
  Transcoder.logger.info("\nCreating Screenshot: #{command}\n")
  frame_result = `#{command} 2>&1`
  
  # Different versions of ffmpeg report errors differently when a screenshot cannot be extracted from
  # a video given its set of options. Some versions return a non-zero exit code and report errors while
  # others simply
  unless File.exists?(output_file)
    msg = <<-EOT.gsub(/(^\s+|\n)/, '')
      This means that ffmpeg could not extract a screenshot from the video. It may indicate that
      the video file was corrupt or that the requested frame to be captured was outside the length
      of the video. Full command: #{command}
    EOT
    Transcoder.logger.error msg
    raise TranscoderError::OutputFileNotFound, msg
  end
  
  Transcoder.logger.info("\nScreenshot results: #{frame_result}")
  output_file
end

#containerObject

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"


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

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

#durationObject

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.



306
307
308
309
310
311
# File 'lib/rvideo/inspector.rb', line 306

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_buildObject

Returns the build description for ffmpeg.

Example:

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


260
261
262
# File 'lib/rvideo/inspector.rb', line 260

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

#ffmpeg_configurationObject

Returns the configuration options used to build ffmpeg.

Example:

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


232
233
234
# File 'lib/rvideo/inspector.rb', line 232

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

#ffmpeg_libavObject

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


247
248
249
# File 'lib/rvideo/inspector.rb', line 247

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

#ffmpeg_versionObject

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

Examples:

SVN-r6399
CVS


219
220
221
# File 'lib/rvideo/inspector.rb', line 219

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

#fpsObject

The frame rate of the video in frames per second

Example:

"29.97"


529
530
531
532
533
# File 'lib/rvideo/inspector.rb', line 529

def fps
  return nil unless video?
  
  /([0-9\.]+) (fps|tb)/.match(video_stream)[1]
end

#heightObject

The height of the video in pixels.



501
502
503
504
505
# File 'lib/rvideo/inspector.rb', line 501

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

#invalid?Boolean

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

Returns:

  • (Boolean)


82
83
84
# File 'lib/rvideo/inspector.rb', line 82

def invalid?
  !valid?
end

#raw_durationObject

The duration of the movie, as a string.

Example:

"00:00:24.4"  # 24.4 seconds


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

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

#resolutionObject

width x height, as a string.

Examples:

320x240
1280x720


515
516
517
518
519
# File 'lib/rvideo/inspector.rb', line 515

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

#unknown_format?Boolean

True if the format is not understood (“Unknown Format”)

Returns:

  • (Boolean)


90
91
92
93
94
95
96
# File 'lib/rvideo/inspector.rb', line 90

def unknown_format?
  if @unknown_format
    true
  else
    false
  end
end

#unreadable_file?Boolean

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

Returns:

  • (Boolean)


102
103
104
105
106
107
108
# File 'lib/rvideo/inspector.rb', line 102

def unreadable_file?
  if @unreadable_file
    true
  else
    false
  end
end

#valid?Boolean

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

Returns:

  • (Boolean)


70
71
72
73
74
75
76
# File 'lib/rvideo/inspector.rb', line 70

def valid?
  if @unknown_format or @unreadable_file
    false
  else
    true
  end
end

#video?Boolean

Does the file have a video stream?

Returns:

  • (Boolean)


126
127
128
129
130
131
132
# File 'lib/rvideo/inspector.rb', line 126

def video?
  if video_match.nil?
    false
  else
    true
  end
end

#video_codecObject

The video codec used.

Example:

"mpeg4"


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

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

#video_colorspaceObject

The colorspace of the video stream.

Example:

"yuv420p"


481
482
483
484
485
# File 'lib/rvideo/inspector.rb', line 481

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

#video_streamObject



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

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

#video_stream_idObject

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

Example:

#0.0


453
454
455
456
457
# File 'lib/rvideo/inspector.rb', line 453

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

#widthObject

The width of the video in pixels.



491
492
493
494
495
# File 'lib/rvideo/inspector.rb', line 491

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