kbl 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 73abc7025129ec8803f02d292b4c2de0f0fb9f9d
4
- data.tar.gz: 994c849b5b69b728d069909f3e0823fa1b251240
3
+ metadata.gz: 7675c0becfd84f2bc39016316d19c9cca200daeb
4
+ data.tar.gz: f3df0ab1a649a300f7ac996f342160cd47f34aa9
5
5
  SHA512:
6
- metadata.gz: ad74dea81d0e2264bf620a123164ceb167d3b909877c151588491a3417bc2e9e71db201e4364a512ff1de2a1de8bfd634ade4a79909ff9dc2722b1c34d25973a
7
- data.tar.gz: d1720e1506bb0f198d7037e355cf3e2d97edd99603a7e90986328303ad4ab0348c4f9e554ce34e436bb8b1ad049703401045fe0619657f6444c4f885e96db9bf
6
+ metadata.gz: a405a118a81a11dcf0a564fe3060358e117bd2d643a72013430ffb95ea69c6cf198f16f2ca31f34ee8b871112965bb3fe0dda9a26333bba877e6b9bff06b2e32
7
+ data.tar.gz: de489185b4a7ce05520c8b0f4959c207d37f4886eba8b198095923584188a19a143fdf6763ef27ee15fe92ca0d86b193f61d51a9548f6dc3512147434b1c6943
data/README.md CHANGED
@@ -42,6 +42,20 @@ end
42
42
  puts kbl.to_kbl #=> the KBL source code (in XML)
