From 2ef47de25c32646cafdd0f1f0fa0581aedbecf02 Mon Sep 17 00:00:00 2001
From: Ben Roberts
Date: Fri, 24 Sep 2010 20:05:37 +0100
Subject: [PATCH 1/4] 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.
---
lib/RippingCluster/Config.class.php | 4 +-
lib/RippingCluster/Source.class.php | 10 +-
.../Source/Plugin/Bluray.class.php | 3 +-
.../Source/Plugin/HandBrake.class.php | 11 +-
.../Source/Plugin/MkvInfo.class.php | 128 ++++++++++++++++++
.../Source/PluginFactory.class.php | 2 +-
.../Utility/MkvFileIterator.class.php | 9 ++
.../VisibleFilesRecursiveIterator.class.php | 9 ++
.../Worker/Plugin/FfmpegTranscode.class.php | 85 ++++++++++++
webui/pages/rips/sources.php | 11 +-
webui/templates/rips/sources.tpl | 33 +++--
11 files changed, 274 insertions(+), 31 deletions(-)
create mode 100644 lib/RippingCluster/Source/Plugin/MkvInfo.class.php
create mode 100644 lib/RippingCluster/Utility/MkvFileIterator.class.php
create mode 100644 lib/RippingCluster/Utility/VisibleFilesRecursiveIterator.class.php
create mode 100644 lib/RippingCluster/Worker/Plugin/FfmpegTranscode.class.php
diff --git a/lib/RippingCluster/Config.class.php b/lib/RippingCluster/Config.class.php
index 88d6064..66785a5 100644
--- a/lib/RippingCluster/Config.class.php
+++ b/lib/RippingCluster/Config.class.php
@@ -132,8 +132,8 @@ class RippingCluster_Config {
}
switch ($this->settings[$key]['type']) {
- case TYPE_STRING_LIST:
- return explode("\n", $this->settings[$key]['value']);
+ case self::TYPE_STRING_LIST:
+ return array_map('trim', explode("\n", $this->settings[$key]['value']));
default:
return $this->settings[$key]['value'];
diff --git a/lib/RippingCluster/Source.class.php b/lib/RippingCluster/Source.class.php
index e602767..c3e907c 100644
--- a/lib/RippingCluster/Source.class.php
+++ b/lib/RippingCluster/Source.class.php
@@ -18,7 +18,7 @@ class RippingCluster_Source {
$this->plugin = $plugin;
}
- public static function isCached($source_filename) {
+ public static function isSourceCached($source_filename) {
$main = RippingCluster_Main::instance();
$cache = $main->cache();
$config = $main->config();
@@ -26,6 +26,14 @@ class RippingCluster_Source {
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() {
if (!$this->exists) {
throw new RippingCluster_Exception_InvalidSourceDirectory();
diff --git a/lib/RippingCluster/Source/Plugin/Bluray.class.php b/lib/RippingCluster/Source/Plugin/Bluray.class.php
index b3a8c80..96c6082 100644
--- a/lib/RippingCluster/Source/Plugin/Bluray.class.php
+++ b/lib/RippingCluster/Source/Plugin/Bluray.class.php
@@ -19,13 +19,12 @@ class RippingCluster_Source_Plugin_Bluray extends RippingCluster_PluginBase impl
$config = RippingCluster_Main::instance()->config();
$directories = $config->get('source.bluray.dir');
+ $sources = array();
foreach ($directories as $directory) {
if (!is_dir($directory)) {
throw new RippingCluster_Exception_InvalidSourceDirectory($directory);
}
- $sources = array();
-
$iterator = new RippingCluster_Utility_BlurayDirectoryIterator(new RippingCluster_Utility_VisibleFilesIterator(new DirectoryIterator($directory)));
foreach ($iterator as /** @var SplFileInfo */ $source_vts) {
$sources[] = self::load($source_vts->getPathname(), false);
diff --git a/lib/RippingCluster/Source/Plugin/HandBrake.class.php b/lib/RippingCluster/Source/Plugin/HandBrake.class.php
index d961023..aec5f88 100644
--- a/lib/RippingCluster/Source/Plugin/HandBrake.class.php
+++ b/lib/RippingCluster/Source/Plugin/HandBrake.class.php
@@ -25,13 +25,12 @@ class RippingCluster_Source_Plugin_HandBrake extends RippingCluster_PluginBase i
$config = RippingCluster_Main::instance()->config();
$directories = $config->get('source.handbrake.dir');
+ $sources = array();
foreach ($directories as $directory) {
if (!is_dir($directory)) {
throw new RippingCluster_Exception_InvalidSourceDirectory($directory);
}
- $sources = array();
-
$iterator = new RippingCluster_Utility_DvdDirectoryIterator(new RippingCluster_Utility_VisibleFilesIterator(new DirectoryIterator($directory)));
foreach ($iterator as /** @var SplFileInfo */ $source_vts) {
$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
$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);
- if (substr($real_source_filename, 0, strlen($real_source_basedir)) != $real_source_basedir) {
- return false;
+ if (substr($real_source_filename, 0, strlen($real_source_basedir)) == $real_source_basedir) {
+ return true;
}
}
- return true;
+ return false;
}
}
diff --git a/lib/RippingCluster/Source/Plugin/MkvInfo.class.php b/lib/RippingCluster/Source/Plugin/MkvInfo.class.php
new file mode 100644
index 0000000..c16ea8c
--- /dev/null
+++ b/lib/RippingCluster/Source/Plugin/MkvInfo.class.php
@@ -0,0 +1,128 @@
+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;
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/lib/RippingCluster/Source/PluginFactory.class.php b/lib/RippingCluster/Source/PluginFactory.class.php
index 89d6618..ec25bdc 100644
--- a/lib/RippingCluster/Source/PluginFactory.class.php
+++ b/lib/RippingCluster/Source/PluginFactory.class.php
@@ -31,7 +31,7 @@ class RippingCluster_Source_PluginFactory extends RippingCluster_PluginFactory {
$sources = array();
foreach (self::getValidPlugins() as $plugin) {
- $sources = array_merge($sources, self::enumerate($plugin));
+ $sources[$plugin] = self::enumerate($plugin);
}
return $sources;
diff --git a/lib/RippingCluster/Utility/MkvFileIterator.class.php b/lib/RippingCluster/Utility/MkvFileIterator.class.php
new file mode 100644
index 0000000..37bd51d
--- /dev/null
+++ b/lib/RippingCluster/Utility/MkvFileIterator.class.php
@@ -0,0 +1,9 @@
+current()->getFilename());
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/lib/RippingCluster/Utility/VisibleFilesRecursiveIterator.class.php b/lib/RippingCluster/Utility/VisibleFilesRecursiveIterator.class.php
new file mode 100644
index 0000000..151d938
--- /dev/null
+++ b/lib/RippingCluster/Utility/VisibleFilesRecursiveIterator.class.php
@@ -0,0 +1,9 @@
+current()->getFilename(), 0, 1) == '.');
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/lib/RippingCluster/Worker/Plugin/FfmpegTranscode.class.php b/lib/RippingCluster/Worker/Plugin/FfmpegTranscode.class.php
new file mode 100644
index 0000000..8d63063
--- /dev/null
+++ b/lib/RippingCluster/Worker/Plugin/FfmpegTranscode.class.php
@@ -0,0 +1,85 @@
+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
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/webui/pages/rips/sources.php b/webui/pages/rips/sources.php
index 3aadca0..b9fc539 100644
--- a/webui/pages/rips/sources.php
+++ b/webui/pages/rips/sources.php
@@ -3,14 +3,7 @@
$main = RippingCluster_Main::instance();
$config = $main->config();
-$sources = RippingCluster_Source_PluginFactory::enumerateAll();
-
-$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);
+$all_sources = RippingCluster_Source_PluginFactory::enumerateAll();
+$this->smarty->assign('all_sources', $all_sources);
?>
\ No newline at end of file
diff --git a/webui/templates/rips/sources.tpl b/webui/templates/rips/sources.tpl
index 46951af..c0cbb1a 100644
--- a/webui/templates/rips/sources.tpl
+++ b/webui/templates/rips/sources.tpl
@@ -1,6 +1,6 @@
Sources
-{if $sources}
+{if $all_sources}
The list below contains all the DVD sources that are available and ready for ripping.
@@ -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.
{else}
- There are currently no DVD sources available to rip.
+ There are currently no sources available to rip.
{/if}
From ec4cc8dad44e2d302c687d4f84e93731d101993f Mon Sep 17 00:00:00 2001
From: Ben Roberts
Date: Sat, 25 Sep 2010 17:11:47 +0100
Subject: [PATCH 2/4] Removes obsolete status callback from gearman client
Status callback function was previously removed from run-jobs, but the
callback was still being registered. This change removes the
registration to prevent a warning.
---
webui/run-jobs.php | 1 -
1 file changed, 1 deletion(-)
diff --git a/webui/run-jobs.php b/webui/run-jobs.php
index d553603..afd64ee 100644
--- a/webui/run-jobs.php
+++ b/webui/run-jobs.php
@@ -14,7 +14,6 @@ try {
$gearman->addServers($config->get('rips.job_servers'));
$gearman->setCreatedCallback("gearman_created_callback");
$gearman->setDataCallback("gearman_data_callback");
- $gearman->setStatusCallback("gearman_status_callback");
$gearman->setCompleteCallback("gearman_complete_callback");
$gearman->setFailCallback("gearman_fail_callback");
From 8f88fba0cafa61c39ca98fffcca3e70724a1a0dc Mon Sep 17 00:00:00 2001
From: Ben Roberts
Date: Tue, 12 Oct 2010 19:06:27 +0100
Subject: [PATCH 3/4] Added set accessors to Source objects
For Source plugins that can't parse all the required information before
creating the object, set accessors are needed to populate the
information afterwards.
---
.../Rips/SourceAudioTrack.class.php | 24 +++++++++++++++++++
.../Rips/SourceSubtitleTrack.class.php | 12 ++++++++++
lib/RippingCluster/Rips/SourceTitle.class.php | 20 ++++++++++++++++
3 files changed, 56 insertions(+)
diff --git a/lib/RippingCluster/Rips/SourceAudioTrack.class.php b/lib/RippingCluster/Rips/SourceAudioTrack.class.php
index e753f01..6b7e81e 100644
--- a/lib/RippingCluster/Rips/SourceAudioTrack.class.php
+++ b/lib/RippingCluster/Rips/SourceAudioTrack.class.php
@@ -28,26 +28,50 @@ class RippingCluster_Rips_SourceAudioTrack {
return $name;
}
+ public function setName($name) {
+ $this->name = $name;
+ }
+
public function format() {
return $this->format;
}
+ public function setFormat($format) {
+ $this->format = $format;
+ }
+
public function channels() {
return $this->channels;
}
+ public function setChannels($channels) {
+ $this->channels = $channels;
+ }
+
public function language() {
return $this->language;
}
+ public function setLanguage($language) {
+ $this->language = $language;
+ }
+
public function samplerate() {
return $this->samplerate;
}
+ public function setSampleRate($sample_rate) {
+ $this->samplerate = $sample_rate;
+ }
+
public function bitrate() {
return $this->bitrate;
}
+ public function setBitRate($bit_rate) {
+ $this->bitrate = $bit_rate;
+ }
+
};
?>
\ No newline at end of file
diff --git a/lib/RippingCluster/Rips/SourceSubtitleTrack.class.php b/lib/RippingCluster/Rips/SourceSubtitleTrack.class.php
index 9b198b6..812099c 100644
--- a/lib/RippingCluster/Rips/SourceSubtitleTrack.class.php
+++ b/lib/RippingCluster/Rips/SourceSubtitleTrack.class.php
@@ -22,14 +22,26 @@ class RippingCluster_Rips_SourceSubtitleTrack {
return $this->name;
}
+ public function setName($name) {
+ $this->name = $name;
+ }
+
public function language() {
return $this->language;
}
+ public function setLanguage($language) {
+ $this->language = $language;
+ }
+
public function format() {
return $this->format;
}
+ public function setFormat($format) {
+ $this->format = $format;
+ }
+
};
?>
\ No newline at end of file
diff --git a/lib/RippingCluster/Rips/SourceTitle.class.php b/lib/RippingCluster/Rips/SourceTitle.class.php
index 1afa200..385380a 100644
--- a/lib/RippingCluster/Rips/SourceTitle.class.php
+++ b/lib/RippingCluster/Rips/SourceTitle.class.php
@@ -54,22 +54,42 @@ class RippingCluster_Rips_SourceTitle {
return $this->width;
}
+ public function setWidth($width) {
+ $this->width = $width;
+ }
+
public function height() {
return $this->height;
}
+ public function setHeight($height) {
+ $this->height = $height;
+ }
+
public function displayAspect() {
return $this->display_aspect;
}
+ public function setDisplayAspect($display_aspect) {
+ $this->display_aspect = $display_aspect;
+ }
+
public function pixelAspect() {
return $this->pixel_aspect;
}
+ public function setPixelAspect($pixel_aspect) {
+ $this->pixel_aspect = $pixel_aspect;
+ }
+
public function framerate() {
return $this->framerate;
}
+ public function setFramerate($framerate) {
+ $this->framerate = $framerate;
+ }
+
public function setDisplayInfo($width, $height, $display_aspect, $pixel_aspect, $framerate) {
$this->width = $width;
$this->height = $height;
From 7ffccb851cde5b25c69131c1c655f5f6dfdee8de Mon Sep 17 00:00:00 2001
From: Ben Roberts
Date: Tue, 12 Oct 2010 19:13:21 +0100
Subject: [PATCH 4/4] WIP parser for mkvinfo output
---
.../Source/Plugin/MkvInfo.class.php | 123 +++++++++++++++++-
1 file changed, 122 insertions(+), 1 deletion(-)
diff --git a/lib/RippingCluster/Source/Plugin/MkvInfo.class.php b/lib/RippingCluster/Source/Plugin/MkvInfo.class.php
index c16ea8c..f039532 100644
--- a/lib/RippingCluster/Source/Plugin/MkvInfo.class.php
+++ b/lib/RippingCluster/Source/Plugin/MkvInfo.class.php
@@ -14,6 +14,13 @@ class RippingCluster_Source_Plugin_MkvInfo extends RippingCluster_PluginBase imp
*/
const CONFIG_SOURCE_DIR = 'source.mkvinfo.dir';
+ const PM_HEADERS = 0;
+ const PM_TRACK = 1;
+ const PM_TITLE = 2;
+ const PM_CHAPTER = 3;
+ const PM_AUDIO = 4;
+ const PM_SUBTITLE = 5;
+
/**
* Returns a list of all Sources discovered by this plugin.
*
@@ -55,6 +62,7 @@ class RippingCluster_Source_Plugin_MkvInfo extends RippingCluster_PluginBase imp
*/
public static function load($source_filename, $scan = true, $use_cache = true) {
$cache = RippingCluster_Main::instance()->cache();
+ $config = RippingCluster_Main::instance()->config();
// Ensure the source is a valid directory, and lies below the configured source_dir
if ( ! self::isValidSource($source_filename)) {
@@ -67,7 +75,120 @@ class RippingCluster_Source_Plugin_MkvInfo extends RippingCluster_PluginBase imp
} else {
$source = new RippingCluster_Source($source_filename, self::name(), true);
- // TODO Populate source object with content
+ if ($scan) {
+ $cmd = escapeshellcmd($config->get('source.mkvinfo.bin')) . ' ' . escapeshellarg($source_filename);
+ list($retval, $output, $error) = RippingCluster_ForegroundTask::execute($cmd);
+
+ // Process the output
+ $lines = explode("\n", $output);
+ $track = null;
+ $track_details = null;
+ $duration = null;
+ $mode = self::PM_HEADERS;
+
+ foreach ($lines as $line) {
+ // Skip any line that doesn't begin with a |+ (with optional whitespace)
+ if ( ! preg_match('/^|\s*\+/', $line)) {
+ continue;
+ }
+
+ $matches = array();
+ switch (true) {
+
+ case $mode == self::PM_HEADERS && preg_match('/^| \+ Duration: [\d\.]+s ([\d:]+])$/', $line, $matches): {
+ $duration = $matches['duration'];
+ } break;
+
+ case preg_match('/^| \+ A track$/', $line, $matches): {
+ $mode = self::PM_TRACK;
+ $track_details = array();
+ } break;
+
+ case $mode == self::PM_TRACK && preg_match('/^| \+ Track number: (?P\d+):$/', $line, $matches): {
+ $track_details['id'] = $matches['id'];
+ } break;
+
+ case $mode == self::PM_TRACK && preg_match('/^| \+ Track type: (?P.+)$/', $line, $matches): {
+ switch ($type) {
+ case 'video': {
+ $mode = self::PM_TITLE;
+ $track = new RippingCluster_Rips_SourceTitle($track_details['id']);
+ $track->setDuration($duration);
+ } break;
+
+ case 'audio': {
+ $mode = self::PM_AUDIO;
+ $track = new RippingCluster_Rips_SourceAudioTrack($track_details['id']);
+ } break;
+
+ case 'subtitles': {
+ $mode = self::PM_SUBTITLE;
+ $track = new RippingCluster_Rips_SourceSubtitleTrack($track_details['id']);
+ } break;
+ }
+ } break;
+
+ case $mode == self::PM_AUDIO && $track && preg_match('/^| \+ Codec ID: (?P.+)$/', $line, $matches): {
+ $track->setFormat($matches['codec']);
+ } break;
+
+ case $mode == self::PM_AUDIO && $track && preg_match('/^| \+ Language: (?P.+)$/', $line, $matches): {
+ $track->setLanguage($matches['language']);
+ } break;
+
+ case $mode == self::PM_AUDIO && $track && preg_match('/^| \+ Sampling frequency: (?P.+)$/', $line, $matches): {
+ $track->setSampleRate($matches['samplerate']);
+ } break;
+
+ case $mode == self::PM_AUDIO && $track && preg_match('/^| \+ Channels: (?P.+)$/', $line, $matches): {
+ $track->setFormat($matches['channels']);
+ } break;
+
+ case $mode == self::PM_SUBTITLE && $track && preg_match('/^| \+ Language: (?P.*)$/', $line): {
+ $track->setLanguage($matches['language']);
+ } break;
+
+ case $mode == self::PM_TITLE && $track && preg_match('/^ \+ Default duration: [\d\.]+ \((?P[\d\.]+ fps for a video track)\)$/', $line, $matches): {
+ $title->setFramerate($matches['framerate']);
+ } break;
+
+ case $mode == self::PM_TITLE && $track && preg_match('/^ \+ Pixel width: (?P\d+)$/', $line, $matches): {
+ $title->setWidth($matches['width']);
+ } break;
+
+ case $mode == self::PM_TITLE && $track && preg_match('/^ \+ Pixel height: (?P\d+)$/', $line, $matches): {
+ $title->setHeight($matches['height']);
+ } break;
+
+ case $title && $mode == self::PM_CHAPTER && preg_match('/^ \+ (?P\d+): cells \d+->\d+, \d+ blocks, duration (?P\d+:\d+:\d+)$/', $line, $matches): {
+ $title->addChapter($matches['id'], $matches['duration']);
+ } break;
+
+ case $title && $mode == self::PM_AUDIO && preg_match('/^ \+ (?P\d+), (?P.+) \((?P.+)\) \((?P(.+ ch|Dolby Surround))\) \((?P.+)\), (?P\d+)Hz, (?P\d+)bps$/', $line, $matches): {
+ $title->addAudioTrack(
+ new RippingCluster_Rips_SourceAudioTrack(
+ $matches['id'], $matches['name'], $matches['format'], $matches['channels'],
+ $matches['language'], $matches['samplerate'], $matches['bitrate']
+ )
+ );
+ } break;
+
+ case $title && $mode == self::PM_SUBTITLE && preg_match('/^ \+ (?P\d+), (?P.+) \((?P.+)\) \((?P.+)\)$/', $line, $matches): {
+ $title->addSubtitleTrack(
+ new RippingCluster_Rips_SourceSubtitleTrack(
+ $matches['id'], $matches['name'], $matches['language'], $matches['format']
+ )
+ );
+ } break;
+
+ default: {
+ // Ignore this unmatched line
+ } break;
+
+ }
+ }
+
+ }
// If requested, store the new source object in the cache
if ($use_cache) {