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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 378b27ab3089eed1a4a51d4639c2ff78dc119643
4
+ data.tar.gz: ce72f42da6904205207bfb5a91665c0e4f34c92f
5
+ SHA512:
6
+ metadata.gz: 83a09271c9eb2c78cf16399d2b6dce680fe0c970fa140ce202448c32b8bcc2cfecf3fc5f4317a4289bbe74e9ef071173ab316e25426229c1a659dea6c8609cf6
7
+ data.tar.gz: e996832bcccd44afb3ef5286890c563b64b7510d953b3b4d558904f1518944bf06d75f30f7861a943b66e8f1a5aab669f73fdd5148c83871dbedbbfb7f616749
@@ -0,0 +1 @@
1
+ service_name: travis-ci
@@ -0,0 +1,23 @@
1
+ *.DS_Store
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ ripped
7
+ coverage
8
+ InstalledFiles
9
+ lib/bundler/man
10
+ pkg
11
+ rdoc
12
+ spec/reports
13
+ test/tmp
14
+ test/version_tmp
15
+ tmp
16
+
17
+ # YARD artifacts
18
+ .yardoc
19
+ _yardoc
20
+ doc/
21
+ .bundle
22
+ Gemfile.lock
23
+ pkg/*
@@ -0,0 +1 @@
1
+ 2.0.0-p247
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
data/Gemfile CHANGED
@@ -1,14 +1,4 @@
1
- source "http://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
- gem "trollop", ">=1.16.2"
4
- gem "progressbar", ">=0.9.1"
5
- gem "rb-appscript", ">=0.6.1"
6
-
7
- # Add dependencies to develop your gem here.
8
- # Include everything needed to run rake, tests, features, etc.
9
- group :development do
10
- gem "rspec", "~> 2.3.0"
11
- gem "bundler", "~> 1.0.0"
12
- gem "jeweler", "~> 1.6.4"
13
- gem "rcov", ">= 0"
14
- end
3
+ # Specify your gem's dependencies in musical.gemspec
4
+ gemspec
@@ -0,0 +1,24 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+
9
+ # Rails example
10
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
11
+ watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
12
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
13
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
14
+ watch('config/routes.rb') { "spec/routing" }
15
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
16
+
17
+ # Capybara features specs
18
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
19
+
20
+ # Turnip features and steps
21
+ watch(%r{^spec/acceptance/(.+)\.feature$})
22
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
23
+ end
24
+
@@ -0,0 +1,59 @@
1
+ # musical [![Build Status](https://travis-ci.org/katsuma/musical.png?branch=master)](https://travis-ci.org/katsuma/musical) [![Coverage Status](https://coveralls.io/repos/katsuma/musical/badge.png)](https://coveralls.io/r/katsuma/musical)
2
+
3
+ `musical` is a simple tool for your favorite music DVD.
4
+
5
+ You can rip vob files by each DVD chapter, convert them to wav file and add them to your iTunes library.
6
+
7
+
8
+ ## Install
9
+
10
+ `musical` depends on `dvdbackup` and `ffmpeg`.
11
+ To install them try this for example.
12
+
13
+ ```sh
14
+ brew install dvdbackup
15
+ brew install ffmpeg
16
+ ```
17
+
18
+ And install gem.
19
+
20
+ ```sh
21
+ gem install musical
22
+ ```
23
+
24
+
25
+ ## Usage
26
+ Set your DVD and type
27
+
28
+ ```sh
29
+ musical <options>
30
+ ```
31
+
32
+ Options:
33
+ ```sh
34
+ --info, -i: Show your DVD data
35
+ --ignore-convert-sound, -g: Rip data only, NOT convert them to wav file
36
+ --ignore-use-itunes, -n: NOT add ripped files to iTunes and encode them
37
+ --dev, -d <s>: Set location of DVD device
38
+ --title, -t <s>: Set DVD title (default: LIVE)
39
+ --artist, -a <s>: Set DVD artist (default: Artist)
40
+ --output, -o <s>: Set location of ripped data (default: ripped)
41
+ --version, -v: Print version and exit
42
+ --help, -h: Show this message
43
+ ```
44
+
45
+ When you use iTunes, you should use `--title` and `--artist` options.
46
+
47
+ They will help you to manage your music library easily.
48
+
49
+
50
+ ## Supported OS
51
+ - OSX 10.8 (Mountain Lion)
52
+
53
+ ## Supported Ruby
54
+ - 2.0.0
55
+ - 1.9.3
56
+
57
+
58
+ ## License
59
+ `musical` is released under the MIT License.
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
- # encoding: utf-8
2
-
3
1
  require 'rubygems'
2
+ require "bundler/gem_tasks"
4
3
  require 'bundler'
4
+
5
5
  begin
6
6
  Bundler.setup(:default, :development)
7
7
  rescue Bundler::BundlerError => e
@@ -9,41 +9,11 @@ rescue Bundler::BundlerError => e
9
9
  $stderr.puts "Run `bundle install` to install missing gems"
10
10
  exit e.status_code
11
11
  end
12
- require 'rake'
13
-
14
- require 'jeweler'
15
- Jeweler::Tasks.new do |gem|
16
- # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
- gem.name = "musical"
18
- gem.homepage = "http://github.com/katsuma/musical"
19
- gem.license = "MIT"
20
- gem.summary = %Q{A simple rip, encode and iTunes library tool for your favorite music DVD}
21
- gem.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.}
22
- gem.email = "katsuma@gmail.com"
23
- gem.authors = ["ryo katsuma"]
24
- # dependencies defined in Gemfile
25
- end
26
- Jeweler::RubygemsDotOrgTasks.new
27
12
 
28
13
  require 'rspec/core'
29
14
  require 'rspec/core/rake_task'
30
- RSpec::Core::RakeTask.new(:spec) do |spec|
31
- spec.pattern = FileList['spec/**/*_spec.rb']
32
- end
33
15
 
