listlace 0.0.3 → 0.0.4
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 +10 -1
- data/bin/listlace +0 -1
- data/lib/listlace/commands/library.rb +72 -29
- data/lib/listlace/commands/playback.rb +6 -6
- data/lib/listlace/commands/queue.rb +36 -20
- data/lib/listlace/commands/selectors.rb +186 -0
- data/lib/listlace/database.rb +10 -0
- data/lib/listlace/models/track.rb +8 -5
- data/lib/listlace/player.rb +26 -12
- data/lib/listlace/playlist_array.rb +82 -0
- data/lib/listlace.rb +2 -1
- data/listlace.gemspec +2 -2
- metadata +3 -2
data/Gemfile.lock
CHANGED
data/README
CHANGED
@@ -39,7 +39,7 @@ The only way to populate the database right
|
|
39
39
|
now is to import your iTunes library.
|
40
40
|
That goes a little something like this:
|
41
41
|
|
42
|
-
>>
|
42
|
+
>> import :itunes, "/Users/jeremy/Music/iTunes/iTunes Music Library.xml"
|
43
43
|
|
44
44
|
Now you can play your music.
|
45
45
|
|
@@ -75,3 +75,12 @@ itself will start playing the queue.
|
|
75
75
|
* queue
|
76
76
|
- q: append some tracks to the queue, or get the queue
|
77
77
|
- clear: clear the queue
|
78
|
+
- shuffle: shuffle the songs in the queue
|
79
|
+
- sort: sort the songs in the queue however you want
|
80
|
+
* library
|
81
|
+
- import: import another program's music library
|
82
|
+
- wipe_library: remove all tracks and playlists from the library
|
83
|
+
|
84
|
+
-- selectors
|
85
|
+
|
86
|
+
...
|
data/bin/listlace
CHANGED
@@ -2,41 +2,84 @@ require "plist"
|
|
2
2
|
require "active_support/core_ext/string"
|
3
3
|
|
4
4
|
module Listlace
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
attributes = row.inject({}) do |acc, (key, value)|
|
14
|
-
attribute = key.gsub(" ", "").underscore
|
15
|
-
attribute = "original_id" if attribute == "track_id"
|
16
|
-
acc[attribute] = value if whitelist.include? attribute
|
17
|
-
acc
|
18
|
-
end
|
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)
|
19
13
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
24
27
|
|
25
|
-
|
26
|
-
|
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
|
28
38
|
|
29
|
-
|
30
|
-
playlist = Playlist.new(name: playlist_data["Name"])
|
31
|
-
playlist.save!
|
39
|
+
track = Track.new(attributes)
|
32
40
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
41
|
+
if track.kind =~ /audio/
|
42
|
+
if track.save
|
43
|
+
num_tracks += 1
|
44
|
+
end
|
45
|
+
else
|
46
|
+
puts "[skipping non-audio file]"
|
38
47
|
end
|
39
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"
|
40
83
|
end
|
41
84
|
end
|
42
85
|
end
|
@@ -48,8 +48,8 @@ module Listlace
|
|
48
48
|
end
|
49
49
|
|
50
50
|
# Go back one song in the queue.
|
51
|
-
def back
|
52
|
-
if $player.back
|
51
|
+
def back(n = 1)
|
52
|
+
if $player.back(n)
|
53
53
|
status
|
54
54
|
else
|
55
55
|
puts "End of queue."
|
@@ -57,8 +57,8 @@ module Listlace
|
|
57
57
|
end
|
58
58
|
|
59
59
|
# Go directly to the next song in the queue.
|
60
|
-
def skip
|
61
|
-
if $player.skip
|
60
|
+
def skip(n = 1)
|
61
|
+
if $player.skip(n)
|
62
62
|
status
|
63
63
|
else
|
64
64
|
puts "End of queue."
|
@@ -114,9 +114,9 @@ module Listlace
|
|
114
114
|
num_tracks = q.length
|
115
115
|
repeat_one = $player.repeat_mode == :one ? REPEAT_SYMBOL : ""
|
116
116
|
repeat_all = $player.repeat_mode == :all ? REPEAT_SYMBOL : ""
|
117
|
-
puts "Playlist:
|
117
|
+
puts "Playlist: %s (%d%s / %d%s)" % [q.name, track_number, repeat_one, num_tracks, repeat_all]
|
118
118
|
else
|
119
|
-
puts "Playlist:
|
119
|
+
puts "Playlist: %s" % [q]
|
120
120
|
end
|
121
121
|
when :playing
|
122
122
|
if $player.started?
|
@@ -1,21 +1,10 @@
|
|
1
1
|
module Listlace
|
2
|
-
# The queue command. Simply appends tracks to the queue.
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
def q(*
|
7
|
-
|
8
|
-
case playlist_or_track
|
9
|
-
when Track
|
10
|
-
$player.queue playlist_or_track
|
11
|
-
when Playlist
|
12
|
-
q *playlist_or_track.tracks
|
13
|
-
when Array
|
14
|
-
q *playlist_or_track
|
15
|
-
when ActiveRecord::Relation
|
16
|
-
q *playlist_or_track.all
|
17
|
-
end
|
18
|
-
end
|
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)
|
19
8
|
$player.queue
|
20
9
|
end
|
21
10
|
|
@@ -25,11 +14,38 @@ module Listlace
|
|
25
14
|
puts "Queue cleared."
|
26
15
|
end
|
27
16
|
|
17
|
+
# Shuffles the queue, keeping the current track at the top.
|
28
18
|
def shuffle
|
29
|
-
|
19
|
+
$player.shuffle
|
20
|
+
puts "Shuffled."
|
30
21
|
end
|
31
22
|
|
32
|
-
|
33
|
-
|
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
|
34
50
|
end
|
35
51
|
end
|
@@ -0,0 +1,186 @@
|
|
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
|
data/lib/listlace/database.rb
CHANGED
@@ -7,11 +7,6 @@ module Listlace
|
|
7
7
|
Track.format_time(total_time)
|
8
8
|
end
|
9
9
|
|
10
|
-
def play
|
11
|
-
$player.queue = [self]
|
12
|
-
Listlace.play
|
13
|
-
end
|
14
|
-
|
15
10
|
def self.format_time(milliseconds)
|
16
11
|
total_seconds = milliseconds / 1000
|
17
12
|
|
@@ -25,5 +20,13 @@ module Listlace
|
|
25
20
|
"%d:%02d" % [minutes, seconds]
|
26
21
|
end
|
27
22
|
end
|
23
|
+
|
24
|
+
def self.parse_time(string)
|
25
|
+
parts = string.split(":").map(&:to_i)
|
26
|
+
parts = [0] + parts if parts.length == 2
|
27
|
+
hours, minutes, seconds = parts
|
28
|
+
seconds = hours * 3600 + minutes * 60 + seconds
|
29
|
+
seconds * 1000
|
30
|
+
end
|
28
31
|
end
|
29
32
|
end
|
data/lib/listlace/player.rb
CHANGED
@@ -7,7 +7,7 @@ module Listlace
|
|
7
7
|
|
8
8
|
def initialize
|
9
9
|
@mplayer = nil
|
10
|
-
@queue = []
|
10
|
+
@queue = PlaylistArray.new([], :queue)
|
11
11
|
@current_track = nil
|
12
12
|
@current_track_index = nil
|
13
13
|
@paused = false
|
@@ -15,14 +15,14 @@ module Listlace
|
|
15
15
|
@repeat_mode = false
|
16
16
|
end
|
17
17
|
|
18
|
-
def queue(
|
19
|
-
@queue <<
|
18
|
+
def queue(playlist = nil)
|
19
|
+
@queue << playlist if playlist
|
20
20
|
@queue.dup
|
21
21
|
end
|
22
22
|
|
23
23
|
def clear
|
24
24
|
stop
|
25
|
-
@queue
|
25
|
+
@queue.clear
|
26
26
|
end
|
27
27
|
|
28
28
|
def empty?
|
@@ -88,12 +88,12 @@ module Listlace
|
|
88
88
|
change_track(0)
|
89
89
|
end
|
90
90
|
|
91
|
-
def back
|
92
|
-
change_track(-
|
91
|
+
def back(n = 1)
|
92
|
+
change_track(-n)
|
93
93
|
end
|
94
94
|
|
95
|
-
def skip
|
96
|
-
change_track(
|
95
|
+
def skip(n = 1)
|
96
|
+
change_track(n)
|
97
97
|
end
|
98
98
|
|
99
99
|
def seek(where)
|
@@ -103,10 +103,7 @@ module Listlace
|
|
103
103
|
when Range
|
104
104
|
@mplayer.command("seek %d 2" % [where.begin * 60 + where.end], expect_answer: true)
|
105
105
|
when String
|
106
|
-
|
107
|
-
parts = [0] + parts if parts.length == 2
|
108
|
-
hours, minutes, seconds = parts
|
109
|
-
@mplayer.command("seek %d 2" % [hours * 3600 + minutes * 60 + seconds], expect_answer: true)
|
106
|
+
@mplayer.command("seek %d 2" % [Track.parse_time(where) / 1000], expect_answer: true)
|
110
107
|
when Hash
|
111
108
|
if where[:abs]
|
112
109
|
if where[:abs].is_a? Integer
|
@@ -131,6 +128,23 @@ module Listlace
|
|
131
128
|
@mplayer.command("speed_set %f" % [speed], expect_answer: true)
|
132
129
|
end
|
133
130
|
|
131
|
+
def shuffle
|
132
|
+
if started?
|
133
|
+
@queue.shuffle_except! @current_track
|
134
|
+
@current_track_index = 0
|
135
|
+
else
|
136
|
+
@queue.shuffle!
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def sort(&by)
|
141
|
+
@queue.sort! &by
|
142
|
+
|
143
|
+
if started?
|
144
|
+
@current_track_index = @queue.index(@current_track)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
134
148
|
def current_time
|
135
149
|
answer = @mplayer.command "get_time_pos", expect_answer: true
|
136
150
|
if answer =~ /^ANS_TIME_POSITION=([0-9.]+)$/
|
@@ -0,0 +1,82 @@
|
|
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
|
data/lib/listlace.rb
CHANGED
@@ -8,10 +8,12 @@ end
|
|
8
8
|
require "open4"
|
9
9
|
require "shellwords"
|
10
10
|
require "active_record"
|
11
|
+
require "fileutils"
|
11
12
|
|
12
13
|
require "listlace/database"
|
13
14
|
require "listlace/player"
|
14
15
|
require "listlace/mplayer"
|
16
|
+
require "listlace/playlist_array"
|
15
17
|
|
16
18
|
require "listlace/models/track"
|
17
19
|
require "listlace/models/playlist"
|
@@ -23,7 +25,6 @@ require "listlace/commands/selectors"
|
|
23
25
|
require "listlace/commands/volume"
|
24
26
|
require "listlace/commands/queue"
|
25
27
|
|
26
|
-
# gotta ged rid of this global sometime
|
27
28
|
$player = Listlace::Player.new
|
28
29
|
|
29
30
|
at_exit do
|
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.4"
|
4
|
+
s.date = "2012-08-25"
|
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"
|
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.4
|
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-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pry
|
@@ -147,6 +147,7 @@ files:
|
|
147
147
|
- lib/listlace/models/track.rb
|
148
148
|
- lib/listlace/mplayer.rb
|
149
149
|
- lib/listlace/player.rb
|
150
|
+
- lib/listlace/playlist_array.rb
|
150
151
|
- lib/listlace.rb
|
151
152
|
homepage: http://github.com/yjerem/listlace
|
152
153
|
licenses:
|