# control.py --- Python interface to libxmms -- control module. # Copyright (c) 2002, 2003 Florent Rougon # # This file is part of PyXMMS. # # PyXMMS is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # PyXMMS is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, # MA 02111-1307, USA. """Python interface to XMMS --- control module. This module provides a Python interface to control XMMS (the X MultiMedia System), an audio and video player for Unix-like platforms. This module provides bindings for all the xmms_remote_* functions accessible through the libxmms library (which comes with XMMS), plus a few higher-level functions that I (Florent Rougon) find useful. The function names and mappings between the calling syntax of the C functions from libxmms and that of their Python bindings are meant to be mechanical (see below). Note: I use the expression "Python binding for foo()" for a Python function that wraps (thus calls) directly, not doing additional work, the C function foo() defined in libxmms. Function names -------------- The binding for the libxmms function xmms_remote_foo() will be called foo; therefore, you will probably use it in this way: import xmms.control res = xmms.control.foo(arg, ...) Calling syntax -- passing arguments and getting results ------------------------------------------------------- Each xmms_remote_* function from libxmms takes as its first argument the XMMS session to control. With xmms.control, this argument is optional (defaulting to 0, which is generally what you want if you don't launch multiple XMMS sessions at once) and comes last. For the other arguments: - the type mapping should be obvious (if the C function expects a gint, the Python binding expects a Python integer, same for char * and strings, gfloats and floats, etc.); when the C function expects a list (such as a GList *), the Python binding expects a sequence (like a list or a tuple). gboolean types are mapped to Python integers (FALSE is mapped to 0 and TRUE to 1). Except for 'session', which has to come last so as to be optional, the order of the arguments is always preserved. Example: void xmms_remote_set_volume(gint session, gint vl, gint vr) is mapped to: set_volume(vl, vr, session) where 'vl' and 'vr' have to be Python integers and 'session' (also an integer), is optional and defaults to 0. Note: 'vl' and 'vr' stand for left and right volume, respectively. - if the C function returns values through the use of pointers, these are returned by the corresponding Python binding and therefore are removed from the argument list of the Python binding. The type/structure of the return value is the most obvious one I could think of (e.g. a single integer if the function returns a gboolean). The most non-obvious example is indeed quite simple, as you can see: void xmms_remote_get_eq(gint session, gfloat *preamp, gfloat **bands) which returns a global preamp gain (in dB) and a list of gains for 10 frequeny bands. It is called like this in Python: (preamp, bands) = get_eq(session) where 'session' is optional (see above), 'preamp' is a float and 'bands' is a 10-tuple of floats. Note: this is written from this module's scope, but in real life, you would of course probably have: import xmms.control [...] (preamp, bands) = xmms.control.get_eq(session) Functions exported by this module --------------------------------- * From libxmms playlist playlist_add playlist_delete playlist_clear playlist_add_url_string playlist_ins_url_string get_playlist_length get_playlist_pos set_playlist_pos get_playlist_file get_playlist_title get_playlist_time play pause play_pause stop eject playlist_prev playlist_next jump_to_time is_running is_playing is_paused get_output_time get_info get_volume set_volume get_main_volume set_main_volume get_balance set_balance get_eq set_eq get_eq_preamp set_eq_preamp get_eq_band set_eq_band get_skin set_skin main_win_toggle pl_win_toggle eq_win_toggle is_main_win is_pl_win is_eq_win show_prefs_box show_about_box toggle_aot toggle_repeat toggle_shuffle is_repeat is_shuffle get_version quit play_files (deprecated in libxmms) * Specific to this module (no direct binding in libxmms) playlist_add_allow_relative enqueue_and_play enqueue_and_play_launch_if_session_not_started fade_out Exceptions specific to this module ---------------------------------- ExecutableNotFound InvalidFadeOutAction Both exceptions are subclasses of xmms.error. """ import re, os, os.path, string, time import common from _xmmscontrol import * class ExecutableNotFound(common.error): """Exception raised when the XMMS executable can't be found.""" ExceptionShortDescription = "Executable not found" class InvalidFadeOutAction(common.error): """Exception raised when fade_out is given an invalid action.""" ExceptionShortDescription = "Invalid action" def _find_in_path(prog_name): """Search an executable in the PATH, like the exec*p functions do. If PATH is not defined, the default path ":/bin:/usr/bin" is used, as with the C library exec*p functions. Return the absolute file name or None if no readable and executable file is found. """ PATH = os.getenv("PATH", ":/bin:/usr/bin") # see the execvp(3) man page for dir in string.split(PATH, ":"): full_path = os.path.join(dir, prog_name) if os.path.isfile(full_path) \ and os.access(full_path, os.R_OK | os.X_OK): return full_path return None def _find_and_check_executable(prog_name): """Return the absolute file name if OK, None otherwise.""" if prog_name[0:2] == "./" or prog_name[0:3] == "../": abs_file_name = os.path.join(os.getcwd(), prog_name) elif os.path.isabs(prog_name): abs_file_name = prog_name else: # This checks the r and x bits return _find_in_path(prog_name) if os.path.isfile(abs_file_name) and \ os.access(abs_file_name, os.R_OK | os.X_OK): return abs_file_name else: return None # Same as playlist_add but converts all relative paths to absolute def playlist_add_allow_relative(seq, session=0): """Add files/URLs to the playlist, allowing relative file names. seq -- a sequence of files/URLs session -- the XMMS session to act on Return None. """ # Regexp matching absolute paths and strings containing "://" abs_re = re.compile(r"/|.*://") abs_seq = list(seq) for i in range(len(seq)): if not (abs_re.match(seq[i])): abs_seq[i] = os.path.join(os.getcwd(), seq[i]) playlist_add(abs_seq, session) def enqueue_and_play(seq, session=0): """Add files/URLs to the playlist and start playing from the first one. seq -- a sequence of files/URLs session -- the XMMS session to act on The files/URLs in seq are added to the playlist and XMMS is asked to play starting at the first element of seq. Return None. """ pl = get_playlist_length(session) playlist_add_allow_relative(seq, session) set_playlist_pos(pl, session) play() def enqueue_and_play_launch_if_session_not_started(seq, xmms_prg="xmms", session=0): """Add files/URLs to the playlist and start playing from the first one. seq -- a sequence of files/URLs xmms_prg -- the name (absolute or looked up in the PATH) of an XMMS binary to invoke in case the specified session is not running session -- the XMMS session to act on This function is identical to enqueue_and_play except that it spawns an XMMS process if the requested session is not running. Return None. Notable exception: xmms.control.ExecutableNotFound is raised if 'xmms_prg' can't be found, read and executed. """ if not is_running(session): # We want to warn the user if the XMMS executable can't be # found or executed, so we have to check *before* forking. abs_prog_name = _find_and_check_executable(xmms_prg) if not abs_prog_name: raise ExecutableNotFound("can't find XMMS executable") child_pid = os.fork() if child_pid == 0: # We are in the child, me MUST NOT trigger any exception (look at #_spawnvef in Python's os.py). try: os.execvp(abs_prog_name, (abs_prog_name, "-p") + tuple(seq)) except: # We cannot know in the father process if the child's execvp # failed, but AFAIK, there is no simple solution since I don't # want the father to wait for the child's completion. init # will be a good father. :-) os._exit(127) else: enqueue_and_play(seq, session) def fade_out(action="stop", nb_steps=20, step_duration=0.5, restore_volume=1, session=0): """Fade out the volume to stop or pause the playback. Progressively decrease the main volume, then stop or pause (depending on the 'action' argument), then optionally restore the original main volume setting. action -- a string, either "stop" or "pause" nb_steps -- number of decrease-volume steps to use step_duration -- duration of a step (float, in seconds) restore_volume -- boolean (0 = false, 1 = true) telling whether to restore the original main volume setting after the fade out session -- the XMMS session to act on Return None. Notable exception: xmms.control.InvalidFadeOutAction is raised if 'action' is invalid. """ vol = orig_volume = get_main_volume(session) # int() to be safe with Python >= 3.0 while still working before 2.2 # (first Python version with the // floor division) # Also, int() returns an integer, contrary to math.floor(). decr = int(orig_volume / nb_steps) for i in range(nb_steps): vol = vol - decr set_main_volume(vol, session) time.sleep(step_duration) # Stop/pause the playback and restore the volume as it was before the fade # out. if action == "stop": stop(session) elif action == "pause": pause(session) else: raise InvalidFadeOutAction("invalid action for fade_out: %s" % action) if restore_volume: set_main_volume(orig_volume)