staugaard-transcoding_machine 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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