mpd 0.17.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.
- data/README.md +3 -0
- data/bin/mpc.rb +8 -0
- data/lib/mpd.rb +12 -0
- data/lib/mpd/controller.rb +206 -0
- data/lib/mpd/controller/audio.rb +78 -0
- data/lib/mpd/controller/channels.rb +117 -0
- data/lib/mpd/controller/commands.rb +57 -0
- data/lib/mpd/controller/config.rb +35 -0
- data/lib/mpd/controller/current_playlist.rb +110 -0
- data/lib/mpd/controller/database.rb +235 -0
- data/lib/mpd/controller/decoders.rb +54 -0
- data/lib/mpd/controller/do.rb +42 -0
- data/lib/mpd/controller/player.rb +109 -0
- data/lib/mpd/controller/playlists.rb +113 -0
- data/lib/mpd/controller/stats.rb +31 -0
- data/lib/mpd/controller/status.rb +69 -0
- data/lib/mpd/controller/stickers.rb +87 -0
- data/lib/mpd/controller/supported_protocols.rb +36 -0
- data/lib/mpd/controller/supported_tags.rb +36 -0
- data/lib/mpd/controller/toggle.rb +72 -0
- data/lib/mpd/protocol.rb +15 -0
- data/lib/mpd/protocol/command.rb +50 -0
- data/lib/mpd/protocol/command_list.rb +48 -0
- data/lib/mpd/protocol/response.rb +181 -0
- data/lib/mpd/version.rb +15 -0
- data/mpd.gemspec +16 -0
- metadata +71 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
module MPD; class Controller
|
12
|
+
|
13
|
+
class Config < Hash
|
14
|
+
attr_reader :controller
|
15
|
+
|
16
|
+
def initialize (controller)
|
17
|
+
@controller = controller
|
18
|
+
|
19
|
+
controller.do_and_raise_if_needed(:config).each {|name, value|
|
20
|
+
self[name] = value
|
21
|
+
}
|
22
|
+
|
23
|
+
freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_to_missing? (id, include_private = false)
|
27
|
+
include? id
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing (id, *)
|
31
|
+
self[id]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end; end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
module MPD; class Controller
|
12
|
+
|
13
|
+
class CurrentPlaylist
|
14
|
+
include Enumerable
|
15
|
+
|
16
|
+
attr_reader :controller
|
17
|
+
|
18
|
+
def initialize (controller)
|
19
|
+
@controller = controller
|
20
|
+
end
|
21
|
+
|
22
|
+
def version
|
23
|
+
controller.status.playlist.version
|
24
|
+
end
|
25
|
+
|
26
|
+
def length
|
27
|
+
controller.status.playlist.length
|
28
|
+
end
|
29
|
+
|
30
|
+
def add (uri, position = nil)
|
31
|
+
controller.do_and_raise_if_needed(:addid, uri, *position).first.last
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete (what)
|
35
|
+
if what.is_a?(Integer) || what.is_a?(Range)
|
36
|
+
controller.do_and_raise_if_needed :delete, what
|
37
|
+
else
|
38
|
+
controller.do_and_raise_if_needed :deleteid, what
|
39
|
+
end
|
40
|
+
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def move (from, to)
|
45
|
+
if from.is_a?(Integer) || what.is_a?(Range)
|
46
|
+
controller.do_and_raise_if_needed :move, from, to
|
47
|
+
else
|
48
|
+
controller.do_and_raise_if_needed :moveid, from, to
|
49
|
+
end
|
50
|
+
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def clear
|
55
|
+
controller.do_and_raise_if_needed :clear
|
56
|
+
end
|
57
|
+
|
58
|
+
def save_as (name, force = false)
|
59
|
+
if force
|
60
|
+
controller.playlists[name].delete!
|
61
|
+
end
|
62
|
+
|
63
|
+
controller.do_and_raise_if_needed :save, name
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def search (pattern, options = { tag: :title, strict: false })
|
69
|
+
Database::Song.from_data(controller.do_and_raise_if_needed(options[:strict] ? :playlistfind : :playlistsearch, options[:tag], pattern))
|
70
|
+
end
|
71
|
+
|
72
|
+
def each
|
73
|
+
return to_enum unless block_given?
|
74
|
+
|
75
|
+
Database::Song.from_data(controller.do(:playlistinfo)).each {|song|
|
76
|
+
yield song
|
77
|
+
}
|
78
|
+
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def [] (id)
|
83
|
+
Database::Song.from_data(controller.do_and_raise_if_needed(:playlistid, id))
|
84
|
+
end
|
85
|
+
|
86
|
+
def priority (priority, *args)
|
87
|
+
controller.do_and_raise_if_needed :prio, priority, *args.select { |o| o.is_a?(Range) }
|
88
|
+
controller.do_and_raise_if_needed :prioid, priority, *args.reject { |o| o.is_a?(Range) }
|
89
|
+
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
def shuffle (range = nil)
|
94
|
+
controller.do_and_raise_if_needed :shuffle, *range
|
95
|
+
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
def swap (a, b)
|
100
|
+
if a.is_a?(Integer) && b.is_a?(Integer)
|
101
|
+
controller.do_and_raise_if_needed :swap, a, b
|
102
|
+
else
|
103
|
+
controller.do_and_raise_if_needed :swapip, a, b
|
104
|
+
end
|
105
|
+
|
106
|
+
self
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end; end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
module MPD; class Controller
|
12
|
+
|
13
|
+
class Database
|
14
|
+
class Song
|
15
|
+
Tags = Struct.new(:track, :title, :artist, :album, :genre, :date, :composer, :performer, :disc, :id)
|
16
|
+
|
17
|
+
def self.from_data (data, controller = nil)
|
18
|
+
if data.count { |name, _| name == :file } > 1
|
19
|
+
result = []
|
20
|
+
to_read = nil
|
21
|
+
|
22
|
+
data.each {|name, value|
|
23
|
+
if name == :file
|
24
|
+
if to_read
|
25
|
+
result << Song.from_data(to_read, controller)
|
26
|
+
end
|
27
|
+
|
28
|
+
to_read = [[name, value]]
|
29
|
+
else
|
30
|
+
to_read << [name, value]
|
31
|
+
end
|
32
|
+
}
|
33
|
+
|
34
|
+
result << Song.from_data(to_read, controller)
|
35
|
+
|
36
|
+
result
|
37
|
+
else
|
38
|
+
song = new(controller)
|
39
|
+
|
40
|
+
data.each {|name, value|
|
41
|
+
case name
|
42
|
+
when :file then song.file = value
|
43
|
+
when :Pos then song.position = value
|
44
|
+
when :Time then song.duration = value
|
45
|
+
when :Track then song.tags.track = value
|
46
|
+
when :Title then song.tags.title = value
|
47
|
+
when :Artist then song.tags.artist = value
|
48
|
+
when :Album then song.tags.album = value
|
49
|
+
when :Genre then song.tags.genre = value
|
50
|
+
when :Date then song.tags.date = value
|
51
|
+
when :Composer then song.tags.composer = value
|
52
|
+
when :Performer then song.tags.performer = value
|
53
|
+
when :Disc then song.tags.disc = value
|
54
|
+
when :MUSICBRAINZ_TRACKID then song.tags.id = value
|
55
|
+
end
|
56
|
+
}
|
57
|
+
|
58
|
+
song
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.from_uri (uri, controller = nil)
|
63
|
+
if controller
|
64
|
+
from_data(controller.do_and_raise_if_needed(:listallinfo, uri), controller)
|
65
|
+
else
|
66
|
+
Song.new.tap {|song|
|
67
|
+
song.file = uri
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
attr_reader :controller, :tags
|
73
|
+
attr_accessor :file, :position, :duration
|
74
|
+
|
75
|
+
def initialize (controller = nil)
|
76
|
+
@controller = controller
|
77
|
+
@tags = Tags.new
|
78
|
+
end
|
79
|
+
|
80
|
+
def add
|
81
|
+
raise 'no controller, cannot add to playlist' unless controller
|
82
|
+
end
|
83
|
+
|
84
|
+
def respond_to_missing (id, include_private = false)
|
85
|
+
@tags.respond_to?(id, include_private)
|
86
|
+
end
|
87
|
+
|
88
|
+
def method_missing (id, *)
|
89
|
+
if @tags.respond_to? id
|
90
|
+
return @tags.__send__ id
|
91
|
+
end
|
92
|
+
|
93
|
+
super
|
94
|
+
end
|
95
|
+
|
96
|
+
alias uri file
|
97
|
+
|
98
|
+
def inspect
|
99
|
+
"#<#{self.class.name}: #{file.inspect}>"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Directory
|
104
|
+
include Enumerable
|
105
|
+
|
106
|
+
attr_reader :database, :uri
|
107
|
+
|
108
|
+
def initialize (database, uri = nil)
|
109
|
+
@database = database
|
110
|
+
@uri = uri
|
111
|
+
end
|
112
|
+
|
113
|
+
def each
|
114
|
+
return enum_for :each unless block_given?
|
115
|
+
|
116
|
+
database.controller.do_and_raise_if_needed(:lsinfo, *uri).each {|name, value|
|
117
|
+
case name
|
118
|
+
when :file then yield Song.from_uri(value, database.controller)
|
119
|
+
when :directory then yield Directory.new(database, value)
|
120
|
+
end
|
121
|
+
}
|
122
|
+
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
def [] (name)
|
127
|
+
each { |n| return n if n.uri == name }
|
128
|
+
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
def inspect
|
133
|
+
"#<#{self.class.name}: #{uri.inspect}>"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Artist
|
138
|
+
attr_reader :database, :name
|
139
|
+
|
140
|
+
def initialize (database, name)
|
141
|
+
@database = database
|
142
|
+
@name = name
|
143
|
+
end
|
144
|
+
|
145
|
+
def nil?
|
146
|
+
name.empty?
|
147
|
+
end
|
148
|
+
|
149
|
+
def songs (strict = false)
|
150
|
+
database.search(name, tag: :artist, strict: strict)
|
151
|
+
end
|
152
|
+
|
153
|
+
def inspect
|
154
|
+
"#<#{self.class.name}: #{name.inspect}>"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class Element
|
159
|
+
attr_reader :database, :tag, :name
|
160
|
+
|
161
|
+
def initialize (database, tag, name)
|
162
|
+
@database = database
|
163
|
+
@tag = tag
|
164
|
+
@name = name
|
165
|
+
end
|
166
|
+
|
167
|
+
def nil?
|
168
|
+
name.empty?
|
169
|
+
end
|
170
|
+
|
171
|
+
def songs (strict = false)
|
172
|
+
database.search(name, tag: tag, strict: strict)
|
173
|
+
end
|
174
|
+
|
175
|
+
def inspect
|
176
|
+
"#<#{self.class.name.sub(/::Element$/, "::#{tag.capitalize}")}: #{name.inspect}>"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
include Enumerable
|
181
|
+
|
182
|
+
attr_reader :controller, :root
|
183
|
+
|
184
|
+
def initialize (controller)
|
185
|
+
@controller = controller
|
186
|
+
@root = Directory.new(self)
|
187
|
+
end
|
188
|
+
|
189
|
+
def tags (name, artist = nil)
|
190
|
+
return enum_for :tags, name, artist unless block_given?
|
191
|
+
|
192
|
+
controller.do_and_raise_if_needed(:list, name, *artist).each {|_, value|
|
193
|
+
yield value
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
%w[artists albums genres composers performers].each {|name|
|
198
|
+
tag = name[0 .. -2].to_sym
|
199
|
+
|
200
|
+
define_method name do |artist = nil|
|
201
|
+
tags(tag, artist).map { |n| Element.new(self, tag, n) }
|
202
|
+
end
|
203
|
+
}
|
204
|
+
|
205
|
+
def search (pattern, options = { tag: :title, strict: false })
|
206
|
+
Song.from_data(controller.do_and_raise_if_needed(options[:strict] ? :find : :search, options[:tag], pattern))
|
207
|
+
end
|
208
|
+
|
209
|
+
def each (&block)
|
210
|
+
return to_enum unless block_given?
|
211
|
+
|
212
|
+
@root.each(&block)
|
213
|
+
|
214
|
+
self
|
215
|
+
end
|
216
|
+
|
217
|
+
def [] (name)
|
218
|
+
@root[name]
|
219
|
+
end
|
220
|
+
|
221
|
+
def update (*args)
|
222
|
+
options = args.last.is_a?(Hash) ? args.pop : { force: false }
|
223
|
+
uri = args.shift
|
224
|
+
|
225
|
+
if options[:force]
|
226
|
+
controller.do_and_raise_if_needed :rescan, *uri
|
227
|
+
else
|
228
|
+
controller.do_and_raise_if_needed :update, *uri
|
229
|
+
end
|
230
|
+
|
231
|
+
self
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
end; end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
module MPD; class Controller
|
12
|
+
|
13
|
+
class Decoders
|
14
|
+
Decoder = Struct.new(:name, :suffixes, :mime_types)
|
15
|
+
|
16
|
+
include Enumerable
|
17
|
+
|
18
|
+
attr_reader :controller
|
19
|
+
|
20
|
+
def initialize (controller)
|
21
|
+
@controller = controller
|
22
|
+
@decoders = []
|
23
|
+
|
24
|
+
name = nil
|
25
|
+
suffixes = nil
|
26
|
+
mime_types = nil
|
27
|
+
|
28
|
+
controller.do_and_raise_if_needed(:decoders).each {|type, value|
|
29
|
+
if type == :plugin
|
30
|
+
if name
|
31
|
+
@decoders << Decoder.new(name, suffixes, mime_types)
|
32
|
+
end
|
33
|
+
|
34
|
+
name = value
|
35
|
+
suffixes = []
|
36
|
+
mime_types = []
|
37
|
+
elsif type == :suffix
|
38
|
+
suffixes << value
|
39
|
+
elsif type == :mime_type
|
40
|
+
mime_types << value
|
41
|
+
end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def each (&block)
|
46
|
+
return to_enum unless block_given?
|
47
|
+
|
48
|
+
@decoders.each(&block)
|
49
|
+
|
50
|
+
self
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end; end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
module MPD; class Controller
|
12
|
+
|
13
|
+
class Do < BasicObject
|
14
|
+
attr_reader :controller
|
15
|
+
|
16
|
+
def initialize (controller, &block)
|
17
|
+
@controller = controller
|
18
|
+
@commands = []
|
19
|
+
|
20
|
+
instance_exec &block if block
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing (id, *args)
|
24
|
+
@commands << ::MPD::Protocol::Command.new(id, args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def send
|
28
|
+
result = []
|
29
|
+
|
30
|
+
controller.puts ::MPD::Protocol::CommandList.new(@commands).to_s
|
31
|
+
|
32
|
+
@commands.each {|command|
|
33
|
+
result << ::MPD::Protocol::Response.read(controller, command)
|
34
|
+
|
35
|
+
break if result.last.is_a? ::MPD::Protocol::Error
|
36
|
+
} and controller.readline
|
37
|
+
|
38
|
+
result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end; end
|