staugaard-transcoding_machine 0.0.2

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.
@@ -0,0 +1,17 @@
1
+ require 'transcoding_machine/media_format'
2
+
3
+ module TranscodingMachine
4
+ class MediaPlayer
5
+ attr_reader :formats
6
+ def initialize(args)
7
+ if args[:formats]
8
+ @formats = args[:formats].sort {|f1, f2| f2.priority <=> f1.priority}
9
+ end
10
+ @formats ||= []
11
+ end
12
+
13
+ def best_format_for(media_file_attributes)
14
+ @formats.find {|f| f.can_transcode?(media_file_attributes)}
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+
3
+ $LOAD_PATH << File.expand_path('..', File.dirname(__FILE__))
4
+
5
+ require 'transcoding_machine'
6
+ require 'transcoding_machine/server/transcoder'
@@ -0,0 +1,79 @@
1
+ require 'open-uri'
2
+ require 'yaml'
3
+ require 'right_aws'
4
+
5
+ module TranscodingMachine
6
+ module Server
7
+ class Ec2Environment
8
+ @@logger = STDOUT
9
+ @@data = nil
10
+ @@transcoding_settings = nil
11
+
12
+ def self.logger=(new_logger)
13
+ @@logger = new_logger
14
+ end
15
+
16
+ def self.logger
17
+ @@logger || STDOUT
18
+ end
19
+
20
+ def self.load
21
+ !data.nil?
22
+ end
23
+
24
+ # Load user data
25
+ def self.data
26
+ return @@data if @@data
27
+ begin
28
+ logger.puts "Getting EC2 instance ID"
29
+ iid = open('http://169.254.169.254/latest/meta-data/instance-id').read(200)
30
+ logger.puts "Getting EC2 user data"
31
+ user_data = open('http://169.254.169.254/latest/user-data').read(2000)
32
+ data = YAML.load(user_data)
33
+ aws = { :aws_env => data[:aws_env],
34
+ :aws_access_key => data[:aws_access_key],
35
+ :aws_secret_key => data[:aws_secret_key]
36
+ }
37
+
38
+ ENV['AWS_ACCESS_KEY_ID'] = data[:aws_access_key]
39
+ ENV['AWS_SECRET_ACCESS_KEY'] = data[:aws_secret_key]
40
+ rescue
41
+ # when running locally, use fake iid
42
+ iid = "unknown"
43
+ user_data = nil
44
+ aws = {}
45
+ end
46
+ @@data = {:aws => aws, :iid => iid, :user_data => data}
47
+ end
48
+
49
+ def self.keys
50
+ [data[:aws][:aws_access_key], data[:aws][:aws_secret_key]]
51
+ end
52
+
53
+ def self.instance_id
54
+ data[:iid]
55
+ end
56
+
57
+ def self.transcoding_settings
58
+ return @@transcoding_settings if @@transcoding_settings
59
+
60
+ bucket = data[:user_data][:transcoding_settings][:bucket]
61
+ key = data[:user_data][:transcoding_settings][:key]
62
+
63
+ logger.puts "Getting transcoding settings from S3 #{bucket}/#{key}"
64
+
65
+ s3 = RightAws::S3.new
66
+
67
+ @@transcoding_settings ||= YAML.load(s3.bucket(bucket).key(key).data)
68
+ end
69
+
70
+ def self.work_queue_name
71
+ data[:user_data][:work_queue_name]
72
+ end
73
+
74
+ def self.status_queue_name
75
+ data[:user_data][:status_queue_name]
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,23 @@
1
+ module TranscodingMachine
2
+ module Server
3
+ class FileStorage
4
+ def initialize(root_directory = '.')
5
+ @root = root_directory
6
+ end
7
+
8
+ def get_file(source_file_name, destination_file_path, options)
9
+ puts File.expand_path(source_file_name, @root)
10
+ puts destination_file_path
11
+ FileUtils.cp(File.expand_path(source_file_name, @root), destination_file_path)
12
+ end
13
+
14
+ def put_file(source_file_path, destination_file_name, media_format, options)
15
+ FileUtils.mv(source_file_path, File.expand_path(destination_file_name, @root))
16
+ end
17
+
18
+ def put_thumbnail_file(thumbnail_file_path, source_file, options)
19
+ FileUtils.mv(thumbnail_file_path.path, @root)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,582 @@
1
+ require 'rubygems'
2
+ require 'open3'
3
+ require 'pathname'
4
+ require 'timeout'
5
+ require 'unicode'
6
+
7
+ module TranscodingMachine
8
+ module Server
9
+ class MediaFileAttributes < Hash
10
+ ASPECT_RATIO_2_35_BY_1 = 2.35
11
+ ASPECT_RATIO_16_BY_9 = 16.0 / 9.0
12
+ ASPECT_RATIO_4_BY_3 = 4.0 / 3.0
13
+ ASPECT_RATIO_NAMES = {ASPECT_RATIO_2_35_BY_1 => '2.35/1', ASPECT_RATIO_16_BY_9 => '16/9', ASPECT_RATIO_4_BY_3 => '4/3'}
14
+ ASPECT_RATIO_VALUES = ASPECT_RATIO_NAMES.invert
15
+
16
+ CODECS = {'ffmp3' => :mp3, 'mp3' => :mp3, 'faad' => :aac, 'ffh264' => :h264, 'h264' => :h264, 'ffvp6f' => :flash_video}
17
+
18
+ TRACK_FIELD_TYPES = {
19
+ :codec => :codec,
20
+ :width => :integer,
21
+ :height => :integer,
22
+ :format => :string,
23
+ :aspect => :float,
24
+ :id => :integer,
25
+ :bitrate => :integer,
26
+ :fps => :float,
27
+ :file_name => :string,
28
+ :length => :float,
29
+ :demuxer => :string,
30
+ :rate => :integer,
31
+ :channels => :integer
32
+ }
33
+
34
+ FIELD_TYPES = {
35
+ :audio => :boolean,
36
+ :audio_format => :string,
37
+ :audio_rate => :integer,
38
+ :audio_bitrate => :integer,
39
+ :audio_codec => :codec,
40
+ :audio_channels => :integer,
41
+ :video => :boolean,
42
+ :video_format => :string,
43
+ :video_codec => :codec,
44
+ :width => :integer,
45
+ :height => :integer,
46
+ :aspect_ratio => :float,
47
+ :video_fps => :float,
48
+ :video_bitrate => :integer,
49
+ :ipod_uuid => :boolean,
50
+ :bitrate => :integer,
51
+ :length => :float,
52
+ :file_name => :string,
53
+ :file_extension => :string,
54
+ :demuxer => :string,
55
+ :poster_time => :float
56
+ }
57
+
58
+ def initialize(media_file_path)
59
+ super()
60
+ ffmpeg = FfmpegIntegrator.new(media_file_path)
61
+ mplayer = MplayerIntegrator.new(media_file_path)
62
+
63
+ puts "FFMPEG:\n#{ffmpeg.tracks.inspect}"
64
+
65
+ puts "MPLAYER:\n#{mplayer.tracks.inspect}"
66
+
67
+ store(:ipod_uuid, false)
68
+
69
+ merge!(get_video_info(get_video_track(ffmpeg.tracks), get_video_track(mplayer.tracks)))
70
+ merge!(get_audio_info(get_audio_track(ffmpeg.tracks), get_audio_track(mplayer.tracks)))
71
+ merge!(get_container_info(ffmpeg.tracks[:container], mplayer.tracks[:container]))
72
+
73
+ derive_values
74
+
75
+ if video?
76
+ atomic_parsley = AtomicParsleyIntegrator.new(media_file_path)
77
+ puts "ATOMIC_PARSLEY:\n#{atomic_parsley.tracks.inspect}"
78
+ store(:ipod_uuid, atomic_parsley.tracks[:container][:ipod_uuid])
79
+
80
+ exiftool = ExifToolIntegrator.new(media_file_path)
81
+ puts "EXIFTOOL:"
82
+ puts exiftool.inspect
83
+ store(:poster_time, exiftool.poster_time)
84
+ if exiftool.aspect_ratio and video_aspect != exiftool.aspect_ratio
85
+ store(:height, (width / exiftool.aspect_ratio).to_i)
86
+ store(:video_aspect, exiftool.aspect_ratio)
87
+ elsif exiftool.width && exiftool.height
88
+ store(:width, exiftool.width)
89
+ store(:height, exiftool.height)
90
+ derive_values
91
+ end
92
+ fix_dimensions
93
+ end
94
+
95
+ delete_if {|key, value| value.nil?}
96
+ end
97
+
98
+ def video?
99
+ video
100
+ end
101
+
102
+ def audio?
103
+ audio
104
+ end
105
+
106
+ def thumbnail_file
107
+ return @thumbnail_file if @thumbnail_file
108
+
109
+ return nil unless video? and height and width
110
+
111
+ time = poster_time
112
+ if time.nil? or time == 0 or time > 30
113
+ time = length / 10.0
114
+ time = 10 if time > 10
115
+ end
116
+
117
+ @thumbnail_file = FfmpegIntegrator.create_thumbnail(file_name, width, height, time)
118
+ end
119
+
120
+ def self.parse_values(values)
121
+ values.values.each do |track_values|
122
+ track_values.each do |key, value|
123
+ case TRACK_FIELD_TYPES[key]
124
+ when :integer
125
+ track_values[key] = value.to_i
126
+ when :float
127
+ track_values[key] = value.to_f
128
+ when :codec
129
+ track_values[key] = CODECS[value] || value
130
+ end
131
+ end
132
+ end
133
+
134
+ values
135
+ end
136
+
137
+ private
138
+
139
+ def get_video_track(tracks)
140
+ get_track_by_type(tracks, :video)
141
+ end
142
+
143
+ def get_audio_track(tracks)
144
+ get_track_by_type(tracks, :audio)
145
+ end
146
+
147
+ def get_track_by_type(tracks, type)
148
+ return nil if tracks.nil?
149
+ tracks.values.each do |track|
150
+ return track if track[:type] == type
151
+ end
152
+ nil
153
+ end
154
+
155
+ def has_real_video_track?(ffmpeg_video_track, mplayer_video_track)
156
+ return false if ffmpeg_video_track.nil? && mplayer_video_track.nil?
157
+
158
+ unless ffmpeg_video_track.nil?
159
+ return false if ffmpeg_video_track[:codec] == 'png'
160
+ return false if ffmpeg_video_track[:width] == 1
161
+ return false if ffmpeg_video_track[:height] == 1
162
+ return false if ffmpeg_video_track[:fps] && ffmpeg_video_track[:fps] > 1000
163
+ end
164
+
165
+ unless mplayer_video_track.nil?
166
+ return false if mplayer_video_track[:format] == 'jpeg'
167
+ return false if mplayer_video_track[:fps] == 0
168
+ end
169
+
170
+ ffmpeg_video_track ||= {}
171
+ mplayer_video_track ||= {}
172
+
173
+ return false if (ffmpeg_video_track[:codec].nil? && mplayer_video_track[:codec].nil?)
174
+ return false if (ffmpeg_video_track[:width].nil? && mplayer_video_track[:width].nil?)
175
+ return false if (ffmpeg_video_track[:height].nil? && mplayer_video_track[:height].nil?)
176
+
177
+ return true
178
+ end
179
+
180
+ def has_real_audio_track?(ffmpeg_audio_track, mplayer_audio_track)
181
+ unless ffmpeg_audio_track.nil?
182
+ return true if ffmpeg_audio_track[:codec]
183
+ return true if ffmpeg_audio_track[:format]
184
+ end
185
+ unless mplayer_audio_track.nil?
186
+ return true if mplayer_audio_track[:codec]
187
+ return true if mplayer_audio_track[:format]
188
+ end
189
+ return false
190
+ end
191
+
192
+ def get_video_info(ffmpeg_track, mplayer_track)
193
+ return {:video => false} unless has_real_video_track?(ffmpeg_track, mplayer_track)
194
+
195
+ ffmpeg_track ||= {}
196
+ mplayer_track ||= {}
197
+
198
+ output = {:video => true}
199
+
200
+ output[:video_format] = ffmpeg_track[:format] || mplayer_track[:format]
201
+ output[:width] = ffmpeg_track[:width] || mplayer_track[:width]
202
+ output[:height] = ffmpeg_track[:height] || mplayer_track[:height]
203
+ output[:video_aspect] = ffmpeg_track[:aspect] || mplayer_track[:aspect]
204
+ output[:video_fps] = ffmpeg_track[:fps] || mplayer_track[:fps]
205
+ output[:video_bitrate] = ffmpeg_track[:bitrate] || mplayer_track[:bitrate]
206
+
207
+ if ffmpeg_track[:codec].class == String && mplayer_track[:codec].class == Symbol
208
+ output[:video_codec] = mplayer_track[:codec]
209
+ else
210
+ output[:video_codec] = ffmpeg_track[:codec] || mplayer_track[:codec]
211
+ end
212
+
213
+ output
214
+ end
215
+
216
+ def get_audio_info(ffmpeg_track, mplayer_track)
217
+ return {:audio => false} unless has_real_audio_track?(ffmpeg_track, mplayer_track)
218
+
219
+ ffmpeg_track ||= {}
220
+ mplayer_track ||= {}
221
+
222
+ output = {:audio => true}
223
+
224
+ output[:audio_format] = ffmpeg_track[:format] || mplayer_track[:format]
225
+ output[:audio_rate] = ffmpeg_track[:rate] || mplayer_track[:rate]
226
+ output[:audio_bitrate] = ffmpeg_track[:bitrate] || mplayer_track[:bitrate]
227
+ output[:audio_channels] = ffmpeg_track[:channels] || mplayer_track[:channels]
228
+
229
+ if ffmpeg_track[:codec].class == String && mplayer_track[:codec].class == Symbol
230
+ output[:audio_codec] = mplayer_track[:codec]
231
+ else
232
+ output[:audio_codec] = ffmpeg_track[:codec] || mplayer_track[:codec]
233
+ end
234
+
235
+ output
236
+ end
237
+
238
+ def get_container_info(ffmpeg_track, mplayer_track)
239
+ output = Hash.new
240
+
241
+ high_prio = ffmpeg_track || {}
242
+ low_prio = mplayer_track || {}
243
+
244
+ output[:bitrate] = high_prio[:bitrate] || low_prio[:bitrate]
245
+ output[:length] = high_prio[:length] || low_prio[:length]
246
+ output[:file_name] = high_prio[:file_name] || low_prio[:file_name]
247
+ output[:demuxer] = high_prio[:demuxer] || low_prio[:demuxer]
248
+ output
249
+ end
250
+
251
+ def video_aspect_ratio
252
+ return nil unless video?
253
+
254
+ ratio = width.to_f / height.to_f
255
+
256
+ return nil if ratio.nan?
257
+
258
+ diffs = ASPECT_RATIO_NAMES.keys.map {|ar| [(ar - ratio).abs, ar]}
259
+
260
+ diffs.sort! {|x, y| x[0] <=> y[0]}
261
+
262
+ diffs.first[1]
263
+ end
264
+
265
+ def derive_values
266
+ store(:video_aspect, video_aspect_ratio) if (video? && has_key?(:width) && has_key?(:height))
267
+
268
+ if file_name && (m = file_name.match(/.+\.(\w+)/))
269
+ store(:file_extension, m[1])
270
+ end
271
+ end
272
+
273
+ def fix_dimensions
274
+ [:width, :height].each do |key|
275
+ if has_key?(key) and (fetch(key) % 2 == 1)
276
+ store(key, fetch(key) - 1)
277
+ end
278
+ end
279
+ end
280
+
281
+ # Intercept calls
282
+ def method_missing(method_name, *args)
283
+ if (FIELD_TYPES[method_name])
284
+ self[method_name]
285
+ else
286
+ super
287
+ end
288
+ end
289
+ end
290
+
291
+ class FfmpegIntegrator
292
+ attr_accessor :tracks
293
+ BINARY = ["ffmpeg"]
294
+ OPTIONS = ["-i"]
295
+ TIMEOUT = 60
296
+
297
+ def initialize(file_path)
298
+ commandline = []
299
+ commandline += BINARY
300
+ commandline += OPTIONS
301
+ commandline += [file_path]
302
+ puts "trying to run: #{commandline.join(' ')}"
303
+ result = begin
304
+ timeout(TIMEOUT) do
305
+ Open3.popen3(*commandline) do |i, o, e|
306
+ [o.read, e.read]
307
+ end
308
+ end
309
+ rescue Timeout::Error => e
310
+ puts "Timeout reached when inspecting #{file_path} with ffmpeg"
311
+ raise e
312
+ end
313
+
314
+ result = result.join("\n")
315
+
316
+ ffmpeg_values = Hash.new
317
+
318
+ start_index = result.index("Input #0, ")
319
+ @tracks = {}
320
+
321
+ unless start_index.nil?
322
+ result = result[start_index..-1]
323
+
324
+ result.split(/\n/).each do |line|
325
+ line.strip!
326
+ if match = line.match(/^Duration: ((\d\d):(\d\d):(\d\d(.\d)?)), .*, bitrate: (\d+) kb\/s/)
327
+ puts "found duration #{line}"
328
+ ffmpeg_values[:container] = {:type => :container}
329
+ if match[1]
330
+ hours = match[2] ? match[2].to_i : 0
331
+ minutes = match[3] ? match[3].to_i : 0
332
+ seconds = match[4] ? match[4].to_f : 0
333
+ ffmpeg_values[:container][:length] = (hours * 60 * 60) + (minutes * 60) + seconds
334
+ end
335
+ if match[6]
336
+ ffmpeg_values[:container][:bitrate] = match[6].to_i * 1000
337
+ end
338
+ elsif match = line.match(/^Stream #0.(\d).*: Video: ([^,]*)(, [^,]*)?(, (\d+)x(\d+))(, (\d+.\d+) fps)?/)
339
+ puts "found video #{line}"
340
+ track_info = ffmpeg_values["track_#{match[1]}".to_sym] = {:type => :video}
341
+ if match[2]
342
+ track_info[:codec] = match[2]
343
+ end
344
+ if match[5]
345
+ track_info[:width] = match[5]
346
+ end
347
+ if match[6]
348
+ track_info[:height] = match[6]
349
+ end
350
+ if match[8]
351
+ track_info[:fps] = match[8]
352
+ end
353
+ elsif match = line.match(/^Stream #0.(\d).*: Audio: ([^,]*)(, (\d+) Hz)?(, (mono|stereo))?(, (\d+) kb\/s)?/)
354
+ puts "found audio #{line}"
355
+ track_info = ffmpeg_values["track_#{match[1]}".to_sym] = {:type => :audio}
356
+ if match[2]
357
+ track_info[:codec] = match[2]
358
+ end
359
+ if match[4]
360
+ track_info[:rate] = match[4]
361
+ end
362
+ if match[6]
363
+ if match[6] == 'stereo'
364
+ track_info[:channels] = 2
365
+ elsif match[6] == 'mono'
366
+ track_info[:channels] = 1
367
+ end
368
+ end
369
+ if match[8]
370
+ track_info[:bitrate] = match[8].to_i * 1000
371
+ end
372
+ elsif match = line.match(/^Stream #0.(\d).*: Data: (.*)/)
373
+ puts "found data #{line}"
374
+ track_info = ffmpeg_values["track_#{match[1]}".to_sym] = {:type => :data}
375
+ else
376
+ puts "found other #{line}"
377
+ end
378
+ end
379
+
380
+ @tracks = MediaFileAttributes.parse_values(ffmpeg_values)
381
+ end
382
+ end
383
+
384
+ def self.create_thumbnail(file_path, width, height, time)
385
+ thumbnail_file_path = "#{file_path}.jpg"
386
+ commandline = []
387
+ commandline += BINARY
388
+ commandline << '-ss'
389
+ commandline << time.to_s
390
+ commandline << '-i'
391
+ commandline << file_path
392
+ commandline += ['-f', 'mjpeg', '-deinterlace', '-vframes', '1', '-an', '-y', '-s']
393
+ commandline << "#{width}x#{height}"
394
+ commandline << thumbnail_file_path
395
+
396
+ puts "trying to run: #{commandline.join(' ')}"
397
+ result = begin
398
+ timeout(60) do
399
+ Open3.popen3(*commandline) do |i, o, e|
400
+ [o.read, e.read]
401
+ end
402
+ end
403
+ rescue Timeout::Error => e
404
+ puts "Timeout reached when inspecting #{file_path} with ffmpeg"
405
+ raise e
406
+ end
407
+ thumbnail_file = File.new(thumbnail_file_path)
408
+ if thumbnail_file.stat.size == 0
409
+ FileUtils.rm_f(thumbnail_file.path)
410
+ throw result.join
411
+ end
412
+
413
+ return thumbnail_file
414
+ end
415
+ end
416
+
417
+ class MplayerIntegrator
418
+ attr_accessor :tracks
419
+ BINARY = ["mplayer"]
420
+ OPTIONS = ["-identify", "-vo", "null", "-ao", "null", "-frames", "0", "-really-quiet", "-msgcharset", "utf8"]
421
+ TIMEOUT = 60
422
+
423
+ MPLAYER_TRACK_FIELD_MAP = {
424
+ 'CODEC' => :codec,
425
+ 'WIDTH' => :width,
426
+ 'HEIGHT' => :height,
427
+ 'FORMAT' => :format,
428
+ 'ASPECT' => :aspect,
429
+ 'ID' => :id,
430
+ 'BITRATE' => :bitrate,
431
+ 'FPS' => :fps,
432
+ 'FILENAME' => :file_name,
433
+ 'LENGTH' => :length,
434
+ 'DEMUXER' => :demuxer,
435
+ 'RATE' => :rate,
436
+ 'NCH' => :channels
437
+ }
438
+
439
+ def initialize(file_path)
440
+ commandline = []
441
+ commandline += BINARY
442
+ commandline += OPTIONS
443
+ commandline += [file_path]
444
+ puts "trying to run: #{commandline.join(' ')}"
445
+ result = begin
446
+ timeout(TIMEOUT) do
447
+ Open3.popen3(*commandline) do |i, o, e|
448
+ [o.read, e.read]
449
+ end
450
+ end
451
+ rescue Timeout::Error => e
452
+ puts "Timeout reached when inspecting #{file_path} with mplayer"
453
+ raise e
454
+ end
455
+
456
+ raise "mplayer error when inspecting #{file_path}: #{result.last}" if result.first.empty? && !result.last.empty?
457
+
458
+ mplayer_values = {:container => {:type => :container}}
459
+
460
+ match = result.first.match(/.*ID_AUDIO_ID=(\d).*/)
461
+ audio_track = mplayer_values["track_#{match[1]}".to_sym] = {:type => :audio} unless match.nil?
462
+
463
+ match = result.first.match(/.*ID_VIDEO_ID=(\d).*/)
464
+ video_track = mplayer_values["track_#{match[1]}".to_sym] = {:type => :video} unless match.nil?
465
+
466
+ result.first.split(/\n/).each do |line|
467
+ #puts line
468
+ if (match = line.match(/ID_([^_]+)(_([^=].*))?=(.*)/))
469
+ if match[3]
470
+ key = MPLAYER_TRACK_FIELD_MAP[match[3]] || match[3]
471
+ else
472
+ key = MPLAYER_TRACK_FIELD_MAP[match[1]] || match[1]
473
+ end
474
+ case match[1]
475
+ when 'VIDEO'
476
+ video_track[key] = match[4]
477
+ when 'AUDIO'
478
+ audio_track[key] = match[4]
479
+ else
480
+ mplayer_values[:container][key] = match[4]
481
+ end
482
+ end
483
+ end
484
+
485
+ @tracks = MediaFileAttributes.parse_values(mplayer_values)
486
+ end
487
+ end
488
+
489
+ class AtomicParsleyIntegrator
490
+ attr_reader :values, :tracks
491
+ BINARY = ["AtomicParsley"]
492
+ OPTIONS = ['-T', '1']
493
+ TIMEOUT = 60
494
+
495
+ def initialize(file_path)
496
+ commandline = []
497
+ commandline += BINARY
498
+ commandline += [file_path]
499
+ commandline += OPTIONS
500
+ puts "trying to run: #{commandline.join(' ')}"
501
+ result = begin
502
+ timeout(TIMEOUT) do
503
+ Open3.popen3(*commandline) do |i, o, e|
504
+ o.read
505
+ end
506
+ end
507
+ rescue Timeout::Error => e
508
+ puts "Timeout reached when inspecting #{file_path} with AtomicParsley"
509
+ raise e
510
+ end
511
+
512
+ @tracks = {:container => {:type => :container, :ipod_uuid => false}}
513
+
514
+ atomic_parsley_values = Hash.new
515
+
516
+ if result =~ /Atom uuid=6b6840f2-5f24-4fc5-ba39-a51bcf0323f3/
517
+ @tracks[:container][:ipod_uuid] = true
518
+ atomic_parsley_values[:ipod_uuid] = true
519
+ end
520
+ @values = atomic_parsley_values
521
+ end
522
+ end
523
+
524
+ class ExifToolIntegrator
525
+ attr_accessor :aspect_ratio, :poster_time, :width, :height
526
+ BINARY = ["exiftool"]
527
+ OPTIONS = ["-q", "-q", "-s", "-t"]
528
+ TIMEOUT = 60
529
+
530
+ def initialize(file_path)
531
+ commandline = []
532
+ commandline += BINARY
533
+ commandline += OPTIONS
534
+ commandline += [file_path]
535
+ puts "trying to run: #{commandline.join(' ')}"
536
+ result = begin
537
+ timeout(TIMEOUT) do
538
+ Open3.popen3(*commandline) do |i, o, e|
539
+ o.read
540
+ end
541
+ end
542
+ rescue Timeout::Error => e
543
+ puts "Timeout reached when inspecting #{file_path} with ExifTool"
544
+ raise e
545
+ end
546
+
547
+ @values = Hash.new
548
+
549
+ result.split(/\n/).each do |line|
550
+ line.strip!
551
+ if match = line.match(/([^\t]+)\t(.+)/)
552
+ @values[match[1].underscore] = match[2]
553
+ end
554
+ end
555
+
556
+ unless @values['aspect_ratio'].blank?
557
+ aspect_ratio_match = @values['aspect_ratio'].match(/\d+:\d+/)
558
+ aspect_ratio_match = aspect_ratio_match[0] if aspect_ratio_match
559
+ case aspect_ratio_match
560
+ when "16:9", "16/9"
561
+ @aspect_ratio = MediaFileAttributes::ASPECT_RATIO_16_BY_9
562
+ when "4:3", "4/3"
563
+ @aspect_ratio = MediaFileAttributes::ASPECT_RATIO_4_BY_3
564
+ end
565
+ end
566
+
567
+ unless @values['poster_time'].blank?
568
+ poster_time_match = @values['poster_time'].match(/(\d+\.\d+)s/)
569
+ @poster_time = poster_time_match[1].to_f if poster_time_match
570
+ end
571
+
572
+ unless @values['image_height'].blank?
573
+ @height = @values['image_height'].to_i
574
+ end
575
+
576
+ unless @values['image_width'].blank?
577
+ @width = @values['image_width'].to_i
578
+ end
579
+ end
580
+ end
581
+ end
582
+ end