34
- RSpec::Core::RakeTask.new(:rcov) do |spec|
35
- spec.pattern = 'spec/**/*_spec.rb'
36
- spec.rcov = true
37
- end
16
+ RSpec::Core::RakeTask.new(:spec)
38
17
 
39
18
  task :default => :spec
40
-
41
- require 'rake/rdoctask'
42
- Rake::RDocTask.new do |rdoc|
43
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
-
45
- rdoc.rdoc_dir = 'rdoc'
46
- rdoc.title = "musical #{version}"
47
- rdoc.rdoc_files.include('README*')
48
- rdoc.rdoc_files.include('lib/**/*.rb')
49
- end
19
+ require "bundler/gem_tasks"
@@ -3,5 +3,42 @@
3
3
  require 'musical'
4
4
  include Musical
5
5
 
6
- opts = Musical.setup
7
- DVD.new opts
6
+ Musical.setup
7
+
8
+ dvd_options = {
9
+ title: Musical.configuration.title,
10
+ artist: Musical.configuration.artist,
11
+ path: Musical.configuration.path
12
+ }
13
+
14
+ DVD.load(dvd_options) do |dvd|
15
+ next puts dvd.info if Musical.configuration.info
16
+
17
+ chapters = dvd.rip
18
+
19
+ chapters.each do |chapter|
20
+ next if Musical.configuration.ignore_convert_sound
21
+
22
+ wav = chapter.to_wav
23
+ next if Musical.configuration.ignore_use_itunes
24
+
25
+ track = Itunes::Player.add(wav.expand_path)
26
+
27
+ converted_track = track.convert
28
+ converted_track.update_attributes(
29
+ name: chapter.name,
30
+ album: dvd.title,
31
+ artist: dvd.artist,
32
+ track_count: chapters.size,
33
+ track_number: chapter.chapter_number,
34
+ )
35
+
36
+ wav.delete!
37
+ track.delete!
38
+ end
39
+ end
40
+
41
+ if !Musical.configuration.ignore_convert_sound &&
42
+ !Musical.configuration.ignore_use_itunes
43
+ FileUtils.rm_f(Musical.configuration.output)
44
+ end
@@ -1,40 +1,48 @@
1
- require 'rubygems'
2
1
  require 'trollop'
3
- require 'progressbar'
2
+ require 'ruby-progressbar'
4
3
  require 'fileutils'
5
- require 'appscript'
4
+ require 'open3'
5
+ require 'ostruct'
6
+ require 'itunes-client'
6
7
 
8
+ require 'musical/configuration'
9
+ require 'musical/util'
10
+ require 'musical/version'
7
11
  require 'musical/dvd'
