handbrake 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,10 @@
1
+ 0.2.0
2
+ =====
3
+
4
+ - Support overwrite detection and behaviors for the
5
+ {HandBrake::CLI#output}.
6
+ - Support "atomic" output mode in {HandBrake::CLI#output}.
7
+
1
8
  0.1.0
2
9
  =====
3
10
 
data/README.md CHANGED
@@ -126,6 +126,13 @@ To put it more technically, the chain object returned from each
126
126
  intermediate configuration step returns an independent copy of the
127
127
  configuration chain.
128
128
 
129
+ ### Output options
130
+
131
+ By default, the `output` execution method behaves just like invoking
132
+ HandBrakeCLI directly. `handbrake.rb` also supports a couple of
133
+ additional behaviors, including overwrite detection and atomic output
134
+ file creation. See {HandBrake::CLI#output} for more details.
135
+
129
136
  Additional resources
130
137
  --------------------
131
138
 
@@ -8,4 +8,10 @@ module HandBrake
8
8
  autoload :Titles, 'handbrake/titles'
9
9
  autoload :Title, 'handbrake/titles'
10
10
  autoload :Chapter, 'handbrake/titles'
11
+
12
+ ##
13
+ # The exception thrown when a file exists that shouldn't.
14
+ #
15
+ # @see CLI#output
16
+ class FileExistsError < StandardError; end;
11
17
  end
@@ -59,11 +59,88 @@ module HandBrake
59
59
  # Performs a conversion. This method immediately begins the
60
60
  # transcoding process; set all other options first.
61
61
  #
62
+ # @param [String] filename the desired name for the final output
63
+ # file
64
+ # @param [Hash] options additional options to control the behavior
65
+ # of the output process
66
+ # @option options [Boolean,:ignore] :overwrite (true) determines
67
+ # the behavior if the desired output file already exists. If
68
+ # `true`, the file is replaced. If `false`, an exception is
69
+ # thrown. If `:ignore`, the file is skipped; i.e., HandBrakeCLI
70
+ # is not invoked.
71
+ # @option options [Boolean] :atomic (false) provides a
72
+ # pseudo-atomic mode for transcoded output. If true, the
73
+ # transcode will go into a temporary file and only be copied to
74
+ # the specified filename if it completes. The temporary filename
75
+ # is the target filename with `.handbraking` inserted before the
76
+ # extension. Any `:overwrite` checking will be applied to the
77
+ # target filename both before and after the transcode happens
78
+ # (the temporary file will always be overwritten). This option
79
+ # is intended to aid in writing automatically resumable batch
80
+ # scripts.
81
+ #
62
82
  # @return [void]
63
- def output(filename)
64
- run('--output', filename)
83
+ def output(filename, options={})
84
+ overwrite = options.delete :overwrite
85
+ case overwrite
86
+ when true, nil
87
+ # no special behavior
88
+ when false
89
+ raise FileExistsError, filename if File.exist?(filename)
90
+ when :ignore
91
+ if File.exist?(filename)
92
+ trace "Ignoring transcode to #{filename.inspect} because it already exists"
93
+ return
94
+ end
95
+ else
96
+ raise "Unsupported value for :overwrite: #{overwrite.inspect}"
97
+ end
98
+
99
+ atomic = options.delete :atomic
100
+ interim_filename =
101
+ if atomic
102
+ partial_filename(filename)
103
+ else
104
+ filename
105
+ end
106
+
107
+ unless options.empty?
108
+ raise "Unknown options for output: #{options.keys.inspect}"
109
+ end
110
+
111
+ run('--output', interim_filename)
112
+
113
+ if filename != interim_filename
114
+ replace =
115
+ if File.exist?(filename)
116
+ trace "#{filename.inspect} showed up during transcode"
117
+ case overwrite
118
+ when false
119
+ raise FileExistsError, filename
120
+ when :ignore
121
+ trace "- will leave #{filename.inspect} as is; copy #{interim_filename.inspect} manually if you want to replace it"
122
+ false
123
+ else
124
+ trace '- will replace with new transcode'
125
+ true
126
+ end
127
+ else
128
+ true
129
+ end
130
+ FileUtils.mv interim_filename, filename if replace
131
+ end
65
132
  end
66
133
 
134
+ def partial_filename(name)
135
+ if File.basename(name).index '.'
136
+ dot_at = name.rindex '.'
137
+ name.dup.insert dot_at, '.handbraking'
138
+ else
139
+ name + '.handbraking'
140
+ end
141
+ end
142
+ private :partial_filename
143
+
67
144
  ##
68
145
  # Performs a title scan. Unlike HandBrakeCLI, if you do not
69
146
  # specify a title, this method will return information for all
@@ -136,6 +213,10 @@ module HandBrake
136
213
  end
137
214
  end
138
215
 
216
+ def trace(msg)
217
+ $stderr.puts msg if trace?
218
+ end
219
+
139
220
  ##
140
221
  # Copies this CLI instance and appends another command line switch
141
222
  # plus optional arguments.
@@ -1,5 +1,5 @@
1
1
  module HandBrake
2
2
  ##
3
3
  # The current version
4
- VERSION = "0.1.0"
4
+ VERSION = '0.2.0'
5
5
  end
@@ -71,14 +71,167 @@ module HandBrake
71
71
  end
72
72
 
73
73
  it 'works' do
74
- lambda { cli.update }.should_not raise_error
74
+ lambda { cli.preset_list }.should_not raise_error
75
75
  end
76
76
  end
77
77
 
78
78
  describe '#output' do
79
+ let(:filename) { File.join(tmpdir, 'foo.m4v') }
80
+
81
+ def it_should_have_run(expected_fn=filename)
82
+ runner.actual_arguments.should == ['--output', expected_fn]
83
+ end
84
+
85
+ def it_should_not_have_run
86
+ runner.actual_arguments.should be_nil
87
+ end
88
+
79
89
  it 'uses the --output argument' do
80
- cli.output('/foo/bar.m4v')
81
- runner.actual_arguments.should == %w(--output /foo/bar.m4v)
90
+ cli.output(filename)
91
+ runner.actual_arguments.should == ['--output', filename]
92
+ end
93
+
94
+ it 'fails for an unknown option' do
95
+ lambda { cli.output(filename, :quux => 'zap') }.
96
+ should raise_error('Unknown options for output: [:quux]')
97
+ end
98
+
99
+ describe ':overwrite' do
100
+ context 'true' do
101
+ it 'executes when the file does not exist' do
102
+ cli.output(filename, :overwrite => true)
103
+ it_should_have_run
104
+ end
105
+
106
+ it 'executes when the file exists' do
107
+ FileUtils.touch filename
108
+ cli.output(filename, :overwrite => true)
109
+ it_should_have_run
110
+ end
111
+
112
+ it 'is the default' do
113
+ FileUtils.touch filename
114
+ cli.output(filename)
115
+ it_should_have_run
116
+ end
117
+ end
118
+
119
+ context 'false' do
120
+ it 'executes when the file does not exist' do
121
+ cli.output(filename, :overwrite => false)
122
+ it_should_have_run
123
+ end
124
+
125
+ it 'throws an exception when the file does exist' do
126
+ FileUtils.touch filename
127
+ lambda { cli.output(filename, :overwrite => false) }.
128
+ should raise_error(HandBrake::FileExistsError)
129
+ end
130
+ end
131
+
132
+ context ':ignore' do
133
+ it 'executes when the file does not exist' do
134
+ cli.output(filename, :overwrite => :ignore)
135
+ it_should_have_run
136
+ end
137
+
138
+ it 'does nothing when the file does exist' do
139
+ FileUtils.touch filename
140
+ cli.output(filename, :overwrite => :ignore)
141
+ it_should_not_have_run
142
+ end
143
+ end
144
+
145
+ context 'other value' do
146
+ it 'throws an exception' do
147
+ lambda { cli.output(filename, :overwrite => 'flip') }.
148
+ should raise_error('Unsupported value for :overwrite: "flip"')
149
+ end
150
+ end
151
+ end
152
+
153
+ describe ':atomic' do
154
+ context 'false' do
155
+ it 'executes normally' do
156
+ cli.output(filename, :atomic => false)
157
+ it_should_have_run
158
+ end
159
+
160
+ it 'is the default' do
161
+ cli.output(filename)
162
+ it_should_have_run
163
+ end
164
+ end
165
+
166
+ context 'true' do
167
+ let(:expected_temporary_file) { File.join(tmpdir, "foo.handbraking.m4v") }
168
+
169
+ it 'outputs to the temporary file with ".handbraking" inserted before the extension' do
170
+ cli.output(filename, :atomic => true)
171
+ it_should_have_run(expected_temporary_file)
172
+ end
173
+
174
+ it 'appends .handbraking if the output file does not have an extension' do
175
+ cli.output(File.join(tmpdir('a.b.c'), 'foo'), :atomic => true)
176
+ it_should_have_run(File.join(tmpdir('a.b.c'), 'foo.handbraking'))
177
+ end
178
+
179
+ it 'copies the output to the desired filename' do
180
+ cli.output(filename, :atomic => true)
181
+ File.read(filename).should == 'This is the file created by --output'
182
+ end
183
+
184
+ it 'removes the temporary file' do
185
+ cli.output(filename, :atomic => true)
186
+ File.exist?(expected_temporary_file).should be_false
187
+ end
188
+
189
+ it 'overwrites the temporary file if it exists' do
190
+ FileUtils.touch expected_temporary_file
191
+ cli.output(filename, :atomic => true)
192
+ it_should_have_run(expected_temporary_file)
193
+ end
194
+
195
+ describe 'and :overwrite' do
196
+ describe 'false' do
197
+ it 'throws an exception if the target filename exists initially' do
198
+ FileUtils.touch filename
199
+ lambda { cli.output(filename, :atomic => true, :overwrite => false) }.
200
+ should raise_error(HandBrake::FileExistsError)
201
+ end
202
+
203
+ it 'throws an exception if the target filename exists after output' do
204
+ runner.behavior = lambda { FileUtils.touch filename }
205
+ lambda { cli.output(filename, :atomic => true, :overwrite => false) }.
206
+ should raise_error(HandBrake::FileExistsError)
207
+ end
208
+ end
209
+
210
+ describe ':ignore' do
211
+ it 'does nothing if the target filename exists initially' do
212
+ FileUtils.touch filename
213
+ cli.output(filename, :atomic => true, :overwrite => :ignore)
214
+ it_should_not_have_run
215
+ end
216
+
217
+ it 'does not copy the output if the target filename exists after encoding' do
218
+ runner.behavior = lambda {
219
+ File.open(filename, 'w') { |f| f.write 'Other process result' }
220
+ }
221
+ cli.output(filename, :atomic => true, :overwrite => :ignore)
222
+ File.read(filename).should == 'Other process result'
223
+ end
224
+
225
+ it 'does not remove the temporary output if the target filename exists after encoding' do
226
+ runner.behavior = lambda {
227
+ File.open(filename, 'w') { |f| f.write 'Other process result' }
228
+ }
229
+ cli.output(filename, :atomic => true, :overwrite => :ignore)
230
+ File.exist?(expected_temporary_file).should be_true
231
+ end
232
+ end
233
+ end
234
+ end
82
235
  end
83
236
  end
84
237
 
@@ -2,38 +2,29 @@ require 'bundler'
2
2
  Bundler.setup
3
3
 
4
4
  require 'rspec'
5
+ require 'fileutils'
5
6
 
6
7
  $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
7
8
 
8
9
  require 'handbrake'
9
10
 
10
11
  RSpec.configure do |config|
11
- # Captures everything printed to stdout during the block
12
- # and returns it as a string.
13
- def capture_stdout
14
- old_stdout, $stdout = $stdout, StringIO.new
15
- begin
16
- yield
17
- $stdout.string
18
- ensure
19
- $stdout = old_stdout
20
- end
21
- end
22
-
23
12
  def tmpdir(sub=nil)
24
13
  @tmpdir ||= begin
25
14
  dirname = File.expand_path("../tmp", __FILE__)
26
- mkdir_p dirname
15
+ FileUtils.mkdir_p dirname
27
16
  dirname
28
17
  end
29
18
  if sub
30
19
  full = File.join(@tmpdir, sub)
31
- mkdir_p full
20
+ FileUtils.mkdir_p full
32
21
  full
33
22
  else
34
23
  @tmpdir
35
24
  end
36
25
  end
26
+
27
+ config.after { FileUtils.rm_rf @tmpdir if @tmpdir }
37
28
  end
38
29
 
39
30
  module HandBrake
@@ -42,10 +33,22 @@ module HandBrake
42
33
  # A stub implementation of the runner for HandBrake::CLI.
43
34
  class StaticRunner
44
35
  attr_accessor :output, :status
36
+ ##
37
+ # A lambda that specifies desired side effects of execution or
38
+ # simulates things happening outside the execution but
39
+ # simultaneous with it.
40
+ attr_accessor :behavior
45
41
  attr_reader :actual_arguments
46
42
 
47
43
  def run(args)
48
44
  @actual_arguments = args
45
+ if i = args.index('--output')
46
+ fn = args[i + 1]
47
+ File.open(fn, 'w') { |f| f.write 'This is the file created by --output' }
48
+ end
49
+ if behavior
50
+ behavior.call
51
+ end
49
52
  HandBrake::CLI::RunnerResult.new(output, status)
50
53
  end
51
54
 
metadata CHANGED
@@ -1,96 +1,68 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: handbrake
3
- version: !ruby/object:Gem::Version
4
- hash: 27
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 1
9
- - 0
10
- version: 0.1.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Rhett Sutphin
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-06-15 00:00:00 -05:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
12
+ date: 2011-08-07 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
22
15
  name: rubytree
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &2153799900 !ruby/object:Gem::Requirement
25
17
  none: false
26
- requirements:
18
+ requirements:
27
19
  - - ~>
28
- - !ruby/object:Gem::Version
29
- hash: 61
30
- segments:
31
- - 0
32
- - 8
33
- - 1
20
+ - !ruby/object:Gem::Version
34
21
  version: 0.8.1
35
22
  type: :runtime
36
- version_requirements: *id001
37
- - !ruby/object:Gem::Dependency
38
- name: rspec
39
23
  prerelease: false
40
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: *2153799900
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &2153799400 !ruby/object:Gem::Requirement
41
28
  none: false
42
- requirements:
29
+ requirements:
43
30
  - - ~>
44
- - !ruby/object:Gem::Version
45
- hash: 9
46
- segments:
47
- - 2
48
- - 5
49
- version: "2.5"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.5'
50
33
  type: :development
51
- version_requirements: *id002
52
- - !ruby/object:Gem::Dependency
53
- name: rake
54
34
  prerelease: false
55
- requirement: &id003 !ruby/object:Gem::Requirement
35
+ version_requirements: *2153799400
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &2153798940 !ruby/object:Gem::Requirement
56
39
  none: false
57
- requirements:
40
+ requirements:
58
41
  - - ~>
59
- - !ruby/object:Gem::Version
60
- hash: 59
61
- segments:
62
- - 0
63
- - 9
64
- - 0
42
+ - !ruby/object:Gem::Version
65
43
  version: 0.9.0
66
44
  type: :development
67
- version_requirements: *id003
68
- - !ruby/object:Gem::Dependency
69
- name: yard
70
45
  prerelease: false
71
- requirement: &id004 !ruby/object:Gem::Requirement
46
+ version_requirements: *2153798940
47
+ - !ruby/object:Gem::Dependency
48
+ name: yard
49
+ requirement: &2153798480 !ruby/object:Gem::Requirement
72
50
  none: false
73
- requirements:
51
+ requirements:
74
52
  - - ~>
75
- - !ruby/object:Gem::Version
76
- hash: 3
77
- segments:
78
- - 0
79
- - 7
80
- - 0
53
+ - !ruby/object:Gem::Version
81
54
  version: 0.7.0
82
55
  type: :development
83
- version_requirements: *id004
84
- description: A lightweight literate ruby wrapper for HandBrakeCLI, the command-line interface for the HandBrake video transcoder.
85
- email:
56
+ prerelease: false
57
+ version_requirements: *2153798480
58
+ description: A lightweight literate ruby wrapper for HandBrakeCLI, the command-line
59
+ interface for the HandBrake video transcoder.
60
+ email:
86
61
  - rhett@detailedbalance.net
87
62
  executables: []
88
-
89
63
  extensions: []
90
-
91
64
  extra_rdoc_files: []
92
-
93
- files:
65
+ files:
94
66
  - .gitignore
95
67
  - .rvmrc
96
68
  - .travis.yml
@@ -112,41 +84,31 @@ files:
112
84
  - spec/handbrake/titles_spec.rb
113
85
  - spec/handbrake/version_spec.rb
114
86
  - spec/spec_helper.rb
115
- has_rdoc: true
116
87
  homepage: https://github.com/rsutphin/handbrake.rb
117
88
  licenses: []
118
-
119
89
  post_install_message:
120
90
  rdoc_options: []
121
-
122
- require_paths:
91
+ require_paths:
123
92
  - lib
124
- required_ruby_version: !ruby/object:Gem::Requirement
93
+ required_ruby_version: !ruby/object:Gem::Requirement
125
94
  none: false
126
- requirements:
127
- - - ">="
128
- - !ruby/object:Gem::Version
129
- hash: 3
130
- segments:
131
- - 0
132
- version: "0"
133
- required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
100
  none: false
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- hash: 3
139
- segments:
140
- - 0
141
- version: "0"
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
142
105
  requirements: []
143
-
144
106
  rubyforge_project:
145
- rubygems_version: 1.3.7
107
+ rubygems_version: 1.8.6
146
108
  signing_key:
147
109
  specification_version: 3
148
110
  summary: A ruby wrapper for HandBrakeCLI
149
- test_files:
111
+ test_files:
150
112
  - spec/handbrake/cli_spec.rb
151
113
  - spec/handbrake/sample-preset-list.out
152
114
  - spec/handbrake/sample-titles-scan.err