rev |
line source |
meillo@0
|
1 #!/usr/bin/env python
|
meillo@0
|
2 # -*- python -*-
|
meillo@0
|
3
|
meillo@0
|
4 __version__ = "cplay 1.49"
|
meillo@0
|
5
|
meillo@0
|
6 """
|
meillo@0
|
7 cplay - A curses front-end for various audio players
|
meillo@0
|
8 Copyright (C) 1998-2003 Ulf Betlehem <flu@iki.fi>
|
meillo@0
|
9
|
meillo@0
|
10 This program is free software; you can redistribute it and/or
|
meillo@0
|
11 modify it under the terms of the GNU General Public License
|
meillo@0
|
12 as published by the Free Software Foundation; either version 2
|
meillo@0
|
13 of the License, or (at your option) any later version.
|
meillo@0
|
14
|
meillo@0
|
15 This program is distributed in the hope that it will be useful,
|
meillo@0
|
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
meillo@0
|
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
meillo@0
|
18 GNU General Public License for more details.
|
meillo@0
|
19
|
meillo@0
|
20 You should have received a copy of the GNU General Public License
|
meillo@0
|
21 along with this program; if not, write to the Free Software
|
meillo@0
|
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
meillo@0
|
23 """
|
meillo@0
|
24
|
meillo@0
|
25 # ------------------------------------------
|
meillo@0
|
26 from types import *
|
meillo@0
|
27
|
meillo@0
|
28 import os
|
meillo@0
|
29 import sys
|
meillo@0
|
30 import time
|
meillo@0
|
31 import getopt
|
meillo@0
|
32 import signal
|
meillo@0
|
33 import string
|
meillo@0
|
34 import select
|
meillo@0
|
35 import re
|
meillo@0
|
36
|
meillo@0
|
37 try: from ncurses import curses
|
meillo@0
|
38 except ImportError: import curses
|
meillo@0
|
39
|
meillo@0
|
40 try: import tty
|
meillo@0
|
41 except ImportError: tty = None
|
meillo@0
|
42
|
meillo@0
|
43 try: import locale; locale.setlocale(locale.LC_ALL, "")
|
meillo@0
|
44 except: pass
|
meillo@0
|
45
|
meillo@0
|
46 # ------------------------------------------
|
meillo@0
|
47 _locale_domain = "cplay"
|
meillo@0
|
48 _locale_dir = "/usr/local/share/locale"
|
meillo@0
|
49
|
meillo@0
|
50 try:
|
meillo@0
|
51 import gettext # python 2.0
|
meillo@0
|
52 gettext.install(_locale_domain, _locale_dir)
|
meillo@0
|
53 except ImportError:
|
meillo@0
|
54 try:
|
meillo@0
|
55 import fintl
|
meillo@0
|
56 fintl.bindtextdomain(_locale_domain, _locale_dir)
|
meillo@0
|
57 fintl.textdomain(_locale_domain)
|
meillo@0
|
58 _ = fintl.gettext
|
meillo@0
|
59 except ImportError:
|
meillo@0
|
60 def _(s): return s
|
meillo@0
|
61 except:
|
meillo@0
|
62 def _(s): return s
|
meillo@0
|
63
|
meillo@0
|
64 # ------------------------------------------
|
meillo@0
|
65 XTERM = re.search("rxvt|xterm", os.environ["TERM"])
|
meillo@0
|
66 CONTROL_FIFO = "/var/tmp/cplay_control"
|
meillo@0
|
67
|
meillo@0
|
68 # ------------------------------------------
|
meillo@0
|
69 def which(program):
|
meillo@0
|
70 for path in string.split(os.environ["PATH"], ":"):
|
meillo@0
|
71 if os.path.exists(os.path.join(path, program)):
|
meillo@0
|
72 return os.path.join(path, program)
|
meillo@0
|
73
|
meillo@0
|
74 # ------------------------------------------
|
meillo@0
|
75 def cut(s, n, left=0):
|
meillo@0
|
76 if left: return len(s) > n and "<%s" % s[-n+1:] or s
|
meillo@0
|
77 else: return len(s) > n and "%s>" % s[:n-1] or s
|
meillo@0
|
78
|
meillo@0
|
79 # ------------------------------------------
|
meillo@0
|
80 class Stack:
|
meillo@0
|
81 def __init__(self):
|
meillo@0
|
82 self.items = ()
|
meillo@0
|
83
|
meillo@0
|
84 def push(self, item):
|
meillo@0
|
85 self.items = (item,) + self.items
|
meillo@0
|
86
|
meillo@0
|
87 def pop(self):
|
meillo@0
|
88 self.items, item = self.items[1:], self.items[0]
|
meillo@0
|
89 return item
|
meillo@0
|
90
|
meillo@0
|
91 # ------------------------------------------
|
meillo@0
|
92 class KeymapStack(Stack):
|
meillo@0
|
93 def process(self, code):
|
meillo@0
|
94 for keymap in self.items:
|
meillo@0
|
95 if keymap and keymap.process(code):
|
meillo@0
|
96 break
|
meillo@0
|
97
|
meillo@0
|
98 # ------------------------------------------
|
meillo@0
|
99 class Keymap:
|
meillo@0
|
100 def __init__(self):
|
meillo@0
|
101 self.methods = [None] * curses.KEY_MAX
|
meillo@0
|
102
|
meillo@0
|
103 def bind(self, key, method, args=None):
|
meillo@0
|
104 if type(key) in (TupleType, ListType):
|
meillo@0
|
105 for i in key: self.bind(i, method, args)
|
meillo@0
|
106 return
|
meillo@0
|
107 if type(key) is StringType:
|
meillo@0
|
108 key = ord(key)
|
meillo@0
|
109 self.methods[key] = (method, args)
|
meillo@0
|
110
|
meillo@0
|
111 def process(self, key):
|
meillo@0
|
112 if self.methods[key] is None: return 0
|
meillo@0
|
113 method, args = self.methods[key]
|
meillo@0
|
114 if args is None:
|
meillo@0
|
115 apply(method, (key,))
|
meillo@0
|
116 else:
|
meillo@0
|
117 apply(method, args)
|
meillo@0
|
118 return 1
|
meillo@0
|
119
|
meillo@0
|
120 # ------------------------------------------
|
meillo@0
|
121 class Window:
|
meillo@0
|
122 chars = string.letters+string.digits+string.punctuation+string.whitespace
|
meillo@0
|
123
|
meillo@0
|
124 t = ['?'] * 256
|
meillo@0
|
125 for c in chars: t[ord(c)] = c
|
meillo@0
|
126 translationTable = string.join(t, ""); del t
|
meillo@0
|
127
|
meillo@0
|
128 def __init__(self, parent):
|
meillo@0
|
129 self.parent = parent
|
meillo@0
|
130 self.children = []
|
meillo@0
|
131 self.name = None
|
meillo@0
|
132 self.keymap = None
|
meillo@0
|
133 self.visible = 1
|
meillo@0
|
134 self.resize()
|
meillo@0
|
135 if parent: parent.children.append(self)
|
meillo@0
|
136
|
meillo@0
|
137 def insstr(self, s):
|
meillo@0
|
138 if not s: return
|
meillo@0
|
139 self.w.addstr(s[:-1])
|
meillo@0
|
140 self.w.hline(ord(s[-1]), 1) # insch() work-around
|
meillo@0
|
141
|
meillo@0
|
142 def __getattr__(self, name):
|
meillo@0
|
143 return getattr(self.w, name)
|
meillo@0
|
144
|
meillo@0
|
145 def getmaxyx(self):
|
meillo@0
|
146 y, x = self.w.getmaxyx()
|
meillo@0
|
147 try: curses.version # tested with 1.2 and 1.6
|
meillo@0
|
148 except AttributeError:
|
meillo@0
|
149 # pyncurses - emulate traditional (silly) behavior
|
meillo@0
|
150 y, x = y+1, x+1
|
meillo@0
|
151 return y, x
|
meillo@0
|
152
|
meillo@0
|
153 def touchwin(self):
|
meillo@0
|
154 try: self.w.touchwin()
|
meillo@0
|
155 except AttributeError: self.touchln(0, self.getmaxyx()[0])
|
meillo@0
|
156
|
meillo@0
|
157 def attron(self, attr):
|
meillo@0
|
158 try: self.w.attron(attr)
|
meillo@0
|
159 except AttributeError: self.w.attr_on(attr)
|
meillo@0
|
160
|
meillo@0
|
161 def attroff(self, attr):
|
meillo@0
|
162 try: self.w.attroff(attr)
|
meillo@0
|
163 except AttributeError: self.w.attr_off(attr)
|
meillo@0
|
164
|
meillo@0
|
165 def newwin(self):
|
meillo@0
|
166 return curses.newwin(0, 0, 0, 0)
|
meillo@0
|
167
|
meillo@0
|
168 def resize(self):
|
meillo@0
|
169 self.w = self.newwin()
|
meillo@0
|
170 self.ypos, self.xpos = self.getbegyx()
|
meillo@0
|
171 self.rows, self.cols = self.getmaxyx()
|
meillo@0
|
172 self.keypad(1)
|
meillo@0
|
173 self.leaveok(0)
|
meillo@0
|
174 self.scrollok(0)
|
meillo@0
|
175 for child in self.children:
|
meillo@0
|
176 child.resize()
|
meillo@0
|
177
|
meillo@0
|
178 def update(self):
|
meillo@0
|
179 self.clear()
|
meillo@0
|
180 self.refresh()
|
meillo@0
|
181 for child in self.children:
|
meillo@0
|
182 child.update()
|
meillo@0
|
183
|
meillo@0
|
184 # ------------------------------------------
|
meillo@0
|
185 class ProgressWindow(Window):
|
meillo@0
|
186 def __init__(self, parent):
|
meillo@0
|
187 Window.__init__(self, parent)
|
meillo@0
|
188 self.value = 0
|
meillo@0
|
189
|
meillo@0
|
190 def newwin(self):
|
meillo@0
|
191 return curses.newwin(1, self.parent.cols, self.parent.rows-2, 0)
|
meillo@0
|
192
|
meillo@0
|
193 def update(self):
|
meillo@0
|
194 self.move(0, 0)
|
meillo@0
|
195 self.hline(ord('-'), self.cols)
|
meillo@0
|
196 if self.value > 0:
|
meillo@0
|
197 self.move(0, 0)
|
meillo@0
|
198 x = int(self.value * self.cols) # 0 to cols-1
|
meillo@0
|
199 x and self.hline(ord('='), x)
|
meillo@0
|
200 self.move(0, x)
|
meillo@0
|
201 self.insstr('|')
|
meillo@0
|
202 self.touchwin()
|
meillo@0
|
203 self.refresh()
|
meillo@0
|
204
|
meillo@0
|
205 def progress(self, value):
|
meillo@0
|
206 self.value = min(value, 0.99)
|
meillo@0
|
207 self.update()
|
meillo@0
|
208
|
meillo@0
|
209 # ------------------------------------------
|
meillo@0
|
210 class StatusWindow(Window):
|
meillo@0
|
211 def __init__(self, parent):
|
meillo@0
|
212 Window.__init__(self, parent)
|
meillo@0
|
213 self.default_message = ''
|
meillo@0
|
214 self.current_message = ''
|
meillo@0
|
215 self.tid = None
|
meillo@0
|
216
|
meillo@0
|
217 def newwin(self):
|
meillo@0
|
218 return curses.newwin(1, self.parent.cols-12, self.parent.rows-1, 0)
|
meillo@0
|
219
|
meillo@0
|
220 def update(self):
|
meillo@0
|
221 msg = string.translate(self.current_message, Window.translationTable)
|
meillo@0
|
222 self.move(0, 0)
|
meillo@0
|
223 self.clrtoeol()
|
meillo@0
|
224 self.insstr(cut(msg, self.cols))
|
meillo@0
|
225 self.touchwin()
|
meillo@0
|
226 self.refresh()
|
meillo@0
|
227
|
meillo@0
|
228 def status(self, message, duration = 0):
|
meillo@0
|
229 self.current_message = str(message)
|
meillo@0
|
230 if self.tid: app.timeout.remove(self.tid)
|
meillo@0
|
231 if duration: self.tid = app.timeout.add(duration, self.timeout)
|
meillo@0
|
232 else: self.tid = None
|
meillo@0
|
233 self.update()
|
meillo@0
|
234
|
meillo@0
|
235 def timeout(self):
|
meillo@0
|
236 self.tid = None
|
meillo@0
|
237 self.restore_default_status()
|
meillo@0
|
238
|
meillo@0
|
239 def set_default_status(self, message):
|
meillo@0
|
240 if self.current_message == self.default_message: self.status(message)
|
meillo@0
|
241 self.default_message = message
|
meillo@0
|
242 XTERM and sys.stderr.write("\033]0;%s\a" % (message or "cplay"))
|
meillo@0
|
243
|
meillo@0
|
244 def restore_default_status(self):
|
meillo@0
|
245 self.status(self.default_message)
|
meillo@0
|
246
|
meillo@0
|
247 # ------------------------------------------
|
meillo@0
|
248 class CounterWindow(Window):
|
meillo@0
|
249 def __init__(self, parent):
|
meillo@0
|
250 Window.__init__(self, parent)
|
meillo@0
|
251 self.values = [0, 0]
|
meillo@0
|
252 self.mode = 1
|
meillo@0
|
253
|
meillo@0
|
254 def newwin(self):
|
meillo@0
|
255 return curses.newwin(1, 11, self.parent.rows-1, self.parent.cols-11)
|
meillo@0
|
256
|
meillo@0
|
257 def update(self):
|
meillo@0
|
258 h, s = divmod(self.values[self.mode], 3600)
|
meillo@0
|
259 m, s = divmod(s, 60)
|
meillo@0
|
260 self.move(0, 0)
|
meillo@0
|
261 self.attron(curses.A_BOLD)
|
meillo@0
|
262 self.insstr("%02dh %02dm %02ds" % (h, m, s))
|
meillo@0
|
263 self.attroff(curses.A_BOLD)
|
meillo@0
|
264 self.touchwin()
|
meillo@0
|
265 self.refresh()
|
meillo@0
|
266
|
meillo@0
|
267 def counter(self, values):
|
meillo@0
|
268 self.values = values
|
meillo@0
|
269 self.update()
|
meillo@0
|
270
|
meillo@0
|
271 def toggle_mode(self):
|
meillo@0
|
272 self.mode = not self.mode
|
meillo@0
|
273 tmp = [_("elapsed"), _("remaining")][self.mode]
|
meillo@0
|
274 app.status(_("Counting %s time") % tmp, 1)
|
meillo@0
|
275 self.update()
|
meillo@0
|
276
|
meillo@0
|
277 # ------------------------------------------
|
meillo@0
|
278 class RootWindow(Window):
|
meillo@0
|
279 def __init__(self, parent):
|
meillo@0
|
280 Window.__init__(self, parent)
|
meillo@0
|
281 keymap = Keymap()
|
meillo@0
|
282 app.keymapstack.push(keymap)
|
meillo@0
|
283 self.win_progress = ProgressWindow(self)
|
meillo@0
|
284 self.win_status = StatusWindow(self)
|
meillo@0
|
285 self.win_counter = CounterWindow(self)
|
meillo@0
|
286 self.win_tab = TabWindow(self)
|
meillo@0
|
287 keymap.bind(12, self.update, ()) # C-l
|
meillo@0
|
288 keymap.bind([curses.KEY_LEFT, 2], app.seek, (-1, 1)) # C-b
|
meillo@0
|
289 keymap.bind([curses.KEY_RIGHT, 6], app.seek, (1, 1)) # C-f
|
meillo@0
|
290 keymap.bind([1, '^'], app.seek, (0, 0)) # C-a
|
meillo@0
|
291 keymap.bind([5, '$'], app.seek, (-1, 0)) # C-e
|
meillo@0
|
292 keymap.bind(range(48,58), app.key_volume) # 0123456789
|
meillo@0
|
293 keymap.bind(['+', '='], app.inc_volume, ())
|
meillo@0
|
294 keymap.bind('-', app.dec_volume, ())
|
meillo@0
|
295 keymap.bind('n', app.next_song, ())
|
meillo@0
|
296 keymap.bind('p', app.prev_song, ())
|
meillo@0
|
297 keymap.bind('z', app.toggle_pause, ())
|
meillo@0
|
298 keymap.bind('x', app.toggle_stop, ())
|
meillo@0
|
299 keymap.bind('c', self.win_counter.toggle_mode, ())
|
meillo@0
|
300 keymap.bind('Q', app.quit, ())
|
meillo@0
|
301 keymap.bind('q', self.command_quit, ())
|
meillo@0
|
302 keymap.bind('v', app.mixer, ("toggle",))
|
meillo@0
|
303
|
meillo@0
|
304 def command_quit(self):
|
meillo@0
|
305 app.do_input_hook = self.do_quit
|
meillo@0
|
306 app.start_input(_("Quit? (y/N)"))
|
meillo@0
|
307
|
meillo@0
|
308 def do_quit(self, ch):
|
meillo@0
|
309 if chr(ch) == 'y': app.quit()
|
meillo@0
|
310 app.stop_input()
|
meillo@0
|
311
|
meillo@0
|
312 # ------------------------------------------
|
meillo@0
|
313 class TabWindow(Window):
|
meillo@0
|
314 def __init__(self, parent):
|
meillo@0
|
315 Window.__init__(self, parent)
|
meillo@0
|
316 self.active_child = 0
|
meillo@0
|
317
|
meillo@0
|
318 self.win_filelist = self.add(FilelistWindow)
|
meillo@0
|
319 self.win_playlist = self.add(PlaylistWindow)
|
meillo@0
|
320 self.win_help = self.add(HelpWindow)
|
meillo@0
|
321
|
meillo@0
|
322 keymap = Keymap()
|
meillo@0
|
323 keymap.bind('\t', self.change_window, ()) # tab
|
meillo@0
|
324 keymap.bind('h', self.help, ())
|
meillo@0
|
325 app.keymapstack.push(keymap)
|
meillo@0
|
326 app.keymapstack.push(self.children[self.active_child].keymap)
|
meillo@0
|
327
|
meillo@0
|
328 def newwin(self):
|
meillo@0
|
329 return curses.newwin(self.parent.rows-2, self.parent.cols, 0, 0)
|
meillo@0
|
330
|
meillo@0
|
331 def update(self):
|
meillo@0
|
332 self.update_title()
|
meillo@0
|
333 self.move(1, 0)
|
meillo@0
|
334 self.hline(ord('-'), self.cols)
|
meillo@0
|
335 self.move(2, 0)
|
meillo@0
|
336 self.clrtobot()
|
meillo@0
|
337 self.refresh()
|
meillo@0
|
338 child = self.children[self.active_child]
|
meillo@0
|
339 child.visible = 1
|
meillo@0
|
340 child.update()
|
meillo@0
|
341
|
meillo@0
|
342 def update_title(self, refresh = 1):
|
meillo@0
|
343 child = self.children[self.active_child]
|
meillo@0
|
344 self.move(0, 0)
|
meillo@0
|
345 self.clrtoeol()
|
meillo@0
|
346 self.attron(curses.A_BOLD)
|
meillo@0
|
347 self.insstr(child.get_title())
|
meillo@0
|
348 self.attroff(curses.A_BOLD)
|
meillo@0
|
349 if refresh: self.refresh()
|
meillo@0
|
350
|
meillo@0
|
351 def add(self, Class):
|
meillo@0
|
352 win = Class(self)
|
meillo@0
|
353 win.visible = 0
|
meillo@0
|
354 return win
|
meillo@0
|
355
|
meillo@0
|
356 def change_window(self, window = None):
|
meillo@0
|
357 app.keymapstack.pop()
|
meillo@0
|
358 self.children[self.active_child].visible = 0
|
meillo@0
|
359 if window:
|
meillo@0
|
360 self.active_child = self.children.index(window)
|
meillo@0
|
361 else:
|
meillo@0
|
362 # toggle windows 0 and 1
|
meillo@0
|
363 self.active_child = not self.active_child
|
meillo@0
|
364 app.keymapstack.push(self.children[self.active_child].keymap)
|
meillo@0
|
365 self.update()
|
meillo@0
|
366
|
meillo@0
|
367 def help(self):
|
meillo@0
|
368 if self.children[self.active_child] == self.win_help:
|
meillo@0
|
369 self.change_window(self.win_last)
|
meillo@0
|
370 else:
|
meillo@0
|
371 self.win_last = self.children[self.active_child]
|
meillo@0
|
372 self.change_window(self.win_help)
|
meillo@0
|
373 app.status(__version__, 2)
|
meillo@0
|
374
|
meillo@0
|
375 # ------------------------------------------
|
meillo@0
|
376 class ListWindow(Window):
|
meillo@0
|
377 def __init__(self, parent):
|
meillo@0
|
378 Window.__init__(self, parent)
|
meillo@0
|
379 self.buffer = []
|
meillo@0
|
380 self.bufptr = self.scrptr = 0
|
meillo@0
|
381 self.search_direction = 0
|
meillo@0
|
382 self.last_search = ""
|
meillo@0
|
383 self.hoffset = 0
|
meillo@0
|
384 self.keymap = Keymap()
|
meillo@0
|
385 self.keymap.bind(['k', curses.KEY_UP, 16], self.cursor_move, (-1,))
|
meillo@0
|
386 self.keymap.bind(['j', curses.KEY_DOWN, 14], self.cursor_move, (1,))
|
meillo@0
|
387 self.keymap.bind(['K', curses.KEY_PPAGE], self.cursor_ppage, ())
|
meillo@0
|
388 self.keymap.bind(['J', curses.KEY_NPAGE], self.cursor_npage, ())
|
meillo@0
|
389 self.keymap.bind(['g', curses.KEY_HOME], self.cursor_home, ())
|
meillo@0
|
390 self.keymap.bind(['G', curses.KEY_END], self.cursor_end, ())
|
meillo@0
|
391 self.keymap.bind(['?', 18], self.start_search,
|
meillo@0
|
392 (_("backward-isearch"), -1))
|
meillo@0
|
393 self.keymap.bind(['/', 19], self.start_search,
|
meillo@0
|
394 (_("forward-isearch"), 1))
|
meillo@0
|
395 self.keymap.bind(['>'], self.hscroll, (8,))
|
meillo@0
|
396 self.keymap.bind(['<'], self.hscroll, (-8,))
|
meillo@0
|
397
|
meillo@0
|
398 def newwin(self):
|
meillo@0
|
399 return curses.newwin(self.parent.rows-2, self.parent.cols,
|
meillo@0
|
400 self.parent.ypos+2, self.parent.xpos)
|
meillo@0
|
401
|
meillo@0
|
402 def update(self, force = 1):
|
meillo@0
|
403 self.bufptr = max(0, min(self.bufptr, len(self.buffer) - 1))
|
meillo@0
|
404 scrptr = (self.bufptr / self.rows) * self.rows
|
meillo@0
|
405 if force or self.scrptr != scrptr:
|
meillo@0
|
406 self.scrptr = scrptr
|
meillo@0
|
407 self.move(0, 0)
|
meillo@0
|
408 self.clrtobot()
|
meillo@0
|
409 i = 0
|
meillo@0
|
410 for entry in self.buffer[self.scrptr:]:
|
meillo@0
|
411 self.move(i, 0)
|
meillo@0
|
412 i = i + 1
|
meillo@0
|
413 self.putstr(entry)
|
meillo@0
|
414 if self.getyx()[0] == self.rows - 1: break
|
meillo@0
|
415 if self.visible:
|
meillo@0
|
416 self.refresh()
|
meillo@0
|
417 self.parent.update_title()
|
meillo@0
|
418 self.update_line(curses.A_REVERSE)
|
meillo@0
|
419
|
meillo@0
|
420 def update_line(self, attr = None, refresh = 1):
|
meillo@0
|
421 if not self.buffer: return
|
meillo@0
|
422 ypos = self.bufptr - self.scrptr
|
meillo@0
|
423 if attr: self.attron(attr)
|
meillo@0
|
424 self.move(ypos, 0)
|
meillo@0
|
425 self.hline(ord(' '), self.cols)
|
meillo@0
|
426 self.putstr(self.current())
|
meillo@0
|
427 if attr: self.attroff(attr)
|
meillo@0
|
428 if self.visible and refresh: self.refresh()
|
meillo@0
|
429
|
meillo@0
|
430 def get_title(self, data=""):
|
meillo@0
|
431 pos = "%s-%s/%s" % (self.scrptr+min(1, len(self.buffer)),
|
meillo@0
|
432 min(self.scrptr+self.rows, len(self.buffer)),
|
meillo@0
|
433 len(self.buffer))
|
meillo@0
|
434 width = self.cols-len(pos)-2
|
meillo@0
|
435 data = cut(data, width-len(self.name), 1)
|
meillo@0
|
436 return "%-*s %s" % (width, cut(self.name+data, width), pos)
|
meillo@0
|
437
|
meillo@0
|
438 def putstr(self, entry, *pos):
|
meillo@0
|
439 s = string.translate(str(entry), Window.translationTable)
|
meillo@0
|
440 pos and apply(self.move, pos)
|
meillo@0
|
441 if self.hoffset: s = "<%s" % s[self.hoffset+1:]
|
meillo@0
|
442 self.insstr(cut(s, self.cols))
|
meillo@0
|
443
|
meillo@0
|
444 def current(self):
|
meillo@0
|
445 if self.bufptr >= len(self.buffer): self.bufptr = len(self.buffer) - 1
|
meillo@0
|
446 return self.buffer[self.bufptr]
|
meillo@0
|
447
|
meillo@0
|
448 def cursor_move(self, ydiff):
|
meillo@0
|
449 if app.input_mode: app.cancel_input()
|
meillo@0
|
450 if not self.buffer: return
|
meillo@0
|
451 self.update_line(refresh = 0)
|
meillo@0
|
452 self.bufptr = (self.bufptr + ydiff) % len(self.buffer)
|
meillo@0
|
453 self.update(force = 0)
|
meillo@0
|
454
|
meillo@0
|
455 def cursor_ppage(self):
|
meillo@0
|
456 tmp = self.bufptr % self.rows
|
meillo@0
|
457 if tmp == self.bufptr:
|
meillo@0
|
458 self.cursor_move(-(tmp+(len(self.buffer) % self.rows) or self.rows))
|
meillo@0
|
459 else:
|
meillo@0
|
460 self.cursor_move(-(tmp+self.rows))
|
meillo@0
|
461
|
meillo@0
|
462 def cursor_npage(self):
|
meillo@0
|
463 tmp = self.rows - self.bufptr % self.rows
|
meillo@0
|
464 if self.bufptr + tmp > len(self.buffer):
|
meillo@0
|
465 self.cursor_move(len(self.buffer) - self.bufptr)
|
meillo@0
|
466 else:
|
meillo@0
|
467 self.cursor_move(tmp)
|
meillo@0
|
468
|
meillo@0
|
469 def cursor_home(self): self.cursor_move(-self.bufptr)
|
meillo@0
|
470
|
meillo@0
|
471 def cursor_end(self): self.cursor_move(-self.bufptr - 1)
|
meillo@0
|
472
|
meillo@0
|
473 def start_search(self, type, direction):
|
meillo@0
|
474 self.search_direction = direction
|
meillo@0
|
475 self.not_found = 0
|
meillo@0
|
476 if app.input_mode:
|
meillo@0
|
477 app.input_prompt = "%s: " % type
|
meillo@0
|
478 self.do_search(advance = direction)
|
meillo@0
|
479 else:
|
meillo@0
|
480 app.do_input_hook = self.do_search
|
meillo@0
|
481 app.stop_input_hook = self.stop_search
|
meillo@0
|
482 app.start_input(type)
|
meillo@0
|
483
|
meillo@0
|
484 def stop_search(self):
|
meillo@0
|
485 self.last_search = app.input_string
|
meillo@0
|
486 app.status(_("ok"), 1)
|
meillo@0
|
487
|
meillo@0
|
488 def do_search(self, ch = None, advance = 0):
|
meillo@0
|
489 if ch in [8, 127]: app.input_string = app.input_string[:-1]
|
meillo@0
|
490 elif ch: app.input_string = "%s%c" % (app.input_string, ch)
|
meillo@0
|
491 else: app.input_string = app.input_string or self.last_search
|
meillo@0
|
492 index = self.bufptr + advance
|
meillo@0
|
493 while 1:
|
meillo@0
|
494 if not 0 <= index < len(self.buffer):
|
meillo@0
|
495 app.status(_("Not found: %s ") % app.input_string)
|
meillo@0
|
496 self.not_found = 1
|
meillo@0
|
497 break
|
meillo@0
|
498 line = string.lower(str(self.buffer[index]))
|
meillo@0
|
499 if string.find(line, string.lower(app.input_string)) != -1:
|
meillo@0
|
500 app.show_input()
|
meillo@0
|
501 self.update_line(refresh = 0)
|
meillo@0
|
502 self.bufptr = index
|
meillo@0
|
503 self.update(force = 0)
|
meillo@0
|
504 self.not_found = 0
|
meillo@0
|
505 break
|
meillo@0
|
506 if self.not_found:
|
meillo@0
|
507 app.status(_("Not found: %s ") % app.input_string)
|
meillo@0
|
508 break
|
meillo@0
|
509 index = index + self.search_direction
|
meillo@0
|
510
|
meillo@0
|
511 def hscroll(self, value):
|
meillo@0
|
512 self.hoffset = max(0, self.hoffset + value)
|
meillo@0
|
513 self.update()
|
meillo@0
|
514
|
meillo@0
|
515 # ------------------------------------------
|
meillo@0
|
516 class HelpWindow(ListWindow):
|
meillo@0
|
517 def __init__(self, parent):
|
meillo@0
|
518 ListWindow.__init__(self, parent)
|
meillo@0
|
519 self.name = _("Help")
|
meillo@0
|
520 self.keymap.bind('q', self.parent.help, ())
|
meillo@0
|
521 self.buffer = string.split(_("""\
|
meillo@0
|
522 Global t, T : tag current/regex
|
meillo@0
|
523 ------ u, U : untag current/regex
|
meillo@0
|
524 Up, Down, k, j, C-p, C-n, Sp, i : invert current/all
|
meillo@0
|
525 PgUp, PgDn, K, J, ! : shell ($@ = tagged or current)
|
meillo@0
|
526 Home, End, g, G : movement
|
meillo@0
|
527 Enter : chdir or play Filelist
|
meillo@0
|
528 Tab : filelist/playlist --------
|
meillo@0
|
529 n, p : next/prev track a : add (tagged) to playlist
|
meillo@0
|
530 z, x : toggle pause/stop s : recursive search
|
meillo@0
|
531 BS, o : goto parent/specified dir
|
meillo@0
|
532 Left, Right, m, ' : set/get bookmark
|
meillo@0
|
533 C-f, C-b : seek forward/backward
|
meillo@0
|
534 C-a, C-e : restart/end track Playlist
|
meillo@0
|
535 C-s, C-r, / : isearch --------
|
meillo@0
|
536 C-g, Esc : cancel d, D : delete (tagged) tracks/playlist
|
meillo@0
|
537 1..9, +, - : volume control m, M : move tagged tracks after/before
|
meillo@0
|
538 c, v : counter/volume mode r, R : toggle repeat/Random mode
|
meillo@0
|
539 <, > : horizontal scrolling s, S : shuffle/Sort playlist
|
meillo@0
|
540 C-l, l : refresh, list mode w, @ : write playlist, jump to active
|
meillo@0
|
541 h, q, Q : help, quit?, Quit! X : stop playlist after each track
|
meillo@0
|
542 """), "\n")
|
meillo@0
|
543
|
meillo@0
|
544 # ------------------------------------------
|
meillo@0
|
545 class ListEntry:
|
meillo@0
|
546 def __init__(self, pathname, dir=0):
|
meillo@0
|
547 self.filename = os.path.basename(pathname)
|
meillo@0
|
548 self.pathname = pathname
|
meillo@0
|
549 self.slash = dir and "/" or ""
|
meillo@0
|
550 self.tagged = 0
|
meillo@0
|
551
|
meillo@0
|
552 def set_tagged(self, value):
|
meillo@0
|
553 self.tagged = value
|
meillo@0
|
554
|
meillo@0
|
555 def is_tagged(self):
|
meillo@0
|
556 return self.tagged == 1
|
meillo@0
|
557
|
meillo@0
|
558 def __str__(self):
|
meillo@0
|
559 mark = self.is_tagged() and "#" or " "
|
meillo@0
|
560 return "%s %s%s" % (mark, self.vp(), self.slash)
|
meillo@0
|
561
|
meillo@0
|
562 def vp(self):
|
meillo@0
|
563 return self.vps[0][1](self)
|
meillo@0
|
564
|
meillo@0
|
565 def vp_filename(self):
|
meillo@0
|
566 return self.filename or self.pathname
|
meillo@0
|
567
|
meillo@0
|
568 def vp_pathname(self):
|
meillo@0
|
569 return self.pathname
|
meillo@0
|
570
|
meillo@0
|
571 vps = [[_("filename"), vp_filename],
|
meillo@0
|
572 [_("pathname"), vp_pathname]]
|
meillo@0
|
573
|
meillo@0
|
574 # ------------------------------------------
|
meillo@0
|
575 class PlaylistEntry(ListEntry):
|
meillo@0
|
576 def __init__(self, pathname):
|
meillo@0
|
577 ListEntry.__init__(self, pathname)
|
meillo@0
|
578 self.metadata = None
|
meillo@0
|
579 self.active = 0
|
meillo@0
|
580
|
meillo@0
|
581 def set_active(self, value):
|
meillo@0
|
582 self.active = value
|
meillo@0
|
583
|
meillo@0
|
584 def is_active(self):
|
meillo@0
|
585 return self.active == 1
|
meillo@0
|
586
|
meillo@0
|
587 def vp_metadata(self):
|
meillo@0
|
588 return self.metadata or self.read_metadata()
|
meillo@0
|
589
|
meillo@0
|
590 def read_metadata(self):
|
meillo@0
|
591 self.metadata = get_tag(self.pathname)
|
meillo@0
|
592 return self.metadata
|
meillo@0
|
593
|
meillo@0
|
594 vps = ListEntry.vps[:]
|
meillo@0
|
595
|
meillo@0
|
596 # ------------------------------------------
|
meillo@0
|
597 class TagListWindow(ListWindow):
|
meillo@0
|
598 def __init__(self, parent):
|
meillo@0
|
599 ListWindow.__init__(self, parent)
|
meillo@0
|
600 self.keymap.bind(' ', self.command_tag_untag, ())
|
meillo@0
|
601 self.keymap.bind('i', self.command_invert_tags, ())
|
meillo@0
|
602 self.keymap.bind('t', self.command_tag, (1,))
|
meillo@0
|
603 self.keymap.bind('u', self.command_tag, (0,))
|
meillo@0
|
604 self.keymap.bind('T', self.command_tag_regexp, (1,))
|
meillo@0
|
605 self.keymap.bind('U', self.command_tag_regexp, (0,))
|
meillo@0
|
606 self.keymap.bind('l', self.command_change_viewpoint, ())
|
meillo@0
|
607
|
meillo@0
|
608 def command_change_viewpoint(self, klass=ListEntry):
|
meillo@0
|
609 klass.vps.append(klass.vps.pop(0))
|
meillo@0
|
610 app.status(_("Listing %s") % klass.vps[0][0], 1)
|
meillo@0
|
611 app.player.update_status()
|
meillo@0
|
612 self.update()
|
meillo@0
|
613
|
meillo@0
|
614 def command_invert_tags(self):
|
meillo@0
|
615 for i in self.buffer:
|
meillo@0
|
616 i.set_tagged(not i.is_tagged())
|
meillo@0
|
617 self.update()
|
meillo@0
|
618
|
meillo@0
|
619 def command_tag_untag(self):
|
meillo@0
|
620 if not self.buffer: return
|
meillo@0
|
621 tmp = self.buffer[self.bufptr]
|
meillo@0
|
622 tmp.set_tagged(not tmp.is_tagged())
|
meillo@0
|
623 self.cursor_move(1)
|
meillo@0
|
624
|
meillo@0
|
625 def command_tag(self, value):
|
meillo@0
|
626 if not self.buffer: return
|
meillo@0
|
627 self.buffer[self.bufptr].set_tagged(value)
|
meillo@0
|
628 self.cursor_move(1)
|
meillo@0
|
629
|
meillo@0
|
630 def command_tag_regexp(self, value):
|
meillo@0
|
631 self.tag_value = value
|
meillo@0
|
632 app.stop_input_hook = self.stop_tag_regexp
|
meillo@0
|
633 app.start_input(value and _("Tag regexp") or _("Untag regexp"))
|
meillo@0
|
634
|
meillo@0
|
635 def stop_tag_regexp(self):
|
meillo@0
|
636 try:
|
meillo@0
|
637 r = re.compile(app.input_string, re.I)
|
meillo@0
|
638 for entry in self.buffer:
|
meillo@0
|
639 if r.search(str(entry)):
|
meillo@0
|
640 entry.set_tagged(self.tag_value)
|
meillo@0
|
641 self.update()
|
meillo@0
|
642 app.status(_("ok"), 1)
|
meillo@0
|
643 except re.error, e:
|
meillo@0
|
644 app.status(e, 2)
|
meillo@0
|
645
|
meillo@0
|
646 def get_tagged(self):
|
meillo@0
|
647 return filter(lambda x: x.is_tagged(), self.buffer)
|
meillo@0
|
648
|
meillo@0
|
649 def not_tagged(self, l):
|
meillo@0
|
650 return filter(lambda x: not x.is_tagged(), l)
|
meillo@0
|
651
|
meillo@0
|
652 # ------------------------------------------
|
meillo@0
|
653 class FilelistWindow(TagListWindow):
|
meillo@0
|
654 def __init__(self, parent):
|
meillo@0
|
655 TagListWindow.__init__(self, parent)
|
meillo@0
|
656 self.oldposition = {}
|
meillo@0
|
657 try: self.chdir(os.getcwd())
|
meillo@0
|
658 except OSError: self.chdir(os.environ['HOME'])
|
meillo@0
|
659 self.startdir = self.cwd
|
meillo@0
|
660 self.mtime_when = 0
|
meillo@0
|
661 self.mtime = None
|
meillo@0
|
662 self.keymap.bind(['\n', curses.KEY_ENTER],
|
meillo@0
|
663 self.command_chdir_or_play, ())
|
meillo@0
|
664 self.keymap.bind(['.', curses.KEY_BACKSPACE],
|
meillo@0
|
665 self.command_chparentdir, ())
|
meillo@0
|
666 self.keymap.bind('a', self.command_add_recursively, ())
|
meillo@0
|
667 self.keymap.bind('o', self.command_goto, ())
|
meillo@0
|
668 self.keymap.bind('s', self.command_search_recursively, ())
|
meillo@0
|
669 self.keymap.bind('m', self.command_set_bookmark, ())
|
meillo@0
|
670 self.keymap.bind("'", self.command_get_bookmark, ())
|
meillo@0
|
671 self.keymap.bind('!', self.command_shell, ())
|
meillo@0
|
672 self.bookmarks = { 39: [self.cwd, 0] }
|
meillo@0
|
673
|
meillo@0
|
674 def command_shell(self):
|
meillo@0
|
675 if app.restricted: return
|
meillo@0
|
676 app.stop_input_hook = self.stop_shell
|
meillo@0
|
677 app.complete_input_hook = self.complete_shell
|
meillo@0
|
678 app.start_input(_("shell$ "), colon=0)
|
meillo@0
|
679
|
meillo@0
|
680 def stop_shell(self):
|
meillo@0
|
681 s = app.input_string
|
meillo@0
|
682 curses.endwin()
|
meillo@0
|
683 sys.stderr.write("\n")
|
meillo@0
|
684 argv = map(lambda x: x.pathname, self.get_tagged() or [self.current()])
|
meillo@0
|
685 argv = ["/bin/sh", "-c", s, "--"] + argv
|
meillo@0
|
686 pid = os.fork()
|
meillo@0
|
687 if pid == 0:
|
meillo@0
|
688 try: os.execv(argv[0], argv)
|
meillo@0
|
689 except: os._exit(1)
|
meillo@0
|
690 pid, r = os.waitpid(pid, 0)
|
meillo@0
|
691 sys.stderr.write("\nshell returned %s, press return!\n" % r)
|
meillo@0
|
692 sys.stdin.readline()
|
meillo@0
|
693 app.win_root.update()
|
meillo@0
|
694 app.restore_default_status()
|
meillo@0
|
695 app.cursor(0)
|
meillo@0
|
696
|
meillo@0
|
697 def complete_shell(self, line):
|
meillo@0
|
698 return self.complete_generic(line, quote=1)
|
meillo@0
|
699
|
meillo@0
|
700 def complete_generic(self, line, quote=0):
|
meillo@0
|
701 import glob
|
meillo@0
|
702 if quote:
|
meillo@0
|
703 s = re.sub('.*[^\\\\][ \'"()\[\]{}$`]', '', line)
|
meillo@0
|
704 s, part = re.sub('\\\\', '', s), line[:len(line)-len(s)]
|
meillo@0
|
705 else:
|
meillo@0
|
706 s, part = line, ""
|
meillo@0
|
707 results = glob.glob(os.path.expanduser(s)+"*")
|
meillo@0
|
708 if len(results) == 0:
|
meillo@0
|
709 return line
|
meillo@0
|
710 if len(results) == 1:
|
meillo@0
|
711 lm = results[0]
|
meillo@0
|
712 lm = lm + (os.path.isdir(lm) and "/" or "")
|
meillo@0
|
713 else:
|
meillo@0
|
714 lm = results[0]
|
meillo@0
|
715 for result in results:
|
meillo@0
|
716 for i in range(min(len(result), len(lm))):
|
meillo@0
|
717 if result[i] != lm[i]:
|
meillo@0
|
718 lm = lm[:i]
|
meillo@0
|
719 break
|
meillo@0
|
720 if quote: lm = re.sub('([ \'"()\[\]{}$`])', '\\\\\\1', lm)
|
meillo@0
|
721 return part + lm
|
meillo@0
|
722
|
meillo@0
|
723 def command_get_bookmark(self):
|
meillo@0
|
724 app.do_input_hook = self.do_get_bookmark
|
meillo@0
|
725 app.start_input(_("bookmark"))
|
meillo@0
|
726
|
meillo@0
|
727 def do_get_bookmark(self, ch):
|
meillo@0
|
728 app.input_string = ch
|
meillo@0
|
729 bookmark = self.bookmarks.get(ch)
|
meillo@0
|
730 if bookmark:
|
meillo@0
|
731 self.bookmarks[39] = [self.cwd, self.bufptr]
|
meillo@0
|
732 dir, pos = bookmark
|
meillo@0
|
733 self.chdir(dir)
|
meillo@0
|
734 self.listdir()
|
meillo@0
|
735 self.bufptr = pos
|
meillo@0
|
736 self.update()
|
meillo@0
|
737 app.status(_("ok"), 1)
|
meillo@0
|
738 else:
|
meillo@0
|
739 app.status(_("Not found!"), 1)
|
meillo@0
|
740 app.stop_input()
|
meillo@0
|
741
|
meillo@0
|
742 def command_set_bookmark(self):
|
meillo@0
|
743 app.do_input_hook = self.do_set_bookmark
|
meillo@0
|
744 app.start_input(_("set bookmark"))
|
meillo@0
|
745
|
meillo@0
|
746 def do_set_bookmark(self, ch):
|
meillo@0
|
747 app.input_string = ch
|
meillo@0
|
748 self.bookmarks[ch] = [self.cwd, self.bufptr]
|
meillo@0
|
749 ch and app.status(_("ok"), 1) or app.stop_input()
|
meillo@0
|
750
|
meillo@0
|
751 def command_search_recursively(self):
|
meillo@0
|
752 app.stop_input_hook = self.stop_search_recursively
|
meillo@0
|
753 app.start_input(_("search"))
|
meillo@0
|
754
|
meillo@0
|
755 def stop_search_recursively(self):
|
meillo@0
|
756 try: re_tmp = re.compile(app.input_string, re.I)
|
meillo@0
|
757 except re.error, e:
|
meillo@0
|
758 app.status(e, 2)
|
meillo@0
|
759 return
|
meillo@0
|
760 app.status(_("Searching..."))
|
meillo@0
|
761 results = []
|
meillo@0
|
762 for entry in self.buffer:
|
meillo@0
|
763 if entry.filename == "..":
|
meillo@0
|
764 continue
|
meillo@0
|
765 if re_tmp.search(entry.filename):
|
meillo@0
|
766 results.append(entry)
|
meillo@0
|
767 elif os.path.isdir(entry.pathname):
|
meillo@0
|
768 try: self.search_recursively(re_tmp, entry.pathname, results)
|
meillo@0
|
769 except: pass
|
meillo@0
|
770 if not self.search_mode:
|
meillo@0
|
771 self.chdir(os.path.join(self.cwd,_("search results")))
|
meillo@0
|
772 self.search_mode = 1
|
meillo@0
|
773 self.buffer = results
|
meillo@0
|
774 self.bufptr = 0
|
meillo@0
|
775 self.parent.update_title()
|
meillo@0
|
776 self.update()
|
meillo@0
|
777 app.restore_default_status()
|
meillo@0
|
778
|
meillo@0
|
779 def search_recursively(self, re_tmp, dir, results):
|
meillo@0
|
780 for filename in os.listdir(dir):
|
meillo@0
|
781 pathname = os.path.join(dir, filename)
|
meillo@0
|
782 if re_tmp.search(filename):
|
meillo@0
|
783 if os.path.isdir(pathname):
|
meillo@0
|
784 results.append(ListEntry(pathname, 1))
|
meillo@0
|
785 elif VALID_PLAYLIST(filename) or VALID_SONG(filename):
|
meillo@0
|
786 results.append(ListEntry(pathname))
|
meillo@0
|
787 elif os.path.isdir(pathname):
|
meillo@0
|
788 self.search_recursively(re_tmp, pathname, results)
|
meillo@0
|
789
|
meillo@0
|
790 def get_title(self):
|
meillo@0
|
791 self.name = _("Filelist: ")
|
meillo@0
|
792 return ListWindow.get_title(self, re.sub("/?$", "/", self.cwd))
|
meillo@0
|
793
|
meillo@0
|
794 def listdir_maybe(self, now=0):
|
meillo@0
|
795 if now < self.mtime_when+2: return
|
meillo@0
|
796 self.mtime_when = now
|
meillo@0
|
797 self.oldposition[self.cwd] = self.bufptr
|
meillo@0
|
798 try: self.mtime == os.stat(self.cwd)[8] or self.listdir(quiet=1)
|
meillo@0
|
799 except os.error: pass
|
meillo@0
|
800
|
meillo@0
|
801 def listdir(self, quiet=0, prevdir=None):
|
meillo@0
|
802 quiet or app.status(_("Reading directory..."))
|
meillo@0
|
803 self.search_mode = 0
|
meillo@0
|
804 dirs = []
|
meillo@0
|
805 files = []
|
meillo@0
|
806 try:
|
meillo@0
|
807 self.mtime = os.stat(self.cwd)[8]
|
meillo@0
|
808 self.mtime_when = time.time()
|
meillo@0
|
809 filenames = os.listdir(self.cwd)
|
meillo@0
|
810 filenames.sort()
|
meillo@0
|
811 for filename in filenames:
|
meillo@0
|
812 if filename[0] == ".": continue
|
meillo@0
|
813 pathname = os.path.join(self.cwd, filename)
|
meillo@0
|
814 if os.path.isdir(pathname): dirs.append(pathname)
|
meillo@0
|
815 elif VALID_SONG(filename): files.append(pathname)
|
meillo@0
|
816 elif VALID_PLAYLIST(filename): files.append(pathname)
|
meillo@0
|
817 except os.error: pass
|
meillo@0
|
818 dots = ListEntry(os.path.join(self.cwd, ".."), 1)
|
meillo@0
|
819 self.buffer = [[dots], []][self.cwd == "/"]
|
meillo@0
|
820 for i in dirs: self.buffer.append(ListEntry(i, 1))
|
meillo@0
|
821 for i in files: self.buffer.append(ListEntry(i))
|
meillo@0
|
822 if prevdir:
|
meillo@0
|
823 for self.bufptr in range(len(self.buffer)):
|
meillo@0
|
824 if self.buffer[self.bufptr].filename == prevdir: break
|
meillo@0
|
825 else: self.bufptr = 0
|
meillo@0
|
826 elif self.oldposition.has_key(self.cwd):
|
meillo@0
|
827 self.bufptr = self.oldposition[self.cwd]
|
meillo@0
|
828 else: self.bufptr = 0
|
meillo@0
|
829 self.parent.update_title()
|
meillo@0
|
830 self.update()
|
meillo@0
|
831 quiet or app.restore_default_status()
|
meillo@0
|
832
|
meillo@0
|
833 def chdir(self, dir):
|
meillo@0
|
834 if hasattr(self, "cwd"): self.oldposition[self.cwd] = self.bufptr
|
meillo@0
|
835 self.cwd = os.path.normpath(dir)
|
meillo@0
|
836 try: os.chdir(self.cwd)
|
meillo@0
|
837 except: pass
|
meillo@0
|
838
|
meillo@0
|
839 def command_chdir_or_play(self):
|
meillo@0
|
840 if not self.buffer: return
|
meillo@0
|
841 if self.current().filename == "..":
|
meillo@0
|
842 self.command_chparentdir()
|
meillo@0
|
843 elif os.path.isdir(self.current().pathname):
|
meillo@0
|
844 self.chdir(self.current().pathname)
|
meillo@0
|
845 self.listdir()
|
meillo@0
|
846 elif VALID_SONG(self.current().filename):
|
meillo@0
|
847 app.play(self.current())
|
meillo@0
|
848
|
meillo@0
|
849 def command_chparentdir(self):
|
meillo@0
|
850 if app.restricted and self.cwd == self.startdir: return
|
meillo@0
|
851 dir = os.path.basename(self.cwd)
|
meillo@0
|
852 self.chdir(os.path.dirname(self.cwd))
|
meillo@0
|
853 self.listdir(prevdir=dir)
|
meillo@0
|
854
|
meillo@0
|
855 def command_goto(self):
|
meillo@0
|
856 if app.restricted: return
|
meillo@0
|
857 app.stop_input_hook = self.stop_goto
|
meillo@0
|
858 app.complete_input_hook = self.complete_generic
|
meillo@0
|
859 app.start_input(_("goto"))
|
meillo@0
|
860
|
meillo@0
|
861 def stop_goto(self):
|
meillo@0
|
862 dir = os.path.expanduser(app.input_string)
|
meillo@0
|
863 if dir[0] != '/': dir = os.path.join(self.cwd, dir)
|
meillo@0
|
864 if not os.path.isdir(dir):
|
meillo@0
|
865 app.status(_("Not a directory!"), 1)
|
meillo@0
|
866 return
|
meillo@0
|
867 self.chdir(dir)
|
meillo@0
|
868 self.listdir()
|
meillo@0
|
869
|
meillo@0
|
870 def command_add_recursively(self):
|
meillo@0
|
871 l = self.get_tagged()
|
meillo@0
|
872 if not l:
|
meillo@0
|
873 app.win_playlist.add(self.current().pathname)
|
meillo@0
|
874 self.cursor_move(1)
|
meillo@0
|
875 return
|
meillo@0
|
876 app.status(_("Adding tagged files"), 1)
|
meillo@0
|
877 for entry in l:
|
meillo@0
|
878 app.win_playlist.add(entry.pathname, quiet=1)
|
meillo@0
|
879 entry.set_tagged(0)
|
meillo@0
|
880 self.update()
|
meillo@0
|
881
|
meillo@0
|
882 # ------------------------------------------
|
meillo@0
|
883 class PlaylistWindow(TagListWindow):
|
meillo@0
|
884 def __init__(self, parent):
|
meillo@0
|
885 TagListWindow.__init__(self, parent)
|
meillo@0
|
886 self.pathname = None
|
meillo@0
|
887 self.repeat = 0
|
meillo@0
|
888 self.random = 0
|
meillo@0
|
889 self.random_prev = []
|
meillo@0
|
890 self.random_next = []
|
meillo@0
|
891 self.random_left = []
|
meillo@0
|
892 self.stop = 0
|
meillo@0
|
893 self.keymap.bind(['\n', curses.KEY_ENTER],
|
meillo@0
|
894 self.command_play, ())
|
meillo@0
|
895 self.keymap.bind('d', self.command_delete, ())
|
meillo@0
|
896 self.keymap.bind('D', self.command_delete_all, ())
|
meillo@0
|
897 self.keymap.bind('m', self.command_move, (1,))
|
meillo@0
|
898 self.keymap.bind('M', self.command_move, (0,))
|
meillo@0
|
899 self.keymap.bind('s', self.command_shuffle, ())
|
meillo@0
|
900 self.keymap.bind('S', self.command_sort, ())
|
meillo@0
|
901 self.keymap.bind('r', self.command_toggle_repeat, ())
|
meillo@0
|
902 self.keymap.bind('R', self.command_toggle_random, ())
|
meillo@0
|
903 self.keymap.bind('X', self.command_toggle_stop, ())
|
meillo@0
|
904 self.keymap.bind('w', self.command_save_playlist, ())
|
meillo@0
|
905 self.keymap.bind('@', self.command_jump_to_active, ())
|
meillo@0
|
906
|
meillo@0
|
907 def command_change_viewpoint(self, klass=PlaylistEntry):
|
meillo@0
|
908 if not globals().get("ID3"):
|
meillo@0
|
909 try:
|
meillo@0
|
910 global ID3, ogg, codecs
|
meillo@0
|
911 import ID3, ogg.vorbis, codecs
|
meillo@0
|
912 klass.vps.append([_("metadata"), klass.vp_metadata])
|
meillo@0
|
913 except ImportError: pass
|
meillo@0
|
914 TagListWindow.command_change_viewpoint(self, klass)
|
meillo@0
|
915
|
meillo@0
|
916 def get_title(self):
|
meillo@0
|
917 space_out = lambda value, s: value and s or " "*len(s)
|
meillo@0
|
918 self.name = _("Playlist %s %s %s") % (
|
meillo@0
|
919 space_out(self.repeat, _("[repeat]")),
|
meillo@0
|
920 space_out(self.random, _("[random]")),
|
meillo@0
|
921 space_out(self.stop, _("[stop]")))
|
meillo@0
|
922 return ListWindow.get_title(self)
|
meillo@0
|
923
|
meillo@0
|
924 def append(self, item):
|
meillo@0
|
925 self.buffer.append(item)
|
meillo@0
|
926 if self.random: self.random_left.append(item)
|
meillo@0
|
927
|
meillo@0
|
928 def add_dir(self, dir):
|
meillo@0
|
929 filenames = os.listdir(dir)
|
meillo@0
|
930 filenames.sort()
|
meillo@0
|
931 subdirs = []
|
meillo@0
|
932 for filename in filenames:
|
meillo@0
|
933 pathname = os.path.join(dir, filename)
|
meillo@0
|
934 if VALID_SONG(filename):
|
meillo@0
|
935 self.append(PlaylistEntry(pathname))
|
meillo@0
|
936 if os.path.isdir(pathname):
|
meillo@0
|
937 subdirs.append(pathname)
|
meillo@0
|
938 map(self.add_dir, subdirs)
|
meillo@0
|
939
|
meillo@0
|
940 def add_m3u(self, line):
|
meillo@0
|
941 if re.match("^(#.*)?$", line): return
|
meillo@0
|
942 if re.match("^(/|http://)", line):
|
meillo@0
|
943 self.append(PlaylistEntry(self.fix_url(line)))
|
meillo@0
|
944 else:
|
meillo@0
|
945 dirname = os.path.dirname(self.pathname)
|
meillo@0
|
946 self.append(PlaylistEntry(os.path.join(dirname, line)))
|
meillo@0
|
947
|
meillo@0
|
948 def add_pls(self, line):
|
meillo@0
|
949 # todo - support title & length
|
meillo@0
|
950 m = re.match("File(\d+)=(.*)", line)
|
meillo@0
|
951 if m: self.append(PlaylistEntry(self.fix_url(m.group(2))))
|
meillo@0
|
952
|
meillo@0
|
953 def add_playlist(self, pathname):
|
meillo@0
|
954 self.pathname = pathname
|
meillo@0
|
955 if re.search("\.m3u$", pathname, re.I): f = self.add_m3u
|
meillo@0
|
956 if re.search("\.pls$", pathname, re.I): f = self.add_pls
|
meillo@0
|
957 file = open(pathname)
|
meillo@0
|
958 map(f, map(string.strip, file.readlines()))
|
meillo@0
|
959 file.close()
|
meillo@0
|
960
|
meillo@0
|
961 def add(self, pathname, quiet=0):
|
meillo@0
|
962 try:
|
meillo@0
|
963 if os.path.isdir(pathname):
|
meillo@0
|
964 app.status(_("Working..."))
|
meillo@0
|
965 self.add_dir(pathname)
|
meillo@0
|
966 elif VALID_PLAYLIST(pathname):
|
meillo@0
|
967 self.add_playlist(pathname)
|
meillo@0
|
968 else:
|
meillo@0
|
969 pathname = self.fix_url(pathname)
|
meillo@0
|
970 self.append(PlaylistEntry(pathname))
|
meillo@0
|
971 # todo - refactor
|
meillo@0
|
972 filename = os.path.basename(pathname) or pathname
|
meillo@0
|
973 quiet or app.status(_("Added: %s") % filename, 1)
|
meillo@0
|
974 except Exception, e:
|
meillo@0
|
975 app.status(e, 2)
|
meillo@0
|
976
|
meillo@0
|
977 def fix_url(self, url):
|
meillo@0
|
978 return re.sub("(http://[^/]+)/?(.*)", "\\1/\\2", url)
|
meillo@0
|
979
|
meillo@0
|
980 def putstr(self, entry, *pos):
|
meillo@0
|
981 if entry.is_active(): self.attron(curses.A_BOLD)
|
meillo@0
|
982 apply(ListWindow.putstr, (self, entry) + pos)
|
meillo@0
|
983 if entry.is_active(): self.attroff(curses.A_BOLD)
|
meillo@0
|
984
|
meillo@0
|
985 def change_active_entry(self, direction):
|
meillo@0
|
986 if not self.buffer: return
|
meillo@0
|
987 old = self.get_active_entry()
|
meillo@0
|
988 new = None
|
meillo@0
|
989 if self.random:
|
meillo@0
|
990 if direction > 0:
|
meillo@0
|
991 if self.random_next: new = self.random_next.pop()
|
meillo@0
|
992 elif self.random_left: pass
|
meillo@0
|
993 elif self.repeat: self.random_left = self.buffer[:]
|
meillo@0
|
994 else: return
|
meillo@0
|
995 if not new:
|
meillo@0
|
996 import random
|
meillo@0
|
997 new = random.choice(self.random_left)
|
meillo@0
|
998 self.random_left.remove(new)
|
meillo@0
|
999 try: self.random_prev.remove(new)
|
meillo@0
|
1000 except ValueError: pass
|
meillo@0
|
1001 self.random_prev.append(new)
|
meillo@0
|
1002 else:
|
meillo@0
|
1003 if len(self.random_prev) > 1:
|
meillo@0
|
1004 self.random_next.append(self.random_prev.pop())
|
meillo@0
|
1005 new = self.random_prev[-1]
|
meillo@0
|
1006 else: return
|
meillo@0
|
1007 old and old.set_active(0)
|
meillo@0
|
1008 elif old:
|
meillo@0
|
1009 index = self.buffer.index(old)+direction
|
meillo@0
|
1010 if not (0 <= index < len(self.buffer) or self.repeat): return
|
meillo@0
|
1011 old.set_active(0)
|
meillo@0
|
1012 new = self.buffer[index % len(self.buffer)]
|
meillo@0
|
1013 else:
|
meillo@0
|
1014 new = self.buffer[0]
|
meillo@0
|
1015 new.set_active(1)
|
meillo@0
|
1016 self.update()
|
meillo@0
|
1017 return new
|
meillo@0
|
1018
|
meillo@0
|
1019 def get_active_entry(self):
|
meillo@0
|
1020 for entry in self.buffer:
|
meillo@0
|
1021 if entry.is_active(): return entry
|
meillo@0
|
1022
|
meillo@0
|
1023 def command_jump_to_active(self):
|
meillo@0
|
1024 entry = self.get_active_entry()
|
meillo@0
|
1025 if not entry: return
|
meillo@0
|
1026 self.bufptr = self.buffer.index(entry)
|
meillo@0
|
1027 self.update()
|
meillo@0
|
1028
|
meillo@0
|
1029 def command_play(self):
|
meillo@0
|
1030 if not self.buffer: return
|
meillo@0
|
1031 entry = self.get_active_entry()
|
meillo@0
|
1032 entry and entry.set_active(0)
|
meillo@0
|
1033 entry = self.current()
|
meillo@0
|
1034 entry.set_active(1)
|
meillo@0
|
1035 self.update()
|
meillo@0
|
1036 app.play(entry)
|
meillo@0
|
1037
|
meillo@0
|
1038 def command_delete(self):
|
meillo@0
|
1039 if not self.buffer: return
|
meillo@0
|
1040 current_entry, n = self.current(), len(self.buffer)
|
meillo@0
|
1041 self.buffer = self.not_tagged(self.buffer)
|
meillo@0
|
1042 if n > len(self.buffer):
|
meillo@0
|
1043 try: self.bufptr = self.buffer.index(current_entry)
|
meillo@0
|
1044 except ValueError: pass
|
meillo@0
|
1045 else:
|
meillo@0
|
1046 current_entry.set_tagged(1)
|
meillo@0
|
1047 del self.buffer[self.bufptr]
|
meillo@0
|
1048 if self.random:
|
meillo@0
|
1049 self.random_prev = self.not_tagged(self.random_prev)
|
meillo@0
|
1050 self.random_next = self.not_tagged(self.random_next)
|
meillo@0
|
1051 self.random_left = self.not_tagged(self.random_left)
|
meillo@0
|
1052 self.update()
|
meillo@0
|
1053
|
meillo@0
|
1054 def command_delete_all(self):
|
meillo@0
|
1055 self.buffer = []
|
meillo@0
|
1056 self.random_prev = []
|
meillo@0
|
1057 self.random_next = []
|
meillo@0
|
1058 self.random_left = []
|
meillo@0
|
1059 app.status(_("Deleted playlist"), 1)
|
meillo@0
|
1060 self.update()
|
meillo@0
|
1061
|
meillo@0
|
1062 def command_move(self, after):
|
meillo@0
|
1063 if not self.buffer: return
|
meillo@0
|
1064 current_entry, l = self.current(), self.get_tagged()
|
meillo@0
|
1065 if not l or current_entry.is_tagged(): return
|
meillo@0
|
1066 self.buffer = self.not_tagged(self.buffer)
|
meillo@0
|
1067 self.bufptr = self.buffer.index(current_entry)+after
|
meillo@0
|
1068 self.buffer[self.bufptr:self.bufptr] = l
|
meillo@0
|
1069 self.update()
|
meillo@0
|
1070
|
meillo@0
|
1071 def command_shuffle(self):
|
meillo@0
|
1072 import random
|
meillo@0
|
1073 l = []
|
meillo@0
|
1074 n = len(self.buffer)
|
meillo@0
|
1075 while n > 0:
|
meillo@0
|
1076 n = n-1
|
meillo@0
|
1077 r = random.randint(0, n)
|
meillo@0
|
1078 l.append(self.buffer[r])
|
meillo@0
|
1079 del self.buffer[r]
|
meillo@0
|
1080 self.buffer = l
|
meillo@0
|
1081 self.bufptr = 0
|
meillo@0
|
1082 self.update()
|
meillo@0
|
1083 app.status(_("Shuffled playlist... Oops?"), 1)
|
meillo@0
|
1084
|
meillo@0
|
1085 def command_sort(self):
|
meillo@0
|
1086 app.status(_("Working..."))
|
meillo@0
|
1087 self.buffer.sort(lambda x, y: x.vp() > y.vp() or -1)
|
meillo@0
|
1088 self.bufptr = 0
|
meillo@0
|
1089 self.update()
|
meillo@0
|
1090 app.status(_("Sorted playlist"), 1)
|
meillo@0
|
1091
|
meillo@0
|
1092 def command_toggle_repeat(self):
|
meillo@0
|
1093 self.toggle("repeat", _("Repeat: %s"))
|
meillo@0
|
1094
|
meillo@0
|
1095 def command_toggle_random(self):
|
meillo@0
|
1096 self.toggle("random", _("Random: %s"))
|
meillo@0
|
1097 self.random_prev = []
|
meillo@0
|
1098 self.random_next = []
|
meillo@0
|
1099 self.random_left = self.buffer[:]
|
meillo@0
|
1100
|
meillo@0
|
1101 def command_toggle_stop(self):
|
meillo@0
|
1102 self.toggle("stop", _("Stop playlist: %s"))
|
meillo@0
|
1103
|
meillo@0
|
1104 def toggle(self, attr, format):
|
meillo@0
|
1105 setattr(self, attr, not getattr(self, attr))
|
meillo@0
|
1106 app.status(format % (getattr(self, attr) and _("on") or _("off")), 1)
|
meillo@0
|
1107 self.parent.update_title()
|
meillo@0
|
1108
|
meillo@0
|
1109 def command_save_playlist(self):
|
meillo@0
|
1110 if app.restricted: return
|
meillo@0
|
1111 default = self.pathname or "%s/" % app.win_filelist.cwd
|
meillo@0
|
1112 app.stop_input_hook = self.stop_save_playlist
|
meillo@0
|
1113 app.start_input(_("Save playlist"), default)
|
meillo@0
|
1114
|
meillo@0
|
1115 def stop_save_playlist(self):
|
meillo@0
|
1116 pathname = app.input_string
|
meillo@0
|
1117 if pathname[0] != '/':
|
meillo@0
|
1118 pathname = os.path.join(app.win_filelist.cwd, pathname)
|
meillo@0
|
1119 if not re.search("\.m3u$", pathname, re.I):
|
meillo@0
|
1120 pathname = "%s%s" % (pathname, ".m3u")
|
meillo@0
|
1121 try:
|
meillo@0
|
1122 file = open(pathname, "w")
|
meillo@0
|
1123 for entry in self.buffer:
|
meillo@0
|
1124 file.write("%s\n" % entry.pathname)
|
meillo@0
|
1125 file.close()
|
meillo@0
|
1126 self.pathname = pathname
|
meillo@0
|
1127 app.status(_("ok"), 1)
|
meillo@0
|
1128 except IOError, e:
|
meillo@0
|
1129 app.status(e, 2)
|
meillo@0
|
1130
|
meillo@0
|
1131 # ------------------------------------------
|
meillo@0
|
1132 def get_tag(pathname):
|
meillo@0
|
1133 if re.compile("^http://").match(pathname) or not os.path.exists(pathname):
|
meillo@0
|
1134 return pathname
|
meillo@0
|
1135 tags = {}
|
meillo@0
|
1136 # FIXME: use magic instead of file extensions to identify OGGs and MP3s
|
meillo@0
|
1137 if re.compile(".*\.ogg$", re.I).match(pathname):
|
meillo@0
|
1138 try:
|
meillo@0
|
1139 vf = ogg.vorbis.VorbisFile(pathname)
|
meillo@0
|
1140 vc = vf.comment()
|
meillo@0
|
1141 tags = vc.as_dict()
|
meillo@0
|
1142 except NameError: pass
|
meillo@0
|
1143 except (IOError, UnicodeError): return os.path.basename(pathname)
|
meillo@0
|
1144 elif re.compile(".*\.mp3$", re.I).match(pathname):
|
meillo@0
|
1145 try:
|
meillo@0
|
1146 vc = ID3.ID3(pathname, as_tuple=1)
|
meillo@0
|
1147 tags = vc.as_dict()
|
meillo@0
|
1148 except NameError: pass
|
meillo@0
|
1149 except (IOError, ID3.InvalidTagError): return os.path.basename(pathname)
|
meillo@0
|
1150 else:
|
meillo@0
|
1151 return os.path.basename(pathname)
|
meillo@0
|
1152
|
meillo@0
|
1153 artist = tags.get("ARTIST", [""])[0]
|
meillo@0
|
1154 title = tags.get("TITLE", [""])[0]
|
meillo@0
|
1155 tag = os.path.basename(pathname)
|
meillo@0
|
1156 try:
|
meillo@0
|
1157 if artist and title:
|
meillo@0
|
1158 tag = codecs.latin_1_encode(artist)[0] + " - " + codecs.latin_1_encode(title)[0]
|
meillo@0
|
1159 elif artist:
|
meillo@0
|
1160 tag = artist
|
meillo@0
|
1161 elif title:
|
meillo@0
|
1162 tag = title
|
meillo@0
|
1163 return codecs.latin_1_encode(tag)[0]
|
meillo@0
|
1164 except (NameError, UnicodeError): return tag
|
meillo@0
|
1165
|
meillo@0
|
1166 # ------------------------------------------
|
meillo@0
|
1167 class Player:
|
meillo@0
|
1168 def __init__(self, commandline, files, fps=1):
|
meillo@0
|
1169 self.commandline = commandline
|
meillo@0
|
1170 self.re_files = re.compile(files, re.I)
|
meillo@0
|
1171 self.fps = fps
|
meillo@0
|
1172 self.stdin_r, self.stdin_w = os.pipe()
|
meillo@0
|
1173 self.stdout_r, self.stdout_w = os.pipe()
|
meillo@0
|
1174 self.stderr_r, self.stderr_w = os.pipe()
|
meillo@0
|
1175 self.entry = None
|
meillo@0
|
1176 self.stopped = 0
|
meillo@0
|
1177 self.paused = 0
|
meillo@0
|
1178 self.time_setup = None
|
meillo@0
|
1179 self.buf = ''
|
meillo@0
|
1180 self.tid = None
|
meillo@0
|
1181
|
meillo@0
|
1182 def setup(self, entry, offset):
|
meillo@0
|
1183 self.argv = string.split(self.commandline)
|
meillo@0
|
1184 self.argv[0] = which(self.argv[0])
|
meillo@0
|
1185 for i in range(len(self.argv)):
|
meillo@0
|
1186 if self.argv[i] == "%s": self.argv[i] = entry.pathname
|
meillo@0
|
1187 if self.argv[i] == "%d": self.argv[i] = str(offset*self.fps)
|
meillo@0
|
1188 self.entry = entry
|
meillo@0
|
1189 if offset == 0:
|
meillo@0
|
1190 app.progress(0)
|
meillo@0
|
1191 self.offset = 0
|
meillo@0
|
1192 self.length = 0
|
meillo@0
|
1193 self.values = [0, 0]
|
meillo@0
|
1194 self.time_setup = time.time()
|
meillo@0
|
1195 return self.argv[0]
|
meillo@0
|
1196
|
meillo@0
|
1197 def play(self):
|
meillo@0
|
1198 self.pid = os.fork()
|
meillo@0
|
1199 if self.pid == 0:
|
meillo@0
|
1200 os.dup2(self.stdin_w, sys.stdin.fileno())
|
meillo@0
|
1201 os.dup2(self.stdout_w, sys.stdout.fileno())
|
meillo@0
|
1202 os.dup2(self.stderr_w, sys.stderr.fileno())
|
meillo@0
|
1203 os.setpgrp()
|
meillo@0
|
1204 try: os.execv(self.argv[0], self.argv)
|
meillo@0
|
1205 except: os._exit(1)
|
meillo@0
|
1206 self.stopped = 0
|
meillo@0
|
1207 self.paused = 0
|
meillo@0
|
1208 self.step = 0
|
meillo@0
|
1209 self.update_status()
|
meillo@0
|
1210
|
meillo@0
|
1211 def stop(self, quiet=0):
|
meillo@0
|
1212 self.paused and self.toggle_pause(quiet)
|
meillo@0
|
1213 try:
|
meillo@0
|
1214 while 1:
|
meillo@0
|
1215 try: os.kill(-self.pid, signal.SIGINT)
|
meillo@0
|
1216 except os.error: pass
|
meillo@0
|
1217 os.waitpid(self.pid, os.WNOHANG)
|
meillo@0
|
1218 except Exception: pass
|
meillo@0
|
1219 self.stopped = 1
|
meillo@0
|
1220 quiet or self.update_status()
|
meillo@0
|
1221
|
meillo@0
|
1222 def toggle_pause(self, quiet=0):
|
meillo@0
|
1223 try: os.kill(-self.pid, [signal.SIGSTOP, signal.SIGCONT][self.paused])
|
meillo@0
|
1224 except os.error: return
|
meillo@0
|
1225 self.paused = not self.paused
|
meillo@0
|
1226 quiet or self.update_status()
|
meillo@0
|
1227
|
meillo@0
|
1228 def parse_progress(self):
|
meillo@0
|
1229 if self.stopped or self.step: self.tid = None
|
meillo@0
|
1230 else:
|
meillo@0
|
1231 self.parse_buf()
|
meillo@0
|
1232 self.tid = app.timeout.add(1.0, self.parse_progress)
|
meillo@0
|
1233
|
meillo@0
|
1234 def read_fd(self, fd):
|
meillo@0
|
1235 self.buf = os.read(fd, 512)
|
meillo@0
|
1236 self.tid or self.parse_progress()
|
meillo@0
|
1237
|
meillo@0
|
1238 def poll(self):
|
meillo@0
|
1239 try: os.waitpid(self.pid, os.WNOHANG)
|
meillo@0
|
1240 except:
|
meillo@0
|
1241 # something broken? try again
|
meillo@0
|
1242 if self.time_setup and (time.time() - self.time_setup) < 2.0:
|
meillo@0
|
1243 self.play()
|
meillo@0
|
1244 return 0
|
meillo@0
|
1245 app.set_default_status("")
|
meillo@0
|
1246 app.counter([0,0])
|
meillo@0
|
1247 app.progress(0)
|
meillo@0
|
1248 return 1
|
meillo@0
|
1249
|
meillo@0
|
1250 def seek(self, offset, relative):
|
meillo@0
|
1251 if relative:
|
meillo@0
|
1252 d = offset * self.length * 0.002
|
meillo@0
|
1253 self.step = self.step * (self.step * d > 0) + d
|
meillo@0
|
1254 self.offset = min(self.length, max(0, self.offset+self.step))
|
meillo@0
|
1255 else:
|
meillo@0
|
1256 self.step = 1
|
meillo@0
|
1257 self.offset = (offset < 0) and self.length+offset or offset
|
meillo@0
|
1258 self.show_position()
|
meillo@0
|
1259
|
meillo@0
|
1260 def set_position(self, offset, length, values):
|
meillo@0
|
1261 self.offset = offset
|
meillo@0
|
1262 self.length = length
|
meillo@0
|
1263 self.values = values
|
meillo@0
|
1264 self.show_position()
|
meillo@0
|
1265
|
meillo@0
|
1266 def show_position(self):
|
meillo@0
|
1267 app.counter(self.values)
|
meillo@0
|
1268 app.progress(self.length and (float(self.offset) / self.length))
|
meillo@0
|
1269
|
meillo@0
|
1270 def update_status(self):
|
meillo@0
|
1271 if not self.entry:
|
meillo@0
|
1272 app.set_default_status("")
|
meillo@0
|
1273 elif self.stopped:
|
meillo@0
|
1274 app.set_default_status(_("Stopped: %s") % self.entry.vp())
|
meillo@0
|
1275 elif self.paused:
|
meillo@0
|
1276 app.set_default_status(_("Paused: %s") % self.entry.vp())
|
meillo@0
|
1277 else:
|
meillo@0
|
1278 app.set_default_status(_("Playing: %s") % self.entry.vp())
|
meillo@0
|
1279
|
meillo@0
|
1280 # ------------------------------------------
|
meillo@0
|
1281 class FrameOffsetPlayer(Player):
|
meillo@0
|
1282 re_progress = re.compile("Time.*\s(\d+):(\d+).*\[(\d+):(\d+)")
|
meillo@0
|
1283
|
meillo@0
|
1284 def parse_buf(self):
|
meillo@0
|
1285 match = self.re_progress.search(self.buf)
|
meillo@0
|
1286 if match:
|
meillo@0
|
1287 m1, s1, m2, s2 = map(string.atoi, match.groups())
|
meillo@0
|
1288 head, tail = m1*60+s1, m2*60+s2
|
meillo@0
|
1289 self.set_position(head, head+tail, [head, tail])
|
meillo@0
|
1290
|
meillo@0
|
1291 # ------------------------------------------
|
meillo@0
|
1292 class TimeOffsetPlayer(Player):
|
meillo@0
|
1293 re_progress = re.compile("(\d+):(\d+):(\d+)")
|
meillo@0
|
1294
|
meillo@0
|
1295 def parse_buf(self):
|
meillo@0
|
1296 match = self.re_progress.search(self.buf)
|
meillo@0
|
1297 if match:
|
meillo@0
|
1298 h, m, s = map(string.atoi, match.groups())
|
meillo@0
|
1299 tail = h*3600+m*60+s
|
meillo@0
|
1300 head = max(self.length, tail) - tail
|
meillo@0
|
1301 self.set_position(head, head+tail, [head, tail])
|
meillo@0
|
1302
|
meillo@0
|
1303 # ------------------------------------------
|
meillo@0
|
1304 class NoOffsetPlayer(Player):
|
meillo@0
|
1305
|
meillo@0
|
1306 def parse_buf(self):
|
meillo@0
|
1307 head = self.offset+1
|
meillo@0
|
1308 self.set_position(head, 0, [head, head])
|
meillo@0
|
1309
|
meillo@0
|
1310 def seek(self, *dummy):
|
meillo@0
|
1311 return 1
|
meillo@0
|
1312
|
meillo@0
|
1313 # ------------------------------------------
|
meillo@0
|
1314 class Timeout:
|
meillo@0
|
1315 def __init__(self):
|
meillo@0
|
1316 self.next = 0
|
meillo@0
|
1317 self.dict = {}
|
meillo@0
|
1318
|
meillo@0
|
1319 def add(self, timeout, func, args=()):
|
meillo@0
|
1320 tid = self.next = self.next + 1
|
meillo@0
|
1321 self.dict[tid] = (func, args, time.time() + timeout)
|
meillo@0
|
1322 return tid
|
meillo@0
|
1323
|
meillo@0
|
1324 def remove(self, tid):
|
meillo@0
|
1325 del self.dict[tid]
|
meillo@0
|
1326
|
meillo@0
|
1327 def check(self, now):
|
meillo@0
|
1328 for tid, (func, args, timeout) in self.dict.items():
|
meillo@0
|
1329 if now >= timeout:
|
meillo@0
|
1330 self.remove(tid)
|
meillo@0
|
1331 apply(func, args)
|
meillo@0
|
1332 return len(self.dict) and 0.2 or None
|
meillo@0
|
1333
|
meillo@0
|
1334 # ------------------------------------------
|
meillo@0
|
1335 class FIFOControl:
|
meillo@0
|
1336 def __init__(self):
|
meillo@0
|
1337 try: self.fd = open(CONTROL_FIFO, "rb+", 0)
|
meillo@0
|
1338 except: self.fd = None
|
meillo@0
|
1339 self.commands = {"pause" : app.toggle_pause,
|
meillo@0
|
1340 "next" : app.next_song,
|
meillo@0
|
1341 "prev" : app.prev_song,
|
meillo@0
|
1342 "forward" : self.forward,
|
meillo@0
|
1343 "backward" : self.backward,
|
meillo@0
|
1344 "play" : app.toggle_stop,
|
meillo@0
|
1345 "stop" : app.toggle_stop,
|
meillo@0
|
1346 "volup" : app.inc_volume,
|
meillo@0
|
1347 "voldown" : app.dec_volume,
|
meillo@0
|
1348 "quit" : app.quit}
|
meillo@0
|
1349
|
meillo@0
|
1350 def handle_command(self):
|
meillo@0
|
1351 command = string.strip(self.fd.readline())
|
meillo@0
|
1352 if command in self.commands.keys():
|
meillo@0
|
1353 self.commands[command]()
|
meillo@0
|
1354
|
meillo@0
|
1355 def forward(self):
|
meillo@0
|
1356 app.seek(1, 1)
|
meillo@0
|
1357
|
meillo@0
|
1358 def backward(self):
|
meillo@0
|
1359 app.seek(-1, 1)
|
meillo@0
|
1360
|
meillo@0
|
1361 # ------------------------------------------
|
meillo@0
|
1362 class Application:
|
meillo@0
|
1363 def __init__(self):
|
meillo@0
|
1364 self.keymapstack = KeymapStack()
|
meillo@0
|
1365 self.input_mode = 0
|
meillo@0
|
1366 self.input_prompt = ""
|
meillo@0
|
1367 self.input_string = ""
|
meillo@0
|
1368 self.do_input_hook = None
|
meillo@0
|
1369 self.stop_input_hook = None
|
meillo@0
|
1370 self.complete_input_hook = None
|
meillo@0
|
1371 self.channels = []
|
meillo@0
|
1372 self.restricted = 0
|
meillo@0
|
1373 self.input_keymap = Keymap()
|
meillo@0
|
1374 self.input_keymap.bind(list(Window.chars), self.do_input)
|
meillo@0
|
1375 self.input_keymap.bind(curses.KEY_BACKSPACE, self.do_input, (8,))
|
meillo@0
|
1376 self.input_keymap.bind([21, 23], self.do_input)
|
meillo@0
|
1377 self.input_keymap.bind(['\a', 27], self.cancel_input, ())
|
meillo@0
|
1378 self.input_keymap.bind(['\n', curses.KEY_ENTER],
|
meillo@0
|
1379 self.stop_input, ())
|
meillo@0
|
1380
|
meillo@0
|
1381 def setup(self):
|
meillo@0
|
1382 if tty:
|
meillo@0
|
1383 self.tcattr = tty.tcgetattr(sys.stdin.fileno())
|
meillo@0
|
1384 tcattr = tty.tcgetattr(sys.stdin.fileno())
|
meillo@0
|
1385 tcattr[0] = tcattr[0] & ~(tty.IXON)
|
meillo@0
|
1386 tty.tcsetattr(sys.stdin.fileno(), tty.TCSANOW, tcattr)
|
meillo@0
|
1387 self.w = curses.initscr()
|
meillo@0
|
1388 curses.cbreak()
|
meillo@0
|
1389 curses.noecho()
|
meillo@0
|
1390 try: curses.meta(1)
|
meillo@0
|
1391 except: pass
|
meillo@0
|
1392 self.cursor(0)
|
meillo@0
|
1393 signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
meillo@0
|
1394 signal.signal(signal.SIGHUP, self.handler_quit)
|
meillo@0
|
1395 signal.signal(signal.SIGINT, self.handler_quit)
|
meillo@0
|
1396 signal.signal(signal.SIGTERM, self.handler_quit)
|
meillo@0
|
1397 signal.signal(signal.SIGWINCH, self.handler_resize)
|
meillo@0
|
1398 self.win_root = RootWindow(None)
|
meillo@0
|
1399 self.win_root.update()
|
meillo@0
|
1400 self.win_tab = self.win_root.win_tab
|
meillo@0
|
1401 self.win_filelist = self.win_root.win_tab.win_filelist
|
meillo@0
|
1402 self.win_playlist = self.win_root.win_tab.win_playlist
|
meillo@0
|
1403 self.win_status = self.win_root.win_status
|
meillo@0
|
1404 self.status = self.win_status.status
|
meillo@0
|
1405 self.set_default_status = self.win_status.set_default_status
|
meillo@0
|
1406 self.restore_default_status = self.win_status.restore_default_status
|
meillo@0
|
1407 self.counter = self.win_root.win_counter.counter
|
meillo@0
|
1408 self.progress = self.win_root.win_progress.progress
|
meillo@0
|
1409 self.player = PLAYERS[0]
|
meillo@0
|
1410 self.timeout = Timeout()
|
meillo@0
|
1411 self.play_tid = None
|
meillo@0
|
1412 self.kludge = 0
|
meillo@0
|
1413 self.win_filelist.listdir()
|
meillo@0
|
1414 self.control = FIFOControl()
|
meillo@0
|
1415
|
meillo@0
|
1416 def cleanup(self):
|
meillo@0
|
1417 try: curses.endwin()
|
meillo@0
|
1418 except curses.error: return
|
meillo@0
|
1419 XTERM and sys.stderr.write("\033]0;%s\a" % "xterm")
|
meillo@0
|
1420 tty and tty.tcsetattr(sys.stdin.fileno(), tty.TCSADRAIN, self.tcattr)
|
meillo@0
|
1421 print
|
meillo@0
|
1422
|
meillo@0
|
1423 def run(self):
|
meillo@0
|
1424 while 1:
|
meillo@0
|
1425 now = time.time()
|
meillo@0
|
1426 timeout = self.timeout.check(now)
|
meillo@0
|
1427 self.win_filelist.listdir_maybe(now)
|
meillo@0
|
1428 if not self.player.stopped:
|
meillo@0
|
1429 timeout = 0.5
|
meillo@0
|
1430 if self.kludge and self.player.poll():
|
meillo@0
|
1431 self.player.stopped = 1 # end of playlist hack
|
meillo@0
|
1432 if not self.win_playlist.stop:
|
meillo@0
|
1433 entry = self.win_playlist.change_active_entry(1)
|
meillo@0
|
1434 entry and self.play(entry)
|
meillo@0
|
1435 R = [sys.stdin, self.player.stdout_r, self.player.stderr_r]
|
meillo@0
|
1436 self.control.fd and R.append(self.control.fd)
|
meillo@0
|
1437 try: r, w, e = select.select(R, [], [], timeout)
|
meillo@0
|
1438 except select.error: continue
|
meillo@0
|
1439 self.kludge = 1
|
meillo@0
|
1440 # user
|
meillo@0
|
1441 if sys.stdin in r:
|
meillo@0
|
1442 c = self.win_root.getch()
|
meillo@0
|
1443 self.keymapstack.process(c)
|
meillo@0
|
1444 # player
|
meillo@0
|
1445 if self.player.stderr_r in r:
|
meillo@0
|
1446 self.player.read_fd(self.player.stderr_r)
|
meillo@0
|
1447 # player
|
meillo@0
|
1448 if self.player.stdout_r in r:
|
meillo@0
|
1449 self.player.read_fd(self.player.stdout_r)
|
meillo@0
|
1450 # remote
|
meillo@0
|
1451 if self.control.fd in r:
|
meillo@0
|
1452 self.control.handle_command()
|
meillo@0
|
1453
|
meillo@0
|
1454 def play(self, entry, offset = 0):
|
meillo@0
|
1455 self.kludge = 0
|
meillo@0
|
1456 self.play_tid = None
|
meillo@0
|
1457 if entry is None or offset is None: return
|
meillo@0
|
1458 self.player.stop(quiet=1)
|
meillo@0
|
1459 for self.player in PLAYERS:
|
meillo@0
|
1460 if self.player.re_files.search(entry.pathname):
|
meillo@0
|
1461 if self.player.setup(entry, offset): break
|
meillo@0
|
1462 else:
|
meillo@0
|
1463 app.status(_("Player not found!"), 1)
|
meillo@0
|
1464 self.player.stopped = 0 # keep going
|
meillo@0
|
1465 return
|
meillo@0
|
1466 self.player.play()
|
meillo@0
|
1467
|
meillo@0
|
1468 def delayed_play(self, entry, offset):
|
meillo@0
|
1469 if self.play_tid: self.timeout.remove(self.play_tid)
|
meillo@0
|
1470 self.play_tid = self.timeout.add(0.5, self.play, (entry, offset))
|
meillo@0
|
1471
|
meillo@0
|
1472 def next_song(self):
|
meillo@0
|
1473 self.delayed_play(self.win_playlist.change_active_entry(1), 0)
|
meillo@0
|
1474
|
meillo@0
|
1475 def prev_song(self):
|
meillo@0
|
1476 self.delayed_play(self.win_playlist.change_active_entry(-1), 0)
|
meillo@0
|
1477
|
meillo@0
|
1478 def seek(self, offset, relative):
|
meillo@0
|
1479 if not self.player.entry: return
|
meillo@0
|
1480 self.player.seek(offset, relative)
|
meillo@0
|
1481 self.delayed_play(self.player.entry, self.player.offset)
|
meillo@0
|
1482
|
meillo@0
|
1483 def toggle_pause(self):
|
meillo@0
|
1484 if not self.player.entry: return
|
meillo@0
|
1485 if not self.player.stopped: self.player.toggle_pause()
|
meillo@0
|
1486
|
meillo@0
|
1487 def toggle_stop(self):
|
meillo@0
|
1488 if not self.player.entry: return
|
meillo@0
|
1489 if not self.player.stopped: self.player.stop()
|
meillo@0
|
1490 else: self.play(self.player.entry, self.player.offset)
|
meillo@0
|
1491
|
meillo@0
|
1492 def inc_volume(self):
|
meillo@0
|
1493 self.mixer("cue", 1)
|
meillo@0
|
1494
|
meillo@0
|
1495 def dec_volume(self):
|
meillo@0
|
1496 self.mixer("cue", -1)
|
meillo@0
|
1497
|
meillo@0
|
1498 def key_volume(self, ch):
|
meillo@0
|
1499 self.mixer("set", (ch & 0x0f)*10)
|
meillo@0
|
1500
|
meillo@0
|
1501 def mixer(self, cmd=None, arg=None):
|
meillo@0
|
1502 try: self._mixer(cmd, arg)
|
meillo@0
|
1503 except Exception, e: app.status(e, 2)
|
meillo@0
|
1504
|
meillo@0
|
1505 def _mixer(self, cmd, arg):
|
meillo@0
|
1506 try:
|
meillo@0
|
1507 import ossaudiodev
|
meillo@0
|
1508 mixer = ossaudiodev.openmixer()
|
meillo@0
|
1509 get, set = mixer.get, mixer.set
|
meillo@0
|
1510 self.channels = self.channels or \
|
meillo@0
|
1511 [['MASTER', ossaudiodev.SOUND_MIXER_VOLUME],
|
meillo@0
|
1512 ['PCM', ossaudiodev.SOUND_MIXER_PCM]]
|
meillo@0
|
1513 except ImportError:
|
meillo@0
|
1514 import oss
|
meillo@0
|
1515 mixer = oss.open_mixer()
|
meillo@0
|
1516 get, set = mixer.read_channel, mixer.write_channel
|
meillo@0
|
1517 self.channels = self.channels or \
|
meillo@0
|
1518 [['MASTER', oss.SOUND_MIXER_VOLUME],
|
meillo@0
|
1519 ['PCM', oss.SOUND_MIXER_PCM]]
|
meillo@0
|
1520 if cmd is "toggle": self.channels.insert(0, self.channels.pop())
|
meillo@0
|
1521 name, channel = self.channels[0]
|
meillo@0
|
1522 if cmd is "cue": arg = min(100, max(0, get(channel)[0] + arg))
|
meillo@0
|
1523 if cmd in ["set", "cue"]: set(channel, (arg, arg))
|
meillo@0
|
1524 app.status(_("%s volume %s%%") % (name, get(channel)[0]), 1)
|
meillo@0
|
1525 mixer.close()
|
meillo@0
|
1526
|
meillo@0
|
1527 def show_input(self):
|
meillo@0
|
1528 n = len(self.input_prompt)+1
|
meillo@0
|
1529 s = cut(self.input_string, self.win_status.cols-n, left=1)
|
meillo@0
|
1530 app.status("%s%s " % (self.input_prompt, s))
|
meillo@0
|
1531
|
meillo@0
|
1532 def start_input(self, prompt="", data="", colon=1):
|
meillo@0
|
1533 self.input_mode = 1
|
meillo@0
|
1534 self.cursor(1)
|
meillo@0
|
1535 app.keymapstack.push(self.input_keymap)
|
meillo@0
|
1536 self.input_prompt = prompt + (colon and ": " or "")
|
meillo@0
|
1537 self.input_string = data
|
meillo@0
|
1538 self.show_input()
|
meillo@0
|
1539
|
meillo@0
|
1540 def do_input(self, *args):
|
meillo@0
|
1541 if self.do_input_hook:
|
meillo@0
|
1542 return apply(self.do_input_hook, args)
|
meillo@0
|
1543 ch = args and args[0] or None
|
meillo@0
|
1544 if ch in [8, 127]: # backspace
|
meillo@0
|
1545 self.input_string = self.input_string[:-1]
|
meillo@0
|
1546 elif ch == 9 and self.complete_input_hook:
|
meillo@0
|
1547 self.input_string = self.complete_input_hook(self.input_string)
|
meillo@0
|
1548 elif ch == 21: # C-u
|
meillo@0
|
1549 self.input_string = ""
|
meillo@0
|
1550 elif ch == 23: # C-w
|
meillo@0
|
1551 self.input_string = re.sub("((.* )?)\w.*", "\\1", self.input_string)
|
meillo@0
|
1552 elif ch:
|
meillo@0
|
1553 self.input_string = "%s%c" % (self.input_string, ch)
|
meillo@0
|
1554 self.show_input()
|
meillo@0
|
1555
|
meillo@0
|
1556 def stop_input(self, *args):
|
meillo@0
|
1557 self.input_mode = 0
|
meillo@0
|
1558 self.cursor(0)
|
meillo@0
|
1559 app.keymapstack.pop()
|
meillo@0
|
1560 if not self.input_string:
|
meillo@0
|
1561 app.status(_("cancel"), 1)
|
meillo@0
|
1562 elif self.stop_input_hook:
|
meillo@0
|
1563 apply(self.stop_input_hook, args)
|
meillo@0
|
1564 self.do_input_hook = None
|
meillo@0
|
1565 self.stop_input_hook = None
|
meillo@0
|
1566 self.complete_input_hook = None
|
meillo@0
|
1567
|
meillo@0
|
1568 def cancel_input(self):
|
meillo@0
|
1569 self.input_string = ""
|
meillo@0
|
1570 self.stop_input()
|
meillo@0
|
1571
|
meillo@0
|
1572 def cursor(self, visibility):
|
meillo@0
|
1573 try: curses.curs_set(visibility)
|
meillo@0
|
1574 except: pass
|
meillo@0
|
1575
|
meillo@0
|
1576 def quit(self):
|
meillo@0
|
1577 self.player.stop(quiet=1)
|
meillo@0
|
1578 sys.exit(0)
|
meillo@0
|
1579
|
meillo@0
|
1580 def handler_resize(self, sig, frame):
|
meillo@0
|
1581 # curses trickery
|
meillo@0
|
1582 while 1:
|
meillo@0
|
1583 try: curses.endwin(); break
|
meillo@0
|
1584 except: time.sleep(1)
|
meillo@0
|
1585 self.w.refresh()
|
meillo@0
|
1586 self.win_root.resize()
|
meillo@0
|
1587 self.win_root.update()
|
meillo@0
|
1588
|
meillo@0
|
1589 def handler_quit(self, sig, frame):
|
meillo@0
|
1590 self.quit()
|
meillo@0
|
1591
|
meillo@0
|
1592 # ------------------------------------------
|
meillo@0
|
1593 def main():
|
meillo@0
|
1594 try:
|
meillo@0
|
1595 opts, args = getopt.getopt(sys.argv[1:], "nrRv")
|
meillo@0
|
1596 except:
|
meillo@0
|
1597 usage = _("Usage: %s [-nrRv] [ file | dir | playlist ] ...\n")
|
meillo@0
|
1598 sys.stderr.write(usage % sys.argv[0])
|
meillo@0
|
1599 sys.exit(1)
|
meillo@0
|
1600
|
meillo@0
|
1601 global app
|
meillo@0
|
1602 app = Application()
|
meillo@0
|
1603
|
meillo@0
|
1604 playlist = []
|
meillo@0
|
1605 if not sys.stdin.isatty():
|
meillo@0
|
1606 playlist = map(string.strip, sys.stdin.readlines())
|
meillo@0
|
1607 os.close(0)
|
meillo@0
|
1608 os.open("/dev/tty", 0)
|
meillo@0
|
1609 try:
|
meillo@0
|
1610 app.setup()
|
meillo@0
|
1611 for opt, optarg in opts:
|
meillo@0
|
1612 if opt == "-n": app.restricted = 1
|
meillo@0
|
1613 if opt == "-r": app.win_playlist.command_toggle_repeat()
|
meillo@0
|
1614 if opt == "-R": app.win_playlist.command_toggle_random()
|
meillo@0
|
1615 if opt == "-v": app.mixer("toggle")
|
meillo@0
|
1616 if args or playlist:
|
meillo@0
|
1617 for i in args or playlist:
|
meillo@0
|
1618 app.win_playlist.add(os.path.abspath(i))
|
meillo@0
|
1619 app.win_tab.change_window()
|
meillo@0
|
1620 app.run()
|
meillo@0
|
1621 except SystemExit:
|
meillo@0
|
1622 app.cleanup()
|
meillo@0
|
1623 except Exception:
|
meillo@0
|
1624 app.cleanup()
|
meillo@0
|
1625 import traceback
|
meillo@0
|
1626 traceback.print_exc()
|
meillo@0
|
1627
|
meillo@0
|
1628 # ------------------------------------------
|
meillo@0
|
1629 PLAYERS = [
|
meillo@0
|
1630 FrameOffsetPlayer("ogg123 -q -v -k %d %s", "\.ogg$"),
|
meillo@0
|
1631 FrameOffsetPlayer("splay -f -k %d %s", "(^http://|\.mp[123]$)", 38.28),
|
meillo@0
|
1632 FrameOffsetPlayer("mpg123 -q -v -k %d %s", "(^http://|\.mp[123]$)", 38.28),
|
meillo@0
|
1633 FrameOffsetPlayer("mpg321 -q -v -k %d %s", "(^http://|\.mp[123]$)", 38.28),
|
meillo@0
|
1634 TimeOffsetPlayer("madplay -v --display-time=remaining -s %d %s", "\.mp[123]$"),
|
meillo@0
|
1635 NoOffsetPlayer("mikmod -q -p0 %s", "\.(mod|xm|fm|s3m|med|col|669|it|mtm)$"),
|
meillo@0
|
1636 NoOffsetPlayer("xmp -q %s", "\.(mod|xm|fm|s3m|med|col|669|it|mtm|stm)$"),
|
meillo@0
|
1637 NoOffsetPlayer("play %s", "\.(aiff|au|cdr|mp3|ogg|wav)$"),
|
meillo@0
|
1638 NoOffsetPlayer("speexdec %s", "\.spx$")
|
meillo@0
|
1639 ]
|
meillo@0
|
1640
|
meillo@0
|
1641 def VALID_SONG(name):
|
meillo@0
|
1642 for player in PLAYERS:
|
meillo@0
|
1643 if player.re_files.search(name):
|
meillo@0
|
1644 return 1
|
meillo@0
|
1645
|
meillo@0
|
1646 def VALID_PLAYLIST(name):
|
meillo@0
|
1647 if re.search("\.(m3u|pls)$", name, re.I):
|
meillo@0
|
1648 return 1
|
meillo@0
|
1649
|
meillo@0
|
1650 for rc in [os.path.expanduser("~/.cplayrc"), "/etc/cplayrc"]:
|
meillo@0
|
1651 try: execfile(rc); break
|
meillo@0
|
1652 except IOError: pass
|
meillo@0
|
1653
|
meillo@0
|
1654 # ------------------------------------------
|
meillo@0
|
1655 if __name__ == "__main__": main()
|