8
- require 'musical/itunes'
12
+ require 'musical/dvd/chapter'
13
+ require 'musical/dvd/wav'
9
14
 
10
15
  module Musical
16
+ extend Musical::Util
17
+
18
+ def configuration
19
+ Configuration.config || Musical.setup
20
+ end
21
+ module_function :configuration
22
+
11
23
  def setup
12
- # check env
13
- ['dvdbackup', 'ffmpeg'].each do |app|
14
- if `which #{app}`.empty?
15
- raise RuntimeError, "\n\n'#{app}' is not installed.\n\ntry:\n brew install #{app}\n\n"
16
- end
17
- end
24
+ return unless check_env
18
25
 
19
- version = open(File.join(File.dirname(__FILE__), "..", "VERSION")){ |f| f.gets }
26
+ # init working directory
27
+ working_dir = File.join(File.expand_path('~'), '.musical')
28
+ FileUtils.mkdir_p(working_dir) unless File.exist?(working_dir)
20
29
 
21
30
  # parse options
22
- opts = Trollop::options do
23
- version "Musical #{version}"
24
- opt :info, "Show your DVD data", :type => :boolean
25
- opt :ignore_convert_sound, "Rip data only, NOT convert them to wav file", :type => :boolean
26
- opt :ignore_use_itunes, "NOT add ripped files to iTunes and encode them", :type => :boolean
27
- opt :dev, "Set location of DVD device"
28
- opt :title, "Set DVD title", :default => 'LIVE'
29
- opt :artist, "Set DVD artist", :default => 'Artist'
30
- opt :output, "Set location of ripped data", :default => 'ripped'
31
+ options = Trollop::options do
32
+ version "Musical #{Musical::VERSION}"
33
+ opt :info, "Show your DVD data", type: :boolean
34
+ opt :ignore_convert_sound, "Rip data only, NOT convert them to wav file", type: :boolean
35
+ opt :ignore_use_itunes, "NOT add ripped files to iTunes and encode them", type: :boolean
36
+ opt :path, "Set device path of DVD", type: :string
37
+ opt :title, "Set DVD title", type: :string, default: 'LIVE'
38
+ opt :artist, "Set DVD artist", type: :string, default: 'Artist'
39
+ opt :output, "Set location of ripped data", type: :string, default: 'ripped'
31
40
  end
32
41
 
33
- # fix fox dvdbackup
34
- opts[:trim_title] = opts[:title].gsub(" ", "_")
35
- opts[:trim_artist] = opts[:artist].gsub(" ", "_")
42
+ configuration = Configuration.build(options.merge(working_dir: working_dir))
43
+ yield(configuration) if block_given?
36
44
 
37
- opts
45
+ configuration
38
46
  end
39
-
47
+ module_function :setup
40
48
  end
@@ -0,0 +1,18 @@
1
+ # coding: utf-8
2
+ require 'singleton'
3
+
4
+ module Musical
5
+ class Configuration < OpenStruct
6
+ include Singleton
7
+
8
+ @@config = nil
9
+
10
+ def self.build(options)
11
+ @@config = OpenStruct.new(options)
12
+ end
13
+
14
+ def self.config
15
+ @@config
16
+ end
17
+ end
18
+ end
@@ -1,132 +1,112 @@
1
+ # coding: utf-8
2
+ require 'singleton'
3
+
1
4
  module Musical
2
5
  class DVD
3
- attr_accessor :opts
6
+ include Singleton
7
+ include Musical::Util
8
+ extend Musical::Util
4
9
 
5
- def self.detect
6
- dev_infos = `df -h | awk {'print $1,$5,$6'}`.split("\n")
7
- candidates = []
8
- dev_infos.each do |dev_info|
9
- info = dev_info.split(" ")
10
- file_system = info[0]
11
- capacity = info[1]
12
- mounted = info[2]
13
- if capacity == "100%" && mounted.include?("/Volumes") && !(mounted.include?("MobileBackups"))
14
- candidates << mounted
15
- end
16
- end
10
+ attr_accessor :title, :artist
17
11
 
