musical 0.0.4 → 1.0.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.
- 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
|