media_processing_tool 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/mig/http.rb ADDED
@@ -0,0 +1,54 @@
1
+ require 'json'
2
+ require 'sinatra/base'
3
+
4
+ class MediaInformationGatherer
5
+
6
+ class HTTP < Sinatra::Base
7
+ enable :logging
8
+ disable :protection
9
+
10
+ # Will try to convert a body to parameters and merge them into the params hash
11
+ # Params will override the body parameters
12
+ #
13
+ # @params [Hash] _params (params) The parameters parsed from the query and form fields
14
+ def merge_params_from_body(_params = params)
15
+ _params = _params.dup
16
+ if request.media_type == 'application/json'
17
+ request.body.rewind
18
+ body_contents = request.body.read
19
+ logger.debug { "Parsing: '#{body_contents}'" }
20
+ if body_contents
21
+ json_params = JSON.parse(body_contents)
22
+ if json_params.is_a?(Hash)
23
+ _params = json_params.merge(_params)
24
+ else
25
+ _params['body'] = json_params
26
+ end
27
+ end
28
+ end
29
+ _params
30
+ end # merge_params_from_body
31
+
32
+
33
+ post '/' do
34
+ logger.level = Logger::DEBUG
35
+ _params = merge_params_from_body
36
+ logger.debug { "Params: #{_params}" }
37
+ #return params
38
+
39
+ response = { }
40
+ file_paths = _params['file_paths']
41
+ [*file_paths].each do |file_path|
42
+ begin
43
+ response[file_path] = settings.mig.run(file_path)
44
+ rescue => e
45
+ response[file_path] = {:exception => {:message => e.message, :backtrace => e.backtrace}}
46
+ end
47
+ end
48
+ content_type :json
49
+ JSON.generate(response)
50
+ end # post '/'
51
+
52
+ end # HTTP
53
+
54
+ end # MediaInformationGatherer
@@ -0,0 +1,333 @@
1
+ require 'time'
2
+
3
+ class MediaInformationGatherer
4
+
5
+ class Common
6
+
7
+ STANDARD_VIDEO_FRAME_RATES = [ 23.97, 23.976, 24.0, 24.97, 24.975, 25.0, 29.97, 30.0, 50.0, 59.94, 60.0 ]
8
+
9
+ def self.common_variables(metadata_sources)
10
+ new.common_variables(metadata_sources)
11
+ end
12
+
13
+ def metadata_sources; @metadata_sources || { } end
14
+ def ffmpeg; @ffmpeg ||= metadata_sources[:ffmpeg] || { } end
15
+ def mediainfo; @mediainfo ||= metadata_sources[:mediainfo] || { 'section_type_count' => { 'audio' => 0 } } end
16
+ def stat; @stat ||= metadata_sources[:stat] || { } end
17
+ def cv; @cv ||= { } end
18
+
19
+ def common_variables(_metadata_sources)
20
+ @metadata_sources = _metadata_sources.dup
21
+ @cv = { }
22
+
23
+ file_path = ffmpeg['path']
24
+ source_directory = file_path ? File.dirname(File.expand_path(file_path)) : ''
25
+ creation_date_time = Time.parse(ffmpeg['creation_time']).strftime('%B %d, %Y %r') rescue ffmpeg['creation_time']
26
+
27
+ cv[:file_path] = file_path
28
+ cv[:source_directory] = source_directory
29
+ cv[:creation_date_time] = creation_date_time
30
+ cv[:ctime] = stat[:ctime]
31
+ cv[:mtime] = stat[:mtime]
32
+ cv[:bytes] = stat[:size]
33
+ cv[:size] = (mediainfo['General'] || { })['File size']
34
+ cv[:uid] = stat[:uid]
35
+ cv[:gid] = stat[:gid]
36
+ cv[:ftype] = stat[:ftype]
37
+
38
+ type = :video # Need to figure out where to determine the type from
39
+ case type #.to_s.downcase.to_sym
40
+ when :video
41
+ common_audio_variables
42
+ common_video_variables
43
+ when :audio
44
+ common_audio_variables
45
+ when :image
46
+ common_image_variables
47
+ else
48
+ # What else is there?
49
+ end
50
+ if RUBY_VERSION.start_with?('1.8.')
51
+ Hash[cv.map { |a| [ a[0].to_s, a[1] ] }.sort.map { |a| [ a[0].to_sym, a[1] ] }]
52
+ else
53
+ Hash[cv.sort]
54
+ end
55
+ end # common_variables
56
+
57
+ def common_audio_variables
58
+ mi_audio = mediainfo['Audio'] || { }
59
+ mi_audio = { } unless mi_audio.is_a?(Hash)
60
+
61
+ duration = ffmpeg['duration']
62
+ if duration
63
+ dl = duration
64
+ dlh = dl / 3600
65
+ dl %= 3600
66
+ dlm = dl / 60
67
+ dl %= 60
68
+ duration_long = sprintf('%02d:%02d:%02d', dlh, dlm, dl)
69
+ else
70
+ duration_long = '00:00:00'
71
+ end
72
+ section_type_counts = mediainfo['section_type_counts'] || { }
73
+ audio_track_count = section_type_counts['audio']
74
+
75
+ cv[:audio_codec_id] = mi_audio['Codec ID']
76
+ cv[:audio_sample_rate] = ffmpeg['audio_sample_rate']
77
+ cv[:duration] = duration
78
+ cv[:duration_long] = duration_long
79
+ cv[:number_of_audio_tracks] = audio_track_count # Determine the number of audio channels
80
+ cv[:number_of_audio_channels] = ffmpeg['audio_channel_count']
81
+
82
+ end # common_audio_variables
83
+
84
+ # @return [Fixed]
85
+ def aspect_from_dimensions(height, width)
86
+ aspect = width.to_f / height.to_f
87
+ aspect.nan? ? nil : aspect
88
+ end
89
+
90
+ # Determines if the aspect from dimensions is widescreen (>= 1.5 (3/2)
91
+ # 1.55 is derived from the following tables
92
+ # {http://en.wikipedia.org/wiki/Storage_Aspect_Ratio#Previous_and_currently_used_aspect_ratios Aspect Ratios}
93
+ # {http://en.wikipedia.org/wiki/List_of_common_resolutions#Television}
94
+ #
95
+ # 1.55:1 (14:9): Widescreen aspect ratio sometimes used in shooting commercials etc. as a compromise format
96
+ # between 4:3 (12:9) and 16:9. When converted to a 16:9 frame, there is slight pillarboxing, while conversion to
97
+ # 4:3 creates slight letterboxing. All widescreen content on ABC Family's SD feed is presented in this ratio.
98
+ #
99
+ # @return [Boolean]
100
+ def is_widescreen?(height, width)
101
+ _aspect_from_dimensions = aspect_from_dimensions(height, width)
102
+ (_aspect_from_dimensions ? (_aspect_from_dimensions >= 1.55) : false)
103
+ end
104
+
105
+ # (@link http://en.wikipedia.osrg/wiki/List_of_common_resolution)
106
+ #
107
+ # Lowest Width High Resolution Format Found:
108
+ # Panasonic DVCPRO100 for 50/60Hz over 720p - SMPTE Resolution = 960x720
109
+ #
110
+ # @return [Boolean]
111
+ def is_high_definition?(height, width)
112
+ (width.respond_to?(:to_i) and height.respond_to?(:to_i)) ? (width.to_i >= 950 and height.to_i >= 700) : false
113
+ end
114
+
115
+
116
+ def common_image_variables
117
+
118
+ end # common_image_variables
119
+
120
+ def common_video_variables
121
+ mi_video = mediainfo['Video'] || { }
122
+ #return unless ffmpeg['video_stream'] or !mi_video.empty?
123
+
124
+ frame_rate = ffmpeg['frame_rate']
125
+ frame_rate ||= mi_video['Frame rate'].respond_to?(:to_f) ? mi_video['Frame rate'].to_f : mi_video['Frame rate']
126
+
127
+ height = ffmpeg['height'] || mi_video['Height']
128
+ width = ffmpeg['width'] || mi_video['Width']
129
+
130
+ is_widescreen = ffmpeg['is_widescreen']
131
+ is_widescreen ||= is_widescreen?(height, width) if is_widescreen.nil?
132
+
133
+ is_high_definition = is_high_definition?(height, width)
134
+
135
+ calculated_aspect_ratio = ffmpeg['calculated_aspect_ratio'] || (height.respond_to?(:to_f) and width.respond_to?(:to_f) ? (width.to_f / height.to_f) : nil)
136
+
137
+ video_codec_id = mi_video['Codec ID']
138
+ video_codec_description = video_codec_descriptions.fetch(video_codec_id, 'Unknown')
139
+
140
+ video_system = determine_video_system(height, width, frame_rate)
141
+
142
+ #aspect_ratio = ffmpeg['video_stream'] ? (ffmpeg['is_widescreen'] ? '16:9' : '4:3') : nil
143
+ if ffmpeg['video_stream']
144
+ aspect_ratio = ffmpeg['is_widescreen'] ? '16:9' : '4:3'
145
+ else
146
+ aspect_ratio = nil
147
+ end
148
+
149
+ cv[:aspect_ratio] = aspect_ratio
150
+ cv[:bit_depth] = mi_video['Bit depth']
151
+ cv[:calculated_aspect_ratio] = calculated_aspect_ratio
152
+ cv[:chroma_subsampling] = mi_video['Chroma subsampling']
153
+ cv[:display_aspect_ratio] = ffmpeg['display_aspect_ratio']
154
+ cv[:frames_per_second] = frame_rate # Video frames per second
155
+ cv[:height] = height
156
+ cv[:is_high_definition] = is_high_definition # Determine if video is Standard Def or High Definition
157
+ cv[:is_widescreen] = is_widescreen
158
+ cv[:pixel_aspect_ratio] = ffmpeg['pixel_aspect_ratio']
159
+ cv[:resolution] = ffmpeg['resolution']
160
+ cv[:scan_order] = mi_video['Scan order']
161
+ cv[:scan_type] = mi_video['Scan type']
162
+ cv[:storage_aspect_ratio] = ffmpeg['storage_aspect_ratio']
163
+ cv[:timecode] = ffmpeg['timecode']
164
+ cv[:video_codec_id] = video_codec_id
165
+ cv[:video_codec_commercial_name] = mi_video['Commercial name']
166
+ cv[:video_codec_description] = video_codec_description
167
+ cv[:video_system] = video_system
168
+ cv[:width] = width
169
+ cv
170
+ end # common_video_variables
171
+
172
+ # A hash of fourcc codes
173
+ # http://www.videolan.org/developers/vlc/src/misc/fourcc.c
174
+ def fourcc_codes
175
+ @fourcc_codes ||= {
176
+ '2vuy' => 'Apple FCP Uncompressed 8-bit 4:2:2',
177
+ 'v210' => 'Apple FCP Uncompressed 10-bit 4:2:2',
178
+ 'apcn' => 'Apple ProRes Standard',
179
+ 'apch' => 'Apple ProRes High Quality (HQ)',
180
+ 'apcs' => 'Apple ProRes LT',
181
+ 'apco' => 'Apple ProRes Proxy',
182
+ 'ap4c' => 'Apple ProRes 4444',
183
+ 'ap4h' => 'Apple ProRes 4444',
184
+ 'xdv1' => 'XDCAM HD 720p30 35Mb/s',
185
+ 'xdv2' => 'XDCAM HD 1080i60 35Mb/s',
186
+ 'xdv3' => 'XDCAM HD 1080i50 35Mb/s',
187
+ 'xdv4' => 'XDCAM HD 720p24 35Mb/s',
188
+ 'xdv5' => 'XDCAM HD 720p25 35Mb/s',
189
+ 'xdv6' => 'XDCAM HD 1080p24 35Mb/s',
190
+ 'xdv7' => 'XDCAM HD 1080p25 35Mb/s',
191
+ 'xdv8' => 'XDCAM HD 1080p30 35Mb/s',
192
+ 'xdv9' => 'XDCAM HD 720p60 35Mb/s',
193
+ 'xdva' => 'XDCAM HD 720p50 35Mb/s',
194
+ 'xdhd' => 'XDCAM HD 540p',
195
+ 'xdh2' => 'XDCAM HD422 540p',
196
+ 'xdvb' => 'XDCAM EX 1080i60 50Mb/s CBR',
197
+ 'xdvc' => 'XDCAM EX 1080i50 50Mb/s CBR',
198
+ 'xdvd' => 'XDCAM EX 1080p24 50Mb/s CBR',
199
+ 'xdve' => 'XDCAM EX 1080p25 50Mb/s CBR',
200
+ 'xdvf' => 'XDCAM EX 1080p30 50Mb/s CBR',
201
+ 'xd54' => 'XDCAM HD422 720p24 50Mb/s CBR',
202
+ 'xd55' => 'XDCAM HD422 720p25 50Mb/s CBR',
203
+ 'xd59' => 'XDCAM HD422 720p60 50Mb/s CBR',
204
+ 'xd5a' => 'XDCAM HD422 720p50 50Mb/s CBR',
205
+ 'xd5b' => 'XDCAM HD422 1080i60 50Mb/s CBR',
206
+ 'xd5c' => 'XDCAM HD422 1080i50 50Mb/s CBR',
207
+ 'xd5d' => 'XDCAM HD422 1080p24 50Mb/s CBR',
208
+ 'xd5e' => 'XDCAM HD422 1080p25 50Mb/s CBR',
209
+ 'xd5f' => 'XDCAM HD422 1080p30 50Mb/s CBR',
210
+ 'dvh2' => 'DV Video 720p24',
211
+ 'dvh3' => 'DV Video 720p25',
212
+ 'dvh4' => 'DV Video 720p30',
213
+ 'dvcp' => 'DV Video PAL',
214
+ 'dvc' => 'DV Video NTSC',
215
+ 'dvp' => 'DV Video Pro',
216
+ 'dvpp' => 'DV Video Pro PAL',
217
+ 'dv50' => 'DV Video C Pro 50',
218
+ 'dv5p' => 'DV Video C Pro 50 PAL',
219
+ 'dv5n' => 'DV Video C Pro 50 NTSC',
220
+ 'dv1p' => 'DV Video C Pro 100 PAL',
221
+ 'dv1n' => 'DV Video C Pro 100 NTSC',
222
+ 'dvhp' => 'DV Video C Pro HD 720p',
223
+ 'dvh5' => 'DV Video C Pro HD 1080i50',
224
+ 'dvh6' => 'DV Video C Pro HD 1080i60',
225
+ 'AVdv' => 'AVID DV',
226
+ 'AVd1' => 'MPEG2 I',
227
+ 'mx5n' => 'MPEG2 IMX NTSC 625/60 50Mb/s (FCP)',
228
+ 'mx5p' => 'MPEG2 IMX PAL 525/50 50Mb/s (FCP',
229
+ 'mx4n' => 'MPEG2 IMX NTSC 625/60 40Mb/s (FCP)',
230
+ 'mx4p' => 'MPEG2 IMX PAL 525/50 40Mb/s (FCP',
231
+ 'mx3n' => 'MPEG2 IMX NTSC 625/60 30Mb/s (FCP)',
232
+ 'mx3p' => 'MPEG2 IMX PAL 525/50 30Mb/s (FCP)',
233
+ 'hdv1' => 'HDV 720p30',
234
+ 'hdv2' => 'HDV 1080i60',
235
+ 'hdv3' => 'HDV 1080i50',
236
+ 'hdv4' => 'HDV 720p24',
237
+ 'hdv5' => 'HDV 720p25',
238
+ 'hdv6' => 'HDV 1080p24',
239
+ 'hdv7' => 'HDV 1080p25',
240
+ 'hdv8' => 'HDV 1080p30',
241
+ 'hdv9' => 'HDV 720p60',
242
+ 'hdva' => 'HDV 720p50',
243
+ 'avc1' => 'AVC-Intra',
244
+ 'ai5p' => 'AVC-Intra 50M 720p25/50',
245
+ 'ai5q' => 'AVC-Intra 50M 1080p25/50',
246
+ 'ai52' => 'AVC-Intra 50M 1080p24/30',
247
+ 'ai53' => 'AVC-Intra 50M 1080i50',
248
+ 'ai55' => 'AVC-Intra 50M 1080i60',
249
+ 'ai56' => 'AVC-Intra 100M 720p24/30',
250
+ 'ai1p' => 'AVC-Intra 100M 720p25/50',
251
+ 'ai1q' => 'AVC-Intra 100M 1080p25/50',
252
+ 'ai12' => 'AVC-Intra 100M 1080p24/30',
253
+ 'ai13' => 'AVC-Intra 100M 1080i50',
254
+ 'ai15' => 'AVC-Intra 100M 1080i60',
255
+ 'ai16' => 'AVC-Intra 100M 1080i60',
256
+ 'mpgv' => 'MPEG-2',
257
+ 'mp1v' => 'MPEG-2',
258
+ 'mpeg' => 'MPEG-2',
259
+ 'mpg1' => 'MPEG-2',
260
+ 'mp2v' => 'MPEG-2',
261
+ 'MPEG' => 'MPEG-2',
262
+ 'mpg2' => 'MPEG-2',
263
+ 'MPG2' => 'MPEG-2',
264
+ 'H262' => 'MPEG-2',
265
+ 'mjpg' => 'Motion JPEG',
266
+ 'mJPG' => 'Motion JPEG',
267
+ 'mjpa' => 'Motion JPEG',
268
+ 'JPEG' => 'Motion JPEG',
269
+ 'AVRn' => 'Avid Motion JPEG',
270
+ 'AVRn' => 'Avid Motion JPEG',
271
+ 'AVDJ' => 'Avid Motion JPEG',
272
+ 'ADJV' => 'Avid Motion JPEG',
273
+ 'wvc1' => 'Microsoft VC-1',
274
+ 'vc-1' => 'Microsoft VC-1',
275
+ 'VC-1' => 'Microsoft VC-1',
276
+ 'jpeg' => 'Photo JPEG',
277
+ }
278
+ end # fourcc_codes
279
+
280
+ def video_codec_descriptions
281
+ @video_codec_descriptions ||= fourcc_codes.merge({
282
+ 27 => 'MPEG-TS',
283
+ })
284
+ end
285
+
286
+ def determine_video_system(height, width, frame_rate)
287
+ # http://en.wikipedia.org/wiki/Broadcast_television_system
288
+ # http://en.wikipedia.org/wiki/Standard-definition_television#Resolution
289
+ # http://en.wikipedia.org/wiki/Pixel_aspect_ratio
290
+ # http://www.bambooav.com/ntsc-and-pal-video-standards.html
291
+ # Programmer's Guide to Video Systems - http://lurkertech.com/lg/video-systems/#fields
292
+
293
+ # PAL = 25fps Standard: 768x576 Widescreen: 1024x576
294
+ # NTSC = 29.97fps Standard: 720x540 Widescreen: 854x480
295
+ video_system = 'unknown'
296
+
297
+ return video_system unless height and width and frame_rate
298
+
299
+ frame_rate = frame_rate.to_f
300
+ return video_system unless STANDARD_VIDEO_FRAME_RATES.include?(frame_rate)
301
+
302
+ height = height.to_i
303
+ width = width.to_i
304
+
305
+ # The following case statement is based off of - http://images.apple.com/finalcutpro/docs/Apple_ProRes_White_Paper_October_2012.pdf
306
+ case height
307
+ when 480, 486, 512
308
+ case width
309
+ when 720, 848, 854
310
+ video_system = 'NTSC'
311
+ end
312
+ when 576, 608
313
+ case width
314
+ when 720
315
+ video_system = 'PAL'
316
+ end
317
+ when 720
318
+ case width
319
+ when 960, 1280
320
+ video_system = 'HD'
321
+ end
322
+ when 1080
323
+ case width
324
+ when 1280, 1440, 1920
325
+ video_system = 'HD'
326
+ end
327
+ end # case height
328
+ video_system
329
+ end # determine_video_system
330
+
331
+ end # Common
332
+
333
+ end # MediaInformationGatherer
@@ -0,0 +1,26 @@
1
+ require 'json'
2
+ require 'shellwords'
3
+ class MediaInformationGatherer
4
+
5
+ class ExifTool
6
+
7
+ DEFAULT_EXECUTABLE_PATH = 'exiftool'
8
+
9
+ def initialize(options = { })
10
+ #@logger = options[:logger] || Logger.new(STDOUT)
11
+ @exiftool_cmd_path = options.fetch(:exiftool_cmd_path, DEFAULT_EXECUTABLE_PATH)
12
+ end # initialize
13
+
14
+ # @param [String] file_path
15
+ # @param [Hash] options
16
+ def run(file_path, options = {})
17
+ cmd_line = [@exiftool_cmd_path, '-json', file_path].shelljoin
18
+ #@logger.debug { "[ExifTool] Executing command: #{cmd_line}" }
19
+ metadata_json = %x(#{cmd_line})
20
+ #@logger.debug { "[ExifTool] Result: #{metadata_json}" }
21
+ JSON.parse(metadata_json)[0]
22
+ end # self.run
23
+
24
+ end #ExifTool
25
+
26
+ end
@@ -0,0 +1,225 @@
1
+ require 'open3'
2
+ require 'shellwords'
3
+ require 'time' # unless defined? Time
4
+
5
+ class MediaInformationGatherer
6
+ class FFMPEG
7
+
8
+ DEFAULT_EXECUTABLE_PATH = 'ffmpeg'
9
+
10
+ class Movie
11
+ attr_reader :command, :output,
12
+ :path, :duration, :time, :bitrate, :rotation, :creation_time,
13
+ :video_stream, :video_codec, :video_bitrate, :colorspace, :resolution,
14
+ :dar, :display_aspect_ratio,
15
+ :sar, :storage_aspect_ratio,
16
+ :par, :pixel_aspect_ratio,
17
+ :width, :height, :is_widescreen, :is_high_definition, :calculated_aspect_ratio,
18
+ :audio_stream, :audio_codec, :audio_bitrate, :audio_sample_rate
19
+
20
+ def initialize(path, options = { })
21
+ raise Errno::ENOENT, "No such file or directory - '#{path}'" unless File.exists?(path)
22
+ @path = path
23
+
24
+ #@logger = options.fetch(:logger, Logger.new(STDOUT))
25
+ @ffmpeg_cmd_path = options.fetch(:ffmpeg_cmd_path, FFMPEG::DEFAULT_EXECUTABLE_PATH)
26
+
27
+
28
+ # ffmpeg will output to stderr
29
+ @command = [@ffmpeg_cmd_path, '-i', path].shelljoin
30
+ #@logger.debug { "[FFMPEG] Executing command '#{command}'" }
31
+ @output = Open3.popen3(command) { |stdin, stdout, stderr| stderr.read }
32
+ #@logger.debug { "[FFMPEG] Command response: #{@output}" }
33
+
34
+ fix_encoding(@output)
35
+
36
+ @output[/Duration: (\d{2}):(\d{2}):(\d{2}\.\d{2})/]
37
+ @duration = ($1.to_i*60*60) + ($2.to_i*60) + $3.to_f
38
+
39
+ @output[/start: (\d*\.\d*)/]
40
+ @time = $1 ? $1.to_f : 0.0
41
+
42
+ @output[/creation_time {1,}: {1,}(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/]
43
+ @creation_time = $1 ? Time.parse("#{$1}") : nil
44
+
45
+ @output[/bitrate: (\d*)/]
46
+ @bitrate = $1 ? $1.to_i : nil
47
+
48
+ @output[/rotate\ {1,}:\ {1,}(\d*)/]
49
+ @rotation = $1 ? $1.to_i : nil
50
+
51
+ @output[/Video: (.*)/]
52
+ @video_stream = $1
53
+
54
+ @output[/Audio: (.*)/]
55
+ @audio_stream = $1
56
+
57
+ @output[/timecode .* : (.*)/]
58
+ @timecode = $1
59
+
60
+ if video_stream
61
+ # Example Strings
62
+ #
63
+ # "dvvideo (dvc / 0x20637664), 720x480, 28771 kb/s): unspecified pixel format"
64
+ # "mpeg2video (4:2:2) (mx5n / 0x6E35786D), yuv422p, 720x512 [SAR 512:405 DAR 16:9], 50084 kb/s, SAR 40:33 DAR 75:44, 29.97 fps, 29.97 tbr, 2997 tbn, 59.94 tbc"
65
+ # "h264 (Main) (avc1 / 0x31637661), yuv420p, 854x480 [SAR 1:1 DAR 427:240], 2196 kb/s, 29.97 fps, 29.97 tbr, 2997 tbn, 59.94 tbc"
66
+ # "prores (apcn / 0x6E637061), yuv422p10le, 720x486, 23587 kb/s, SAR 10:11 DAR 400:297, 29.97 fps, 29.97 tbr, 2997 tbn, 2997 tbc"
67
+ # "Apple ProRes 422 (HQ), 1920x1080, 187905 kb/s, 29.97 fps, 29.97 tbr, 29970 tbn, 29970 tbc"
68
+ if video_stream.end_with?('unspecified pixel format')
69
+ @video_codec, @resolution, video_bitrate = video_stream.split(':').first.split(/\s?,\s?/)
70
+ video_bitrate = video_bitrate[0..-2] if video_bitrate and video_bitrate.end_with?(')')
71
+ else
72
+ @video_codec, @colorspace, @resolution, video_bitrate, aspect_ratios = video_stream.split(/\s?,\s?/)
73
+ if @colorspace.match(/\d*x\d*/)
74
+ @colorspace = nil
75
+ @video_codec, @resolution, video_bitrate, fps, tbr, tbn, tbc = video_stream.split(/\s?,\s?/)
76
+ end
77
+ end
78
+
79
+
80
+ @video_bitrate = video_bitrate =~ %r(\A(\d+) kb/s\Z) ? $1.to_i : nil
81
+
82
+
83
+ process_aspect_ratios(aspect_ratios) if aspect_ratios and aspect_ratios.include?(':')
84
+
85
+ @resolution, aspect_ratios = @resolution.strip.split(' ', 2) rescue @resolution = aspect_ratios = nil
86
+
87
+ process_aspect_ratios(aspect_ratios) if aspect_ratios and aspect_ratios.include?(':')
88
+
89
+ @width, @height = @resolution.split('x') rescue @width = @height = nil
90
+ @frame_rate = $1 if video_stream[/(\d*\.?\d*)\s?fps/]
91
+
92
+ is_widescreen?
93
+
94
+ is_high_definition?
95
+ end
96
+
97
+ if audio_stream
98
+ @audio_codec, audio_sample_rate, @audio_channels, unused, audio_bitrate = audio_stream.split(/\s?,\s?/)
99
+ @audio_bitrate = audio_bitrate =~ %r(\A(\d+) kb/s\Z) ? $1.to_i : nil
100
+ @audio_sample_rate = audio_sample_rate[/\d*/].to_i
101
+ end
102
+
103
+ @invalid = true if @video_stream.to_s.empty? && @audio_stream.to_s.empty?
104
+ @invalid = true if @output.include?('is not supported')
105
+ @invalid = true if @output.include?('could not find codec parameters')
106
+ end # initialize
107
+
108
+ def process_aspect_ratios(aspect_ratios)
109
+ @dar = @display_aspect_ratio = $1 if aspect_ratios[/DAR (\d+:\d+)/] rescue nil # Display Aspect Ratio = SAR * PAR
110
+ @sar = @storage_aspect_ratio = $1 if aspect_ratios[/SAR (\d+:\d+)/] rescue nil # Storage Aspect Ratio = DAR/PAR
111
+ @par = @pixel_aspect_ratio = $1 if aspect_ratios[/PAR (\d+:\d+)/] rescue nil # Pixel aspect ratio = DAR/SAR
112
+ end # process_aspect_ratios
113
+
114
+ # @return [Boolean]
115
+ def valid?
116
+ not @invalid
117
+ end
118
+
119
+ # Determines if the aspect from dimensions is widescreen (> 1.5 (3/2)
120
+ # 1.55 is derived from the following tables
121
+ # {http://en.wikipedia.org/wiki/Storage_Aspect_Ratio#Previous_and_currently_used_aspect_ratios Aspect Ratios}
122
+ # {http://en.wikipedia.org/wiki/List_of_common_resolutions#Television}
123
+ #
124
+ # 1.55:1 (14:9): Widescreen aspect ratio sometimes used in shooting commercials etc. as a compromise format
125
+ # between 4:3 (12:9) and 16:9. When converted to a 16:9 frame, there is slight pillarboxing, while conversion to
126
+ # 4:3 creates slight letterboxing. All widescreen content on ABC Family's SD feed is presented in this ratio.
127
+ #
128
+ # @return [Boolean]
129
+ def is_widescreen?
130
+ @is_widescreen ||= (calculated_aspect_ratio ? (calculated_aspect_ratio >= 1.55) : false)
131
+ end
132
+ alias :is_wide_screen :is_widescreen
133
+
134
+ # (@link http://en.wikipedia.osrg/wiki/List_of_common_resolution)
135
+ #
136
+ # Lowest Width High Resolution Format Found:
137
+ # Panasonic DVCPRO100 for 50/60Hz over 720p - SMPTE Resolution = 960x720
138
+ #
139
+ # @return [Boolean]
140
+ def is_high_definition?
141
+ @is_high_definition ||= ( (width.respond_to?(:to_i) and height.respond_to?(:to_i)) ? (@width.to_i >= 950 and @height.to_i >= 700) : false )
142
+ end
143
+ alias :is_high_def? :is_high_definition?
144
+
145
+ # Will attempt to
146
+ def calculated_aspect_ratio
147
+ @calculated_aspect_ratio ||= aspect_from_dar || aspect_from_dimensions
148
+ end
149
+
150
+ # @return [Integer] File Size
151
+ def size
152
+ @size ||= File.size(@path)
153
+ end
154
+
155
+ # @return [Integer]
156
+ def audio_channel_count(audio_channels = @audio_channels)
157
+ return 0 unless audio_channels
158
+ return 1 if audio_channels['mono']
159
+ return 2 if audio_channels['stereo']
160
+ return 6 if audio_channels['5.1']
161
+ return 9 if audio_channels['7.2']
162
+
163
+ # If we didn't hit a match above then find any number in #.# format and add them together to get a channel count
164
+ audio_channels[/(\d+.?\d?).*/]
165
+ audio_channels = $1.to_s.split('.').map(&:to_i).inject(:+) if $1 rescue audio_channels
166
+ return audio_channels if audio_channels.is_a? Integer
167
+ end
168
+
169
+ # Outputs relavant instance variables names and values as a hash
170
+ # @return [Hash]
171
+ def to_hash
172
+ hash = Hash.new
173
+ variables = instance_variables
174
+ [ :@ffmpeg_cmd_path, :@logger ].each { |cmd| variables.delete(cmd) }
175
+ variables.each { |instance_variable_name|
176
+ hash[instance_variable_name.to_s[1..-1]] = instance_variable_get(instance_variable_name)
177
+ }
178
+ hash['audio_channel_count'] = audio_channel_count
179
+ hash['calculated_aspect_ratio'] = calculated_aspect_ratio
180
+ hash
181
+ end
182
+
183
+ protected
184
+ # @return [Integer|nil]
185
+ def aspect_from_dar
186
+ return nil unless dar
187
+ return @aspect_from_dar if @aspect_from_dar
188
+ w, h = dar.split(':')
189
+ aspect = w.to_f / h.to_f
190
+ @aspect_from_dar = aspect.zero? ? nil : aspect
191
+ end
192
+
193
+ # @return [Fixed]
194
+ def aspect_from_dimensions
195
+ return @aspect_from_dimensions if @aspect_from_dimensions
196
+
197
+ aspect = width.to_f / height.to_f
198
+ @aspect_from_dimensions = aspect.nan? ? nil : aspect
199
+ end
200
+
201
+ # @param [String] output
202
+ def fix_encoding(output)
203
+ output[/test/] # Running a regexp on the string throws error if it's not UTF-8
204
+ rescue ArgumentError
205
+ output.force_encoding('ISO-8859-1')
206
+ end
207
+ end
208
+
209
+ # @param [Hash] options
210
+ # @option options [String] :ffmpeg_cmd_path
211
+ def initialize(options = { })
212
+ @ffmpeg_cmd_path = options.fetch(:ffmpeg_cmd_path, 'ffmpeg')
213
+ end # initialize
214
+
215
+ # @param [String] file_path
216
+ # @param [Hash] options
217
+ # @option options [String] :ffmpeg_cmd_path
218
+ def run(file_path, options = { })
219
+ options = { :ffmpeg_cmd_path => @ffmpeg_cmd_path }.merge(options)
220
+ Movie.new(file_path, options).to_hash
221
+ end # run
222
+
223
+ end # FFMPEG
224
+
225
+ end