Added MKV source/worker plugins, tidied sources page

Added placeholder for source plugin to read from an existing mkv file.
Added corresponding worker placeholder to transcode an mkv file using
ffmpeg.
Updated the sources page to show which sources come from which plugins.
This commit is contained in:
2010-09-24 20:05:37 +01:00
parent 3977dc6038
commit 2ef47de25c
11 changed files with 274 additions and 31 deletions

View File

@@ -132,8 +132,8 @@ class RippingCluster_Config {
} }
switch ($this->settings[$key]['type']) { switch ($this->settings[$key]['type']) {
case TYPE_STRING_LIST: case self::TYPE_STRING_LIST:
return explode("\n", $this->settings[$key]['value']); return array_map('trim', explode("\n", $this->settings[$key]['value']));
default: default:
return $this->settings[$key]['value']; return $this->settings[$key]['value'];

View File

@@ -18,7 +18,7 @@ class RippingCluster_Source {
$this->plugin = $plugin; $this->plugin = $plugin;
} }
public static function isCached($source_filename) { public static function isSourceCached($source_filename) {
$main = RippingCluster_Main::instance(); $main = RippingCluster_Main::instance();
$cache = $main->cache(); $cache = $main->cache();
$config = $main->config(); $config = $main->config();
@@ -26,6 +26,14 @@ class RippingCluster_Source {
return $cache->exists($source_filename, $config->get('rips.cache_ttl')); return $cache->exists($source_filename, $config->get('rips.cache_ttl'));
} }
public function isCached() {
$main = RippingCluster_Main::instance();
$cache = $main->cache();
$config = $main->config();
return $cache->exists($this->filename, $config->get('rips.cache_ttl'));
}
public function cache() { public function cache() {
if (!$this->exists) { if (!$this->exists) {
throw new RippingCluster_Exception_InvalidSourceDirectory(); throw new RippingCluster_Exception_InvalidSourceDirectory();

View File

@@ -19,13 +19,12 @@ class RippingCluster_Source_Plugin_Bluray extends RippingCluster_PluginBase impl
$config = RippingCluster_Main::instance()->config(); $config = RippingCluster_Main::instance()->config();
$directories = $config->get('source.bluray.dir'); $directories = $config->get('source.bluray.dir');
$sources = array();
foreach ($directories as $directory) { foreach ($directories as $directory) {
if (!is_dir($directory)) { if (!is_dir($directory)) {
throw new RippingCluster_Exception_InvalidSourceDirectory($directory); throw new RippingCluster_Exception_InvalidSourceDirectory($directory);
} }
$sources = array();
$iterator = new RippingCluster_Utility_BlurayDirectoryIterator(new RippingCluster_Utility_VisibleFilesIterator(new DirectoryIterator($directory))); $iterator = new RippingCluster_Utility_BlurayDirectoryIterator(new RippingCluster_Utility_VisibleFilesIterator(new DirectoryIterator($directory)));
foreach ($iterator as /** @var SplFileInfo */ $source_vts) { foreach ($iterator as /** @var SplFileInfo */ $source_vts) {
$sources[] = self::load($source_vts->getPathname(), false); $sources[] = self::load($source_vts->getPathname(), false);

View File

@@ -25,13 +25,12 @@ class RippingCluster_Source_Plugin_HandBrake extends RippingCluster_PluginBase i
$config = RippingCluster_Main::instance()->config(); $config = RippingCluster_Main::instance()->config();
$directories = $config->get('source.handbrake.dir'); $directories = $config->get('source.handbrake.dir');
$sources = array();
foreach ($directories as $directory) { foreach ($directories as $directory) {
if (!is_dir($directory)) { if (!is_dir($directory)) {
throw new RippingCluster_Exception_InvalidSourceDirectory($directory); throw new RippingCluster_Exception_InvalidSourceDirectory($directory);
} }
$sources = array();
$iterator = new RippingCluster_Utility_DvdDirectoryIterator(new RippingCluster_Utility_VisibleFilesIterator(new DirectoryIterator($directory))); $iterator = new RippingCluster_Utility_DvdDirectoryIterator(new RippingCluster_Utility_VisibleFilesIterator(new DirectoryIterator($directory)));
foreach ($iterator as /** @var SplFileInfo */ $source_vts) { foreach ($iterator as /** @var SplFileInfo */ $source_vts) {
$sources[] = self::load($source_vts->getPathname(), false); $sources[] = self::load($source_vts->getPathname(), false);
@@ -207,15 +206,15 @@ class RippingCluster_Source_Plugin_HandBrake extends RippingCluster_PluginBase i
// Check all of the source directories specified in the config // Check all of the source directories specified in the config
$source_directories = $config->get('source.handbrake.dir'); $source_directories = $config->get('source.handbrake.dir');
foreach ($source_directories as $source_basedir) { foreach ($source_directories as $source_basedir) {
$real_source_basedir = realpath($source_basedir); $real_source_basedir = realpath($source_basedir);
if (substr($real_source_filename, 0, strlen($real_source_basedir)) != $real_source_basedir) { if (substr($real_source_filename, 0, strlen($real_source_basedir)) == $real_source_basedir) {
return false; return true;
} }
} }
return true; return false;
} }
} }

View File

@@ -0,0 +1,128 @@
<?php
class RippingCluster_Source_Plugin_MkvInfo extends RippingCluster_PluginBase implements RippingCluster_Source_IPlugin {
/**
* Name of this plugin
* @var string
*/
const PLUGIN_NAME = 'MkvInfo';
/**
* Name of the config setting that stores the list of source directories for this pluing
* @var string
*/
const CONFIG_SOURCE_DIR = 'source.mkvinfo.dir';
/**
* Returns a list of all Sources discovered by this plugin.
*
* The sources are not scanned until specifically requested.
*
* @return array(RippingCluster_Source)
*/
public static function enumerate() {
$config = RippingCluster_Main::instance()->config();
$directories = $config->get(self::CONFIG_SOURCE_DIR);
$sources = array();
foreach ($directories as $directory) {
if (!is_dir($directory)) {
throw new RippingCluster_Exception_InvalidSourceDirectory($directory);
}
$iterator = new RippingCluster_Utility_MkvFileIterator(new RecursiveIteratorIterator(new RippingCluster_Utility_VisibleFilesRecursiveIterator(new RecursiveDirectoryIterator($directory))));
foreach ($iterator as /** @var SplFileInfo */ $source_mkv) {
$sources[] = self::load($source_mkv->getPathname(), false);
}
}
return $sources;
}
/**
* Creates an object to represent the given source.
*
* The source is not actually scanned unless specifically requested.
* An unscanned object cannot be used until it has been manually scanned.
*
* If requested, the source can be cached to prevent high load, and long scan times.
*
* @param string $source_filename Filename of the source
* @param bool $scan Request that the source be scanned for content. Defaults to true.
* @param bool $use_cache Request that the cache be used. Defaults to true.
* @return RippingCluster_Source
*/
public static function load($source_filename, $scan = true, $use_cache = true) {
$cache = RippingCluster_Main::instance()->cache();
// Ensure the source is a valid directory, and lies below the configured source_dir
if ( ! self::isValidSource($source_filename)) {
return new RippingCluster_Source($source_filename, self::name(), false);
}
$source = null;
if ($use_cache && $cache->exists($source_filename)) {
$source = unserialize($cache->fetch($source_filename));
} else {
$source = new RippingCluster_Source($source_filename, self::name(), true);
// TODO Populate source object with content
// If requested, store the new source object in the cache
if ($use_cache) {
$source->cache();
}
}
}
/**
* Creates an object to represent the given source using an encoded filename.
*
* Wraps the call to load the source after the filename has been decoded.
*
* @param string $encoded_filename Encoded filename of the source
* @param bool $scan Request that the source be scanned for content. Defaults to true.
* @param bool $use_cache Request that the cache be used. Defaults to true.
* @return RippingCluster_Source
*
* @see RippingCluster_Source_IPlugin::load()
*/
public static function loadEncoded($encoded_filename, $scan = true, $use_cache = true) {
// Decode the filename
$source_filename = base64_decode(str_replace('-', '/', $encoded_filename));
return self::load($source_filename, $scan, $use_cache);
}
/**
* Determins if a filename is a valid source loadable using this plugin
*
* @param string $source_filename Filename of the source
* @return bool
*/
public static function isValidSource($source_filename) {
$config = RippingCluster_Main::instance()->config();
// Ensure the source is a valid directory, and lies below the configured source_dir
if ( ! is_dir($source_filename)) {
return false;
}
$real_source_filename = realpath($source_filename);
// Check all of the source directories specified in the config
$source_directories = $config->get(self::CONFIG_SOURCE_DIR);
foreach ($source_directories as $source_basedir) {
$real_source_basedir = realpath($source_basedir);
if (substr($real_source_filename, 0, strlen($real_source_basedir)) != $real_source_basedir) {
return false;
}
}
return true;
}
}
?>

View File

@@ -31,7 +31,7 @@ class RippingCluster_Source_PluginFactory extends RippingCluster_PluginFactory {
$sources = array(); $sources = array();
foreach (self::getValidPlugins() as $plugin) { foreach (self::getValidPlugins() as $plugin) {
$sources = array_merge($sources, self::enumerate($plugin)); $sources[$plugin] = self::enumerate($plugin);
} }
return $sources; return $sources;

View File

@@ -0,0 +1,9 @@
<?php
class RippingCluster_Utility_MkvFileIterator extends FilterIterator {
public function accept() {
return preg_match('/\.mkv$/i', $this->current()->getFilename());
}
}
?>

View File

@@ -0,0 +1,9 @@
<?php
class RippingCluster_Utility_VisibleFilesRecursiveIterator extends RecursiveFilterIterator {
public function accept() {
return !(substr($this->current()->getFilename(), 0, 1) == '.');
}
}
?>

View File

@@ -0,0 +1,85 @@
<?php
class RippingCluster_Worker_FfmpegTranscode extends RippingCluster_PluginBase implements RippingCluster_Worker_IPlugin {
/**
* Name of this plugin
* @var string
*/
const PLUGIN_NAME = 'FfmpegTranscode';
/**
* Output produced by the worker process
* @var string
*/
private $output;
/**
* Gearman Job object describing the task distributed to this worker
* @var GearmanJob
*/
private $gearman_job;
/**
* Ripping Job that is being processed by this Worker
* @var RippingCluster_Job
*/
private $job;
/**
* Associative array of options describing the rip to be carried out
* @var array(string=>string)
*/
private $rip_options;
/**
* Constructs a new instance of this Worker class
*
* @param GearmanJob $gearman_job GearmanJob object describing the task distributed to this worker
* @throws RippingCluster_Exception_LogicException
*/
private function __construct(GearmanJob $gearman_job) {
$this->output = '';
$this->gearman_job = $gearman_job;
$this->rip_options = unserialize($this->gearman_job->workload());
if ( ! $this->rip_options['id']) {
throw new RippingCluster_Exception_LogicException("Job ID must not be zero/null");
}
$this->job = RippingCluster_Job::fromId($this->rip_options['id']);
}
/**
* Returns the list of functions (and names) implemented by this plugin for registration with Gearman
*
* @return array(string => callback)
*/
public static function workerFunctions() {
return array(
'bluray_rip' => array(__CLASS__, 'rip'),
);
}
/**
* Creates an instance of the Worker plugin, and uses it to execute a single job
*
* @param GearmanJob $job Gearman Job object, describing the work to be done
*/
public static function rip(GearmanJob $job) {
$rip = new self($job);
$rip->execute();
}
/**
* Executes the process for ripping the source to the final output
*
*/
private function execute() {
// TODO
}
}
?>

View File

@@ -3,14 +3,7 @@
$main = RippingCluster_Main::instance(); $main = RippingCluster_Main::instance();
$config = $main->config(); $config = $main->config();
$sources = RippingCluster_Source_PluginFactory::enumerateAll(); $all_sources = RippingCluster_Source_PluginFactory::enumerateAll();
$this->smarty->assign('all_sources', $all_sources);
$sources_cached = array();
foreach ($sources as $source) {
$sources_cached[$source->filename()] = RippingCluster_Source::isCached($source->filename());
}
$this->smarty->assign('sources', $sources);
$this->smarty->assign('sources_cached', $sources_cached);
?> ?>

View File

@@ -1,6 +1,6 @@
<h2>Sources</h2> <h2>Sources</h2>
{if $sources} {if $all_sources}
<p> <p>
The list below contains all the DVD sources that are available and ready for ripping. The list below contains all the DVD sources that are available and ready for ripping.
</p> </p>
@@ -9,19 +9,32 @@
Sources that have not been cached will be scanned when the link is clicked, and this may take several minutes so please be patient. Sources that have not been cached will be scanned when the link is clicked, and this may take several minutes so please be patient.
</p> </p>
<ul> <ul>
{foreach from=$sources item=source} {foreach from=$all_sources key=type item=sources}
{assign var='source_plugin' value=$source->plugin()} <li>{$type}
{assign var='source_filename' value=$source->filename()} {if $sources}
{assign var='source_filename_encoded' value=$source->filenameEncoded()} <ul>
<li> {foreach from=$sources item=source}
[ <a href="{$base_uri}rips/source-details/plugin/{$source_plugin}/id/{$source_filename_encoded}" title="Browse source details">Browse</a> | {assign var='source_plugin' value=$source->plugin()}
<a href="{$base_uri}rips/setup-rip/plugin/{$source_plugin}/id/{$source_filename_encoded}" title="Rip this source">Rip</a> ] {assign var='source_filename' value=$source->filename()}
{$source_filename|escape:'html'}{if $sources_cached.$source_filename} (cached){/if} {assign var='source_filename_encoded' value=$source->filenameEncoded()}
{assign var='source_cached' value="$source->isCached()}
<li>
[ <a href="{$base_uri}rips/source-details/plugin/{$source_plugin}/id/{$source_filename_encoded}" title="Browse source details">Browse</a> |
<a href="{$base_uri}rips/setup-rip/plugin/{$source_plugin}/id/{$source_filename_encoded}" title="Rip this source">Rip</a> ]
{$source_filename|escape:'html'}{if $source_cached} (cached){/if}
</li>
{/foreach}
</ul>
{else}
<p>
<em>There are no {$type} sources available to rip.</em>
</p>
{/if}
</li> </li>
{/foreach} {/foreach}
</ul> </ul>
{else} {else}
<p> <p>
<em>There are currently no DVD sources available to rip.</em> <em>There are currently no sources available to rip.</em>
</p> </p>
{/if} {/if}