added README
[mpdhero.git] / mpdhero
blobb9d64908c3fb27219a9fe057b9f8d38eae4dedb4
1 #!/usr/bin/ruby
2 version = '0.0.0'
4 require 'pathname'
5 require 'optparse'
6 require 'ostruct'
8 $: << MYDIR = File.dirname(Pathname(__FILE__).realpath)
9 require 'mpd'
10 require 'classes'
12 conf = OpenStruct.new
13 conf.n = 1
14 oparser = nil
15 o = OptionParser.new do |opts|
16 oparser = opts
17 opts.banner = "Usage: mpdhero [options]"
18 opts.on("--version", "display version") do |v| puts version; exit end
19 opts.on("-v", "--[no-]verbose", "print information about what is happening") do |v|
20 conf.verbose = v
21 end
22 opts.on("-q", "--[no-]quiet", "supress warnings") do |v|
23 conf.quiet = v
24 end
25 opts.on("-R", "--[no-]more-randomness", "add more randomness in some operations") do |v|
26 conf.more_randomness = v
27 end
28 opts.on("-a", "--shuffle-albums", "shuffle the playlist but keep albums intact") do |v|
29 conf.mode = :shuffle_albums
30 end
31 opts.on("-k", "--shuffle-chunks", "shuffle 20-minute chunks of albums") do |v|
32 conf.mode = :shuffle_chunks
33 end
34 opts.on("-A", "--shuffle-artists", "shuffle the playlist but keep artists intact") do |v|
35 conf.mode = :shuffle_artists
36 end
37 opts.on("-b", "--add-random-album [PATH]", String, "adds a random album") do |v|
38 conf.mode = :add_random_album
39 conf.random_album_path = v
40 end
41 opts.on("-d", "--add-random-song [PATH]", String, "adds a random song") do |v|
42 conf.mode = :add_random_song
43 conf.random_song_path = v
44 end
45 opts.on("-n", "--number n", Numeric, "specifies a number") do |v|
46 conf.n = v
47 end
48 opts.on("-l", "--log [n]", Numeric, "delay the addition of songs by log n (x) minutes") do |v|
49 conf.log = v || 1.3
50 end
51 opts.on("-P", "--play", "start playing") do |v|
52 conf.play = true
53 end
54 opts.on("-C", "--clear", "clear playlist before doing anything") do |v|
55 conf.clear = true
56 end
57 opts.on("-c", "--[no-]current-first", "put current playling song/album first, when shuffling") do |v|
58 conf.current_first = v
59 end
60 opts.on("-h", "--host IP", String, "the mpd server, defaults to MPD_HOST || localhost") do |v|
61 conf.host = v
62 end
63 opts.on("-p", "--port PORT", Integer, "the mpd server port, defaults to MPD_PORT || 6600") do |v|
64 conf.port = v
65 end
66 opts.on("-r", "--remove-current", "removes the current entry/entries if something is added") do |v|
67 conf.remove_current = v
68 end
69 end
71 o.parse! rescue abort $!.name, $!.msg
74 puts oparser.help or exit if !conf.mode
75 $stderr.reopen('/dev/null', 'w') if conf.quiet
76 $stdout.reopen('/dev/null', 'w') if !conf.verbose
77 Mpd = MPD.new(conf.host, conf.port)
78 Mpd.connect rescue abort "failed to connect"
80 def sleep_log(base, x)
81 return if base.nil?
82 n = Math.log(x+1) / Math.log(base) * 60.0
83 puts "i sleep #{n}"
84 sleep n
85 #rescue
86 # p $!
87 # nil
88 end
90 def splitter(list, average) #{{{
91 chunks = []
92 average = average.to_f
94 for name, album in list
95 total_time = album.time
96 parts = [[ (total_time / average).round, album.length ].min , 1].max
97 avg = total_time / parts
98 part = []
100 stack = Array.new(album)
101 while item = stack.shift
102 if part.empty?
103 part << item
104 elsif part.time < avg
105 if part.time + item.time.to_i < avg * 1.5
106 part << item
107 else
108 chunks << part
109 part = [item]
111 else
112 chunks << part
113 part = [item]
116 if not part.empty?
117 chunks << part
119 # puts "##{album.length} #{total_time} (#{total_time/60}m) / #{parts}"
120 # for chunk in chunks do print "##{chunk.length} #{chunk.time/60}m -- " end
121 # puts
123 return chunks
124 end #}}}
126 Mpd.clear if conf.clear
128 case conf.mode
129 when :shuffle_albums
132 ## albums will be {"album1" => [track3, track1, track2], "album2" => [...], ...}
133 albums = {}
134 for track in Mpd.playlistinfo do
135 (albums[track.album] ||= []) << track
138 ## make sure tracks within albums are sorted
139 for key, album in albums do
140 album.sort! {|a, b| a.track <=> b.track}
143 list = albums.keys.shuffle!
145 if conf.current_first
146 list.swap!(0, list.index(Mpd.currentsong.album))
149 ## shuffle!
150 i, arr = -1, []
151 for album_key in list do
152 puts "%-30s - %s" % [albums[album_key].first.artist, album_key] if conf.verbose
153 for track in albums[album_key] do
154 arr << [track.dbid, i+=1]
155 # Mpd.moveid(track.dbid, i) if track.dbid != i
158 Mpd.move_pairs(*arr)
159 Mpd.play if conf.play
161 when :shuffle_chunks
163 ## albums will be {"album1" => [track3, track1, track2], "album2" => [...], ...}
164 albums = {}
165 for track in Mpd.playlistinfo do
166 (albums[File.dirname(track.file)] ||= []) << track
169 # require 'pp'
170 # pp albums
171 # abort
173 if conf.more_randomness
174 ## shuffle album tracks
175 for key, album in albums do
176 album.shuffle!
178 else
179 ## make sure tracks within albums are sorted
180 for key, album in albums do
181 album.sort! {|a, b| (a.track || '') <=> (b.track || '')}
185 list = splitter(albums, 1200)
186 list.shuffle!
188 if conf.current_first
189 list.each_with_index do |chunk, i|
190 if chunk.include? Mpd.currentsong
191 list.swap!(0, i)
192 break
197 ## shuffle!
198 i, arr = -1, []
199 for chunk in list do
200 puts "%-30s - %s" % [chunk.first.artist, chunk.first.album] if conf.verbose
201 for track in chunk do
202 arr << [track.dbid, i+=1]
203 # Mpd.moveid(track.dbid, i) if track.dbid != i
206 Mpd.move_pairs(*arr)
207 Mpd.play if conf.play
209 when :shuffle_artists
211 ## artists will be {"album1" => [track3, track1, track2], "album2" => [...], ...}
212 artists = {}
213 for track in Mpd.playlistinfo do
214 (artists[track.artist] ||= []) << track
217 ## make sure tracks within artists are sorted
218 # for key, album in artists do
219 # album.sort! {|a, b| a.track <=> b.track}
220 # end
222 list = artists.keys.shuffle!
224 if conf.current_first
225 list.swap!(0, list.index(Mpd.currentsong.artist))
228 ## shuffle!
229 i, arr = -1, []
230 for artist_key in list do
231 puts "%-30s - %s" % [artists[artist_key].first.artist, artist_key] if conf.verbose
232 for track in artists[artist_key] do
233 arr << [track.dbid, i+=1]
234 # Mpd.moveid(track.dbid, i) if track.dbid != i
237 Mpd.move_pairs(*arr)
238 Mpd.play if conf.play
240 when :add_random_album
242 # get a list of all files in the given path
243 p conf
244 all = Mpd.listall(conf.random_album_path || '/')
246 conf.n.times do |x|
247 # get a random song and find all in the same album
248 one = Mpd.find("filename", all.random).first
249 found = Mpd.find('album', one.album)
251 # remove unwanted entries (duplicates, other artist with same album)
252 memory = []
253 found = found.select do |this|
254 if this.artist == one.artist and !memory.include?(this.title)
255 memory << this.title
256 true
257 else
258 false
262 # add them
263 found.each do |this|
264 Mpd.add this.file
267 Mpd.play if conf.play && x == 0
268 sleep_log(conf.log, x)
271 when :add_random_song
273 all = Mpd.listall(conf.random_song_path || '/')
274 if (conf.remove_current)
275 Mpd.delete(Mpd.currentsong.pos)
277 xstart = Mpd.playlistlength
278 conf.n.times do |x|
279 Mpd.add(Mpd.find("filename", all.random).first.file)
280 Mpd.play if conf.play && x == 0
281 sleep_log(conf.log, x + xstart)
284 else
285 puts "no mode specified"