18
- raise "Not detect DVD device" if candidates.empty?
19
- candidates
20
- end
12
+ @@path = nil
13
+
14
+ DETECT_ERROR_MESSAGE = 'Not detect DVD, Try `DVD.load` and check your drive device path.'
15
+ DRIVE_NOT_FOUND_MESSAGE = 'DVD drive is not found.'
16
+ DVD_NOT_INSERTED_MESSAGE = 'DVD is not inserted.'
21
17
 
22
- def initialize(options={})
23
- @opts = options
18
+ def self.detect
19
+ drutil_out = execute_command('drutil status')
24
20
 
25
- return puts info if @opts[:info]
21
+ raise RuntimeError.new DRIVE_NOT_FOUND_MESSAGE unless drutil_out
22
+ raise RuntimeError.new DVD_NOT_INSERTED_MESSAGE unless drutil_out.include?('Name:')
26
23
 
27
- rip_by_chapter
28
- convert_sound unless @opts[:ignore_convert_sound]
29
- to_itunes unless @opts[:ignore_use_itunes]
24
+ file_system = drutil_out.split("\n").select do |line|
25
+ line.include?('Name:')
26
+ end.first.match(/Name: (.+)/)[1]
30
27
  end
31
28
 
32
- def dev
33
- @_dev ||= DVD.detect.first
29
+ def self.path=(path)
30
+ @@path = path
34
31
  end
35
32
 
36
- def info
37
- raise "Not detect DVD device" if dev.empty?
38
- @_info ||= `dvdbackup --input=#{dev} --info 2>/dev/null`
33
+ def self.path
34
+ @@path
39
35
  end
40
36
 
41
- def title_with_chapters
42
- return @_title_chapters unless @_title_chapters.nil?
43
-
44
- @_title_chapters = []
45
- info_str = info.split("\n")
46
- info_str.each_with_index do |line, index|
47
- if line =~ /\s*Title (\d):$/
48
- @_title_chapters << { :title => $1.to_i, :line => index }
49
- end
37
+ def self.load(options = {})
38
+ if @@path.nil? || options[:forcibly]
39
+ @@path = options[:path] || self.detect
50
40
  end
51
41
 
52
- @_title_chapters.each do |title_chapter|
53
- line = title_chapter[:line]
54
- if info_str[line + 1] =~ /\s*Title (\d) has (\d*) chapter/
55
- title_chapter[:chapter] = $2.to_i
56
- title_chapter.delete(:line)
57
- end
58
- end
59
- @_title_chapters
60
- end
42
+ dvd = DVD.instance
43
+ dvd.title = options[:title] || Musical.configuration.title
44
+ dvd.artist = options[:artist] || Musical.configuration.artist
61
45
 
62
- def rip_by_chapter
63
- task_message "Ripping"
64
-
65
- pbar = ProgressBar.new "Ripping", chapter_size
66
- title_with_chapters.each_with_index do |title_chapter, title_index|
67
- ripped_dir_base = "#{@opts[:output]}"
68
- saved_dir = "#{ripped_dir_base}/#{@opts[:trim_title]}/title_#{title_index+1}"
69
- FileUtils.mkdir_p "#{saved_dir}"
70
-
71
- 1.upto title_chapter[:chapter] do |chapter|
72
- `dvdbackup --name=#{@opts[:trim_title]} --input=#{dev} --title=#{title_index+1} --start=#{chapter} --end=#{chapter} --output=#{ripped_dir_base}_#{title_index}_#{chapter} 2>/dev/null`
73
- pbar.inc
74
- # moved file
75
- vob_path = `find #{ripped_dir_base}_#{title_index}_#{chapter} -name "*.VOB"`.chomp
76
- FileUtils.mv vob_path, "#{saved_dir}/chapter_#{chapter}.VOB"
77
- FileUtils.rm_rf "#{ripped_dir_base}_#{title_index}_#{chapter}"
78
- end
46
+ if block_given?
47
+ yield(dvd)
79
48
  end
80
- pbar.finish
49
+
50
+ dvd.info
81
51
  end
82
52
 
83
- def convert_sound
84
- task_message "Converting"
53
+ def info
54
+ raise RuntimeError.new DETECT_ERROR_MESSAGE unless @@path
85
55
 
