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
@@ -1,52 +0,0 @@
|
|
1
|
-
module Listlace
|
2
|
-
# The queue command. Simply appends tracks to the queue. It creates a playlist
|
3
|
-
# with the arguments you give it, so anything you can pass to the playlist()
|
4
|
-
# method you can pass to this. It returns the queue, so you can use this
|
5
|
-
# method as an accessor by not passing any arguments.
|
6
|
-
def q(*args)
|
7
|
-
$player.queue playlist(*args)
|
8
|
-
$player.queue
|
9
|
-
end
|
10
|
-
|
11
|
-
# Clears the queue.
|
12
|
-
def clear
|
13
|
-
$player.clear
|
14
|
-
puts "Queue cleared."
|
15
|
-
end
|
16
|
-
|
17
|
-
# Shuffles the queue, keeping the current track at the top.
|
18
|
-
def shuffle
|
19
|
-
$player.shuffle
|
20
|
-
puts "Shuffled."
|
21
|
-
end
|
22
|
-
|
23
|
-
# Sorts the queue by a list of fields and directions in the form of a symbol,
|
24
|
-
# or uses the proc given to it, which should take two Tracks and return -1, 0,
|
25
|
-
# or 1.
|
26
|
-
def sort(by = :artist_asc_album_asc_track_number_asc, &proc)
|
27
|
-
if proc
|
28
|
-
$player.sort(&proc)
|
29
|
-
else
|
30
|
-
$player.sort do |a, b|
|
31
|
-
result = 0
|
32
|
-
by.to_s.scan(/([a-z_]+?)_(asc|desc)(?:_|$)/).each do |column, direction|
|
33
|
-
a_value = a.send(column)
|
34
|
-
b_value = b.send(column)
|
35
|
-
a_value = a_value.downcase if a_value.respond_to? :downcase
|
36
|
-
b_value = b_value.downcase if b_value.respond_to? :downcase
|
37
|
-
dir = (direction == "desc") ? -1 : 1
|
38
|
-
if a_value != b_value
|
39
|
-
if a_value.nil? || b_value.nil?
|
40
|
-
result = dir
|
41
|
-
else
|
42
|
-
result = (a_value <=> b_value) * dir
|
43
|
-
end
|
44
|
-
break
|
45
|
-
end
|
46
|
-
end
|
47
|
-
result
|
48
|
-
end
|
49
|
-
end
|
50
|
-
puts "Sorted."
|
51
|
-
end
|
52
|
-
end
|
@@ -1,186 +0,0 @@
|
|
1
|
-
module Listlace
|
2
|
-
STRING_SELECTORS = %w(name artist composer album album_artist genre comments location)
|
3
|
-
INTEGER_SELECTORS = %w(disc_number disc_count track_number track_count year bit_rate sample_rate play_count skip_count rating)
|
4
|
-
|
5
|
-
STRING_SELECTORS.each do |column|
|
6
|
-
define_method(column) do |*args|
|
7
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
8
|
-
|
9
|
-
playlists = args.map { |query| string_selector(column, query, options) }
|
10
|
-
playlist *playlists
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
INTEGER_SELECTORS.each do |column|
|
15
|
-
define_method(column) do |*args|
|
16
|
-
playlists = args.map { |arg| integer_selector(column, arg) }
|
17
|
-
playlist *playlists
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
# rename the "name" selector to "song"
|
22
|
-
alias_method :song, :name
|
23
|
-
remove_method :name
|
24
|
-
|
25
|
-
# The length selector is an integer selector for the length of a track. A
|
26
|
-
# plain integer given to it represents the number of seconds. It can also take
|
27
|
-
# a String in the format "1:23", to represent 83 seconds, for example. These
|
28
|
-
# can be part of a Range, as usual: "1:23".."2:05", for example.
|
29
|
-
def length(*args)
|
30
|
-
normalize = lambda do |value|
|
31
|
-
case value
|
32
|
-
when String
|
33
|
-
Track.parse_time(value)
|
34
|
-
when Integer
|
35
|
-
value * 1000
|
36
|
-
when Range
|
37
|
-
(normalize.(value.begin))..(normalize.(value.end))
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
playlists = args.map do |arg|
|
42
|
-
if arg.is_a? Hash
|
43
|
-
key = arg.keys.first
|
44
|
-
arg[key] = normalize.(arg[key])
|
45
|
-
else
|
46
|
-
arg = normalize.(arg)
|
47
|
-
end
|
48
|
-
|
49
|
-
# If they want tracks of length "0:05", for example, we need to look for
|
50
|
-
# tracks that are from 5000 to 5999 milliseconds long.
|
51
|
-
if arg.is_a? Integer
|
52
|
-
arg = (arg)..(arg + 999)
|
53
|
-
end
|
54
|
-
|
55
|
-
integer_selector(:total_time, arg)
|
56
|
-
end
|
57
|
-
|
58
|
-
playlist *playlists
|
59
|
-
end
|
60
|
-
|
61
|
-
# Makes a playlist out of tracks that match the string query on the given
|
62
|
-
# column. It's SQL underneath, so you can use % and _ as wildcards in the
|
63
|
-
# query. By default, % wildcards are inserted on the left and right of your
|
64
|
-
# query. Use the :match option to change this:
|
65
|
-
#
|
66
|
-
# :match => :middle "%query%" (default)
|
67
|
-
# :match => :left "query%"
|
68
|
-
# :match => :right "%query"
|
69
|
-
# :match => :exact "query"
|
70
|
-
#
|
71
|
-
# This method shouldn't have to be used directly. Many convenient methods are
|
72
|
-
# generated for you, one for each string field you may want to select on.
|
73
|
-
# These are: artist, composer, album, album_artist, genre, comments, location.
|
74
|
-
# For example:
|
75
|
-
#
|
76
|
-
# artist :muse, match: :exact #=> playlist (108 tracks)
|
77
|
-
# composer :rachmanino #=> playlist (33 tracks)
|
78
|
-
#
|
79
|
-
# To match the name of a track, use song:
|
80
|
-
#
|
81
|
-
# song "frontier psychiatrist" #=> playlist (1 track)
|
82
|
-
#
|
83
|
-
def string_selector(column, query, options = {})
|
84
|
-
options[:match] ||= :middle
|
85
|
-
|
86
|
-
query = {
|
87
|
-
exact: "#{query}",
|
88
|
-
left: "#{query}%",
|
89
|
-
right: "%#{query}",
|
90
|
-
middle: "%#{query}%"
|
91
|
-
}[options[:match]]
|
92
|
-
|
93
|
-
tracks = Track.arel_table
|
94
|
-
PlaylistArray.new(Track.where(tracks[column].matches(query)).all)
|
95
|
-
end
|
96
|
-
|
97
|
-
# Makes a playlist out of tracks that satisfy certain conditions on the given
|
98
|
-
# integer column. You can pass an exact value to check for equality, a range,
|
99
|
-
# or a hash that specifies greater-than and less-than options like this:
|
100
|
-
#
|
101
|
-
# integer_selector :year, greater_than: 2000 #=> playlist (3555 tracks)
|
102
|
-
#
|
103
|
-
# The possible operators, with their shortcuts, are:
|
104
|
-
#
|
105
|
-
# :greater_than / :gt
|
106
|
-
# :less_than / :lt
|
107
|
-
# :greater_than_or_equal / :gte
|
108
|
-
# :less_than_or_equal / :lte
|
109
|
-
# :not_equal / :ne
|
110
|
-
#
|
111
|
-
# Note: You can only use one of these operators at a time. If you want a
|
112
|
-
# range, use a Range.
|
113
|
-
#
|
114
|
-
# This method shouldn't have to be used directly. Many convenient methods are
|
115
|
-
# generated for you, one for each integer field you may want to select on.
|
116
|
-
# These are: disc_number, disc_count, track_number, track_count, year,
|
117
|
-
# bit_rate, sample_rate, play_count, skip_count, rating, length. Length is
|
118
|
-
# special, it can take any of the time formats that the seek command can. For
|
119
|
-
# example:
|
120
|
-
#
|
121
|
-
# year 2010..2012 #=> playlist (1060 tracks)
|
122
|
-
# length gt: "4:00" #=> playlist (2543 tracks)
|
123
|
-
#
|
124
|
-
def integer_selector(column, value_or_options)
|
125
|
-
if value_or_options.is_a? Hash
|
126
|
-
operator = {
|
127
|
-
greater_than: ">",
|
128
|
-
gt: ">",
|
129
|
-
less_than: "<",
|
130
|
-
lt: "<",
|
131
|
-
greater_than_or_equal: ">=",
|
132
|
-
gte: ">=",
|
133
|
-
less_than_or_equal: "<=",
|
134
|
-
lte: "<=",
|
135
|
-
not_equal: "<>",
|
136
|
-
ne: "<>"
|
137
|
-
}[value_or_options.keys.first]
|
138
|
-
tracks = Track.where("tracks.#{column} #{operator} ?", value_or_options.values.first)
|
139
|
-
PlaylistArray.new(tracks.all)
|
140
|
-
else
|
141
|
-
tracks = Track.arel_table
|
142
|
-
PlaylistArray.new(Track.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
|
-
PlaylistArray.new([object])
|
164
|
-
when Playlist
|
165
|
-
PlaylistArray.new([object.tracks.all], object.name)
|
166
|
-
when PlaylistArray
|
167
|
-
object
|
168
|
-
when Array
|
169
|
-
playlist *object
|
170
|
-
when ActiveRecord::Relation
|
171
|
-
if object.table.name == "tracks"
|
172
|
-
PlaylistArray.new([object.all])
|
173
|
-
elsif object.table.name == "playlists"
|
174
|
-
playlist *object.all
|
175
|
-
end
|
176
|
-
when Symbol, String
|
177
|
-
playlists = Playlist.arel_table
|
178
|
-
if playlist = Playlist.where(playlists[:name].matches(object.to_s)).first
|
179
|
-
PlaylistArray.new(playlist)
|
180
|
-
else
|
181
|
-
PlaylistArray.new([], object)
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end.inject(:+)
|
185
|
-
end
|
186
|
-
end
|
File without changes
|
data/lib/listlace/database.rb
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
module Listlace
|
2
|
-
module Database
|
3
|
-
extend self
|
4
|
-
|
5
|
-
ADAPTER = "sqlite3"
|
6
|
-
PATH = "#{Listlace::DIR}/library.sqlite3"
|
7
|
-
|
8
|
-
def connect
|
9
|
-
ActiveRecord::Base.establish_connection(adapter: ADAPTER, database: PATH)
|
10
|
-
end
|
11
|
-
|
12
|
-
def exists?
|
13
|
-
File.exists? PATH
|
14
|
-
end
|
15
|
-
|
16
|
-
def delete
|
17
|
-
FileUtils.rm PATH
|
18
|
-
end
|
19
|
-
|
20
|
-
def wipe
|
21
|
-
delete
|
22
|
-
connect
|
23
|
-
generate_schema
|
24
|
-
end
|
25
|
-
|
26
|
-
def generate_schema
|
27
|
-
ActiveRecord::Schema.define do
|
28
|
-
create_table :tracks do |t|
|
29
|
-
t.integer :original_id
|
30
|
-
t.string :name
|
31
|
-
t.string :artist
|
32
|
-
t.string :composer
|
33
|
-
t.string :album
|
34
|
-
t.string :album_artist
|
35
|
-
t.string :genre
|
36
|
-
t.string :kind
|
37
|
-
t.integer :size
|
38
|
-
t.integer :total_time
|
39
|
-
t.integer :disc_number
|
40
|
-
t.integer :disc_count
|
41
|
-
t.integer :track_number
|
42
|
-
t.integer :track_count
|
43
|
-
t.integer :year
|
44
|
-
t.datetime :date_modified
|
45
|
-
t.datetime :date_added
|
46
|
-
t.integer :bit_rate
|
47
|
-
t.integer :sample_rate
|
48
|
-
t.text :comments
|
49
|
-
t.integer :play_count
|
50
|
-
t.integer :play_date
|
51
|
-
t.datetime :play_date_utc
|
52
|
-
t.integer :skip_count
|
53
|
-
t.datetime :skip_date
|
54
|
-
t.integer :rating
|
55
|
-
t.integer :album_rating
|
56
|
-
t.boolean :album_rating_computed
|
57
|
-
t.string :location
|
58
|
-
end
|
59
|
-
|
60
|
-
create_table :playlists do |t|
|
61
|
-
t.string :name
|
62
|
-
t.datetime :created_at
|
63
|
-
t.datetime :updated_at
|
64
|
-
end
|
65
|
-
|
66
|
-
create_table :playlist_items do |t|
|
67
|
-
t.references :playlist, null: false
|
68
|
-
t.references :track, null: false
|
69
|
-
t.integer :position
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
data/lib/listlace/mplayer.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
module Listlace
|
2
|
-
# This is a simple MPlayer wrapper, it just handles opening the MPlayer
|
3
|
-
# process, hooking into when mplayer exits (when the song is done), and
|
4
|
-
# issuing commands through the slave protocol.
|
5
|
-
class MPlayer
|
6
|
-
def initialize(track, &on_quit)
|
7
|
-
cmd = "/usr/bin/mplayer -slave -quiet #{Shellwords.shellescape(track.location)}"
|
8
|
-
@pid, @stdin, @stdout, @stderr = Open4.popen4(cmd)
|
9
|
-
@paused = false
|
10
|
-
@extra_lines = 0
|
11
|
-
|
12
|
-
until @stdout.gets["playback"]
|
13
|
-
end
|
14
|
-
|
15
|
-
@quit_hook_active = false
|
16
|
-
@quit_hook = Thread.new do
|
17
|
-
Process.wait(@pid)
|
18
|
-
@quit_hook_active = true
|
19
|
-
on_quit.call
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def command(cmd, options = {})
|
24
|
-
if cmd == "pause"
|
25
|
-
@paused = !@paused
|
26
|
-
elsif @paused
|
27
|
-
cmd = "pausing #{cmd}"
|
28
|
-
end
|
29
|
-
|
30
|
-
@stdin.puts cmd
|
31
|
-
|
32
|
-
if options[:expect_answer]
|
33
|
-
answer = "\n"
|
34
|
-
answer = @stdout.gets.sub("\e[A\r\e[K", "") while answer == "\n"
|
35
|
-
answer
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def quit
|
40
|
-
@quit_hook.kill unless @quit_hook_active
|
41
|
-
command "quit" if alive?
|
42
|
-
end
|
43
|
-
|
44
|
-
def alive?
|
45
|
-
Process.getpgid(@pid)
|
46
|
-
true
|
47
|
-
rescue Errno::ESRCH
|
48
|
-
false
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
@@ -1,82 +0,0 @@
|
|
1
|
-
module Listlace
|
2
|
-
class PlaylistArray < Array
|
3
|
-
attr_accessor :name, :model
|
4
|
-
|
5
|
-
def initialize(tracks_or_playlist = [], name = :playlist)
|
6
|
-
if tracks_or_playlist.is_a? Playlist
|
7
|
-
replace tracks_or_playlist.tracks.to_a
|
8
|
-
@name = tracks_or_playlist.name
|
9
|
-
@model = tracks_or_playlist
|
10
|
-
else
|
11
|
-
replace tracks_or_playlist.to_a
|
12
|
-
@name = name.to_s
|
13
|
-
@model = nil
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def save(name = nil)
|
18
|
-
if @model
|
19
|
-
@model.playlist_items.destroy_all
|
20
|
-
@model.name = @name = name if name
|
21
|
-
else
|
22
|
-
@name = name if name
|
23
|
-
@model = Playlist.new(name: @name)
|
24
|
-
end
|
25
|
-
|
26
|
-
if @model.save
|
27
|
-
each.with_index do |track, i|
|
28
|
-
item = PlaylistItem.new(position: i)
|
29
|
-
item.playlist = @model
|
30
|
-
item.track = track
|
31
|
-
item.save!
|
32
|
-
end
|
33
|
-
@model
|
34
|
-
else
|
35
|
-
false
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def <<(other)
|
40
|
-
if other.is_a? Track
|
41
|
-
super
|
42
|
-
else
|
43
|
-
other.each do |track|
|
44
|
-
self << track
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def +(other)
|
50
|
-
result = self.dup
|
51
|
-
result << Listlace.playlist(other)
|
52
|
-
result
|
53
|
-
end
|
54
|
-
|
55
|
-
def &(other)
|
56
|
-
replace super
|
57
|
-
end
|
58
|
-
|
59
|
-
def shuffle_except(track)
|
60
|
-
ary = dup
|
61
|
-
dup.shuffle_except! track
|
62
|
-
dup
|
63
|
-
end
|
64
|
-
|
65
|
-
def shuffle_except!(track)
|
66
|
-
replace([track] + (self - [track]).shuffle)
|
67
|
-
end
|
68
|
-
|
69
|
-
def to_s
|
70
|
-
"%s (%d track%s)" % [@name || "playlist", length, ("s" if length != 1)]
|
71
|
-
end
|
72
|
-
|
73
|
-
def inspect
|
74
|
-
to_s
|
75
|
-
end
|
76
|
-
|
77
|
-
# override pry
|
78
|
-
def pretty_inspect
|
79
|
-
inspect
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|