dxmms2 1.0.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 +7 -0
- data/Gemfile +3 -0
- data/Makefile +6 -0
- data/README.md +7 -0
- data/bin/dxmms2 +668 -0
- data/dxmms2.gemspec +23 -0
- data/dxmms2.png +0 -0
- metadata +123 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c0558d7e7c3d2afdfce8c3cda5a5d462b19fb1e2
|
4
|
+
data.tar.gz: 717ae54b84456c746f9486f3c5703776250c7ae1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: abe3d53027584aa876d6114c529e1820fa91d92e8499afed04cb143167f030ce8c94f1ea60a5cf550375a517d6a8419e0b45fe4bf0fc1896dce0c2e00797bef4
|
7
|
+
data.tar.gz: 54e52c04408e46a6da49a815ee1d819d53f4091759d418154dea49efb61725d3e75e0b2b7fb288a2a6fe30ce20782c81db33771b4da3f4c49ab7154289172638
|
data/Gemfile
ADDED
data/Makefile
ADDED
data/README.md
ADDED
data/bin/dxmms2
ADDED
@@ -0,0 +1,668 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'dmenu'
|
3
|
+
require 'xmms2_utils'
|
4
|
+
require 'stringio'
|
5
|
+
require 'uri'
|
6
|
+
require 'socket'
|
7
|
+
require 'set'
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'glib2'
|
11
|
+
$GLIB=true
|
12
|
+
rescue
|
13
|
+
$GLIB=false
|
14
|
+
end
|
15
|
+
|
16
|
+
##########################
|
17
|
+
# simple xmms2 script #
|
18
|
+
# for dmenu #
|
19
|
+
##########################
|
20
|
+
|
21
|
+
$xc = Xmms::client("dxmms2")
|
22
|
+
|
23
|
+
$COMMAND_SIG = "@"
|
24
|
+
# default configs {{{
|
25
|
+
$SCREEN_WIDTH=Integer(ENV.fetch('DXMMS2_MENU_WIDTH', 600))
|
26
|
+
$FONT_WIDTH=Integer(ENV.fetch('DXMMS2_FONT_WIDTH', 13)) #in pixels)
|
27
|
+
$BG_COLOR=ENV.fetch('DMENU_BG_COLOR', '#000000')
|
28
|
+
$FG_COLOR=ENV.fetch('DMENU_FG_COLOR', '#dc322f')
|
29
|
+
$SEL_BG_COLOR=ENV.fetch('DMENU_SEL_BG_COLOR', $FG_COLOR)
|
30
|
+
$SEL_FG_COLOR=ENV.fetch('DMENU_SEL_FG_COLOR', $BG_COLOR)
|
31
|
+
if $GLIB
|
32
|
+
fallback = ENV.fetch('HOME', '/')
|
33
|
+
xdg_dir = GLib.get_user_special_dir(GLib::USER_DIRECTORY_MUSIC)
|
34
|
+
if !xdg_dir.nil?
|
35
|
+
fallback = xdg_dir
|
36
|
+
end
|
37
|
+
$MUSIC_DIRECTORY = ENV.fetch('DXMMS2_MUSIC_DIRECTORY', fallback)
|
38
|
+
end
|
39
|
+
|
40
|
+
$FONT = ENV.fetch('DMENU_FONT', 'Noto Sans Mono CJK JP Regular')
|
41
|
+
$FONT = $FONT + ':pixelsize=' + $FONT_WIDTH.to_s
|
42
|
+
$LIST_ENTRIES_PER_PAGE = 15
|
43
|
+
$LINE_HEIGHT=10
|
44
|
+
# }}}
|
45
|
+
|
46
|
+
#if [ -e $CONFIG ] ; then
|
47
|
+
#source $CONFIG
|
48
|
+
#fi
|
49
|
+
class Integer
|
50
|
+
def ms_to_time_string
|
51
|
+
minutes=(self / 60000)
|
52
|
+
seconds=(self % 60000) / 1000
|
53
|
+
"%d:%02d" % [minutes, seconds]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def time_string_to_ms(str)
|
58
|
+
res = 0
|
59
|
+
s = 1
|
60
|
+
for n in str.split(":").reverse do
|
61
|
+
s = 60 * s
|
62
|
+
res = res + s * (n.to_i)
|
63
|
+
end
|
64
|
+
return res.to_i
|
65
|
+
end
|
66
|
+
|
67
|
+
def my_dmenu (entries, prompt='dxmms2', height=entries.count, width=$SCREEN_WIDTH)
|
68
|
+
height = [height, 20].min
|
69
|
+
Dmenu::dmenu(entries, prompt, height, width,
|
70
|
+
$FG_COLOR,
|
71
|
+
$BG_COLOR,
|
72
|
+
$SEL_FG_COLOR,
|
73
|
+
$SEL_BG_COLOR,
|
74
|
+
$FONT,
|
75
|
+
$LINE_HEIGHT)
|
76
|
+
end
|
77
|
+
|
78
|
+
def playlists_prompt(&call_back)
|
79
|
+
playlists = $xc.playlist_list.wait.value
|
80
|
+
playlists = playlists.delete_if {|s| s.start_with?("_")}
|
81
|
+
selected = my_dmenu(playlists)
|
82
|
+
if !call_back.nil?
|
83
|
+
if !selected.empty?
|
84
|
+
call_back.call(selected)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
selected
|
88
|
+
end
|
89
|
+
|
90
|
+
def pl_list(start=nil,nentries=nil,prompt="Track: ",plname=nil)
|
91
|
+
pl = plname.nil? ? $xc.playlist : $xc.playlist(plname)
|
92
|
+
entries = pl.entries.wait.value
|
93
|
+
current = pl.current_pos.wait.value
|
94
|
+
cur = current.nil? ? 0 : current[:position]
|
95
|
+
start = start.nil? ? cur : start
|
96
|
+
list_ids(entries,prompt,start,nentries)
|
97
|
+
end
|
98
|
+
|
99
|
+
def list_ids(entries, prompt, start=0, nentries=nil, commands=nil)
|
100
|
+
# XXX: Create a list object to pass to user-supplied commands
|
101
|
+
# list object allows for setting list start, page size, list content,
|
102
|
+
# whether to redisplay the list, and maybe other things
|
103
|
+
morestring,endstring,startstring,backstring =
|
104
|
+
%w[more end start back].map{|w| $COMMAND_SIG + w}
|
105
|
+
if commands.nil?
|
106
|
+
commands = Hash.new
|
107
|
+
end
|
108
|
+
|
109
|
+
c1 = Hash.new
|
110
|
+
commands.each do |k,v|
|
111
|
+
c1[$COMMAND_SIG + k] = v
|
112
|
+
end
|
113
|
+
listing=1
|
114
|
+
nentries = (nentries.nil? ? $LIST_ENTRIES_PER_PAGE : nentries)
|
115
|
+
pos = nil
|
116
|
+
list_start = start
|
117
|
+
while ( listing == 1 ) do
|
118
|
+
start_clamped = false
|
119
|
+
end_clamped = false
|
120
|
+
items = Array.new
|
121
|
+
|
122
|
+
#clamps start
|
123
|
+
(list_start <= 0) and (list_start = 0 ; start_clamped = true)
|
124
|
+
|
125
|
+
list_end = list_start + nentries
|
126
|
+
|
127
|
+
#clamps end
|
128
|
+
(list_end > entries.length) and (list_end = entries.length ; end_clamped = true)
|
129
|
+
|
130
|
+
|
131
|
+
nw = list_end.to_s.length
|
132
|
+
i = list_start
|
133
|
+
|
134
|
+
items += xmms2_ids_to_display_list_strings(entries[list_start..list_end]).map do |string|
|
135
|
+
string = "#{i.to_s.rjust(nw)}. #{string}"
|
136
|
+
i += 1
|
137
|
+
string
|
138
|
+
end
|
139
|
+
|
140
|
+
if not start_clamped
|
141
|
+
items << backstring
|
142
|
+
items << startstring
|
143
|
+
end
|
144
|
+
|
145
|
+
if not end_clamped
|
146
|
+
items << endstring
|
147
|
+
items << morestring
|
148
|
+
end
|
149
|
+
|
150
|
+
c1.keys.each do |d|
|
151
|
+
items << d
|
152
|
+
end
|
153
|
+
|
154
|
+
choice = my_dmenu(items, prompt, items.length).gsub(/^\s+|\s+$/, "")
|
155
|
+
pos = choice[/^-?\d+|#{$COMMAND_SIG}[a-zA-Z0-9\-_.]+/]
|
156
|
+
|
157
|
+
case pos
|
158
|
+
when backstring
|
159
|
+
list_start -= nentries
|
160
|
+
when morestring
|
161
|
+
list_start += nentries
|
162
|
+
when endstring
|
163
|
+
list_start = entries.length - nentries
|
164
|
+
when startstring
|
165
|
+
list_start = 0
|
166
|
+
when *(c1.keys)
|
167
|
+
f = c1[pos]
|
168
|
+
f.call(entries)
|
169
|
+
pos = nil
|
170
|
+
listing = 0
|
171
|
+
else
|
172
|
+
listing = 0
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
if pos.nil?
|
177
|
+
return nil
|
178
|
+
end
|
179
|
+
|
180
|
+
pos = pos.to_i
|
181
|
+
if pos < 0
|
182
|
+
entries.length + pos
|
183
|
+
else
|
184
|
+
pos
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def xmms2_ids_to_display_list_strings(ids, fields=%w[artist title url duration])
|
189
|
+
ids.map do |id|
|
190
|
+
my_info = $xc.extract_medialib_info(id, *fields)
|
191
|
+
artist_part = my_info[:artist]
|
192
|
+
some_title = (my_info[:title] or File.basename(my_info[:url]))
|
193
|
+
duration = my_info[:duration].to_i.ms_to_time_string
|
194
|
+
"#{artist_part}|||#{some_title} [#{duration}]"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_usertags(id)
|
199
|
+
info = $xc.extract_medialib_info(id, :usertags)
|
200
|
+
if info.has_key?(:usertags)
|
201
|
+
info[:usertags].lines.to_a
|
202
|
+
else
|
203
|
+
[]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def set_tags(id, tags)
|
208
|
+
$xc.medialib_entry_property_set(id, :usertags, tags.map{|x| x.chomp}.uniq.join("\n")).wait
|
209
|
+
end
|
210
|
+
|
211
|
+
def tag_track(id, tag)
|
212
|
+
tags = get_usertags(id) << tag.chomp
|
213
|
+
set_tags(id, tags)
|
214
|
+
end
|
215
|
+
|
216
|
+
def untag_track(id, tag)
|
217
|
+
tags = get_usertags(id).reject{|x|x==tag}
|
218
|
+
set_tags(id, tags)
|
219
|
+
end
|
220
|
+
|
221
|
+
def tag_menu(ids, previous_tags)
|
222
|
+
tag_file = File.join(Xmms::userconfdir, 'usertags')
|
223
|
+
all_tags = File.new(tag_file,File::CREAT).to_a
|
224
|
+
|
225
|
+
previous_tags = previous_tags.map{|x| x.chomp}
|
226
|
+
|
227
|
+
tag_list = previous_tags.map{|k| "!" + k} + all_tags
|
228
|
+
tag_list.map!{|x| x.chomp}
|
229
|
+
|
230
|
+
tag = my_dmenu(tag_list, "Add/delete a tag")
|
231
|
+
if (!tag.empty?)
|
232
|
+
Thread.new do
|
233
|
+
if (tag[0] == "!")
|
234
|
+
ids.each do |id|
|
235
|
+
untag_track(id, tag[1..-1])
|
236
|
+
end
|
237
|
+
else
|
238
|
+
ids.each do |id|
|
239
|
+
tag_track(id, tag)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
if (tag[0] != "!")
|
246
|
+
all_tags << tag + "\n"
|
247
|
+
all_tags.uniq!
|
248
|
+
end
|
249
|
+
File.open(tag_file, "w") do |file|
|
250
|
+
all_tags.each do |t|
|
251
|
+
file.write(t)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
tag
|
255
|
+
else
|
256
|
+
nil
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def get_album_info(id, *fields)
|
261
|
+
get_collection_info(get_album_collection(id), *fields)
|
262
|
+
end
|
263
|
+
|
264
|
+
def get_collection_info(coll, *fields)
|
265
|
+
data = $xc.coll_query_info(coll, fields).wait.value
|
266
|
+
data.map do |infos|
|
267
|
+
res = Hash.new
|
268
|
+
fields = fields.map! {|f| f.to_sym }
|
269
|
+
fields.each do |field|
|
270
|
+
values = infos[field]
|
271
|
+
if not values.nil?
|
272
|
+
my_value = values
|
273
|
+
if field == :url
|
274
|
+
my_value = Xmms::decode_xmms2_url(my_value)
|
275
|
+
end
|
276
|
+
res[field] = my_value.to_s.force_encoding("utf-8")
|
277
|
+
end
|
278
|
+
end
|
279
|
+
res
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def get_album_ids(id)
|
284
|
+
album_coll = get_album_collection(id)
|
285
|
+
$xc.coll_query_ids(album_coll).wait.value
|
286
|
+
end
|
287
|
+
|
288
|
+
def get_album_collection(id)
|
289
|
+
album_name = $xc.extract_medialib_info(id, :album)[:album]
|
290
|
+
match_collection('album', album_name)
|
291
|
+
end
|
292
|
+
|
293
|
+
def match_collection(field, pattern, base_coll=nil)
|
294
|
+
coll = Xmms::Collection.new(Xmms::Collection::TYPE_MATCH)
|
295
|
+
if !coll.nil?
|
296
|
+
coll.operands << base_coll
|
297
|
+
else
|
298
|
+
coll.operands << Xmms::Collection.universe
|
299
|
+
end
|
300
|
+
coll.attributes["field"] = field
|
301
|
+
coll.attributes["value"] = pattern
|
302
|
+
coll
|
303
|
+
end
|
304
|
+
|
305
|
+
# The idea of this is that if I like a song around the middle of the track, I think it's a
|
306
|
+
# really good song and it's probably new since I had to listen halfway through to decide
|
307
|
+
# I liked it. I also consider that there may be an over-eagerness in my reaction early in
|
308
|
+
# a track, so I penalize that. Lastly, for me, if I like the track near the end, I'm only
|
309
|
+
# sort-of listening to it or only like it enough to appreciate it as a whole, but can't
|
310
|
+
# say much for it in parts.
|
311
|
+
#
|
312
|
+
# This should only be used additively with past `likes'
|
313
|
+
def track_appreciation_factor(playtime, track_duration)
|
314
|
+
k = [0, [playtime, track_duration].min].max.to_f / track_duration - 0.5
|
315
|
+
l = 1.0 - k.abs
|
316
|
+
l
|
317
|
+
end
|
318
|
+
|
319
|
+
$commands=%w<toggle list +fav +fav-album
|
320
|
+
tag tag-album prev next stop repeat-playlist
|
321
|
+
info change-playlist remove-playlist show-playlist
|
322
|
+
clear edit-album-metadata edit-metadata remove shuffle search
|
323
|
+
repeat-track repeat-off shutdown seek add>
|
324
|
+
|
325
|
+
|
326
|
+
def current_pl
|
327
|
+
$xc.playlist
|
328
|
+
end
|
329
|
+
|
330
|
+
def play_new_track(xc, pos=false)
|
331
|
+
if pos
|
332
|
+
xc.playlist_set_next(pos).wait
|
333
|
+
end
|
334
|
+
xc.playback_tickle.wait
|
335
|
+
xc.playback_stop.wait
|
336
|
+
xc.playback_start.wait
|
337
|
+
end
|
338
|
+
|
339
|
+
def edit_metadata(urls)
|
340
|
+
prefix = "file://"
|
341
|
+
plen = prefix.length
|
342
|
+
urls = urls.select do |x|
|
343
|
+
x.start_with?(prefix)
|
344
|
+
end.map do |x|
|
345
|
+
x[plen..-1]
|
346
|
+
end
|
347
|
+
files = urls.map{|x|"\"#{x}\""}.join(" ")
|
348
|
+
`picard #{ files }`
|
349
|
+
end
|
350
|
+
|
351
|
+
while true do
|
352
|
+
command = my_dmenu($commands).chomp
|
353
|
+
case command
|
354
|
+
# NOTE: The *break statements* in here are for the *while loop*
|
355
|
+
# not for the switch
|
356
|
+
when "list"
|
357
|
+
pos = pl_list(nil, nil, "Play Track:")
|
358
|
+
if not pos.nil?
|
359
|
+
puts "moving to positon #{pos}"
|
360
|
+
play_new_track($xc, pos)
|
361
|
+
end
|
362
|
+
break
|
363
|
+
when "add"
|
364
|
+
d = Dir.new($MUSIC_DIRECTORY)
|
365
|
+
add_dir = $COMMAND_SIG + 'add-dir'
|
366
|
+
while !d.nil?
|
367
|
+
entries = d.entries.map do |x|
|
368
|
+
if Dir.exist?(File.join(d, x))
|
369
|
+
x + '/'
|
370
|
+
else
|
371
|
+
x
|
372
|
+
end
|
373
|
+
end
|
374
|
+
choice = my_dmenu(entries.sort << add_dir, "Add entries", d.entries.length)
|
375
|
+
path = File.join(d, choice)
|
376
|
+
if choice.empty?
|
377
|
+
d = nil
|
378
|
+
elsif choice == add_dir
|
379
|
+
begin
|
380
|
+
current_pl.radd("file://" + d.path).wait
|
381
|
+
ensure
|
382
|
+
d = nil
|
383
|
+
end
|
384
|
+
else
|
385
|
+
if Dir.exists?(path)
|
386
|
+
if choice != '.'
|
387
|
+
d = Dir.new(path)
|
388
|
+
end
|
389
|
+
elsif File.exists?(path)
|
390
|
+
begin
|
391
|
+
$stderr.puts "Adding " + path
|
392
|
+
current_pl.add_entry("file://"+path).wait
|
393
|
+
ensure
|
394
|
+
d = nil
|
395
|
+
end
|
396
|
+
else
|
397
|
+
begin
|
398
|
+
$stderr.puts "Got a result we couldn't handle: "+choice.to_s
|
399
|
+
ensure
|
400
|
+
d = nil
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
break
|
406
|
+
when "info"
|
407
|
+
entries = current_pl.entries.wait.value
|
408
|
+
if not entries.nil?
|
409
|
+
pos = pl_list(nil,nil,"Track Info:")
|
410
|
+
if pos.nil?
|
411
|
+
break
|
412
|
+
end
|
413
|
+
id = entries[pos]
|
414
|
+
info = $xc.extract_medialib_info(id, *%w<artist title album tracknr favorite timesplayed url date duration laststarted added usertags comment>)
|
415
|
+
info = Hash[info.map { |k,v|
|
416
|
+
k = k.to_s
|
417
|
+
if %w<duration>.include?(k)
|
418
|
+
v = [v.to_i.ms_to_time_string, v]
|
419
|
+
elsif %w<laststarted added lmod>.include?(k)
|
420
|
+
v = [Time.at(v.to_i).strftime("%F"), v]
|
421
|
+
elsif k == "usertags"
|
422
|
+
v = [v.gsub(%r{\n}, ", ")]
|
423
|
+
else
|
424
|
+
v = [v]
|
425
|
+
end
|
426
|
+
[k,v]
|
427
|
+
}]
|
428
|
+
while true
|
429
|
+
field = my_dmenu(info.map {|k,v| "#{k} |||#{v[0]}"}, "Info", info.size)
|
430
|
+
if field.empty?
|
431
|
+
break
|
432
|
+
else
|
433
|
+
field = field[/^[^ ]+/]
|
434
|
+
values = info[field]
|
435
|
+
if values.length > 1
|
436
|
+
value = my_dmenu(values.map{ |v| v.to_s }, "Version?")
|
437
|
+
else
|
438
|
+
value = values[0]
|
439
|
+
end
|
440
|
+
operation = my_dmenu(%w<copy full-display back>, value)
|
441
|
+
case operation
|
442
|
+
when "copy"
|
443
|
+
`echo '#{value}' | xsel --clipboard -i`
|
444
|
+
break
|
445
|
+
when "full-display"
|
446
|
+
`echo '#{value}' | zenity --text-info`
|
447
|
+
break
|
448
|
+
when "back"
|
449
|
+
else
|
450
|
+
break
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
break
|
456
|
+
when "search"
|
457
|
+
fields = ["artist", "title", "genre", "album", "date", "url"]
|
458
|
+
def add(ids)
|
459
|
+
ids.each do |z|
|
460
|
+
current_pl.add_entry(z).wait
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
def new_playlist(title, ids)
|
465
|
+
pl_coll = Xmms::Collection.new(Xmms::Collection::TYPE_IDLIST)
|
466
|
+
pl_coll.idlist = ids
|
467
|
+
if !title.empty?
|
468
|
+
$xc.coll_save(pl_coll, title, Xmms::Collection::NS_PLAYLISTS).wait
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
def new_playlist_prompt(ids)
|
473
|
+
new_playlist(my_dmenu([], "Playlist name"), ids)
|
474
|
+
end
|
475
|
+
|
476
|
+
def edit_ids_metadata(ids)
|
477
|
+
edit_metadata(ids.map{ |id| $xc.extract_medialib_info(id, "url")[:url] })
|
478
|
+
end
|
479
|
+
|
480
|
+
current_collection = Xmms::Collection.universe # TODO: Support searching multiple fields as in xmmsfs
|
481
|
+
refine = false
|
482
|
+
cs = {
|
483
|
+
'add' => proc { |k| add(k) },
|
484
|
+
'edit-metadata' => proc { |k| edit_ids_metadata(k) },
|
485
|
+
'new-playlist' => proc { |k| new_playlist_prompt(k) },
|
486
|
+
'refine' => proc { |k| refine=true }
|
487
|
+
}
|
488
|
+
|
489
|
+
while true
|
490
|
+
field = my_dmenu(fields, prompt="Field")
|
491
|
+
if field.empty?
|
492
|
+
break
|
493
|
+
end
|
494
|
+
field_sym = field.to_sym
|
495
|
+
options = Set.new(get_collection_info(current_collection, field).map{|x| x.fetch(field_sym, "UNDEF")}).to_a.sort
|
496
|
+
|
497
|
+
lists = [options]
|
498
|
+
search_string = ""
|
499
|
+
# XXX: Save and display past searches
|
500
|
+
begin
|
501
|
+
lists.each_with_index do |x, i|
|
502
|
+
search_string = my_dmenu(x, prompt="Search string (#{i}/#{lists.length})", [x.length, 20].min)
|
503
|
+
break
|
504
|
+
end
|
505
|
+
rescue Exception
|
506
|
+
lists = lists.inject(Array.new){ |g, x| g + [x[0..(x.length/2 - 1)], x[x.length/2..-1]]}
|
507
|
+
retry
|
508
|
+
end
|
509
|
+
|
510
|
+
if !options.include?(search_string)
|
511
|
+
search_string.gsub!(" ","*")
|
512
|
+
elsif field == 'url'
|
513
|
+
search_string = Xmms::encode_url(search_string)
|
514
|
+
end
|
515
|
+
|
516
|
+
if search_string.empty?
|
517
|
+
break
|
518
|
+
end
|
519
|
+
search_string = "*#{search_string}*"
|
520
|
+
puts search_string
|
521
|
+
coll = match_collection(field, search_string, current_collection)
|
522
|
+
d = $xc.coll_query_ids(coll,"artist,album,tracknr").wait.value
|
523
|
+
d = d.reverse
|
524
|
+
pos = list_ids(d, prompt="Results", 0, $LIST_ENTRIES_PER_PAGE, cs)
|
525
|
+
|
526
|
+
# Make the playlist collection
|
527
|
+
new_playlist('search-result-list', d)
|
528
|
+
|
529
|
+
# add the entry and load the playlist
|
530
|
+
if !pos.nil?
|
531
|
+
pl = $xc.playlist('search-result-list')
|
532
|
+
pl.load.wait
|
533
|
+
play_new_track($xc, pos)
|
534
|
+
break
|
535
|
+
end
|
536
|
+
|
537
|
+
if refine
|
538
|
+
current_collection = coll
|
539
|
+
else
|
540
|
+
current_collection = Xmms::Collection.universe
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
break
|
545
|
+
when "remove"
|
546
|
+
while true
|
547
|
+
pos = pl_list(nil,nil,"Remove entry:")
|
548
|
+
if pos.nil?
|
549
|
+
break
|
550
|
+
else
|
551
|
+
current_pl.remove_entry(pos).wait
|
552
|
+
end
|
553
|
+
end
|
554
|
+
break
|
555
|
+
when "+fav"
|
556
|
+
begin
|
557
|
+
id = $xc.playback_current_id.wait.value
|
558
|
+
p = $xc.playback_playtime.wait.value
|
559
|
+
data = $xc.extract_medialib_info(id, :favorite, :duration)
|
560
|
+
old_favorite = data[:favorite].to_f
|
561
|
+
duration = data[:duration].to_i
|
562
|
+
appreciation = track_appreciation_factor(p, duration)
|
563
|
+
$xc.medialib_entry_property_set(id, :favorite, (old_favorite+appreciation).to_s).wait
|
564
|
+
end
|
565
|
+
break
|
566
|
+
when "+fav-album"
|
567
|
+
id = $xc.playback_current_id.wait.value
|
568
|
+
get_album_ids(id).each do |al_id|
|
569
|
+
old_favorite = $xc.extract_medialib_info(al_id, :favorite)[:favorite].to_f
|
570
|
+
$xc.medialib_entry_property_set(al_id, :favorite, old_favorite+1).wait
|
571
|
+
end
|
572
|
+
break
|
573
|
+
when "tag"
|
574
|
+
# TODO: refcount the tags in the tag file so they get removed
|
575
|
+
# when no file is tagged with them
|
576
|
+
|
577
|
+
id = $xc.playback_current_id.wait.value
|
578
|
+
while tag_menu([id], get_usertags(id))
|
579
|
+
end
|
580
|
+
|
581
|
+
break
|
582
|
+
when "edit-metadata"
|
583
|
+
id = $xc.playback_current_id.wait.value
|
584
|
+
url = $xc.extract_medialib_info(id, "url")[:url]
|
585
|
+
edit_metadata([url])
|
586
|
+
break
|
587
|
+
when "edit-album-metadata"
|
588
|
+
id = $xc.playback_current_id.wait.value
|
589
|
+
urls = get_album_info(id, "url").map{|x| x[:url]}
|
590
|
+
edit_metadata(urls)
|
591
|
+
break
|
592
|
+
when "tag-album"
|
593
|
+
begin
|
594
|
+
id = $xc.playback_current_id.wait.value
|
595
|
+
album_ids = get_album_ids(id)
|
596
|
+
while tag_menu(album_ids, [])
|
597
|
+
end
|
598
|
+
rescue => e
|
599
|
+
puts e
|
600
|
+
end
|
601
|
+
|
602
|
+
break
|
603
|
+
when "change-playlist"
|
604
|
+
begin
|
605
|
+
playlists_prompt do |selected|
|
606
|
+
$xc.playlist(selected).load.wait
|
607
|
+
end
|
608
|
+
end
|
609
|
+
break
|
610
|
+
when "remove-playlist"
|
611
|
+
begin
|
612
|
+
playlists_prompt do |selected|
|
613
|
+
$xc.playlist(selected).remove.wait
|
614
|
+
end
|
615
|
+
end
|
616
|
+
break
|
617
|
+
when "show-playlist"
|
618
|
+
begin
|
619
|
+
playlists_prompt do |selected|
|
620
|
+
pl_list(nil,nil,prompt=selected, plname=selected)
|
621
|
+
end
|
622
|
+
end
|
623
|
+
break
|
624
|
+
when %r{repeat-(off|track|playlist)}
|
625
|
+
case command.split(%r{-})[1]
|
626
|
+
when "off"
|
627
|
+
$xc.config_set_value('playlist.repeat_one', "0").wait
|
628
|
+
$xc.config_set_value('playlist.repeat_all', "0").wait
|
629
|
+
when "track"
|
630
|
+
$xc.config_set_value('playlist.repeat_one', "1").wait
|
631
|
+
$xc.config_set_value('playlist.repeat_all', "0").wait
|
632
|
+
when "playlist"
|
633
|
+
$xc.config_set_value('playlist.repeat_one', "0").wait
|
634
|
+
$xc.config_set_value('playlist.repeat_all', "1").wait
|
635
|
+
end
|
636
|
+
break
|
637
|
+
when "shuffle"
|
638
|
+
$xc.shuffle_by(current_pl, :artist)
|
639
|
+
#current_pl.shuffle.wait
|
640
|
+
break
|
641
|
+
when "shutdown"
|
642
|
+
$xc.quit.wait
|
643
|
+
break
|
644
|
+
when "seek"
|
645
|
+
print $xc.playback_playtime.wait.value
|
646
|
+
id = $xc.playback_current_id.wait.value
|
647
|
+
info = $xc.extract_medialib_info(id, :duration)
|
648
|
+
dur = info[:duration].to_i
|
649
|
+
interval_size = dur / 10
|
650
|
+
times = []
|
651
|
+
times << ["start", 0]
|
652
|
+
Range.new(0,dur).step(interval_size).each do |x|
|
653
|
+
times << [x.ms_to_time_string, x]
|
654
|
+
end
|
655
|
+
times << ["end", dur]
|
656
|
+
seek_location = my_dmenu(times.map{|x| x[0]}).to_s
|
657
|
+
# FIXME: This math is clearly janky. Sadly I have actual work to do.
|
658
|
+
seek = 20 * time_string_to_ms(seek_location)
|
659
|
+
puts $xc.playback_seek_ms(seek).wait.value
|
660
|
+
$stdout.flush
|
661
|
+
break
|
662
|
+
else
|
663
|
+
if not command.empty?
|
664
|
+
`xmms2 #{command}`
|
665
|
+
end
|
666
|
+
break
|
667
|
+
end
|
668
|
+
end
|
data/dxmms2.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "dxmms2"
|
5
|
+
spec.version = "1.0.0"
|
6
|
+
spec.authors = ["Mark Watts"]
|
7
|
+
spec.email = ["wattsmark2015@gmail.com"]
|
8
|
+
spec.summary = %q{A client for working with xmms2}
|
9
|
+
spec.description = File.new("./README.md").read()
|
10
|
+
spec.homepage = "http://github.com/mwatts15/xmms2-stuff"
|
11
|
+
spec.license = "MPL-2.0"
|
12
|
+
|
13
|
+
spec.files = `git ls-files -z`.split("\x0")
|
14
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
15
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
|
+
spec.require_paths = ["."]
|
17
|
+
|
18
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
19
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
20
|
+
spec.add_runtime_dependency "glib2", "~> 3.1.0"
|
21
|
+
spec.add_runtime_dependency "xmms2_utils", "~> 0.1.2"
|
22
|
+
spec.add_runtime_dependency "markw-dmenu", "~> 1.0.0"
|
23
|
+
end
|
data/dxmms2.png
ADDED
Binary file
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dxmms2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mark Watts
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: glib2
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.1.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.1.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: xmms2_utils
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.1.2
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.1.2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: markw-dmenu
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.0.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.0.0
|
83
|
+
description: "# dxmms2\n*a dmenu shell for xmms2*\n\nSetting the dmenu width in dxmms2
|
84
|
+
requires the patch provided in this repo. The \npatch hasn't been tested--application
|
85
|
+
may require manual effort.\n\n \n"
|
86
|
+
email:
|
87
|
+
- wattsmark2015@gmail.com
|
88
|
+
executables:
|
89
|
+
- dxmms2
|
90
|
+
extensions: []
|
91
|
+
extra_rdoc_files: []
|
92
|
+
files:
|
93
|
+
- Gemfile
|
94
|
+
- Makefile
|
95
|
+
- README.md
|
96
|
+
- bin/dxmms2
|
97
|
+
- dxmms2.gemspec
|
98
|
+
- dxmms2.png
|
99
|
+
homepage: http://github.com/mwatts15/xmms2-stuff
|
100
|
+
licenses:
|
101
|
+
- MPL-2.0
|
102
|
+
metadata: {}
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- "."
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 2.2.2
|
120
|
+
signing_key:
|
121
|
+
specification_version: 4
|
122
|
+
summary: A client for working with xmms2
|
123
|
+
test_files: []
|