apple-tv-converter 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/Gemfile +11 -0
  2. data/Gemfile.lock +44 -0
  3. data/README.md +3 -2
  4. data/bin/SublerCLI +0 -0
  5. data/gems/streamio-ffmpeg/CHANGELOG +198 -0
  6. data/gems/streamio-ffmpeg/Gemfile +3 -0
  7. data/gems/streamio-ffmpeg/LICENSE +20 -0
  8. data/gems/streamio-ffmpeg/README.md +180 -0
  9. data/gems/streamio-ffmpeg/Rakefile +22 -0
  10. data/gems/streamio-ffmpeg/lib/ffmpeg/encoding_options.rb +139 -0
  11. data/gems/streamio-ffmpeg/lib/ffmpeg/errors.rb +4 -0
  12. data/gems/streamio-ffmpeg/lib/ffmpeg/io_monkey.rb +42 -0
  13. data/gems/streamio-ffmpeg/lib/ffmpeg/movie.rb +206 -0
  14. data/gems/streamio-ffmpeg/lib/ffmpeg/stream.rb +15 -0
  15. data/gems/streamio-ffmpeg/lib/ffmpeg/transcoder.rb +120 -0
  16. data/gems/streamio-ffmpeg/lib/ffmpeg/version.rb +3 -0
  17. data/gems/streamio-ffmpeg/lib/streamio-ffmpeg.rb +49 -0
  18. data/gems/streamio-ffmpeg/spec/ffmpeg/encoding_options_spec.rb +135 -0
  19. data/gems/streamio-ffmpeg/spec/ffmpeg/movie_spec.rb +325 -0
  20. data/gems/streamio-ffmpeg/spec/ffmpeg/transcoder_spec.rb +210 -0
  21. data/gems/streamio-ffmpeg/spec/fixtures/bin/ffmpeg-audio-only +62 -0
  22. data/gems/streamio-ffmpeg/spec/fixtures/bin/ffmpeg-hanging +44 -0
  23. data/gems/streamio-ffmpeg/spec/fixtures/movies/awesome movie.mov +0 -0
  24. data/gems/streamio-ffmpeg/spec/fixtures/movies/awesome'movie.mov +0 -0
  25. data/gems/streamio-ffmpeg/spec/fixtures/movies/awesome_widescreen.mov +0 -0
  26. data/gems/streamio-ffmpeg/spec/fixtures/movies/broken.mp4 +0 -0
  27. data/gems/streamio-ffmpeg/spec/fixtures/movies/empty.flv +0 -0
  28. data/gems/streamio-ffmpeg/spec/fixtures/movies/sideways movie.mov +0 -0
  29. data/gems/streamio-ffmpeg/spec/fixtures/movies/weird_aspect.small.mpg +0 -0
  30. data/gems/streamio-ffmpeg/spec/fixtures/outputs/file_with_iso-8859-1.txt +35 -0
  31. data/gems/streamio-ffmpeg/spec/fixtures/outputs/file_with_no_audio.txt +18 -0
  32. data/gems/streamio-ffmpeg/spec/fixtures/outputs/file_with_non_supported_audio.txt +29 -0
  33. data/gems/streamio-ffmpeg/spec/fixtures/outputs/file_with_surround_sound.txt +19 -0
  34. data/gems/streamio-ffmpeg/spec/fixtures/outputs/file_with_weird_dar.txt +24 -0
  35. data/gems/streamio-ffmpeg/spec/fixtures/sounds/napoleon.mp3 +0 -0
  36. data/gems/streamio-ffmpeg/spec/spec_helper.rb +21 -0
  37. data/gems/streamio-ffmpeg/spec/streamio-ffmpeg_spec.rb +40 -0
  38. data/gems/streamio-ffmpeg/streamio-ffmpeg.gemspec +19 -0
  39. data/lib/apple_tv_converter.rb +10 -4
  40. data/lib/apple_tv_converter/apple_tv_converter.sublime-project +0 -6
  41. data/lib/apple_tv_converter/apple_tv_converter.sublime-workspace +501 -378
  42. data/lib/apple_tv_converter/command_line.rb +46 -22
  43. data/lib/apple_tv_converter/media.rb +49 -16
  44. data/lib/apple_tv_converter/media_converter.rb +19 -18
  45. data/lib/apple_tv_converter/media_converter_adapter.rb +58 -14
  46. data/lib/apple_tv_converter/media_converter_mac_adapter.rb +46 -35
  47. data/lib/apple_tv_converter/version.rb +1 -1
  48. metadata +49 -12