43
43
  ```
44
44
 
45
+ ### CLI Tool
46
+
47
+ This gem provides a CLI tool `kbl` that simplifies playlist importing / dumping via Song IDs (pathname).
48
+
49
+ Usage:
50
+
51
+ ```txt
52
+ # Import KKBOX Songs from a list of IDs through [FILENAME] or STDIN.
53
+ kbl import [INPUT_FILE or STDIN]
54
+
55
+ # Dump meta data of KKBOX songs from your local KKBOX client database.
56
+ kbl dump [INPUT_FILE or STDIN] [-o=OUTPUT_FILE or STDOUT] [-f=tsv]
57
+ ```
58
+
45
59
  ## Required Song Meta Data
46
60
 
47
61
  To generate a working KBL file, the following data for every song must be provided.
@@ -94,24 +108,4 @@ Can exist multiple times across multiple playlists.
94
108
 
95
109
  ## License
96
110
 
97
- The MIT License (MIT)
98
-
99
- Copyright (c) 2014 Yu-Cheng Chuang
100
-
101
- Permission is hereby granted, free of charge, to any person obtaining a copy
102
- of this software and associated documentation files (the "Software"), to deal
103
- in the Software without restriction, including without limitation the rights
104
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
105
- copies of the Software, and to permit persons to whom the Software is
106
- furnished to do so, subject to the following conditions:
107
-
108
- The above copyright notice and this permission notice shall be included in
109
- all copies or substantial portions of the Software.
110
-
111
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
112
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
113
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
114
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
115
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
116
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
117
- THE SOFTWARE.
111
+ The MIT License (MIT). See LICENSE.txt.
data/bin/kbl ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'kbl'
6
+ require 'kbl/cli'
7
+
8
+ KBL::CLI.start(ARGV)
@@ -5,7 +5,7 @@ require 'kbl/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "kbl"
8
- spec.version = Kbl::VERSION
8
+ spec.version = KBL::VERSION
9
9
  spec.authors = ["Yu-Cheng Chuang"]
10
10
  spec.email = ["ducksteven@gmail.com"]
11
11
  spec.summary = %q{KKBOX Playlist Builder}
@@ -14,11 +14,13 @@ Gem::Specification.new do |spec|
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.executables = %w(kbl)
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency 'nokogiri', '~> 1.6.1'
22
+ spec.add_dependency 'thor', '~> 0.19.1'
23
+ spec.add_dependency 'sqlite3', '~> 1.3.9'
22
24
 
23
25
  spec.add_development_dependency "bundler", "~> 1.5"
24
26
  spec.add_development_dependency "rake"
@@ -0,0 +1,170 @@
1
+ require 'thor'
2
+ require 'readline'
3
+ require 'sqlite3'
4
+ require 'csv'
5
+
6
+ module KBL
7
+ class CLI < Thor
8
+ class MetaData < Struct.new(:song_id, :song_name, :artist_id, :artist_name, :album_id, :album_name)
9
+ def to_a
10
+ [
11
+ self.song_id,
12
+ self.song_name,
13
+ self.artist_id,
14
+ self.artist_name,
15
+ self.album_id,
16
+ self.album_name
17
+ ]
18
+ end
19
+
20
+ def to_tsv
21
+ to_a.join "\t"
22
+ end
23
+ end
24
+
25
+ desc "import [INPUT_FILE or STDIN]", "Import KKBOX Songs from a list of IDs through [FILENAME] or STDIN."
26
+ long_desc <<-EOS
27
+ Given a list of KKBOX Song IDs (pathname), import them to your KKBOX client's
28
+ Temporary Playlist, by brute-forcely `open`ing a bunch of
29
+ `kkbox://play_song_xxxx` URLs.
30
+
31
+ This will affect your current Temporary Playlist and make "now playing"
32
+ song to the last song in the list.
33
+ EOS
34
+
35
+ option :yes, :type => :boolean, :default => false, :aliases => :y,
36
+ :desc => "Skip warning for overriding KKBOX now playing."
37
+
38
+ def import(filename=nil)
39
+ input = open_file(filename) || STDIN
40
+
41
+ ids = input.readlines
42
+ ids.each(&:chomp!).keep_if {|id| id.match(/\d+/) }#.delete("")
43
+
44
+ if ids.empty?
45
+ puts "No songs to import. Exit."
46
+ exit 2
47
+ end
48
+
49
+ if !options[:yes]
50
+ puts "\e[1;31mThis will import #{ids.size} songs into KKBOX and play the last song.\e[m"
51
+
52
+ while answer = Readline.readline("Continue? [Y/n] ", false)
53
+ case answer.downcase
54
+ when 'n', 'no'
55
+ puts "Cancelled."
56
+ exit 3
57
+ when 'y', 'yes', ''
58
+ break # stop asking and continue importing
59
+ else
60
+ puts "Please answer 'y' or 'n', or hit enter for 'y'."
61
+ end
62
+ end
63
+ end
64
+
65
+ puts "Importing..."
66
+
67
+ system "open #{ids.map { |id| "kkbox://play_song_#{id}" }.join(' ')}"
68
+ end
69
+
70
+ desc "dump [INPUT_FILE or STDIN] [-o=OUTPUT_FILE or STDOUT] [-f=tsv]",
71
+ "Dump meta data of KKBOX songs from your local KKBOX client database."
72
+ long_desc <<-EOS
73
+ Given a list of KKBOX Song IDs (pathname), find their meta data from your
74
+ KKBOX client's local database, and if found, export them to the file
75
+ set in --output option, or the STDOUT stream if not given.
76
+
77
+ If you have multiple accounts logged in to KKBOX client,
78
+ it will look into all of them.
79
+
80
+ The output contains the following fields:
81
+
82
+ name: Song Title
83
+
84
+ pathname: Song ID
85
+
86
+ artist: Artist Name
87
+
88
+ artist_id: Artist ID
89
+
90
+ album: Album Name
91
+
92
+ album_id: Album ID
93
+
94
+ For tabular format like TSV and CSV, these columns will be printed
95
+ in the above order.
96
+ EOS
97
+
98
+ option :format, :type => :string, :default => "tsv", :aliases => :f,
99
+ :desc => "Format of output data.",
100
+ :banner => "tsv"
101
+
102
+ option :output, :type => :string, :default => nil, :aliases => :o,
103
+ :desc => "The output file to write. Uses STDOUT if absent.",
104
+ :banner => "OUTPUT_FILE"
105
+
106
+ def dump(input_filename=nil)
107
+ input = open_file(input_filename) || STDIN
108
+
109
+ if output_filename = options[:output]
110
+ output = open_file(output_filename, 'w')
111
+ if output.nil?
112
+ puts "Cannot open file #{output_filename} for output."
113
+ exit 1
114
+ end
115
+ else
116
+ output = STDOUT
117
+ end
118
+
119
+ ids = input.readlines
120
+ ids.each(&:chomp!).keep_if {|id| id.match(/\d+/) }#.delete("")
121
+
122
+ if ids.empty?
123
+ puts "No songs to export. Exit."
124
+ exit 2
125
+ end
126
+
127
+ meta_data = {}
128
+
129
+ db_files = Dir.glob(File.expand_path("~/Library/Application Support/KKBOX/*/Playlists2.db"))
130
+
131
+ db_files.each do |db_file|
132
+ db = SQLite3::Database.new db_file
133
+
134
+ sql = <<-SQL
135
+ SELECT song_id, song_name, artist_id, artist_name, album_id, album_name
136
+ FROM song_tracks
137
+ WHERE song_id IN (#{ids.map {|id|"'#{id}'"}.join(',')});
138
+ SQL
139
+
140
+ db.execute(sql) do |row|
141
+ data = MetaData.new(*row)
142
+
143
+ meta_data[data.song_id] = data
144
+ end
145
+ end
146
+
147
+ ids.each do |id|
148
+ if data = meta_data[id]
149
+ output.puts data.send(:"to_#{options[:format]}")
150
+ else
151
+ STDERR.puts "ID Not Found: #{id}"
152
+ end
153
+ end
154
+ end
155
+
156
+ private
157
+ def open_file(filename, mode='r')
158
+ if filename
159
+ begin
160
+ input = File.open(filename, mode)
161
+ rescue Errno::ENOENT => e
162
+ puts "Cannot Open File #{filename} in #{mode} mode"
163
+ exit 1
164
+ end
165
+ else
166
+ nil
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,27 @@
1
+ module KBL
2
+ class Package
3
+ include KBL::Serializable::Package
4
+
5
+ attr_reader :kkbox_version, :version, :description, :date, :playlists
6
+
7
+ def initialize(&block)
8
+ @date = Time.now
9
+ @playlists = []
10
+ @kkbox_version = "04000016"
11
+
12
+ block.call(self) if block_given?
13
+ end
14
+
15
+ def add_playlist(&block)
16
+ playlist = Playlist.new
17
+ playlist.id = @playlists.size + 1
18
+ block.call(playlist)
19
+
20
+ @playlists << playlist
21
+ end
22
+
23
+ def total_songs
24
+ @playlists.map { |playlist| playlist.songs.size }.reduce(:+)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ module KBL
2
+ class Playlist
3
+ include KBL::Serializable::Playlist
4
+
5
+ attr_reader :songs
6
+ attr_accessor :name, :description, :id
7
+
8
+ def initialize(&block)
9
+ @songs = []
10
+
11
+ block.call(self) if block_given?
12
+ end
13
+
14
+ def add_song(&block)
15
+ song = Song.new
16
+ block.call(song)
17
+
18
+ @songs << song
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ module KBL::Serializable
2
+ module Package
3
+ def to_kbl
4
+ to_kbl_node.to_xml({
5
+ :encoding => 'UTF-8',
6
+ :indent => 2,
7
+ :indent_text => " "
8
+ }).gsub(/<(.+)\/>/, '<\1></\1>')
9
+ end
10
+
11
+ def to_kbl_node
12
+ doc = Nokogiri::XML.parse(<<-XML, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS)
13
+ <utf-8_data><kkbox_package></kkbox_package></utf-8_data>
14
+ XML
15
+
16
+ package_node = doc.css("kkbox_package").first
17
+
18
+ package_node << "<kkbox_ver>#{self.kkbox_version}</kkbox_ver>"
19
+
20
+ @playlists.each do |playlist|
21
+ package_node << playlist.to_kbl_node
22
+ end
23
+
24
+ package_node << Nokogiri::XML.parse(<<-XML, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root
25
+ <package>
26
+ <ver>1.0</ver>
27
+ <descr>包裝說明</descr>
28
+ <packdate>#{ self.date.strftime("%Y%m%d%H%M%S") }</packdate>
29
+ <playlistcnt>#{ self.playlists.size }</playlistcnt>
30
+ <songcnt>#{ self.total_songs }</songcnt>
31
+ </package>
32
+ XML
33
+
34
+ doc.root
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ require 'nokogiri'
2
+
3
+ module KBL::Serializable
4
+ module Playlist
5
+ def to_kbl_node
6
+ node = Nokogiri::XML.parse(<<-XML, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS)
7
+ <playlist>
8
+ <playlist_id>#{ self.id }</playlist_id>
9
+ <playlist_name>#{ self.name }</playlist_name>
10
+ <playlist_descr>#{ self.description }</playlist_descr>
11
+ <playlist_data></playlist_data>
12
+ </playlist>
13
+ XML
14
+
15
+ data_node = node.css("playlist_data").first
16
+
17
+ @songs.each do |song|
18
+ data_node << song.to_kbl_node
19
+ end
20
+
21
+ node.root
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ require 'nokogiri'
2
+
3
+ module KBL::Serializable
4
+ module Song
5
+ def to_kbl_node
6
+ doc = Nokogiri::XML.parse(<<-XML, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS)
7
+ <song_data>
8
+ <song_name>#{ self.name }</song_name>
9
+ <song_artist>#{ self.artist }</song_artist>
10
+ <song_album>#{ self.album }</song_album>
11
+ <song_genre>#{ self.genre }</song_genre>
12
+ <song_preference>#{ self.preference }</song_preference>
13
+ <song_playcnt>#{ self.play_count }</song_playcnt>
14
+ <song_pathname>#{ self.pathname }</song_pathname>
15
+ <song_type>#{ self.type }</song_type>
16
+ <song_lyricsexist>#{ self.has_lyrics }</song_lyricsexist>
17
+ <song_artist_id>#{ self.artist_id }</song_artist_id>
18
+ <song_album_id>#{ self.album_id }</song_album_id>
19
+ <song_song_idx>#{ self.song_index }</song_song_idx>
20
+ </song_data>
21
+ XML
22
+
23
+ doc.root
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ module KBL
2
+ class Song
3
+ include KBL::Serializable::Song
4
+
5
+ attr_accessor :name, :artist, :album, :genre, :preference,
6
+ :play_count, :pathname, :type, :has_lyrics,
7
+ :artist_id, :album_id, :song_index
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
- module Kbl
2
- VERSION = "0.0.1"
1
+ module KBL
2
+ VERSION = "0.1.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kbl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yu-Cheng Chuang
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.6.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.19.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 0.19.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 1.3.9
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.9
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: bundler
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -55,7 +83,8 @@ dependencies:
55
83
  description: Building KKBOX Playlist and serialize to KBL format.
56
84
  email:
57
85
  - ducksteven@gmail.com
58
- executables: []
86
+ executables:
87
+ - kbl
59
88
  extensions: []
60
89
  extra_rdoc_files: []
61
90
  files:
@@ -64,8 +93,16 @@ files:
64
93
  - LICENSE.txt
65
94
  - README.md
66
95
  - Rakefile
96
+ - bin/kbl
67
97
  - kbl.gemspec
68
98
  - lib/kbl.rb
99
+ - lib/kbl/cli.rb
100
+ - lib/kbl/package.rb
101
+ - lib/kbl/playlist.rb
102
+ - lib/kbl/serializable/package.rb
103
+ - lib/kbl/serializable/playlist.rb
104
+ - lib/kbl/serializable/song.rb
105
+ - lib/kbl/song.rb
69
106
  - lib/kbl/version.rb
70
107
  homepage: ''
71
108
  licenses: