kbl 0.0.1 → 0.1.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 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: