├── .cvsignore ├── install ├── pycdg ├── pykar ├── pympg ├── pykaraoke ├── pykaraoke_mini ├── pykaraoke_mini.desktop ├── pykaraoke.desktop ├── rescan_songs.gpe ├── pykaraoke.gpe ├── cdg2mpg ├── windows_installer.nsi └── mplayer-gp2x-cmdline-pykaraoke.diff ├── icons ├── note.ico ├── splash.png ├── splash.xcf ├── audio_16.png ├── microphone.ico ├── microphone.png ├── folder_close_16.png ├── folder_open_16.png └── pykaraoke.xpm ├── fonts ├── DejaVuSans.ttf ├── DejaVuSansCondensed.ttf ├── DejaVuSansCondensed-Bold.ttf └── LICENSE ├── cross-build-gp2x.sh ├── pykenv.py ├── pykversion.py ├── TODO ├── MANIFEST.in ├── setup.cfg ├── pykconstants.py ├── performer_prompt.py ├── _cpuctrl.c ├── setup.py ├── pympg.py ├── pykplayer.py ├── ChangeLog ├── pycdgAux.py ├── pycdg.py └── pykmanager.py /.cvsignore: -------------------------------------------------------------------------------- 1 | .cvsignore 2 | *.pyc 3 | MANIFEST 4 | build 5 | dist 6 | -------------------------------------------------------------------------------- /install/pycdg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import pycdg 3 | pycdg.main() 4 | -------------------------------------------------------------------------------- /install/pykar: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import pykar 3 | pykar.main() 4 | -------------------------------------------------------------------------------- /install/pympg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import pympg 3 | pympg.main() 4 | -------------------------------------------------------------------------------- /icons/note.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinlawson/pykaraoke/HEAD/icons/note.ico -------------------------------------------------------------------------------- /install/pykaraoke: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import pykaraoke 3 | pykaraoke.main() 4 | -------------------------------------------------------------------------------- /icons/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinlawson/pykaraoke/HEAD/icons/splash.png -------------------------------------------------------------------------------- /icons/splash.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinlawson/pykaraoke/HEAD/icons/splash.xcf -------------------------------------------------------------------------------- /fonts/DejaVuSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinlawson/pykaraoke/HEAD/fonts/DejaVuSans.ttf -------------------------------------------------------------------------------- /icons/audio_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinlawson/pykaraoke/HEAD/icons/audio_16.png -------------------------------------------------------------------------------- /icons/microphone.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinlawson/pykaraoke/HEAD/icons/microphone.ico -------------------------------------------------------------------------------- /icons/microphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinlawson/pykaraoke/HEAD/icons/microphone.png -------------------------------------------------------------------------------- /install/pykaraoke_mini: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import pykaraoke_mini 3 | pykaraoke_mini.main() 4 | -------------------------------------------------------------------------------- /icons/folder_close_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinlawson/pykaraoke/HEAD/icons/folder_close_16.png -------------------------------------------------------------------------------- /icons/folder_open_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinlawson/pykaraoke/HEAD/icons/folder_open_16.png -------------------------------------------------------------------------------- /fonts/DejaVuSansCondensed.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinlawson/pykaraoke/HEAD/fonts/DejaVuSansCondensed.ttf -------------------------------------------------------------------------------- /fonts/DejaVuSansCondensed-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinlawson/pykaraoke/HEAD/fonts/DejaVuSansCondensed-Bold.ttf -------------------------------------------------------------------------------- /install/pykaraoke_mini.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Encoding=UTF-8 4 | Name=PyKaraoke Mini 5 | GenericName=Karaoke player 6 | Comment=Minimum-interface PyKaraoke 7 | Icon=/usr/share/pykaraoke/icons/pykaraoke.xpm 8 | Exec=pykaraoke_mini 9 | Terminal=false 10 | MimeType=audio/x-karaoke;audio/x-midi;audio/midi 11 | Categories=Application;AudioVideo;Player; 12 | -------------------------------------------------------------------------------- /install/pykaraoke.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Encoding=UTF-8 4 | Name=PyKaraoke 5 | Name[lt]=PyKaraoke 6 | GenericName=Karaoke player 7 | Comment=Universal karaoke songs and movies player 8 | Comment[lt]=Universalus karaoke muzikos bei video grotuvas 9 | Icon=/usr/share/pykaraoke/icons/pykaraoke.xpm 10 | Exec=pykaraoke 11 | Terminal=false 12 | MimeType=audio/x-karaoke;audio/x-midi;audio/midi 13 | Categories=Application;AudioVideo;Player; 14 | -------------------------------------------------------------------------------- /install/rescan_songs.gpe: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # This script is used to rescan the songs directory on the GP2X console. 4 | # 5 | 6 | PYTHONHOME=python 7 | export PYTHONHOME 8 | 9 | LD_LIBRARY_PATH=${PYTHONHOME}/extras:${LD_LIBRARY_PATH} 10 | export LD_LIBRARY_PATH 11 | 12 | ${PYTHONHOME}/python pykaraoke_mini.py --set-scan-dir=songs --scan > pykaraoke.log 2>&1 13 | echo Exit with status $? >> pykaraoke.log 14 | 15 | sync 16 | cd /usr/gp2x 17 | exec ./gp2xmenu 18 | -------------------------------------------------------------------------------- /install/pykaraoke.gpe: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # This script is used to start PyKaraoke on the GP2X console. 4 | # 5 | 6 | PYTHONHOME=python 7 | export PYTHONHOME 8 | 9 | LD_LIBRARY_PATH=${PYTHONHOME}/extras:${LD_LIBRARY_PATH} 10 | export LD_LIBRARY_PATH 11 | 12 | # Uncomment the following two lines to write temporary files to the SD 13 | # card, instead of to /tmp. This will be slower, but will allow you 14 | # to player larger files stored in .zip files (unless your SD card is 15 | # nearly full). 16 | 17 | #PYKARAOKE_TEMP_DIR=.pykaraoke 18 | #export PYKARAOKE_TEMP_DIR 19 | 20 | rm -f pykaraoke.log 21 | 22 | ${PYTHONHOME}/python pykaraoke_mini.py >> pykaraoke.log 2>&1 23 | echo Exit with status $? >> pykaraoke.log 24 | 25 | sync 26 | cd /usr/gp2x 27 | exec ./gp2xmenu 28 | -------------------------------------------------------------------------------- /cross-build-gp2x.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | export PATH=/gp2xsdk/Tools/bin:$PATH 4 | export LD_LIBRARY_PATH=/gp2xsdk/Tools/lib:$LD_LIBRARY_PATH 5 | 6 | test -d build/temp.arm-gp2x-linux || mkdir build/temp.arm-gp2x-linux 7 | test -d build/lib.arm-gp2x-linux || mkdir build/lib.arm-gp2x-linux 8 | 9 | arm-gp2x-linux-gcc -pthread -fno-strict-aliasing -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -D_GNU_SOURCE -fPIC -I/gp2xsdk/installed/include/python2.4 -I/gp2xsdk/installed/include/SDL -I/gp2xsdk/Tools/arm-gp2x-linux/include/SDL -c _pycdgAux.c -o build/temp.arm-gp2x-linux/_pycdgAux.o || exit 10 | 11 | arm-gp2x-linux-gcc -pthread -shared -L/gp2xsdk/installed/lib -L/gp2xsdk/Tools/arm-gp2x-linux/lib build/temp.arm-gp2x-linux/_pycdgAux.o -o build/lib.arm-gp2x-linux/_pycdgAux.so -lSDL || exit 12 | 13 | 14 | arm-gp2x-linux-gcc -pthread -fno-strict-aliasing -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -D_GNU_SOURCE -fPIC -I/gp2xsdk/installed/include/python2.4 -c _cpuctrl.c -o build/temp.arm-gp2x-linux/_cpuctrl.o || exit 15 | 16 | arm-gp2x-linux-gcc -pthread -shared -L/gp2xsdk/installed/lib -L/gp2xsdk/Tools/arm-gp2x-linux/lib build/temp.arm-gp2x-linux/_cpuctrl.o -o build/lib.arm-gp2x-linux/_cpuctrl.so || exit 17 | 18 | -------------------------------------------------------------------------------- /pykenv.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2010 Kelvin Lawson (kelvinl@users.sourceforge.net) 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | 18 | """ This module guesses the execution environment of the application. """ 19 | 20 | from pykconstants import * 21 | import os 22 | 23 | # Try to guess which environment we're running in. 24 | if os.name == "posix": 25 | (uname, host, release, version, machine) = os.uname() 26 | if host == "gp2x": 27 | env = ENV_GP2X 28 | elif (uname.lower()[:6] == "darwin"): 29 | env = ENV_OSX 30 | else: 31 | env = ENV_POSIX 32 | elif os.name == "nt": 33 | env = ENV_WINDOWS 34 | else: 35 | env = ENV_UNKNOWN 36 | -------------------------------------------------------------------------------- /install/cdg2mpg: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # Sanity-check parameters. 4 | for cdg in "$@"; do 5 | if [ `basename "$cdg" .cdg` == "$cdg" ]; then 6 | echo "$cdg" does not end in .cdg. 7 | exit 1 8 | fi 9 | if [ ! -f "$cdg" ]; then 10 | echo "$cdg" does not exist. 11 | exit 1 12 | fi 13 | wav=`dirname "$cdg"`/`basename "$cdg" .cdg`.wav 14 | if [ ! -f "$wav" ]; then 15 | echo "$wav" does not exist. 16 | exit 1 17 | fi 18 | done 19 | 20 | for cdg in "$@"; do 21 | # Get temporary and output filenames, based on the input filename. 22 | wav=`dirname "$cdg"`/`basename "$cdg" .cdg`.wav 23 | ppm=`dirname "$cdg"`/`basename "$cdg" .cdg`.ppm 24 | mpa=`dirname "$cdg"`/`basename "$cdg" .cdg`.mpa 25 | mpv=`dirname "$cdg"`/`basename "$cdg" .cdg`.mpv 26 | mpg=`dirname "$cdg"`/`basename "$cdg" .cdg`.mpg 27 | 28 | # Use PyKaraoke to extract the frames. We render into a slightly 29 | # larger window than 288x192, to give room for a border and allow 30 | # for TV overscan. 31 | 32 | python pycdg.py --dump="$ppm" --dump-fps=29.97 --zoom=none --width=320 --height=240 "$cdg" || exit 33 | 34 | # Scale the video up to the DVD-sized screen, and convert it to mpeg. 35 | ppmtoy4m -v0 -F30000:1001 "$ppm" | y4mscaler -v0 -O preset=dvd | mpeg2enc -v0 -f8 -b7500 -o "$mpv" || exit 36 | 37 | # Convert the audio to mpeg. 38 | mp2enc -v0 -b224 -r48000 -s -o "$mpa" <"$wav" || exit 39 | 40 | # Multiplex the audio and the video together. 41 | mplex -v0 -f8 "$mpa" "$mpv" -o "$mpg" || exit 42 | 43 | # Clean up. 44 | rm -f "$ppm" "$mpa" "$mpv" 45 | done 46 | 47 | -------------------------------------------------------------------------------- /pykversion.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | #**** **** 3 | #**** Copyright (C) 2011 Kelvin Lawson (kelvinl@users.sourceforge.net) **** 4 | #**** Copyright (C) 2011 PyKaraoke Development Team **** 5 | #**** **** 6 | #**** This library is free software; you can redistribute it and/or **** 7 | #**** modify it under the terms of the GNU Lesser General Public **** 8 | #**** License as published by the Free Software Foundation; either **** 9 | #**** version 2.1 of the License, or (at your option) any later version. **** 10 | #**** **** 11 | #**** This library is distributed in the hope that it will be useful, **** 12 | #**** but WITHOUT ANY WARRANTY; without even the implied warranty of **** 13 | #**** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU **** 14 | #**** Lesser General Public License for more details. **** 15 | #**** **** 16 | #**** You should have received a copy of the GNU Lesser General Public **** 17 | #**** License along with this library; if not, write to the **** 18 | #**** Free Software Foundation, Inc. **** 19 | #**** 59 Temple Place, Suite 330 **** 20 | #**** Boston, MA 02111-1307 USA **** 21 | #****************************************************************************** 22 | 23 | # Version string 24 | PYKARAOKE_VERSION_STRING = "0.7.5" 25 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | #**** This is a part of PyKaraoke. **** 3 | #**** **** 4 | #**** Copyright (C) 2010 PyKaraoke Development Team **** 5 | #**** **** 6 | #**** This library is free software; you can redistribute it and/or **** 7 | #**** modify it under the terms of the GNU Lesser General Public **** 8 | #**** License as published by the Free Software Foundation; either **** 9 | #**** version 2.1 of the License, or (at your option) any later version. **** 10 | #**** **** 11 | #**** This library is distributed in the hope that it will be useful, **** 12 | #**** but WITHOUT ANY WARRANTY; without even the implied warranty of **** 13 | #**** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU **** 14 | #**** Lesser General Public License for more details. **** 15 | #**** **** 16 | #**** You should have received a copy of the GNU Lesser General Public **** 17 | #**** License along with this library; if not, write to the **** 18 | #**** Free Software Foundation, Inc. **** 19 | #**** 59 Temple Place, Suite 330 **** 20 | #**** Boston, MA 02111-1307 USA **** 21 | #****************************************************************************** 22 | 23 | * Have a position bar for where the current karaoke song is. 24 | 25 | * Clear search box on click 26 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | #**** This is a part of PyKaraoke. **** 3 | #**** **** 4 | #**** Copyright (C) 2007 Kelvin Lawson (kelvinl@users.sourceforge.net) **** 5 | #**** Copyright (C) 2009 PyKaraoke Development Team **** 6 | #**** **** 7 | #**** This library is free software; you can redistribute it and/or **** 8 | #**** modify it under the terms of the GNU Lesser General Public **** 9 | #**** License as published by the Free Software Foundation; either **** 10 | #**** version 2.1 of the License, or (at your option) any later version. **** 11 | #**** **** 12 | #**** This library is distributed in the hope that it will be useful, **** 13 | #**** but WITHOUT ANY WARRANTY; without even the implied warranty of **** 14 | #**** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU **** 15 | #**** Lesser General Public License for more details. **** 16 | #**** **** 17 | #**** You should have received a copy of the GNU Lesser General Public **** 18 | #**** License along with this library; if not, write to the **** 19 | #**** Free Software Foundation, Inc. **** 20 | #**** 59 Temple Place, Suite 330 **** 21 | #**** Boston, MA 02111-1307 USA **** 22 | #****************************************************************************** 23 | 24 | recursive-include fonts *.ttf 25 | recursive-include icons *.png *.ico *.xpm 26 | recursive-include install * 27 | include README.txt COPYING TODO -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | #**** This is a part of PyKaraoke. **** 3 | #**** **** 4 | #**** Copyright (C) 2010 Kelvin Lawson (kelvinl@users.sourceforge.net) **** 5 | #**** Copyright (C) 2010 PyKaraoke Development Team **** 6 | #**** **** 7 | #**** This library is free software; you can redistribute it and/or **** 8 | #**** modify it under the terms of the GNU Lesser General Public **** 9 | #**** License as published by the Free Software Foundation; either **** 10 | #**** version 2.1 of the License, or (at your option) any later version. **** 11 | #**** **** 12 | #**** This library is distributed in the hope that it will be useful, **** 13 | #**** but WITHOUT ANY WARRANTY; without even the implied warranty of **** 14 | #**** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU **** 15 | #**** Lesser General Public License for more details. **** 16 | #**** **** 17 | #**** You should have received a copy of the GNU Lesser General Public **** 18 | #**** License along with this library; if not, write to the **** 19 | #**** Free Software Foundation, Inc. **** 20 | #**** 59 Temple Place, Suite 330 **** 21 | #**** Boston, MA 02111-1307 USA **** 22 | #****************************************************************************** 23 | 24 | [bdist_rpm] 25 | doc_files = README.txt, COPYING, TODO 26 | group = Applications/Multimedia 27 | requires=python,pygame,numeric,wxpython,timidity,freepats 28 | 29 | [build_ext] 30 | include_dirs = /usr/include/SDL 31 | -------------------------------------------------------------------------------- /pykconstants.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2010 Kelvin Lawson (kelvinl@users.sourceforge.net) 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | 18 | """ This module defines various constants that are used throughout 19 | this package. """ 20 | 21 | # Environment 22 | ENV_WINDOWS = 1 23 | ENV_POSIX = 2 24 | ENV_OSX = 3 25 | ENV_GP2X = 4 26 | ENV_UNKNOWN = 5 27 | 28 | # States 29 | STATE_INIT = 0 30 | STATE_PLAYING = 1 31 | STATE_PAUSED = 2 32 | STATE_NOT_PLAYING = 3 33 | STATE_CLOSING = 4 34 | STATE_CLOSED = 5 35 | STATE_CAPTURING = 6 36 | 37 | # GP2X joystick button mappings 38 | GP2X_BUTTON_UP = (0) 39 | GP2X_BUTTON_DOWN = (4) 40 | GP2X_BUTTON_LEFT = (2) 41 | GP2X_BUTTON_RIGHT = (6) 42 | GP2X_BUTTON_UPLEFT = (1) 43 | GP2X_BUTTON_UPRIGHT = (7) 44 | GP2X_BUTTON_DOWNLEFT = (3) 45 | GP2X_BUTTON_DOWNRIGHT = (5) 46 | GP2X_BUTTON_CLICK = (18) 47 | GP2X_BUTTON_A = (12) 48 | GP2X_BUTTON_B = (13) 49 | GP2X_BUTTON_X = (14) 50 | GP2X_BUTTON_Y = (15) 51 | GP2X_BUTTON_L = (10) 52 | GP2X_BUTTON_R = (11) 53 | GP2X_BUTTON_START = (8) 54 | GP2X_BUTTON_SELECT = (9) 55 | GP2X_BUTTON_VOLUP = (16) 56 | GP2X_BUTTON_VOLDOWN = (17) 57 | 58 | # Left and top margins 59 | Y_BORDER = 20 60 | X_BORDER = 20 61 | -------------------------------------------------------------------------------- /install/windows_installer.nsi: -------------------------------------------------------------------------------- 1 | ; 2 | ; This script needs to be processed by the NSIS compiler to produce a 3 | ; convenient Windows installer for public distribution. 4 | ; 5 | ; This script is designed to be invoked from setup.py, which passes 6 | ; the following variables to it: 7 | ; 8 | ; VERSION - the current version of PyKaraoke 9 | ; 10 | ;-------------------------------- 11 | 12 | !include "MUI.nsh" 13 | 14 | ; The name of the installer 15 | Name "PyKaraoke" 16 | 17 | ; The file to write 18 | OutFile "..\pykaraoke-${VERSION}.exe" 19 | 20 | ; The default installation directory 21 | InstallDir $PROGRAMFILES\PyKaraoke 22 | 23 | ; Registry key to check for the directory (so if you install again, it 24 | ; will overwrite the old one automatically) 25 | InstallDirRegKey HKLM "Software\PyKaraoke" "Install_Dir" 26 | 27 | SetCompress auto 28 | 29 | ; Comment out this line to use the default compression instead during 30 | ; development, which is much faster, but doesn't do quite as good a 31 | ; job. 32 | SetCompressor lzma 33 | 34 | ;-------------------------------- 35 | 36 | ; Pages 37 | 38 | !insertmacro MUI_PAGE_WELCOME 39 | !insertmacro MUI_PAGE_DIRECTORY 40 | Var STARTMENU_FOLDER 41 | !insertmacro MUI_PAGE_STARTMENU "PyKaraoke" $STARTMENU_FOLDER 42 | !insertmacro MUI_PAGE_INSTFILES 43 | !insertmacro MUI_PAGE_FINISH 44 | 45 | !insertmacro MUI_UNPAGE_WELCOME 46 | !insertmacro MUI_UNPAGE_CONFIRM 47 | !insertmacro MUI_UNPAGE_INSTFILES 48 | !insertmacro MUI_UNPAGE_FINISH 49 | 50 | !insertmacro MUI_LANGUAGE "English" 51 | 52 | ;-------------------------------- 53 | 54 | ; The stuff to install 55 | Section "" ;No components page, name is not important 56 | 57 | ; Set output path to the installation directory. 58 | SetOutPath $INSTDIR 59 | 60 | ; Put the entire contents of the dist directory there. 61 | File /r "..\dist\*" 62 | 63 | RMDir /r "$SMPROGRAMS\$STARTMENU_FOLDER" 64 | CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER" 65 | CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\PyKaraoke.lnk" "$INSTDIR\pykaraoke.exe" 66 | CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\PyKaraoke Mini.lnk" "$INSTDIR\pykaraoke_mini.exe" 67 | WriteINIStr "$SMPROGRAMS\$STARTMENU_FOLDER\PyKaraoke on the Web.url" "InternetShortcut" "URL" "http://www.kibosh.org/pykaraoke/" 68 | 69 | SectionEnd ; end the section 70 | 71 | Section -post 72 | DetailPrint "Adding the uninstaller ..." 73 | Delete "$INSTDIR\uninst.exe" 74 | WriteUninstaller "$INSTDIR\uninst.exe" 75 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PyKaraoke" "DisplayName" "PyKaraoke" 76 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PyKaraoke" "UninstallString" '"$INSTDIR\uninst.exe"' 77 | CreateShortcut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall PyKaraoke.lnk" "$INSTDIR\uninst.exe" 78 | 79 | SectionEnd 80 | 81 | Section Uninstall 82 | 83 | Delete "$INSTDIR\uninst.exe" 84 | RMDir /r "$SMPROGRAMS\$STARTMENU_FOLDER" 85 | RMDir /r "$INSTDIR" 86 | DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\PyKaraoke" 87 | 88 | SectionEnd 89 | -------------------------------------------------------------------------------- /performer_prompt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # performer_prompt - Karaoke Performer Request 4 | # 5 | #****************************************************************************** 6 | #**** **** 7 | #**** Copyright (C) 2010 PyKaraoke Development Team **** 8 | #**** **** 9 | #**** This library is free software; you can redistribute it and/or **** 10 | #**** modify it under the terms of the GNU Lesser General Public **** 11 | #**** License as published by the Free Software Foundation; either **** 12 | #**** version 2.1 of the License, or (at your option) any later version. **** 13 | #**** **** 14 | #**** This library is distributed in the hope that it will be useful, **** 15 | #**** but WITHOUT ANY WARRANTY; without even the implied warranty of **** 16 | #**** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU **** 17 | #**** Lesser General Public License for more details. **** 18 | #**** **** 19 | #**** You should have received a copy of the GNU Lesser General Public **** 20 | #**** License along with this library; if not, write to the **** 21 | #**** Free Software Foundation, Inc. **** 22 | #**** 59 Temple Place, Suite 330 **** 23 | #**** Boston, MA 02111-1307 USA **** 24 | #****************************************************************************** 25 | 26 | import wx 27 | 28 | class PerformerPrompt(wx.Dialog): 29 | """ An interface for requesting a performer's name. """ 30 | def __init__(self, parent): 31 | """ Creates the interface. """ 32 | wx.Dialog.__init__(self, parent, -1, "Karaoke Performer Prompt") 33 | 34 | # Add the performer prompt 35 | self.PerformerText = wx.StaticText(self, wx.ID_ANY, "Performer Name:") 36 | self.PerformerID = wx.NewId() 37 | self.PerformerTxtCtrl = wx.TextCtrl(self, self.PerformerID, "", size=(150, 20), style=wx.TE_PROCESS_ENTER) 38 | self.PerformerSizer = wx.BoxSizer(wx.HORIZONTAL) 39 | self.PerformerSizer.Add(self.PerformerText, 0, wx.ALL) 40 | self.PerformerSizer.Add(self.PerformerTxtCtrl, 0, wx.ALL) 41 | 42 | # Add window buttons 43 | self.ButtonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL) 44 | self.Bind(wx.EVT_BUTTON, self.onOK, id = wx.ID_OK) 45 | self.Bind(wx.EVT_BUTTON, self.onCANCEL, id = wx.ID_CANCEL) 46 | 47 | # Create GUI with Sizers 48 | self.MainSizer = wx.BoxSizer(wx.VERTICAL) 49 | self.MainSizer.Add(self.PerformerSizer, 0, wx.ALL, 3) 50 | self.MainSizer.Add(self.ButtonSizer, 0, wx.ALL, 3) 51 | self.SetSizerAndFit(self.MainSizer) 52 | 53 | self.performer = "" 54 | self.PerformerTxtCtrl.SetFocus() 55 | 56 | def onCANCEL(self, event): 57 | """ Sets the performer entered and closes the dialogue. """ 58 | self.performer = "" 59 | self.EndModal(wx.ID_CANCEL) 60 | return False 61 | 62 | def onOK(self, event): 63 | """ Sets the performer entered and closes the dialogue. """ 64 | self.performer = self.PerformerTxtCtrl.GetValue() 65 | self.EndModal(wx.ID_OK) 66 | return True 67 | 68 | def getPerformer(self): 69 | """ Gives the performer's name """ 70 | return self.performer 71 | -------------------------------------------------------------------------------- /fonts/LICENSE: -------------------------------------------------------------------------------- 1 | Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. Glyphs imported from Arev fonts are (c) Tavmjung Bah (see below) 2 | 3 | Bitstream Vera Fonts Copyright 4 | ------------------------------ 5 | 6 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is 7 | a trademark of Bitstream, Inc. 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of the fonts accompanying this license ("Fonts") and associated 11 | documentation files (the "Font Software"), to reproduce and distribute the 12 | Font Software, including without limitation the rights to use, copy, merge, 13 | publish, distribute, and/or sell copies of the Font Software, and to permit 14 | persons to whom the Font Software is furnished to do so, subject to the 15 | following conditions: 16 | 17 | The above copyright and trademark notices and this permission notice shall 18 | be included in all copies of one or more of the Font Software typefaces. 19 | 20 | The Font Software may be modified, altered, or added to, and in particular 21 | the designs of glyphs or characters in the Fonts may be modified and 22 | additional glyphs or characters may be added to the Fonts, only if the fonts 23 | are renamed to names not containing either the words "Bitstream" or the word 24 | "Vera". 25 | 26 | This License becomes null and void to the extent applicable to Fonts or Font 27 | Software that has been modified and is distributed under the "Bitstream 28 | Vera" names. 29 | 30 | The Font Software may be sold as part of a larger software package but no 31 | copy of one or more of the Font Software typefaces may be sold by itself. 32 | 33 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 34 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, 35 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, 36 | TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME 37 | FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING 38 | ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, 39 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 40 | THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE 41 | FONT SOFTWARE. 42 | 43 | Except as contained in this notice, the names of Gnome, the Gnome 44 | Foundation, and Bitstream Inc., shall not be used in advertising or 45 | otherwise to promote the sale, use or other dealings in this Font Software 46 | without prior written authorization from the Gnome Foundation or Bitstream 47 | Inc., respectively. For further information, contact: fonts at gnome dot 48 | org. 49 | 50 | Arev Fonts Copyright 51 | ------------------------------ 52 | 53 | Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. 54 | 55 | Permission is hereby granted, free of charge, to any person obtaining 56 | a copy of the fonts accompanying this license ("Fonts") and 57 | associated documentation files (the "Font Software"), to reproduce 58 | and distribute the modifications to the Bitstream Vera Font Software, 59 | including without limitation the rights to use, copy, merge, publish, 60 | distribute, and/or sell copies of the Font Software, and to permit 61 | persons to whom the Font Software is furnished to do so, subject to 62 | the following conditions: 63 | 64 | The above copyright and trademark notices and this permission notice 65 | shall be included in all copies of one or more of the Font Software 66 | typefaces. 67 | 68 | The Font Software may be modified, altered, or added to, and in 69 | particular the designs of glyphs or characters in the Fonts may be 70 | modified and additional glyphs or characters may be added to the 71 | Fonts, only if the fonts are renamed to names not containing either 72 | the words "Tavmjong Bah" or the word "Arev". 73 | 74 | This License becomes null and void to the extent applicable to Fonts 75 | or Font Software that has been modified and is distributed under the 76 | "Tavmjong Bah Arev" names. 77 | 78 | The Font Software may be sold as part of a larger software package but 79 | no copy of one or more of the Font Software typefaces may be sold by 80 | itself. 81 | 82 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 83 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 84 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 85 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL 86 | TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 87 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 88 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 89 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 90 | OTHER DEALINGS IN THE FONT SOFTWARE. 91 | 92 | Except as contained in this notice, the name of Tavmjong Bah shall not 93 | be used in advertising or otherwise to promote the sale, use or other 94 | dealings in this Font Software without prior written authorization 95 | from Tavmjong Bah. For further information, contact: tavmjong @ free 96 | . fr. 97 | 98 | $Id: LICENSE,v 1.1 2006/05/22 22:51:38 kelvinl Exp $ 99 | -------------------------------------------------------------------------------- /_cpuctrl.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2010 Kelvin Lawson (kelvinl@users.sourceforge.net) 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Lesser General Public 6 | License as published by the Free Software Foundation; either 7 | version 2.1 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this library; if not, write to the Free Software 16 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | */ 18 | 19 | /* 20 | This module provides support for controlling the GP2X CPU speed at 21 | runtime, via Python. The core GP2X code in this module is taken 22 | from: 23 | 24 | cpuctrl.c for GP2X (CPU/LCD/RAM-Tuner Version 2.0) 25 | Copyright (C) 2006 god_at_hell 26 | original CPU-Overclocker (c) by Hermes/PS2Reality 27 | the gamma-routine was provided by theoddbot 28 | parts (c) Rlyehs Work 29 | 30 | gp2xminilib.c 31 | GP2X minimal library v0.5 by rlyeh, 2005. 32 | + GP2X video library with double buffering. 33 | + GP2X soundring buffer library with double buffering. 34 | + GP2X joystick library. 35 | */ 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #define SYS_CLK_FREQ 7372800 43 | 44 | static int gp2x_dev_mem_fd = -1; 45 | static volatile unsigned short *gp2x_memregs = 0; 46 | 47 | /* If this is not called explicitly, it will be called implicitly. */ 48 | static void 49 | init() { 50 | if (gp2x_dev_mem_fd >= 0) { 51 | /* Already initialised. */ 52 | return; 53 | } 54 | 55 | /* Get a pointer to the memory-mapped address for the GP2X control 56 | registers. */ 57 | 58 | gp2x_dev_mem_fd = open("/dev/mem", O_RDWR); 59 | gp2x_memregs = (unsigned short *)mmap(0, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, gp2x_dev_mem_fd, 0xc0000000); 60 | } 61 | 62 | /* Call this to free up resources. */ 63 | static void 64 | shutdown() { 65 | if (gp2x_dev_mem_fd < 0) { 66 | /* Already shut down. */ 67 | return; 68 | } 69 | 70 | munmap((void *)gp2x_memregs, 0x10000); 71 | gp2x_memregs = NULL; 72 | 73 | close(gp2x_dev_mem_fd); 74 | gp2x_dev_mem_fd = -1; 75 | } 76 | 77 | /* Sets the GP2X CPU speed to the indicated value in MHz. Legal 78 | values are in the range 33 .. 340, though values greater than 266 79 | are overclock values and may crash the machine, depending on the 80 | individual. 81 | 82 | Note that there the GP2X also has a separate CPU divider which is 83 | not modified by this function. */ 84 | static void 85 | set_FCLK(unsigned int MHZ) { 86 | unsigned int v; 87 | unsigned int mdiv, pdiv = 3, scale = 0; 88 | 89 | if (gp2x_dev_mem_fd < 0) { 90 | /* Not initialised. */ 91 | printf("Not setting CPU speed to %u: not initialised.\n", MHZ); 92 | return; 93 | } 94 | 95 | MHZ *= 1000000; 96 | mdiv = (MHZ * pdiv) / SYS_CLK_FREQ; 97 | mdiv = ((mdiv - 8) << 8) & 0xff00; 98 | pdiv = ((pdiv - 2) << 2) & 0xfc; 99 | scale &= 3; 100 | v = mdiv | pdiv | scale; 101 | 102 | gp2x_memregs[0x910 >> 1] = v; 103 | } 104 | 105 | /* Returns the current CPU speed in MHz. Note that the GP2X also has 106 | a separate CPU divider which is not consulted by this function. */ 107 | static unsigned int 108 | get_FCLK() { 109 | unsigned v; 110 | unsigned mdiv, pdiv, scale; 111 | unsigned MHZ; 112 | 113 | /* Implicitly initialise if necessary. */ 114 | init(); 115 | 116 | v = gp2x_memregs[0x910>>1]; 117 | mdiv = ((v & 0xff00) >> 8) + 8; 118 | pdiv = ((v & 0xfc) >> 2) + 2; 119 | scale = v & 3; 120 | 121 | if (pdiv == 0) { 122 | /* This presumably isn't possible. */ 123 | MHZ = 0; 124 | } else { 125 | MHZ = (mdiv * SYS_CLK_FREQ) / pdiv; 126 | MHZ = (MHZ + 500000) / 1000000; 127 | } 128 | return MHZ; 129 | } 130 | 131 | 132 | static PyObject * 133 | wrapper_init() { 134 | init(); 135 | 136 | Py_RETURN_NONE; 137 | } 138 | 139 | static PyObject * 140 | wrapper_shutdown() { 141 | shutdown(); 142 | 143 | Py_RETURN_NONE; 144 | } 145 | 146 | static PyObject * 147 | wrapper_set_FCLK(PyObject *self, PyObject *args, PyObject *kwds) { 148 | static char *keyword_list[] = { "MHZ", NULL }; 149 | unsigned int MHZ; 150 | 151 | if (!PyArg_ParseTupleAndKeywords(args, kwds, 152 | "I:set_FCLK", 153 | keyword_list, &MHZ)) { 154 | return NULL; 155 | } 156 | 157 | set_FCLK(MHZ); 158 | 159 | Py_RETURN_NONE; 160 | } 161 | 162 | static PyObject * 163 | wrapper_get_FCLK() { 164 | unsigned int MHZ; 165 | 166 | MHZ = get_FCLK(); 167 | 168 | if ((long)MHZ < 0) { 169 | return PyLong_FromUnsignedLong((unsigned long)MHZ); 170 | } else { 171 | return PyInt_FromLong((long)MHZ); 172 | } 173 | } 174 | 175 | /* Returns a 3-tuple (x, y, tvout) representing the current screen 176 | width and height, and true if the screen is in tv-out mode. */ 177 | static PyObject * 178 | get_screen_info() { 179 | int x, y, tvout; 180 | 181 | /* Implicitly initialise if necessary. */ 182 | init(); 183 | 184 | x = gp2x_memregs[0x2816>>1] + 1; 185 | y = gp2x_memregs[0x2818>>1] + 1; 186 | tvout = (gp2x_memregs[0x2800>>1] & 0x100) != 0; 187 | 188 | if (tvout && y < 400) { 189 | /* Not sure why, but this is apparently off by a factor of two in 190 | TV mode. */ 191 | y *= 2; 192 | } 193 | 194 | return Py_BuildValue("(iii)", x, y, tvout); 195 | } 196 | 197 | static PyMethodDef cpuctrl_methods[] = { 198 | {"init", (PyCFunction)wrapper_init, METH_NOARGS }, 199 | {"shutdown", (PyCFunction)wrapper_shutdown, METH_NOARGS }, 200 | {"set_FCLK", (PyCFunction)wrapper_set_FCLK, METH_VARARGS | METH_KEYWORDS }, 201 | {"get_FCLK", (PyCFunction)wrapper_get_FCLK, METH_NOARGS }, 202 | {"get_screen_info", (PyCFunction)get_screen_info, METH_NOARGS }, 203 | {NULL} /* Sentinel */ 204 | }; 205 | 206 | #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ 207 | #define PyMODINIT_FUNC void 208 | #endif 209 | PyMODINIT_FUNC 210 | init_cpuctrl(void) { 211 | PyObject *m; 212 | 213 | m = Py_InitModule3("_cpuctrl", cpuctrl_methods, NULL); 214 | } 215 | -------------------------------------------------------------------------------- /install/mplayer-gp2x-cmdline-pykaraoke.diff: -------------------------------------------------------------------------------- 1 | diff -ru --exclude=.svn mplayer/config.mak mplayer_cmdline/config.mak 2 | --- mplayer/config.mak 2008-02-17 13:59:30.000000000 -0800 3 | +++ mplayer_cmdline/config.mak 2008-02-17 09:14:14.000000000 -0800 4 | @@ -3,12 +3,13 @@ 5 | # set by developer, godori(ghcstop@gmail.com) 6 | # start ================================================================= 7 | 8 | -AR = arm-linux-ar 9 | -CC = arm-linux-gcc 10 | -HOST_CC = arm-linux-gcc 11 | +AR = arm-gp2x-linux-ar 12 | +CC = arm-gp2x-linux-gcc 13 | +STRIP = arm-gp2x-linux-strip 14 | +BUILD_CC = gcc 15 | 16 | # When I developed, use gcc-2.95.3 ==> release test: godori use gcc-3.3.4 17 | -TCDIR = /home/depss/DEPSS/library/image 18 | +TCDIR = /gp2xsdk/Tools/arm-gp2x-linux 19 | # end ================================================================== 20 | 21 | 22 | @@ -29,7 +30,7 @@ 23 | AWK = 24 | RANLIB = true 25 | INSTALL = install 26 | -EXTRA_INC = -I$(TCDIR)/include 27 | +EXTRA_INC = -I$(TCDIR)/include -I$(TCDIR)/include/SDL 28 | OPTFLAGS = -I../libvo -I../../libvo -O4 -pipe -ffast-math -fomit-frame-pointer -D_REENTRANT $(EXTRA_INC) 29 | STRIPBINARIES = yes 30 | HELP_FILE = help/help_mp-en.h 31 | diff -ru --exclude=.svn mplayer/filelistview.c mplayer_cmdline/filelistview.c 32 | --- mplayer/filelistview.c 2008-02-17 13:59:30.000000000 -0800 33 | +++ mplayer_cmdline/filelistview.c 2008-02-14 22:01:21.000000000 -0800 34 | @@ -12,21 +12,12 @@ 35 | 36 | #define IMAGEPATH "./imgmovie/" 37 | #define FILELIST_COUNT 8 38 | -#define CLOCKADJUST_COUNT 3 39 | #define IMG_BODY_MAIN IMAGEPATH"body.png" 40 | #define IMG_FULL_ICON IMAGEPATH"full.png" 41 | #define IMG_NORM_ICON IMAGEPATH"normal.png" 42 | #define IMG_SAVE_ICON IMAGEPATH"save.png" 43 | #define SHOWFILETYPE FOLDER_AVI_MODE 44 | 45 | -#define FCLK_200 10 46 | -#define FCLK_166 11 47 | -#define FCLK_133 12 48 | -#define FCLK_100 13 49 | -#define FCLK_78 14 50 | -#define FCLK_64 15 51 | -#define FCLK_DEFAULT 16 52 | - 53 | SViewInfomation infoView; 54 | SDirInfomation infoDir; 55 | static SDL_Surface *pImgBackGround = NULL; 56 | diff -ru --exclude=.svn mplayer/filelistview.h mplayer_cmdline/filelistview.h 57 | --- mplayer/filelistview.h 2008-02-17 13:59:30.000000000 -0800 58 | +++ mplayer_cmdline/filelistview.h 2008-02-14 22:01:17.000000000 -0800 59 | @@ -24,4 +24,5 @@ 60 | void FileView_OnMoveHighFolder(SDL_Surface *pScreen); 61 | void FileView_OnMoveSub_Execute(SDL_Surface *pScreen, SDL_Event *pOpenEvent); 62 | 63 | -#endif 64 | \ No newline at end of file 65 | +#endif 66 | + 67 | diff -ru --exclude=.svn mplayer/gv.c mplayer_cmdline/gv.c 68 | --- mplayer/gv.c 2008-02-17 13:59:30.000000000 -0800 69 | +++ mplayer_cmdline/gv.c 2008-02-17 13:43:00.000000000 -0800 70 | @@ -22,6 +22,7 @@ 71 | #include "SDL_inifile.h" 72 | 73 | #define IMG_RESUME "./imgcommon/resume.png" 74 | +#define PATH_MAX 4096 75 | 76 | SDL_Surface *g_pScreen = NULL; 77 | SDL_Joystick *m_pJoy = NULL; 78 | @@ -37,6 +38,7 @@ 79 | float CurrentPlayTime = 0.0f; 80 | float g_fResumePlayTime = 0.0f; 81 | bool g_bResumePlay = false; 82 | +bool g_bUseMenu = true; 83 | 84 | int g_Kbps = 0; 85 | int g_Khz = 0; 86 | @@ -98,6 +100,8 @@ 87 | int SetMode(int argc, char *argv[]); 88 | void OnExitClick(); 89 | 90 | +extern int AdjustClock[CLOCKADJUST_COUNT]; 91 | + 92 | extern void volume_change(bool up_flag); 93 | 94 | void InitApp() 95 | @@ -134,6 +138,11 @@ 96 | 97 | int main(int argc, char *argv[]) 98 | { 99 | + SDL_Event open_event; 100 | + char path[PATH_MAX]; 101 | + char *filename = NULL; 102 | + unsigned int i; 103 | + 104 | if(SetMode(argc, argv) != 0) 105 | { 106 | printf("Display Mode Set failed!!\n"); 107 | @@ -143,16 +152,72 @@ 108 | init_SDL_engine(); 109 | InitApp(); 110 | init_etc_setting(); 111 | - osd_plane_open(); 112 | - 113 | - 114 | - 115 | + osd_plane_open(); 116 | 117 | + if(argc < 2) { 118 | + g_bUseMenu = true; 119 | + viewmode = FILE_VIEW; 120 | + FileView_Initialize(); 121 | + FileView_OnDraw(g_pScreen); 122 | + } else if (argc >= 2) { 123 | + getcwd(path, PATH_MAX); 124 | + printf("cwd: %s\n", path ); 125 | + printf("argv[1]: %s\n", argv[1]); 126 | + for( i = strlen(argv[1])-1; i >= 0; i -=1 ) { 127 | + // printf("i: %d | c: %c === \n", i, argv[1][i]); 128 | + if (argv[1][i] == '/') break; 129 | + if (!i) break; 130 | + } 131 | + if (i) { 132 | + if (argv[1][0] == '/') { 133 | + // It's a full path. 134 | + strncpy(path, argv[1], i); 135 | + path[i] = '\0'; 136 | + } else { 137 | + // It's a relative path. 138 | + strncat(path, "/", 1); 139 | + strncat(path, argv[1], i); 140 | + } 141 | + } 142 | + filename = argv[1]; 143 | + printf("new path: %s\nnew filename: %s\n", path, filename); 144 | + fflush(stderr); 145 | + 146 | + DeleteDirInfo(&infoDir); 147 | + GetDirInfo(&infoDir, path, FOLDER_AVI_MODE); 148 | + infoView.nStartCount = 0; 149 | + infoView.nPosition = 0; 150 | + infoView.nStatus = 1; 151 | + if(infoDir.nCount > 8) infoView.nEndCount = 8; 152 | + else infoView.nEndCount = infoDir.nCount; 153 | + 154 | + while(1) { 155 | + if(strcmp(infoDir.pList[infoView.nPosition].szName, filename) == 0) break; 156 | + 157 | + if(infoView.nPosition >= infoDir.nCount -1) break; 158 | + infoView.nPosition++; 159 | + if(infoView.nPosition >= infoView.nEndCount) { 160 | + infoView.nStartCount++; 161 | + infoView.nEndCount++; 162 | + } 163 | + } 164 | + 165 | + g_bUseMenu = false; 166 | + g_Command = EXIT_CMD; 167 | + 168 | + ifile = strdup(filename); 169 | + 170 | + viewmode = MOVIE_VIEW; 171 | + bFileSelect = 1; 172 | + 173 | + if(cpuclock < 0)cpuclock = CLOCKADJUST_COUNT - 1; 174 | + if(cpuclock >= CLOCKADJUST_COUNT)cpuclock = 0; 175 | + SDL_SYS_JoystickGp2xSys(m_pJoy, AdjustClock[cpuclock]); // FCLK_200 176 | + 177 | + open_event.type = EVENT_MOVIE_PLAY; 178 | + SDL_PushEvent(&open_event); 179 | + } 180 | 181 | - viewmode = FILE_VIEW; 182 | - FileView_Initialize(); 183 | - FileView_OnDraw(g_pScreen); 184 | - 185 | event_loop(); 186 | 187 | INI_Open("/usr/gp2x/common.ini"); 188 | @@ -162,8 +227,8 @@ 189 | 190 | osd_plane_close(); 191 | close_etc_setting(); 192 | - 193 | - ExitApp(); 194 | + 195 | + ExitApp(); 196 | 197 | return 1; 198 | } 199 | @@ -346,6 +411,7 @@ 200 | 201 | void OnToolBar_Open() 202 | { 203 | + g_bUseMenu = true; 204 | if(vstate == STOP_STATUS) 205 | { 206 | switch_fb1_to_fb0(); 207 | @@ -950,8 +1016,10 @@ 208 | 209 | g_fResumePlayTime = 0.0f; 210 | 211 | - if(GetResumeInfomation(&g_fResumePlayTime, &resumepathname, &resumefilename) == true) { 212 | + if(GetResumeInfomation(&g_fResumePlayTime, &resumepathname, &resumefilename) == true 213 | + && g_bUseMenu == true) { 214 | if(g_fResumePlayTime != 0.0f && resumepathname != NULL && resumefilename != NULL) { 215 | + g_bResumePlay = false; 216 | if(ifile != NULL) free(ifile); 217 | ifile = (char*)malloc(strlen(resumepathname) + strlen(resumefilename) + 3); 218 | sprintf(ifile, "%s/%s", resumepathname, resumefilename); 219 | @@ -1025,6 +1093,7 @@ 220 | volume_change(false); 221 | break; 222 | case VK_START : 223 | + case VK_SELECT : 224 | if(viewmode != FILE_VIEW) 225 | SaveResumeInfo(); 226 | 227 | diff -ru --exclude=.svn mplayer/Makefile mplayer_cmdline/Makefile 228 | --- mplayer/Makefile 2008-02-17 13:59:30.000000000 -0800 229 | +++ mplayer_cmdline/Makefile 2008-02-16 20:35:09.000000000 -0800 230 | @@ -37,7 +37,7 @@ 231 | default : $(PRG_CFG) $(TGT_NAME) 232 | 233 | $(PRG_CFG): version.h codec-cfg.c codec-cfg.h 234 | - gcc -g codec-cfg.c mp_msg.c -o $(PRG_CFG) -DCODECS2HTML $(I18NLIBS) 235 | + $(BUILD_CC) -g codec-cfg.c mp_msg.c -o $(PRG_CFG) -DCODECS2HTML $(I18NLIBS) 236 | ./$(PRG_CFG) ./etc/codecs.conf > codecs.conf.h 237 | 238 | 239 | @@ -95,7 +95,7 @@ 240 | @$(RM) *.o 241 | 242 | install : 243 | - arm-linux-strip $(TGT_NAME) 244 | + $(STRIP) $(TGT_NAME) 245 | 246 | all : 247 | @make clean 248 | diff -ru --exclude=.svn mplayer/mplayer.c mplayer_cmdline/mplayer.c 249 | --- mplayer/mplayer.c 2008-02-17 14:08:54.000000000 -0800 250 | +++ mplayer_cmdline/mplayer.c 2008-02-17 14:08:54.000000000 -0800 251 | @@ -946,7 +946,7 @@ 252 | { 253 | mixer.audio_out = audio_out; 254 | mixer_setvolume(&mixer, leftVol, leftVol); 255 | - hwEq_change(0); 256 | + mixer_hwEq(&mixer, 0); 257 | 258 | inited_flags |= INITED_AO; 259 | #if 0 260 | @@ -1813,7 +1813,3 @@ 261 | if(bMenuStatus) OnDraw_Volume(vol); 262 | } 263 | 264 | -void hwEq_change(int eq_flag) 265 | -{ 266 | - mixer_hwEq(&mixer, eq_flag); 267 | -} 268 | \ No newline at end of file 269 | diff -ru --exclude=.svn mplayer/typed.h mplayer_cmdline/typed.h 270 | --- mplayer/typed.h 2008-02-17 14:08:54.000000000 -0800 271 | +++ mplayer_cmdline/typed.h 2008-02-17 14:08:54.000000000 -0800 272 | @@ -134,5 +134,14 @@ 273 | #define false 0 274 | typedef int bool; 275 | //[*]----------------------------------------------------------------------------------------------------[*] 276 | +#define CLOCKADJUST_COUNT 3 277 | +#define FCLK_200 10 278 | +#define FCLK_166 11 279 | +#define FCLK_133 12 280 | +#define FCLK_100 13 281 | +#define FCLK_78 14 282 | +#define FCLK_64 15 283 | +#define FCLK_DEFAULT 16 284 | +//[*]----------------------------------------------------------------------------------------------------[*] 285 | #endif 286 | //[*]----------------------------------------------------------------------------------------------------[*] 287 | -------------------------------------------------------------------------------- /icons/pykaraoke.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static char * pykaraoke_xpm[] = { 3 | "32 32 403 2", 4 | " c None", 5 | ". c #ACAEA7", 6 | "+ c #A2A5A0", 7 | "@ c #B2B5B4", 8 | "# c #C2C6C4", 9 | "$ c #CCCCC7", 10 | "% c #E4E5E3", 11 | "& c #87867E", 12 | "* c #54524C", 13 | "= c #625D59", 14 | "- c #54504A", 15 | "; c #4A4A44", 16 | "> c #84807C", 17 | ", c #ADAFB0", 18 | "' c #A0A19E", 19 | ") c #C2C1BD", 20 | "! c #CFCCC5", 21 | "~ c #F2F2F0", 22 | "{ c #74746A", 23 | "] c #53514B", 24 | "^ c #3E3B36", 25 | "/ c #38342F", 26 | "( c #5C5651", 27 | "_ c #403A31", 28 | ": c #64625A", 29 | "< c #67635A", 30 | "[ c #E2E1E6", 31 | "} c #F4F7FC", 32 | "| c #F2F4F8", 33 | "1 c #EFEDF1", 34 | "2 c #F3F3F4", 35 | "3 c #B8B8B4", 36 | "4 c #6C6864", 37 | "5 c #6C6760", 38 | "6 c #58544A", 39 | "7 c #403C31", 40 | "8 c #5E5748", 41 | "9 c #68624E", 42 | "0 c #443F39", 43 | "a c #AAA49C", 44 | "b c #FEFEFE", 45 | "c c #FFFFFF", 46 | "d c #FEFEFF", 47 | "e c #FAF8FC", 48 | "f c #F8F8FA", 49 | "g c #FEFCFD", 50 | "h c #B0ADAC", 51 | "i c #64625C", 52 | "j c #525149", 53 | "k c #463F35", 54 | "l c #373025", 55 | "m c #6A6340", 56 | "n c #58523A", 57 | "o c #6F6A5A", 58 | "p c #BAB7AF", 59 | "q c #F4F6FA", 60 | "r c #FAFAFE", 61 | "s c #FBFAFC", 62 | "t c #48423F", 63 | "u c #3F3A36", 64 | "v c #413A34", 65 | "w c #241E19", 66 | "x c #241F19", 67 | "y c #1A1614", 68 | "z c #211C17", 69 | "A c #403C34", 70 | "B c #7C7B73", 71 | "C c #A8AAA4", 72 | "D c #E5E7E4", 73 | "E c #F2F6FA", 74 | "F c #F6F5FA", 75 | "G c #FCFCFC", 76 | "H c #9D9B99", 77 | "I c #3C3B32", 78 | "J c #3A352F", 79 | "K c #3B342F", 80 | "L c #2F2B1E", 81 | "M c #4C452E", 82 | "N c #3F3C30", 83 | "O c #726C61", 84 | "P c #A8A3A2", 85 | "Q c #A69EA4", 86 | "R c #7A7272", 87 | "S c #4F4642", 88 | "T c #94918F", 89 | "U c #F0F4F8", 90 | "V c #D5D5D3", 91 | "W c #F7F5F9", 92 | "X c #FCFAFD", 93 | "Y c #918F88", 94 | "Z c #615C58", 95 | "` c #4F4842", 96 | " . c #413832", 97 | ".. c #40382C", 98 | "+. c #423A2E", 99 | "@. c #2F2924", 100 | "#. c #362F2D", 101 | "$. c #2F2B24", 102 | "%. c #595651", 103 | "&. c #767670", 104 | "*. c #B2B1AD", 105 | "=. c #F1F0F0", 106 | "-. c #E6E9EE", 107 | ";. c #6D615D", 108 | ">. c #7E786A", 109 | ",. c #F3F2F4", 110 | "'. c #35302A", 111 | "). c #1D1A14", 112 | "!. c #3A342F", 113 | "~. c #2A2621", 114 | "{. c #1C1A10", 115 | "]. c #262217", 116 | "^. c #302C23", 117 | "/. c #625C4F", 118 | "(. c #ABABA6", 119 | "_. c #C9C5CA", 120 | ":. c #A49FA6", 121 | "<. c #7E7878", 122 | "[. c #CFCECC", 123 | "}. c #D4D4D8", 124 | "|. c #9B9690", 125 | "1. c #CBC7C3", 126 | "2. c #77746E", 127 | "3. c #5F5B55", 128 | "4. c #544C44", 129 | "5. c #3D362E", 130 | "6. c #504936", 131 | "7. c #474134", 132 | "8. c #484138", 133 | "9. c #5F5853", 134 | "0. c #544F4C", 135 | "a. c #413B38", 136 | "b. c #53504A", 137 | "c. c #7D7C75", 138 | "d. c #F1F0F1", 139 | "e. c #E0DDE2", 140 | "f. c #9C8E8C", 141 | "g. c #978D83", 142 | "h. c #635F5D", 143 | "i. c #45413D", 144 | "j. c #413B33", 145 | "k. c #322E27", 146 | "l. c #16120D", 147 | "m. c #16130E", 148 | "n. c #252118", 149 | "o. c #48453C", 150 | "p. c #8E8E89", 151 | "q. c #B2B2B1", 152 | "r. c #C3C3C6", 153 | "s. c #B0AAAD", 154 | "t. c #E6E7E9", 155 | "u. c #A39F9E", 156 | "v. c #6C655A", 157 | "w. c #BFBDB8", 158 | "x. c #4A4841", 159 | "y. c #403C36", 160 | "z. c #443F36", 161 | "A. c #342E22", 162 | "B. c #4A452E", 163 | "C. c #3E3A2D", 164 | "D. c #5B5547", 165 | "E. c #8A847F", 166 | "F. c #9F959B", 167 | "G. c #6F6465", 168 | "H. c #514844", 169 | "I. c #706C68", 170 | "J. c #E2E2E7", 171 | "K. c #BEBDBE", 172 | "L. c #BAB2B2", 173 | "M. c #CCC9C4", 174 | "N. c #9C9A96", 175 | "O. c #65605E", 176 | "P. c #504D48", 177 | "Q. c #483F37", 178 | "R. c #312B24", 179 | "S. c #362E27", 180 | "T. c #26201C", 181 | "U. c #221D1A", 182 | "V. c #2D2923", 183 | "W. c #615E58", 184 | "X. c #7A7A77", 185 | "Y. c #A6A7A7", 186 | "Z. c #E7E5E4", 187 | "`. c #D7D9DF", 188 | " + c #918686", 189 | ".+ c #5E5044", 190 | "++ c #C5C2BB", 191 | "@+ c #C0BFBB", 192 | "#+ c #29251F", 193 | "$+ c #2B2722", 194 | "%+ c #29231E", 195 | "&+ c #201C14", 196 | "*+ c #2E291B", 197 | "=+ c #322E23", 198 | "-+ c #565248", 199 | ";+ c #979594", 200 | ">+ c #C2BEC0", 201 | ",+ c #959094", 202 | "'+ c #766E6E", 203 | ")+ c #AEABAA", 204 | "!+ c #CFCDD2", 205 | "~+ c #868079", 206 | "{+ c #A7A39E", 207 | "]+ c #D6D4D4", 208 | "^+ c #8C8986", 209 | "/+ c #504E48", 210 | "(+ c #4A443F", 211 | "_+ c #3F3730", 212 | ":+ c #453E2E", 213 | "<+ c #443E31", 214 | "[+ c #37322D", 215 | "}+ c #46403C", 216 | "|+ c #433C3D", 217 | "1+ c #37312E", 218 | "2+ c #4D4B48", 219 | "3+ c #807C76", 220 | "4+ c #E6E5E4", 221 | "5+ c #D2CED2", 222 | "6+ c #B3A7A5", 223 | "7+ c #998D86", 224 | "8+ c #85837F", 225 | "9+ c #3A3430", 226 | "0+ c #403A34", 227 | "a+ c #24201D", 228 | "b+ c #16120E", 229 | "c+ c #191710", 230 | "d+ c #23201A", 231 | "e+ c #444036", 232 | "f+ c #8D8E8B", 233 | "g+ c #B1B1B0", 234 | "h+ c #AFAEB4", 235 | "i+ c #ACA5A8", 236 | "j+ c #E7E5E6", 237 | "k+ c #AFA7A9", 238 | "l+ c #4D463B", 239 | "m+ c #958F87", 240 | "n+ c #5E5B57", 241 | "o+ c #302C27", 242 | "p+ c #504841", 243 | "q+ c #322A22", 244 | "r+ c #484230", 245 | "s+ c #3F3A2D", 246 | "t+ c #4B453E", 247 | "u+ c #6A6461", 248 | "v+ c #7F7A7C", 249 | "w+ c #585152", 250 | "x+ c #403A35", 251 | "y+ c #5A534D", 252 | "z+ c #E7E6E7", 253 | "A+ c #BBB4B4", 254 | "B+ c #B7B0AC", 255 | "C+ c #BDB8B4", 256 | "D+ c #A2A09A", 257 | "E+ c #746F6C", 258 | "F+ c #48433E", 259 | "G+ c #4A423C", 260 | "H+ c #2A241E", 261 | "I+ c #332C24", 262 | "J+ c #1F1A16", 263 | "K+ c #1D1A15", 264 | "L+ c #302E29", 265 | "M+ c #6B6965", 266 | "N+ c #7D8080", 267 | "O+ c #A2A2A3", 268 | "P+ c #DBD8D8", 269 | "Q+ c #E5E5E8", 270 | "R+ c #B0A6A6", 271 | "S+ c #796D66", 272 | "T+ c #888074", 273 | "U+ c #686662", 274 | "V+ c #5D5A56", 275 | "W+ c #47443F", 276 | "X+ c #463A32", 277 | "Y+ c #483A2F", 278 | "Z+ c #5D563C", 279 | "`+ c #4C4638", 280 | " @ c #655E54", 281 | ".@ c #999596", 282 | "+@ c #ADA6AB", 283 | "@@ c #7C757B", 284 | "#@ c #6A6160", 285 | "$@ c #94908E", 286 | "%@ c #D9D9DD", 287 | "&@ c #7E766F", 288 | "*@ c #867F78", 289 | "=@ c #C1C0BE", 290 | "-@ c #5C5857", 291 | ";@ c #3C3834", 292 | ">@ c #26221F", 293 | ",@ c #2C2823", 294 | "'@ c #3C342E", 295 | ")@ c #242019", 296 | "!@ c #39342D", 297 | "~@ c #423D35", 298 | "{@ c #BEBFC0", 299 | "]@ c #F0F2F5", 300 | "^@ c #B5BCC5", 301 | "/@ c #F7F8FA", 302 | "(@ c #C9C7CC", 303 | "_@ c #BBB0B2", 304 | ":@ c #9A908E", 305 | "<@ c #D5D2D1", 306 | "[@ c #484441", 307 | "}@ c #322E2A", 308 | "|@ c #36312C", 309 | "1@ c #3C3530", 310 | "2@ c #221D18", 311 | "3@ c #393432", 312 | "4@ c #2C2722", 313 | "5@ c #B2B5BB", 314 | "6@ c #AEB4BA", 315 | "7@ c #AEB1B8", 316 | "8@ c #FDFCFD", 317 | "9@ c #F9F6F8", 318 | "0@ c #BEBBC0", 319 | "a@ c #CEC6C8", 320 | "b@ c #B8B4B7", 321 | "c@ c #CAC7CA", 322 | "d@ c #736E6C", 323 | "e@ c #47433E", 324 | "f@ c #3B3632", 325 | "g@ c #302A27", 326 | "h@ c #635C58", 327 | "i@ c #3B3535", 328 | "j@ c #6C6A68", 329 | "k@ c #8E8E92", 330 | "l@ c #A6A2A8", 331 | "m@ c #B8B3B7", 332 | "n@ c #BBB6BA", 333 | "o@ c #B1AFB4", 334 | "p@ c #B2ADB2", 335 | "q@ c #B4B0B2", 336 | "r@ c #8A8583", 337 | "s@ c #7A726E", 338 | "t@ c #615857", 339 | "u@ c #766F6C", 340 | "v@ c #655F5F", 341 | "w@ c #665F5A", 342 | "x@ c #847E7A", 343 | "y@ c #8A8381", 344 | "z@ c #9A9698", 345 | "A@ c #A29FA2", 346 | "B@ c #B6B4B6", 347 | "C@ c #D0CED0", 348 | "D@ c #847F7C", 349 | "E@ c #504844", 350 | "F@ c #6C6660", 351 | "G@ c #5D524D", 352 | "H@ c #6E6562", 353 | "I@ c #958E8E", 354 | "J@ c #A9A4A7", 355 | "K@ c #B6B4B7", 356 | "L@ c #CFCDCF", 357 | "M@ c #787370", 358 | "N@ c #4E4742", 359 | "O@ c #514B48", 360 | "P@ c #534C46", 361 | "Q@ c #867E7A", 362 | "R@ c #BEBEC2", 363 | "S@ c #BBBCC0", 364 | "T@ c #B6B6BB", 365 | "U@ c #767270", 366 | "V@ c #403B37", 367 | "W@ c #443F3B", 368 | "X@ c #564F4A", 369 | "Y@ c #938D8C", 370 | "Z@ c #959698", 371 | "`@ c #AEADB0", 372 | " # c #B2B1B5", 373 | ".# c #615650", 374 | "+# c #3A322D", 375 | "@# c #3B342D", 376 | "## c #59544E", 377 | "$# c #5C5852", 378 | "%# c #C4BEBA", 379 | "&# c #8A8786", 380 | "*# c #ADAAA9", 381 | "=# c #887F7C", 382 | "-# c #4D433B", 383 | ";# c #4A4138", 384 | "># c #49352C", 385 | ",# c #48403A", 386 | "'# c #A19E9E", 387 | ")# c #908E92", 388 | "!# c #BAB5B6", 389 | "~# c #746C68", 390 | "{# c #4B3C34", 391 | "]# c #605A5A", 392 | "^# c #4C453F", 393 | "/# c #776F6A", 394 | "(# c #A8A6A6", 395 | "_# c #C9C7C5", 396 | ":# c #635951", 397 | "<# c #4F4039", 398 | "[# c #9C989A", 399 | "}# c #868487", 400 | "|# c #A5A2A1", 401 | "1# c #646160", 402 | "2# c #1E1915", 403 | "3# c #29211C", 404 | "4# c #85878B", 405 | "5# c #9FA0A4", 406 | "6# c #A7A6A8", 407 | " ", 408 | " . + @ # $ % ", 409 | " & * = - ; > , ' ) ! ~ ", 410 | " { ] ^ / ( _ : < [ } | 1 2 ", 411 | " 3 4 5 6 7 8 9 0 a b c d e f g ", 412 | " h i j k l m n o p c c c d q r s ", 413 | " t u v w x y z A B C D b E r F G ", 414 | " H I J K L M N O P Q R S T U V W X ", 415 | " Y Z ` ...+.@.#.$.%.&.*.=.-.;.>.,. ", 416 | " '.).!.~.{.].^./.(._.:.<.[.}.|.1. ", 417 | " 2.3.4.5.6.7.8.9.0.a.b.c.d.e.f.g. ", 418 | " h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w. ", 419 | " x.y.z.A.B.C.D.E.F.G.H.I.J.K.L.M. ", 420 | " N.O.P.Q.R.S.T.U.V.W.X.Y.Z.`. +.+++ ", 421 | " @+#+$+%+&+*+=+-+;+>+,+'+)+!+~+{+]+ ", 422 | " ^+/+(+_+:+<+[+}+|+1+2+3+4+5+6+7+ ", 423 | " 8+9+0+a+b+c+d+e+f+g+h+i+j+k+l+m+ ", 424 | " n+o+p+q+r+s+t+u+v+w+x+y+z+A+B+C+ ", 425 | " D+E+F+G+H+I+J+K+L+M+N+O+P+Q+R+S+T+ ", 426 | " U+V+W+X+Y+Z+`+ @.@+@@@#@$@%@&@*@=@ ", 427 | " -@;@>@,@'@)@!@~@{@]@^@/@c (@_@:@<@ ", 428 | " [@}@|@1@2@3@4@5@6@7@8@9@0@a@b@c@ ", 429 | " d@e@f@g@h@i@j@k@l@m@n@o@p@q@ ", 430 | " r@s@t@u@v@w@x@y@z@A@B@C@ ", 431 | " D@E@F@G@H@I@J@K@L@ ", 432 | " M@N@O@P@Q@R@S@T@ ", 433 | " U@V@W@X@Y@Z@`@ # ", 434 | " .#+#@###$#%#&#*# ", 435 | " =#-#;#>#,#'#)#!# ", 436 | " ~#{#]#^#/#(# ", 437 | " _#:#<#[#}#|# ", 438 | " 1#2#3#4#5#6# "}; 439 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #****************************************************************************** 3 | #**** **** 4 | #**** Copyright (C) 2010 Kelvin Lawson (kelvinl@users.sourceforge.net) **** 5 | #**** Copyright (C) 2010 PyKaraoke Development Team **** 6 | #**** **** 7 | #**** This library is free software; you can redistribute it and/or **** 8 | #**** modify it under the terms of the GNU Lesser General Public **** 9 | #**** License as published by the Free Software Foundation; either **** 10 | #**** version 2.1 of the License, or (at your option) any later version. **** 11 | #**** **** 12 | #**** This library is distributed in the hope that it will be useful, **** 13 | #**** but WITHOUT ANY WARRANTY; without even the implied warranty of **** 14 | #**** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU **** 15 | #**** Lesser General Public License for more details. **** 16 | #**** **** 17 | #**** You should have received a copy of the GNU Lesser General Public **** 18 | #**** License along with this library; if not, write to the **** 19 | #**** Free Software Foundation, Inc. **** 20 | #**** 59 Temple Place, Suite 330 **** 21 | #**** Boston, MA 02111-1307 USA **** 22 | #****************************************************************************** 23 | 24 | from distutils.core import setup, Extension 25 | from distutils.command.build_ext import build_ext 26 | import pykversion 27 | import sys 28 | import glob 29 | from pykenv import * 30 | 31 | import wxversion 32 | wxversion.ensureMinimal('2.6') 33 | import wx 34 | 35 | # patch distutils if it can't cope with the "classifiers" or 36 | # "download_url" keywords 37 | if sys.version < '2.2.3': 38 | from distutils.dist import DistributionMetadata 39 | DistributionMetadata.classifiers = None 40 | DistributionMetadata.download_url = None 41 | 42 | gotPy2exe = 0 43 | if env == ENV_WINDOWS: 44 | # On Windows, we use py2exe to build a binary executable. But we 45 | # don't require it, since even without it it's still possible to 46 | # use distutils to install pykaraoke as a standard Python module. 47 | try: 48 | import py2exe 49 | gotPy2exe = True 50 | except ImportError: 51 | pass 52 | 53 | # These are the data files that should be installed for all systems, 54 | # including Windows. 55 | 56 | data_files = [ 57 | ('share/pykaraoke/icons', 58 | ['icons/audio_16.png', 59 | 'icons/folder_close_16.png', 60 | 'icons/folder_open_16.png', 61 | 'icons/microphone.ico', 62 | 'icons/microphone.png', 63 | 'icons/pykaraoke.xpm', 64 | 'icons/splash.png']), 65 | ('share/pykaraoke/fonts', [ 66 | 'fonts/DejaVuSans.ttf', 67 | 'fonts/DejaVuSansCondensed.ttf', 68 | 'fonts/DejaVuSansCondensed-Bold.ttf', 69 | ])] 70 | 71 | # These data files only make sense on Unix-like systems. 72 | if env != ENV_WINDOWS: 73 | data_files += [ 74 | ('bin', ['install/pykaraoke', 75 | 'install/pykaraoke_mini', 76 | 'install/pycdg', 77 | 'install/pykar', 78 | 'install/pympg', 79 | 'install/cdg2mpg']), 80 | ('share/applications', ['install/pykaraoke.desktop', 81 | 'install/pykaraoke_mini.desktop'])] 82 | 83 | # These are the basic keyword arguments we will pass to distutil's 84 | # setup() function. 85 | cmdclass = {} 86 | setupArgs = { 87 | 'name' : "pykaraoke", 88 | 'version' : pykversion.PYKARAOKE_VERSION_STRING, 89 | 'description' : 'PyKaraoke = CD+G/MPEG/KAR Karaoke Player', 90 | 'maintainer' : 'Kelvin Lawson', 91 | 'maintainer_email' : 'kelvin@kibosh.org', 92 | 'url' : 'http://www.kibosh.org/pykaraoke', 93 | 'license' : 'LGPL', 94 | 'long_description' : 'PyKaraoke - CD+G/MPEG/KAR Karaoke Player', 95 | 'py_modules' : [ "pycdgAux", "pycdg", "pykaraoke_mini", 96 | "pykaraoke", "pykar", "pykconstants", 97 | "pykdb", "pykenv", "pykmanager", 98 | "pykplayer", "pykversion", "pympg", "performer_prompt" ], 99 | 'ext_modules' : [Extension("_pycdgAux", ["_pycdgAux.c"], 100 | libraries = ['SDL'])], 101 | 'data_files' : data_files, 102 | 'cmdclass' : cmdclass, 103 | 'classifiers' : ['Development Status :: 5 - Production/Stable', 104 | 'Environment :: X11 Applications', 105 | 'Environment :: Win32 (MS Windows)', 106 | 'Intended Audience :: End Users/Desktop', 107 | 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 108 | 'Operating System :: Microsoft :: Windows', 109 | 'Operating System :: POSIX', 110 | 'Programming Language :: Python', 111 | 'Topic :: Games/Entertainment', 112 | 'Topic :: Multimedia :: Sound/Audio :: Players'], 113 | } 114 | 115 | # Let's extend build_ext so we can allow the user to specify 116 | # explicitly the location of the SDL installation (or we can try to 117 | # guess where it might be.) 118 | class my_build_ext(build_ext): 119 | user_options = build_ext.user_options 120 | user_options += [('sdl-location=', None, 121 | "Specify the path to the SDL source directory, assuming sdl_location/include and sdl_location/lib exist beneath that. (Otherwise, use --include-dirs and --library-dirs.)"), 122 | ] 123 | 124 | def initialize_options(self): 125 | build_ext.initialize_options(self) 126 | self.sdl_location = None 127 | 128 | def finalize_options(self): 129 | build_ext.finalize_options(self) 130 | if self.sdl_location is None: 131 | 132 | # The default SDL location. This is useful only if your 133 | # SDL source is installed under a common root, with 134 | # sdl_loc/include and sdl_loc/lib directories beneath that 135 | # root. This is the standard way that SDL is distributed 136 | # on Windows, but not on Unix. For a different 137 | # configuration, just specify --include-dirs and 138 | # --library-dirs separately. 139 | 140 | if env == ENV_WINDOWS: 141 | # For a default location on Windows, look around for SDL 142 | # in the current directory. 143 | sdl_dirs = glob.glob('SDL*') 144 | 145 | # Sort them in order, so that the highest-numbered version 146 | # will (probably) fall to the end. 147 | sdl_dirs.sort() 148 | 149 | for dir in sdl_dirs: 150 | if os.path.isdir(os.path.join(dir, 'include')): 151 | self.sdl_location = dir 152 | 153 | if self.sdl_location is not None: 154 | # Now append the system paths. 155 | self.include_dirs.append(os.path.join(self.sdl_location, 'include')) 156 | self.library_dirs.append(os.path.join(self.sdl_location, 'lib')) 157 | 158 | # Also put the lib dir on the PATH, so py2exe can find SDL.dll. 159 | if env == ENV_WINDOWS: 160 | libdir = os.path.join(self.sdl_location, 'lib') 161 | os.environ["PATH"] = '%s;%s' % (libdir, os.environ["PATH"]) 162 | 163 | 164 | cmdclass['build_ext'] = my_build_ext 165 | 166 | 167 | # On Windows, we might want to build an installer. This means 168 | # subclassing from py2exe to add new behavior. 169 | if gotPy2exe: 170 | class BuildInstaller(py2exe.build_exe.py2exe): 171 | 172 | user_options = py2exe.build_exe.py2exe.user_options 173 | user_options += [('makensis=', None, 174 | "path to makensis.exe, the NSIS compiler."), 175 | ] 176 | 177 | def isSystemDLL(self, pathname): 178 | # Trap and flag as non-system DLLs those that py2exe 179 | # would otherwise get incorrect. 180 | if os.path.basename(pathname).lower() in ["sdl_ttf.dll"]: 181 | return 0 182 | elif os.path.basename(pathname).lower() in ["libogg-0.dll"]: 183 | return 0 184 | else: 185 | return self.origIsSystemDLL(pathname) 186 | 187 | def initialize_options(self): 188 | py2exe.build_exe.py2exe.initialize_options(self) 189 | self.makensis = None 190 | 191 | def finalize_options(self): 192 | py2exe.build_exe.py2exe.finalize_options(self) 193 | if self.makensis is None: 194 | try: 195 | import win32api 196 | self.makensis = win32api.FindExecutable('makensis') 197 | except: 198 | # Default path for makensis. This is where it gets 199 | # installed by default. 200 | self.makensis = os.path.join(os.environ['ProgramFiles'], 'NSIS\\makensis') 201 | 202 | def run(self): 203 | # Make sure the dist directory doesn't exist already--make 204 | # the user delete it first if it does. (This is safer 205 | # than calling rm_rf() on it, in case the user has 206 | # specified '/' or some equally foolish directory as the 207 | # dist directory.) 208 | if os.path.exists(self.dist_dir): 209 | print "Error, the directory %s already exists." % (self.dist_dir) 210 | print "Please remove it before starting this script." 211 | sys.exit(1) 212 | 213 | # Override py2exe's isSystemDLL because it erroneously 214 | # flags sdl_ttf.dll and libogg-0.dll as system DLLs 215 | self.origIsSystemDLL = py2exe.build_exe.isSystemDLL 216 | py2exe.build_exe.isSystemDLL = self.isSystemDLL 217 | 218 | # Build the .exe files, etc. 219 | py2exe.build_exe.py2exe.run(self) 220 | 221 | # Now run NSIS to build the installer. 222 | cmd = '"%(makensis)s" /DVERSION=%(version)s install\\windows_installer.nsi' % { 223 | 'makensis' : self.makensis, 224 | 'version' : pykversion.PYKARAOKE_VERSION_STRING, 225 | } 226 | print cmd 227 | os.system(cmd) 228 | 229 | # Now that we've got an installer, we can empty the dist 230 | # dir again. 231 | self.rm_rf(self.dist_dir) 232 | 233 | def rm_rf(self, dirname): 234 | """Recursively removes a directory's contents.""" 235 | if os.path.isdir(dirname): 236 | for root, dirs, files in os.walk(dirname, topdown=False): 237 | for name in files: 238 | pathname = os.path.join(root, name) 239 | try: 240 | os.remove(pathname) 241 | except: 242 | # Try to make it writable first, then remove it. 243 | os.chmod(pathname, 0666) 244 | os.remove(pathname) 245 | 246 | for name in dirs: 247 | pathname = os.path.join(root, name) 248 | os.rmdir(pathname) 249 | os.rmdir(dirname) 250 | 251 | cmdclass['nsis'] = BuildInstaller 252 | 253 | # tell py2exe what the front end applications are. 254 | setupArgs['windows'] = [ 255 | { "script": "pykaraoke.py", 256 | "icon_resources" : [(0, "icons\\microphone.ico")], 257 | }, 258 | { "script": "pykaraoke_mini.py", 259 | "icon_resources" : [(0, "icons\\microphone.ico")], 260 | }, 261 | ] 262 | 263 | setup(**setupArgs) 264 | -------------------------------------------------------------------------------- /pympg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # pympg - MPEG Karaoke Player 4 | # 5 | # Copyright (C) 2010 Kelvin Lawson (kelvinl@users.sourceforge.net) 6 | # 7 | # This library is free software; you can redistribute it and/or 8 | # modify it under the terms of the GNU Lesser General Public 9 | # License as published by the Free Software Foundation; either 10 | # version 2.1 of the License, or (at your option) any later version. 11 | # 12 | # This library is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # Lesser General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public 18 | # License along with this library; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | from pykconstants import * 22 | from pykplayer import pykPlayer 23 | from pykenv import env 24 | from pykmanager import manager 25 | import pygame, sys, os, string, subprocess 26 | import threading 27 | 28 | # OVERVIEW 29 | # 30 | # pympg is an MPEG player built using python. It was written for the 31 | # PyKaraoke project but is in fact a general purpose MPEG player that 32 | # could be used in other python projects requiring an MPEG player. 33 | # 34 | # The player uses the pygame library (www.pygame.org), and can therefore 35 | # run on any operating system that runs pygame (currently Linux, Windows 36 | # and OSX). 37 | # 38 | # You can use this file as a standalone player, or together with 39 | # PyKaraoke. PyKaraoke provides a graphical user interface, playlists, 40 | # searchable song database etc. 41 | # 42 | # For those writing a media player or similar project who would like 43 | # MPG support, this module has been designed to be easily incorporated 44 | # into such projects and is released under the LGPL. 45 | 46 | 47 | # REQUIREMENTS 48 | # 49 | # pympg requires the following to be installed on your system: 50 | # . Python (www.python.org) 51 | # . Pygame (www.pygame.org) 52 | 53 | 54 | # USAGE INSTRUCTIONS 55 | # 56 | # To start the player, pass the MPEG filename/path on the command line: 57 | # python pympg.py /songs/theboxer.mpg 58 | # 59 | # You can also incorporate a MPG player in your own projects by 60 | # importing this module. The class mpgPlayer is exported by the 61 | # module. You can import and start it as follows: 62 | # import pympg 63 | # player = pympg.mpgPlayer("/songs/theboxer.mpg") 64 | # player.Play() 65 | # If you do this, you must also arrange to call pympg.manager.Poll() 66 | # from time to time, at least every 100 milliseconds or so, to allow 67 | # the player to do its work. 68 | # 69 | # The class also exports Close(), Pause(), Rewind(), GetPos(). 70 | # 71 | # There are two optional parameters to the initialiser, errorNotifyCallback 72 | # and doneCallback: 73 | # 74 | # errorNotifyCallback, if provided, will be used to print out any error 75 | # messages (e.g. song file not found). This allows the module to fit 76 | # together well with GUI playlist managers by utilising the same GUI's 77 | # error popup window mechanism (or similar). If no callback is provided, 78 | # errors are printed to stdout. errorNotifyCallback should take one 79 | # parameter, the error string, e.g.: 80 | # def errorPopup (ErrorString): 81 | # msgBox (ErrorString) 82 | # 83 | # doneCallback can be used to register a callback so that the player 84 | # calls you back when the song is finished playing. The callback should 85 | # take no parameters, e.g.: 86 | # def songFinishedCallback(): 87 | # msgBox ("Song is finished") 88 | # 89 | # To register callbacks, pass the functions in to the initialiser: 90 | # mpgPlayer ("/songs/theboxer.mpg", errorPopup, songFinishedCallback) 91 | # These parameters are optional and default to None. 92 | # 93 | # If the initialiser fails (e.g. the song file is not present), __init__ 94 | # raises an exception. 95 | 96 | 97 | # IMPLEMENTATION DETAILS 98 | # 99 | # pympg is implemented as a handful of python modules. Pygame provides 100 | # all of the MPEG decoding and display capabilities, and can play an 101 | # MPEG file with just a few lines of code. Hence this module is rather 102 | # small. What it provides on top of the basic pygame features, is a 103 | # player-like class interface with Play, Pause, Rewind etc. It also 104 | # implements a resizable player window. And, of course, it integrates 105 | # nicely with pykaraoke.py and pykaraoke_mini.py. 106 | # 107 | # Previous implementations ran the player within a thread; this is no 108 | # longer the case. Instead, it is the caller's responsibility to call 109 | # pycdg.manager.Poll() every once in a while to ensure that the player 110 | # gets enough CPU time to do its work. Ideally, this should be at 111 | # least every 100 milliseconds or so to guarantee good video and audio 112 | # response time. 113 | 114 | # Display depth (bits) 115 | DISPLAY_DEPTH = 32 116 | 117 | # Check to see if the movie module is available. 118 | try: 119 | import pygame.movie as movie 120 | except ImportError: 121 | movie = None 122 | 123 | # mpgPlayer Class 124 | class mpgPlayer(pykPlayer): 125 | # Initialise the player instace 126 | def __init__(self, song, songDb, errorNotifyCallback=None, doneCallback=None): 127 | """The first parameter, song, may be either a pykdb.SongStruct 128 | instance, or it may be a filename. """ 129 | 130 | pykPlayer.__init__(self, song, songDb, errorNotifyCallback, doneCallback) 131 | 132 | self.Movie = None 133 | 134 | manager.setCpuSpeed('mpg') 135 | 136 | manager.InitPlayer(self) 137 | manager.OpenDisplay(depth = DISPLAY_DEPTH) 138 | 139 | # Close the mixer while using Movie 140 | manager.CloseAudio() 141 | 142 | # Open the Movie module 143 | filepath = self.SongDatas[0].GetFilepath() 144 | if type(filepath) == unicode: 145 | filepath = filepath.encode(sys.getfilesystemencoding()) 146 | self.Movie = pygame.movie.Movie(filepath) 147 | self.Movie.set_display(manager.display, (0, 0, manager.displaySize[0], manager.displaySize[1])) 148 | 149 | 150 | def doPlay(self): 151 | self.Movie.play() 152 | 153 | def doPause(self): 154 | self.Movie.pause() 155 | 156 | def doUnpause(self): 157 | self.Movie.play() 158 | 159 | def doRewind(self): 160 | self.Movie.stop() 161 | self.Movie.rewind() 162 | 163 | # Get the movie length (in seconds). 164 | def GetLength(self): 165 | return self.Movie.get_length() 166 | 167 | # Get the current time (in milliseconds). 168 | def GetPos(self): 169 | return (self.Movie.get_time() * 1000) 170 | 171 | def SetupOptions(self): 172 | """ Initialise and return optparse OptionParser object, 173 | suitable for parsing the command line options to this 174 | application. """ 175 | 176 | parser = pykPlayer.SetupOptions(self, usage = "%prog [options] ") 177 | 178 | # Remove irrelevant options. 179 | parser.remove_option('--font-scale') 180 | 181 | return parser 182 | 183 | def shutdown(self): 184 | # This will be called by the pykManager to shut down the thing 185 | # immediately. 186 | if self.Movie: 187 | self.Movie.stop() 188 | # Must remove the object before using pygame.mixer module again 189 | self.Movie = None 190 | pykPlayer.shutdown(self) 191 | 192 | def handleEvent(self, event): 193 | if event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN and (event.mod & (pygame.KMOD_LSHIFT | pygame.KMOD_RSHIFT | pygame.KMOD_LMETA | pygame.KMOD_RMETA)): 194 | # Shift/meta return: start/stop song. Useful for keybinding apps. 195 | self.Close() 196 | return 197 | 198 | pykPlayer.handleEvent(self, event) 199 | 200 | # Internal. Only called by the pykManager. 201 | def doResize(self, newSize): 202 | # Resize the screen. 203 | self.Movie.set_display(manager.display, (0, 0, manager.displaySize[0], manager.displaySize[1])) 204 | 205 | # Internal. Only called by the pykManager. 206 | def doResizeBegin(self): 207 | # The Movie player must be paused while resizing otherwise we 208 | # get Xlib errors. pykmanager will call here before the resize 209 | # so that we can do it. 210 | if self.State == STATE_PLAYING: 211 | self.Movie.pause() 212 | 213 | # Internal. Only called by the pykManager. 214 | def doResizeEnd(self): 215 | # Called by pykmanager when resizing has finished. 216 | # We only play if it was playing in the first place. 217 | if self.State == STATE_PLAYING: 218 | self.Movie.play() 219 | 220 | class externalPlayer(pykPlayer): 221 | 222 | """ This class is used to invoke an external command and wait for 223 | it to finish. It is usually used to play a video file using an 224 | external player. """ 225 | 226 | def __init__(self, song, songDb, errorNotifyCallback=None, doneCallback=None): 227 | """The first parameter, song, may be either a pykdb.SongStruct 228 | instance, or it may be a filename. """ 229 | 230 | pykPlayer.__init__(self, song, songDb, errorNotifyCallback, doneCallback) 231 | 232 | self.Movie = None 233 | 234 | manager.setCpuSpeed('mpg') 235 | manager.InitPlayer(self) 236 | 237 | # Close the audio and the display 238 | manager.CloseAudio() 239 | manager.CloseDisplay() 240 | manager.CloseCPUControl() 241 | 242 | self.procReturnCode = None 243 | self.proc = None 244 | 245 | 246 | def doPlay(self): 247 | if self.procReturnCode != None: 248 | # The movie is done. 249 | self.__stop() 250 | 251 | if not self.proc: 252 | self.__start() 253 | 254 | def GetLength(self): 255 | # We cannot fetch the length from arbitrary external players. 256 | # Return zero-length. 257 | return 0 258 | 259 | def GetPos(self): 260 | # Use the default GetPos() which simply checks the time 261 | # since we started playing. This does not take account 262 | # for any fast-forward/rewind that may occur in the 263 | # external player, but we cannot support getting the 264 | # song position from arbitrary user-supplied players. 265 | return pykPlayer.GetPos(self) 266 | 267 | def doStuff(self): 268 | if self.procReturnCode != None: 269 | # The movie is done. 270 | self.__stop() 271 | self.Close() 272 | 273 | pykPlayer.doStuff(self) 274 | 275 | def __start(self): 276 | filepath = self.SongDatas[0].GetFilepath() 277 | 278 | external = manager.settings.MpgExternal 279 | if '%' in external: 280 | # Assume the filename parameter is embedded in the string. 281 | cmd = external % { 282 | 'file' : filepath, 283 | } 284 | 285 | elif external: 286 | # No parameter appears to be present; assume the program 287 | # accepts the filename as the only parameter. 288 | cmd = [external, filepath] 289 | 290 | shell = True 291 | if env == ENV_WINDOWS: 292 | # Don't try to open the process under a "shell" in 293 | # Windows; that just breaks commands with spaces. 294 | shell = False 295 | 296 | assert self.procReturnCode == None 297 | sys.stdout.flush() 298 | self.proc = subprocess.Popen(cmd, shell = shell) 299 | if manager.settings.MpgExternalThreaded: 300 | # Wait for it to complete in a thread. 301 | self.thread = threading.Thread(target = self.__runThread) 302 | self.thread.start() 303 | else: 304 | # Don't wait for it in a thread; wait for it here. 305 | self.thread = None 306 | self.__runThread() 307 | 308 | def __stop(self): 309 | if self.thread: 310 | self.thread.join() 311 | self.proc = None 312 | self.procReturnCode = None 313 | self.thread = None 314 | 315 | 316 | def __runThread(self): 317 | """ This method runs in a sub-thread. Its job is just to wait 318 | for the process to finish. """ 319 | 320 | try: 321 | self.procReturnCode = self.proc.wait() 322 | except OSError: 323 | self.procReturnCode = -1 324 | 325 | 326 | 327 | # Can be called from the command line with the MPG filepath as parameter 328 | def main(): 329 | player = mpgPlayer(None, None) 330 | player.Play() 331 | manager.WaitForPlayer() 332 | 333 | if __name__ == "__main__": 334 | sys.exit(main()) 335 | #import profile 336 | #result = profile.run('main()', 'pympg.prof') 337 | #sys.exit(result) 338 | 339 | -------------------------------------------------------------------------------- /pykplayer.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2010 Kelvin Lawson (kelvinl@users.sourceforge.net) 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | 18 | """This module defines the class pykPlayer, which is a base class used 19 | by the modules pykar.py, pycdg.py, and pympg.py. This collects 20 | together some common interfaces used by these different 21 | implementations for different types of Karaoke files.""" 22 | 23 | from pykconstants import * 24 | from pykmanager import manager 25 | from pykenv import env 26 | import pygame 27 | import sys 28 | import types 29 | import os 30 | 31 | class pykPlayer: 32 | def __init__(self, song, songDb, 33 | errorNotifyCallback = None, doneCallback = None, 34 | windowTitle = None): 35 | """The first parameter, song, may be either a pykdb.SongStruct 36 | instance, or it may be a filename. """ 37 | 38 | if songDb == None: 39 | import pykdb 40 | songDb = pykdb.globalSongDB 41 | songDb.LoadSettings(None) 42 | self.songDb = songDb 43 | 44 | # Set the global command-line options if they have not already 45 | # been set. 46 | if manager.options == None: 47 | parser = self.SetupOptions() 48 | (manager.options, args) = parser.parse_args() 49 | manager.ApplyOptions(self.songDb) 50 | 51 | if song is None: 52 | if (len(args) != 1): 53 | parser.print_help() 54 | sys.exit(2) 55 | song = args[0] 56 | 57 | # Unfortunately, we can't capture sound when dumping. There 58 | # are two reasons for this. (1) pymedia doesn't currently 59 | # support multiplexing audio with a video stream, so when 60 | # you're dumping an mpeg file, it has to be video-only. (2) 61 | # pygame doesn't provide a way for us to programmatically 62 | # convert a midi file to sound samples anyway--all you can do 63 | # with a midi file is route it through the speakers. 64 | 65 | # So, for these reasons, we always just disable sound when 66 | # dumping images or movies. 67 | if manager.options.dump: 68 | manager.options.nomusic = True 69 | 70 | if isinstance(song, types.StringTypes): 71 | # We were given a filename. Convert it to a SongStruct. 72 | song = self.songDb.makeSongStruct(song) 73 | 74 | # Store the parameters 75 | self.Song = song 76 | self.WindowTitle = windowTitle 77 | 78 | # And look up the actual files corresponding to this SongStruct. 79 | self.SongDatas = song.GetSongDatas() 80 | if windowTitle is None: 81 | self.WindowTitle = song.DisplayFilename 82 | 83 | # Caller can register a callback by which we 84 | # print out error information, use stdout if none registered 85 | if errorNotifyCallback: 86 | self.ErrorNotifyCallback = errorNotifyCallback 87 | else: 88 | self.ErrorNotifyCallback = self.__defaultErrorPrint 89 | 90 | # Caller can register a callback by which we 91 | # let them know when the song is finished 92 | if doneCallback: 93 | self.SongFinishedCallback = doneCallback 94 | else: 95 | self.SongFinishedCallback = None 96 | 97 | self.State = STATE_INIT 98 | self.InternalOffsetTime = 0 99 | 100 | # These values are used to keep track of the current position 101 | # through the song based on pygame's get_ticks() interface. 102 | # It's used only when get_pos() cannot be used or is 103 | # unreliable for some reason. 104 | self.PlayTime = 0 105 | self.PlayStartTime = 0 106 | self.PlayFrame = 0 107 | 108 | # self.PlayStartTime is valid while State == STATE_PLAYING; it 109 | # indicates the get_ticks() value at which the song started 110 | # (adjusted for any pause intervals that occurred during 111 | # play). self.PlayTime is valid while State != STATE_PLAYING; 112 | # it indicates the total number of ticks (milliseconds) that 113 | # have elapsed in the song so far. 114 | 115 | # self.PlayFrame starts at 0 and increments once for each 116 | # frame. It's not very meaningful, except in STATE_CAPTURING 117 | # mode. 118 | 119 | # Keep track of the set of modifier buttons that are held 120 | # down. This is currently used only for the GP2X interface. 121 | self.ShoulderLHeld = False 122 | self.ShoulderRHeld = False 123 | 124 | # Set this true if the player can zoom font sizes. 125 | self.SupportsFontZoom = False 126 | 127 | # The following methods are part of the public API and intended to 128 | # be exported from this class. 129 | 130 | def Validate(self): 131 | """ Returns True if the karaoke file appears to be playable 132 | and contains lyrics, or False otherwise. """ 133 | 134 | return self.doValidate() 135 | 136 | def Play(self): 137 | self.doPlay() 138 | 139 | if manager.options.dump: 140 | self.setupDump() 141 | else: 142 | self.PlayStartTime = pygame.time.get_ticks() 143 | self.State = STATE_PLAYING 144 | 145 | # Pause the song - Use Pause() again to unpause 146 | def Pause(self): 147 | if self.State == STATE_PLAYING: 148 | self.doPause() 149 | self.PlayTime = pygame.time.get_ticks() - self.PlayStartTime 150 | self.State = STATE_PAUSED 151 | elif self.State == STATE_PAUSED: 152 | self.doUnpause() 153 | self.PlayStartTime = pygame.time.get_ticks() - self.PlayTime 154 | self.State = STATE_PLAYING 155 | 156 | # Close the whole thing down 157 | def Close(self): 158 | self.State = STATE_CLOSING 159 | 160 | # you must call Play() to restart. Blocks until pygame is initialised 161 | def Rewind(self): 162 | self.doRewind() 163 | self.PlayTime = 0 164 | self.PlayStartTime = 0 165 | self.PlayFrame = 0 166 | self.State = STATE_NOT_PLAYING 167 | 168 | # Stop the song and go back to the start. As you would 169 | # expect Stop to do on a CD player. Play() restarts from 170 | # the beginning 171 | def Stop(self): 172 | self.Rewind() 173 | 174 | # Get the song length (in seconds) 175 | def GetLength(self): 176 | ErrorString = "GetLength() not supported" 177 | self.ErrorNotifyCallback (ErrorString) 178 | return None 179 | 180 | # Get the current time (in milliseconds). 181 | def GetPos(self): 182 | if self.State == STATE_PLAYING: 183 | return pygame.time.get_ticks() - self.PlayStartTime 184 | else: 185 | return self.PlayTime 186 | 187 | def SetupOptions(self, usage = None): 188 | """ Initialise and return optparse OptionParser object, 189 | suitable for parsing the command line options to this 190 | application. """ 191 | 192 | if usage == None: 193 | usage = "%prog [options] " 194 | 195 | return manager.SetupOptions(usage, self.songDb) 196 | 197 | # Below methods are internal. 198 | 199 | def setupDump(self): 200 | # Capture the output as a sequence of numbered frame images. 201 | self.PlayTime = 0 202 | self.PlayStartTime = 0 203 | self.PlayFrame = 0 204 | self.State = STATE_CAPTURING 205 | 206 | self.dumpFrameRate = manager.options.dump_fps 207 | assert self.dumpFrameRate 208 | 209 | filename = manager.options.dump 210 | base, ext = os.path.splitext(filename) 211 | ext_lower = ext.lower() 212 | 213 | self.dumpEncoder = None 214 | if ext_lower == '.mpg': 215 | # Use pymedia to convert frames to an mpeg2 stream 216 | # on-the-fly. 217 | import pymedia 218 | import pymedia.video.vcodec as vcodec 219 | 220 | self.dumpFile = open(filename, 'wb') 221 | frameRate = int(self.dumpFrameRate * 100 + 0.5) 222 | self.dumpFrameRate = float(frameRate) / 100.0 223 | 224 | params= { \ 225 | 'type': 0, 226 | 'gop_size': 12, 227 | 'frame_rate_base': 125, 228 | 'max_b_frames': 0, 229 | 'height': manager.options.size_y, 230 | 'width': manager.options.size_x, 231 | 'frame_rate': frameRate, 232 | 'deinterlace': 0, 233 | 'bitrate': 9800000, 234 | 'id': vcodec.getCodecID('mpeg2video') 235 | } 236 | self.dumpEncoder = vcodec.Encoder( params ) 237 | return 238 | 239 | # Don't dump a video file; dump a sequence of frames instead. 240 | self.dumpPPM = (ext_lower == '.ppm' or ext_lower == '.pnm') 241 | self.dumpAppend = False 242 | 243 | # Convert the filename to a pattern. 244 | if '#' in filename: 245 | hash = filename.index('#') 246 | end = hash 247 | while end < len(filename) and filename[end] == '#': 248 | end += 1 249 | count = end - hash 250 | filename = filename[:hash] + '%0' + str(count) + 'd' + filename[end:] 251 | else: 252 | # There's no hash in the filename. 253 | if self.dumpPPM: 254 | # We can dump a series of frames all to the same file, 255 | # if we're dumping ppm frames. Mjpegtools likes this. 256 | self.dumpAppend = True 257 | try: 258 | os.remove(filename) 259 | except OSError: 260 | pass 261 | else: 262 | # Implicitly append a frame number. 263 | filename = base + '%04d' + ext 264 | 265 | self.dumpFilename = filename 266 | 267 | def doFrameDump(self): 268 | if self.dumpEncoder: 269 | import pymedia.video.vcodec as vcodec 270 | 271 | ss = pygame.image.tostring(manager.surface, "RGB") 272 | bmpFrame = vcodec.VFrame( 273 | vcodec.formats.PIX_FMT_RGB24, 274 | manager.surface.get_size(), (ss,None,None)) 275 | yuvFrame = bmpFrame.convert(vcodec.formats.PIX_FMT_YUV420P) 276 | d = self.dumpEncoder.encode(yuvFrame) 277 | self.dumpFile.write(d.data) 278 | return 279 | 280 | if self.dumpAppend: 281 | filename = self.dumpFilename 282 | else: 283 | filename = self.dumpFilename % self.PlayFrame 284 | print filename 285 | 286 | if self.dumpPPM: 287 | # Dump a PPM file. We do PPM by hand since pygame 288 | # doesn't support it directly, but it's so easy and 289 | # useful. 290 | 291 | w, h = manager.surface.get_size() 292 | if self.dumpAppend: 293 | f = open(filename, 'ab') 294 | else: 295 | f = open(filename, 'wb') 296 | f.write('P6\n%s %s 255\n' % (w, h)) 297 | f.write(pygame.image.tostring(manager.surface, 'RGB')) 298 | 299 | else: 300 | # Ask pygame to dump the file. We trust that pygame knows 301 | # how to store an image in the requested format. 302 | pygame.image.save(manager.surface, filename) 303 | 304 | 305 | def doValidate(self): 306 | return True 307 | 308 | def doPlay(self): 309 | pass 310 | 311 | def doPause(self): 312 | pass 313 | 314 | def doUnpause(self): 315 | pass 316 | 317 | def doRewind(self): 318 | pass 319 | 320 | def doStuff(self): 321 | # Override this in a derived class to do some useful per-frame 322 | # activity. 323 | # Common handling code for a close request or if the 324 | # pygame window was quit 325 | if self.State == STATE_CLOSING: 326 | if manager.display: 327 | manager.display.fill((0,0,0)) 328 | pygame.display.flip() 329 | self.shutdown() 330 | 331 | elif self.State == STATE_CAPTURING: 332 | # We are capturing a video file. 333 | self.doFrameDump() 334 | 335 | # Set the frame time for the next frame. 336 | self.PlayTime = 1000.0 * self.PlayFrame / self.dumpFrameRate 337 | 338 | self.PlayFrame += 1 339 | 340 | 341 | def doResize(self, newSize): 342 | # This will be called internally whenever the window is 343 | # resized for any reason, either due to an application resize 344 | # request being processed, or due to the user dragging the 345 | # window handles. 346 | pass 347 | 348 | def doResizeBegin(self): 349 | # This will be called internally before the screen is resized 350 | # by pykmanager and doResize() is called. Not all players need 351 | # to do anything here. 352 | pass 353 | 354 | def doResizeEnd(self): 355 | # This will be called internally after the screen is resized 356 | # by pykmanager and doResize() is called. Not all players need 357 | # to do anything here. 358 | pass 359 | 360 | def handleEvent(self, event): 361 | if event.type == pygame.USEREVENT: 362 | self.Close() 363 | elif event.type == pygame.KEYDOWN: 364 | if event.key == pygame.K_ESCAPE: 365 | self.Close() 366 | 367 | elif event.key == pygame.K_PAUSE or event.key == pygame.K_p: 368 | self.Pause() 369 | 370 | elif event.key == pygame.K_BACKSPACE or event.key == pygame.K_DELETE: 371 | self.Rewind() 372 | self.Play() 373 | 374 | # Use control-left/right arrow to offset the current 375 | # graphics time by 1/4 sec. Use control-down arrow to 376 | # restore them to sync. 377 | elif self.State == STATE_PLAYING and event.key == pygame.K_RIGHT and event.mod & (pygame.KMOD_LCTRL | pygame.KMOD_RCTRL): 378 | manager.settings.SyncDelayMs += 250 379 | print "sync %s" % manager.settings.SyncDelayMs 380 | elif self.State == STATE_PLAYING and event.key == pygame.K_LEFT and event.mod & (pygame.KMOD_LCTRL | pygame.KMOD_RCTRL): 381 | manager.settings.SyncDelayMs -= 250 382 | print "sync %s" % manager.settings.SyncDelayMs 383 | elif self.State == STATE_PLAYING and event.key == pygame.K_DOWN and event.mod & (pygame.KMOD_LCTRL | pygame.KMOD_RCTRL): 384 | manager.settings.SyncDelayMs = 0 385 | print "sync %s" % manager.settings.SyncDelayMs 386 | 387 | if self.SupportsFontZoom: 388 | if event.key == pygame.K_PLUS or event.key == pygame.K_EQUALS or \ 389 | event.key == pygame.K_KP_PLUS: 390 | manager.ZoomFont(1.0/0.9) 391 | elif event.key == pygame.K_MINUS or event.key == pygame.K_UNDERSCORE or \ 392 | event.key == pygame.K_KP_MINUS: 393 | manager.ZoomFont(0.9) 394 | 395 | elif event.type == pygame.QUIT: 396 | self.Close() 397 | 398 | elif env == ENV_GP2X and event.type == pygame.JOYBUTTONDOWN: 399 | if event.button == GP2X_BUTTON_SELECT: 400 | self.Close() 401 | elif event.button == GP2X_BUTTON_START: 402 | self.Pause() 403 | elif event.button == GP2X_BUTTON_L: 404 | self.ShoulderLHeld = True 405 | elif event.button == GP2X_BUTTON_R: 406 | self.ShoulderRHeld = True 407 | 408 | if self.SupportsFontZoom: 409 | if event.button == GP2X_BUTTON_RIGHT and self.ShoulderLHeld: 410 | manager.ZoomFont(1.0/0.9) 411 | elif event.button == GP2X_BUTTON_LEFT and self.ShoulderLHeld: 412 | manager.ZoomFont(0.9) 413 | 414 | elif env == ENV_GP2X and event.type == pygame.JOYBUTTONUP: 415 | if event.button == GP2X_BUTTON_L: 416 | self.ShoulderLHeld = False 417 | elif event.button == GP2X_BUTTON_R: 418 | self.ShoulderRHeld = False 419 | 420 | def shutdown(self): 421 | # This will be called by the pykManager to shut down the thing 422 | # immediately. 423 | 424 | # If the caller gave us a callback, let them know we're finished 425 | if self.State != STATE_CLOSED: 426 | self.State = STATE_CLOSED 427 | if self.SongFinishedCallback != None: 428 | self.SongFinishedCallback() 429 | 430 | 431 | def __defaultErrorPrint(self, ErrorString): 432 | print (ErrorString) 433 | 434 | 435 | def findPygameFont(self, fontData, fontSize): 436 | """ Returns a pygame.Font selected by this data. """ 437 | if not fontData.size: 438 | # The font names a specific filename. 439 | filename = fontData.name 440 | if os.path.sep not in filename: 441 | filename = os.path.join(manager.FontPath, filename) 442 | return pygame.font.Font(filename, fontSize) 443 | 444 | # The font names a system font. 445 | pointSize = int(fontData.size * fontSize / 10.0 + 0.5) 446 | return pygame.font.SysFont( 447 | fontData.name, pointSize, bold = fontData.bold, 448 | italic = fontData.italic) 449 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | **** This is a part of PyKaraoke. **** 3 | **** **** 4 | **** Copyright (C) 2007 Kelvin Lawson (kelvinl@users.sourceforge.net) **** 5 | **** Copyright (C) 2009 PyKaraoke Development Team **** 6 | **** **** 7 | **** This library is free software; you can redistribute it and/or **** 8 | **** modify it under the terms of the GNU Lesser General Public **** 9 | **** License as published by the Free Software Foundation; either **** 10 | **** version 2.1 of the License, or (at your option) any later version. **** 11 | **** **** 12 | **** This library is distributed in the hope that it will be useful, **** 13 | **** but WITHOUT ANY WARRANTY; without even the implied warranty of **** 14 | **** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU **** 15 | **** Lesser General Public License for more details. **** 16 | **** **** 17 | **** You should have received a copy of the GNU Lesser General Public **** 18 | **** License along with this library; if not, write to the **** 19 | **** Free Software Foundation, Inc. **** 20 | **** 59 Temple Place, Suite 330 **** 21 | **** Boston, MA 02111-1307 USA **** 22 | ******************************************************************************* 23 | 24 | 2011-08-23 Kelvin Lawson 25 | * Enhancements: 26 | pykdb.py: Reduce memory usage during duplicate file hash check. 27 | pykdb.py: Handle corrupted ZIP files. 28 | 29 | 2011-08-22 Kelvin Lawson 30 | * Bug fixes: 31 | pykdb.py: Fixes for unicode strings converted to ASCII. 32 | pykaraoke.py: Fixes for bytestrings that are not unicode. 33 | 34 | 2011-06-19 Kelvin Lawson 0.7.4 35 | * Bug fixes: 36 | pykaraoke.py: Allow search for unicode song names. 37 | pykaraoke.py: Fix FileDetails dialog for non-ascii characters. 38 | pykdb.py: Fixes for international characters in filenames. 39 | 40 | 2011-01-02 Kelvin Lawson 41 | * Enhancements: 42 | performer_prompt.py: Improve sizing on Windows. 43 | 44 | 2010-09-16 Kelvin Lawson 45 | * Enhancements: 46 | pykar.py: Better platform detection using ENV_POSIX. 47 | pykaraoke.py: Better platform detection using ENV_POSIX. 48 | pykconstants.py: Better platform detection using ENV_POSIX. 49 | pykenv.py: Better platform detection using ENV_POSIX. 50 | 51 | 2010-05-11 Kelvin Lawson 0.7.3 52 | * Bug fixes: 53 | -r1.62 pykaraoke.py: Make About window work with Wx2.6 again. Recent 54 | About window changes work on Wx2.8 only. 55 | 56 | 2010-04-21 57 | * Enhancements: 58 | -r1.61 pykaraoke.py: Add options for double-buffered and hardware 59 | -r1.39 pykdb.py: Add options for double-buffered and hardware 60 | -r1.18 pykmanager.py: Add options for double-buffered and hardware 61 | -r1.12 pykplayer.py: 'P' key pauses. 62 | -r1.11 pykplayer.py: Backspace rewinds. 63 | 64 | * Bug fixes: 65 | - r1.9 _pcdgAux.c: Fix occasional crash in CDG player. 66 | 67 | 2010-04-20 68 | * Enhancements: 69 | -r1.60 pykaraoke.py: Support left/right margins, and two-column printing. 70 | -r1.20 pykar.py: OSX works better with Timidity. 71 | 72 | 2010-04-16 73 | * Enhancements: 74 | -r1.59 pykaraoke.py: Allow omitting filename from printout. 75 | 76 | 2010-04-14 77 | * Enhancements: 78 | -r1.38 pykdb.py: Remove articles from sort key, faster scrolling on GP2X. 79 | -r1.34 pycdg.py: Clean up keyboard operations and allow remote control 80 | access to pykaraoke_mini. 81 | -r1.10 pykaraoke_mini.py: Clean up keyboard operations and allow remote 82 | control access to pykaraoke_mini. 83 | -r1.9 pykaraoke_mini.py: Remove articles from sort key, faster scrolling on GP2X. 84 | -r1.19 pykar.py: Clean up keyboard operations and allow remote control 85 | -r1.17 pympg.py: Clean up keyboard operations and allow remote control 86 | 87 | 2010-04-09 88 | * Enhancements: 89 | -r1.8 pycdgAux.py: Filter out draw commands based on mysterious bit 0x20. 90 | -r1.8 _pycdgAux.c: Filter out draw commands based on mysterious bit 0x20. 91 | 92 | 2010-04-08 93 | * Enhancements: 94 | -r1.37 pykdb.py: Handle invalid utf-8 in titles.txt. 95 | 96 | 2010-03-31 Kelvin Lawson 0.7.3 97 | * Bug fixes: 98 | - r1.36 pykdb.py: Fix unicode folder searches. Add time string to temp 99 | filename to work around bug in pygame.mixer.music on Windows. 100 | - r1.33 pycdg.py: Fix unicode filename opens. 101 | - r1.18 pykar.py: Fix unicode filename opens. 102 | - r1.16 pympg.py: Fix unicode filename opens. 103 | 104 | 2010-03-29 105 | * Enhancements: 106 | -r1.58 pykaraoke.py: Minor OSX fixes 107 | 108 | 2009-10-25 Kelvin Lawson 0.7.2 109 | * Bug fixes: 110 | - r1.15 pympg.py: Support GetLength() call with external player. 111 | 112 | 2009-08-26 Kelvin Lawson 0.7.1 113 | * Enhancements: 114 | - r1.57 pykaraoke.py: Improved layout of CDG Configuration page with 115 | song-derivation options. 116 | - r1.56 pykaraoke.py, r1.35 pykdb.py: Add new option to exclude filenames 117 | that don't match naming scheme from search results. 118 | - r1.33 pykdb.py: Move from md5 to hashlib due to Python deprecation 119 | warning. 120 | - r1.32 pykdb.py: Now allows songs in database if they do not match the 121 | "Derive song information from filename" format. Sets the title field 122 | only to the filename. 123 | - r1.31 pycdg.py: Switch from MP3Info.py to python-mutagen for MP3 info 124 | as more widely available in package managers. 125 | 126 | * Bug Fixes: 127 | - r1.31 pykdb.py: Fix failure to play songs from File View due to addition 128 | of "Derive song information from filename" feature. 129 | - r1.31 pykdb.py: Report that song file does not match filename scheme, 130 | rather than the claimed ZIP error. 131 | 132 | 2009-08-12 Kelvin Lawson 0.7.0 133 | * Bug Fixes: 134 | - r1.49 pykaraoke.py: Fix File Details available from Folder View. 135 | - r1.50 pykaraoke.py: Fix selection of items in Folder View. 136 | - r1.16 pykar.py: KAR/MID player now reports song length. 137 | 138 | * Enhancements: 139 | - r1.51 pykaraoke.py: Performer prompt now pops up when adding to Playlist 140 | from any panel. 141 | - r1.52 pykaraoke.py: Defaults to Filename columns, Artist/Title columns 142 | can be enabled from the Configuration menu. 143 | - r1.30 pykdb.py: Add configuration option for Artist/Title columns. 144 | Increased configuration structure version SETTINGS_VERSION to 4. 145 | - r1.52 pykaraoke.py: New playlist resizing logic. 146 | 147 | 2009-07-31 John Schneiderman 0.7.0 148 | * Bug Fixes: 149 | - r1.33 pykaraoke.py: Could not directly play from a selected song in 150 | the play-list. 151 | - r1.37 pykaraoke.py: Printer setup in GNU/Linux without issue. 152 | - r1.35 pykaraoke.py: Clicking search would load the entire database. 153 | Locking up the system if the database was of a significant size. To load 154 | the entire database a user simply needs to put an asterisk in the search 155 | field. 156 | - r1.41 pykaraoke.py: File scanning no longer locks the system when a file 157 | name has invalid characters for the specified character set. 158 | - r1.41 pykaraoke.py: Searching a string with characters not supported by 159 | ASCII would lock up the system. 160 | - r1.14 pymanager.py: File scanning no longer locks the system when a file 161 | name has invalid characters for the specified character set. 162 | - r1.14 pymanager.py: Searching a string with characters not supported by 163 | ASCII would lock up the system. 164 | - r1.38 pykaraoke.py: play-list window did not take advantage of the full 165 | window size. 166 | - r1.26 pykdb.py: displays error message if unable to write to a file. 167 | - r1.46 pykaraoke.py: Play-list window now displays information cleanly. 168 | * Enhancements: 169 | - r1.30 pykaraoke.py: Time progress indicator in the status message of the 170 | song being played. 171 | - r1.36 pykaraoke.py: Displays license information. 172 | - r1.34 pykaraoke.py: Automatically save the window size on closing. 173 | - r1.33 pykaraoke.py: When not using an auto play-list, you can start and 174 | stop various songs. 175 | - r1.32 pykaraoke.py: Adds a sorting identifier to each search column. 176 | - r1.31 pykaraoke.py: Adds the option to play straight from a search list 177 | or add the song to the play-list on a double click. 178 | - r1.29 pykaraoke.py: Adds volume control of the song being played. 179 | - r1.28 pykaraoke.py: Adds Kamikaze button to add a random song to 180 | the play-list. 181 | - r1.28 pykaraoke.py: Adds option to stop automatically playing next song 182 | in play-list. 183 | - r1.28 pykaraoke.py: Adds support for extracting artist and title 184 | information automatically from file-names in a specified scheme. 185 | - r1.38 pykaraoke.py: Adds a dialogue to track the karaoke performer. 186 | - r1.24 pykdb.py: When a zip file encoding is not supported the full path 187 | is given for the problem file. 188 | - r1.40 pykaraoke.py: Adds option to disable clearing from the play-list 189 | with the right click menu in the play-list. 190 | - r1.24 pykdb.py: Gives path location when unable to open a zip file. 191 | - r1.38 pykaraoke.py: Adds programme name to license display. 192 | - r1.25 pykaraoke.py: Adds 48 kHz to available sample rates. 193 | - r1.42 pykaraoke.py, r1.26 pykdb.py: Adds option to disable double-click 194 | playing from the play-list. 195 | - r1.45 pykaraoke.py: Right click to play in the play-list is only 196 | displayed when a song is not already playing. 197 | - r1.45 pykaraoke.py, r1.29 pykdb.py, r1.17 pykmanager.py: Adds option to 198 | display the karaoke player without a frame. 199 | - r1.46 pykaraoke.py: Play-list window now displays artist information. 200 | - r1.47 pykaraoke.py: Moved clear play-list to reduce accidentally clearing 201 | while running. 202 | 203 | v0.6: 204 | 205 | * Reworked config page to add many more options. Config options 206 | are now respected by pykaraoke_mini as well as by pykaraoke 207 | (though the config dialog itself is only accessible via 208 | pykaraoke). 209 | 210 | * Added capability to convert cdg files to mpg, via the --dump 211 | command-line option. 212 | 213 | * Added support for an external movie player, for instance, 214 | Windows Media Player or mplayer, to play video file formats not 215 | supported directly by pygame, such as AVI. Using this method, 216 | full-motion video karaoke files can now be played on the GP2X. 217 | 218 | * The search/playlist window now has a user-draggable splitter 219 | handle to control the relative size of the search and playlist 220 | sides. It's also possible to configure whether they should be 221 | split vertically (left/right) or horizontally (top/bottom). 222 | 223 | * Added pulldown menus to the search/playlist window. Moved some 224 | of the buttons on that window into the menu. 225 | 226 | * Added options to print or export the song list. 227 | 228 | * Added the ability to edit a song's artist or title on-the-fly 229 | from within the search window. The changes are written back into 230 | the appropriate titles*.txt file. 231 | 232 | * Fixed international character support for kar files. 233 | 234 | * Fixed some incorrect lyrics detection in kar file parsing. 235 | 236 | * Fixed a bug preventing loading titles*.txt files from nested 237 | subdirectories within zip files. 238 | 239 | * Changed the text colour on the pykaraoke_mini browser to 240 | indicate the type of Karaoke file: kar, cdg, or mpg. 241 | 242 | * Added left/right arrow keys to the pykaraoke_mini browser. 243 | These skip to the next letter. 244 | 245 | * The pykaraoke_mini browser will collapse multiple song files 246 | sharing the same artist / title into a single line. 247 | 248 | * Changed sound-sync keys from left/right arrow to 249 | control-left/right arrow, to reduce accidental use of these keys. 250 | 251 | * Further minor performance improvements to CDG player. 252 | 253 | * Added automatic control of GP2X CPU speed. This improves both 254 | response time and battery life on the GP2X. 255 | 256 | * Changed meaning of the shoulder buttons on GP2X. Now right 257 | button + joystick up/down means to skip a full page at a time; 258 | left button + joystick left/right means to zoom the text smaller 259 | or larger. 260 | 261 | * New progress bar visible while scanning database. 262 | 263 | v0.5.1: 264 | 265 | * All: Update all copyright dates. 266 | 267 | * pykversion.py: Increase to v0.5.1. 268 | 269 | * pykaraoke.py: Fix slow CDG playback with WxPython v2.8 by 270 | overriding the default MainLoop() and making it sleep plenty. 271 | 272 | * pykaraoke.py: Allow WxPython v2.8, fix usage of system variable 273 | name now used by Wx 2.8. 274 | 275 | * _pycdgAux.c, pycdgAux.py: Fix potential array bounds problem 276 | with corrupt CDG data. 277 | 278 | * pykaraoke.py: More sensible sizing of column in search results 279 | and made Title and Artist columns more visible. 280 | 281 | * pykaraoke.py: Check for WxPython v2.6. 282 | 283 | * Reduced size of visible display of CDG graphics from 294x204 to 284 | 288x192, based on a new understanding of a presumed typo in the 285 | CDG document. This appears to solve some minor scrolling issues. 286 | 287 | v0.5: 288 | 289 | * Fixed a problem in pykar.py with synchronization of lyrics to 290 | music on certain MIDI files (files in which the tempo changes 291 | during the song). 292 | 293 | * Reworked rendering engine in pykar.py to support wordwrap and 294 | font scaling. 295 | 296 | * Wrote pykaraoke_mini.py, with an in-window scrolling interface 297 | for environments in which a full keyboard/mouse is not available. 298 | 299 | * Added pykplayer.py and pykmanager.py to collect together common 300 | bits of code between the various player types. 301 | 302 | * Made command-line options available to all public entry points: 303 | pycdg.py, pykar.py, pymgr.py, pykaraoke.py, and pykaraoke_mini.py. 304 | 305 | * Replaced threading code with explicit calls to manager.Poll(). 306 | 307 | * Moved the CDG-processing code from pycdg.py into pycdgAux.py, 308 | and also ported it down to C in _pycdgAux.c for further runtime 309 | optimization. 310 | 311 | * Pushed default framerate back to 30 fps. Setting it lower than 312 | that has limited benefit with the new codebase. 313 | 314 | * Added --zoom to control the mode in which pycdg.py scales its 315 | display to fit the window. 316 | 317 | * Added command-line parameters to control audio properties. 318 | 319 | * Added separate "titles" and "artists" columns to the song 320 | database, making it possible to sort the returned songlist by any 321 | of the three columns. The file titles.txt can be defined in the 322 | directory with all of your song files to define the title and/or 323 | artist for each song. 324 | 325 | * Ported to the GP2X. 326 | 327 | * Fixed horizontal scrolling CDGs. 328 | 329 | * Smoother CDG scrolling. 330 | 331 | v0.4.3: 332 | * pycdg.py: Fix "scratchy" audio on some systems. 333 | 334 | * pykar.py: Support non-ASCII, non-unicode charsets . 335 | 336 | * pykaraoke.py: Add charset option & launch from command-line 337 | . 338 | 339 | * pykaraoke.desktop: Add task-launcher file for file associations 340 | . 341 | 342 | * pykaraoke.py: Show pykaraoke.xpm icon as WX window icon. 343 | 344 | v0.4.2: 345 | * pycdg.py: Allow CDG filenames without extension (just a .) to allow for 346 | tab-completion. 347 | 348 | * pycdg.py: Fix border preset (don't clear full-screen). 349 | 350 | * pycdg.py: Add --nomusic option. 351 | 352 | * pycdg.py: pycdg: Fix option type 'str' in optparse 353 | 354 | * pycdg.py: pycdg: Fix FutureWarning on 0xFFFFFFFFs 355 | 356 | * pykaraoke.py: Add drag-and-drop support from search results and within 357 | playlist. 358 | 359 | * pykaraoke.py: Add drag-and-drop from Folder View 360 | 361 | * pykaraoke.py: Reuse PyKaraoke without the GUI from Craig Rindy: 362 | scripters might find it useful to reuse many things 363 | implemented in pykaraoke.py such as zip file handling. 364 | The attached patch allows the pykaraoke module (in 365 | particular PyKaraokeManager) to be used without the 366 | main application window. The patch also includes a 367 | refactoring of SongDB.FolderScan to allow for reuse. 368 | 369 | * pykaraoke.py: Support non-ASCII characters in filenames from Craig Rindy: 370 | Any valid file can be imported, and filenames that present 371 | problems in the search are skipped so that the search may 372 | continue through the rest of the database. 373 | 374 | v0.4.1: 375 | 376 | * pycdg.py: Fix typo in "CDG file may be corrupt" warning (wwf) 377 | 378 | * pycdg.py: Add -t/--title option to set the window title to 379 | something specific (useful for window managers that can remember 380 | window settings like sticky, size, location, stacking, desktop, 381 | etc., based on window title/name/class attributes, like 382 | Enlightenment does) (wwf) 383 | 384 | * Add install script and /usr/bin links in install directory. 385 | 386 | * Get icons and fonts from current directory or /usr/share/pykaraoke. 387 | 388 | * Use /usr/bin/env for shebang. 389 | 390 | * pykaraoke.py: Add KAR inside ZIP fix from Andrei Gavrila. 391 | 392 | * pykaraoke.py: Add mid/mpg extension fix from Andrei Gavrila. 393 | 394 | * pycdg.py: Default to 10 frames per second. 395 | 396 | * pycdg.py: Fix scrolling variable names 397 | 398 | * pykaraoke.py: Fix wx 2.6 API change. 399 | 400 | * pycdg.py: Split the screen into 24 tiles for better screen 401 | update performance. 402 | 403 | * pycdg.py: Lower delay time when no packets are due. 404 | 405 | * pycdg.py: Don't update the screen if 1/4 second out of sync. 406 | 407 | * pycdg.py: Don't specify the display depth, pygame will use the 408 | most appropriate. 409 | 410 | v0.4: 411 | 412 | * Use optparse to support additional command-line options 413 | (optparse is included in Python 2.3 and newer, on all standard 414 | Python-supporting platforms); run "pycdg.py --help" to see a full 415 | list of supported options. 416 | 417 | * Permit user to specify window's starting X and Y positions 418 | 419 | * Permit user to specify window's starting X and Y sizes 420 | 421 | * Permit user to start the player in fullscreen mode 422 | 423 | * Permit user to specify FPS on command line, defaults to 60 424 | 425 | * Pass cdgPlayer.__init__() an "options" object now instead of a 426 | filename; contains size_x, size_y, pos_x, pos_y, fullscreen, 427 | cdgFileName 428 | 429 | * cdgPlayer.run(): it's pedantic, but use self.Close() instead of 430 | setting self.State manually 431 | 432 | * Add key binding "q" to exit ([ESC] still works) 433 | 434 | * Hide the mouse cursor (both in fullscreen and in windowed mode) 435 | 436 | * Fix "Frames Per Second" so it's honored (previously it was 437 | ignored because curr_pos wasn't always updated as often as needed) 438 | 439 | * Change order of import statements so local versions of pygame, 440 | Numeric can be picked up if present. 441 | 442 | * Check for all mixed-case cases of matching audio files (mp3, 443 | ogg) 444 | 445 | * Misc. tab/spacing fixes in "constant" definitions 446 | 447 | v0.3.1: 448 | 449 | * Added full-screen player mode (CDG and MPG) 450 | 451 | * Supports the latest WxPython (v2.6) 452 | 453 | * Improved CPU usage 454 | 455 | * Displays ZIP filename together with the internal song filename 456 | 457 | v0.3: 458 | 459 | * Added MIDI/KAR file support 460 | 461 | * CDG player now uses psyco for faster playback on low-end 462 | machines 463 | 464 | * Better handling of corrupt CDG rips 465 | 466 | * Minor changes to make it more OSX-friendly 467 | 468 | * Added facility for cancelling song database builds in PyKaraoke 469 | GUI 470 | 471 | v0.2.1: 472 | 473 | * Fixed colour cycling in the CDG player 474 | 475 | * Fixed transparent colours used in CDG files 476 | 477 | * Searches are optimised to handle thousands of CDG files very 478 | quickly 479 | 480 | * Fixed inaccurate right-clicking in the playlist on some systems 481 | 482 | * Fixed Windows drive icon 483 | 484 | * Fixed tree root issue on some Linux systems 485 | 486 | * Added more status messages to the status bars 487 | 488 | v0.2: 489 | 490 | * PyKaraoke can now be used on Windows (98/XP/2000) 491 | 492 | * Modified the playlist logic 493 | 494 | * Changes to work with pygame-1.6.2 495 | -------------------------------------------------------------------------------- /pycdgAux.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2010 Kelvin Lawson (kelvinl@users.sourceforge.net) 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | 18 | """ This module is the Python implementation of the auxiliary classes 19 | and functions which have been moved into C for performance reasons. 20 | On the off-chance a C compiler is not available or not reliable for 21 | some reason, you can use this implementation instead. """ 22 | 23 | import pygame 24 | try: 25 | import Numeric as N 26 | except ImportError: 27 | import numpy.oldnumeric as N 28 | 29 | # CDG Command Code 30 | CDG_COMMAND = 0x09 31 | 32 | # CDG Instruction Codes 33 | CDG_INST_MEMORY_PRESET = 1 34 | CDG_INST_BORDER_PRESET = 2 35 | CDG_INST_TILE_BLOCK = 6 36 | CDG_INST_SCROLL_PRESET = 20 37 | CDG_INST_SCROLL_COPY = 24 38 | CDG_INST_DEF_TRANSP_COL = 28 39 | CDG_INST_LOAD_COL_TBL_0_7 = 30 40 | CDG_INST_LOAD_COL_TBL_8_15 = 31 41 | CDG_INST_TILE_BLOCK_XOR = 38 42 | 43 | # Bitmask for all CDG fields 44 | CDG_MASK = 0x3F 45 | 46 | # This is the size of the display as defined by the CDG specification. 47 | # The pixels in this region can be painted, and scrolling operations 48 | # rotate through this number of pixels. 49 | CDG_FULL_WIDTH = 300 50 | CDG_FULL_HEIGHT = 216 51 | 52 | # This is the size of the array that we operate on. We add an 53 | # additional border on the right and bottom edge of 6 and 12 pixels, 54 | # respectively, to allow for display shifting. (It's not clear from 55 | # the spec which colour should be visible when the display is shifted 56 | # to the right or down. We say it should be the border colour.) 57 | 58 | # This is the size of the screen that is actually intended to be 59 | # visible. It is the center area of CDG_FULL. The remaining border 60 | # area surrounding it is not meant to be visible. 61 | CDG_DISPLAY_WIDTH = 288 62 | CDG_DISPLAY_HEIGHT = 192 63 | 64 | # Screen tile positions 65 | # The viewable area of the screen (288x192) is divided into 66 | # 24 tiles (6x4 of 49x51 each). This is used to only update 67 | # those tiles which have changed on every screen update, 68 | # thus reducing the CPU load of screen updates. A bitmask of 69 | # tiles requiring update is held in cdgPlayer.UpdatedTiles. 70 | # This stores each of the 4 columns in separate bytes, with 6 bits used 71 | # to represent the 6 rows. 72 | TILES_PER_ROW = 6 73 | TILES_PER_COL = 4 74 | TILE_WIDTH = CDG_DISPLAY_WIDTH / TILES_PER_ROW 75 | TILE_HEIGHT = CDG_DISPLAY_HEIGHT / TILES_PER_COL 76 | 77 | COLOUR_TABLE_SIZE = 16 78 | 79 | class CdgPacket: 80 | """ This class just represents a single 24-byte packet read from 81 | the CDG stream. It's not used outside this module. """ 82 | 83 | def __init__(self, packetData): 84 | self.command = packetData[0] 85 | self.instruction = packetData[1] 86 | #self.parityQ = packetData[2:4] 87 | self.data = packetData[4:20] 88 | #self.parity = packetData[20:24] 89 | 90 | class CdgPacketReader: 91 | """ This class does the all work of reading packets from the CDG 92 | file, and evaluating them to fill in pixels in a Numeric array. 93 | Its public interface is in five methods. """ 94 | 95 | # In this class, we are aggressive with the use of the leading 96 | # double underscore, Python's convention to indicate private 97 | # members. We do this to clearly delineate the private data and 98 | # methods from the public data and methods, since only the public 99 | # members are of interest to the C port of this class. (Though, 100 | # in practice, the C port follows this class structure quite 101 | # closely, including duplicating the private members.) 102 | 103 | def __init__(self, cdgData, mapperSurface): 104 | self.__cdgData = cdgData 105 | self.__cdgDataPos = 0 106 | 107 | # This is just for the purpose of mapping colors. 108 | self.__mapperSurface = mapperSurface 109 | 110 | self.Rewind() 111 | 112 | def Rewind(self): 113 | """ Rewinds the stream to the beginning, and resets all 114 | internal state in preparation for decoding the tiles 115 | again. """ 116 | 117 | self.__cdgDataPos = 0 118 | 119 | # Initialise the colour table. Set a default value for any 120 | # CDG files that don't actually load the colour table 121 | # before doing something with it. 122 | defaultColour = 0 123 | self.__cdgColourTable = [defaultColour] * COLOUR_TABLE_SIZE 124 | 125 | self.__justClearedColourIndex = -1 126 | self.__cdgPresetColourIndex = -1 127 | self.__cdgBorderColourIndex = -1 128 | # Support only one transparent colour 129 | self.__cdgTransparentColour = -1 130 | 131 | # These values are used to implement screen shifting. The CDG 132 | # specification allows the entire screen to be shifted, up to 133 | # 5 pixels right and 11 pixels down. This shift is persistent 134 | # until it is reset to a different value. In practice, this 135 | # is used in conjunction with scrolling (which always jumps in 136 | # integer blocks of 6x12 pixels) to perform 137 | # one-pixel-at-a-time scrolls. 138 | self.__hOffset = 0 139 | self.__vOffset = 0 140 | 141 | # Build a 306x228 array for the pixel indeces, including border area 142 | self.__cdgPixelColours = N.zeros((CDG_FULL_WIDTH, CDG_FULL_HEIGHT)) 143 | 144 | # Build a 306x228 array for the actual RGB values. This will 145 | # be changed by the various commands, and blitted to the 146 | # screen now and again. But the border area will not be 147 | # blitted, only the central 288x192 area. 148 | self.__cdgSurfarray = N.zeros((CDG_FULL_WIDTH, CDG_FULL_HEIGHT)) 149 | 150 | # Start with all tiles requiring update 151 | self.__updatedTiles = 0xFFFFFFFFL 152 | 153 | def MarkTilesDirty(self): 154 | """ Marks all the tiles dirty, so that the next call to 155 | GetDirtyTiles() will return the complete list of tiles. """ 156 | 157 | self.__updatedTiles = 0xFFFFFFFFL 158 | 159 | def GetDirtyTiles(self): 160 | """ Returns a list of (row, col) tuples, corresponding to all 161 | of the currently-dirty tiles. Then resets the list of dirty 162 | tiles to empty. """ 163 | 164 | tiles = [] 165 | if self.__updatedTiles != 0: 166 | for col in range(TILES_PER_COL): 167 | for row in range(TILES_PER_ROW): 168 | if (self.__updatedTiles & ((1 << row) << (col * 8))): 169 | tiles.append((row, col)) 170 | 171 | self.__updatedTiles = 0 172 | return tiles 173 | 174 | def GetBorderColour(self): 175 | """ Returns the current border colour, as a mapped integer 176 | ready to apply to the surface. Returns None if the border 177 | colour has not yet been specified by the CDG stream. """ 178 | 179 | if self.__cdgBorderColourIndex == -1: 180 | return None 181 | return self.__cdgColourTable[self.__cdgBorderColourIndex] 182 | 183 | def DoPackets(self, numPackets): 184 | """ Reads numPackets 24-byte packets from the CDG stream, and 185 | processes their instructions on the internal tables stored 186 | within this object. Returns True on success, or False when 187 | the end-of-file has been reached and no more packets can be 188 | processed.""" 189 | 190 | for i in range(numPackets): 191 | # Extract the nexxt packet 192 | packd = self.__getNextPacket() 193 | if not packd: 194 | # No more packets. Return False, but only if we 195 | # reached this condition on the first packet. 196 | return (i != 0) 197 | 198 | self.__cdgPacketProcess (packd) 199 | 200 | return True 201 | 202 | def FillTile(self, surface, row, col): 203 | """ Fills in the pixels on the indicated one-tile surface 204 | (which must be a TILE_WIDTH x TILE_HEIGHT sized surface) with 205 | the pixels from the indicated tile. """ 206 | 207 | # Calculate the row & column starts/ends 208 | row_start = 6 + self.__hOffset + (row * TILE_WIDTH) 209 | row_end = 6 + self.__hOffset + ((row + 1) * TILE_WIDTH) 210 | col_start = 12 + self.__vOffset + (col * TILE_HEIGHT) 211 | col_end = 12 + self.__vOffset + ((col + 1) * TILE_HEIGHT) 212 | pygame.surfarray.blit_array( \ 213 | surface, \ 214 | self.__cdgSurfarray[row_start:row_end, col_start:col_end]) 215 | 216 | 217 | # The remaining methods are all private; they are not part of the 218 | # public interface. 219 | 220 | # Read the next CDG command from the file (24 bytes each) 221 | def __getNextPacket(self): 222 | packetData = map(ord, self.__cdgData[self.__cdgDataPos : self.__cdgDataPos + 24]) 223 | self.__cdgDataPos += 24 224 | if (len(packetData) == 24): 225 | return CdgPacket(packetData) 226 | else: 227 | self.__cdgDataPos = len(self.__cdgData) 228 | return None 229 | 230 | # Decode and perform the CDG commands in the indicated packet. 231 | def __cdgPacketProcess (self, packd): 232 | if (packd.command & CDG_MASK) == CDG_COMMAND: 233 | inst_code = (packd.instruction & CDG_MASK) 234 | if inst_code == CDG_INST_MEMORY_PRESET: 235 | self.__cdgMemoryPreset (packd) 236 | elif inst_code == CDG_INST_BORDER_PRESET: 237 | self.__cdgBorderPreset (packd) 238 | elif inst_code == CDG_INST_TILE_BLOCK: 239 | self.__cdgTileBlockCommon(packd, xor = 0) 240 | elif inst_code == CDG_INST_SCROLL_PRESET: 241 | self.__cdgScrollPreset (packd) 242 | elif inst_code == CDG_INST_SCROLL_COPY: 243 | self.__cdgScrollCopy (packd) 244 | elif inst_code == CDG_INST_DEF_TRANSP_COL: 245 | self.__cdgDefineTransparentColour (packd) 246 | elif inst_code == CDG_INST_LOAD_COL_TBL_0_7: 247 | self.__cdgLoadColourTableCommon (packd, 0) 248 | elif inst_code == CDG_INST_LOAD_COL_TBL_8_15: 249 | self.__cdgLoadColourTableCommon (packd, 1) 250 | elif inst_code == CDG_INST_TILE_BLOCK_XOR: 251 | self.__cdgTileBlockCommon(packd, xor = 1) 252 | else: 253 | # Don't use the error popup, ignore the unsupported command 254 | ErrorString = "CDG file may be corrupt, cmd: " + str(inst_code) 255 | print (ErrorString) 256 | 257 | # Memory preset (clear the viewable area + border) 258 | def __cdgMemoryPreset (self, packd): 259 | colour = packd.data[0] & 0x0F 260 | repeat = packd.data[1] & 0x0F 261 | 262 | # The "repeat" flag is nonzero if this is a repeat of a 263 | # previously-appearing preset command. (Often a CDG will 264 | # issue several copies of this command in case one gets 265 | # corrupted.) 266 | 267 | # We could ignore the entire command if repeat is nonzero, but 268 | # our data stream is not 100% reliable, since it might have 269 | # come from a bad rip. So we should honor every preset 270 | # command; but we shouldn't waste CPU time clearing the screen 271 | # repeatedly, needlessly. So we store a flag indicating the 272 | # last color that we just cleared to, and don't bother 273 | # clearing again if it hasn't changed. 274 | 275 | if colour == self.__justClearedColourIndex: 276 | return 277 | self.__justClearedColourIndex = colour 278 | 279 | # Our new interpretation of CD+G Revealed is that memory preset 280 | # commands should also change the border 281 | self.__cdgPresetColourIndex = colour 282 | self.__cdgBorderColourIndex = self.__cdgPresetColourIndex 283 | 284 | # Note that this may be done before any load colour table 285 | # commands by some CDGs. So the load colour table itself 286 | # actual recalculates the RGB values for all pixels when 287 | # the colour table changes. 288 | 289 | # Set the border colour for every pixel. Must be stored in 290 | # the pixel colour table indeces array, as well as 291 | # the screen RGB surfarray. 292 | 293 | # NOTE: The preset area--that is, the visible area--starts at 294 | # (6, 12) and extends to pixel (294, 204). The border area is 295 | # the two stripes of 6 pixels on the left and right of the 296 | # screen, and the stripes of 12 pixels on the top and bottom 297 | # of the screen. 298 | 299 | # The most efficient way of setting the values in a Numeric 300 | # array, is to create a zero array and do addition on the 301 | # the border and preset slices. 302 | self.__cdgPixelColours = N.zeros([CDG_FULL_WIDTH, CDG_FULL_HEIGHT]) 303 | self.__cdgPixelColours[:,:] = self.__cdgPixelColours[:,:] + colour 304 | 305 | # Now set the border and preset colour in our local surfarray. 306 | # This will be blitted next time there is a screen update. 307 | self.__cdgSurfarray = N.zeros([CDG_FULL_WIDTH, CDG_FULL_HEIGHT]) 308 | self.__cdgSurfarray[:,:] = self.__cdgSurfarray[:,:] + self.__cdgColourTable[colour] 309 | 310 | self.__updatedTiles = 0xFFFFFFFFL 311 | 312 | # Border Preset (clear the border area only) 313 | def __cdgBorderPreset (self, packd): 314 | colour = packd.data[0] & 0x0F 315 | if colour == self.__cdgBorderColourIndex: 316 | return 317 | 318 | self.__cdgBorderColourIndex = colour 319 | 320 | # See cdgMemoryPreset() for a description of what's going on. 321 | # In this case we are only clearing the border area. 322 | 323 | # Set up the border area of the pixel colours array 324 | self.__cdgPixelColours[:,:12] = N.zeros([CDG_FULL_WIDTH, 12]) 325 | self.__cdgPixelColours[:,:12] = self.__cdgPixelColours[:,:12] + self.__cdgBorderColourIndex 326 | self.__cdgPixelColours[:,-12:] = N.zeros([CDG_FULL_WIDTH, 12]) 327 | self.__cdgPixelColours[:,-12:] = self.__cdgPixelColours[:,-12:] + self.__cdgBorderColourIndex 328 | self.__cdgPixelColours[:6,12:-12] = N.zeros([6, CDG_FULL_HEIGHT - 24]) 329 | self.__cdgPixelColours[:6,12:-12] = self.__cdgPixelColours[:6,12:-12] + self.__cdgBorderColourIndex 330 | self.__cdgPixelColours[-6:,12:-12] = N.zeros([6, CDG_FULL_HEIGHT - 24]) 331 | self.__cdgPixelColours[-6:,12:-12] = self.__cdgPixelColours[-6:,12:-12] + self.__cdgBorderColourIndex 332 | 333 | # Now that we have set the PixelColours, apply them to 334 | # the Surfarray. 335 | lookupTable = N.array(self.__cdgColourTable) 336 | self.__cdgSurfarray.flat[:] = N.take(lookupTable, N.ravel(self.__cdgPixelColours)) 337 | 338 | return 339 | 340 | # CDG Scroll Command - Set the scrolled in area with a fresh colour 341 | def __cdgScrollPreset (self, packd): 342 | self.__cdgScrollCommon (packd, copy = False) 343 | return 344 | 345 | # CDG Scroll Command - Wrap the scrolled out area into the opposite side 346 | def __cdgScrollCopy (self, packd): 347 | self.__cdgScrollCommon (packd, copy = True) 348 | return 349 | 350 | # Common function to handle the actual pixel scroll for Copy and Preset 351 | def __cdgScrollCommon (self, packd, copy): 352 | 353 | # Decode the scroll command parameters 354 | data_block = packd.data 355 | colour = data_block[0] & 0x0F 356 | hScroll = data_block[1] & 0x3F 357 | vScroll = data_block[2] & 0x3F 358 | hSCmd = (hScroll & 0x30) >> 4 359 | hOffset = (hScroll & 0x07) 360 | vSCmd = (vScroll & 0x30) >> 4 361 | vOffset = (vScroll & 0x0F) 362 | 363 | # Scroll Vertical - Calculate number of pixels 364 | vScrollUpPixels = 0 365 | vScrollDownPixels = 0 366 | if vSCmd == 2: 367 | vScrollUpPixels = 12 368 | elif vSCmd == 1: 369 | vScrollDownPixels = 12 370 | 371 | # Scroll Horizontal- Calculate number of pixels 372 | hScrollLeftPixels = 0 373 | hScrollRightPixels = 0 374 | if hSCmd == 2: 375 | hScrollLeftPixels = 6 376 | elif hSCmd == 1: 377 | hScrollRightPixels = 6 378 | 379 | if hOffset != self.__hOffset or vOffset != self.__vOffset: 380 | # Changing the screen shift. 381 | self.__hOffset = min(hOffset, 5) 382 | self.__vOffset = min(vOffset, 11) 383 | self.__updatedTiles = 0xFFFFFFFFL 384 | 385 | if hScrollLeftPixels == 0 and \ 386 | hScrollRightPixels == 0 and \ 387 | vScrollUpPixels == 0 and \ 388 | vScrollDownPixels == 0: 389 | # Never mind. 390 | return 391 | 392 | # Perform the actual scroll. Use surfarray and slicing to make 393 | # this efficient. A copy scroll (where the data scrolls round) 394 | # can be achieved by slicing and concatenating again. 395 | # For non-copy, the new slice is filled in with a new colour. 396 | if (copy == True): 397 | if (vScrollUpPixels > 0): 398 | self.__cdgPixelColours = N.concatenate((self.__cdgPixelColours[:,vScrollUpPixels:], self.__cdgPixelColours[:,:vScrollUpPixels]), 1) 399 | elif (vScrollDownPixels > 0): 400 | self.__cdgPixelColours = N.concatenate((self.__cdgPixelColours[:,-vScrollDownPixels:], self.__cdgPixelColours[:,:-vScrollDownPixels]), 1) 401 | elif (hScrollLeftPixels > 0): 402 | self.__cdgPixelColours = N.concatenate((self.__cdgPixelColours[hScrollLeftPixels:,:], self.__cdgPixelColours[:hScrollLeftPixels,:]), 0) 403 | elif (hScrollRightPixels > 0): 404 | self.__cdgPixelColours = N.concatenate((self.__cdgPixelColours[-hScrollRightPixels:,:], self.__cdgPixelColours[:-hScrollRightPixels,:]), 0) 405 | elif (copy == False): 406 | if (vScrollUpPixels > 0): 407 | copyBlockActualColour = N.zeros([CDG_FULL_WIDTH,vScrollUpPixels]) + self.__cdgColourTable[colour] 408 | copyBlockColourIndex = N.zeros([CDG_FULL_WIDTH,vScrollUpPixels]) + colour 409 | self.__cdgPixelColours = N.concatenate((self.__cdgPixelColours[:,vScrollUpPixels:], copyBlockColourIndex), 1) 410 | elif (vScrollDownPixels > 0): 411 | copyBlockActualColour = N.zeros([CDG_FULL_WIDTH,vScrollDownPixels]) + self.__cdgColourTable[colour] 412 | copyBlockColourIndex = N.zeros([CDG_FULL_WIDTH,vScrollDownPixels]) + colour 413 | self.__cdgPixelColours = N.concatenate((copyBlockColourIndex, self.__cdgPixelColours[:,:-vScrollDownPixels]), 1) 414 | elif (hScrollLeftPixels > 0): 415 | copyBlockActualColour = N.zeros([hScrollLeftPixels, CDG_FULL_HEIGHT]) + self.__cdgColourTable[colour] 416 | copyBlockColourIndex = N.zeros([hScrollLeftPixels, CDG_FULL_HEIGHT]) + colour 417 | self.__cdgPixelColours = N.concatenate((self.__cdgPixelColours[hScrollLeftPixels:,:], copyBlockColourIndex), 0) 418 | elif (hScrollRightPixels > 0): 419 | copyBlockActualColour = N.zeros([hScrollRightPixels, CDG_FULL_HEIGHT]) + self.__cdgColourTable[colour] 420 | copyBlockColourIndex = N.zeros([hScrollRightPixels, CDG_FULL_HEIGHT]) + colour 421 | self.__cdgPixelColours = N.concatenate((copyBlockColourIndex, self.__cdgPixelColours[:-hScrollRightPixels,:]), 0) 422 | 423 | # Now that we have scrolled the PixelColours, apply them to 424 | # the Surfarray. 425 | 426 | lookupTable = N.array(self.__cdgColourTable) 427 | self.__cdgSurfarray.flat[:] = N.take(lookupTable, N.ravel(self.__cdgPixelColours)) 428 | 429 | # We have modified our local cdgSurfarray. This will be blitted to 430 | # the screen by cdgDisplayUpdate() 431 | self.__updatedTiles = 0xFFFFFFFFL 432 | 433 | # Set one of the colour indeces as transparent. Don't actually do anything with this 434 | # at the moment, as there is currently no mechanism for overlaying onto a movie file. 435 | def __cdgDefineTransparentColour (self, packd): 436 | data_block = packd.data 437 | colour = data_block[0] & 0x0F 438 | self.__cdgTransparentColour = colour 439 | return 440 | 441 | # Load the RGB value for colours 0..7 or 8..15 in the lookup table 442 | def __cdgLoadColourTableCommon (self, packd, table): 443 | if table == 0: 444 | colourTableStart = 0 445 | else: 446 | colourTableStart = 8 447 | for i in range(8): 448 | colourEntry = ((packd.data[2 * i] & CDG_MASK) << 8) 449 | colourEntry = colourEntry + (packd.data[(2 * i) + 1] & CDG_MASK) 450 | colourEntry = ((colourEntry & 0x3F00) >> 2) | (colourEntry & 0x003F) 451 | red = ((colourEntry & 0x0F00) >> 8) * 17 452 | green = ((colourEntry & 0x00F0) >> 4) * 17 453 | blue = ((colourEntry & 0x000F)) * 17 454 | self.__cdgColourTable[i + colourTableStart] = self.__mapperSurface.map_rgb(red, green, blue) 455 | # Redraw the entire screen using the new colour table. We still use the 456 | # same colour indeces (0 to 15) at each pixel but these may translate to 457 | # new RGB colours. This handles CDGs that preset the screen before actually 458 | # loading the colour table. It is done in our local RGB surfarray. 459 | 460 | # Do this with the Numeric module operation take() which can replace all 461 | # values in an array by alternatives from a lookup table. This is ideal as 462 | # we already have an array of colour indeces (0 to 15). We can create a 463 | # new RGB surfarray from that by doing take() which translates the 0-15 464 | # into an RGB colour and stores them in the RGB surfarray. 465 | lookupTable = N.array(self.__cdgColourTable) 466 | self.__cdgSurfarray.flat[:] = N.take(lookupTable, N.ravel(self.__cdgPixelColours)) 467 | 468 | # An alternative way of doing the above - was found to be very slightly slower. 469 | #self.__cdgSurfarray.flat[:] = map(self.__cdgColourTable.__getitem__, self.__cdgPixelColours.flat) 470 | 471 | # Update the screen for any colour changes 472 | self.__updatedTiles = 0xFFFFFFFFL 473 | return 474 | 475 | # Set the colours for a 12x6 tile. The main CDG command for display data 476 | def __cdgTileBlockCommon(self, packd, xor): 477 | # Decode the command parameters 478 | data_block = packd.data 479 | if data_block[1] & 0x20: 480 | # I don't know why, but some disks seem to stick an extra 481 | # bit here to mean "ignore this command". 482 | return 483 | 484 | colour0 = data_block[0] & 0x0F 485 | colour1 = data_block[1] & 0x0F 486 | 487 | column_index = ((data_block[2] & 0x1F) * 12) 488 | row_index = ((data_block[3] & 0x3F) * 6) 489 | 490 | # Sanity check the x,y offset read from the CDG in case a 491 | # corrupted CDG sends us outside of our array bounds 492 | if (column_index > (CDG_FULL_HEIGHT - 12)): 493 | column_index = (CDG_FULL_HEIGHT - 12) 494 | if (row_index > (CDG_FULL_WIDTH - 6)): 495 | row_index = (CDG_FULL_WIDTH - 6) 496 | 497 | # Set the tile update bitmasks. 498 | # Note that the screen update area only covers the non-border area 499 | # excluding the left 6 columns, and top 12 rows. Therefore when 500 | # calculating whether this block fits into a particular tile, we 501 | # add 6 or 12 to the x,y positions. Note also that each tile is 6 502 | # wide and 12 high, so if a block starts less than 6 columns to the 503 | # left of a block, it will incorporate the adjacent block. Similarly 504 | # any update starting less than 12 rows above a block, will also 505 | # incorporate the block below. 506 | 507 | firstRow = max((row_index - 6 - self.__hOffset) / TILE_WIDTH, 0) 508 | lastRow = (row_index - 1 - self.__hOffset) / TILE_WIDTH 509 | 510 | firstCol = max((column_index - 12 - self.__vOffset) / TILE_HEIGHT, 0) 511 | lastCol = (column_index - 1 - self.__vOffset) / TILE_HEIGHT 512 | 513 | for col in range(firstCol, lastCol + 1): 514 | for row in range(firstRow, lastRow + 1): 515 | self.__updatedTiles |= ((1 << row) << (col * 8)) 516 | 517 | # Set the pixel array for each of the pixels in the 12x6 tile. 518 | # Normal = Set the colour to either colour0 or colour1 depending 519 | # on whether the pixel value is 0 or 1. 520 | # XOR = XOR the colour with the colour index currently there. 521 | for i in range (12): 522 | byte = (data_block[4 + i] & 0x3F) 523 | for j in range (6): 524 | pixel = (byte >> (5 - j)) & 0x01 525 | if xor: 526 | # Tile Block XOR 527 | if (pixel == 0): 528 | xor_col = colour0 529 | else: 530 | xor_col = colour1 531 | # Get the colour index currently at this location, and xor with it 532 | currentColourIndex = self.__cdgPixelColours[(row_index + j), (column_index + i)] 533 | new_col = currentColourIndex ^ xor_col 534 | else: 535 | # Tile Block Normal 536 | if (pixel == 0): 537 | new_col = colour0 538 | else: 539 | new_col = colour1 540 | # Set the pixel with the new colour. We set both the surfarray 541 | # containing actual RGB values, as well as our array containing 542 | # the colour indeces into our colour table. 543 | self.__cdgSurfarray[(row_index + j), (column_index + i)] = self.__cdgColourTable[new_col] 544 | self.__cdgPixelColours[(row_index + j), (column_index + i)] = new_col 545 | 546 | # Now the screen has some data on it, so a subsequent clear 547 | # should be respected. 548 | self.__justClearedColourIndex = -1 549 | 550 | # The changes to cdgSurfarray will be blitted on the next screen update 551 | return 552 | -------------------------------------------------------------------------------- /pycdg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # pycdg - CDG/MP3+G Karaoke Player 4 | 5 | # Copyright (C) 2010 Kelvin Lawson (kelvinl@users.sourceforge.net) 6 | # 7 | # This library is free software; you can redistribute it and/or 8 | # modify it under the terms of the GNU Lesser General Public 9 | # License as published by the Free Software Foundation; either 10 | # version 2.1 of the License, or (at your option) any later version. 11 | # 12 | # This library is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # Lesser General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public 18 | # License along with this library; if not, write to the Free Software 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | 21 | 22 | # OVERVIEW 23 | # 24 | # pycdg is a CDG karaoke player which supports MP3+G and OGG+G tracks. 25 | # 26 | # The player uses the pygame library (www.pygame.org), and can therefore 27 | # run on any operating system that runs pygame (currently Linux, Windows 28 | # and OSX). 29 | # 30 | # You can use this file as a standalone player, or together with 31 | # PyKaraoke. PyKaraoke provides a graphical user interface, playlists, 32 | # searchable song database etc. 33 | # 34 | # For those writing a media player or similar project who would like 35 | # CDG support, this module has been designed to be easily incorporated 36 | # into such projects and is released under the LGPL. 37 | 38 | 39 | # REQUIREMENTS 40 | # 41 | # pycdg requires the following to be installed on your system: 42 | # . Python (www.python.org) 43 | # . Pygame (www.pygame.org) 44 | 45 | # . Numeric module (numpy.sourceforge.net) (actually, this is required 46 | # only if you do not use the compiled C low-level CDG implementation in 47 | # _pycdgAux.c) 48 | 49 | 50 | # USAGE INSTRUCTIONS 51 | # 52 | # To start the player, pass the CDG filename/path on the command line: 53 | # python pycdg.py /songs/theboxer.cdg 54 | # 55 | # You can also incorporate a CDG player in your own projects by 56 | # importing this module. The class cdgPlayer is exported by the 57 | # module. You can import and start it as follows: 58 | # import pycdg 59 | # player = pycdg.cdgPlayer("/songs/theboxer.cdg") 60 | # player.Play() 61 | # If you do this, you must also arrange to call pycdg.manager.Poll() 62 | # from time to time, at least every 100 milliseconds or so, to allow 63 | # the player to do its work. 64 | # 65 | # The class also exports Close(), Pause(), Rewind(), GetPos(). 66 | # 67 | # There are two optional parameters to the initialiser, errorNotifyCallback 68 | # and doneCallback: 69 | # 70 | # errorNotifyCallback, if provided, will be used to print out any error 71 | # messages (e.g. song file not found). This allows the module to fit 72 | # together well with GUI playlist managers by utilising the same GUI's 73 | # error popup window mechanism (or similar). If no callback is provided, 74 | # errors are printed to stdout. errorNotifyCallback should take one 75 | # parameter, the error string, e.g.: 76 | # def errorPopup (ErrorString): 77 | # msgBox (ErrorString) 78 | # 79 | # doneCallback can be used to register a callback so that the player 80 | # calls you back when the song is finished playing. The callback should 81 | # take no parameters, e.g.: 82 | # def songFinishedCallback(): 83 | # msgBox ("Song is finished") 84 | # 85 | # To register callbacks, pass the functions in to the initialiser: 86 | # cdgPlayer ("/songs/theboxer.cdg", errorPopup, songFinishedCallback) 87 | # These parameters are optional and default to None. 88 | # 89 | # If the initialiser fails (e.g. the song file is not present), __init__ 90 | # raises an exception. 91 | 92 | 93 | # IMPLEMENTATION DETAILS 94 | # 95 | 96 | # pycdg is implemented as a handful of python modules. All of the CDG 97 | # decoding is handled in the C module _pycdgAux.c, or in the 98 | # equivalent (but slightly slower) pycdgAux.py if the C module is not 99 | # available for some reason. This Python implementation of 100 | # pycdgAux.py uses the python Numeric module, which provides fast 101 | # handling of the arrays of pixel data for the display. 102 | # 103 | # Audio playback and video display capabilities come from the pygame 104 | # library. 105 | # 106 | # All of the information on the CDG file format was learned 107 | # from the fabulous "CDG Revealed" tutorial at www.jbum.com. 108 | # 109 | 110 | # Previous implementations ran the player within a thread; this is no 111 | # longer the case. Instead, it is the caller's responsibility to call 112 | # pycdg.manager.Poll() every once in a while to ensure that the player 113 | # gets enough CPU time to do its work. Ideally, this should be at 114 | # least every 100 milliseconds or so to guarantee good video and audio 115 | # response time. 116 | # 117 | # At each call to Poll(), the player checks the current time in the 118 | # song. It reads the CDG file at the correct location for the current 119 | # position of the song, and decodes the CDG commands stored there. If 120 | # the CDG command requires a screen update, a local array of pixels is 121 | # updated to reflect the new graphic information. Rather than update 122 | # directly to the screen for every command, updates are cached and 123 | # output to the screen a certain number of times per second 124 | # (configurable). Performing the scaling and blitting required for 125 | # screen updates might consume a lot of CPU horsepower, so we reduce 126 | # the load further by dividing the screen into 24 segments. Only those 127 | # segments that have changed are scaled and blitted. If the user 128 | # resizes the window or we get a full-screen modification, the entire 129 | # screen is updated, but during normal CD+G operation only a small 130 | # number of segments are likely to be changed at update time. 131 | # 132 | # Here follows a description of the important data stored by 133 | # the class: 134 | # 135 | # CdgPacketReader.__cdgColourTable[] 136 | # Store the colours for each colour index (0-15). 137 | # These are set using the load colour look up table commands. 138 | # 139 | # CdgPacketReader.__cdgSurfarray[300][216] 140 | # Surfarray object containing pixel colours for the full 300x216 screen. 141 | # The border area is not actually displayed on the screen, however we 142 | # need to store the pixel colours there as they are set when Scroll 143 | # commands are used. This stores the actual pygame colour value, not 144 | # indeces into our colour table. 145 | # 146 | # CdgPacketReader.__cdgPixelColours[300][216] 147 | # Store the colour index for every single pixel. The values stored 148 | # are indeces into our colour table, rather than actual pygame 149 | # colour representations. It's unfortunate that we need to store 150 | # all this data, when in fact the pixel colour is available from 151 | # cdgSurfarray, but we need it for the Tile Block XOR command. 152 | # The XOR command performs an XOR of the colour index currently 153 | # at the pixel, with the new colour index. We therefore need to 154 | # know the actual colour index at that pixel - we can't do a 155 | # get_at() on the screen, or look in cdgSurfarray, and map the RGB 156 | # colour back to a colour index because some CDG files have the 157 | # same colour in two places in the table, making it impossible to 158 | # determine which index is relevant for the XOR. 159 | # 160 | # CdgPacketReader.__cdgPresetColourIndex 161 | # Preset Colour (index into colour table) 162 | # 163 | # CdgPacketReader.__cdgPresetColourIndex 164 | # Border Colour (index into colour table) 165 | # 166 | # CdgPacketReader.__updatedTiles 167 | # Bitmask to mark which screen segments have been updated. 168 | # This is used to reduce the amount of effort required in 169 | # scaling the output video. This is an expensive operation 170 | # which must be done for every screen update so we divide 171 | # the screen into 24 segments and only update those segments 172 | # which have actually been updated. 173 | 174 | from pykconstants import * 175 | from pykplayer import pykPlayer 176 | from pykenv import env 177 | from pykmanager import manager 178 | import sys, pygame, os, string, math 179 | 180 | # Import the optimised C version if available, or fall back to Python 181 | try: 182 | import _pycdgAux as aux_c 183 | except ImportError: 184 | aux_c = None 185 | 186 | try: 187 | import pycdgAux as aux_python 188 | except ImportError: 189 | aux_python = None 190 | 191 | CDG_DISPLAY_WIDTH = 288 192 | CDG_DISPLAY_HEIGHT = 192 193 | 194 | # Screen tile positions 195 | # The viewable area of the screen (294x204) is divided into 24 tiles 196 | # (6x4 of 49x51 each). This is used to only update those tiles which 197 | # have changed on every screen update, thus reducing the CPU load of 198 | # screen updates. A bitmask of tiles requiring update is held in 199 | # cdgPlayer.UpdatedTiles. This stores each of the 4 columns in 200 | # separate bytes, with 6 bits used to represent the 6 rows. 201 | TILES_PER_ROW = 6 202 | TILES_PER_COL = 4 203 | TILE_WIDTH = CDG_DISPLAY_WIDTH / TILES_PER_ROW 204 | TILE_HEIGHT = CDG_DISPLAY_HEIGHT / TILES_PER_COL 205 | 206 | # cdgPlayer Class 207 | class cdgPlayer(pykPlayer): 208 | # Initialise the player instace 209 | def __init__(self, song, songDb, errorNotifyCallback=None, 210 | doneCallback=None): 211 | """The first parameter, song, may be either a pykdb.SongStruct 212 | instance, or it may be a filename. """ 213 | 214 | pykPlayer.__init__(self, song, songDb, errorNotifyCallback, doneCallback) 215 | 216 | # With the nomusic option no music will be played. 217 | soundFileData = None 218 | if not manager.options.nomusic: 219 | # Check for a matching mp3 or ogg file. Check extensions 220 | # in the following order. 221 | validexts = [ 222 | '.wav', '.ogg', '.mp3' 223 | ] 224 | 225 | for ext in validexts: 226 | for data in self.SongDatas: 227 | if data.Ext == ext: 228 | # Found a match! 229 | soundFileData = data 230 | break 231 | if soundFileData: 232 | break 233 | 234 | if not soundFileData: 235 | ErrorString = "There is no mp3 or ogg file to match " + self.Song.DisplayFilename 236 | self.ErrorNotifyCallback (ErrorString) 237 | raise 'NoSoundFile' 238 | 239 | self.cdgFileData = self.SongDatas[0] 240 | self.soundFileData = soundFileData 241 | self.soundLength = 0 242 | 243 | # Handle a bug in pygame (pre-1.7) which means that the position 244 | # timer carries on even when the song has been paused. 245 | self.pauseOffsetTime = 0 246 | 247 | manager.InitPlayer(self) 248 | manager.OpenDisplay() 249 | manager.surface.fill((0, 0, 0)) 250 | 251 | # A working surface for blitting tiles, one at a time. 252 | self.workingTile = pygame.Surface((TILE_WIDTH, TILE_HEIGHT), 253 | 0, manager.surface) 254 | 255 | # A surface that contains the set of all tiles as they are to 256 | # be assembled onscreen. This surface is kept at the original 257 | # scale, then zoomed to display size. It is only used if 258 | # settings.CdgZoom == 'soft'. 259 | self.workingSurface = pygame.Surface((CDG_DISPLAY_WIDTH, CDG_DISPLAY_HEIGHT), 260 | pygame.HWSURFACE, 261 | manager.surface) 262 | 263 | self.borderColour = None 264 | self.computeDisplaySize() 265 | 266 | aux = aux_c 267 | if not aux or not manager.settings.CdgUseC: 268 | print "Using Python implementation of CDG interpreter." 269 | aux = aux_python 270 | 271 | # Open the cdg and sound files 272 | self.packetReader = aux.CdgPacketReader(self.cdgFileData.GetData(), self.workingTile) 273 | manager.setCpuSpeed('cdg') 274 | 275 | if self.soundFileData: 276 | # Play the music normally. 277 | audioProperties = None 278 | if manager.settings.UseMp3Settings: 279 | audioProperties = self.getAudioProperties(self.soundFileData) 280 | if audioProperties == None: 281 | audioProperties = (None, None, None) 282 | try: 283 | manager.OpenAudio(*audioProperties) 284 | audio_path = self.soundFileData.GetFilepath() 285 | if type(audio_path) == unicode: 286 | audio_path = audio_path.encode(sys.getfilesystemencoding()) 287 | pygame.mixer.music.load(audio_path) 288 | except: 289 | self.Close() 290 | raise 291 | 292 | # Set an event for when the music finishes playing 293 | pygame.mixer.music.set_endevent(pygame.USEREVENT) 294 | 295 | # Account for the size of the playback buffer in the lyrics 296 | # display. Assume that the buffer will be mostly full. On a 297 | # slower computer that's struggling to keep up, this may not 298 | # be the right amount of delay, but it should usually be 299 | # pretty close. 300 | self.InternalOffsetTime = -manager.GetAudioBufferMS() 301 | 302 | else: 303 | # Don't play anything. 304 | self.InternalOffsetTime = 0 305 | 306 | # Set the CDG file at the beginning 307 | self.cdgReadPackets = 0 308 | self.cdgPacketsDue = 0 309 | self.LastPos = self.curr_pos = 0 310 | 311 | # Some session-wide constants. 312 | self.ms_per_update = (1000.0 / manager.options.fps) 313 | 314 | def doPlay(self): 315 | if self.soundFileData: 316 | pygame.mixer.music.play() 317 | 318 | # Pause the song - Use Pause() again to unpause 319 | def doPause(self): 320 | if self.soundFileData: 321 | pygame.mixer.music.pause() 322 | self.PauseStartTime = self.GetPos() 323 | 324 | def doUnpause(self): 325 | if self.soundFileData: 326 | self.pauseOffsetTime = self.pauseOffsetTime + (self.GetPos() - self.PauseStartTime) 327 | pygame.mixer.music.unpause() 328 | 329 | # you must call Play() to restart. Blocks until pygame is initialised 330 | def doRewind(self): 331 | # Reset the state of the packet-reading thread 332 | self.cdgReadPackets = 0 333 | self.cdgPacketsDue = 0 334 | self.LastPos = 0 335 | # No need for the Pause() fix anymore 336 | self.pauseOffsetTime = 0 337 | # Move file pointer to the beginning of the file 338 | self.packetReader.Rewind() 339 | 340 | if self.soundFileData: 341 | # Actually stop the audio 342 | pygame.mixer.music.rewind() 343 | pygame.mixer.music.stop() 344 | 345 | def GetLength(self): 346 | """Give the number of seconds in the song.""" 347 | return self.soundLength 348 | 349 | # Get the current time (in milliseconds). Blocks if pygame is 350 | # not initialised yet. 351 | def GetPos(self): 352 | if self.soundFileData: 353 | return pygame.mixer.music.get_pos() 354 | else: 355 | return pykPlayer.GetPos(self) 356 | 357 | def SetupOptions(self): 358 | """ Initialise and return optparse OptionParser object, 359 | suitable for parsing the command line options to this 360 | application. """ 361 | 362 | parser = pykPlayer.SetupOptions(self, usage = "%prog [options] ") 363 | 364 | # Remove irrelevant options. 365 | parser.remove_option('--font-scale') 366 | 367 | return parser 368 | 369 | 370 | def shutdown(self): 371 | # This will be called by the pykManager to shut down the thing 372 | # immediately. 373 | if self.soundFileData: 374 | if manager.audioProps: 375 | pygame.mixer.music.stop() 376 | 377 | # Make sure our surfaces are deallocated before we call up to 378 | # CloseDisplay(), otherwise bad things can happen. 379 | self.workingSurface = None 380 | self.workingTile = None 381 | self.packetReader = None 382 | pykPlayer.shutdown(self) 383 | 384 | def doStuff(self): 385 | pykPlayer.doStuff(self) 386 | 387 | # Check whether the songfile has moved on, if so 388 | # get the relevant CDG data and update the screen. 389 | if self.State == STATE_PLAYING or self.State == STATE_CAPTURING: 390 | self.curr_pos = self.GetPos() + self.InternalOffsetTime + manager.settings.SyncDelayMs - self.pauseOffsetTime 391 | 392 | self.cdgPacketsDue = int((self.curr_pos * 300) / 1000) 393 | numPackets = self.cdgPacketsDue - self.cdgReadPackets 394 | if numPackets > 0: 395 | if not self.packetReader.DoPackets(numPackets): 396 | # End of file. 397 | #print "End of file on cdg." 398 | self.Close() 399 | self.cdgReadPackets += numPackets 400 | 401 | # Check if any screen updates are now due. 402 | if (self.curr_pos - self.LastPos) > self.ms_per_update: 403 | self.cdgDisplayUpdate() 404 | self.LastPos = self.curr_pos 405 | 406 | def handleEvent(self, event): 407 | if event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN and (event.mod & (pygame.KMOD_LSHIFT | pygame.KMOD_RSHIFT | pygame.KMOD_LMETA | pygame.KMOD_RMETA)): 408 | # Shift/meta return: start/stop song. Useful for keybinding apps. 409 | self.Close() 410 | return 411 | 412 | pykPlayer.handleEvent(self, event) 413 | 414 | def doResize(self, newSize): 415 | self.computeDisplaySize() 416 | 417 | if self.borderColour != None: 418 | manager.surface.fill(self.borderColour) 419 | 420 | self.packetReader.MarkTilesDirty() 421 | 422 | def computeDisplaySize(self): 423 | """ Figures out what scale and placement to use for blitting 424 | tiles to the screen. This must be called at startup, and 425 | whenever the window size changes. """ 426 | 427 | winWidth, winHeight = manager.displaySize 428 | 429 | # Compute an appropriate uniform scale to letterbox the image 430 | # within the window 431 | scale = min(float(winWidth) / CDG_DISPLAY_WIDTH, 432 | float(winHeight) / CDG_DISPLAY_HEIGHT) 433 | if manager.settings.CdgZoom == 'none': 434 | scale = 1 435 | elif manager.settings.CdgZoom == 'int': 436 | if scale < 1: 437 | scale = 1.0/math.ceil(1.0/scale) 438 | else: 439 | scale = int(scale) 440 | self.displayScale = scale 441 | 442 | scaledWidth = int(scale * CDG_DISPLAY_WIDTH) 443 | scaledHeight = int(scale * CDG_DISPLAY_HEIGHT) 444 | 445 | if manager.settings.CdgZoom == 'full': 446 | # If we are allowing non-proportional scaling, allow 447 | # scaledWidth and scaledHeight to be independent. 448 | scaledWidth = winWidth 449 | scaledHeight = winHeight 450 | 451 | # And the center of the display after letterboxing. 452 | self.displayRowOffset = (winWidth - scaledWidth) / 2 453 | self.displayColOffset = (winHeight - scaledHeight) / 2 454 | 455 | # Calculate the scaled width and height for each tile 456 | if manager.settings.CdgZoom == 'soft': 457 | self.displayTileWidth = CDG_DISPLAY_WIDTH / TILES_PER_ROW 458 | self.displayTileHeight = CDG_DISPLAY_HEIGHT / TILES_PER_COL 459 | else: 460 | self.displayTileWidth = scaledWidth / TILES_PER_ROW 461 | self.displayTileHeight = scaledHeight / TILES_PER_COL 462 | 463 | def getAudioProperties(self, soundFileData): 464 | """ Attempts to determine the samplerate, etc., from the 465 | specified filename. It would be nice to know this so we can 466 | configure the audio hardware to the same properties, to 467 | minimize run-time resampling. """ 468 | 469 | # Ideally, SDL would tell us this (since it knows!), but 470 | # SDL_mixer doesn't provide an interface to query this 471 | # information, so we have to open the soundfile separately and 472 | # try to figure it out ourselves. 473 | 474 | audioProperties = None 475 | if soundFileData.Ext == '.mp3': 476 | audioProperties = self.getMp3AudioProperties(soundFileData) 477 | 478 | return audioProperties 479 | 480 | def getMp3AudioProperties(self, soundFileData): 481 | """ Attempts to determine the samplerate, etc., from the 482 | specified filename, which is known to be an mp3 file. """ 483 | 484 | # Hopefully we have Mutagen available to pull out the song length 485 | try: 486 | import mutagen.mp3 487 | except: 488 | print "Mutagen not available, will not be able to determine extra MP3 information." 489 | self.soundLength = 0 490 | return None 491 | 492 | # Open the file with mutagen 493 | m = mutagen.mp3.MP3(soundFileData.GetFilepath()) 494 | 495 | # Pull out the song length 496 | self.soundLength = m.info.length 497 | 498 | # Get the number of channels, mode field of 00 or 01 indicate stereo 499 | if m.info.mode < 2: 500 | channels = 2 501 | else: 502 | channels = 1 503 | 504 | # Put the channels and sample rate together in a tuple and return 505 | audioProperties = (m.info.sample_rate, -16, channels) 506 | return audioProperties 507 | 508 | # Actually update/refresh the video output 509 | def cdgDisplayUpdate(self): 510 | # This routine is responsible for taking the unscaled output 511 | # pixel data from self.cdgSurfarray, scaling it and blitting 512 | # it to the actual display surface. The viewable area of the 513 | # unscaled surface is 294x204 pixels. Because scaling and 514 | # blitting are heavy operations, we divide the screen into 24 515 | # tiles, and only scale and blit those tiles which have been 516 | # updated recently. The CdgPacketReader class 517 | # (self.packetReader) is responsible for keeping track of 518 | # which areas of the screen have been modified. 519 | 520 | # There are four different approaches for blitting tiles onto 521 | # the display: 522 | 523 | # settings.CdgZoom == 'none': 524 | # No scaling. The CDG graphics are centered within the 525 | # display. When a tile is dirty, it is blitted directly to 526 | # manager.surface. After all dirty tiles have been blitted, 527 | # we then use display.update to flip only those rectangles 528 | # on the screen that have been blitted. 529 | 530 | # settings.CdgZoom = 'quick': 531 | # Trivial scaling. Similar to 'none', but each tile is 532 | # first scaled to its target scale using 533 | # pygame.transform.scale(), which is quick but gives a 534 | # pixelly result. The scaled tile is then blitted to 535 | # manager.surface. 536 | 537 | # settings.CdgZoom = 'int': 538 | # The same as 'quick', but the scaling is constrained to be 539 | # an integer multiple or divisor of its original size, which 540 | # may reduce artifacts somewhat. 541 | 542 | # settings.CdgZoom = 'full': 543 | # The same as 'quick', but the scaling is allowed to 544 | # completely fill the window in both x and y, regardless of 545 | # aspect ratio constraints. 546 | 547 | # settings.CdgZoom = 'soft': 548 | # Antialiased scaling. We blit all tiles onto 549 | # self.workingSurface, which is maintained as the non-scaled 550 | # version of the CDG graphics, similar to 'none'. Then, 551 | # after all dirty tiles have been blitted to 552 | # self.workingSurface, we use pygame.transform.rotozoom() to 553 | # make a nice, antialiased scaling of workingSurface to 554 | # manager.surface, and then flip the whole display. (We 555 | # can't scale and blit the tiles one a time in this mode, 556 | # since that introduces artifacts between the tile edges.) 557 | borderColour = self.packetReader.GetBorderColour() 558 | if borderColour != self.borderColour: 559 | # When the border colour changes, blit the whole screen 560 | # and redraw it. 561 | self.borderColour = borderColour 562 | if borderColour != None: 563 | manager.surface.fill(borderColour) 564 | self.packetReader.MarkTilesDirty() 565 | 566 | dirtyTiles = self.packetReader.GetDirtyTiles() 567 | if not dirtyTiles: 568 | # If no tiles are dirty, don't bother. 569 | return 570 | 571 | # List of update rectangles (in scaled output window) 572 | rect_list = [] 573 | 574 | # Scale and blit only those tiles which have been updated 575 | for row, col in dirtyTiles: 576 | self.packetReader.FillTile(self.workingTile, row, col) 577 | 578 | if manager.settings.CdgZoom == 'none': 579 | # The no-scale approach. 580 | rect = pygame.Rect(self.displayTileWidth * row + self.displayRowOffset, 581 | self.displayTileHeight * col + self.displayColOffset, 582 | self.displayTileWidth, self.displayTileHeight) 583 | manager.surface.blit(self.workingTile, rect) 584 | rect_list.append(rect) 585 | 586 | elif manager.settings.CdgZoom == 'soft': 587 | # The soft-scale approach. 588 | self.workingSurface.blit(self.workingTile, (self.displayTileWidth * row, self.displayTileHeight * col)) 589 | 590 | else: 591 | # The quick-scale approach. 592 | scaled = pygame.transform.scale(self.workingTile, (self.displayTileWidth,self.displayTileHeight)) 593 | rect = pygame.Rect(self.displayTileWidth * row + self.displayRowOffset, 594 | self.displayTileHeight * col + self.displayColOffset, 595 | self.displayTileWidth, self.displayTileHeight) 596 | manager.surface.blit(scaled, rect) 597 | rect_list.append(rect) 598 | 599 | if manager.settings.CdgZoom == 'soft': 600 | # Now scale and blit the whole screen. 601 | scaled = pygame.transform.rotozoom(self.workingSurface, 0, self.displayScale) 602 | manager.surface.blit(scaled, (self.displayRowOffset, self.displayColOffset)) 603 | manager.Flip() 604 | elif len(rect_list) < 24: 605 | # Only update those areas which have changed 606 | if manager.display: 607 | pygame.display.update(rect_list) 608 | else: 609 | manager.Flip() 610 | 611 | def defaultErrorPrint(ErrorString): 612 | print (ErrorString) 613 | 614 | # Can be called from the command line with the CDG filepath as parameter 615 | def main(): 616 | player = cdgPlayer(None, None) 617 | player.Play() 618 | manager.WaitForPlayer() 619 | 620 | if __name__ == "__main__": 621 | sys.exit(main()) 622 | #import profile 623 | #result = profile.run('main()', 'pycdg.prof') 624 | #sys.exit(result) 625 | 626 | -------------------------------------------------------------------------------- /pykmanager.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | #**** **** 3 | #**** Copyright (C) 2010 Kelvin Lawson (kelvinl@users.sourceforge.net) **** 4 | #**** Copyright (C) 2010 PyKaraoke Development Team **** 5 | #**** **** 6 | #**** This library is free software; you can redistribute it and/or **** 7 | #**** modify it under the terms of the GNU Lesser General Public **** 8 | #**** License as published by the Free Software Foundation; either **** 9 | #**** version 2.1 of the License, or (at your option) any later version. **** 10 | #**** **** 11 | #**** This library is distributed in the hope that it will be useful, **** 12 | #**** but WITHOUT ANY WARRANTY; without even the implied warranty of **** 13 | #**** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU **** 14 | #**** Lesser General Public License for more details. **** 15 | #**** **** 16 | #**** You should have received a copy of the GNU Lesser General Public **** 17 | #**** License along with this library; if not, write to the **** 18 | #**** Free Software Foundation, Inc. **** 19 | #**** 59 Temple Place, Suite 330 **** 20 | #**** Boston, MA 02111-1307 USA **** 21 | #****************************************************************************** 22 | 23 | from pykconstants import * 24 | from pykenv import env 25 | import pykversion 26 | import pygame 27 | import os 28 | import sys 29 | 30 | # Python 2.3 and newer ship with optparse; older Python releases need "Optik" 31 | # installed (optik.sourceforge.net) 32 | try: 33 | import optparse 34 | except: 35 | import Optik as optparse 36 | 37 | if env == ENV_GP2X: 38 | import _cpuctrl as cpuctrl 39 | 40 | class pykManager: 41 | 42 | """ There is only one instance of this class in existence during 43 | program execution, and it is never destructed until program 44 | termination. This class manages the pygame interface, keeping 45 | interfaces open or closed as necessary; it also provides callbacks 46 | into handling pygame events. """ 47 | 48 | def __init__(self): 49 | self.initialized = False 50 | self.player = None 51 | self.options = None 52 | self.display = None 53 | self.surface = None 54 | self.audioProps = None 55 | 56 | self.displaySize = None 57 | self.displayFlags = 0 58 | self.displayDepth = 0 59 | self.cpuSpeed = None 60 | 61 | # Find the correct font path. If fully installed on Linux this 62 | # will be sys.prefix/share/pykaraoke/fonts. Otherwise look for 63 | # it in the current directory. 64 | if (os.path.isfile("fonts/DejaVuSans.ttf")): 65 | self.FontPath = "fonts" 66 | self.IconPath = "icons" 67 | else: 68 | self.FontPath = os.path.join(sys.prefix, "share/pykaraoke/fonts") 69 | self.IconPath = os.path.join(sys.prefix, "share/pykaraoke/icons") 70 | 71 | if env == ENV_GP2X: 72 | speed = cpuctrl.get_FCLK() 73 | print "Initial CPU speed is %s" % (speed) 74 | x, y, tvout = cpuctrl.get_screen_info() 75 | print "Initial screen size is %s, %s" % (x, y) 76 | if tvout: 77 | print "TV-Out mode is enabled." 78 | 79 | # This factor may be changed by the user to make text bigger 80 | # or smaller on those players that support it. 81 | self.fontScale = None 82 | 83 | def setCpuSpeed(self, activityName): 84 | """ Sets the CPU speed appropriately according to what the 85 | current activity is. At the moment, this is used only for the 86 | GP2X. """ 87 | 88 | if self.cpuSpeed == activityName: 89 | # No change. 90 | return 91 | self.cpuSpeed = activityName 92 | 93 | # The activityName directly hooks into a CPU speed indicated 94 | # in the user settings. 95 | 96 | attr = 'CPUSpeed_%s' % (activityName) 97 | speed = getattr(self.settings, attr, None) 98 | if speed is not None: 99 | self.OpenCPUControl() 100 | if env == ENV_GP2X: 101 | cpuctrl.set_FCLK(speed) 102 | pass 103 | 104 | def VolumeUp(self): 105 | try: 106 | volume = pygame.mixer.music.get_volume() 107 | except pygame.error: 108 | print "Failed to raise music volume!" 109 | return 110 | volume = min(volume + 0.1, 1.0) 111 | 112 | pygame.mixer.music.set_volume(volume) 113 | 114 | def VolumeDown(self): 115 | try: 116 | volume = pygame.mixer.music.get_volume() 117 | except pygame.error: 118 | print "Failed to lower music volume!" 119 | return 120 | volume = max(volume - 0.1, 0.0) 121 | 122 | pygame.mixer.music.set_volume(volume) 123 | 124 | def GetVolume(self): 125 | """ Gives the current volume level. """ 126 | if vars().has_key('music'): 127 | return pygame.mixer.music.get_volume() 128 | else: 129 | return 0.50 # 75% is the industry recommended maximum value 130 | 131 | def SetVolume(self, volume): 132 | """ Sets the volume of the music playback. """ 133 | volume = min(volume, 1.0) 134 | volume = max(volume, 0.0) 135 | pygame.mixer.music.set_volume(volume) 136 | 137 | def GetFontScale(self): 138 | """ Returns the current font scale. """ 139 | if self.fontScale == None: 140 | self.fontScale = self.options.font_scale 141 | return self.fontScale 142 | 143 | def ZoomFont(self, factor): 144 | """ Zooms the font scale by the indicated factor. This is 145 | treated like a resize event, even though the window is not 146 | changing size; player.doResize() will be called. """ 147 | self.GetFontScale() 148 | self.fontScale *= factor 149 | if self.player: 150 | self.player.doResize(self.displaySize) 151 | 152 | def InitPlayer(self, player): 153 | 154 | """ A pykPlayer will call this when it constructs. This 155 | registers the player with the pykManager, so that it will get 156 | callbacks and control of the display. This call also ensures 157 | that pygame has been initialized. """ 158 | 159 | if self.player: 160 | self.player.shutdown() 161 | self.player = None 162 | 163 | # Ensure we have been initialized. 164 | if not self.initialized: 165 | self.pygame_init() 166 | 167 | self.player = player 168 | self.player.State = STATE_NOT_PLAYING 169 | 170 | if self.display != None and self.displayTitle == None: 171 | try: 172 | pygame.display.set_caption(player.WindowTitle) 173 | except UnicodeError: 174 | pygame.display.set_caption(player.WindowTitle.encode('UTF-8', 'replace')) 175 | 176 | 177 | def OpenDisplay(self, displaySize = None, flags = None, depth = None): 178 | """ Use this method to open a pygame display or set the 179 | display to a specific mode. """ 180 | 181 | self.getDisplayDefaults() 182 | 183 | if displaySize == None: 184 | displaySize = self.displaySize 185 | if flags == None: 186 | flags = self.displayFlags 187 | if depth == None: 188 | depth = self.displayDepth 189 | 190 | if self.options.dump: 191 | # We're just capturing frames offscreen. In that case, 192 | # just open an offscreen buffer as the "display". 193 | self.display = None 194 | self.surface = pygame.Surface(self.displaySize) 195 | self.mouseVisible = False 196 | self.displaySize = self.surface.get_size() 197 | self.displayFlags = self.surface.get_flags() 198 | self.displayDepth = self.surface.get_bitsize() 199 | else: 200 | # Open the onscreen display normally. 201 | pygame.display.init() 202 | 203 | self.mouseVisible = not (env == ENV_GP2X or self.options.hide_mouse or (self.displayFlags & pygame.FULLSCREEN)) 204 | pygame.mouse.set_visible(self.mouseVisible) 205 | 206 | if self.displayTitle != None: 207 | pygame.display.set_caption(self.displayTitle) 208 | elif self.player != None: 209 | try: 210 | pygame.display.set_caption(self.player.WindowTitle) 211 | except UnicodeError: 212 | pygame.display.set_caption(self.player.WindowTitle.encode('UTF-8', 'replace')) 213 | 214 | if self.display == None or \ 215 | (self.displaySize, self.displayFlags, self.displayDepth) != (displaySize, flags, depth): 216 | self.display = pygame.display.set_mode(displaySize, flags, depth) 217 | self.displaySize = self.display.get_size() 218 | self.displayFlags = flags 219 | self.displayDepth = depth 220 | 221 | self.surface = self.display 222 | 223 | self.displayTime = pygame.time.get_ticks() 224 | 225 | def Flip(self): 226 | """ Call this method to make the displayed frame visible. """ 227 | if self.display: 228 | pygame.display.flip() 229 | 230 | def CloseDisplay(self): 231 | """ Use this method to close the pygame window if it has been 232 | opened. """ 233 | 234 | if self.display: 235 | pygame.display.quit() 236 | pygame.display.init() 237 | self.display = None 238 | 239 | self.surface = None 240 | 241 | def OpenAudio(self, frequency = None, size = None, channels = None): 242 | """ Use this method to initialize or change the audio 243 | parameters.""" 244 | 245 | # We shouldn't mess with the CPU control while the audio is 246 | # open. 247 | self.CloseCPUControl() 248 | 249 | if frequency == None: 250 | frequency = self.settings.SampleRate 251 | 252 | if size == None: 253 | size = -16 254 | 255 | if channels == None: 256 | channels = self.settings.NumChannels 257 | 258 | bufferMs = self.settings.BufferMs 259 | 260 | # Compute the number of samples that would fill the indicated 261 | # buffer time. 262 | bufferSamples = bufferMs * (frequency * channels) / 1000 263 | 264 | # This needs to be a power of 2, so find the first power of 2 265 | # larger, up to 2^15. 266 | p = 1 267 | while p < bufferSamples and p < 32768: 268 | p <<= 1 269 | # Now choose the power of 2 closest. 270 | 271 | if (abs(bufferSamples - (p >> 1)) < abs(p - bufferSamples)): 272 | bufferSamples = p >> 1 273 | else: 274 | bufferSamples = p 275 | 276 | audioProps = (frequency, size, channels, bufferSamples) 277 | if audioProps != self.audioProps: 278 | # If the audio properties have changed, we have to shut 279 | # down and re-start the audio subsystem. 280 | pygame.mixer.quit() 281 | pygame.mixer.init(*audioProps) 282 | self.audioProps = audioProps 283 | 284 | def CloseAudio(self): 285 | pygame.mixer.quit() 286 | self.audioProps = None 287 | 288 | def OpenCPUControl(self): 289 | self.CloseAudio() 290 | if env == ENV_GP2X: 291 | cpuctrl.init() 292 | 293 | def CloseCPUControl(self): 294 | if env == ENV_GP2X: 295 | cpuctrl.shutdown() 296 | 297 | def GetAudioBufferMS(self): 298 | """ Returns the number of milliseconds it will take to 299 | completely empty a full audio buffer with the current 300 | settings. """ 301 | if self.audioProps: 302 | frequency, size, channels, bufferSamples = self.audioProps 303 | return bufferSamples * 1000 / (frequency * channels) 304 | return 0 305 | 306 | def Quit(self): 307 | if self.player: 308 | self.player.shutdown() 309 | self.player = None 310 | 311 | if not self.initialized: 312 | return 313 | self.initialized = False 314 | 315 | pygame.quit() 316 | 317 | def __errorCallback(self, message): 318 | self.songValid = False 319 | print message 320 | def __doneCallback(self): 321 | pass 322 | 323 | def ValidateDatabase(self, songDb): 324 | """ Validates all of the songs in the database, to ensure they 325 | are playable and contain lyrics. """ 326 | 327 | self.CloseDisplay() 328 | invalidFile = open('invalid.txt', 'w') 329 | 330 | songDb.SelectSort('filename') 331 | for song in songDb.SongList[:1074]: 332 | self.songValid = True 333 | player = song.MakePlayer(songDb, self.__errorCallback, self.__doneCallback) 334 | if not player: 335 | self.songValid = False 336 | else: 337 | if not player.Validate(): 338 | self.songValid = False 339 | 340 | if self.songValid: 341 | print '%s ok' % (song.DisplayFilename) 342 | else: 343 | print '%s invalid' % (song.DisplayFilename) 344 | print >> invalidFile, '%s\t%s' % (song.Filepath, song.ZipStoredName) 345 | invalidFile.flush() 346 | 347 | def Poll(self): 348 | """ Your application must call this method from time to 349 | time--ideally, within a hundred milliseconds or so--to perform 350 | the next quantum of activity. Alternatively, if the 351 | application does not require any cycles, you may just call 352 | WaitForPlayer() instead. """ 353 | 354 | if not self.initialized: 355 | self.pygame_init() 356 | 357 | self.handleEvents() 358 | 359 | if self.player: 360 | if self.player.State == STATE_CLOSED: 361 | self.player = None 362 | else: 363 | self.player.doStuff() 364 | 365 | # Wait a bit to save on wasteful CPU usage. 366 | pygame.time.wait(1) 367 | 368 | def WaitForPlayer(self): 369 | """ The interface may choose to call this method in lieu of 370 | repeatedly calling Poll(). It will block until the currently 371 | active player has finished, and then return. """ 372 | 373 | while self.player and self.player.State != STATE_CLOSED: 374 | self.Poll() 375 | 376 | def SetupOptions(self, usage, songDb): 377 | """ Initialise and return optparse OptionParser object, 378 | suitable for parsing the command line options to this 379 | application. This version of this method returns the options 380 | that are likely to be useful for any karaoke application. """ 381 | 382 | version = "%prog " + pykversion.PYKARAOKE_VERSION_STRING 383 | 384 | settings = songDb.Settings 385 | 386 | parser = optparse.OptionParser(usage = usage, version = version, 387 | conflict_handler = "resolve") 388 | 389 | if env != ENV_OSX and env != ENV_GP2X: 390 | pos_x = None 391 | pos_y = None 392 | if settings.PlayerPosition: 393 | pos_x, pos_y = settings.PlayerPosition 394 | parser.add_option('-x', '--window-x', dest = 'pos_x', type = 'int', metavar='X', 395 | help = 'position song window X pixels from the left edge of the screen', default = pos_x) 396 | parser.add_option('-y', '--window-y', dest = 'pos_y', type = 'int', metavar='Y', 397 | help = 'position song window Y pixels from the top edge of the screen', default = pos_y) 398 | 399 | if env != ENV_GP2X: 400 | parser.add_option('-w', '--width', dest = 'size_x', type = 'int', metavar='X', 401 | help = 'draw song window X pixels wide', default = settings.PlayerSize[0]) 402 | parser.add_option('-h', '--height', dest = 'size_y', type = 'int', metavar='Y', 403 | help = 'draw song window Y pixels high', default = settings.PlayerSize[1]) 404 | parser.add_option('-t', '--title', dest = 'title', type = 'string', metavar='TITLE', 405 | help = 'set song window title to TITLE', default = None) 406 | parser.add_option('-f', '--fullscreen', dest = 'fullscreen', action = 'store_true', 407 | help = 'make song window fullscreen', default = settings.FullScreen) 408 | parser.add_option('', '--hide-mouse', dest = 'hide_mouse', action = 'store_true', 409 | help = 'hide the mouse pointer', default = False) 410 | 411 | parser.add_option('-s', '--fps', dest = 'fps', metavar='N', type = 'int', 412 | help = 'restrict visual updates to N frames per second', 413 | default = 30) 414 | parser.add_option('-r', '--sample-rate', dest = 'sample_rate', type = 'int', 415 | help = 'specify the audio sample rate. Ideally, this should match the recording. For MIDI files, higher is better but consumes more CPU.', 416 | default = settings.SampleRate) 417 | parser.add_option('', '--num-channels', dest = 'num_channels', type = 'int', 418 | help = 'specify the number of audio channels: 1 for mono, 2 for stereo.', 419 | default = settings.NumChannels) 420 | parser.add_option('', '--font-scale', metavar='SCALE', dest = 'font_scale', type = 'float', 421 | help = 'specify the font scale factor; small numbers (between 0 and 1) make text smaller so more fits on the screen, while large numbers (greater than 1) make text larger so less fits on the screen.', 422 | default = 1) 423 | 424 | parser.add_option('', '--zoom', metavar='MODE', dest = 'zoom_mode', type = 'choice', 425 | choices = settings.Zoom, 426 | help = 'specify the way in which graphics are scaled to fit the window. The choices are %s.' % (', '.join(map(lambda z: '"%s"' % z, settings.Zoom))), 427 | default = settings.CdgZoom) 428 | 429 | parser.add_option('', '--buffer', dest = 'buffer', metavar = 'MS', type = 'int', 430 | help = 'buffer audio by the indicated number of milliseconds', 431 | default = settings.BufferMs) 432 | parser.add_option('-n', '--nomusic', dest = 'nomusic', action = 'store_true', 433 | help = 'disable music playback, just display graphics', default = False) 434 | 435 | parser.add_option('', '--dump', dest = 'dump', 436 | help = 'dump output as a sequence of frame images, for converting to video', 437 | default = '') 438 | parser.add_option('', '--dump-fps', dest = 'dump_fps', type = 'float', 439 | help = 'specify the number of frames per second of the sequence output by --dump', 440 | default = 29.97) 441 | 442 | parser.add_option('', '--validate', dest = 'validate', action = 'store_true', 443 | help = 'validate that all songs contain lyrics and are playable') 444 | 445 | return parser 446 | 447 | def ApplyOptions(self, songDb): 448 | """ Copies the user-specified command-line options in 449 | self.options to the settings in songDb.Settings. """ 450 | 451 | self.settings = songDb.Settings 452 | 453 | self.settings.CdgZoom = self.options.zoom_mode 454 | 455 | if hasattr(self.options, 'fullscreen'): 456 | self.settings.FullScreen = self.options.fullscreen 457 | self.settings.PlayerSize = (self.options.size_x, self.options.size_y) 458 | if hasattr(self.options, 'pos_x') and \ 459 | self.options.pos_x != None and self.options.pos_y != None: 460 | self.settings.PlayerPosition = (self.options.pos_x, self.options.pos_y) 461 | 462 | self.settings.NumChannels = self.options.num_channels 463 | self.settings.SampleRate = self.options.sample_rate 464 | self.settings.BufferMs = self.options.buffer 465 | 466 | def WordWrapText(self, text, font, maxWidth): 467 | """Folds the line (or lines) of text into as many lines as 468 | necessary to fit within the indicated width (when rendered by 469 | the given font), word-wrapping at spaces. Returns a list of 470 | strings, one string for each separate line.""" 471 | 472 | 473 | lines = [] 474 | 475 | for line in text.split('\n'): 476 | fold = self.FindFoldPoint(line, font, maxWidth) 477 | while line: 478 | lines.append(line[:fold]) 479 | line = line[fold:] 480 | fold = self.FindFoldPoint(line, font, maxWidth) 481 | 482 | return lines 483 | 484 | def FindFoldPoint(self, line, font, maxWidth): 485 | """Returns the index of the character within line which should 486 | begin the next line: the first non-space before maxWidth.""" 487 | 488 | if maxWidth <= 0 or line == '': 489 | return len(line) 490 | 491 | fold = len(line.rstrip()) 492 | width, height = font.size(line[:fold]) 493 | while fold > 0 and width > maxWidth: 494 | sp = line[:fold].rfind(' ') 495 | if sp == -1: 496 | fold -= 1 497 | else: 498 | fold = sp 499 | width, height = font.size(line[:fold]) 500 | 501 | while fold < len(line) and line[fold] == ' ': 502 | fold += 1 503 | 504 | if fold == 0: 505 | # Couldn't even get one character in. Put it in anyway. 506 | fold = 1 507 | 508 | if line[:fold].strip() == '': 509 | # Oops, nothing but whitespace in front of the fold. Try 510 | # again without the whitespace. 511 | ws = line[:fold] 512 | line = line[fold:] 513 | wsWidth, height = font.size(ws) 514 | return self.FindFoldPoint(line, font, maxWidth - wsWidth) + len(ws) 515 | 516 | return fold 517 | 518 | 519 | # The remaining methods are internal. 520 | 521 | def handleEvents(self): 522 | """ Handles the events returned from pygame. """ 523 | 524 | if self.display: 525 | # check for Pygame events 526 | for event in pygame.event.get(): 527 | self.handleEvent(event) 528 | 529 | 530 | def handleEvent(self, event): 531 | # Only handle resize events 250ms after opening the 532 | # window. This is to handle the bizarre problem of SDL making 533 | # the window small automatically if you set 534 | # SDL_VIDEO_WINDOW_POS and move the mouse around while the 535 | # window is opening. Give it some time to settle. 536 | player = self.player 537 | if event.type == pygame.VIDEORESIZE and pygame.time.get_ticks() - self.displayTime > 250: 538 | 539 | # Tell the player we are about to resize. This is required 540 | # for pympg. 541 | if player: 542 | player.doResizeBegin() 543 | 544 | # Do the resize 545 | self.displaySize = event.size 546 | self.settings.PlayerSize = tuple(self.displaySize) 547 | pygame.display.set_mode(event.size, self.displayFlags, self.displayDepth) 548 | # Call any player-specific resize 549 | if player: 550 | player.doResize(event.size) 551 | 552 | # Tell the player we have finished resizing 553 | if player: 554 | player.doResizeEnd() 555 | 556 | elif env == ENV_GP2X and event.type == pygame.JOYBUTTONDOWN: 557 | if event.button == GP2X_BUTTON_VOLUP: 558 | self.VolumeUp() 559 | elif event.button == GP2X_BUTTON_VOLDOWN: 560 | self.VolumeDown() 561 | 562 | if player: 563 | player.handleEvent(event) 564 | 565 | def pygame_init(self): 566 | """ This method is called only once, the first time an 567 | application requests a pygame window. """ 568 | 569 | pygame.init() 570 | 571 | if env == ENV_GP2X: 572 | num_joysticks = pygame.joystick.get_count() 573 | if num_joysticks > 0: 574 | stick = pygame.joystick.Joystick(0) 575 | stick.init() # now we will receive events for the GP2x joystick and buttons 576 | 577 | self.initialized = True 578 | 579 | def getDisplayDefaults(self): 580 | if env == ENV_GP2X: 581 | # The GP2x has no control over its window size or 582 | # placement. You'll get fullscreen and like it. 583 | 584 | # Unfortunately, it appears that pygame--or maybe our SDL, 585 | # even though we'd compiled with paeryn's HW SDL--doesn't 586 | # allow us to open a TV-size window, so we have to settle 587 | # for the standard (320, 240) size and let the hardware 588 | # zooming scale it for TV out. 589 | self.displaySize = (320, 240) 590 | self.displayFlags = pygame.HWSURFACE | pygame.FULLSCREEN 591 | self.displayDepth = 0 592 | self.displayTitle = None 593 | self.mouseVisible = False 594 | return 595 | 596 | # Fix the position at top-left of window. Note when doing 597 | # this, if the mouse was moving around as the window opened, 598 | # it made the window tiny. Have stopped doing anything for 599 | # resize events until 1sec into the song to work around 600 | # this. Note there appears to be no way to find out the 601 | # current window position, in order to bring up the next 602 | # window in the same place. Things seem to be different in 603 | # development versions of pygame-1.7 - it appears to remember 604 | # the position, and it is the only version for which fixing 605 | # the position works on MS Windows. 606 | 607 | # Don't set the environment variable on OSX. 608 | if env != ENV_OSX: 609 | if self.settings.PlayerPosition: 610 | x, y = self.settings.PlayerPosition 611 | os.environ['SDL_VIDEO_WINDOW_POS'] = "%s,%s" % (x, y) 612 | 613 | w, h = self.settings.PlayerSize 614 | self.displaySize = (w, h) 615 | 616 | self.displayFlags = pygame.RESIZABLE 617 | if self.settings.DoubleBuf: 618 | self.displayFlags |= pygame.DOUBLEBUF 619 | if self.settings.HardwareSurface: 620 | self.displayFlags |= pygame.HWSURFACE 621 | 622 | if self.settings.NoFrame: 623 | self.displayFlags |= pygame.NOFRAME 624 | if self.settings.FullScreen: 625 | self.displayFlags |= pygame.FULLSCREEN 626 | 627 | self.displayDepth = 0 628 | self.displayTitle = self.options.title 629 | 630 | self.mouseVisible = not (env == ENV_GP2X or self.options.hide_mouse or (self.displayFlags & pygame.FULLSCREEN)) 631 | 632 | 633 | # Now instantiate a global pykManager object. 634 | manager = pykManager() 635 | --------------------------------------------------------------------------------