handbrake 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|