From e9171da6b3926e83f95ca7c942c50770545c70c0 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Mon, 15 Nov 2010 22:57:40 -0500 Subject: [PATCH 01/26] Add a script to build the win32 dependency pack. --- .gitignore | 2 + win32/makedeps-cross.sh | 251 ++++++++++++++++++++++++++++++++++++++++ win32/makedist.sh | 11 ++ 3 files changed, 264 insertions(+) create mode 100755 win32/makedeps-cross.sh create mode 100755 win32/makedist.sh diff --git a/.gitignore b/.gitignore index 4216526d9..49b6e4c74 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ data/themes/* !data/themes/MegaLight GH3 !data/themes/MegaLight V4 !data/themes/Uberlight +win32/* +!win32/*.sh diff --git a/win32/makedeps-cross.sh b/win32/makedeps-cross.sh new file mode 100755 index 000000000..04db9d302 --- /dev/null +++ b/win32/makedeps-cross.sh @@ -0,0 +1,251 @@ +#!/bin/sh -e +# Script to cross-compile FoFiX's dependency libraries for Win32. +# (Derived from a similar script I wrote for Performous.) +# Copyright (C) 2010 John Stumpo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +die () { echo "$@" >&2 ; exit 1 ; } + +assert_binary_on_path () { + if which "$1" >/dev/null 2>&1; then + echo found program "$1" + else + echo did not find "$1", which is required + exit 1 + fi +} + +if test -z "$CROSS_TOOL_PREFIX"; then + export CROSS_TOOL_PREFIX=i586-mingw32msvc +fi +echo "Using cross compilers prefixed with '$CROSS_TOOL_PREFIX-'." +echo "(Set CROSS_TOOL_PREFIX to change this; don't include the trailing hyphen.)" +if test -z "$CROSS_GCC"; then + assert_binary_on_path "$CROSS_TOOL_PREFIX"-gcc + export CROSS_GCC="$CROSS_TOOL_PREFIX"-gcc + assert_binary_on_path "$CROSS_TOOL_PREFIX"-g++ + export CROSS_GXX="$CROSS_TOOL_PREFIX"-g++ + assert_binary_on_path "$CROSS_TOOL_PREFIX"-ar + export CROSS_AR="$CROSS_TOOL_PREFIX"-ar + assert_binary_on_path "$CROSS_TOOL_PREFIX"-ranlib + export CROSS_RANLIB="$CROSS_TOOL_PREFIX"-ranlib + assert_binary_on_path "$CROSS_TOOL_PREFIX"-ld + export CROSS_LD="$CROSS_TOOL_PREFIX"-ld + assert_binary_on_path "$CROSS_TOOL_PREFIX"-dlltool + export CROSS_DLLTOOL="$CROSS_TOOL_PREFIX"-dlltool + assert_binary_on_path "$CROSS_TOOL_PREFIX"-nm + export CROSS_NM="$CROSS_TOOL_PREFIX"-nm + assert_binary_on_path "$CROSS_TOOL_PREFIX"-windres + export CROSS_WINDRES="$CROSS_TOOL_PREFIX"-windres +fi +if test -z "$WINE"; then + assert_binary_on_path wine + export WINE=wine +fi +echo "wine: $WINE" + +assert_binary_on_path autoreconf +assert_binary_on_path libtoolize +assert_binary_on_path make +assert_binary_on_path pkg-config +assert_binary_on_path svn +assert_binary_on_path tar +assert_binary_on_path unzip +assert_binary_on_path wget + +SCRIPTDIR="`pwd`" +export PREFIX="`pwd`"/deps +export WINEPREFIX="`pwd`"/wine +mkdir -pv "$PREFIX"/bin "$PREFIX"/lib "$PREFIX"/include "$PREFIX"/lib/pkgconfig "$PREFIX"/build-stamps +if test -n "$KEEPTEMP"; then + RM_RF=true + echo 'Keeping the built source trees, as you requested.' +else + RM_RF="rm -rf" + echo 'Unpacked source trees will be removed after compilation.' + echo '(Set KEEPTEMP to any value to preserve them.)' +fi + +echo 'setting up wine environment' +$WINE reg add 'HKCU\Environment' /v PATH /d Z:"`echo "$PREFIX" | tr '/' '\\'`"\\bin + +echo 'creating pkg-config wrapper for cross-compiled environment' +cat >"$PREFIX"/bin/pkg-config <"$PREFIX"/bin/wine-shwrap <<"EOF" +#!/bin/sh -e +path="`(cd $(dirname "$1") && pwd)`/`basename "$1"`" +echo '#!/bin/bash -e' >"$1" +echo '$WINE '"$path"'.exe "$@" | tr -d '"'\\\015'" >>"$1" +echo 'exit ${PIPESTATUS[0]}' >>"$1" +chmod 0755 "$1" +EOF +chmod 0755 $PREFIX/bin/wine-shwrap + +export PATH="$PREFIX"/bin:"$PATH" + +download () { + basename="`basename "$1"`" + if test ! -f "$basename"; then + wget -c -O "$basename".part "$1" + mv -v "$basename".part "$basename" + fi +} + +# We use win-iconv instead of full-fledged GNU libiconv because it still does +# everything the other deps need and is far smaller. +WINICONV="win-iconv-0.0.2" +if test ! -f "$PREFIX"/build-stamps/win-iconv; then + download http://win-iconv.googlecode.com/files/$WINICONV.tar.bz2 + tar jxvf $WINICONV.tar.bz2 + cd $WINICONV + make clean + make -n iconv.dll win_iconv.exe | sed -e 's/^/$CROSS_TOOL_PREFIX-/' | sh -ex + $CROSS_GCC -mdll -o iconv.dll -Wl,--out-implib,libiconv.a iconv.def win_iconv.o + cp -v iconv.dll win_iconv.exe "$PREFIX"/bin + cp -v iconv.h "$PREFIX"/include + echo '' >>"$PREFIX"/include/iconv.h # squelch warnings about no newline at the end + sed -i -e 's://.*$::' "$PREFIX"/include/iconv.h # squelch warnings about C++ comments + cp -v libiconv.a "$PREFIX"/lib + cd .. + touch "$PREFIX"/build-stamps/win-iconv + $RM_RF $WINICONV +fi + +# zlib +ZLIB="zlib-1.2.5" +if test ! -f "$PREFIX"/build-stamps/zlib; then + download http://www.zlib.net/$ZLIB.tar.bz2 + tar jxvf $ZLIB.tar.bz2 + cd $ZLIB + make -f win32/Makefile.gcc PREFIX="$CROSS_TOOL_PREFIX"- zlib1.dll + cp -v zlib.h zconf.h "$PREFIX"/include + cp -v zlib1.dll "$PREFIX"/bin + cp -v libzdll.a "$PREFIX"/lib/libz.a + cd .. + touch "$PREFIX"/build-stamps/zlib + $RM_RF $ZLIB +fi + +# Flags passed to every dependency's ./configure script, for those deps that use autoconf and friends. +COMMON_AUTOCONF_FLAGS="--prefix=$PREFIX --host=$CROSS_TOOL_PREFIX --disable-static --enable-shared CPPFLAGS=-I$PREFIX/include LDFLAGS=-L$PREFIX/lib" + +# Runtime (libintl) of GNU Gettext +GETTEXT="gettext-0.18.1.1" +if test ! -f "$PREFIX"/build-stamps/gettext-runtime; then + download http://ftp.gnu.org/gnu/gettext/$GETTEXT.tar.gz + tar zxvf $GETTEXT.tar.gz + cd $GETTEXT/gettext-runtime + ./configure $COMMON_AUTOCONF_FLAGS --enable-relocatable --disable-libasprintf --disable-java --disable-csharp + make + make install + cd ../.. + touch "$PREFIX"/build-stamps/gettext-runtime + $RM_RF $GETTEXT +fi + +# GLib +GLIB="glib-2.26.1" +if test ! -f "$PREFIX"/build-stamps/glib; then + download http://ftp.gnome.org/pub/GNOME/sources/glib/2.26/$GLIB.tar.bz2 + tar jxvf $GLIB.tar.bz2 + cd $GLIB + ./configure $COMMON_AUTOCONF_FLAGS + make -C glib + make -C gthread + make -C gobject glib-genmarshal.exe + wine-shwrap gobject/glib-genmarshal + make + make install + cd .. + touch "$PREFIX"/build-stamps/glib + $RM_RF $GLIB +fi + +# pkg-config +PKGCONFIG="pkg-config-0.25" +if test ! -f "$PREFIX"/build-stamps/pkg-config; then + download http://pkgconfig.freedesktop.org/releases/$PKGCONFIG.tar.gz + tar zxvf $PKGCONFIG.tar.gz + cd $PKGCONFIG + ./configure $COMMON_AUTOCONF_FLAGS + make + make install + cd .. + touch "$PREFIX"/build-stamps/pkg-config + $RM_RF $PKGCONFIG +fi + +# The rest of GNU Gettext +if test ! -f "$PREFIX"/build-stamps/gettext; then + download http://ftp.gnu.org/gnu/gettext/$GETTEXT.tar.gz + tar zxvf $GETTEXT.tar.gz + cd $GETTEXT + ./configure $COMMON_AUTOCONF_FLAGS --enable-relocatable --disable-libasprintf --disable-java --disable-csharp CXX="$CROSS_GXX" + make + make install + cd .. + touch "$PREFIX"/build-stamps/gettext + $RM_RF $GETTEXT +fi + +# libogg +LIBOGG="libogg-1.2.1" +if test ! -f "$PREFIX"/build-stamps/libogg; then + download http://downloads.xiph.org/releases/ogg/$LIBOGG.tar.gz + tar zxvf $LIBOGG.tar.gz + cd $LIBOGG + libtoolize + autoreconf # fix buggy configure test for 16-bit types + ./configure $COMMON_AUTOCONF_FLAGS + make + make install + cd .. + touch "$PREFIX"/build-stamps/libogg + $RM_RF $LIBOGG +fi + +# libvorbis +LIBVORBIS="libvorbis-1.3.2" +if test ! -f "$PREFIX"/build-stamps/libvorbis; then + download http://downloads.xiph.org/releases/vorbis/$LIBVORBIS.tar.bz2 + tar jxvf $LIBVORBIS.tar.bz2 + cd $LIBVORBIS + ./configure $COMMON_AUTOCONF_FLAGS + make + make install + cd .. + touch "$PREFIX"/build-stamps/libvorbis + $RM_RF $LIBVORBIS +fi + +# libtheora +LIBTHEORA="libtheora-1.1.1" +if test ! -f "$PREFIX"/build-stamps/libtheora; then + download http://downloads.xiph.org/releases/theora/$LIBTHEORA.tar.bz2 + tar jxvf $LIBTHEORA.tar.bz2 + cd $LIBTHEORA + ./configure $COMMON_AUTOCONF_FLAGS + make + make install + cd .. + touch "$PREFIX"/build-stamps/libtheora + $RM_RF $LIBTHEORA +fi + +echo "All dependencies done." diff --git a/win32/makedist.sh b/win32/makedist.sh new file mode 100755 index 000000000..7000c0e06 --- /dev/null +++ b/win32/makedist.sh @@ -0,0 +1,11 @@ +#!/bin/sh -e + +mkdir -pv dist +cp -av deps dist + +rm -rf dist/deps/build-stamps dist/deps/etc dist/deps/share dist/deps/lib/gettext +rm -vf dist/deps/lib/*.def dist/deps/lib/*.la dist/deps/bin/wine-shwrap dist/deps/bin/pkg-config +i586-mingw32msvc-strip --strip-all dist/deps/bin/*.exe dist/deps/bin/*.dll +ZIPFILE="fofix-win32-deppack-`date +%Y%m%d`.zip" +rm -f "$ZIPFILE" +(cd dist && zip -9r ../"$ZIPFILE" deps) From e30ccb78ac342152b30236672b1427d0ab96e6ef Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Tue, 16 Nov 2010 13:32:49 -0500 Subject: [PATCH 02/26] Add functionality to setup.py for querying pkg-config and convert the cmgl build to use it. --- src/setup.py | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/src/setup.py b/src/setup.py index c8dd5cd13..fb4cca6bf 100755 --- a/src/setup.py +++ b/src/setup.py @@ -27,6 +27,8 @@ from Cython.Distutils import build_ext import sys, SceneFactory, Version, glob, os import numpy as np +import shlex +import subprocess # Start setting up py2{exe,app} and building the argument set for setup(). @@ -203,12 +205,122 @@ def isSystemDLL(pathname): } }) + +def find_command(cmd): + '''Find a program on the PATH, or, on win32, in the dependency pack.''' + + print 'checking for program %s...' % cmd, + + if os.name == 'nt': + # Only accept something from the dependency pack. + path = os.path.join('..', 'win32', 'deps', 'bin', cmd+'.exe') + else: + # Search the PATH. + path = None + for dir in os.environ['PATH'].split(os.pathsep): + if os.access(os.path.join(dir, cmd), os.X_OK): + path = os.path.join(dir, cmd) + break + + if path is None or not os.path.isfile(path): + print 'not found' + print >>sys.stderr, 'Could not find required program "%s".' % cmd + if os.name == 'nt': + print >>sys.stderr, '(Check that you have the latest version of the dependency pack installed.)' + sys.exit(1) + + print path + return path + + +# Find pkg-config so we can find the libraries we need. +pkg_config = find_command('pkg-config') + + +def pc_exists(pkg): + '''Check whether pkg-config thinks a library exists.''' + if os.spawnl(os.P_WAIT, pkg_config, 'pkg-config', '--exists', pkg) == 0: + return True + else: + return False + + +# {py26hack} - Python 2.7 has subprocess.check_output for this purpose. +def grab_stdout(*args, **kw): + '''Obtain standard output from a subprocess invocation, raising an exception + if the subprocess fails.''' + + kw['stdout'] = subprocess.PIPE + proc = subprocess.Popen(*args, **kw) + stdout = proc.communicate()[0] + if proc.returncode != 0: + raise RuntimeError, 'subprocess %r returned %d' % (args[0], proc.returncode) + return stdout + + +def pc_info(pkg): + '''Obtain build options for a library from pkg-config and + return a dict that can be expanded into the argument list for + L{distutils.core.Extension}.''' + + print 'checking for library %s...' % pkg, + if not pc_exists(pkg): + print 'not found' + print >>sys.stderr, 'Could not find required library "%s".' % pkg + if os.name == 'nt': + print >>sys.stderr, '(Check that you have the latest version of the dependency pack installed.)' + else: + print >>sys.stderr, '(Check that you have the appropriate development package installed.)' + sys.exit(1) + + cflags = shlex.split(grab_stdout([pkg_config, '--cflags', pkg])) + libs = shlex.split(grab_stdout([pkg_config, '--libs', pkg])) + + # Pick out anything interesting in the cflags and libs, and + # silently drop the rest. + info = { + 'include_dirs': [x[2:] for x in cflags if x[:2] == '-I'], + 'libraries': [x[2:] for x in libs if x[:2] == '-l'], + 'library_dirs': [x[2:] for x in libs if x[:2] == '-L'], + } + + print 'ok' + return info + + +if os.name == 'nt': + # Windows systems: we just know what the OpenGL library is. + gl_info = {'libraries': ['opengl32']} +else: + # Other systems: we ask pkg-config. + gl_info = pc_info('gl') +# Build a similar info record for the numpy headers. +numpy_info = {'include_dirs': [np.get_include()]} + + +def combine_info(*args): + '''Combine multiple result dicts from L{pc_info} into one.''' + + info = { + 'include_dirs': [], + 'libraries': [], + 'library_dirs': [], + } + + for a in args: + info['include_dirs'].extend(a.get('include_dirs', [])) + info['libraries'].extend(a.get('libraries', [])) + info['library_dirs'].extend(a.get('library_dirs', [])) + + return info + + # Add the common arguments to setup(). # This includes arguments to cause FoFiX's extension modules to be built. setup_args.update({ 'options': options, 'ext_modules': [ - Extension('cmgl', ['cmgl.pyx'], include_dirs=[np.get_include()], libraries=['opengl32' if os.name == 'nt' else 'GL']), + Extension('cmgl', ['cmgl.pyx'], **combine_info(numpy_info, gl_info)), Extension('pypitch._pypitch', language='c++', sources=['pypitch/_pypitch.pyx', 'pypitch/pitch.cpp', From 7be04bf79251ec8af1e41c4a8375605cd13090b2 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Wed, 24 Nov 2010 04:50:45 -0500 Subject: [PATCH 03/26] Add the new video player's dependency libraries to setup.py. --- src/setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/setup.py b/src/setup.py index fb4cca6bf..be017cfba 100755 --- a/src/setup.py +++ b/src/setup.py @@ -288,6 +288,9 @@ def pc_info(pkg): return info +ogg_info = pc_info('ogg') +theoradec_info = pc_info('theoradec') +glib_info = pc_info('glib-2.0') if os.name == 'nt': # Windows systems: we just know what the OpenGL library is. gl_info = {'libraries': ['opengl32']} From 530bb967c7ffa0c77692932ada5e621003019d88 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Wed, 24 Nov 2010 04:52:30 -0500 Subject: [PATCH 04/26] Implement the new video player up to initializing the Theora decoder. --- src/VideoPlayer.h | 39 ++++++++ src/VideoPlayerCore.c | 207 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 src/VideoPlayer.h create mode 100644 src/VideoPlayerCore.c diff --git a/src/VideoPlayer.h b/src/VideoPlayer.h new file mode 100644 index 000000000..7549bd401 --- /dev/null +++ b/src/VideoPlayer.h @@ -0,0 +1,39 @@ +/* Frets on Fire X (FoFiX) + * Copyright (C) 2010 Team FoFiX + * 2010 John Stumpo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#ifndef VIDEOPLAYER_H +#define VIDEOPLAYER_H + +#include + +typedef struct _VideoPlayer VideoPlayer; + +VideoPlayer* video_player_new(const char* filename, GError** err); +void video_player_destroy(VideoPlayer* player); + +GQuark video_player_error_quark(void); +#define VIDEO_PLAYER_ERROR video_player_error_quark() + +typedef enum { + VIDEO_PLAYER_NO_VIDEO, + VIDEO_PLAYER_BAD_HEADERS +} VideoPlayerError; + +#endif diff --git a/src/VideoPlayerCore.c b/src/VideoPlayerCore.c new file mode 100644 index 000000000..ef9f5c296 --- /dev/null +++ b/src/VideoPlayerCore.c @@ -0,0 +1,207 @@ +/* Frets on Fire X (FoFiX) + * Copyright (C) 2010 Team FoFiX + * 2010 John Stumpo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include "VideoPlayer.h" + +#include +#include + +#include +#include +#include +#include +#include + +struct _VideoPlayer { + int fd; + ogg_sync_state osync; + GHashTable* stream_table; + ogg_page current_page; + gboolean have_video; + ogg_stream_state* vstream; + th_info tinfo; + th_comment tcomment; + th_setup_info* tsetup; + gboolean eof; + th_dec_ctx* vdecoder; +}; + +static void destroy_stream(gpointer data) +{ + ogg_stream_clear(data); + g_free(data); +} + +static gboolean demux_next_page(VideoPlayer* player, GError** err) +{ + int serialno; + ogg_stream_state* ostream; + + /* Demux the next page into player->current_page. */ + while (ogg_sync_pageout(&player->osync, &player->current_page) != 1) { + char* buf = ogg_sync_buffer(&player->osync, 65536); + int bytes = read(player->fd, buf, 65536); + if (bytes == 0) { + player->eof = TRUE; + return TRUE; + } else if (bytes < 0) { + g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errno), + "Failed to read video: %s", g_strerror(errno)); + return FALSE; + } + ogg_sync_wrote(&player->osync, bytes); + } + + /* Dispatch it to the correct ogg_stream_state. */ + serialno = ogg_page_serialno(&player->current_page); + ostream = g_hash_table_lookup(player->stream_table, &serialno); + if (ostream != NULL) { + ogg_stream_pagein(ostream, &player->current_page); + } else if (ogg_page_bos(&player->current_page)) { + int* key = g_new(int, 1); + *key = serialno; + ostream = g_new(ogg_stream_state, 1); + ogg_stream_init(ostream, serialno); + g_hash_table_insert(player->stream_table, key, ostream); + ogg_stream_pagein(ostream, &player->current_page); + } + return TRUE; +} + +static gboolean demux_headers(VideoPlayer* player, GError** err) +{ + /* Go through the stream header pages, looking for one that starts a Theora stream. */ + while (demux_next_page(player, err)) { + /* If the page isn't a header, we're done this step. */ + if (player->eof || !ogg_page_bos(&player->current_page)) + goto got_all_headers; + + if (!player->have_video) { + /* Grab the first packet and check it for Theoraness. + Otherwise forget about the stream. */ + int header_status; + ogg_packet pkt; + + int serialno = ogg_page_serialno(&player->current_page); + ogg_stream_state* ostream = g_hash_table_lookup(player->stream_table, &serialno); + if (ogg_stream_packetout(ostream, &pkt) != 1) { + g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_HEADERS, + "Bad headers in video file."); + return FALSE; + } + + header_status = th_decode_headerin(&player->tinfo, &player->tcomment, &player->tsetup, &pkt); + if (header_status == TH_ENOTFORMAT) { + /* Forget the stream - it's not Theora. */ + g_hash_table_remove(player->stream_table, &serialno); + } else if (header_status < 0) { + g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_HEADERS, + "Bad headers in Theora stream."); + return FALSE; + } else { + player->have_video = TRUE; + player->vstream = ostream; + /* And keep looping through the header pages so we can throw out the other streams. */ + } + } else { + /* Throw it out - we already found the stream. */ + int serialno = ogg_page_serialno(&player->current_page); + g_hash_table_remove(player->stream_table, &serialno); + } + } + /* If we got here, demux_next_page exploded before we even finished the stream headers. */ + return FALSE; + +got_all_headers: + if (!player->have_video) { + g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_NO_VIDEO, + "Failed to find a Theora stream in the video file."); + return FALSE; + } + + /* Get the rest of the headers. */ + while (!player->eof) { + ogg_packet pkt; + while (ogg_stream_packetout(player->vstream, &pkt) == 1) { + int header_status = th_decode_headerin(&player->tinfo, &player->tcomment, &player->tsetup, &pkt); + if (header_status < 0) { + g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_HEADERS, + "Bad headers in Theora stream."); + return FALSE; + } else if (header_status == 0) { + /* We have everything we need to start decoding. */ + player->vdecoder = th_decode_alloc(&player->tinfo, player->tsetup); + return TRUE; + } + /* Otherwise, there are still more header packets needed. */ + } + if (!demux_next_page(player, err)) + return FALSE; + } + + g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_HEADERS, + "Failed to find all necessary Theora headers."); + return FALSE; +} + +VideoPlayer* video_player_new(const char* filename, GError** err) +{ + VideoPlayer* player = g_new0(VideoPlayer, 1); + + player->fd = open(filename, O_RDONLY); + if (player->fd < 0) { + g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errno), + "Failed to open video: %s", g_strerror(errno)); + g_free(player); + return NULL; + } +#ifdef _WIN32 + setmode(player->fd, O_BINARY); +#endif + + player->stream_table = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, destroy_stream); + ogg_sync_init(&player->osync); + th_info_init(&player->tinfo); + th_comment_init(&player->tcomment); + if (!demux_headers(player, err)) { + video_player_destroy(player); + return NULL; + } + return player; +} + +void video_player_destroy(VideoPlayer* player) +{ + if (player->vdecoder != NULL) + th_decode_free(player->vdecoder); + if (player->tsetup != NULL) + th_setup_free(player->tsetup); + th_comment_clear(&player->tcomment); + th_info_clear(&player->tinfo); + g_hash_table_destroy(player->stream_table); + ogg_sync_clear(&player->osync); + close(player->fd); + g_free(player); +} + +GQuark video_player_error_quark(void) +{ + return g_quark_from_static_string("video-player-error-quark"); +} From 7290c20a036bd6168fcaa403f8049292f5c5b4ec Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Wed, 24 Nov 2010 04:58:10 -0500 Subject: [PATCH 05/26] Use stdio instead of direct file descriptor manipulation to read the video. --- src/VideoPlayerCore.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/VideoPlayerCore.c b/src/VideoPlayerCore.c index ef9f5c296..d48df3004 100644 --- a/src/VideoPlayerCore.c +++ b/src/VideoPlayerCore.c @@ -24,13 +24,10 @@ #include #include -#include -#include -#include -#include +#include struct _VideoPlayer { - int fd; + FILE* file; ogg_sync_state osync; GHashTable* stream_table; ogg_page current_page; @@ -57,7 +54,7 @@ static gboolean demux_next_page(VideoPlayer* player, GError** err) /* Demux the next page into player->current_page. */ while (ogg_sync_pageout(&player->osync, &player->current_page) != 1) { char* buf = ogg_sync_buffer(&player->osync, 65536); - int bytes = read(player->fd, buf, 65536); + int bytes = fread(buf, 1, 65536, player->file); if (bytes == 0) { player->eof = TRUE; return TRUE; @@ -165,16 +162,13 @@ VideoPlayer* video_player_new(const char* filename, GError** err) { VideoPlayer* player = g_new0(VideoPlayer, 1); - player->fd = open(filename, O_RDONLY); - if (player->fd < 0) { + player->file = fopen(filename, "rb"); + if (player->file == NULL) { g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errno), "Failed to open video: %s", g_strerror(errno)); g_free(player); return NULL; } -#ifdef _WIN32 - setmode(player->fd, O_BINARY); -#endif player->stream_table = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, destroy_stream); ogg_sync_init(&player->osync); @@ -197,7 +191,7 @@ void video_player_destroy(VideoPlayer* player) th_info_clear(&player->tinfo); g_hash_table_destroy(player->stream_table); ogg_sync_clear(&player->osync); - close(player->fd); + fclose(player->file); g_free(player); } From f241ad12218628051ed7f6d1b05d0ddd3d8ab250 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Wed, 24 Nov 2010 22:44:01 -0500 Subject: [PATCH 06/26] Implement enough to get hackish black-and-white video. --- src/VideoPlayer.h | 8 ++++- src/VideoPlayerCore.c | 79 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/VideoPlayer.h b/src/VideoPlayer.h index 7549bd401..ae21711a5 100644 --- a/src/VideoPlayer.h +++ b/src/VideoPlayer.h @@ -28,12 +28,18 @@ typedef struct _VideoPlayer VideoPlayer; VideoPlayer* video_player_new(const char* filename, GError** err); void video_player_destroy(VideoPlayer* player); +void video_player_play(VideoPlayer* player); +void video_player_pause(VideoPlayer* player); + +gboolean video_player_bind_frame(VideoPlayer* player, GError** err); + GQuark video_player_error_quark(void); #define VIDEO_PLAYER_ERROR video_player_error_quark() typedef enum { VIDEO_PLAYER_NO_VIDEO, - VIDEO_PLAYER_BAD_HEADERS + VIDEO_PLAYER_BAD_HEADERS, + VIDEO_PLAYER_BAD_DATA } VideoPlayerError; #endif diff --git a/src/VideoPlayerCore.c b/src/VideoPlayerCore.c index d48df3004..6694d2851 100644 --- a/src/VideoPlayerCore.c +++ b/src/VideoPlayerCore.c @@ -20,6 +20,7 @@ #include "VideoPlayer.h" +#include #include #include @@ -38,6 +39,11 @@ struct _VideoPlayer { th_setup_info* tsetup; gboolean eof; th_dec_ctx* vdecoder; + gboolean playing; + glong playback_position; /* microseconds */ + GTimeVal playback_start_time; + GLuint video_texture; + ogg_int64_t decode_granpos; }; static void destroy_stream(gpointer data) @@ -145,6 +151,8 @@ static gboolean demux_headers(VideoPlayer* player, GError** err) } else if (header_status == 0) { /* We have everything we need to start decoding. */ player->vdecoder = th_decode_alloc(&player->tinfo, player->tsetup); + player->playing = FALSE; + player->playback_position = 0; return TRUE; } /* Otherwise, there are still more header packets needed. */ @@ -174,6 +182,7 @@ VideoPlayer* video_player_new(const char* filename, GError** err) ogg_sync_init(&player->osync); th_info_init(&player->tinfo); th_comment_init(&player->tcomment); + glGenTextures(1, &player->video_texture); if (!demux_headers(player, err)) { video_player_destroy(player); return NULL; @@ -187,6 +196,7 @@ void video_player_destroy(VideoPlayer* player) th_decode_free(player->vdecoder); if (player->tsetup != NULL) th_setup_free(player->tsetup); + glDeleteTextures(1, &player->video_texture); th_comment_clear(&player->tcomment); th_info_clear(&player->tinfo); g_hash_table_destroy(player->stream_table); @@ -195,6 +205,75 @@ void video_player_destroy(VideoPlayer* player) g_free(player); } +void video_player_play(VideoPlayer* player) +{ + g_get_current_time(&player->playback_start_time); + g_time_val_add(&player->playback_start_time, -player->playback_position); + player->playing = TRUE; +} + +void video_player_pause(VideoPlayer* player) +{ + player->playing = FALSE; +} + +gboolean video_player_bind_frame(VideoPlayer* player, GError** err) +{ + gboolean must_update_texture = FALSE; + th_ycbcr_buffer frame_buffer; + + glBindTexture(GL_TEXTURE_2D, player->video_texture); + + /* Advance the playback position if we're playing. */ + if (player->playing) { + GTimeVal now; + g_get_current_time(&now); + player->playback_position = (1000000 * (now.tv_sec - player->playback_start_time.tv_sec)) + (now.tv_usec - player->playback_start_time.tv_usec); + } + + while (th_granule_time(player->vdecoder, player->decode_granpos) * 1000000 < player->playback_position) { + ogg_packet pkt; + int decode_status; + + /* Get the next packet. */ + if (ogg_stream_packetout(player->vstream, &pkt) != 1) { + if (player->eof) { + video_player_pause(player); + break; + } + if (!demux_next_page(player, err)) + return FALSE; + continue; + } + + decode_status = th_decode_packetin(player->vdecoder, &pkt, &player->decode_granpos); + if (decode_status != 0 && decode_status != TH_DUPFRAME) { + g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_DATA, + "An error occurred decoding a Theora packet."); + return FALSE; + } + + if (decode_status == 0) { + th_decode_ycbcr_out(player->vdecoder, frame_buffer); + must_update_texture = TRUE; + } + } + + /* TODO: use swscale to get power-of-two dimensions and do yuv->rgb */ + /* Placeholder: black-and-white video by just using Y channel, using (ugh) gluBuild2DMipmaps */ + if (must_update_texture) { +#include + guchar* data = g_malloc(frame_buffer[0].width * frame_buffer[0].height); + int i; + for (i = 0; i < frame_buffer[0].height; i++) + memcpy(data + i*frame_buffer[0].width, frame_buffer[0].data + i*frame_buffer[0].stride, frame_buffer[0].width); + gluBuild2DMipmaps(GL_TEXTURE_2D, GL_LUMINANCE, frame_buffer[0].width, frame_buffer[0].height, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); + g_free(data); + } + + return TRUE; +} + GQuark video_player_error_quark(void) { return g_quark_from_static_string("video-player-error-quark"); From ad1fe9635908792f071b2e0458bbc7aa645d9a12 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Thu, 25 Nov 2010 02:21:48 -0500 Subject: [PATCH 07/26] Fix dropping of the first frame of the video. --- src/VideoPlayerCore.c | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/VideoPlayerCore.c b/src/VideoPlayerCore.c index 6694d2851..1a9c761dd 100644 --- a/src/VideoPlayerCore.c +++ b/src/VideoPlayerCore.c @@ -44,6 +44,7 @@ struct _VideoPlayer { GTimeVal playback_start_time; GLuint video_texture; ogg_int64_t decode_granpos; + th_ycbcr_buffer frame_buffer; }; static void destroy_stream(gpointer data) @@ -88,6 +89,20 @@ static gboolean demux_next_page(VideoPlayer* player, GError** err) return TRUE; } +#include +#include +static void update_texture(VideoPlayer* player) +{ + /* TODO: use swscale to get power-of-two dimensions and do yuv->rgb */ + /* Placeholder: black-and-white video by just using Y channel, using (ugh) gluBuild2DMipmaps */ + guchar* data = g_malloc(player->frame_buffer[0].width * player->frame_buffer[0].height); + int i; + for (i = 0; i < player->frame_buffer[0].height; i++) + memcpy(data + i*player->frame_buffer[0].width, player->frame_buffer[0].data + i*player->frame_buffer[0].stride, player->frame_buffer[0].width); + gluBuild2DMipmaps(GL_TEXTURE_2D, GL_LUMINANCE, player->frame_buffer[0].width, player->frame_buffer[0].height, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); + g_free(data); +} + static gboolean demux_headers(VideoPlayer* player, GError** err) { /* Go through the stream header pages, looking for one that starts a Theora stream. */ @@ -149,10 +164,20 @@ static gboolean demux_headers(VideoPlayer* player, GError** err) "Bad headers in Theora stream."); return FALSE; } else if (header_status == 0) { - /* We have everything we need to start decoding. */ + /* We have everything we need to start decoding, and we have the first video packet. */ + int decode_status; player->vdecoder = th_decode_alloc(&player->tinfo, player->tsetup); player->playing = FALSE; player->playback_position = 0; + decode_status = th_decode_packetin(player->vdecoder, &pkt, &player->decode_granpos); + if (decode_status != 0) { + g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_DATA, + "An error occurred decoding a Theora packet."); + return FALSE; + } + th_decode_ycbcr_out(player->vdecoder, player->frame_buffer); + glBindTexture(GL_TEXTURE_2D, player->video_texture); + update_texture(player); return TRUE; } /* Otherwise, there are still more header packets needed. */ @@ -220,7 +245,6 @@ void video_player_pause(VideoPlayer* player) gboolean video_player_bind_frame(VideoPlayer* player, GError** err) { gboolean must_update_texture = FALSE; - th_ycbcr_buffer frame_buffer; glBindTexture(GL_TEXTURE_2D, player->video_texture); @@ -254,22 +278,13 @@ gboolean video_player_bind_frame(VideoPlayer* player, GError** err) } if (decode_status == 0) { - th_decode_ycbcr_out(player->vdecoder, frame_buffer); + th_decode_ycbcr_out(player->vdecoder, player->frame_buffer); must_update_texture = TRUE; } } - /* TODO: use swscale to get power-of-two dimensions and do yuv->rgb */ - /* Placeholder: black-and-white video by just using Y channel, using (ugh) gluBuild2DMipmaps */ - if (must_update_texture) { -#include - guchar* data = g_malloc(frame_buffer[0].width * frame_buffer[0].height); - int i; - for (i = 0; i < frame_buffer[0].height; i++) - memcpy(data + i*frame_buffer[0].width, frame_buffer[0].data + i*frame_buffer[0].stride, frame_buffer[0].width); - gluBuild2DMipmaps(GL_TEXTURE_2D, GL_LUMINANCE, frame_buffer[0].width, frame_buffer[0].height, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); - g_free(data); - } + if (must_update_texture) + update_texture(player); return TRUE; } From 33bdc5d89b24d69c0169c17b61fc0a6ab7077834 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Thu, 25 Nov 2010 03:45:20 -0500 Subject: [PATCH 08/26] Switch to swscale and use it to get power-of-two dimensions. --- src/VideoPlayerCore.c | 64 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/src/VideoPlayerCore.c b/src/VideoPlayerCore.c index 1a9c761dd..17a2b39c0 100644 --- a/src/VideoPlayerCore.c +++ b/src/VideoPlayerCore.c @@ -21,6 +21,7 @@ #include "VideoPlayer.h" #include +#include #include #include @@ -45,6 +46,10 @@ struct _VideoPlayer { GLuint video_texture; ogg_int64_t decode_granpos; th_ycbcr_buffer frame_buffer; + struct SwsContext* sws_context; + int tex_width; + int tex_height; + guchar* tex_buffer; }; static void destroy_stream(gpointer data) @@ -89,18 +94,27 @@ static gboolean demux_next_page(VideoPlayer* player, GError** err) return TRUE; } -#include -#include +static guint32 next_power_of_two(guint32 n) +{ + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n++; + return n; +} + static void update_texture(VideoPlayer* player) { - /* TODO: use swscale to get power-of-two dimensions and do yuv->rgb */ - /* Placeholder: black-and-white video by just using Y channel, using (ugh) gluBuild2DMipmaps */ - guchar* data = g_malloc(player->frame_buffer[0].width * player->frame_buffer[0].height); - int i; - for (i = 0; i < player->frame_buffer[0].height; i++) - memcpy(data + i*player->frame_buffer[0].width, player->frame_buffer[0].data + i*player->frame_buffer[0].stride, player->frame_buffer[0].width); - gluBuild2DMipmaps(GL_TEXTURE_2D, GL_LUMINANCE, player->frame_buffer[0].width, player->frame_buffer[0].height, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); - g_free(data); + /* TODO: handle pic_[xy] correctly so the whole Theora testsuite works */ + const uint8_t* const src[] = {player->frame_buffer[0].data, player->frame_buffer[1].data, player->frame_buffer[2].data}; + int src_stride[] = {player->frame_buffer[0].stride, player->frame_buffer[1].stride, player->frame_buffer[2].stride}; + uint8_t* dest[] = {player->tex_buffer}; + int dest_stride[] = {player->tex_width * 4}; + sws_scale(player->sws_context, src, src_stride, 0, player->tinfo.pic_height, dest, dest_stride); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, player->tex_width, player->tex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, player->tex_buffer); } static gboolean demux_headers(VideoPlayer* player, GError** err) @@ -166,6 +180,7 @@ static gboolean demux_headers(VideoPlayer* player, GError** err) } else if (header_status == 0) { /* We have everything we need to start decoding, and we have the first video packet. */ int decode_status; + int pix_format; player->vdecoder = th_decode_alloc(&player->tinfo, player->tsetup); player->playing = FALSE; player->playback_position = 0; @@ -176,7 +191,32 @@ static gboolean demux_headers(VideoPlayer* player, GError** err) return FALSE; } th_decode_ycbcr_out(player->vdecoder, player->frame_buffer); + + player->tex_width = next_power_of_two(player->tinfo.pic_width); + player->tex_height = next_power_of_two(player->tinfo.pic_height); + player->tex_buffer = g_malloc(player->tex_width * player->tex_height * 4); + switch (player->tinfo.pixel_fmt) { + case TH_PF_420: + pix_format = PIX_FMT_YUV420P; + break; + case TH_PF_422: + pix_format = PIX_FMT_YUV422P; + break; + case TH_PF_444: + pix_format = PIX_FMT_YUV444P; + break; + default: + g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_HEADERS, + "Bad pixel format in Theora stream."); + return FALSE; + } + player->sws_context = sws_getContext(player->tinfo.pic_width, player->tinfo.pic_height, pix_format, player->tex_width, player->tex_height, PIX_FMT_RGBA, SWS_FAST_BILINEAR, NULL, NULL, NULL); + glBindTexture(GL_TEXTURE_2D, player->video_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); update_texture(player); return TRUE; } @@ -221,6 +261,10 @@ void video_player_destroy(VideoPlayer* player) th_decode_free(player->vdecoder); if (player->tsetup != NULL) th_setup_free(player->tsetup); + if (player->sws_context != NULL) + sws_freeContext(player->sws_context); + if (player->tex_buffer != NULL) + g_free(player->tex_buffer); glDeleteTextures(1, &player->video_texture); th_comment_clear(&player->tcomment); th_info_clear(&player->tinfo); From 7d0cffdf8e75a57427395eabc474017deed90ed7 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Thu, 25 Nov 2010 04:24:30 -0500 Subject: [PATCH 09/26] Add libswscale to the win32 dependency pack build script. --- win32/makedeps-cross.sh | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/win32/makedeps-cross.sh b/win32/makedeps-cross.sh index 04db9d302..190c59c14 100755 --- a/win32/makedeps-cross.sh +++ b/win32/makedeps-cross.sh @@ -248,4 +248,32 @@ if test ! -f "$PREFIX"/build-stamps/libtheora; then $RM_RF $LIBTHEORA fi +# ffmpeg +# We only need libswscale. +if test ! -f "$PREFIX"/build-stamps/ffmpeg; then + if test ! -d ffmpeg; then + svn co svn://svn.ffmpeg.org/ffmpeg/trunk ffmpeg + else + svn up ffmpeg + fi + cd ffmpeg + ./configure --prefix="$PREFIX" --cc="$CROSS_GCC" --nm="$CROSS_NM" --target-os=mingw32 --arch=i386 --disable-static --enable-shared --enable-gpl --enable-runtime-cpudetect --enable-memalign-hack --disable-everything --disable-ffmpeg --disable-ffplay --disable-ffserver --disable-ffprobe --disable-avdevice --disable-avcodec --disable-avcore --disable-avformat --disable-avfilter + sed -i -e 's/-Werror=[^ ]*//g' config.mak + make + make install + for lib in avutil swscale; do + # FFmpeg symlinks its DLLs to a few different names, differing in the level + # of detail of their version number, rather like what is done with ELF shared + # libraries. Unfortunately, the real DLL for each one is *not* the one that + # the implibs reference (that is, the one that will be required at runtime), + # so we must rename it after we nuke the symlinks. + find "$PREFIX"/bin -type l -name "${lib}*.dll" -print0 | xargs -0 rm -f + libfile="`find "$PREFIX"/bin -name "${lib}*.dll" | sed -e 1q`" + mv -v "$libfile" "`echo "$libfile" | sed -e "s/\($lib-[0-9]*\)[.0-9]*\.dll/\1.dll/"`" + done + cd .. + touch "$PREFIX"/build-stamps/ffmpeg + $RM_RF ffmpeg +fi + echo "All dependencies done." From 71bb414ef0f2e0a65f5581f36163e4f1a5b234aa Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Thu, 25 Nov 2010 20:26:10 -0500 Subject: [PATCH 10/26] Replace the VideoPlayer module with one using the new video playing code. --- .gitignore | 1 + src/VideoPlayer.py | 336 -------------------------------------------- src/VideoPlayer.pyx | 195 +++++++++++++++++++++++++ src/setup.py | 5 +- 4 files changed, 200 insertions(+), 337 deletions(-) delete mode 100644 src/VideoPlayer.py create mode 100644 src/VideoPlayer.pyx diff --git a/.gitignore b/.gitignore index 49b6e4c74..4d4e7a532 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ src/*.idb src/build src/pypitch/_pypitch.cpp src/cmgl.c +src/VideoPlayer.c gstreamer/* data/library.zip data/users/players/* diff --git a/src/VideoPlayer.py b/src/VideoPlayer.py deleted file mode 100644 index 9dabff5fe..000000000 --- a/src/VideoPlayer.py +++ /dev/null @@ -1,336 +0,0 @@ -#!/usr/bin/env python -# -*- coding: iso-8859-1 -*- -##################################################################### -# Video Playback Layer for FoFiX # -# Copyright (C) 2009 Pascal Giard # -# # -# This program is free software; you can redistribute it and/or # -# modify it under the terms of the GNU General Public License # -# as published by the Free Software Foundation; either version 2 # -# of the License, or (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program; if not, write to the Free Software # -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # -# MA 02110-1301, USA. # -##################################################################### -from __future__ import with_statement -import os -import sys - -# Twiddle the appropriate envvars under Windows so we can load gstreamer -# directly from [FoFiX root]/gstreamer and not have the ugliness of -# requiring the user to install and configure it separately... -if os.name == 'nt': - if hasattr(sys, 'frozen'): - _gstpath = 'gstreamer' - else: - _gstpath = os.path.join('..', 'gstreamer') - if os.path.isdir(_gstpath): - os.environ['PATH'] = os.path.abspath(os.path.join(_gstpath, 'bin')) + os.pathsep + os.environ['PATH'] - os.environ['GST_PLUGIN_PATH'] = os.path.abspath(os.path.join(_gstpath, 'lib', 'gstreamer-0.10')) - -# Almighty GStreamer -import gobject -import pygst -pygst.require('0.10') -import gst -from gst.extend import discoverer # Video property detection - -import pygame - -from OpenGL.GL import * -from OpenGL.GLU import * -# Array-based drawing -import numpy as np - -import cmgl - -from View import View, BackgroundLayer -import Log -from Texture import Texture - -FUTUREPROOF_VIDEO_CODECS = ['Theora'] -FUTUREPROOF_AUDIO_CODECS = ['Vorbis'] -FUTUREPROOF_CONTAINERS = ['Ogg'] - -# Simple video player -class VideoPlayer(BackgroundLayer): - def __init__(self, framerate, vidSource, (winWidth, winHeight) = (None, None), mute = False, loop = False, startTime = None, endTime = None): - self.updated = False - self.videoList = None - self.videoTex = None - self.videoBuffer = None - self.videoSrc = vidSource - self.mute = mute - self.loop = loop - self.startTime = startTime - self.endTime = endTime - if winWidth is not None and winHeight is not None: - self.winWidth, self.winHeight = winWidth, winHeight - else: # default - self.winWidth, self.winHeight = (640, 480) - Log.warn("VideoPlayer: No resolution specified (default %dx%d)" % - (self.winWidth, self.winHeight)) - self.vidWidth, self.vidHeight = -1, -1 - self.fps = framerate - self.clock = pygame.time.Clock() - self.paused = False - self.finished = False - self.discovered = False - self.timeFormat = gst.Format(gst.FORMAT_TIME) - - self.loadVideo(vidSource) # Load the video - - # Load a new video: - # 1) Detect video resolution - # 2) Setup OpenGL texture - # 3) Setup GStreamer pipeline - def loadVideo(self, vidSource): - Log.debug("Attempting to load video: %s" % self.videoSrc) - if not os.path.exists(vidSource): - Log.error("Video %s does not exist!" % vidSource) - self.videoSrc = vidSource - d = discoverer.Discoverer(self.videoSrc) - d.connect('discovered', self.videoDiscover) - d.discover() - gobject.threads_init() # Start C threads - while not self.discovered: - # Force C threads iteration - gobject.MainLoop().get_context().iteration(True) - if not self.validFile: - Log.error("Invalid video file: %s\n" % self.videoSrc) - return False - self.textureSetup() - self.videoSetup() - return True - - # Use GStreamer's video discoverer to autodetect video properties - def videoDiscover(self, d, isMedia): - self.validFile = True - if isMedia and d.is_video: - - # Warn about codecs that might not be supported in the future. - vcodec = d.tags.get('video-codec', '[plugin did not give a name]') - Log.debug('Video codec: ' + vcodec) - if vcodec not in FUTUREPROOF_VIDEO_CODECS: - self.validFile = False - - if d.is_audio: - acodec = d.tags.get('audio-codec', '[plugin did not give a name]') - Log.debug('Audio codec: ' + acodec) - if acodec not in FUTUREPROOF_AUDIO_CODECS: - self.validFile = False - - container = d.tags.get('container-format', '[plugin did not give a name]') - Log.debug('Container format: ' + container) - if container not in FUTUREPROOF_CONTAINERS: - self.validFile = False - - if not self.validFile: - Log.warn('Refusing to play non-Ogg, non-Theora, or (if audio is present) non-Vorbis video file.') - else: - self.vidWidth, self.vidHeight = d.videowidth, d.videoheight - # Force mute if no sound track is available or - # else you'll get nothing but a black screen! - if not d.is_audio and not self.mute: - Log.warn("Video has no sound ==> forcing mute.") - self.mute = True - - else: - self.validFile = False - - self.discovered = True - - def textureSetup(self): - if not self.validFile: - return - - self.videoTex = Texture(useMipmaps=False) - self.videoBuffer = '\x00\x00\x00' * self.vidWidth * self.vidHeight - self.updated = True - self.textureUpdate() - - # Resize video (polygon) to respect resolution ratio - # (The math is actually simple, take the time to draw it down if required) - winRes = float(self.winWidth)/float(self.winHeight) - vidRes = float(self.vidWidth)/float(self.vidHeight) - vtxX = 1.0 - vtxY = 1.0 - if winRes > vidRes: - r = float(self.winHeight)/float(self.vidHeight) - vtxX = 1.0 - abs(self.winWidth-r*self.vidWidth) / (float(self.winWidth)) - elif winRes < vidRes: - r = float(self.winWidth)/float(self.vidWidth) - vtxY = 1.0 - abs(self.winHeight-r*self.vidHeight) / (float(self.winHeight)) - - # Vertices - videoVtx = np.array([[-vtxX, vtxY], - [ vtxX, -vtxY], - [ vtxX, vtxY], - [-vtxX, vtxY], - [-vtxX, -vtxY], - [ vtxX, -vtxY]], dtype=np.float32) - backVtx = np.array([[-1.0, 1.0], - [ 1.0, -1.0], - [ 1.0, 1.0], - [-1.0, 1.0], - [-1.0, -1.0], - [ 1.0, -1.0]], dtype=np.float32) - # Texture coordinates - videoTex = np.array([[0.0, self.videoTex.size[1]], - [self.videoTex.size[0], 0.0], - [self.videoTex.size[0], self.videoTex.size[1]], - [0.0, self.videoTex.size[1]], - [0.0, 0.0], - [self.videoTex.size[0], 0.0]], dtype=np.float32) - - # Create a compiled OpenGL call list and do array-based drawing - # Could have used GL_QUADS but IIRC triangles are recommended - self.videoList = cmgl.List() - with self.videoList: - # Draw borders where video aspect is different than specified width/height - glColor3f(0., 0., 0.) - cmgl.drawArrays(GL_TRIANGLE_STRIP, vertices=backVtx) - # Draw video - glEnable(GL_TEXTURE_2D) - glColor3f(1., 1., 1.) - cmgl.drawArrays(GL_TRIANGLE_STRIP, vertices=videoVtx, texcoords=videoTex) - glDisable(GL_TEXTURE_2D) - - # Setup GStreamer's pipeline - # Note: playbin2 seems also suitable, we might want to experiment with it - # if decodebin is proven problematic - def videoSetup(self): - if self.startTime is not None or self.endTime is not None: - self.setTime = True - else: - self.setTime = False - if self.startTime is not None: - self.startNs = self.startTime * 1000000 # From ms to ns - else: - self.startNs = 0 - if self.endTime is not None: - self.endNs = self.endTime * 1000000 - else: - self.endNs = -1 - - with_audio = "" - if not self.mute: - with_audio = "! queue ! audioconvert ! audiorate ! audioresample ! autoaudiosink" - s = "filesrc name=input ! decodebin2 name=dbin dbin. ! ffmpegcolorspace ! video/x-raw-rgb ! fakesink name=output signal-handoffs=true sync=true dbin. %s" % with_audio - self.player = gst.parse_launch(s) - self.input = self.player.get_by_name('input') - self.fakeSink = self.player.get_by_name('output') - self.input.set_property("location", self.videoSrc) - self.fakeSink.connect ("handoff", self.newFrame) - - # Catch the end of file as well as errors - # FIXME: - # Messages are sent if i use the following in run(): - # gobject.MainLoop().get_context().iteration(True) - # BUT the main python thread then freezes after ~5 seconds... - # unless we use gobject.idle_add(self.player.elements) - bus = self.player.get_bus() - bus.add_signal_watch() - bus.enable_sync_message_emission() - bus.connect("message", self.onMessage) - # Required to prevent the main python thread from freezing, why?! - # Thanks to max26199 for finding this! - gobject.idle_add(self.player.elements) - - # Handle bus event e.g. end of video or unsupported formats/codecs - def onMessage(self, bus, message): - type = message.type -# print "Message %s" % type - # End of video - if type == gst.MESSAGE_EOS: - if self.loop: - # Seek back to start time and set end time - self.player.seek(1, self.timeFormat, gst.SEEK_FLAG_FLUSH, - gst.SEEK_TYPE_SET, self.startNs, - gst.SEEK_TYPE_SET, self.endNs) - else: - self.player.set_state(gst.STATE_NULL) - self.finished = True - # Error - elif type == gst.MESSAGE_ERROR: - err = message.parse_error() - Log.error("GStreamer error: %s" % err) - self.player.set_state(gst.STATE_NULL) - self.finished = True -# raise NameError("GStreamer error: %s" % err) - elif type == gst.MESSAGE_WARNING: - warning, debug = message.parse_warning() - Log.warn("GStreamer warning: %s\n(---) %s" % (warning, debug)) - elif type == gst.MESSAGE_STATE_CHANGED: - oldstate, newstate, pending = message.parse_state_changed() -# Log.debug("GStreamer state: %s" % newstate) - if newstate == gst.STATE_READY: - # Set start and end time - if self.setTime: - # Note: Weirdly, contrary to loop logic, i need a wait here! - # Moreover, at the beginning, we're ready more than once!? - pygame.time.wait(1000) # Why, oh why... isn't ready, READY?! - self.player.seek(1, self.timeFormat, gst.SEEK_FLAG_FLUSH, - gst.SEEK_TYPE_SET, self.startNs, - gst.SEEK_TYPE_SET, self.endNs) - self.setTime = False # Execute just once - - # Handle new video frames coming from the decoder - def newFrame(self, sink, buffer, pad): - self.videoBuffer = buffer - self.updated = True - - def textureUpdate(self): - if self.updated: - img = pygame.image.frombuffer(self.videoBuffer, - (self.vidWidth, self.vidHeight), - 'RGB') - self.videoTex.loadSurface(img) - self.videoTex.setFilter() - self.updated = False - - def shown(self): - gobject.threads_init() - - def hidden(self): - self.player.set_state(gst.STATE_NULL) - - def run(self, ticks = None): - if not self.validFile: - return - if self.paused == True: - self.player.set_state(gst.STATE_PAUSED) - else: - self.player.set_state(gst.STATE_PLAYING) - self.finished = False - gobject.MainLoop().get_context().iteration(True) - self.clock.tick(self.fps) - - # Render texture to polygon - # Note: Both visibility and topMost are currently unused. - def render(self, visibility = 1.0, topMost = False): - try: - self.textureUpdate() - # Save and clear both transformation matrices - glMatrixMode(GL_PROJECTION) - glPushMatrix() - glLoadIdentity() - glMatrixMode(GL_MODELVIEW) - glPushMatrix() - glLoadIdentity() - # Draw the polygon and apply texture - self.videoTex.bind() - self.videoList() - # Restore both transformation matrices - glPopMatrix() - glMatrixMode(GL_PROJECTION) - glPopMatrix() - except Exception: - Log.error("Error attempting to play video") diff --git a/src/VideoPlayer.pyx b/src/VideoPlayer.pyx new file mode 100644 index 000000000..2f18f3466 --- /dev/null +++ b/src/VideoPlayer.pyx @@ -0,0 +1,195 @@ +##################################################################### +# -*- coding: iso-8859-1 -*- # +# # +# Frets on Fire X (FoFiX) # +# Copyright (C) 2010 FoFiX Team # +# 2010 John Stumpo # +# # +# This program is free software; you can redistribute it and/or # +# modify it under the terms of the GNU General Public License # +# as published by the Free Software Foundation; either version 2 # +# of the License, or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, write to the Free Software # +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # +# MA 02110-1301, USA. # +##################################################################### + +# First a thin wrapper around VideoPlayer from VideoPlayerCore.c... + +cdef extern from "VideoPlayer.h": + ctypedef struct CVideoPlayer "VideoPlayer": + pass + + ctypedef int GQuark + ctypedef struct GError: + GQuark domain + char* message + GQuark G_FILE_ERROR + GQuark VIDEO_PLAYER_ERROR + void g_error_free(GError*) + + CVideoPlayer* video_player_new(char*, GError**) + void video_player_destroy(CVideoPlayer*) + void video_player_play(CVideoPlayer*) + void video_player_pause(CVideoPlayer*) + bint video_player_bind_frame(CVideoPlayer*, GError**) + bint video_player_eof(CVideoPlayer*) + double video_player_aspect_ratio(CVideoPlayer*) + +class VideoPlayerError(Exception): + pass + +cdef object raise_from_gerror(GError* err): + assert err is not NULL + if err.domain == VIDEO_PLAYER_ERROR: + exc = VideoPlayerError(err.message) + elif err.domain == G_FILE_ERROR: + exc = IOError(err.message) + else: + exc = Exception(err.message) + g_error_free(err) + raise exc + +cdef class VideoPlayer(object): + cdef CVideoPlayer* player + + def __cinit__(self, char* filename): + cdef GError* err = NULL + self.player = video_player_new(filename, &err) + if self.player is NULL: + raise_from_gerror(err) + + def __dealloc__(self): + if self.player is not NULL: + video_player_destroy(self.player) + + def play(self): + video_player_play(self.player) + + def pause(self): + video_player_pause(self.player) + + def bind_frame(self): + cdef GError* err = NULL + if not video_player_bind_frame(self.player, &err): + raise_from_gerror(err) + + def eof(self): + return video_player_eof(self.player) + + def aspect_ratio(self): + return video_player_aspect_ratio(self.player) + + +# And now, a layer for playing a video. + +from View import BackgroundLayer +from Input import KeyListener +import numpy as np +import cmgl + +# This is Cython after all, so we may as well directly bind to OpenGL for the video layer implementation. +cdef extern from "glwrap.h": + enum: + GL_PROJECTION + GL_MODELVIEW + GL_QUADS + GL_TEXTURE_2D + void glMatrixMode(int) + void glPushMatrix() + void glPopMatrix() + void glLoadIdentity() + void glEnable(int) + void glDisable(int) + void glColor4f(double, double, double, double) + +class VideoLayer(BackgroundLayer, KeyListener): + def __init__(self, engine, filename, mute = False, loop = False, startTime = None, endTime = None, cancellable = False): + self.engine = engine + self.filename = filename + self.mute = mute # TODO: audio + self.loop = loop # TODO: seeking + self.startTime = startTime # TODO: seeking + self.endTime = endTime # TODO: seeking + self.cancellable = cancellable + + self.finished = False + + self.player = VideoPlayer(self.filename) + + def shown(self): + if self.cancellable: + self.engine.input.addKeyListener(self) + + def hidden(self): + if self.cancellable: + self.engine.input.removeKeyListener(self) + + def keyPressed(self, key, unicode): + self.finished = True + + def play(self): + self.player.play() + + def pause(self): + self.player.pause() + + def render(self, visibility, topMost): + screen_aspect_ratio = float(self.engine.view.geometry[2]) / self.engine.view.geometry[3] + video_aspect_ratio = self.player.aspect_ratio() + + # Figure out the area of the acreen to cover with video. + if screen_aspect_ratio > video_aspect_ratio: + width_fraction = video_aspect_ratio / screen_aspect_ratio + height_fraction = 1.0 + else: + width_fraction = 1.0 + height_fraction = screen_aspect_ratio / video_aspect_ratio + + self.player.bind_frame() + + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + glLoadIdentity() + + # Prepare to draw the video over a black rectangle that will fill the rest of the space. + background_vertices = np.array([[ 1.0, 1.0], + [-1.0, 1.0], + [-1.0, -1.0], + [ 1.0, -1.0]], dtype=np.float32) + video_vertices = np.array([[ width_fraction, height_fraction], + [-width_fraction, height_fraction], + [-width_fraction, -height_fraction], + [ width_fraction, -height_fraction]], dtype=np.float32) + texcoords = np.array([[1.0, 0.0], + [0.0, 0.0], + [0.0, 1.0], + [1.0, 1.0]], dtype=np.float32) + + # The black rectangle. + glColor4f(0.0, 0.0, 0.0, 1.0) + cmgl.drawArrays(GL_QUADS, vertices=background_vertices) + + # The actual video. + glEnable(GL_TEXTURE_2D) + glColor4f(1.0, 1.0, 1.0, 1.0) + cmgl.drawArrays(GL_QUADS, vertices=video_vertices, texcoords=texcoords) + glDisable(GL_TEXTURE_2D) + + glPopMatrix() + glMatrixMode(GL_PROJECTION) + glPopMatrix() + glMatrixMode(GL_MODELVIEW) + + if self.player.eof(): + self.finished = True diff --git a/src/setup.py b/src/setup.py index be017cfba..e9b20bc3e 100755 --- a/src/setup.py +++ b/src/setup.py @@ -291,6 +291,7 @@ def pc_info(pkg): ogg_info = pc_info('ogg') theoradec_info = pc_info('theoradec') glib_info = pc_info('glib-2.0') +swscale_info = pc_info('libswscale') if os.name == 'nt': # Windows systems: we just know what the OpenGL library is. gl_info = {'libraries': ['opengl32']} @@ -327,7 +328,9 @@ def combine_info(*args): Extension('pypitch._pypitch', language='c++', sources=['pypitch/_pypitch.pyx', 'pypitch/pitch.cpp', - 'pypitch/AnalyzerInput.cpp']) + 'pypitch/AnalyzerInput.cpp']), + Extension('VideoPlayer', ['VideoPlayer.pyx', 'VideoPlayerCore.c'], + **combine_info(gl_info, ogg_info, theoradec_info, glib_info, swscale_info)) ], 'cmdclass': {'build_ext': build_ext}, }) From da57ae5fbe854f821aa00d5fe55f2aa4f4893184 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Thu, 25 Nov 2010 20:26:48 -0500 Subject: [PATCH 11/26] Add functionality to VideoPlayerCore for getting end-of-file status and the video aspect ratio. --- src/VideoPlayer.h | 3 +++ src/VideoPlayerCore.c | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/VideoPlayer.h b/src/VideoPlayer.h index ae21711a5..c68ddeaae 100644 --- a/src/VideoPlayer.h +++ b/src/VideoPlayer.h @@ -33,6 +33,9 @@ void video_player_pause(VideoPlayer* player); gboolean video_player_bind_frame(VideoPlayer* player, GError** err); +gboolean video_player_eof(const VideoPlayer* player); +double video_player_aspect_ratio(const VideoPlayer* player); + GQuark video_player_error_quark(void); #define VIDEO_PLAYER_ERROR video_player_error_quark() diff --git a/src/VideoPlayerCore.c b/src/VideoPlayerCore.c index 17a2b39c0..bf19647de 100644 --- a/src/VideoPlayerCore.c +++ b/src/VideoPlayerCore.c @@ -333,6 +333,16 @@ gboolean video_player_bind_frame(VideoPlayer* player, GError** err) return TRUE; } +gboolean video_player_eof(const VideoPlayer* player) +{ + return player->eof; +} + +double video_player_aspect_ratio(const VideoPlayer* player) +{ + return player->tinfo.pic_width / (double)player->tinfo.pic_height; +} + GQuark video_player_error_quark(void) { return g_quark_from_static_string("video-player-error-quark"); From 7a4723496dc91185382a8604f124f1c3bb695a6a Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Thu, 25 Nov 2010 20:28:55 -0500 Subject: [PATCH 12/26] Remove the video player test, which is too specific to the old code. --- src/tests/VideoPlayerTest.py | 152 ----------------------------------- 1 file changed, 152 deletions(-) delete mode 100644 src/tests/VideoPlayerTest.py diff --git a/src/tests/VideoPlayerTest.py b/src/tests/VideoPlayerTest.py deleted file mode 100644 index f4d0782c3..000000000 --- a/src/tests/VideoPlayerTest.py +++ /dev/null @@ -1,152 +0,0 @@ -##################################################################### -# -*- coding: iso-8859-1 -*- # -# # -# FoFiX # -# Copyright (C) 2009 Pascal Giard # -# # -# This program is free software; you can redistribute it and/or # -# modify it under the terms of the GNU General Public License # -# as published by the Free Software Foundation; either version 2 # -# of the License, or (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program; if not, write to the Free Software # -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # -# MA 02110-1301, USA. # -##################################################################### - -import unittest -import os -import pygame -from pygame.locals import * -from OpenGL.GL import * - -from GameEngine import GameEngine -import Config -import Version -from VideoPlayer import VideoPlayer - -# Please change these values to fit your needs. -# Note that video codecs/format supported depends on the gstreamer plugins -# you've installed. On my machine that means pretty much anything e.g. XviD, -# Ogg Theora, Flash, FFmpeg H.264, etc.). -framerate = 24 # Number of frames per seconds (-1 = as fast as it can) -# vidSource # Path to video; relative to FoFiX data/ directory - -# FIXME: Should autodetect video width and height values. - -# Video examples -#===================== -# XviD/MPEG-4 -vidSource = "t1.avi" - -# FFmpeg H.264 -# vidSource = "t2.m4v" - -# Macromedia Flash -# vidSource = "t3.flv" -# vidSource = "t3.1.flv" # 24 seconds - -# Xiph Ogg Theora -# vidSource = "t4.ogv" - -class VideoPlayerTest(unittest.TestCase): - # Simplest way to use the video player, use it as a Layer - def testVideoPlayerLayer(self): - config = Config.load(Version.PROGRAM_UNIXSTYLE_NAME + ".ini", setAsDefault = True) - self.e = GameEngine(config) - winWidth, winHeight = (self.e.view.geometry[2], self.e.view.geometry[3]) - vidPlayer = VideoPlayer(framerate, self.src, (winWidth, winHeight), - loop = False, startTime = 10000, endTime = 14000) - self.e.view.pushLayer(vidPlayer) - while not vidPlayer.finished: - self.e.run() - self.e.view.popLayer(vidPlayer) - self.e.audio.close() - self.e.quit() - - # Keep tight control over the video player - def testVideoPlayerSlave(self): - winWidth, winHeight = 800, 600 - pygame.init() - flags = DOUBLEBUF|OPENGL|HWPALETTE|HWSURFACE - pygame.display.set_mode((winWidth, winHeight), flags) - vidPlayer = VideoPlayer(framerate, self.src, (winWidth, winHeight)) - glViewport(0, 0, winWidth, winHeight) # Both required as... - glScissor(0, 0, winWidth, winHeight) # ...GameEngine changes it - glClearColor(0, 0, 0, 1.) - while not vidPlayer.finished: - vidPlayer.run() - vidPlayer.render() - pygame.display.flip() - pygame.quit() - - # Grab the texture, use the CallList and do whatever we want with it; - # We could also _just_ use the texture and take care of the polygons ourselves - def testVideoPlayerSlaveShowOff(self): - winWidth, winHeight = 500, 500 - pygame.init() - flags = DOUBLEBUF|OPENGL|HWPALETTE|HWSURFACE - pygame.display.set_mode((winWidth, winHeight), flags) - vidPlayer = VideoPlayer(-1, self.src, (winWidth, winHeight)) - glViewport(0, 0, winWidth, winHeight) # Both required as... - glScissor(0, 0, winWidth, winHeight) # ...GameEngine changes it - glClearColor(0, 0, 0, 1.) - x, y = 0.0, 1.0 - fx, fy, ftheta = 1, 1, 1 - theta = 0.0 - time = 0.0 - clock = pygame.time.Clock() - while not vidPlayer.finished: - vidPlayer.run() - vidPlayer.textureUpdate() - # Save and clear both transformation matrices - glMatrixMode(GL_PROJECTION) - glPushMatrix() - glLoadIdentity() - glMatrixMode(GL_MODELVIEW) - glPushMatrix() - glLoadIdentity() - - glClear(GL_COLOR_BUFFER_BIT) - glColor3f(1., 1., 1.) - vidPlayer.videoTex.bind() - glTranslatef(x, y, 0) - glRotatef(theta, 0, 0, 1.) - glScalef(.5, .5, 1.) - glCallList(vidPlayer.videoList) - - # Restore both transformation matrices - glPopMatrix() - glMatrixMode(GL_PROJECTION) - glPopMatrix() - - pygame.display.flip() - - x = (x + fx*time) - y = (y + fy*time) - theta = theta + ftheta - if x > 1.0 or x < -1.0: - fx = fx * -1 - if y > 1.0 or y < -1.0: - fy = fy * -1 - if theta > 90 or theta < -90: - ftheta = ftheta * -1 - time = time + 0.00001 - clock.tick(60) - pygame.quit() - - def setUp(self): - self.src = os.path.join(Version.dataPath(), vidSource) - self.assert_(os.path.exists(self.src), "File %s does not exist!" % self.src) - - def tearDown(self): - pass - -if __name__ == "__main__": - unittest.main() From 856560da17b660cc901e735d3af965ab66474cd1 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Thu, 25 Nov 2010 20:57:39 -0500 Subject: [PATCH 13/26] Convert the intro video player to the new video code and make it cancellable by pressing any key. --- src/FoFiX.py | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/FoFiX.py b/src/FoFiX.py index 5cbbd3777..15125b747 100644 --- a/src/FoFiX.py +++ b/src/FoFiX.py @@ -109,12 +109,7 @@ def _usage(errmsg=None): import Resource import pygame import traceback - -try: - from VideoPlayer import VideoPlayer - videoAvailable = True -except: - videoAvailable = False +from VideoPlayer import VideoLayer, VideoPlayerError def main(): try: @@ -218,33 +213,24 @@ def main(): # Play the intro video if it is present, we have the capability, and # we are not in one-shot mode. videoLayer = False - if videoAvailable and not engine.cmdPlay: - # TODO: Parameters to add to theme.ini: - # - intro_video_file - # - intro_video_start_time - # - intro_video_end_time + if not engine.cmdPlay: themename = Config.get("coffee", "themename") vidSource = os.path.join(Version.dataPath(), 'themes', themename, \ 'menu', 'intro.ogv') if os.path.isfile(vidSource): - winWidth, winHeight = engine.view.geometry[2:4] - songVideoStartTime = 0 - songVideoEndTime = None - vidPlayer = VideoPlayer(-1, vidSource, (winWidth, winHeight), - startTime = songVideoStartTime, - endTime = songVideoEndTime) - if vidPlayer.validFile: + try: + vidPlayer = VideoLayer(engine, vidSource, cancellable=True) + except (IOError, VideoPlayerError): + Log.error("Error loading intro video:") + else: + vidPlayer.play() engine.view.pushLayer(vidPlayer) videoLayer = True - try: - engine.ticksAtStart = pygame.time.get_ticks() - while not vidPlayer.finished: - engine.run() - engine.view.popLayer(vidPlayer) - engine.view.pushLayer(MainMenu(engine)) - except KeyboardInterrupt: - engine.view.popLayer(vidPlayer) - engine.view.pushLayer(MainMenu(engine)) + engine.ticksAtStart = pygame.time.get_ticks() + while not vidPlayer.finished: + engine.run() + engine.view.popLayer(vidPlayer) + engine.view.pushLayer(MainMenu(engine)) if not videoLayer: engine.setStartupLayer(MainMenu(engine)) From 0742bfdfb34aba5d141a4bf6aea35bd6158ab019 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Thu, 25 Nov 2010 20:59:52 -0500 Subject: [PATCH 14/26] Convert the credits video player to the new code. --- src/Credits.py | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/src/Credits.py b/src/Credits.py index 226c25bc1..34e9817c5 100644 --- a/src/Credits.py +++ b/src/Credits.py @@ -40,11 +40,7 @@ import Config import Dialogs -try: - from VideoPlayer import VideoPlayer - videoAvailable = True -except: - videoAvailable = False +from VideoPlayer import VideoLayer, VideoPlayerError class Element: """A basic element in the credits scroller.""" @@ -173,24 +169,17 @@ def __init__(self, engine, songName = None): self.videoLayer = False self.background = None - if videoAvailable: - # TODO: Parameters to add to theme.ini: - # - credits_video_file - # - credits_video_start_time - # - credits_video_end_time - vidSource = os.path.join(Version.dataPath(), 'themes', self.themename, \ - 'menu', 'credits.ogv') - if os.path.exists(vidSource): - winWidth, winHeight = self.engine.view.geometry[2:4] - songVideoStartTime = 0 - songVideoEndTime = None - self.vidPlayer = VideoPlayer(-1, vidSource, (winWidth, winHeight), - mute = True, loop = True, - startTime = songVideoStartTime, - endTime = songVideoEndTime) - if self.vidPlayer.validFile: - self.engine.view.pushLayer(self.vidPlayer) - self.videoLayer = True + vidSource = os.path.join(Version.dataPath(), 'themes', self.themename, \ + 'menu', 'credits.ogv') + if os.path.isfile(vidSource): + try: + self.vidPlayer = VideoLayer(self.engine, vidSource, mute = True, loop = True) + except (IOError, VideoPlayerError): + Log.error('Error loading credits video:') + else: + self.vidPlayer.play() + self.engine.view.pushLayer(self.vidPlayer) + self.videoLayer = True if not self.videoLayer and \ not self.engine.loadImgDrawing(self, 'background', os.path.join('themes', self.themename, 'menu', 'credits.png')): From abeab0589aa78e7f5181987f8c7334e3b0d1d5ca Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Thu, 25 Nov 2010 21:03:02 -0500 Subject: [PATCH 15/26] Add hackish looping to the video player. --- src/VideoPlayer.pyx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/VideoPlayer.pyx b/src/VideoPlayer.pyx index 2f18f3466..018ab48f8 100644 --- a/src/VideoPlayer.pyx +++ b/src/VideoPlayer.pyx @@ -192,4 +192,9 @@ class VideoLayer(BackgroundLayer, KeyListener): glMatrixMode(GL_MODELVIEW) if self.player.eof(): - self.finished = True + if self.loop: + del self.player + self.player = VideoPlayer(self.filename) + self.player.play() + else: + self.finished = True From 05676440dc7b33a90a76c5ef76b0690e3199edeb Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Thu, 25 Nov 2010 21:24:41 -0500 Subject: [PATCH 16/26] Make restart a method of the video layer. --- src/VideoPlayer.pyx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/VideoPlayer.pyx b/src/VideoPlayer.pyx index 018ab48f8..3345079c7 100644 --- a/src/VideoPlayer.pyx +++ b/src/VideoPlayer.pyx @@ -141,6 +141,12 @@ class VideoLayer(BackgroundLayer, KeyListener): def pause(self): self.player.pause() + def restart(self): + # XXX: do this less hackishly + del self.player + self.player = VideoPlayer(self.filename) + self.player.play() + def render(self, visibility, topMost): screen_aspect_ratio = float(self.engine.view.geometry[2]) / self.engine.view.geometry[3] video_aspect_ratio = self.player.aspect_ratio() @@ -193,8 +199,6 @@ class VideoLayer(BackgroundLayer, KeyListener): if self.player.eof(): if self.loop: - del self.player - self.player = VideoPlayer(self.filename) - self.player.play() + self.restart() else: self.finished = True From 2ba0fcda37c73cb69890accd9af3ebf5f2d32451 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Thu, 25 Nov 2010 21:24:18 -0500 Subject: [PATCH 17/26] Convert the song background video code to the new module. --- src/GuitarScene.py | 37 ++++++++++++++++--------------------- src/Stage.py | 32 +++++++++++--------------------- 2 files changed, 27 insertions(+), 42 deletions(-) diff --git a/src/GuitarScene.py b/src/GuitarScene.py index 07e15f79d..a634dddea 100644 --- a/src/GuitarScene.py +++ b/src/GuitarScene.py @@ -1309,22 +1309,17 @@ def __init__(self, engine, libraryName, songName): # evilynux - Load stage background(s) if self.stage.mode == 3: - if Stage.videoAvailable: - songVideo = None - if self.song.info.video is not None: - songVideo = self.song.info.video - songVideoStartTime = self.song.info.video_start_time - songVideoEndTime = self.song.info.video_end_time - if songVideoEndTime == -1: - songVideoEndTime = None - self.stage.loadVideo(self.libraryName, self.songName, - songVideo = songVideo, - songVideoStartTime = songVideoStartTime, - songVideoEndTime = songVideoEndTime) - else: - Log.warn("Video playback is not supported. GStreamer or its python bindings can't be found") - self.engine.config.set("game", "stage_mode", 1) - self.stage.mode = 1 + songVideo = None + if self.song.info.video is not None: + songVideo = self.song.info.video + songVideoStartTime = self.song.info.video_start_time + songVideoEndTime = self.song.info.video_end_time + if songVideoEndTime == -1: + songVideoEndTime = None + self.stage.loadVideo(self.libraryName, self.songName, + songVideo = songVideo, + songVideoStartTime = songVideoStartTime, + songVideoEndTime = songVideoEndTime) self.stage.load(self.libraryName, self.songName, self.playerList[0].practiceMode) @@ -1757,7 +1752,7 @@ def freeResources(self): if self.coOpType: self.coOpScoreCard.lastNoteEvent = None - if self.stage.mode == 3 and Stage.videoAvailable: + if self.stage.mode == 3: self.engine.view.popLayer(self.stage.vidPlayer) def getHandicap(self): @@ -3895,14 +3890,14 @@ def endPick(self, num): self.scoring[num].addScore(scoreTemp) def render3D(self): - if self.stage.mode == 3 and Stage.videoAvailable: + if self.stage.mode == 3: if self.countdown <= 0: if self.pause == True or self.failed == True: - self.stage.vidPlayer.paused = True + self.stage.vidPlayer.pause() else: - self.stage.vidPlayer.paused = False + self.stage.vidPlayer.play() else: - self.stage.vidPlayer.paused = True + self.stage.vidPlayer.pause() self.stage.render(self.visibility) diff --git a/src/Stage.py b/src/Stage.py index 7f45768f8..144972792 100644 --- a/src/Stage.py +++ b/src/Stage.py @@ -34,12 +34,8 @@ from Language import _ import math -try: - from VideoPlayer import VideoPlayer - videoAvailable = True -except: - videoAvailable = False - +from VideoPlayer import VideoLayer, VideoPlayerError + import Rockmeter #blazingamer - new 4.0 code for rendering rockmeters through stage.ini @@ -356,8 +352,6 @@ def get(value, type = str, default = None): def loadVideo(self, libraryName, songName, songVideo = None, songVideoStartTime = None, songVideoEndTime = None): - if not videoAvailable: - raise NameError('Video (gstreamer) is not available!') self.vidSource = None if self.songStage == 1: songAbsPath = os.path.join(libraryName, songName) @@ -383,26 +377,22 @@ def loadVideo(self, libraryName, songName, songVideo = None, self.mode = 1 # Fallback self.vidSource = None return - - winWidth, winHeight = (self.engine.view.geometry[2], - self.engine.view.geometry[3]) - Log.debug("Attempting to load video: %s" % self.vidSource) + try: # Catches invalid video files or unsupported formats Log.debug("Attempting to load video: %s" % self.vidSource) - self.vidPlayer = VideoPlayer(-1, self.vidSource, (winWidth, winHeight), - mute = True, loop = True, - startTime = songVideoStartTime, - endTime = songVideoEndTime) + self.vidPlayer = VideoLayer(self.engine, self.vidSource, + mute = True, loop = True, + startTime = songVideoStartTime, + endTime = songVideoEndTime) self.engine.view.pushLayer(self.vidPlayer) - self.vidPlayer.paused = True - except: + except (IOError, VideoPlayerError): self.mode = 1 - Log.warn("Failed to load video, fallback to default stage mode.") + Log.error("Failed to load song video (falling back to default stage mode):") def restartVideo(self): - if not videoAvailable or not self.mode == 3: + if not self.mode == 3: return - self.vidPlayer.loadVideo(self.vidSource) + self.vidPlayer.restart() def load(self, libraryName, songName, practiceMode = False): rm = os.path.join("themes", self.themename, "rockmeter.ini") From c9ad60074f9fdc6e6d9bfab053b7e27958867324 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Fri, 26 Nov 2010 04:25:43 -0500 Subject: [PATCH 18/26] Change an OpenGL include to glwrap for Windows compatibility. --- src/VideoPlayerCore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VideoPlayerCore.c b/src/VideoPlayerCore.c index bf19647de..b50975165 100644 --- a/src/VideoPlayerCore.c +++ b/src/VideoPlayerCore.c @@ -20,7 +20,7 @@ #include "VideoPlayer.h" -#include +#include "glwrap.h" #include #include #include From 9ba0b7691d22fe112c90200466eb4d6e688da9f1 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Fri, 26 Nov 2010 04:26:34 -0500 Subject: [PATCH 19/26] Add support for receiving macro definitions from pkg-config in setup.py. --- src/setup.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/setup.py b/src/setup.py index e9b20bc3e..5ed46a24c 100755 --- a/src/setup.py +++ b/src/setup.py @@ -278,7 +278,13 @@ def pc_info(pkg): # Pick out anything interesting in the cflags and libs, and # silently drop the rest. + def def_split(x): + pair = list(x.split('=', 1)) + if len(pair) == 1: + pair.append(None) + return tuple(pair) info = { + 'define_macros': [def_split(x[2:]) for x in cflags if x[:2] == '-D'], 'include_dirs': [x[2:] for x in cflags if x[:2] == '-I'], 'libraries': [x[2:] for x in libs if x[:2] == '-l'], 'library_dirs': [x[2:] for x in libs if x[:2] == '-L'], @@ -306,12 +312,14 @@ def combine_info(*args): '''Combine multiple result dicts from L{pc_info} into one.''' info = { + 'define_macros': [], 'include_dirs': [], 'libraries': [], 'library_dirs': [], } for a in args: + info['define_macros'].extend(a.get('define_macros', [])) info['include_dirs'].extend(a.get('include_dirs', [])) info['libraries'].extend(a.get('libraries', [])) info['library_dirs'].extend(a.get('library_dirs', [])) From 03029b289269c37d2a4e3529782c1e069d5eae10 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Fri, 26 Nov 2010 04:27:08 -0500 Subject: [PATCH 20/26] Add a define to unbreak compilation with glib under MSVC on Windows. --- src/setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/setup.py b/src/setup.py index 5ed46a24c..82d440209 100755 --- a/src/setup.py +++ b/src/setup.py @@ -301,6 +301,8 @@ def def_split(x): if os.name == 'nt': # Windows systems: we just know what the OpenGL library is. gl_info = {'libraries': ['opengl32']} + # And glib needs a slight hack to work correctly. + glib_info['define_macros'].append(('inline', '__inline')) else: # Other systems: we ask pkg-config. gl_info = pc_info('gl') From 7513ec4d9fe781380a97c0f189d958cd5025bdf5 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Fri, 26 Nov 2010 18:59:27 -0500 Subject: [PATCH 21/26] Fix loading of modules depending on DLLs from the dependency pack. --- src/FoFiX.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/FoFiX.py b/src/FoFiX.py index 15125b747..8bc08e791 100644 --- a/src/FoFiX.py +++ b/src/FoFiX.py @@ -45,6 +45,11 @@ import os import Version +# Add the directory of DLL dependencies to the PATH if we're running +# from source on Windows so we pick them up when those bits are imported. +if os.name == 'nt' and not hasattr(sys, 'frozen'): + os.environ['PATH'] = os.path.abspath(os.path.join('..', 'win32', 'deps', 'bin')) + os.pathsep + os.environ['PATH'] + def _usage(errmsg=None): usage = """Usage: %(prog)s [options] From d97eabb0bc76c942f4063b634c349cc5b10f3318 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Sat, 27 Nov 2010 00:43:09 -0500 Subject: [PATCH 22/26] Generate .def files for the dependency DLLs at the end of the cross-build script. --- .gitignore | 1 + win32/makedefs.py | 122 ++++++++++++++++++++++++++++++++++++++++ win32/makedeps-cross.sh | 5 ++ win32/makedist.sh | 2 +- 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100755 win32/makedefs.py diff --git a/.gitignore b/.gitignore index 4d4e7a532..e6eee0860 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ data/themes/* !data/themes/Uberlight win32/* !win32/*.sh +!win32/*.py diff --git a/win32/makedefs.py b/win32/makedefs.py new file mode 100755 index 000000000..f55f324cb --- /dev/null +++ b/win32/makedefs.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# Script to create .defs from a folder of MinGW implibs and a folder of DLLs. +# Copyright (C) 2010 John Stumpo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import struct +import sys +import glob +import shlex +import re +import subprocess + +if len(sys.argv) != 4: + sys.stderr.write('''Usage: %s [implib-dir] [dll-dir] [identify-cmd] +For each MinGW-style import library in implib-dir, locates the corresponding +DLL in dll-dir and creates a .def file for it in implib-dir, which MSVC +tools can be run on to create an MSVC-compatible implib. + +identify-cmd is run on each implib to discover what DLL it goes with. It is +split into an argument list using sh-style rules, then the implib name is +added to the end of the argument list. + +The .def files are named [whatever goes after -l to link to the lib].def +''' % sys.argv[0]) + sys.exit(1) + +def make_def(file): + f = open(file, 'rb') + if f.read(2) != 'MZ': + raise ValueError, 'Incorrect magic number in file.' + f.seek(60) + pe_header_offset = struct.unpack('>sys.stderr, 'Could not get a unique DLL name from %s.' % implib + continue + dllname = dllnames[0].rstrip('\n') + + for dll in dlls: + if dll.lower() == dllname.lower(): + def_contents = make_def(os.path.join(sys.argv[2], dll)) + open(os.path.join(sys.argv[1], dash_l_name+'.def'), 'w').write(def_contents) diff --git a/win32/makedeps-cross.sh b/win32/makedeps-cross.sh index 190c59c14..e16a396af 100755 --- a/win32/makedeps-cross.sh +++ b/win32/makedeps-cross.sh @@ -60,6 +60,7 @@ assert_binary_on_path autoreconf assert_binary_on_path libtoolize assert_binary_on_path make assert_binary_on_path pkg-config +assert_binary_on_path python assert_binary_on_path svn assert_binary_on_path tar assert_binary_on_path unzip @@ -277,3 +278,7 @@ if test ! -f "$PREFIX"/build-stamps/ffmpeg; then fi echo "All dependencies done." + +echo -n "Creating .def files... " +python makedefs.py deps/lib deps/bin "$CROSS_DLLTOOL -I" +echo "done" diff --git a/win32/makedist.sh b/win32/makedist.sh index 7000c0e06..f37582bcd 100755 --- a/win32/makedist.sh +++ b/win32/makedist.sh @@ -4,7 +4,7 @@ mkdir -pv dist cp -av deps dist rm -rf dist/deps/build-stamps dist/deps/etc dist/deps/share dist/deps/lib/gettext -rm -vf dist/deps/lib/*.def dist/deps/lib/*.la dist/deps/bin/wine-shwrap dist/deps/bin/pkg-config +rm -vf dist/deps/lib/*.la dist/deps/bin/wine-shwrap dist/deps/bin/pkg-config i586-mingw32msvc-strip --strip-all dist/deps/bin/*.exe dist/deps/bin/*.dll ZIPFILE="fofix-win32-deppack-`date +%Y%m%d`.zip" rm -f "$ZIPFILE" From e93b0b2b0d073ad53f91047b6d0fe1a7290e4b56 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Sat, 27 Nov 2010 02:21:03 -0500 Subject: [PATCH 23/26] Use the .def files in the dep pack to create MSVC-compatible import libraries if necessary. --- src/setup.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/setup.py b/src/setup.py index 82d440209..07895d50f 100755 --- a/src/setup.py +++ b/src/setup.py @@ -24,7 +24,9 @@ # FoFiX fully unified setup script from distutils.core import setup, Extension -from Cython.Distutils import build_ext +import distutils.ccompiler +from distutils.dep_util import newer +from Cython.Distutils import build_ext as _build_ext import sys, SceneFactory, Version, glob, os import numpy as np import shlex @@ -329,6 +331,21 @@ def combine_info(*args): return info +# Extend the build_ext command further to rebuild the import libraries on +# an MSVC build under Windows so they actually work. +class build_ext(_build_ext): + def run(self, *args, **kw): + if self.compiler is None: + self.compiler = distutils.ccompiler.get_default_compiler() + if self.compiler == 'msvc': + msvc = distutils.ccompiler.new_compiler(compiler='msvc', verbose=self.verbose, dry_run=self.dry_run, force=self.force) + msvc.initialize() + for deffile in glob.glob(os.path.join('..', 'win32', 'deps', 'lib', '*.def')): + libfile = os.path.splitext(deffile)[0] + '.lib' + if newer(deffile, libfile): + msvc.spawn([msvc.lib, '/nologo', '/machine:x86', '/out:'+libfile, '/def:'+deffile]) + return _build_ext.run(self, *args, **kw) + # Add the common arguments to setup(). # This includes arguments to cause FoFiX's extension modules to be built. setup_args.update({ From 219488e9d96bf96cb8d269a787ac652bd6d7314e Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Sat, 27 Nov 2010 02:22:02 -0500 Subject: [PATCH 24/26] Add the dependency pack directory to the PATH in setup.py so py2exe picks up necessary DLLs from there. --- src/setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/setup.py b/src/setup.py index 07895d50f..f3ded54e3 100755 --- a/src/setup.py +++ b/src/setup.py @@ -362,5 +362,10 @@ def run(self, *args, **kw): 'cmdclass': {'build_ext': build_ext}, }) +# If we're on Windows, add the dependency directory to the PATH so py2exe will +# pick up necessary DLLs from there. +if os.name == 'nt': + os.environ['PATH'] = os.path.abspath(os.path.join('..', 'win32', 'deps', 'bin')) + os.pathsep + os.environ['PATH'] + # And finally... setup(**setup_args) From 05aa6ff420d7cb40fb055a04159fe021384dadcd Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Sat, 27 Nov 2010 02:22:43 -0500 Subject: [PATCH 25/26] Remove the pygst-related hacks from setup.py now that the pygst code is gone. --- src/setup.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/setup.py b/src/setup.py index f3ded54e3..5a0a5647b 100755 --- a/src/setup.py +++ b/src/setup.py @@ -130,17 +130,7 @@ def isSystemDLL(pathname): except ImportError: pass -# A couple things we need for pygst under Windows... extraDllExcludes = [] -if os.name == 'nt': - try: - # Get the pygst dirs into sys.path if they're there. - import pygst - pygst.require('0.10') - # Skip the DLLs used by pygst; the player code handles them itself. - extraDllExcludes += [os.path.basename(d) for d in glob.glob(os.path.join('..', 'gstreamer', 'bin', 'libgst*.dll'))] - except ImportError: - pass # Command-specific options shared between py2exe and py2app. common_options = { @@ -186,11 +176,6 @@ def isSystemDLL(pathname): "mswsock.dll", "powrprof.dll", "w9xpopen.exe", - "libgio-2.0-0.dll", - "libglib-2.0-0.dll", - "libgmodule-2.0-0.dll", - "libgobject-2.0-0.dll", - "libgthread-2.0-0.dll", ] + extraDllExcludes, "optimize": 2 }) From dd3a7d83dcd52db2ed82ae77e8fb72a2876c2763 Mon Sep 17 00:00:00 2001 From: John Stumpo Date: Sat, 27 Nov 2010 03:23:14 -0500 Subject: [PATCH 26/26] Update RunningFromSource to reflect the removal of gstreamer and the introduction of the win32 dependency pack. --- doc/RunningFromSource.mkd | 83 ++++++++++++--------------------------- 1 file changed, 26 insertions(+), 57 deletions(-) diff --git a/doc/RunningFromSource.mkd b/doc/RunningFromSource.mkd index 7ecd50c00..ff2f828dd 100644 --- a/doc/RunningFromSource.mkd +++ b/doc/RunningFromSource.mkd @@ -8,9 +8,8 @@ Table of Contents 3. [Notes on PyOpenGL versions](#Notes-on-PyOpenGL-versions) 4. [Setting up Python and third-party dependencies](#Setting-up-Python-and-third-party-dependencies) 5. [Compiling the native modules](#Compiling-the-native-modules) -6. [Dependencies for video playback](#Dependencies-for-video-playback) -7. [Starting the game](#Starting-the-game) -8. [Making binaries](#Making-binaries) +6. [Starting the game](#Starting-the-game) +7. [Making binaries](#Making-binaries) Checking out the latest code @@ -179,6 +178,20 @@ The following packages are optional: Install all packages by double-clicking the .exe or .msi files that you downloaded. +#### Installing the Win32 Dependency Pack + +Some code in FoFiX depends on external libraries written in C. The +`win32/` directory contains build scripts, but it can be difficult to +get the proper environment set up to use them. + +Since building and setting up these libraries can be difficult, we are +making available a prebuilt archive of everything you need to compile +FoFiX's native modules. Download the latest FoFiX Win32 Dependency Pack +from [here](http://www.mediafire.com/?x0000ohmctblb) and unzip it into the +`win32/` directory. (The `deps/` directory in the archive should become +a subdirectory of the `win32/` directory.) Now you are ready to compile +the native modules. + ### Mac OS X @@ -217,7 +230,8 @@ The following are required: * Python's development headers * A C++ compiler * Cython - * The OpenGL and GLU development headers + * pkg-config + * The OpenGL, GLU, GLib, libogg, libtheora, and libswscale (part of ffmpeg) development headers The following are optional (refer to the Windows instructions to see what each one is needed for): @@ -230,9 +244,10 @@ what each one is needed for): For those of you on Debian or Ubuntu, this means installing the following packages: `python-pygame`, `python-opengl`, `python-numpy`, `python-imaging`, `python-ogg`, `python-pyvorbis`, `python-dev`, -`build-essential`, `cython`, `libgl1-mesa-dev`, `libglu1-mesa-dev`. -If you're stuck without pygame 1.9, also install `python-numeric`. -If you want Psyco, install `python-psyco`. +`build-essential`, `cython`, `pkg-config`, `libgl1-mesa-dev`, +`libglu1-mesa-dev`, `libglib2.0-dev`, `libogg-dev`, `libtheora-dev`, +`libswscale-dev`. If you're stuck without pygame 1.9, also install +`python-numeric`. If you want Psyco, install `python-psyco`. Some packages can be troublesome, so we have notes below about certain packages. @@ -325,6 +340,10 @@ You will have to do this **every time** you receive changes to a `.c`, are in danger of weird crashes, and our first question will probably be whether or not you rebuilt the native modules. +(If `setup.py` complains about any programs or libraries being missing, +check that you have installed all of the dependencies, and for Windows +users, that the Win32 Dependency Pack is unpacked in the proper location.) + As for making sure you have a compiler, read the section for your operating system. @@ -355,56 +374,6 @@ Install the appropriate package from your distribution's repository. Under Debian and Ubuntu, you want `build-essential`. ----- - -Dependencies for video playback -------------------------------- - -_This section is **optional**, and it is only necessary if you want to -try out the current implementation, which will be replaced soon._ - -To play videos in FoFiX you currently need to install GStreamer. -We'll be moving away from GStreamer soon (before 4.0), but for now, -here are the instructions. - -_N.B.: While many types of video (i.e. anything the GStreamer installation -knows how to decode) work with the current code, the replacement video -code is likely not to support anything other than Theora._ - -Follow the instructions for your operating system. - -### Windows - -#### GStreamer binaries -Download a GStreamer binary repack: - - -Use 7-Zip or a compatible archiving program to unpack it into the root of -the FoFiX environment. (If done correctly, this should create a folder -named `gstreamer` on the same level as `data`, `doc`, `pkg`, `src`, and -`svg`, and the `gstreamer` folder should have as its direct children -folders named `bin`, `etc`, `lib`, `share`, and `src` and a file named -`COPYING`. Carefully check how you unpacked it if this is not the case.) - -#### PyGObject - -Download and run this: - - -#### PyGst - -Download and run this: - - -### Mac OS X - -Someone with a Mac will have to expand this section. - -### GNU/Linux - -Install the Python GStreamer bindings (pygst) and all of their -dependencies. Under Debian and Ubuntu, the package to install is -`python-gst0.10`. ----