listlace 0.0.5 → 0.0.6
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.
- data/Gemfile.lock +1 -1
- data/README.md +53 -0
- data/{README → README.old} +0 -0
- data/bin/listlace +13 -8
- data/lib/listlace/array_ext.rb +41 -0
- data/lib/listlace/commands/library_commands.rb +101 -0
- data/lib/listlace/commands/player_commands.rb +192 -0
- data/lib/listlace/commands.rb +7 -0
- data/lib/listlace/library/database.rb +89 -0
- data/lib/listlace/library/selectors.rb +196 -0
- data/lib/listlace/library.rb +41 -0
- data/lib/listlace/models.rb +3 -0
- data/lib/listlace/player/mplayer.rb +53 -0
- data/lib/listlace/player.rb +19 -2
- data/lib/listlace.rb +12 -27
- data/listlace.gemspec +3 -3
- metadata +13 -11
- data/lib/listlace/commands/library.rb +0 -85
- data/lib/listlace/commands/playback.rb +0 -137
- data/lib/listlace/commands/queue.rb +0 -52
- data/lib/listlace/commands/selectors.rb +0 -186
- data/lib/listlace/commands/volume.rb +0 -0
- data/lib/listlace/database.rb +0 -74
- data/lib/listlace/mplayer.rb +0 -51
- data/lib/listlace/playlist_array.rb +0 -82
@@ -0,0 +1,196 @@
|
|
1
|
+
module Listlace
|
2
|
+
class Library
|
3
|
+
module Selectors
|
4
|
+
STRING_SELECTORS = %w(name artist composer album album_artist genre comments location)
|
5
|
+
INTEGER_SELECTORS = %w(disc_number disc_count track_number track_count year bit_rate sample_rate play_count skip_count rating)
|
6
|
+
|
7
|
+
STRING_SELECTORS.each do |column|
|
8
|
+
define_method(column) do |*args|
|
9
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
10
|
+
|
11
|
+
playlists = args.map { |query| string_selector(column, query, options) }
|
12
|
+
playlist *playlists
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
INTEGER_SELECTORS.each do |column|
|
17
|
+
define_method(column) do |*args|
|
18
|
+
playlists = args.map { |arg| integer_selector(column, arg) }
|
19
|
+
playlist *playlists
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# rename the "name" selector to "song"
|
24
|
+
alias_method :song, :name
|
25
|
+
remove_method :name
|
26
|
+
|
27
|
+
# The length selector is an integer selector for the length of a track. A
|
28
|
+
# plain integer given to it represents the number of seconds. It can also take
|
29
|
+
# a String in the format "1:23", to represent 83 seconds, for example. These
|
30
|
+
# can be part of a Range, as usual: "1:23".."2:05", for example.
|
31
|
+
def length(*args)
|
32
|
+
normalize = lambda do |value|
|
33
|
+
case value
|
34
|
+
when String
|
35
|
+
Track.parse_time(value)
|
36
|
+
when Integer
|
37
|
+
value * 1000
|
38
|
+
when Range
|
39
|
+
(normalize.(value.begin))..(normalize.(value.end))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
playlists = args.map do |arg|
|
44
|
+
if arg.is_a? Hash
|
45
|
+
key = arg.keys.first
|
46
|
+
arg[key] = normalize.(arg[key])
|
47
|
+
else
|
48
|
+
arg = normalize.(arg)
|
49
|
+
end
|
50
|
+
|
51
|
+
# If they want tracks of length "0:05", for example, we need to look for
|
52
|
+
# tracks that are from 5000 to 5999 milliseconds long.
|
53
|
+
if arg.is_a? Integer
|
54
|
+
arg = (arg)..(arg + 999)
|
55
|
+
end
|
56
|
+
|
57
|
+
integer_selector(:total_time, arg)
|
58
|
+
end
|
59
|
+
|
60
|
+
playlist *playlists
|
61
|
+
end
|
62
|
+
|
63
|
+
# Makes a playlist out of tracks that match the string query on the given
|
64
|
+
# column. It's SQL underneath, so you can use % and _ as wildcards in the
|
65
|
+
# query. By default, % wildcards are inserted on the left and right of your
|
66
|
+
# query. Use the :match option to change this:
|
67
|
+
#
|
68
|
+
# :match => :middle "%query%" (default)
|
69
|
+
# :match => :left "query%"
|
70
|
+
# :match => :right "%query"
|
71
|
+
# :match => :exact "query"
|
72
|
+
#
|
73
|
+
# This method shouldn't have to be used directly. Many convenient methods are
|
74
|
+
# generated for you, one for each string field you may want to select on.
|
75
|
+
# These are: artist, composer, album, album_artist, genre, comments, location.
|
76
|
+
# For example:
|
77
|
+
#
|
78
|
+
# artist :muse, match: :exact #=> playlist (108 tracks)
|
79
|
+
# composer :rachmanino #=> playlist (33 tracks)
|
80
|
+
#
|
81
|
+
# To match the name of a track, use song:
|
82
|
+
#
|
83
|
+
# song "frontier psychiatrist" #=> playlist (1 track)
|
84
|
+
#
|
85
|
+
def string_selector(column, query, options = {})
|
86
|
+
options[:match] ||= :middle
|
87
|
+
|
88
|
+
query = {
|
89
|
+
exact: "#{query}",
|
90
|
+
left: "#{query}%",
|
91
|
+
right: "%#{query}",
|
92
|
+
middle: "%#{query}%"
|
93
|
+
}[options[:match]]
|
94
|
+
|
95
|
+
tracks = library.tracks.arel_table
|
96
|
+
library.tracks.where(tracks[column].matches(query)).all
|
97
|
+
end
|
98
|
+
|
99
|
+
# Makes a playlist out of tracks that satisfy certain conditions on the given
|
100
|
+
# integer column. You can pass an exact value to check for equality, a range,
|
101
|
+
# or a hash that specifies greater-than and less-than options like this:
|
102
|
+
#
|
103
|
+
# integer_selector :year, greater_than: 2000 #=> playlist (3555 tracks)
|
104
|
+
#
|
105
|
+
# The possible operators, with their shortcuts, are:
|
106
|
+
#
|
107
|
+
# :greater_than / :gt
|
108
|
+
# :less_than / :lt
|
109
|
+
# :greater_than_or_equal / :gte
|
110
|
+
# :less_than_or_equal / :lte
|
111
|
+
# :not_equal / :ne
|
112
|
+
#
|
113
|
+
# Note: You can only use one of these operators at a time. If you want a
|
114
|
+
# range, use a Range.
|
115
|
+
#
|
116
|
+
# This method shouldn't have to be used directly. Many convenient methods are
|
117
|
+
# generated for you, one for each integer field you may want to select on.
|
118
|
+
# These are: disc_number, disc_count, track_number, track_count, year,
|
119
|
+
# bit_rate, sample_rate, play_count, skip_count, rating, length. Length is
|
120
|
+
# special, it can take any of the time formats that the seek command can. For
|
121
|
+
# example:
|
122
|
+
#
|
123
|
+
# year 2010..2012 #=> playlist (1060 tracks)
|
124
|
+
# length gt: "4:00" #=> playlist (2543 tracks)
|
125
|
+
#
|
126
|
+
def integer_selector(column, value_or_options)
|
127
|
+
if value_or_options.is_a? Hash
|
128
|
+
operator = {
|
129
|
+
greater_than: ">",
|
130
|
+
gt: ">",
|
131
|
+
less_than: "<",
|
132
|
+
lt: "<",
|
133
|
+
greater_than_or_equal: ">=",
|
134
|
+
gte: ">=",
|
135
|
+
less_than_or_equal: "<=",
|
136
|
+
lte: "<=",
|
137
|
+
not_equal: "<>",
|
138
|
+
ne: "<>"
|
139
|
+
}[value_or_options.keys.first]
|
140
|
+
library.tracks.where("tracks.#{column} #{operator} ?", value_or_options.values.first).all
|
141
|
+
else
|
142
|
+
library.tracks.where(column => value_or_options).all
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Creates or looks up a playlist. You can pass any number of multiple types of
|
147
|
+
# objects to make a playlist:
|
148
|
+
#
|
149
|
+
# Track: Makes a playlist with one track
|
150
|
+
# ActiveRecord::Relation: Makes a playlist out of the resulting Track or Playlist records
|
151
|
+
# Symbol or String: Tries to retrieve a saved playlist with the given name.
|
152
|
+
# If it can't find one, it creates a new one.
|
153
|
+
#
|
154
|
+
# You can also pass in the results of a selector. In this way you can create a
|
155
|
+
# playlist out of many smaller pieces.
|
156
|
+
#
|
157
|
+
# If given no arguments, it returns a blank playlist.
|
158
|
+
#
|
159
|
+
def playlist(*args)
|
160
|
+
args.map do |object|
|
161
|
+
case object
|
162
|
+
when Track
|
163
|
+
[object]
|
164
|
+
when Playlist
|
165
|
+
pl = [object.tracks.all]
|
166
|
+
pl.name = object.name
|
167
|
+
pl
|
168
|
+
when Array
|
169
|
+
if object.playlist?
|
170
|
+
object
|
171
|
+
else
|
172
|
+
playlist *object
|
173
|
+
end
|
174
|
+
when ActiveRecord::Relation
|
175
|
+
if object.table.name == "tracks"
|
176
|
+
object.all
|
177
|
+
elsif object.table.name == "playlists"
|
178
|
+
playlist *object.all
|
179
|
+
end
|
180
|
+
when Symbol, String
|
181
|
+
playlists = library.playlists.arel_table
|
182
|
+
if playlist = library.playlists.where(playlists[:name].matches(object.to_s)).first
|
183
|
+
pl = playlist.tracks.all
|
184
|
+
pl.name = playlist.name
|
185
|
+
pl
|
186
|
+
else
|
187
|
+
pl = []
|
188
|
+
pl.name = object
|
189
|
+
pl
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end.inject(:+)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "listlace/library/database"
|
2
|
+
require "listlace/library/selectors"
|
3
|
+
|
4
|
+
module Listlace
|
5
|
+
class Library
|
6
|
+
def initialize(options = {})
|
7
|
+
options[:db_path] ||= "library"
|
8
|
+
options[:db_adapter] ||= "sqlite3"
|
9
|
+
|
10
|
+
unless File.exists? Listlace::DIR
|
11
|
+
FileUtils.mkdir_p Listlace::DIR
|
12
|
+
end
|
13
|
+
|
14
|
+
@db_path = options[:db_path]
|
15
|
+
@db_path = "#{Listlace::DIR}/#{@db_path}" unless @db_path.include? "/"
|
16
|
+
@db_path = "#{@db_path}.sqlite3" unless @db_path =~ /\.sqlite3$/
|
17
|
+
|
18
|
+
@db_adapter = options[:db_adapter]
|
19
|
+
|
20
|
+
Database.disconnect if Database.connected?
|
21
|
+
Database.connect(@db_adapter, @db_path)
|
22
|
+
Database.generate_schema unless Database.exists?(@db_path)
|
23
|
+
end
|
24
|
+
|
25
|
+
def tracks
|
26
|
+
Track.scoped
|
27
|
+
end
|
28
|
+
|
29
|
+
def playlists
|
30
|
+
Playlist.scoped
|
31
|
+
end
|
32
|
+
|
33
|
+
def size
|
34
|
+
tracks.length
|
35
|
+
end
|
36
|
+
|
37
|
+
def wipe
|
38
|
+
Database.wipe(@db_adapter, @db_path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Listlace
|
2
|
+
class Player
|
3
|
+
# This is a simple MPlayer wrapper, it just handles opening the MPlayer
|
4
|
+
# process, hooking into when mplayer exits (when the song is done), and
|
5
|
+
# issuing commands through the slave protocol.
|
6
|
+
class MPlayer
|
7
|
+
def initialize(track, &on_quit)
|
8
|
+
cmd = "/usr/bin/mplayer -slave -quiet #{Shellwords.shellescape(track.location)}"
|
9
|
+
@pid, @stdin, @stdout, @stderr = Open4.popen4(cmd)
|
10
|
+
@paused = false
|
11
|
+
@extra_lines = 0
|
12
|
+
|
13
|
+
until @stdout.gets["playback"]
|
14
|
+
end
|
15
|
+
|
16
|
+
@quit_hook_active = false
|
17
|
+
@quit_hook = Thread.new do
|
18
|
+
Process.wait(@pid)
|
19
|
+
@quit_hook_active = true
|
20
|
+
on_quit.call
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def command(cmd, options = {})
|
25
|
+
if cmd == "pause"
|
26
|
+
@paused = !@paused
|
27
|
+
elsif @paused
|
28
|
+
cmd = "pausing #{cmd}"
|
29
|
+
end
|
30
|
+
|
31
|
+
@stdin.puts cmd
|
32
|
+
|
33
|
+
if options[:expect_answer]
|
34
|
+
answer = "\n"
|
35
|
+
answer = @stdout.gets.sub("\e[A\r\e[K", "") while answer == "\n"
|
36
|
+
answer
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def quit
|
41
|
+
@quit_hook.kill unless @quit_hook_active
|
42
|
+
command "quit" if alive?
|
43
|
+
end
|
44
|
+
|
45
|
+
def alive?
|
46
|
+
Process.getpgid(@pid)
|
47
|
+
true
|
48
|
+
rescue Errno::ESRCH
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/listlace/player.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "listlace/player/mplayer"
|
2
|
+
|
1
3
|
module Listlace
|
2
4
|
# This is the music box. It contains a queue, which is an array of tracks. It
|
3
5
|
# then plays these tracks sequentially. The buttons for play, pause, next,
|
@@ -7,7 +9,8 @@ module Listlace
|
|
7
9
|
|
8
10
|
def initialize
|
9
11
|
@mplayer = nil
|
10
|
-
@queue =
|
12
|
+
@queue = []
|
13
|
+
@queue.name = :queue
|
11
14
|
@current_track = nil
|
12
15
|
@current_track_index = nil
|
13
16
|
@paused = false
|
@@ -16,13 +19,21 @@ module Listlace
|
|
16
19
|
end
|
17
20
|
|
18
21
|
def queue(playlist = nil)
|
19
|
-
|
22
|
+
if playlist.is_a? Array
|
23
|
+
if @queue.empty? && playlist.name && !playlist.name.empty?
|
24
|
+
@queue = playlist.dup
|
25
|
+
else
|
26
|
+
@queue += playlist
|
27
|
+
@queue.name = :queue
|
28
|
+
end
|
29
|
+
end
|
20
30
|
@queue.dup
|
21
31
|
end
|
22
32
|
|
23
33
|
def clear
|
24
34
|
stop
|
25
35
|
@queue.clear
|
36
|
+
@queue.name = :queue
|
26
37
|
end
|
27
38
|
|
28
39
|
def empty?
|
@@ -93,6 +104,8 @@ module Listlace
|
|
93
104
|
end
|
94
105
|
|
95
106
|
def skip(n = 1)
|
107
|
+
@current_track.increment! :skip_count
|
108
|
+
@current_track.update_column :skip_date, Time.now
|
96
109
|
change_track(n)
|
97
110
|
end
|
98
111
|
|
@@ -159,6 +172,10 @@ module Listlace
|
|
159
172
|
private
|
160
173
|
|
161
174
|
def change_track(by = 1, options = {})
|
175
|
+
if options[:auto]
|
176
|
+
@current_track.increment! :play_count
|
177
|
+
@current_track.update_column :play_date_utc, Time.now
|
178
|
+
end
|
162
179
|
@current_track_index += by
|
163
180
|
if options[:auto] && @repeat_mode
|
164
181
|
case @repeat_mode
|
data/lib/listlace.rb
CHANGED
@@ -1,37 +1,22 @@
|
|
1
|
-
module Listlace
|
2
|
-
extend self
|
3
|
-
|
4
|
-
DIR = ENV["LISTLACE_DIR"] || (ENV["HOME"] + "/.listlace")
|
5
|
-
PROMPT = [proc { ">> " }, proc { " | " }]
|
6
|
-
PRINT = proc do |output, value|
|
7
|
-
unless value.nil?
|
8
|
-
Pry::DEFAULT_PRINT.call(output, value)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
1
|
require "open4"
|
14
2
|
require "shellwords"
|
15
3
|
require "active_record"
|
16
4
|
require "fileutils"
|
5
|
+
require "plist"
|
6
|
+
require "active_support/core_ext/string"
|
17
7
|
|
18
|
-
require "listlace/
|
8
|
+
require "listlace/array_ext"
|
9
|
+
require "listlace/library"
|
19
10
|
require "listlace/player"
|
20
|
-
require "listlace/
|
21
|
-
require "listlace/
|
22
|
-
|
23
|
-
require "listlace/models/track"
|
24
|
-
require "listlace/models/playlist"
|
25
|
-
require "listlace/models/playlist_item"
|
11
|
+
require "listlace/commands"
|
12
|
+
require "listlace/models"
|
26
13
|
|
27
|
-
|
28
|
-
|
29
|
-
require "listlace/commands/selectors"
|
30
|
-
require "listlace/commands/volume"
|
31
|
-
require "listlace/commands/queue"
|
14
|
+
module Listlace
|
15
|
+
extend Listlace::Library::Selectors
|
32
16
|
|
33
|
-
|
17
|
+
class << self
|
18
|
+
attr_accessor :library, :player
|
19
|
+
end
|
34
20
|
|
35
|
-
|
36
|
-
Listlace.stop
|
21
|
+
DIR = ENV["LISTLACE_DIR"] || (ENV["HOME"] + "/.listlace")
|
37
22
|
end
|
data/listlace.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "listlace"
|
3
|
-
s.version = "0.0.
|
4
|
-
s.date = "2012-08-
|
3
|
+
s.version = "0.0.6"
|
4
|
+
s.date = "2012-08-31"
|
5
5
|
s.summary = "A music player in a REPL."
|
6
6
|
s.description = "Listlace is a music player which is interacted with through a Ruby REPL."
|
7
7
|
s.author = "Jeremy Ruten"
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.requirements << "mplayer"
|
13
13
|
s.executables << "listlace"
|
14
14
|
|
15
|
-
s.files = ["Gemfile", "Gemfile.lock", "LICENSE", "listlace.gemspec", "README"]
|
15
|
+
s.files = ["Gemfile", "Gemfile.lock", "LICENSE", "listlace.gemspec", "README.md", "README.old"]
|
16
16
|
s.files += ["bin/listlace"]
|
17
17
|
s.files += Dir["lib/**/*.rb"]
|
18
18
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: listlace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pry
|
@@ -134,20 +134,22 @@ files:
|
|
134
134
|
- Gemfile.lock
|
135
135
|
- LICENSE
|
136
136
|
- listlace.gemspec
|
137
|
-
- README
|
137
|
+
- README.md
|
138
|
+
- README.old
|
138
139
|
- bin/listlace
|
139
|
-
- lib/listlace/
|
140
|
-
- lib/listlace/commands/
|
141
|
-
- lib/listlace/commands/
|
142
|
-
- lib/listlace/commands
|
143
|
-
- lib/listlace/
|
144
|
-
- lib/listlace/
|
140
|
+
- lib/listlace/array_ext.rb
|
141
|
+
- lib/listlace/commands/library_commands.rb
|
142
|
+
- lib/listlace/commands/player_commands.rb
|
143
|
+
- lib/listlace/commands.rb
|
144
|
+
- lib/listlace/library/database.rb
|
145
|
+
- lib/listlace/library/selectors.rb
|
146
|
+
- lib/listlace/library.rb
|
145
147
|
- lib/listlace/models/playlist.rb
|
146
148
|
- lib/listlace/models/playlist_item.rb
|
147
149
|
- lib/listlace/models/track.rb
|
148
|
-
- lib/listlace/
|
150
|
+
- lib/listlace/models.rb
|
151
|
+
- lib/listlace/player/mplayer.rb
|
149
152
|
- lib/listlace/player.rb
|
150
|
-
- lib/listlace/playlist_array.rb
|
151
153
|
- lib/listlace.rb
|
152
154
|
homepage: http://github.com/yjerem/listlace
|
153
155
|
licenses:
|
@@ -1,85 +0,0 @@
|
|
1
|
-
require "plist"
|
2
|
-
require "active_support/core_ext/string"
|
3
|
-
|
4
|
-
module Listlace
|
5
|
-
# Imports the music library from another program. Currently only iTunes is
|
6
|
-
# supported.
|
7
|
-
def import(from, path)
|
8
|
-
if not File.exists?(path)
|
9
|
-
puts "File '%s' doesn't exist." % [path]
|
10
|
-
elsif from == :itunes
|
11
|
-
puts "Parsing XML..."
|
12
|
-
data = Plist::parse_xml(path)
|
13
|
-
|
14
|
-
puts "Importing #{data['Tracks'].length} tracks..."
|
15
|
-
num_tracks = 0
|
16
|
-
whitelist = Track.new.attributes.keys
|
17
|
-
data["Tracks"].each do |track_id, row|
|
18
|
-
# row already contains a hash of attributes almost ready to be passed to
|
19
|
-
# ActiveRecord. We just need to modify the keys, e.g. change "Play Count"
|
20
|
-
# to "play_count".
|
21
|
-
attributes = row.inject({}) do |acc, (key, value)|
|
22
|
-
attribute = key.gsub(" ", "").underscore
|
23
|
-
attribute = "original_id" if attribute == "track_id"
|
24
|
-
acc[attribute] = value if whitelist.include? attribute
|
25
|
-
acc
|
26
|
-
end
|
27
|
-
|
28
|
-
# change iTunes' URL-style locations into simple paths
|
29
|
-
if attributes["location"] && attributes["location"] =~ /^file:\/\//
|
30
|
-
attributes["location"].sub! /^file:\/\/localhost/, ""
|
31
|
-
|
32
|
-
# CGI::unescape changes plus signs to spaces. This is a work around to
|
33
|
-
# keep the plus signs.
|
34
|
-
attributes["location"].gsub! "+", "%2B"
|
35
|
-
|
36
|
-
attributes["location"] = CGI::unescape(attributes["location"])
|
37
|
-
end
|
38
|
-
|
39
|
-
track = Track.new(attributes)
|
40
|
-
|
41
|
-
if track.kind =~ /audio/
|
42
|
-
if track.save
|
43
|
-
num_tracks += 1
|
44
|
-
end
|
45
|
-
else
|
46
|
-
puts "[skipping non-audio file]"
|
47
|
-
end
|
48
|
-
end
|
49
|
-
puts "Imported #{num_tracks} tracks successfully."
|
50
|
-
|
51
|
-
puts "Importing #{data['Playlists'].length} playlists..."
|
52
|
-
num_playlists = 0
|
53
|
-
data["Playlists"].each do |playlist_data|
|
54
|
-
playlist = Playlist.new(name: playlist_data["Name"])
|
55
|
-
|
56
|
-
if ["Library", "Music", "Movies", "TV Shows", "iTunes DJ"].include? playlist.name
|
57
|
-
puts "[skipping \"#{playlist.name}\" playlist]"
|
58
|
-
else
|
59
|
-
if playlist.save
|
60
|
-
playlist_data["Playlist Items"].map(&:values).flatten.each.with_index do |track_id, i|
|
61
|
-
playlist_item = PlaylistItem.new(position: i)
|
62
|
-
playlist_item.playlist = playlist
|
63
|
-
if playlist_item.track = Track.where(original_id: track_id).first
|
64
|
-
playlist_item.save!
|
65
|
-
end
|
66
|
-
end
|
67
|
-
num_playlists += 1
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
puts "Imported #{num_playlists} playlists successfully."
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# Wipes the database. With no arguments, it just asks "Are you sure?" without
|
76
|
-
# doing anything. To actually wipe the database, pass :yes_im_sure.
|
77
|
-
def wipe_library(are_you_sure = :nope)
|
78
|
-
if are_you_sure == :yes_im_sure
|
79
|
-
Database.wipe
|
80
|
-
puts "Library wiped."
|
81
|
-
else
|
82
|
-
puts "Are you sure? If you are, then type: wipe_library :yes_im_sure"
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
@@ -1,137 +0,0 @@
|
|
1
|
-
module Listlace
|
2
|
-
REPEAT_SYMBOL = "\u221E"
|
3
|
-
TIMES_SYMBOL = "\u00D7"
|
4
|
-
|
5
|
-
# The play command. With no arguments, it either resumes playback or starts
|
6
|
-
# playing the queue. With arguments, it replaces the queue with the given
|
7
|
-
# tracks and starts playing.
|
8
|
-
def p(*tracks)
|
9
|
-
if tracks.empty?
|
10
|
-
if $player.paused?
|
11
|
-
$player.resume
|
12
|
-
status
|
13
|
-
elsif $player.started?
|
14
|
-
if $player.speed == 1
|
15
|
-
$player.pause
|
16
|
-
status
|
17
|
-
else
|
18
|
-
$player.set_speed 1
|
19
|
-
status
|
20
|
-
end
|
21
|
-
else
|
22
|
-
if $player.empty?
|
23
|
-
puts "Nothing to play."
|
24
|
-
else
|
25
|
-
$player.start
|
26
|
-
status
|
27
|
-
end
|
28
|
-
end
|
29
|
-
else
|
30
|
-
$player.stop
|
31
|
-
$player.clear
|
32
|
-
q *tracks
|
33
|
-
p
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# Stops playback. The queue is still intact, but goes back to the beginning
|
38
|
-
# when playback is started again.
|
39
|
-
def stop
|
40
|
-
$player.stop
|
41
|
-
puts "Stopped."
|
42
|
-
end
|
43
|
-
|
44
|
-
# Start the current track from the beginning.
|
45
|
-
def restart
|
46
|
-
$player.restart
|
47
|
-
status
|
48
|
-
end
|
49
|
-
|
50
|
-
# Go back one song in the queue.
|
51
|
-
def back(n = 1)
|
52
|
-
if $player.back(n)
|
53
|
-
status
|
54
|
-
else
|
55
|
-
puts "End of queue."
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# Go directly to the next song in the queue.
|
60
|
-
def skip(n = 1)
|
61
|
-
if $player.skip(n)
|
62
|
-
status
|
63
|
-
else
|
64
|
-
puts "End of queue."
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
# Seek to a particular position in the current track. If given an integer, it
|
69
|
-
# will seek that many seconds forward or backward. If given a Range, it will
|
70
|
-
# seek to that specific time, the first number in the Range representing the
|
71
|
-
# minutes, the second number representing the seconds. You can also pass a
|
72
|
-
# String like "1:23:45" to do the same thing. To seek to an absolute time in
|
73
|
-
# seconds, do it like "seek(abs: 40)". To seek to a percentage, do something
|
74
|
-
# like "seek(percent: 75)".
|
75
|
-
def seek(where)
|
76
|
-
$player.seek(where)
|
77
|
-
status
|
78
|
-
end
|
79
|
-
|
80
|
-
# Fast-forward at a particular speed. Induces the chipmunk effect, which I
|
81
|
-
# find agreeable. Call p to go back to normal. You can also pass a value
|
82
|
-
# smaller than one to slow down.
|
83
|
-
def ff(speed = 2)
|
84
|
-
$player.set_speed(speed)
|
85
|
-
status
|
86
|
-
end
|
87
|
-
|
88
|
-
# Pass :all to start playing from the top of the queue when it gets to the
|
89
|
-
# end. Pass :one to repeat the current track.
|
90
|
-
def repeat(one_or_all = :all)
|
91
|
-
$player.repeat one_or_all
|
92
|
-
status
|
93
|
-
end
|
94
|
-
|
95
|
-
# Turn off the repeat mode set by the repeat command.
|
96
|
-
def norepeat
|
97
|
-
$player.repeat :off
|
98
|
-
status
|
99
|
-
end
|
100
|
-
|
101
|
-
# Show various information about the status of the player. The information it
|
102
|
-
# shows depends on what status types you pass:
|
103
|
-
#
|
104
|
-
# :playlist - Shows the playlist that is currently playing
|
105
|
-
# :playing - Shows the current track
|
106
|
-
#
|
107
|
-
def status(*types)
|
108
|
-
types = [:playlist, :playing] if types.empty?
|
109
|
-
types.each do |type|
|
110
|
-
case type
|
111
|
-
when :playlist
|
112
|
-
if $player.started?
|
113
|
-
track_number = $player.current_track_index + 1
|
114
|
-
num_tracks = q.length
|
115
|
-
repeat_one = $player.repeat_mode == :one ? REPEAT_SYMBOL : ""
|
116
|
-
repeat_all = $player.repeat_mode == :all ? REPEAT_SYMBOL : ""
|
117
|
-
puts "Playlist: %s (%d%s / %d%s)" % [q.name, track_number, repeat_one, num_tracks, repeat_all]
|
118
|
-
else
|
119
|
-
puts "Playlist: %s" % [q]
|
120
|
-
end
|
121
|
-
when :playing
|
122
|
-
if $player.started?
|
123
|
-
name = $player.current_track.name
|
124
|
-
artist = $player.current_track.artist
|
125
|
-
time = $player.formatted_current_time
|
126
|
-
total_time = $player.current_track.formatted_total_time
|
127
|
-
paused = $player.paused? ? "|| " : ""
|
128
|
-
speed = $player.speed != 1 ? "#{TIMES_SYMBOL}#{$player.speed} " : ""
|
129
|
-
puts "%s - %s (%s / %s) %s%s" % [name, artist, time, total_time, paused, speed]
|
130
|
-
else
|
131
|
-
puts "Stopped."
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
nil
|
136
|
-
end
|
137
|
-
end
|