@@ -0,0 +1,3 @@
1
+ module FFMPEG
2
+ VERSION = "0.9.0"
3
+ end
@@ -0,0 +1,49 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ require 'logger'
4
+ require 'stringio'
5
+
6
+ require 'ffmpeg/version'
7
+ require 'ffmpeg/errors'
8
+ require 'ffmpeg/movie'
9
+ require 'ffmpeg/stream'
10
+ require 'ffmpeg/io_monkey'
11
+ require 'ffmpeg/transcoder'
12
+ require 'ffmpeg/encoding_options'
13
+
14
+ module FFMPEG
15
+ # FFMPEG logs information about its progress when it's transcoding.
16
+ # Jack in your own logger through this method if you wish to.
17
+ #
18
+ # @param [Logger] log your own logger
19
+ # @return [Logger] the logger you set
20
+ def self.logger=(log)
21
+ @logger = log
22
+ end
23
+
24
+ # Get FFMPEG logger.
25
+ #
26
+ # @return [Logger]
27
+ def self.logger
28
+ return @logger if @logger
29
+ logger = Logger.new(STDOUT)
30
+ logger.level = Logger::INFO
31
+ @logger = logger
32
+ end
33
+
34
+ # Set the path of the ffmpeg binary.
35
+ # Can be useful if you need to specify a path such as /usr/local/bin/ffmpeg
36
+ #
37
+ # @param [String] path to the ffmpeg binary
38
+ # @return [String] the path you set
39
+ def self.ffmpeg_binary=(bin)
40
+ @ffmpeg_binary = bin
41
+ end
42
+
43
+ # Get the path to the ffmpeg binary, defaulting to 'ffmpeg'
44
+ #
45
+ # @return [String] the path to the ffmpeg binary
46
+ def self.ffmpeg_binary
47
+ @ffmpeg_binary || 'ffmpeg'
48
+ end
49
+ end
@@ -0,0 +1,135 @@
1
+ require 'spec_helper.rb'
2
+
3
+ module FFMPEG
4
+ describe EncodingOptions do
5
+ describe "ffmpeg arguments conversion" do
6
+ it "should convert video codec" do
7
+ EncodingOptions.new(video_codec: "libx264").to_s.should == "-vcodec libx264"
8
+ end
9
+
10
+ it "should know the width from the resolution or be nil" do
11
+ EncodingOptions.new(resolution: "320x240").width.should == 320
12
+ EncodingOptions.new.width.should be_nil
13
+ end
14
+
15
+ it "should know the height from the resolution or be nil" do
16
+ EncodingOptions.new(resolution: "320x240").height.should == 240
17
+ EncodingOptions.new.height.should be_nil
18
+ end
19
+
20
+ it "should convert frame rate" do
21
+ EncodingOptions.new(frame_rate: 29.9).to_s.should == "-r 29.9"
22
+ end
23
+
24
+ it "should convert the resolution" do
25
+ EncodingOptions.new(resolution: "640x480").to_s.should include("-s 640x480")
26
+ end
27
+
28
+ it "should add calculated aspect ratio" do
29
+ EncodingOptions.new(resolution: "640x480").to_s.should include("-aspect 1.3333333")
30
+ EncodingOptions.new(resolution: "640x360").to_s.should include("-aspect 1.7777777777777")
31
+ end
32
+
33
+ it "should use specified aspect ratio if given" do
34
+ output = EncodingOptions.new(resolution: "640x480", aspect: 1.77777777777778).to_s
35
+ output.should include("-s 640x480")
36
+ output.should include("-aspect 1.77777777777778")
37
+ end
38
+
39
+ it "should convert video bitrate" do
40
+ EncodingOptions.new(video_bitrate: "600k").to_s.should == "-b:v 600k"
41
+ end
42
+
43
+ it "should use k unit for video bitrate" do
44
+ EncodingOptions.new(video_bitrate: 600).to_s.should == "-b:v 600k"
45
+ end
46
+
47
+ it "should convert audio codec" do
48
+ EncodingOptions.new(audio_codec: "aac").to_s.should == "-acodec aac"
49
+ end
50
+
51
+ it "should convert audio bitrate" do
52
+ EncodingOptions.new(audio_bitrate: "128k").to_s.should == "-b:a 128k"
53
+ end
54
+
55
+ it "should use k unit for audio bitrate" do
56
+ EncodingOptions.new(audio_bitrate: 128).to_s.should == "-b:a 128k"
57
+ end
58
+
59
+ it "should convert audio sample rate" do
60
+ EncodingOptions.new(audio_sample_rate: 44100).to_s.should == "-ar 44100"
61
+ end
62
+
63
+ it "should convert audio channels" do
64
+ EncodingOptions.new(audio_channels: 2).to_s.should == "-ac 2"
65
+ end
66
+
67
+ it "should convert maximum video bitrate" do
68
+ EncodingOptions.new(video_max_bitrate: 600).to_s.should == "-maxrate 600k"
69
+ end
70
+
71
+ it "should convert mininimum video bitrate" do
72
+ EncodingOptions.new(video_min_bitrate: 600).to_s.should == "-minrate 600k"
73
+ end
74
+
75
+ it "should convert video bitrate tolerance" do
76
+ EncodingOptions.new(video_bitrate_tolerance: 100).to_s.should == "-bt 100k"
77
+ end
78
+
79
+ it "should convert buffer size" do
80
+ EncodingOptions.new(buffer_size: 2000).to_s.should == "-bufsize 2000k"
81
+ end
82
+
83
+ it "should convert threads" do
84
+ EncodingOptions.new(threads: 2).to_s.should == "-threads 2"
85
+ end
86
+
87
+ it "should convert duration" do
88
+ EncodingOptions.new(duration: 30).to_s.should == "-t 30"
89
+ end
90
+
91
+ it "should convert keyframe interval" do
92
+ EncodingOptions.new(keyframe_interval: 60).to_s.should == "-g 60"
93
+ end
94
+
95
+ it "should convert video preset" do
96
+ EncodingOptions.new(video_preset: "max").to_s.should == "-vpre max"
97
+ end
98
+
99
+ it "should convert audio preset" do
100
+ EncodingOptions.new(audio_preset: "max").to_s.should == "-apre max"
101
+ end
102
+
103
+ it "should convert file preset" do
104
+ EncodingOptions.new(file_preset: "max.ffpreset").to_s.should == "-fpre max.ffpreset"
105
+ end
106
+
107
+ it "should specify seek time" do
108
+ EncodingOptions.new(seek_time: 1).to_s.should == "-ss 1"
109
+ end
110
+
111
+ it "should specify screenshot parameters" do
112
+ EncodingOptions.new(screenshot: true).to_s.should == "-vframes 1 -f image2"
113
+ end
114
+
115
+ it "should put the parameters in order of codecs, presets, others" do
116
+ opts = Hash.new
117
+ opts[:frame_rate] = 25
118
+ opts[:video_codec] = "libx264"
119
+ opts[:video_preset] = "normal"
120
+
121
+ converted = EncodingOptions.new(opts).to_s
122
+ converted.should == "-vcodec libx264 -vpre normal -r 25"
123
+ end
124
+
125
+ it "should convert a lot of them simultaneously" do
126
+ converted = EncodingOptions.new(video_codec: "libx264", audio_codec: "aac", video_bitrate: "1000k").to_s
127
+ converted.should match(/-acodec aac/)
128
+ end
129
+
130
+ it "should ignore options with nil value" do
131
+ EncodingOptions.new(video_codec: "libx264", frame_rate: nil).to_s.should == "-vcodec libx264 "
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,325 @@
1
+ require 'spec_helper.rb'
2
+
3
+ module FFMPEG
4
+ describe Movie do
5
+ describe "initializing" do
6
+ context "given a non existing file" do
7
+ it "should throw ArgumentError" do
8
+ expect { Movie.new("i_dont_exist") }.to raise_error(Errno::ENOENT, /does not exist/)
9
+ end
10
+ end
11
+
12
+ context "given a file containing a single quotation mark in the filename" do
13
+ before(:all) do
14
+ @movie = Movie.new("#{fixture_path}/movies/awesome'movie.mov")
15
+ end
16
+
17
+ it "should run ffmpeg successfully" do
18
+ @movie.duration.should == 7.56
19
+ @movie.frame_rate.should == 16.75
20
+ end
21
+ end
22
+
23
+ context "given a non movie file" do
24
+ before(:all) do
25
+ @movie = Movie.new(__FILE__)
26
+ end
27
+
28
+ it "should not be valid" do
29
+ @movie.should_not be_valid
30
+ end
31
+
32
+ it "should have a duration of 0" do
33
+ @movie.duration.should == 0
34
+ end
35
+
36
+ it "should have nil height" do
37
+ @movie.height.should be_nil
38
+ end
39
+
40
+ it "should have nil width" do
41
+ @movie.width.should be_nil
42
+ end
43
+
44
+ it "should have nil frame_rate" do
45
+ @movie.frame_rate.should be_nil
46
+ end
47
+ end
48
+
49
+ context "given an empty flv file (could not find codec parameters)" do
50
+ before(:all) do
51
+ @movie = Movie.new("#{fixture_path}/movies/empty.flv")
52
+ end
53
+
54
+ it "should not be valid" do
55
+ @movie.should_not be_valid
56
+ end
57
+ end
58
+
59
+ context "given a broken mp4 file" do
60
+ before(:all) do
61
+ @movie = Movie.new("#{fixture_path}/movies/broken.mp4")
62
+ end
63
+
64
+ it "should not be valid" do
65
+ @movie.should_not be_valid
66
+ end
67
+
68
+ it "should have nil calculated_aspect_ratio" do
69
+ @movie.calculated_aspect_ratio.should be_nil
70
+ end
71
+ end
72
+
73
+ context "given a weird aspect ratio file" do
74
+ before(:all) do
75
+ @movie = Movie.new("#{fixture_path}/movies/weird_aspect.small.mpg")
76
+ end
77
+
78
+ it "should parse the DAR" do
79
+ @movie.dar.should == "704:405"
80
+ end
81
+
82
+ it "should have correct calculated_aspect_ratio" do
83
+ @movie.calculated_aspect_ratio.to_s[0..14].should == "1.7382716049382" # substringed to be 1.9 compatible
84
+ end
85
+ end
86
+
87
+ context "given an impossible DAR" do
88
+ before(:all) do
89
+ fake_output = StringIO.new(File.read("#{fixture_path}/outputs/file_with_weird_dar.txt"))
90
+ Open3.stub!(:popen3).and_yield(nil,nil,fake_output)
91
+ @movie = Movie.new(__FILE__)
92
+ end
93
+
94
+ it "should parse the DAR" do
95
+ @movie.dar.should == "0:1"
96
+ end
97
+
98
+ it "should calulate using width and height instead" do
99
+ @movie.calculated_aspect_ratio.to_s[0..14].should == "1.7777777777777" # substringed to be 1.9 compatible
100
+ end
101
+ end
102
+
103
+ context "given a file with ISO-8859-1 characters in output" do
104
+ it "should not crash" do
105
+ fake_output = StringIO.new(File.read("#{fixture_path}/outputs/file_with_iso-8859-1.txt"))
106
+ Open3.stub!(:popen3).and_yield(nil, nil, fake_output)
107
+ expect { Movie.new(__FILE__) }.to_not raise_error
108
+ end
109
+ end
110
+
111
+ context "given a file with 5.1 audio" do
112
+ before(:all) do
113
+ fake_output = StringIO.new(File.read("#{fixture_path}/outputs/file_with_surround_sound.txt"))
114
+ Open3.stub!(:popen3).and_yield(nil, nil, fake_output)
115
+ @movie = Movie.new(__FILE__)
116
+ end
117
+
118
+ it "should have 6 audio channels" do
119
+ @movie.audio_channels.should == 6
120
+ end
121
+ end
122
+
123
+ context "given a file with no audio" do
124
+ before(:all) do
125
+ fake_output = StringIO.new(File.read("#{fixture_path}/outputs/file_with_no_audio.txt"))
126
+ Open3.stub!(:popen3).and_yield(nil, nil, fake_output)
127
+ @movie = Movie.new(__FILE__)
128
+ end
129
+
130
+ it "should have nil audio channels" do
131
+ @movie.audio_channels.should == nil
132
+ end
133
+ end
134
+
135
+ context "given a file with non supported audio" do
136
+ before(:all) do
137
+ fake_output = StringIO.new(File.read("#{fixture_path}/outputs/file_with_non_supported_audio.txt"))
138
+ Open3.stub!(:popen3).and_yield(nil, nil, fake_output)
139
+ @movie = Movie.new(__FILE__)
140
+ end
141
+
142
+ it "should not be valid" do
143
+ @movie.should_not be_valid
144
+ end
145
+ end
146
+
147
+ context "given an awesome movie file" do
148
+ before(:all) do
149
+ @movie = Movie.new("#{fixture_path}/movies/awesome movie.mov")
150
+ end
151
+
152
+ it "should remember the movie path" do
153
+ @movie.path.should == "#{fixture_path}/movies/awesome movie.mov"
154
+ end
155
+
156
+ it "should parse duration to number of seconds" do
157
+ @movie.duration.should == 7.56
158
+ end
159
+
160
+ it "should parse the bitrate" do
161
+ @movie.bitrate.should == 481
162
+ end
163
+
164
+ it "should return nil rotation when no rotation exists" do
165
+ @movie.rotation.should == nil
166
+ end
167
+
168
+ it "should parse the creation_time" do
169
+ @movie.creation_time.should == Time.parse("2010-02-05 16:05:04")
170
+ end
171
+
172
+ it "should parse video stream information" do
173
+ @movie.video_stream.should == "h264 (Main) (avc1 / 0x31637661), yuv420p, 640x480 [SAR 1:1 DAR 4:3], 371 kb/s, 16.75 fps, 600 tbr, 600 tbn, 1200 tbc"
174
+ end
175
+
176
+ it "should know the video codec" do
177
+ @movie.video_codec.should =~ /h264/
178
+ end
179
+
180
+ it "should know the colorspace" do
181
+ @movie.colorspace.should == "yuv420p"
182
+ end
183
+
184
+ it "should know the resolution" do
185
+ @movie.resolution.should == "640x480"
186
+ end
187
+
188
+ it "should know the video bitrate" do
189
+ @movie.video_bitrate.should == 371
190
+ end
191
+
192
+ it "should know the width and height" do
193
+ @movie.width.should == 640
194
+ @movie.height.should == 480
195
+ end
196
+
197
+ it "should know the framerate" do
198
+ @movie.frame_rate.should == 16.75
199
+ end
200
+
201
+ it "should parse audio stream information" do
202
+ @movie.audio_stream.should == "aac (mp4a / 0x6134706D), 44100 Hz, stereo, s16, 75 kb/s"
203
+ end
204
+
205
+ it "should know the audio codec" do
206
+ @movie.audio_codec.should =~ /aac/
207
+ end
208
+
209
+ it "should know the sample rate" do
210
+ @movie.audio_sample_rate.should == 44100
211
+ end
212
+
213
+ it "should know the number of audio channels" do
214
+ @movie.audio_channels.should == 2
215
+ end
216
+
217
+ it "should know the audio bitrate" do
218
+ @movie.audio_bitrate.should == 75
219
+ end
220
+
221
+ it "should should be valid" do
222
+ @movie.should be_valid
223
+ end
224
+
225
+ it "should calculate the aspect ratio" do
226
+ @movie.calculated_aspect_ratio.to_s[0..14].should == "1.3333333333333" # substringed to be 1.9 compatible
227
+ end
228
+
229
+ it "should know the file size" do
230
+ @movie.size.should == 455546
231
+ end
232
+
233
+ it "should know the container" do
234
+ @movie.container.should == "mov,mp4,m4a,3gp,3g2,mj2"
235
+ end
236
+ end
237
+ end
238
+
239
+ context "given a rotated movie file" do
240
+ before(:all) do
241
+ @movie = Movie.new("#{fixture_path}/movies/sideways movie.mov")
242
+ end
243
+
244
+ it "should parse the rotation" do
245
+ @movie.rotation.should == 90
246
+ end
247
+ end
248
+
249
+ describe "transcode" do
250
+ it "should run the transcoder" do
251
+ movie = Movie.new("#{fixture_path}/movies/awesome movie.mov")
252
+
253
+ mockery = mock(Transcoder)
254
+ Transcoder.should_receive(:new).with(movie, "#{tmp_path}/awesome.flv", {custom: "-vcodec libx264"}, preserve_aspect_ratio: :width).and_return(mockery)
255
+ mockery.should_receive(:run)
256
+
257
+ movie.transcode("#{tmp_path}/awesome.flv", {custom: "-vcodec libx264"}, preserve_aspect_ratio: :width)
258
+ end
259
+ end
260
+
261
+ describe "screenshot" do
262
+ it "should run the transcoder with screenshot option" do
263
+ movie = Movie.new("#{fixture_path}/movies/awesome movie.mov")
264
+
265
+ mockery = mock(Transcoder)
266
+ Transcoder.should_receive(:new).with(movie, "#{tmp_path}/awesome.jpg", {seek_time: 2, dimensions: "640x480", screenshot: true}, preserve_aspect_ratio: :width).and_return(mockery)
267
+ mockery.should_receive(:run)
268
+
269
+ movie.screenshot("#{tmp_path}/awesome.jpg", {seek_time: 2, dimensions: "640x480"}, preserve_aspect_ratio: :width)
270
+ end
271
+ end
272
+
273
+ context "given a movie with audio" do
274
+ before(:all) do
275
+ @movie = Movie.new("#{fixture_path}/movies/awesome movie.mov")
276
+ end
277
+
278
+ it "should have two total streams" do
279
+ @movie.streams.count.should == 2
280
+ end
281
+
282
+ it "should have one audio stream" do
283
+ @movie.streams.select { |s| s.type == :audio }.count.should == 1
284
+ end
285
+
286
+ it "should have one video stream" do
287
+ @movie.streams.select { |s| s.type == :video }.count.should == 1
288
+ end
289
+
290
+ it "should have no subtitle streams" do
291
+ @movie.streams.select { |s| s.type == :subtitle }.count.should == 0
292
+ end
293
+
294
+ it "should identify the audio stream language" do
295
+ @movie.streams.select { |s| s.type == :audio }.first.language.should == 'und'
296
+ end
297
+ end
298
+
299
+ context "given an audio file" do
300
+ before(:all) do
301
+ @movie = Movie.new("#{fixture_path}/sounds/napoleon.mp3")
302
+ end
303
+
304
+ it "should have one total stream" do
305
+ @movie.streams.count.should == 1
306
+ end
307
+
308
+ it "should have one audio stream" do
309
+ @movie.streams.select { |s| s.type == :audio }.count.should == 1
310
+ end
311
+
312
+ it "should have no video streams" do
313
+ @movie.streams.select { |s| s.type == :video }.count.should == 0
314
+ end
315
+
316
+ it "should have no subtitle streams" do
317
+ @movie.streams.select { |s| s.type == :subtitle }.count.should == 0
318
+ end
319
+
320
+ it "should know the container format" do
321
+ @movie.container.should =~ /mp3/
322
+ end
323
+ end
324
+ end
325
+ end