musical 0.0.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +23 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +3 -13
- data/Guardfile +24 -0
- data/README.md +59 -0
- data/Rakefile +4 -34
- data/bin/musical +39 -2
- data/lib/musical.rb +33 -25
- data/lib/musical/configuration.rb +18 -0
- data/lib/musical/dvd.rb +80 -100
- data/lib/musical/dvd/chapter.rb +29 -0
- data/lib/musical/dvd/wav.rb +12 -0
- data/lib/musical/util.rb +31 -0
- data/lib/musical/version.rb +4 -0
- data/musical.gemspec +30 -69
- data/spec/musical/configuration_spec.rb +17 -0
- data/spec/musical/dvd/chapter_spec.rb +47 -0
- data/spec/musical/dvd/wav_spec.rb +18 -0
- data/spec/musical/dvd_spec.rb +235 -0
- data/spec/musical_spec.rb +54 -4
- data/spec/spec_helper.rb +19 -8
- data/spec/util_spec.rb +68 -0
- metadata +221 -108
- data/README.rdoc +0 -63
- data/VERSION +0 -1
- data/lib/musical/itunes.rb +0 -31
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Musical
|
3
|
+
class DVD::Chapter
|
4
|
+
include Musical::Util
|
5
|
+
|
6
|
+
attr_accessor :vob_path, :name, :chapter_number
|
7
|
+
|
8
|
+
DEFAULT_CHAPTER_NUMBER = 1
|
9
|
+
DEFAULT_CHAPTER_NAME = 'default chapter name'
|
10
|
+
DEFAULT_TITLE_NUMBER = 1
|
11
|
+
|
12
|
+
def initialize(vob_path, options = {})
|
13
|
+
raise ArgumentError.new 'VOB path is not given' if vob_path.nil?
|
14
|
+
|
15
|
+
@vob_path = vob_path
|
16
|
+
@name = options[:name] || DEFAULT_CHAPTER_NAME
|
17
|
+
@chapter_number = options[:chapter_number] || DEFAULT_CHAPTER_NUMBER
|
18
|
+
@title_number = options[:title_number] || DEFAULT_TITLE_NUMBER
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_wav(wav_path = "#{Musical.configuration.output}/chapter_#{@title_number}_#{@chapter_number}.wav")
|
22
|
+
return @wav if @wav
|
23
|
+
|
24
|
+
command = "ffmpeg -i #{@vob_path} #{wav_path}"
|
25
|
+
execute_command(command, true)
|
26
|
+
DVD::Wav.new(wav_path)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/musical/util.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Musical
|
3
|
+
module Util
|
4
|
+
REQUIRED_APPS = %w(dvdbackup ffmpeg).freeze
|
5
|
+
|
6
|
+
def check_env
|
7
|
+
REQUIRED_APPS.each do |app|
|
8
|
+
unless installed?(app)
|
9
|
+
messages = []
|
10
|
+
messages << "'#{app}' is not installed."
|
11
|
+
messages << "Try this command to install '#{app}'."
|
12
|
+
messages << ""
|
13
|
+
messages << " brew install #{app}"
|
14
|
+
messages << ""
|
15
|
+
raise RuntimeError, messages.join("\n")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def installed?(app)
|
22
|
+
!execute_command("which #{app}").empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute_command(cmd, silent = false)
|
26
|
+
cmd << ' 2>/dev/null' if silent
|
27
|
+
execute_out, _ = *Open3.capture2(cmd)
|
28
|
+
execute_out
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/musical.gemspec
CHANGED
@@ -1,73 +1,34 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'musical/version'
|
5
5
|
|
6
|
-
Gem::Specification.new do |
|
7
|
-
|
8
|
-
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "musical"
|
8
|
+
spec.version = Musical::VERSION
|
9
|
+
spec.authors = ["ryo katsuma"]
|
10
|
+
spec.email = ["katsuma@gmail.com"]
|
11
|
+
spec.description = %q{Musical is a simple tool for your favorite music DVD. You can rip vob file by DVD chapter, convert it to wav file and add it to your iTunes library.}
|
12
|
+
spec.summary = %q{A simple rip, encode and iTunes library tool for your favorite music DVD}
|
13
|
+
spec.homepage = "http://github.com/katsuma/musical"
|
14
|
+
spec.license = "MIT"
|
9
15
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
s.description = %q{Musical is a simple tool for your favorite music DVD. You can rip vob file by DVD chapter, convert it to wav file and add it to your iTunes library.}
|
15
|
-
s.email = %q{katsuma@gmail.com}
|
16
|
-
s.executables = ["musical"]
|
17
|
-
s.extra_rdoc_files = [
|
18
|
-
"LICENSE.txt",
|
19
|
-
"README.rdoc"
|
20
|
-
]
|
21
|
-
s.files = [
|
22
|
-
".document",
|
23
|
-
".rspec",
|
24
|
-
"Gemfile",
|
25
|
-
"LICENSE.txt",
|
26
|
-
"README.rdoc",
|
27
|
-
"Rakefile",
|
28
|
-
"VERSION",
|
29
|
-
"bin/musical",
|
30
|
-
"lib/musical.rb",
|
31
|
-
"lib/musical/dvd.rb",
|
32
|
-
"lib/musical/itunes.rb",
|
33
|
-
"musical.gemspec",
|
34
|
-
"spec/musical_spec.rb",
|
35
|
-
"spec/spec_helper.rb"
|
36
|
-
]
|
37
|
-
s.homepage = %q{http://github.com/katsuma/musical}
|
38
|
-
s.licenses = ["MIT"]
|
39
|
-
s.require_paths = ["lib"]
|
40
|
-
s.rubygems_version = %q{1.6.2}
|
41
|
-
s.summary = %q{A simple rip, encode and iTunes library tool for your favorite music DVD}
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
42
20
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
s.add_dependency(%q<progressbar>, [">= 0.9.1"])
|
57
|
-
s.add_dependency(%q<rb-appscript>, [">= 0.6.1"])
|
58
|
-
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
59
|
-
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
60
|
-
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
61
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
62
|
-
end
|
63
|
-
else
|
64
|
-
s.add_dependency(%q<trollop>, [">= 1.16.2"])
|
65
|
-
s.add_dependency(%q<progressbar>, [">= 0.9.1"])
|
66
|
-
s.add_dependency(%q<rb-appscript>, [">= 0.6.1"])
|
67
|
-
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
68
|
-
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
69
|
-
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
70
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
71
|
-
end
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0.3"
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.14.0"
|
24
|
+
spec.add_development_dependency "rb-fsevent", "~> 0.3"
|
25
|
+
spec.add_development_dependency "guard", "~> 1.6.2"
|
26
|
+
spec.add_development_dependency "guard-rspec", "~> 2.4.1"
|
27
|
+
spec.add_development_dependency "growl", "~> 1.0.3"
|
28
|
+
spec.add_development_dependency "fakefs", "~> 0.4.2"
|
29
|
+
spec.add_development_dependency "simplecov", "~> 0.7.1"
|
30
|
+
spec.add_development_dependency "coveralls", "~> 0.6.6"
|
31
|
+
spec.add_runtime_dependency "ruby-progressbar", ">= 1.2.0"
|
32
|
+
spec.add_runtime_dependency "trollop", ">= 2.0"
|
33
|
+
spec.add_runtime_dependency "itunes-client", "~> 0.1.2"
|
72
34
|
end
|
73
|
-
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'musical'
|
4
|
+
|
5
|
+
describe Musical::Configuration do
|
6
|
+
describe '#build' do
|
7
|
+
subject { described_class.build(options) }
|
8
|
+
let(:options) { { title: 'foo', artist: 'bar' } }
|
9
|
+
|
10
|
+
it 'sets hash data to config class varible as subclass of OpenStruct' do
|
11
|
+
subject
|
12
|
+
expect(described_class.config).to be_an OpenStruct
|
13
|
+
expect(subject.title).to eq('foo')
|
14
|
+
expect(subject.artist).to eq('bar')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'musical'
|
4
|
+
|
5
|
+
describe Musical::DVD::Chapter do
|
6
|
+
describe '#initialize' do
|
7
|
+
subject { described_class.new(vob_path, options) }
|
8
|
+
|
9
|
+
context 'when vob path is not given' do
|
10
|
+
let(:vob_path) { nil }
|
11
|
+
let(:options) { {} }
|
12
|
+
it 'raises an ArgumentError' do
|
13
|
+
expect{ subject }.to raise_error(ArgumentError)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when vob path is given' do
|
18
|
+
let(:vob_path) { '/path/to/foo.vob' }
|
19
|
+
let(:options) { { chapter_number: 3 } }
|
20
|
+
it 'returns an instance of Chapter' do
|
21
|
+
expect(subject).to be_a(described_class)
|
22
|
+
expect(subject.chapter_number).to eq(3)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#to_wav' do
|
28
|
+
subject { chapter.to_wav(wav_path) }
|
29
|
+
|
30
|
+
let(:chapter) { described_class.new(vob_path, chapter_number: 10) }
|
31
|
+
let(:vob_path) { '/path/to/foo.vob' }
|
32
|
+
let(:wav_path) { '/tmp/foo.wav' }
|
33
|
+
|
34
|
+
before do
|
35
|
+
chapter.should_receive(:execute_command).
|
36
|
+
with("ffmpeg -i #{vob_path} #{wav_path}", true).
|
37
|
+
and_return(FileUtils.touch(wav_path))
|
38
|
+
end
|
39
|
+
|
40
|
+
after { subject.delete! }
|
41
|
+
|
42
|
+
it 'returns wav file which is converted', fakefs: true do
|
43
|
+
expect(subject).to be_a(Musical::DVD::Wav)
|
44
|
+
expect(subject.path).to eq(wav_path)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'musical'
|
4
|
+
|
5
|
+
describe Musical::DVD::Wav do
|
6
|
+
describe '#delete!' do
|
7
|
+
subject { wav.delete! }
|
8
|
+
let!(:wav) do
|
9
|
+
FileUtils.touch(wav_path)
|
10
|
+
described_class.new(wav_path)
|
11
|
+
end
|
12
|
+
let(:wav_path) { '/tmp/foo.wav' }
|
13
|
+
|
14
|
+
it 'deletes original file', faksefs: true do
|
15
|
+
expect { subject }.to change { File.exist?(wav_path) }.from(true).to(false)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'musical'
|
4
|
+
|
5
|
+
include Musical
|
6
|
+
|
7
|
+
describe DVD do
|
8
|
+
describe '.detect' do
|
9
|
+
subject(:detect) { DVD.detect }
|
10
|
+
let(:drutil) { 'drutil status' }
|
11
|
+
|
12
|
+
context 'when DVD drive is not found' do
|
13
|
+
before do
|
14
|
+
DVD.should_receive(:execute_command).with(drutil).and_return('')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'raises a RuntimeError' do
|
18
|
+
expect { detect }.to raise_error(RuntimeError)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when DVD is not inserted' do
|
23
|
+
let(:drutil_out) do
|
24
|
+
<<"EOM"
|
25
|
+
Vendor Product Rev
|
26
|
+
MATSHITA DVD-R UJ-85J FM0S
|
27
|
+
|
28
|
+
Type: No Media Inserted
|
29
|
+
EOM
|
30
|
+
end
|
31
|
+
before { DVD.should_receive(:execute_command).with(drutil).and_return(drutil_out) }
|
32
|
+
|
33
|
+
it 'raises a RuntimeError' do
|
34
|
+
expect { detect }.to raise_error(RuntimeError)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when DVD is inserted' do
|
39
|
+
let(:drutil_out) do
|
40
|
+
<<"EOM"
|
41
|
+
Vendor Product Rev
|
42
|
+
MATSHITA DVD-R UJ-85J FM0S
|
43
|
+
|
44
|
+
Type: DVD-ROM Name: /dev/disk3
|
45
|
+
Sessions: 1 Tracks: 1
|
46
|
+
Overwritable: 00:00:00 blocks: 0 / 0.00MB / 0.00MiB
|
47
|
+
Space Free: 00:00:00 blocks: 0 / 0.00MB / 0.00MiB
|
48
|
+
Space Used: 787:56:00 blocks: 3545700 / 7.26GB / 6.76GiB
|
49
|
+
Writability:
|
50
|
+
Book Type: DVD-ROM (v1)
|
51
|
+
EOM
|
52
|
+
end
|
53
|
+
before do
|
54
|
+
DVD.should_receive(:execute_command).with(drutil).and_return(drutil_out)
|
55
|
+
end
|
56
|
+
|
57
|
+
it { should == '/dev/disk3' }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '.path' do
|
62
|
+
subject { DVD.path }
|
63
|
+
context 'path class property is not set' do
|
64
|
+
it 'returns nil' do
|
65
|
+
expect(subject).to eq(nil)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'path class property is set' do
|
70
|
+
before{ DVD.path = '/dev/path' }
|
71
|
+
it 'returns property value' do
|
72
|
+
expect(subject).to eq('/dev/path')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '.load' do
|
78
|
+
before { DVD.path = nil }
|
79
|
+
before { DVD.any_instance.should_receive(:info).and_return('info data') }
|
80
|
+
|
81
|
+
context 'when options are not given' do
|
82
|
+
subject { DVD.load }
|
83
|
+
|
84
|
+
context 'and if path is set' do
|
85
|
+
before { DVD.path = '/dev/some/path' }
|
86
|
+
it 'does not call DVD.detect' do
|
87
|
+
DVD.should_not_receive(:detect)
|
88
|
+
subject
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'and if DVD path is not set' do
|
93
|
+
it 'sets path by DVD.detect' do
|
94
|
+
DVD.should_receive(:detect).and_return('/dev/some/path')
|
95
|
+
subject
|
96
|
+
expect(DVD.path).to eq('/dev/some/path')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when options are given' do
|
102
|
+
subject { DVD.load(options) }
|
103
|
+
context 'and if option does not have `forcibly` key' do
|
104
|
+
let(:options) { { path: '/dev/path', title: 'some title' } }
|
105
|
+
|
106
|
+
it 'sets path by given option' do
|
107
|
+
subject
|
108
|
+
expect(DVD.path).to eq('/dev/path')
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'sets title by given option' do
|
112
|
+
subject
|
113
|
+
expect(DVD.instance.title).to eq('some title')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'and if option has `forcibly` key' do
|
118
|
+
let(:options) { { forcibly: true } }
|
119
|
+
before { DVD.path = '/dev/some/path' }
|
120
|
+
|
121
|
+
it 'calls DVD.detect forcibly even if path is already set' do
|
122
|
+
DVD.should_receive(:detect).and_return('/dev/some/path')
|
123
|
+
subject
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'when block is given' do
|
129
|
+
subject { DVD.load { |dvd| dvd.artist = 'some artist' } }
|
130
|
+
before { DVD.should_receive(:detect).and_return('/dev/path') }
|
131
|
+
it 'calls proc object' do
|
132
|
+
subject
|
133
|
+
expect(DVD.instance.artist).to eq('some artist')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#info' do
|
139
|
+
subject { dvd.info }
|
140
|
+
let(:dvd) { DVD.instance }
|
141
|
+
|
142
|
+
context 'when DVD.path is not set' do
|
143
|
+
before { DVD.path = nil }
|
144
|
+
it 'raises an RuntimeError' do
|
145
|
+
expect { subject }.to raise_error(RuntimeError)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'when DVD.path is set' do
|
150
|
+
before { DVD.path = '/dev/path' }
|
151
|
+
let(:info_data) { 'dvd data' }
|
152
|
+
it 'returns DVD disk data' do
|
153
|
+
dvd.should_receive(:execute_command).with("dvdbackup --info --input='/dev/path'", true).and_return(info_data)
|
154
|
+
expect(subject).to eq(info_data)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe '#title_sets' do
|
160
|
+
subject { dvd.title_sets }
|
161
|
+
let(:info) do
|
162
|
+
<<"EOM"
|
163
|
+
Title Sets:
|
164
|
+
|
165
|
+
Title set 1
|
166
|
+
The aspect ratio of title set 1 is 16:9
|
167
|
+
Title set 1 has 1 angle
|
168
|
+
Title set 1 has 1 audio track
|
169
|
+
Title set 1 has 0 subpicture channels
|
170
|
+
|
171
|
+
Title included in title set 1 is
|
172
|
+
Title 1:
|
173
|
+
Title 1 has 15 chapters
|
174
|
+
Title 1 has 2 audio channels
|
175
|
+
EOM
|
176
|
+
end
|
177
|
+
let(:dvd) { DVD.instance }
|
178
|
+
|
179
|
+
before { dvd.should_receive(:info).and_return(info) }
|
180
|
+
|
181
|
+
it 'returns each pair of title and chapter data' do
|
182
|
+
expect(subject).to be_an Array
|
183
|
+
expect(subject.size).to eq(1)
|
184
|
+
expect(subject.first[:title]).to eq(1)
|
185
|
+
expect(subject.first[:chapter]).to eq(15)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe '#rip' do
|
190
|
+
subject { dvd.rip }
|
191
|
+
let(:dvd) { DVD.instance }
|
192
|
+
|
193
|
+
context 'when DVD.path if not set' do
|
194
|
+
before { DVD.path = nil }
|
195
|
+
it 'raises an RuntimeError' do
|
196
|
+
expect { subject }.to raise_error(RuntimeError)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'when DVD.path is set' do
|
201
|
+
before { DVD.path = '/dev/path' }
|
202
|
+
|
203
|
+
let(:configuration) { OpenStruct.new(output: '/tmp/out', working_dir: '/tmp/working') }
|
204
|
+
let(:title_sets) { [{ title: 1, chapter: 3 }, { title: 2, chapter: 4 }] }
|
205
|
+
let(:vob_path) { '/tmp/working/foo.vob' }
|
206
|
+
|
207
|
+
def stub_methods
|
208
|
+
Musical.should_receive(:configuration).at_least(1).times.and_return(configuration)
|
209
|
+
dvd.should_receive(:title_sets).at_least(1).and_return(title_sets)
|
210
|
+
dvd.should_receive(:execute_command).at_least(1).with(/dvdbackup (.)*/, true) { FileUtils.touch(vob_path) }
|
211
|
+
dvd.should_receive(:execute_command).at_least(1).with(/find (.)*/).and_return("#{vob_path}\n")
|
212
|
+
|
213
|
+
progress_bar = double
|
214
|
+
progress_bar.should_receive(:increment).at_least(1)
|
215
|
+
ProgressBar.should_receive(:create).and_return(progress_bar)
|
216
|
+
end
|
217
|
+
|
218
|
+
before do
|
219
|
+
FileUtils.mkdir_p(configuration.output)
|
220
|
+
FileUtils.mkdir_p(configuration.working_dir)
|
221
|
+
stub_methods
|
222
|
+
end
|
223
|
+
|
224
|
+
after do
|
225
|
+
FileUtils.rm_rf(configuration.working_dir)
|
226
|
+
FileUtils.rm_rf(configuration.output)
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'returns an array of all chapters', fakefs: true do
|
230
|
+
expect(subject).to be_an Array
|
231
|
+
expect(subject.size).to eq(7)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|