Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
200 lines (169 sloc) 6.74 KB
# This file contains methods which depend heavily on external
# libs such as mediainfo and mini_exiftool
class Video
attr_accessor :file, :md5, :general, :video, :audio, :software, :hardware, :mediainfo_version, :exiftool_version
# This method creates an object with all of the desired metadata
def initialize( path )
# Let the viewer know what we're up to
puts "Scanning #{path}"
@file = path.to_s
# Produce MD5. This is usually *not* a secure identifier against corruption
# because default behaviour is to hash only part of a file
@md5 = heuristic_hash( path )
# Call the MediaInfo library to examine technical metadata
info = path
# Call ExifTool to examine software metadata
exif = path
# Width and height exist in multiple places and we can get stuck
# without them!!
# Let's suss them out here and use the local variables elsewhere
if[0].nil? # We can't get resolution from a video with no video
width = nil
height = nil
width = if[0].width;[0].width); else; end
height = if[0].height;[0].height); else; end
# XDCAM videos in QT wrappers include a giant XML string with data
# not represented elsewhere in the Exif
# Decode it if present for a couple values we want
unless exif.com_sony_bprl_mxf_nrtmetadata.nil?
require 'xmlsimple'
xml = XmlSimple.xml_in( exif.com_sony_bprl_mxf_nrtmetadata )
manufacturer = xml['Device'][0]['manufacturer']
serial = xml['Device'][0]['serialNo']
firmware = xml['Device'][0]['Element']
@general = {
:movie_name => info.general[0]['movie_name'],
:container => info.general.format,
:format_info => info.general.format_info,
:video_tracks =>,
:audio_tracks =>,
:duration => info.general.duration,
:encoded_date => (info.general.encoded_date.to_i if info.general.encoded_date),
:modify_date => (info.general.tagged_date.to_i if info.general.tagged_date)
# Most videos only contain one video track.
@video = nil
@video = {
:codec =>,
:codec_profile =>,
:codec_id =>,
:compressor_name => exif.compressor_name,
:width => width,
:height => height,
:frame_rate =>,
:interlaced =>,
:standard => height.standard?(, ),
:aspect_ratio =>,
:bitrate =>,
:bitrate_mode =>,
:colour_space =>[0]["color_space"],
:chroma_subsampling =>[0]["chroma_subsampling"],
:track_create_date => ( if,
:track_modify_date => ( if
# We only log metadata from the first audio track,
# But we will say how many tracks there are.
@audio = nil
@audio = {
:codec =>[0].format,
:codec_id =>[0].codec_id,
:sampling_rate =>[0].sampling_rate,
:channels =>[0].channels,
:bit_depth =>[0]["bit_depth"],
:bitrate =>[0].bit_rate,
:bitrate_mode =>[0].bit_rate_mode,
:track_create_date => ([0].encoded_date.to_i if[0].encoded_date),
:track_modify_date => ([0].tagged_date.to_i if[0].tagged_date)
@software = {
:writing_library => info.general.writing_library,
:writing_history => exif.history_changed,
:writing_history_date => (exif.history_when.to_i if exif.history_when),
:software => (exif.history_software_agent if exif.history_software_agent)
@hardware = {
:manufacturer => if manufacturer.nil?; exif.make; else manufacturer; end,
:model => if exif.model; exif.model; else exif.user_data_prd; end,
:serial_no => serial,
:firmware => firmware,
:aperture => exif.aperture,
:aperture_setting => exif.aperture_setting,
:shutter_speed => exif.shutter_speed,
:gain => exif.gain,
:exposure => exif.exposure_program,
:focus => exif.focus,
:image_stabilization => exif.image_stabilization,
:gps_version_id => exif.gps_version_id,
:gps_status => if exif.gps_status == 'Measurement Void'; nil; else exif.gps_status; end,
:gps_map_datum => exif.gps_map_datum
@mediainfo_version = Mediainfo.version
@exiftool_version = exif.exiftool_version
# Calculates a hash for the video. By default this only scans
# portions of the file, because large videos on shared folders
# will tend to be very very big and take forever to scan
# Do *not* rely on these partial hashes to identify corruption!!
def heuristic_hash( path )
if File.size?( path ).nil?
return nil
elsif ARGV.include? '--no-hash' # Don't record a hash
return 'not recorded'
elsif ARGV.include? '--full-hash' # Hash the entire file, no matter how long it takes
return Digest::MD5.hexdigest IO.binread( path )
elsif File.size?( path ) < 6291456 # File is too small to seek to 5MB, so hash the whole thing
return Digest::MD5.hexdigest IO.binread( path )
return Digest::MD5.hexdigest path, 'rb' ) { |f| 5242880; 1048576 }
def to_hash
return {
:file => @file,
:md5 => @md5,
:general => @general,
:video => @video,
:audio => @audio,
:software => @software,
:hardware => @hardware,
:mediainfo_version => @mediainfo_version,
:exiftool_version => @exiftool_version
# Comes up with a nice human-readable video standard,
# e.g. 1080i or 720p or summat
# Not strictly necessary since we already have the info to
# generate this, but it's good for humans, and what's good
# for humans is good for robots too.
class Resolution < DelegateClass(Fixnum)
def standard?( interlace, framerate )
case self.to_i
when 1080
case interlace
when false
when 720
'720p' # Pretty sure no one records 720i
when 480
case interlace
when true
if framerate == 29.97; '60i'; else '480i'; end