Newer
Older
Import / web / www.xiaofrog.com / gallery / modules / ffmpeg / classes / FfmpegToolkitHelper.class
<?php
/*
 * Gallery - a web based photo album viewer and editor
 * Copyright (C) 2000-2008 Bharat Mediratta
 *
 * 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.
 */

/**
 * A helper class for FfmpegToolkit
 * @package Ffmpeg
 * @subpackage Classes
 * @author Bharat Mediratta <bharat@menalto.com>
 * @version $Revision: 18154 $
 * @static
 */
class FfmpegToolkitHelper {

    /**
     * Figure out what operations and properties are supported by the
     * FfmpegToolkit and return them.
     *
     * @return GalleryStatus a status code
     *         array('operations' => ..., 'properties' => ...)
     */
    function getOperationsAndProperties() {
	global $gallery;

	list ($ret, $ffmpegPath) = GalleryCoreApi::getPluginParameter('module', 'ffmpeg', 'path');
	if ($ret) {
	    return array($ret, null);
	}

	if (empty($ffmpegPath)) {
	    return array(GalleryCoreApi::error(ERROR_MISSING_VALUE), null);
	}

	list ($ret, $tests, $mimeTypes, $supportsOffset,
	      $mimeTypesEncoder, $encoderAudioCodecs, $version) =
		FfmpegToolkitHelper::testBinary($ffmpegPath);
	if ($ret) {
	    return array($ret, null);
	}

	/* -------------------- Operations -------------------- */

	/* extract */
	$operations['convert-to-image/jpeg']['params'] = array();
	$operations['convert-to-image/jpeg']['description'] = $gallery->i18n('Convert to a JPEG');
	$operations['convert-to-image/jpeg']['mimeTypes'] = $mimeTypes;
	$operations['convert-to-image/jpeg']['outputMimeType'] = 'image/jpeg';

	if ($supportsOffset) {
	    $operations['select-offset'] = array(
		'params' => array(array('type' => 'float',
					'description' => $gallery->i18n('offset in seconds'))),
		'description' => $gallery->i18n('Select time offset in movie file'),
		'mimeTypes' => $mimeTypes,
		'outputMimeType' => null);
	}

	/* Convert to Flash Video */
	if (in_array('video/x-flv', $mimeTypesEncoder)) {
	    $operations['convert-to-video/x-flv']['params'] = array();
	    $operations['convert-to-video/x-flv']['description'] =
		    $gallery->i18n('Convert to Flash Video');
	    $operations['convert-to-video/x-flv']['mimeTypes'] = $mimeTypes;
	    $operations['convert-to-video/x-flv']['outputMimeType'] = 'video/x-flv';

	    /* set-video-dimensions */
	    $operations['set-video-dimensions']['params'] = array(
		array('type' => 'int', 'description' => $gallery->i18n('video width in pixels')),
		array('type' => 'int', 'description' => $gallery->i18n('video height in pixels')));
	    $operations['set-video-dimensions']['description'] =
		    $gallery->i18n('Set video dimensions');
	    $operations['set-video-dimensions']['mimeTypes'] = $mimeTypes;
	    $operations['set-video-dimensions']['outputMimeType'] = null;

	    /* set-video-framerate */
	    $operations['set-video-framerate']['params'] = array(
		array('type' => 'float', 'description' => $gallery->i18n(
		      'frames per second')));
	    $operations['set-video-framerate']['description'] = $gallery->i18n(
		'Set video frames per second property');
	    $operations['set-video-framerate']['mimeTypes'] = $mimeTypes;
	    $operations['set-video-framerate']['outputMimeType'] = null;

	    $operations['set-video-sameq']['params'] = array();
	    $operations['set-video-sameq']['description'] =
		    $gallery->i18n('Set same video quality');
	    $operations['set-video-sameq']['mimeTypes'] = $mimeTypes;
	    $operations['set-video-sameq']['outputMimeType'] = null;

	    /* If mp3 audio supported */
	    if (in_array('mp3', $encoderAudioCodecs)) {
		/* set-audio-channels */
		$operations['set-audio-channels']['params'] = array(
		    array('type' => 'int', 'description' => $gallery->i18n(
			  'number of channels')));
		$operations['set-audio-channels']['description'] = $gallery->i18n(
		    'Set audio channels property');
		$operations['set-audio-channels']['mimeTypes'] = $mimeTypes;
		$operations['set-audio-channels']['outputMimeType'] = null;

		/* set-audio-samplerate */
		$operations['set-audio-samplerate']['params'] = array(
		    array('type' => 'int', 'description' => $gallery->i18n(
			  'audio sample rate (Hz / cycles per second)')));
		$operations['set-audio-samplerate']['description'] = $gallery->i18n(
		    'Set audio rate property');
		$operations['set-audio-samplerate']['mimeTypes'] = $mimeTypes;
		$operations['set-audio-samplerate']['outputMimeType'] = null;
	    }
	}

	/* -------------------- Properties -------------------- */

	/* Dimensions */
	$properties['dimensions']['type'] = 'int,int';
	$properties['dimensions']['description'] =
	    $gallery->i18n('Get the width and height of the movie');
	$properties['dimensions']['mimeTypes'] = $mimeTypes;

	$properties['dimensions-and-duration']['type'] = 'int,int,float';
	$properties['dimensions-and-duration']['description'] =
	    $gallery->i18n('Get the width, height and duration of the movie');
	$properties['dimensions-and-duration']['mimeTypes'] = $mimeTypes;

	$properties['video-framerate']['type'] = 'float';
	$properties['video-framerate']['description'] =
	    $gallery->i18n('Get video frames per seconds of the movie');
	$properties['video-framerate']['mimeTypes'] = $mimeTypes;

	$properties['audio-samplerate']['type'] = 'int';
	$properties['audio-samplerate']['description'] =
	    $gallery->i18n('Get audio samples per seconds of the movie');
	$properties['audio-samplerate']['mimeTypes'] = $mimeTypes;

	$properties['audio-channels']['type'] = 'int';
	$properties['audio-channels']['description'] =
	    $gallery->i18n('Get number of audio channels in movie');
	$properties['audio-channels']['mimeTypes'] = $mimeTypes;

	return array(null, array('operations' => $operations, 'properties' => $properties));
    }

