cplay

annotate cplay @ 0:aa5f022eac8a

Use upstream cplay-1.49 as a start
author markus schnalke <meillo@marmaro.de>
date Wed, 27 Sep 2017 09:22:32 +0200
parents
children c7d8ec7da73b
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()