86
- pbar = ProgressBar.new "Converting", chapter_size
87
- title_with_chapters.each_with_index do |title_chapter, title_index|
88
- ripped_dir_base = "#{@opts[:output]}"
89
- saved_dir = "#{ripped_dir_base}/#{@opts[:trim_title]}/title_#{title_index+1}"
56
+ return @info if @info
90
57
 
91
- 1.upto title_chapter[:chapter] do |chapter|
92
- `ffmpeg -i #{saved_dir}/chapter_#{chapter}.VOB #{saved_dir}/chapter_#{chapter}.wav 2>/dev/null`
93
- FileUtils.rm_f "#{saved_dir}/chapter_#{chapter}.VOB"
94
- pbar.inc
95
- end
96
- end
97
- pbar.finish
58
+ @info = execute_command("dvdbackup --info --input='#{@@path}'", true)
59
+ raise RuntimeError.new DETECT_ERROR_MESSAGE if @info.empty?
60
+ @info
98
61
  end
99
62
 
100
- def to_itunes
101
- task_message "To iTunes"
63
+ def title_sets
64
+ return @title_sets if @title_sets
102
65
 
103
- pbar = ProgressBar.new "To iTunes", chapter_size
104
- its = ITunes.new
105
-
106
- title_with_chapters.each_with_index do |title_chapter, title_index|
107
- ripped_dir_base = "#{@opts[:output]}"
108
- saved_dir = "#{ripped_dir_base}/#{@opts[:trim_title]}/title_#{title_index+1}"
109
-
110
- options = { :album => @opts[:title], :artist => @opts[:artist], :track_count => title_chapter[:chapter]}
111
- 1.upto title_chapter[:chapter] do |chapter|
112
- its.add("#{saved_dir}/chapter_#{chapter}.wav", options.merge(:track_number => chapter))
113
- pbar.inc
66
+ @title_sets = [].tap do |sets|
67
+ sets_regexp = /\s*Title (\d) has (\d*) chapter/
68
+ info.split("\n").each do |line|
69
+ if line =~ sets_regexp
70
+ sets << { title: $1.to_i, chapter: $2.to_i }
71
+ end
114
72
  end
115
73
  end
116
- FileUtils.rm_rf @opts[:output]
117
- pbar.finish
118
74
  end
119
75
 
120
- def chapter_size
121
- @_chapter_size ||= title_with_chapters.inject(0){ |count, t| count + t[:chapter]}
76
+ def vob_path
77
+ find_command = "find '#{Musical.configuration.working_dir}' -name '*.VOB'"
78
+ execute_command(find_command).split("\n").first
122
79
  end
123
- private :chapter_size
124
-
125
- def task_message(task)
126
- raise "Not found any titles and chapters" if title_with_chapters.size == 0 && chapter_size == 0
127
- puts "#{task} #{title_with_chapters.size} titles, #{chapter_size} chapters"
80
+ private :vob_path
81
+
82
+ def rip
83
+ raise RuntimeError.new DETECT_ERROR_MESSAGE unless @@path
84
+ save_dir = Musical.configuration.output
85
+ FileUtils.mkdir_p save_dir
86
+
87
+ chapter_size = title_sets.inject(0){ |size, set| size + set[:chapter] }
88
+ progress_bar = ProgressBar.create(title: 'Ripping', total: chapter_size, format: '%a %B %p%% %t')
89
+ chapters = []
90
+
91
+ title_sets.each do |title_set|
92
+ chapters << (1..title_set[:chapter]).map do |chapter_index|
93
+ commands = []
94
+ commands << 'dvdbackup'
95
+ commands << "--input='#{@@path}'"
96
+ commands << "--title='#{title_set[:title]}'"
97
+ commands << "--start=#{chapter_index}"
98
+ commands << "--end=#{chapter_index}"
99
+ commands << "--output='#{Musical.configuration.working_dir}'"
100
+ execute_command(commands.join(' '), true)
101
+
102
+ progress_bar.increment
103
+
104
+ vob_save_path = "#{save_dir}/TITLE_#{title_set[:title]}_#{chapter_index}.VOB"
105
+ FileUtils.mv(vob_path, vob_save_path)
106
+ Chapter.new(vob_save_path, title_number: title_set[:title], chapter_number: chapter_index)
107
+ end
108
+ end
109
+ chapters.flatten
128
110
  end
129
- private :task_message
130
-
131
111
  end
132
112
  end