    /**
     * Test if the given path has a working Ffmpeg binary.
     *
     * This is done by calling the binary with the -formats flag and
     * making sure it runs properly.
     *
     * @param string $ffmpegPath path to the Ffmpeg we are testing
     * @return array GalleryStatus general status of tests
     *         array of array ('name' => string: the name of the binary,
     *                         'success' => boolean: test successful?
     *                         'results' => string: the ffmpeg output)
     *         array hash map of mime types
     *         boolean true if ffmpeg supports -ss time_offset
     *         array of strings listing mime types that can be encoded
     *         array of strings listing available audio codecs for encoding
     *         string containing the ffmpeg version
     */
    function testBinary($ffmpegPath) {
	global $gallery;
	$platform =& $gallery->getPlatform();

	/*
	 * If the path is not restricted by open_basedir, then verify that it's legal.
	 * Else just hope that it's valid and use it.
	 */
	if (!$platform->isRestrictedByOpenBaseDir($ffmpegPath)) {
	    if (!$platform->file_exists($ffmpegPath) || !$platform->is_file($ffmpegPath)) {
		return array(GalleryCoreApi::error(ERROR_BAD_PATH),
			     null, null, null, null, null, null);
	    }
	} else {
	    return array(GalleryCoreApi::error(ERROR_BAD_PATH, null, null, '"' . $ffmpegPath
			 . '" is not specified in open_basedir.'), null,
			 null, null, null, null, null);
	}

	/* We only care about video for now */
	/** @todo: Add '3g2' => '3g2', '3gp' => '3gp' when recognized as videos */
	$relevantTypes = array('mpeg' => 'mpeg', 'asf' => 'asf', 'avi' => 'avi',
			       'mov' => 'mov', 'wmv1' => 'wmv', 'flv' => 'flv', 'mp4' => 'mp4');
	/* We only care about encoding flash video for now */
	$relevantEncode = array('flv' => 'flv');
	/**
	 * @todo: Add 'adpcm_swf' => 'adpcm_swf' to relevantAudioCodecs if/when videos play
	 *        correctly
	 */
	$relevantAudioCodecs = array('mp3' => 'mp3', 'libmp3lame' => 'mp3');
	list ($ignored, $results) = $platform->exec(array(array($ffmpegPath, '-formats')));
	$version = array();
	list ($ignored, $ignored, $version) =
		$platform->exec(array(array($ffmpegPath, '-version')));

	$mimeTypes = array();
	$mimeTypesEncoder = array();
	$encoderAudioCodecs = array();

	$success = false;
	$i = 0;
	while ($i < sizeof($results)) {
	    $resultLine = $results[$i++];
	    /*
	     * ffmpeg 0.4.6 says:
	     * File formats:
	     *   Decoding: mpeg mpegts pgm pgmyuv ppm .Y.U.V pgmpipe pgmyuvpipe
	     * ppmpipe mp3 ac3 m4v mpegvideo mjpeg s16le s16be u16le u16be s8 u8 mulaw alaw
	     * rawvideo rm asf avi wav swf au mov jpeg dv ffm video_grab_device
	     * audio_device rtsp redir sdp rtp
	     * [multiple lines]
	     * Codecs:
	     * ....
	     *
	     * ffmpeg 0.4.7 says:
	     * Input audio/video file formats: mpeg mpegts image ...
	     * [all on one line]
	     *
	     * ffmpeg 0.4.8 says:
	     * File formats:
	     *   E 3gp
	     *  D  4xm
	     *  D  RoQ
	     *  DE ac3
	     *  DE ala
	     *  DE asf
	     *  E asf
	     * ...
	     * Image formats:
	     *  D  pnm
	     *   E pbm
	     *   E pgm
	     * ...
	     * Codecs:
	     *  D V    4xm
	     *  D V D  8bps
	     *   EA    ac3
	     *  DEA    adpcm_4xm
	     */
	    if (preg_match('|Input audio/video file formats: (.*)$|', $resultLine, $regs)) {
		/* We have found 0.4.7 format, read single line */
		/** @todo: Support mimeTypesEncoder and encoderAudioCodecs */
		foreach (explode(' ', $regs[1]) as $type) {
		    if (isset($relevantTypes[$type])) {
			list ($ret, $mime) =
			    GalleryCoreApi::convertExtensionToMime($relevantTypes[$type]);
			if ($ret) {
			    return array($ret, null, null, null, null, null, null);
			}
			$mimeTypes[$mime] = 1;
		    }
		}
		$success = true;
	    } else if (preg_match('/  Decod(?:ing|ers): (.*)$/', $resultLine, $regs)) {
		/* We have found 0.4.6 format, read until Codec: line */
		/** @todo: Support mimeTypesEncoder and encoderAudioCodecs */
		do {
		    foreach (explode(' ', $regs[1]) as $type) {
			if (isset($relevantTypes[$type])) {
			    list ($ret, $mime) = GalleryCoreApi::convertExtensionToMime(
				$relevantTypes[$type]);
			    if ($ret) {
				return array($ret, null, null, null, null, null, null);
			    }
			    $mimeTypes[$mime] = 1;
			}
		    }
		    $resultLine = $results[$i++];
		} while ($i < sizeof($results) && !preg_match('|Codecs:|', $resultLine));
		$success = true;
	    } else if (preg_match('/(File formats|Codecs):/', $resultLine, $regs)) {
		$resultLine = $results[$i++];
		if (preg_match('/^  \w+: /', $resultLine)) {
		    /* We have found 0.4.6 format */
		    continue;
		}
		if (!strcmp('File formats', $regs[1])) {
		    /* Parse File formats */
		    do {
			list ($capabilities, $types) =
			    preg_split('/\s+/', $resultLine, -1, PREG_SPLIT_NO_EMPTY);
			foreach (explode(',', $types) as $type) {
			    if (isset($relevantTypes[$type])) {
				list ($ret, $mime) = GalleryCoreApi::convertExtensionToMime(
				    $relevantTypes[$type]);
				if ($ret) {
				    return array($ret, null, null, null, null, null, null);
				}
				if (preg_match('|D|', $capabilities)) {
				    $mimeTypes[$mime] = 1;
				}
				if (isset($relevantEncode[$type])
					&& preg_match('|E|', $capabilities)) {
				    $mimeTypesEncoder[$mime] = 1;
				}
			    }
			}

			$resultLine = $results[$i++];
		    } while (!empty($resultLine));
		    $success = true;
		} else {
		    /* Parse codecs */
		    do {
			$type = substr($resultLine, 8);
			$decode = ('D' == substr($resultLine, 1, 1));
			$encode = ('E' == substr($resultLine, 2, 1));
			$audio  = ('A' == substr($resultLine, 3, 1));
			$video  = ('V' == substr($resultLine, 3, 1));
			if (isset($relevantAudioCodecs[$type]) && $encode && $audio) {
			    $encoderAudioCodecs[$relevantAudioCodecs[$type]] = 1;
			}
			/* Check relevant types too since wmv uses an asf container */
			if (isset($relevantTypes[$type]) && $decode && $video) {
			    list ($ret, $mime) = GalleryCoreApi::convertExtensionToMime(
				$relevantTypes[$type]);
			    if ($ret) {
				return array($ret, null, null, null, null, null, null);
			    }
			    $mimeTypes[$mime] = 1;
			}
			$resultLine = $results[$i++];
		    } while (!empty($resultLine));
		}
	    }
	}
	$tests[] = array('name' => 'ffmpeg',
			 'success' => $success,
			 'results' => $results);

	/* Test if this ffmpeg supports -ss flag.. */
	$supportsOffset = false;
	list ($ok, $flags) = $platform->exec(array(array($ffmpegPath, '-h')));
	foreach ($flags as $line) {
	    if (!strncmp($line, '-ss ', 4)) {
		$supportsOffset = true;
		break;
	    }
	}

	return array(null, $tests, array_keys($mimeTypes), $supportsOffset,
		     array_keys($mimeTypesEncoder), array_keys($encoderAudioCodecs), $version);
    }
}
?>