handbrake 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +16 -0
- data/README.md +12 -14
- data/lib/handbrake.rb +4 -3
- data/lib/handbrake/cli.rb +34 -17
- data/lib/handbrake/{titles.rb → disc.rb} +53 -13
- data/lib/handbrake/version.rb +1 -1
- data/spec/handbrake/cli_spec.rb +44 -19
- data/spec/handbrake/disc_spec.rb +201 -0
- data/spec/handbrake/single-title-scan.err +96 -0
- metadata +15 -13
- data/spec/handbrake/titles_spec.rb +0 -115
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
0.3.0
|
2
|
+
=====
|
3
|
+
|
4
|
+
- Change the output from the `scan` action to be a {HandBrake::Disc}
|
5
|
+
object which contains a titles hash, rather than the hash directly.
|
6
|
+
- When `scan`ning for a single title, return only a single
|
7
|
+
{HandBrake::Title}.
|
8
|
+
- Accept a path for the `:atomic` option to
|
9
|
+
{HandBrake::CLI#output}. If specified, the temporary file will be
|
10
|
+
written to this path rather than the ultimate target directory.
|
11
|
+
- Ensure that trace mode prints updating lines (e.g., the encode
|
12
|
+
status) that do not end in newlines in a timely fashion.
|
13
|
+
- Add property-based constructors to {HandBrake::Title} and
|
14
|
+
{HandBrake::Chapter} for easier construction in tests of consuming
|
15
|
+
apps/libs.
|
16
|
+
|
1
17
|
0.2.1
|
2
18
|
=====
|
3
19
|
|
data/README.md
CHANGED
@@ -39,13 +39,13 @@ A brief sample:
|
|
39
39
|
|
40
40
|
project = hb.input('/Volumes/Arcturan Megafreighter/DVDs/L')
|
41
41
|
|
42
|
-
|
43
|
-
titles[1].number # => 1
|
44
|
-
titles[1].main_feature? # => true
|
45
|
-
titles[1].duration # => "01:21:18"
|
46
|
-
titles[1].seconds # => 4878
|
47
|
-
titles[1].chapters.size # => 23
|
48
|
-
titles[1].chapters[3].seconds # => 208
|
42
|
+
disc = project.scan
|
43
|
+
disc.titles[1].number # => 1
|
44
|
+
disc.titles[1].main_feature? # => true
|
45
|
+
disc.titles[1].duration # => "01:21:18"
|
46
|
+
disc.titles[1].seconds # => 4878
|
47
|
+
disc.titles[1].chapters.size # => 23
|
48
|
+
disc.titles[1].chapters[3].seconds # => 208
|
49
49
|
|
50
50
|
project.title(1).
|
51
51
|
preset('Normal').
|
@@ -99,10 +99,9 @@ come in any order, a few must come last and have particular return
|
|
99
99
|
values:
|
100
100
|
|
101
101
|
* `output`: triggers a transcode using all the options set up to this
|
102
|
-
point.
|
103
|
-
* `scan`: triggers a title scan and returns a
|
104
|
-
|
105
|
-
input. The hash is indexed by title number.
|
102
|
+
point. No return value.
|
103
|
+
* `scan`: triggers a title scan and returns either a {HandBrake::Disc}
|
104
|
+
(for all titles) or a {HandBrake::Title} (for a single title).
|
106
105
|
* `update`: returns true or false depending on whether the version of
|
107
106
|
HandBrakeCLI in use is up to date.
|
108
107
|
* `preset_list`: returns a hash containing all the known presets and
|
@@ -122,9 +121,8 @@ along different paths. E.g.:
|
|
122
121
|
# TV
|
123
122
|
project.preset('High Profile').output('project-tv.m4v')
|
124
123
|
|
125
|
-
To put it more technically,
|
126
|
-
|
127
|
-
configuration chain.
|
124
|
+
To put it more technically, each intermediate configuration step
|
125
|
+
returns an independent copy of the configuration chain.
|
128
126
|
|
129
127
|
### Output options
|
130
128
|
|
data/lib/handbrake.rb
CHANGED
@@ -5,9 +5,10 @@ module HandBrake
|
|
5
5
|
autoload :VERSION, 'handbrake/version'
|
6
6
|
|
7
7
|
autoload :CLI, 'handbrake/cli'
|
8
|
-
autoload :
|
9
|
-
autoload :Title, 'handbrake/
|
10
|
-
autoload :
|
8
|
+
autoload :Disc, 'handbrake/disc'
|
9
|
+
autoload :Title, 'handbrake/disc'
|
10
|
+
autoload :Titles, 'handbrake/disc'
|
11
|
+
autoload :Chapter, 'handbrake/disc'
|
11
12
|
|
12
13
|
##
|
13
14
|
# The exception thrown when a file exists that shouldn't.
|
data/lib/handbrake/cli.rb
CHANGED
@@ -68,16 +68,19 @@ module HandBrake
|
|
68
68
|
# `true`, the file is replaced. If `false`, an exception is
|
69
69
|
# thrown. If `:ignore`, the file is skipped; i.e., HandBrakeCLI
|
70
70
|
# is not invoked.
|
71
|
-
# @option options [Boolean] :atomic (false) provides a
|
71
|
+
# @option options [Boolean, String] :atomic (false) provides a
|
72
72
|
# pseudo-atomic mode for transcoded output. If true, the
|
73
73
|
# transcode will go into a temporary file and only be copied to
|
74
|
-
# the specified filename if it completes.
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
74
|
+
# the specified filename if it completes. If the value is
|
75
|
+
# literally `true`, the temporary filename is the target
|
76
|
+
# filename with `.handbraking` inserted before the extension. If
|
77
|
+
# the value is a string, it is interpreted as a path; the
|
78
|
+
# temporary file is written to this path instead of in the
|
79
|
+
# ultimate target directory. Any `:overwrite` checking will be
|
80
|
+
# applied to the target filename both before and after the
|
81
|
+
# transcode happens (the temporary file will always be
|
82
|
+
# overwritten). This option is intended to aid in writing
|
83
|
+
# automatically resumable batch scripts.
|
81
84
|
#
|
82
85
|
# @return [void]
|
83
86
|
def output(filename, options={})
|
@@ -98,10 +101,15 @@ module HandBrake
|
|
98
101
|
|
99
102
|
atomic = options.delete :atomic
|
100
103
|
interim_filename =
|
101
|
-
|
104
|
+
case atomic
|
105
|
+
when true
|
102
106
|
partial_filename(filename)
|
103
|
-
|
107
|
+
when String
|
108
|
+
partial_filename(File.join(atomic, File.basename(filename)))
|
109
|
+
when false, nil
|
104
110
|
filename
|
111
|
+
else
|
112
|
+
fail "Unsupported value for :atomic: #{atomic.inspect}"
|
105
113
|
end
|
106
114
|
|
107
115
|
unless options.empty?
|
@@ -127,7 +135,7 @@ module HandBrake
|
|
127
135
|
else
|
128
136
|
true
|
129
137
|
end
|
130
|
-
FileUtils.mv
|
138
|
+
FileUtils.mv(interim_filename, filename, :verbose => trace?) if replace
|
131
139
|
end
|
132
140
|
end
|
133
141
|
|
@@ -147,13 +155,22 @@ module HandBrake
|
|
147
155
|
# titles. (HandBrakeCLI defaults to only returning information for
|
148
156
|
# title 1.)
|
149
157
|
#
|
150
|
-
# @return [
|
158
|
+
# @return [Disc,Title] a {Disc} when scanning for all titles, or a
|
159
|
+
# {Title} when scanning for one title.
|
151
160
|
def scan
|
152
|
-
|
153
|
-
|
154
|
-
|
161
|
+
one_title = arguments.include?('--title')
|
162
|
+
|
163
|
+
args = %w(--scan)
|
164
|
+
unless one_title
|
165
|
+
args.unshift('--title', '0')
|
166
|
+
end
|
167
|
+
|
168
|
+
disc = Disc.from_output(run(*args).output)
|
169
|
+
|
170
|
+
if one_title
|
171
|
+
disc.titles.values.first
|
155
172
|
else
|
156
|
-
|
173
|
+
disc
|
157
174
|
end
|
158
175
|
end
|
159
176
|
|
@@ -264,7 +281,7 @@ module HandBrake
|
|
264
281
|
|
265
282
|
$stderr.puts "Spawning HandBrakeCLI using #{cmd.inspect}" if @cli.trace?
|
266
283
|
IO.popen(cmd) do |io|
|
267
|
-
while line = io.
|
284
|
+
while line = io.read(60)
|
268
285
|
output << line
|
269
286
|
$stderr.write(line) if @cli.trace?
|
270
287
|
end
|
@@ -4,13 +4,12 @@ require 'handbrake'
|
|
4
4
|
|
5
5
|
module HandBrake
|
6
6
|
##
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# numbers and the values will be {Title} instances.
|
7
|
+
# An object representation of the output from HandBrakeCLI's
|
8
|
+
# `--scan` mode.
|
10
9
|
#
|
11
10
|
# @see Title
|
12
11
|
# @see Chapter
|
13
|
-
class
|
12
|
+
class Disc
|
14
13
|
##
|
15
14
|
# The HandBrakeCLI scan output from which this instance was
|
16
15
|
# parsed, if available.
|
@@ -26,18 +25,37 @@ module HandBrake
|
|
26
25
|
attr_reader :raw_tree
|
27
26
|
|
28
27
|
##
|
29
|
-
#
|
28
|
+
# The titles in the disc. The keys are the title numbers.
|
29
|
+
#
|
30
|
+
# @return [Hash<Fixnum, Title>]
|
31
|
+
attr_reader :titles
|
32
|
+
|
33
|
+
##
|
34
|
+
# The name of the disc.
|
35
|
+
#
|
36
|
+
# @return [String]
|
37
|
+
attr_accessor :name
|
38
|
+
|
39
|
+
def initialize(name=nil)
|
40
|
+
@name = name
|
41
|
+
@titles = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Builds a new {Disc} instance from the output of `HandBrakeCLI
|
30
46
|
# --scan`.
|
31
47
|
#
|
32
48
|
# @param [String] output the raw contents from the scan
|
33
|
-
# @return [
|
49
|
+
# @return [Disc] a new, completely initialized title catalog
|
34
50
|
def self.from_output(output)
|
35
|
-
|
36
|
-
|
37
|
-
|
51
|
+
name = File.basename(
|
52
|
+
output.split("\n").grep(/hb_scan/).first.scan(/path=([^,]*),/).first.first)
|
53
|
+
self.new(name).tap do |disc|
|
54
|
+
disc.raw_output = output
|
55
|
+
disc.raw_tree.children.
|
38
56
|
collect { |title_node| Title.from_tree(title_node) }.
|
39
|
-
each { |title| title.
|
40
|
-
each { |title| titles[title.number] = title }
|
57
|
+
each { |title| title.disc = disc }.
|
58
|
+
each { |title| disc.titles[title.number] = title }
|
41
59
|
end
|
42
60
|
end
|
43
61
|
|
@@ -54,6 +72,10 @@ module HandBrake
|
|
54
72
|
@raw_tree = extract_tree
|
55
73
|
end
|
56
74
|
|
75
|
+
def to_yaml_properties
|
76
|
+
%w(@name @titles)
|
77
|
+
end
|
78
|
+
|
57
79
|
private
|
58
80
|
|
59
81
|
def extract_tree
|
@@ -100,10 +122,27 @@ module HandBrake
|
|
100
122
|
end
|
101
123
|
end
|
102
124
|
|
125
|
+
##
|
126
|
+
# Mix-in that provides a class with a constructor that sets any
|
127
|
+
# publicly writable properties from values in a hash.
|
128
|
+
module ConstructWithProperties
|
129
|
+
def initialize(properties={})
|
130
|
+
properties.each do |prop, value|
|
131
|
+
setter = :"#{prop}="
|
132
|
+
if self.respond_to?(setter)
|
133
|
+
self.send(setter, value)
|
134
|
+
else
|
135
|
+
fail("No property #{prop.inspect} in #{self.class}")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
103
141
|
##
|
104
142
|
# Metadata about a single DVD title.
|
105
143
|
class Title
|
106
144
|
include DurationAsSeconds
|
145
|
+
include ConstructWithProperties
|
107
146
|
|
108
147
|
##
|
109
148
|
# @return [Fixnum] The title number of this title (a positive integer).
|
@@ -125,8 +164,8 @@ module HandBrake
|
|
125
164
|
attr_writer :main_feature
|
126
165
|
|
127
166
|
##
|
128
|
-
# @return [
|
129
|
-
attr_accessor :
|
167
|
+
# @return [Disc] The disc this title belongs to.
|
168
|
+
attr_accessor :disc
|
130
169
|
|
131
170
|
##
|
132
171
|
# Creates a new instance from the given scan subtree.
|
@@ -176,6 +215,7 @@ module HandBrake
|
|
176
215
|
# The metadata about a single chapter in a title of a DVD.
|
177
216
|
class Chapter
|
178
217
|
include DurationAsSeconds
|
218
|
+
include ConstructWithProperties
|
179
219
|
|
180
220
|
##
|
181
221
|
# @return [String] The duration of the title in the format
|
data/lib/handbrake/version.rb
CHANGED
data/spec/handbrake/cli_spec.rb
CHANGED
@@ -163,32 +163,30 @@ module HandBrake
|
|
163
163
|
end
|
164
164
|
end
|
165
165
|
|
166
|
-
|
167
|
-
let(:expected_temporary_file) { File.join(tmpdir, "foo.handbraking.m4v") }
|
168
|
-
|
166
|
+
shared_examples 'atomic enabled' do
|
169
167
|
it 'outputs to the temporary file with ".handbraking" inserted before the extension' do
|
170
|
-
cli.output(filename, :atomic =>
|
168
|
+
cli.output(filename, :atomic => atomic_option_value)
|
171
169
|
it_should_have_run(expected_temporary_file)
|
172
170
|
end
|
173
171
|
|
174
172
|
it 'appends .handbraking if the output file does not have an extension' do
|
175
|
-
cli.output(File.join(tmpdir('a.b.c'), 'foo'), :atomic =>
|
176
|
-
it_should_have_run(
|
173
|
+
cli.output(File.join(tmpdir('a.b.c'), 'foo'), :atomic => atomic_option_value)
|
174
|
+
it_should_have_run(expected_no_extension_temporary_filename)
|
177
175
|
end
|
178
176
|
|
179
177
|
it 'copies the output to the desired filename' do
|
180
|
-
cli.output(filename, :atomic =>
|
178
|
+
cli.output(filename, :atomic => atomic_option_value)
|
181
179
|
File.read(filename).should == 'This is the file created by --output'
|
182
180
|
end
|
183
181
|
|
184
182
|
it 'removes the temporary file' do
|
185
|
-
cli.output(filename, :atomic =>
|
183
|
+
cli.output(filename, :atomic => atomic_option_value)
|
186
184
|
File.exist?(expected_temporary_file).should be_false
|
187
185
|
end
|
188
186
|
|
189
187
|
it 'overwrites the temporary file if it exists' do
|
190
188
|
FileUtils.touch expected_temporary_file
|
191
|
-
cli.output(filename, :atomic =>
|
189
|
+
cli.output(filename, :atomic => atomic_option_value)
|
192
190
|
it_should_have_run(expected_temporary_file)
|
193
191
|
end
|
194
192
|
|
@@ -196,13 +194,13 @@ module HandBrake
|
|
196
194
|
describe 'false' do
|
197
195
|
it 'throws an exception if the target filename exists initially' do
|
198
196
|
FileUtils.touch filename
|
199
|
-
lambda { cli.output(filename, :atomic =>
|
197
|
+
lambda { cli.output(filename, :atomic => atomic_option_value, :overwrite => false) }.
|
200
198
|
should raise_error(HandBrake::FileExistsError)
|
201
199
|
end
|
202
200
|
|
203
201
|
it 'throws an exception if the target filename exists after output' do
|
204
202
|
runner.behavior = lambda { FileUtils.touch filename }
|
205
|
-
lambda { cli.output(filename, :atomic =>
|
203
|
+
lambda { cli.output(filename, :atomic => atomic_option_value, :overwrite => false) }.
|
206
204
|
should raise_error(HandBrake::FileExistsError)
|
207
205
|
end
|
208
206
|
end
|
@@ -210,7 +208,7 @@ module HandBrake
|
|
210
208
|
describe ':ignore' do
|
211
209
|
it 'does nothing if the target filename exists initially' do
|
212
210
|
FileUtils.touch filename
|
213
|
-
cli.output(filename, :atomic =>
|
211
|
+
cli.output(filename, :atomic => atomic_option_value, :overwrite => :ignore)
|
214
212
|
it_should_not_have_run
|
215
213
|
end
|
216
214
|
|
@@ -218,7 +216,7 @@ module HandBrake
|
|
218
216
|
runner.behavior = lambda {
|
219
217
|
File.open(filename, 'w') { |f| f.write 'Other process result' }
|
220
218
|
}
|
221
|
-
cli.output(filename, :atomic =>
|
219
|
+
cli.output(filename, :atomic => atomic_option_value, :overwrite => :ignore)
|
222
220
|
File.read(filename).should == 'Other process result'
|
223
221
|
end
|
224
222
|
|
@@ -226,17 +224,38 @@ module HandBrake
|
|
226
224
|
runner.behavior = lambda {
|
227
225
|
File.open(filename, 'w') { |f| f.write 'Other process result' }
|
228
226
|
}
|
229
|
-
cli.output(filename, :atomic =>
|
227
|
+
cli.output(filename, :atomic => atomic_option_value, :overwrite => :ignore)
|
230
228
|
File.exist?(expected_temporary_file).should be_true
|
231
229
|
end
|
232
230
|
end
|
233
231
|
end
|
234
232
|
end
|
233
|
+
|
234
|
+
context 'true' do
|
235
|
+
let(:atomic_option_value) { true }
|
236
|
+
let(:expected_temporary_file) { File.join(tmpdir, "foo.handbraking.m4v") }
|
237
|
+
let(:expected_no_extension_temporary_filename) {
|
238
|
+
File.join(tmpdir('a.b.c'), 'foo.handbraking')
|
239
|
+
}
|
240
|
+
|
241
|
+
include_examples 'atomic enabled'
|
242
|
+
end
|
243
|
+
|
244
|
+
context 'an alternate path' do
|
245
|
+
let(:atomic_option_value) { tmpdir('somewhere/else') }
|
246
|
+
let(:expected_temporary_file) { File.join(atomic_option_value, "foo.handbraking.m4v") }
|
247
|
+
let(:expected_no_extension_temporary_filename) {
|
248
|
+
File.join(atomic_option_value, 'foo.handbraking')
|
249
|
+
}
|
250
|
+
|
251
|
+
include_examples 'atomic enabled'
|
252
|
+
end
|
235
253
|
end
|
236
254
|
end
|
237
255
|
|
238
256
|
describe '#scan' do
|
239
257
|
let(:sample_scan) { File.read(File.expand_path('../sample-titles-scan.err', __FILE__)) }
|
258
|
+
let(:single_scan) { File.read(File.expand_path('../single-title-scan.err', __FILE__)) }
|
240
259
|
|
241
260
|
before do
|
242
261
|
runner.output = sample_scan
|
@@ -253,8 +272,8 @@ module HandBrake
|
|
253
272
|
end
|
254
273
|
|
255
274
|
it 'only scans the specified title if one is specified' do
|
256
|
-
cli.title(
|
257
|
-
runner.actual_arguments.should == %w(--title
|
275
|
+
cli.title(2).scan
|
276
|
+
runner.actual_arguments.should == %w(--title 2 --scan)
|
258
277
|
end
|
259
278
|
|
260
279
|
it 'does not permanently modify the argument list when using the default title setting' do
|
@@ -262,9 +281,15 @@ module HandBrake
|
|
262
281
|
cli.arguments.should_not include('--title')
|
263
282
|
end
|
264
283
|
|
265
|
-
it '
|
266
|
-
# The details for this are tested in
|
267
|
-
cli.scan.
|
284
|
+
it 'return a Disc when scanning all titles' do
|
285
|
+
# The details for this are tested in disc_spec
|
286
|
+
cli.scan.should be_a HandBrake::Disc
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'returns a Title when scanning one title' do
|
290
|
+
# The details for this are tested in disc_spec
|
291
|
+
runner.output = single_scan
|
292
|
+
cli.title(2).scan.should be_a HandBrake::Title
|
268
293
|
end
|
269
294
|
end
|
270
295
|
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require File.expand_path('../../spec_helper.rb', __FILE__)
|
2
|
+
|
3
|
+
module HandBrake
|
4
|
+
describe Disc do
|
5
|
+
let(:body) { File.read(File.expand_path('../sample-titles-scan.err', __FILE__)) }
|
6
|
+
let(:disc) { Disc.from_output(body) }
|
7
|
+
let(:titles) { disc.titles }
|
8
|
+
|
9
|
+
describe '#titles' do
|
10
|
+
it 'contains all the titles' do
|
11
|
+
titles.size.should == 5
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'indexes the titles correctly' do
|
15
|
+
titles[3].number.should == 3
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'is enumerable' do
|
19
|
+
titles.collect { |k, v| k }.sort.should == [1, 2, 3, 6, 11]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'extracts the name' do
|
24
|
+
disc.name.should == 'D2'
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'contains a reference to the full output' do
|
28
|
+
disc.raw_output.should == body
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#raw_tree" do
|
32
|
+
let(:tree) { disc.raw_tree }
|
33
|
+
|
34
|
+
it 'has the raw values at the top level of nodes' do
|
35
|
+
tree.children.collect { |c| c.name }.should ==
|
36
|
+
['title 1:', 'title 2:', 'title 3:', 'title 6:', 'title 11:']
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'has the raw values at the second level' do
|
40
|
+
tree['title 11:'][3].name.should == 'autocrop: 0/0/0/0'
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'has the raw values at the third level' do
|
44
|
+
tree['title 3:']['audio tracks:'][1].name.should ==
|
45
|
+
'2, Francais (AC3) (Dolby Surround) (iso639-2: fra), 48000Hz, 192000bps'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'YAML serialization' do
|
50
|
+
describe 'round trip' do
|
51
|
+
let(:yaml) { disc.to_yaml }
|
52
|
+
let(:reloaded) { YAML.load(yaml) }
|
53
|
+
|
54
|
+
it 'does not include the raw output' do
|
55
|
+
reloaded.raw_output.should be_nil
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'does not include the raw tree' do
|
59
|
+
reloaded.raw_tree.should be_nil
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'preserves the name' do
|
63
|
+
reloaded.name.should == 'D2'
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'preserves the titles' do
|
67
|
+
reloaded.titles.size.should == 5
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'preserves the backreference from a title to the disc' do
|
71
|
+
pending "Psych issue #19" if RUBY_VERSION =~ /1.9/
|
72
|
+
reloaded.titles[1].disc.should eql(reloaded)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'preserves the chapters' do
|
76
|
+
reloaded.titles[3].chapters.size.should == 13
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'preserves the backreference from a chapter to its title' do
|
80
|
+
pending "Psych issue #19" if RUBY_VERSION =~ /1.9/
|
81
|
+
title_1 = reloaded.titles[1]
|
82
|
+
title_1.chapters[4].title.should eql(title_1)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe Title do
|
89
|
+
let(:body) { File.read(File.expand_path('../sample-titles-scan.err', __FILE__)) }
|
90
|
+
let(:disc) { Disc.from_output(body) }
|
91
|
+
let(:titles) { disc.titles }
|
92
|
+
|
93
|
+
let(:title_1) { titles[1] }
|
94
|
+
let(:title_3) { titles[3] }
|
95
|
+
|
96
|
+
it 'has a reference to its parent disc' do
|
97
|
+
title_1.disc.should be disc
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#initialize' do
|
101
|
+
describe 'without a hash' do
|
102
|
+
it 'works' do
|
103
|
+
lambda { Title.new }.should_not raise_error
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe 'with a hash' do
|
108
|
+
it 'can set settable properties' do
|
109
|
+
Title.new(:number => 5, :duration => '01:45:35').number.should == 5
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'fails for an unknown property' do
|
113
|
+
lambda { Title.new(:foo => 'bar') }.
|
114
|
+
should raise_error('No property :foo in HandBrake::Title')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe '#main_feature?' do
|
120
|
+
it 'is true when it is' do
|
121
|
+
title_1.should be_main_feature
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'is false when it is' do
|
125
|
+
title_3.should_not be_main_feature
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'is not a complex value when true, so that serializations are simpler' do
|
129
|
+
title_1.main_feature?.should == true
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'has the number' do
|
134
|
+
title_1.number.should == 1
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'has the duration' do
|
138
|
+
title_3.duration.should == '01:43:54'
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'has the duration in seconds' do
|
142
|
+
title_3.seconds.should == 6234
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'has the right number of chapters' do
|
146
|
+
title_3.should have(13).chapters
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'has chapters indexed by chapter number' do
|
150
|
+
title_3.chapters[2].tap do |c|
|
151
|
+
c.duration.should == '00:00:52'
|
152
|
+
c.number.should == 2
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'has an array of the chapters in order' do
|
157
|
+
title_3.all_chapters.collect { |ch| ch.number }.should == (1..13).to_a
|
158
|
+
end
|
159
|
+
|
160
|
+
describe 'a chapter' do
|
161
|
+
let(:chapter) { title_3.chapters[5] }
|
162
|
+
|
163
|
+
it 'has the duration' do
|
164
|
+
chapter.duration.should == '00:03:23'
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'has the duration in seconds' do
|
168
|
+
chapter.seconds.should == 203
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'has the number' do
|
172
|
+
chapter.number.should == 5
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'has a reference to its parent' do
|
176
|
+
chapter.title.should eql title_3
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe Chapter do
|
182
|
+
describe '#initialize' do
|
183
|
+
describe 'without a hash' do
|
184
|
+
it 'works' do
|
185
|
+
lambda { Chapter.new }.should_not raise_error
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe 'with a hash' do
|
190
|
+
it 'can set settable properties' do
|
191
|
+
Chapter.new(:number => 5, :duration => '01:45:35').duration.should == '01:45:35'
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'fails for an unknown property' do
|
195
|
+
lambda { Chapter.new(:foo => 'bar') }.
|
196
|
+
should raise_error('No property :foo in HandBrake::Chapter')
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
[21:49:40] hb_init: checking cpu count
|
2
|
+
[21:49:40] hb_init: starting libhb thread
|
3
|
+
HandBrake rev3736 (2011081199) - Darwin x86_64 - http://handbrake.fr
|
4
|
+
2 CPUs detected
|
5
|
+
Opening /Volumes/Arcturan Megafreighter/DVDs/L...
|
6
|
+
[21:49:40] hb_scan: path=/Volumes/Arcturan Megafreighter/DVDs/L, title_index=1
|
7
|
+
libbluray/bdnav/index_parse.c:157: indx_parse(): error opening /Volumes/Arcturan Megafreighter/DVDs/L/BDMV/index.bdmv
|
8
|
+
libbluray/bluray.c:960: nav_get_title_list(/Volumes/Arcturan Megafreighter/DVDs/L) failed (0x102013400)
|
9
|
+
[21:49:40] bd: not a bd - trying as a stream/file instead
|
10
|
+
libdvdnav: Using dvdnav version 4.1.3
|
11
|
+
libdvdread: Using libdvdcss version 1.2.9 for DVD access
|
12
|
+
libdvdread: Couldn't find device name.
|
13
|
+
libdvdnav: Can't read name block. Probably not a DVD-ROM device.
|
14
|
+
libdvdnav: Unable to find map file '/Users/rsutphin/.dvdnav/.map'
|
15
|
+
libdvdnav: DVD disk reports itself with Region mask 0x00fe0000. Regions: 1
|
16
|
+
libdvdread: Using libdvdcss version 1.2.9 for DVD access
|
17
|
+
libdvdread: Couldn't find device name.
|
18
|
+
[21:49:40] scan: DVD has 3 title(s)
|
19
|
+
[21:49:40] scan: scanning title 1
|
20
|
+
[21:49:40] scan: opening IFO for VTS 1
|
21
|
+
[21:49:40] scan: duration is 01:21:18 (4878000 ms)
|
22
|
+
[21:49:40] pgc_id: 1, pgn: 1: pgc: 0x101605130
|
23
|
+
[21:49:40] scan: vts=1, ttn=1, cells=0->22, blocks=58609->58608, 2081952 blocks
|
24
|
+
[21:49:40] scan: checking audio 1
|
25
|
+
[21:49:40] scan: id=80bd, lang=English (AC3), 3cc=eng ext=0
|
26
|
+
[21:49:40] scan: checking audio 2
|
27
|
+
[21:49:40] scan: id=81bd, lang=Francais (AC3), 3cc=fra ext=0
|
28
|
+
[21:49:40] scan: title 1 has 23 chapters
|
29
|
+
[21:49:40] scan: chap 1 c=0->0, b=58609->108062 (49454), 120223 ms
|
30
|
+
[21:49:40] scan: chap 2 c=1->1, b=108063->181132 (73070), 148385 ms
|
31
|
+
[21:49:40] scan: chap 3 c=2->2, b=181133->265640 (84508), 208487 ms
|
32
|
+
[21:49:40] scan: chap 4 c=3->3, b=265641->342075 (76435), 199636 ms
|
33
|
+
[21:49:40] scan: chap 5 c=4->4, b=342076->464043 (121968), 322732 ms
|
34
|
+
[21:49:40] scan: chap 6 c=5->5, b=464044->528308 (64265), 167554 ms
|
35
|
+
[21:49:40] scan: chap 7 c=6->6, b=528309->613576 (85268), 297605 ms
|
36
|
+
[21:49:40] scan: chap 8 c=7->7, b=613577->698083 (84507), 237727 ms
|
37
|
+
[21:49:40] scan: chap 9 c=8->8, b=698084->832431 (134348), 291828 ms
|
38
|
+
[21:49:40] scan: chap 10 c=9->9, b=832432->950393 (117962), 271647 ms
|
39
|
+
[21:49:40] scan: chap 11 c=10->10, b=950394->1067913 (117520), 281843 ms
|
40
|
+
[21:49:40] scan: chap 12 c=11->11, b=1067914->1163132 (95219), 212671 ms
|
41
|
+
[21:49:40] scan: chap 13 c=12->12, b=1163133->1282848 (119716), 276599 ms
|
42
|
+
[21:49:40] scan: chap 14 c=13->13, b=1282849->1330562 (47714), 120309 ms
|
43
|
+
[21:49:40] scan: chap 15 c=14->14, b=1330563->1476681 (146119), 301613 ms
|
44
|
+
[21:49:40] scan: chap 16 c=15->15, b=1476682->1577253 (100572), 205434 ms
|
45
|
+
[21:49:40] scan: chap 17 c=16->16, b=1577254->1650064 (72811), 185610 ms
|
46
|
+
[21:49:40] scan: chap 18 c=17->17, b=1650065->1744727 (94663), 195639 ms
|
47
|
+
[21:49:40] scan: chap 19 c=18->18, b=1744728->1904668 (159941), 328863 ms
|
48
|
+
[21:49:40] scan: chap 20 c=19->19, b=1904669->2018698 (114030), 252789 ms
|
49
|
+
[21:49:40] scan: chap 21 c=20->20, b=2018699->2105949 (87251), 179543 ms
|
50
|
+
[21:49:40] scan: chap 22 c=21->21, b=2105950->2140482 (34533), 64239 ms
|
51
|
+
[21:49:40] scan: chap 23 c=22->22, b=58531->58608 (78), 7013 ms
|
52
|
+
[21:49:40] scan: aspect = 0
|
53
|
+
[21:49:40] scan: decoding previews for title 1
|
54
|
+
libdvdnav: DVD disk reports itself with Region mask 0x00fe0000. Regions: 1
|
55
|
+
[21:49:40] scan: title angle(s) 1
|
56
|
+
[21:49:40] scan: audio 0x80bd: AC-3, rate=48000Hz, bitrate=192000 English (AC3) (2.0 ch)
|
57
|
+
[21:49:40] scan: audio 0x81bd: AC-3, rate=48000Hz, bitrate=192000 Francais (AC3) (2.0 ch)
|
58
|
+
Scanning title 1 of 3...
|
59
|
+
[21:49:40] scan: 10 previews, 720x480, 23.976 fps, autocrop = 0/0/8/4, aspect 4:3, PAR 8:9
|
60
|
+
[21:49:40] scan: title (0) job->width:624, job->height:480
|
61
|
+
[21:49:40] libhb: scan thread found 1 valid title(s)
|
62
|
+
+ title 1:
|
63
|
+
+ vts 1, ttn 1, cells 0->22 (2081952 blocks)
|
64
|
+
+ duration: 01:21:18
|
65
|
+
+ size: 720x480, pixel aspect: 8/9, display aspect: 1.33, 23.976 fps
|
66
|
+
+ autocrop: 0/0/8/4
|
67
|
+
+ chapters:
|
68
|
+
+ 1: cells 0->0, 49454 blocks, duration 00:02:00
|
69
|
+
+ 2: cells 1->1, 73070 blocks, duration 00:02:28
|
70
|
+
+ 3: cells 2->2, 84508 blocks, duration 00:03:28
|
71
|
+
+ 4: cells 3->3, 76435 blocks, duration 00:03:20
|
72
|
+
+ 5: cells 4->4, 121968 blocks, duration 00:05:23
|
73
|
+
+ 6: cells 5->5, 64265 blocks, duration 00:02:48
|
74
|
+
+ 7: cells 6->6, 85268 blocks, duration 00:04:58
|
75
|
+
+ 8: cells 7->7, 84507 blocks, duration 00:03:58
|
76
|
+
+ 9: cells 8->8, 134348 blocks, duration 00:04:52
|
77
|
+
+ 10: cells 9->9, 117962 blocks, duration 00:04:32
|
78
|
+
+ 11: cells 10->10, 117520 blocks, duration 00:04:42
|
79
|
+
+ 12: cells 11->11, 95219 blocks, duration 00:03:33
|
80
|
+
+ 13: cells 12->12, 119716 blocks, duration 00:04:37
|
81
|
+
+ 14: cells 13->13, 47714 blocks, duration 00:02:00
|
82
|
+
+ 15: cells 14->14, 146119 blocks, duration 00:05:02
|
83
|
+
+ 16: cells 15->15, 100572 blocks, duration 00:03:25
|
84
|
+
+ 17: cells 16->16, 72811 blocks, duration 00:03:06
|
85
|
+
+ 18: cells 17->17, 94663 blocks, duration 00:03:16
|
86
|
+
+ 19: cells 18->18, 159941 blocks, duration 00:05:29
|
87
|
+
+ 20: cells 19->19, 114030 blocks, duration 00:04:13
|
88
|
+
+ 21: cells 20->20, 87251 blocks, duration 00:03:00
|
89
|
+
+ 22: cells 21->21, 34533 blocks, duration 00:01:04
|
90
|
+
+ 23: cells 22->22, 78 blocks, duration 00:00:07
|
91
|
+
+ audio tracks:
|
92
|
+
+ 1, English (AC3) (2.0 ch) (iso639-2: eng), 48000Hz, 192000bps
|
93
|
+
+ 2, Francais (AC3) (2.0 ch) (iso639-2: fra), 48000Hz, 192000bps
|
94
|
+
+ subtitle tracks:
|
95
|
+
+ 1, Closed Captions (iso639-2: eng) (Text)(CC)
|
96
|
+
HandBrake has exited.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: handbrake
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-08-
|
12
|
+
date: 2011-08-17 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rubytree
|
16
|
-
requirement: &
|
16
|
+
requirement: &2152701940 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 0.8.1
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2152701940
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &2152701440 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '2.5'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2152701440
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rake
|
38
|
-
requirement: &
|
38
|
+
requirement: &2152700980 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 0.9.0
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2152700980
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: yard
|
49
|
-
requirement: &
|
49
|
+
requirement: &2152700520 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: 0.7.0
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *2152700520
|
58
58
|
description: A lightweight literate ruby wrapper for HandBrakeCLI, the command-line
|
59
59
|
interface for the HandBrake video transcoder.
|
60
60
|
email:
|
@@ -76,12 +76,13 @@ files:
|
|
76
76
|
- hrirb
|
77
77
|
- lib/handbrake.rb
|
78
78
|
- lib/handbrake/cli.rb
|
79
|
-
- lib/handbrake/
|
79
|
+
- lib/handbrake/disc.rb
|
80
80
|
- lib/handbrake/version.rb
|
81
81
|
- spec/handbrake/cli_spec.rb
|
82
|
+
- spec/handbrake/disc_spec.rb
|
82
83
|
- spec/handbrake/sample-preset-list.out
|
83
84
|
- spec/handbrake/sample-titles-scan.err
|
84
|
-
- spec/handbrake/
|
85
|
+
- spec/handbrake/single-title-scan.err
|
85
86
|
- spec/handbrake/version_spec.rb
|
86
87
|
- spec/spec_helper.rb
|
87
88
|
homepage: https://github.com/rsutphin/handbrake.rb
|
@@ -110,8 +111,9 @@ specification_version: 3
|
|
110
111
|
summary: A ruby wrapper for HandBrakeCLI
|
111
112
|
test_files:
|
112
113
|
- spec/handbrake/cli_spec.rb
|
114
|
+
- spec/handbrake/disc_spec.rb
|
113
115
|
- spec/handbrake/sample-preset-list.out
|
114
116
|
- spec/handbrake/sample-titles-scan.err
|
115
|
-
- spec/handbrake/
|
117
|
+
- spec/handbrake/single-title-scan.err
|
116
118
|
- spec/handbrake/version_spec.rb
|
117
119
|
- spec/spec_helper.rb
|
@@ -1,115 +0,0 @@
|
|
1
|
-
require File.expand_path('../../spec_helper.rb', __FILE__)
|
2
|
-
|
3
|
-
module HandBrake
|
4
|
-
describe Titles do
|
5
|
-
let(:body) { File.read(File.expand_path('../sample-titles-scan.err', __FILE__)) }
|
6
|
-
let(:titles) { Titles.from_output(body) }
|
7
|
-
|
8
|
-
it 'contains all the titles' do
|
9
|
-
titles.size.should == 5
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'indexes the titles correctly' do
|
13
|
-
titles[3].number.should == 3
|
14
|
-
end
|
15
|
-
|
16
|
-
it 'is enumerable' do
|
17
|
-
titles.collect { |k, v| k }.sort.should == [1, 2, 3, 6, 11]
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'contains a reference to the full output' do
|
21
|
-
titles.raw_output.should == body
|
22
|
-
end
|
23
|
-
|
24
|
-
describe "#raw_tree" do
|
25
|
-
let(:tree) { titles.raw_tree }
|
26
|
-
|
27
|
-
it 'has the raw values at the top level of nodes' do
|
28
|
-
tree.children.collect { |c| c.name }.should ==
|
29
|
-
['title 1:', 'title 2:', 'title 3:', 'title 6:', 'title 11:']
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'has the raw values at the second level' do
|
33
|
-
tree['title 11:'][3].name.should == 'autocrop: 0/0/0/0'
|
34
|
-
end
|
35
|
-
|
36
|
-
it 'has the raw values at the third level' do
|
37
|
-
tree['title 3:']['audio tracks:'][1].name.should ==
|
38
|
-
'2, Francais (AC3) (Dolby Surround) (iso639-2: fra), 48000Hz, 192000bps'
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
describe Title do
|
44
|
-
let(:body) { File.read(File.expand_path('../sample-titles-scan.err', __FILE__)) }
|
45
|
-
let(:titles) { Titles.from_output(body) }
|
46
|
-
|
47
|
-
let(:title_1) { titles[1] }
|
48
|
-
let(:title_3) { titles[3] }
|
49
|
-
|
50
|
-
it 'has a reference to its parent collection' do
|
51
|
-
title_1.collection.should eql(titles)
|
52
|
-
end
|
53
|
-
|
54
|
-
describe '#main_feature?' do
|
55
|
-
it 'is true when it is' do
|
56
|
-
title_1.should be_main_feature
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'is false when it is' do
|
60
|
-
title_3.should_not be_main_feature
|
61
|
-
end
|
62
|
-
|
63
|
-
it 'is not a complex value when true, so that serializations are simpler' do
|
64
|
-
title_1.main_feature?.should == true
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
it 'has the number' do
|
69
|
-
title_1.number.should == 1
|
70
|
-
end
|
71
|
-
|
72
|
-
it 'has the duration' do
|
73
|
-
title_3.duration.should == '01:43:54'
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'has the duration in seconds' do
|
77
|
-
title_3.seconds.should == 6234
|
78
|
-
end
|
79
|
-
|
80
|
-
it 'has the right number of chapters' do
|
81
|
-
title_3.should have(13).chapters
|
82
|
-
end
|
83
|
-
|
84
|
-
it 'has chapters indexed by chapter number' do
|
85
|
-
title_3.chapters[2].tap do |c|
|
86
|
-
c.duration.should == '00:00:52'
|
87
|
-
c.number.should == 2
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
it 'has an array of the chapters in order' do
|
92
|
-
title_3.all_chapters.collect { |ch| ch.number }.should == (1..13).to_a
|
93
|
-
end
|
94
|
-
|
95
|
-
describe 'a chapter' do
|
96
|
-
let(:chapter) { title_3.chapters[5] }
|
97
|
-
|
98
|
-
it 'has the duration' do
|
99
|
-
chapter.duration.should == '00:03:23'
|
100
|
-
end
|
101
|
-
|
102
|
-
it 'has the duration in seconds' do
|
103
|
-
chapter.seconds.should == 203
|
104
|
-
end
|
105
|
-
|
106
|
-
it 'has the number' do
|
107
|
-
chapter.number.should == 5
|
108
|
-
end
|
109
|
-
|
110
|
-
it 'has a reference to its parent' do
|
111
|
-
chapter.title.should eql title_3
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|