Massive refactor to use SihnonFramework and PEAR's Net_Gearman
This commit is contained in:
38
source/lib/RippingCluster/ClientLogEntry.class.php
Normal file
38
source/lib/RippingCluster/ClientLogEntry.class.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_ClientLogEntry extends RippingCluster_LogEntry {
|
||||
|
||||
protected $jobId;
|
||||
|
||||
protected function __construct($id, $level, $ctime, $pid, $hostname, $progname, $line, $message, $jobId) {
|
||||
parent::__construct($id, $level, $ctime, $pid, $hostname, $progname, $line, $message);
|
||||
|
||||
$this->jobId = $jobId;
|
||||
}
|
||||
|
||||
public static function fromDatabaseRow($row) {
|
||||
return new self(
|
||||
$row['id'],
|
||||
$row['level'],
|
||||
$row['ctime'],
|
||||
$row['pid'],
|
||||
$row['hostname'],
|
||||
$row['progname'],
|
||||
$row['line'],
|
||||
$row['message'],
|
||||
$row['job_id']
|
||||
);
|
||||
}
|
||||
|
||||
public static function initialise() {
|
||||
parent::$table_name = 'client_log';
|
||||
}
|
||||
|
||||
public function jobId() {
|
||||
return $this->jobId;
|
||||
}
|
||||
};
|
||||
|
||||
RippingCluster_ClientLogEntry::initialise();
|
||||
|
||||
?>
|
||||
32
source/lib/RippingCluster/Exceptions.class.php
Normal file
32
source/lib/RippingCluster/Exceptions.class.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Exception extends Exception {};
|
||||
|
||||
class RippingCluster_Exception_DatabaseException extends RippingCluster_Exception {};
|
||||
class RippingCluster_Exception_DatabaseConfigMissing extends RippingCluster_Exception_DatabaseException {};
|
||||
class RippingCluster_Exception_DatabaseConnectFailed extends RippingCluster_Exception_DatabaseException {};
|
||||
class RippingCluster_Exception_NoDatabaseConnection extends RippingCluster_Exception_DatabaseException {};
|
||||
class RippingCluster_Exception_DatabaseQueryFailed extends RippingCluster_Exception_DatabaseException {};
|
||||
class RippingCluster_Exception_ResultCountMismatch extends RippingCluster_Exception_DatabaseException {};
|
||||
|
||||
class RippingCluster_Exception_ConfigException extends RippingCluster_Exception {};
|
||||
class RippingCluster_Exception_UnknownSetting extends RippingCluster_Exception_ConfigException {};
|
||||
|
||||
class RippingCluster_Exception_TemplateException extends RippingCluster_Exception {};
|
||||
class RippingCluster_Exception_AbortEntirePage extends RippingCluster_Exception_TemplateException {};
|
||||
class RippingCluster_Exception_Unauthorized extends RippingCluster_Exception_TemplateException {};
|
||||
class RippingCluster_Exception_FileNotFound extends RippingCluster_Exception_TemplateException {};
|
||||
class RippingCluster_Exception_InvalidParameters extends RippingCluster_Exception_TemplateException {};
|
||||
|
||||
class RippingCluster_Exception_InvalidSourceDirectory extends RippingCluster_Exception {};
|
||||
|
||||
class RippingCluster_Exception_CacheException extends RippingCluster_Exception {};
|
||||
class RippingCluster_Exception_InvalidCacheDir extends RippingCluster_Exception_CacheException {};
|
||||
class RippingCluster_Exception_CacheObjectNotFound extends RippingCluster_Exception_CacheException {};
|
||||
|
||||
class RippingCluster_Exception_LogicException extends RippingCluster_Exception {};
|
||||
class RippingCluster_Exception_JobNotRunning extends RippingCluster_Exception_LogicException {};
|
||||
|
||||
class RippingCluster_Exception_InvalidPluginName extends RippingCluster_Exception {};
|
||||
|
||||
?>
|
||||
340
source/lib/RippingCluster/Job.class.php
Normal file
340
source/lib/RippingCluster/Job.class.php
Normal file
@@ -0,0 +1,340 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Job {
|
||||
|
||||
protected $source;
|
||||
|
||||
private $id;
|
||||
private $name;
|
||||
private $source_plugin;
|
||||
private $rip_plugin;
|
||||
private $source_filename;
|
||||
private $destination_filename;
|
||||
private $title;
|
||||
private $format;
|
||||
private $video_codec;
|
||||
private $video_width;
|
||||
private $video_height;
|
||||
private $quantizer;
|
||||
private $deinterlace;
|
||||
private $audio_tracks;
|
||||
private $audio_codecs;
|
||||
private $audio_names;
|
||||
private $subtitle_tracks;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var array(RippingCluster_JobStatus)
|
||||
*/
|
||||
private $statuses = null;
|
||||
|
||||
private static $cache = array();
|
||||
|
||||
protected function __construct($source, $id, $name, $source_plugin, $rip_plugin, $source_filename, $destination_filename, $title, $format, $video_codec, $video_width, $video_height, $quantizer, $deinterlace,
|
||||
$audio_tracks, $audio_codecs, $audio_names, $subtitle_tracks) {
|
||||
$this->source = $source;
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->source_plugin = $source_plugin;
|
||||
$this->rip_plugin = $rip_plugin;
|
||||
$this->source_filename = $source_filename;
|
||||
$this->destination_filename = $destination_filename;
|
||||
$this->title = $title;
|
||||
$this->format = $format;
|
||||
$this->video_codec = $video_codec;
|
||||
$this->video_width = $video_width;
|
||||
$this->video_height = $video_height;
|
||||
$this->quantizer = $quantizer;
|
||||
$this->deinterlace = $deinterlace;
|
||||
$this->audio_tracks = $audio_tracks;
|
||||
$this->audio_codecs = $audio_codecs;
|
||||
$this->audio_names = $audio_names;
|
||||
$this->subtitle_tracks = $subtitle_tracks;
|
||||
}
|
||||
|
||||
public function __clone() {
|
||||
$this->id = null;
|
||||
|
||||
$this->create();
|
||||
}
|
||||
|
||||
public static function fromDatabaseRow($row) {
|
||||
return new RippingCluster_Job(
|
||||
RippingCluster_Source_PluginFactory::load($row['source_plugin'], $row['source'], false),
|
||||
$row['id'],
|
||||
$row['name'],
|
||||
$row['source_plugin'],
|
||||
$row['rip_plugin'],
|
||||
$row['source'],
|
||||
$row['destination'],
|
||||
$row['title'],
|
||||
$row['format'],
|
||||
$row['video_codec'],
|
||||
$row['video_width'],
|
||||
$row['video_height'],
|
||||
$row['quantizer'],
|
||||
$row['deinterlace'],
|
||||
$row['audio_tracks'],
|
||||
$row['audio_codecs'],
|
||||
$row['audio_names'],
|
||||
$row['subtitle_tracks']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @todo Implement cache of previously loaded jobs
|
||||
*
|
||||
* @param int $id
|
||||
* @return RippingCluster_Job
|
||||
*/
|
||||
public static function fromId($id) {
|
||||
$database = RippingCluster_Main::instance()->database();
|
||||
|
||||
if (isset(self::$cache[$id])) {
|
||||
return self::$cache[$id];
|
||||
}
|
||||
|
||||
$job = RippingCluster_Job::fromDatabaseRow(
|
||||
$database->selectOne('SELECT * FROM jobs WHERE id=:id', array(
|
||||
array('name' => 'id', 'value' => $id, 'type' => PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
self::$cache[$job->id] = $job;
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
public static function all() {
|
||||
$jobs = array();
|
||||
|
||||
$database = RippingCluster_Main::instance()->database();
|
||||
foreach ($database->selectList('SELECT * FROM jobs WHERE id > 0') as $row) {
|
||||
$job = self::fromDatabaseRow($row);
|
||||
|
||||
self::$cache[$job->id] = $job;
|
||||
$jobs[] = $job;
|
||||
}
|
||||
|
||||
return $jobs;
|
||||
}
|
||||
|
||||
public static function allWithStatus($status, $limit = null) {
|
||||
$jobs = array();
|
||||
$database = RippingCluster_Main::instance()->database();
|
||||
|
||||
$params = array(
|
||||
array('name' => 'status', 'value' => $status, 'type' => PDO::PARAM_INT),
|
||||
);
|
||||
|
||||
$limitSql = '';
|
||||
if ($limit) {
|
||||
$limitSql = 'LIMIT :limit';
|
||||
$params[] = array('name' => 'limit', 'value' => $limit, 'type' => PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
foreach ($database->selectList("SELECT * FROM jobs WHERE id IN (SELECT job_id FROM job_status_current WHERE id > 0 AND status=:status) ORDER BY id {$limitSql}", $params) as $row) {
|
||||
$jobs[] = self::fromDatabaseRow($row);
|
||||
}
|
||||
|
||||
return $jobs;
|
||||
}
|
||||
|
||||
public static function fromPostRequest($plugin, $source_id, $global_options, $titles) {
|
||||
$source = RippingCluster_Source_PluginFactory::loadEncoded($plugin, RippingCluster_Main::issetelse($source_id, 'RippingCluster_Exception_InvalidParameters'));
|
||||
|
||||
$jobs = array();
|
||||
foreach ($titles as $title => $details) {
|
||||
if (RippingCluster_Main::issetelse($details['queue'])) {
|
||||
RippingCluster_Main::issetelse($details['output_filename'], 'RippingCluster_Exception_InvalidParameters');
|
||||
|
||||
$job = new RippingCluster_Job(
|
||||
$source,
|
||||
null,
|
||||
RippingCluster_Main::issetelse($details['name'], 'unnamed job'),
|
||||
$source->plugin(),
|
||||
$plugin,
|
||||
$source->filename(),
|
||||
$global_options['output-directory'] . DIRECTORY_SEPARATOR . $details['output_filename'],
|
||||
$title,
|
||||
$global_options['format'],
|
||||
$global_options['video-codec'],
|
||||
$global_options['video-width'],
|
||||
$global_options['video-height'],
|
||||
$global_options['quantizer'],
|
||||
RippingCluster_Main::issetelse($details['deinterlace'], 2),
|
||||
implode(',', RippingCluster_Main::issetelse($details['audio'], array())),
|
||||
implode(',', array_pad(array(), count($details['audio']), 'ac3')), // @todo Make this configurable
|
||||
implode(',', array_pad(array(), count($details['audio']), 'Unknown')), // @todo Make this configurable
|
||||
implode(',', RippingCluster_Main::issetelse($details['subtitles'], array()))
|
||||
);
|
||||
$job->create();
|
||||
|
||||
$jobs[] = $job;
|
||||
}
|
||||
}
|
||||
|
||||
return $jobs;
|
||||
}
|
||||
|
||||
protected function create() {
|
||||
$database = RippingCluster_Main::instance()->database();
|
||||
$database->insert(
|
||||
'INSERT INTO jobs
|
||||
(id,name,source,destination,title,format,video_codec,video_width,video_height,quantizer,deinterlace,audio_tracks,audio_codecs,audio_names,subtitle_tracks)
|
||||
VALUES(NULL,:name,:source,:destination,:title,:format,:video_codec,:video_width,:video_height,:quantizer,:deinterlace,:audio_tracks,:audio_codecs,:audio_names,:subtitle_tracks)',
|
||||
array(
|
||||
array('name' => 'name', 'value' => $this->name, 'type' => PDO::PARAM_STR),
|
||||
array('name' => 'source', 'value' => $this->source_filename, 'type' => PDO::PARAM_STR),
|
||||
array('name' => 'destination', 'value' => $this->destination_filename, 'type' => PDO::PARAM_STR),
|
||||
array('name' => 'title', 'value' => $this->title, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'format', 'value' => $this->format, 'type' => PDO::PARAM_STR),
|
||||
array('name' => 'video_codec', 'value' => $this->video_codec, 'type' => PDO::PARAM_STR),
|
||||
array('name' => 'video_width', 'value' => $this->video_width, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'video_height', 'value' => $this->video_height, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'quantizer', 'value' => $this->quantizer, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'deinterlace', 'value' => $this->deinterlace, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'audio_tracks', 'value' => $this->audio_tracks, 'type' => PDO::PARAM_STR),
|
||||
array('name' => 'audio_codecs', 'value' => $this->audio_codecs, 'type' => PDO::PARAM_STR),
|
||||
array('name' => 'audio_names', 'value' => $this->audio_names, 'type' => PDO::PARAM_STR),
|
||||
array('name' => 'subtitle_tracks', 'value' => $this->subtitle_tracks, 'type' => PDO::PARAM_STR),
|
||||
)
|
||||
);
|
||||
|
||||
$this->id = $database->lastInsertId();
|
||||
$status = RippingCluster_JobStatus::updateStatusForJob($this, RippingCluster_JobStatus::CREATED);
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$database = RippingCluster_Main::instance()->database();
|
||||
$database->update(
|
||||
'DELETE FROM jobs WHERE id=:job_id LIMIT 1',
|
||||
array(
|
||||
array(name => 'job_id', value => $this->id, type => PDO::PARAM_INT),
|
||||
)
|
||||
);
|
||||
|
||||
$this->id = null;
|
||||
}
|
||||
|
||||
public function queue() {
|
||||
$main = RippingCluster_Main::instance();
|
||||
$config = $main->config();
|
||||
|
||||
// Construct the rip options
|
||||
$rip_options = array(
|
||||
'id' => $this->id,
|
||||
'nice' => $config->get('rips.nice', 15),
|
||||
'input_filename' => $this->source_filename,
|
||||
'output_filename' => $this->destination_filename,
|
||||
'title' => $this->title,
|
||||
'format' => $this->format,
|
||||
'video_codec' => $this->video_codec,
|
||||
'video_width' => $this->video_width,
|
||||
'video_height' => $this->video_height,
|
||||
'quantizer' => $this->quantizer,
|
||||
'deinterlace' => $this->deinterlace,
|
||||
'audio_tracks' => $this->audio_tracks,
|
||||
'audio_codec' => $this->audio_codecs,
|
||||
'audio_names' => $this->audio_names,
|
||||
'subtitle_tracks' => $this->subtitle_tracks,
|
||||
);
|
||||
|
||||
return array('HandBrake', array('rip_options' => $rip_options));
|
||||
}
|
||||
|
||||
protected function loadStatuses() {
|
||||
if ($this->statuses == null) {
|
||||
$this->statuses = RippingCluster_JobStatus::allForJob($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return RippingCluster_JobStatus
|
||||
*/
|
||||
public function currentStatus() {
|
||||
$this->loadStatuses();
|
||||
return $this->statuses[count($this->statuses) - 1];
|
||||
}
|
||||
|
||||
public function updateStatus($new_status, $rip_progress = null) {
|
||||
$this->loadStatuses();
|
||||
|
||||
// Only update the status if the state is changing
|
||||
if ($this->currentStatus()->status() != $new_status) {
|
||||
$new_status = RippingCluster_JobStatus::updateStatusForJob($this, $new_status, $rip_progress);
|
||||
$this->statuses[] = $new_status;
|
||||
}
|
||||
|
||||
return $new_status;
|
||||
}
|
||||
|
||||
public function calculateETA() {
|
||||
$current_status = $this->currentStatus();
|
||||
if ($current_status->status() != RippingCluster_JobStatus::RUNNING) {
|
||||
throw new RippingCluster_Exception_JobNotRunning();
|
||||
}
|
||||
|
||||
$running_time = $current_status->mtime() - $current_status->ctime();
|
||||
$progress = $current_status->ripProgress();
|
||||
|
||||
if ($progress > 0) {
|
||||
$remaining_time = round((100 - $progress) * ($running_time / $progress));
|
||||
}
|
||||
|
||||
return $remaining_time;
|
||||
}
|
||||
|
||||
public function fixBrokenTimestamps() {
|
||||
$this->loadStatuses();
|
||||
|
||||
// See if we have both a RUNNING and a COMPLETE status set
|
||||
$statuses = array();
|
||||
foreach ($this->statuses as $status) {
|
||||
switch ($status->status()) {
|
||||
case RippingCluster_JobStatus::RUNNING:
|
||||
case RippingCluster_JobStatus::COMPLETE:
|
||||
$statuses[$status->status()] = $status;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($statuses[RippingCluster_JobStatus::RUNNING]) && isset($statuses[RippingCluster_JobStatus::COMPLETE])) {
|
||||
// Ensure the timestamp on the complete is >= that of the running status
|
||||
if ($statuses[RippingCluster_JobStatus::COMPLETE]->mtime() < $statuses[RippingCluster_JobStatus::RUNNING]->mtime()) {
|
||||
$statuses[RippingCluster_JobStatus::COMPLETE]->mtime($statuses[RippingCluster_JobStatus::RUNNING]->mtime() + 1);
|
||||
$statuses[RippingCluster_JobStatus::COMPLETE]->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function name() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function sourceFilename() {
|
||||
return $this->source_filename;
|
||||
}
|
||||
|
||||
public function destinationFilename() {
|
||||
return $this->destination_filename;
|
||||
}
|
||||
|
||||
public function title() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public static function runAllJobs() {
|
||||
RippingCluster_BackgroundTask::run('/usr/bin/php run-jobs.php');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
?>
|
||||
157
source/lib/RippingCluster/JobStatus.class.php
Normal file
157
source/lib/RippingCluster/JobStatus.class.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_JobStatus {
|
||||
|
||||
const CREATED = 0;
|
||||
const QUEUED = 1;
|
||||
const FAILED = 2;
|
||||
const RUNNING = 3;
|
||||
const COMPLETE = 4;
|
||||
|
||||
private static $status_names = array(
|
||||
self::CREATED => 'Created',
|
||||
self::QUEUED => 'Queued',
|
||||
self::FAILED => 'Failed',
|
||||
self::RUNNING => 'Running',
|
||||
self::COMPLETE => 'Complete',
|
||||
);
|
||||
|
||||
protected $id;
|
||||
protected $job_id;
|
||||
protected $status;
|
||||
protected $ctime;
|
||||
protected $mtime;
|
||||
protected $rip_progress;
|
||||
|
||||
protected function __construct($id, $job_id, $status, $ctime, $mtime, $rip_progress) {
|
||||
$this->id = $id;
|
||||
$this->job_id = $job_id;
|
||||
$this->status = $status;
|
||||
$this->ctime = $ctime;
|
||||
$this->mtime = $mtime;
|
||||
$this->rip_progress = $rip_progress;
|
||||
}
|
||||
|
||||
public static function fromDatabaseRow($row) {
|
||||
return new RippingCluster_JobStatus(
|
||||
$row['id'],
|
||||
$row['job_id'],
|
||||
$row['status'],
|
||||
$row['ctime'],
|
||||
$row['mtime'],
|
||||
$row['rip_progress']
|
||||
);
|
||||
}
|
||||
|
||||
public static function updateStatusForJob($job, $status, $rip_progress = null) {
|
||||
$ctime = $mtime = time();
|
||||
$status = new RippingCluster_JobStatus(null, $job->id(), $status, $ctime, $mtime, $rip_progress);
|
||||
$status->create();
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function updateRipProgress($rip_progress) {
|
||||
$this->rip_progress = $rip_progress;
|
||||
$this->mtime = time();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public static function allForJob(RippingCluster_Job $job) {
|
||||
$statuses = array();
|
||||
|
||||
$database = RippingCluster_Main::instance()->database();
|
||||
foreach ($database->selectList('SELECT * FROM job_status WHERE job_id=:job_id ORDER BY mtime ASC', array(
|
||||
array('name' => 'job_id', 'value' => $job->id(), 'type' => PDO::PARAM_INT),
|
||||
)) as $row) {
|
||||
$statuses[] = RippingCluster_JobStatus::fromDatabaseRow($row);
|
||||
}
|
||||
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
protected function create() {
|
||||
$database = RippingCluster_Main::instance()->database();
|
||||
$database->insert(
|
||||
'INSERT INTO job_status
|
||||
(id, job_id, status, ctime, mtime, rip_progress)
|
||||
VALUES(NULL,:job_id,:status,:ctime,:mtime,:rip_progress)',
|
||||
array(
|
||||
array('name' => 'job_id', 'value' => $this->job_id, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'status', 'value' => $this->status, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'ctime', 'value' => $this->ctime, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'mtime', 'value' => $this->mtime, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'rip_progress', 'value' => $this->rip_progress),
|
||||
)
|
||||
);
|
||||
|
||||
$this->id = $database->lastInsertId();
|
||||
}
|
||||
|
||||
public function save() {
|
||||
$database = RippingCluster_Main::instance()->database();
|
||||
$database->update(
|
||||
'UPDATE job_status SET
|
||||
job_id=:job_id, status=:status, ctime=:ctime, mtime=:mtime, rip_progress=:rip_progress
|
||||
WHERE id=:id',
|
||||
array(
|
||||
array('name' => 'id', 'value' => $this->id, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'job_id', 'value' => $this->job_id, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'status', 'value' => $this->status, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'ctime', 'value' => $this->ctime, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'mtime', 'value' => $this->mtime, 'type' => PDO::PARAM_INT),
|
||||
array('name' => 'rip_progress', 'value' => $this->rip_progress),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function hasProgressInfo() {
|
||||
return ($this->status == self::RUNNING);
|
||||
}
|
||||
|
||||
public static function fixBrokenTimestamps() {
|
||||
$statuses = array();
|
||||
|
||||
$database = RippingCluster_Main::instance()->database();
|
||||
foreach ($database->selectList('SELECT * FROM job_status WHERE status=4 AND job_id IN (SELECT job_id FROM job_status WHERE status=3)') as $row) {
|
||||
$status = RippingCluster_JobStatus::fromDatabaseRow($row);
|
||||
$status->mtime = time();
|
||||
$status->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function jobId() {
|
||||
return $this->job_id;
|
||||
}
|
||||
|
||||
public function status() {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function statusName() {
|
||||
return self::$status_names[$this->status];
|
||||
}
|
||||
|
||||
public function ctime() {
|
||||
return $this->ctime;
|
||||
}
|
||||
|
||||
public function mtime($new_mtime = null) {
|
||||
if ($new_mtime !== null) {
|
||||
$this->mtime = $new_mtime;
|
||||
}
|
||||
|
||||
return $this->mtime;
|
||||
}
|
||||
|
||||
public function ripProgress() {
|
||||
return $this->rip_progress;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
?>
|
||||
50
source/lib/RippingCluster/Main.class.php
Normal file
50
source/lib/RippingCluster/Main.class.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
require 'smarty/Smarty.class.php';
|
||||
|
||||
class RippingCluster_Main extends SihnonFramework_Main {
|
||||
|
||||
protected static $instance;
|
||||
|
||||
protected $smarty;
|
||||
protected $request;
|
||||
|
||||
protected function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$request_string = isset($_GET['l']) ? $_GET['l'] : '';
|
||||
|
||||
$this->request = new RippingCluster_RequestParser($request_string);
|
||||
|
||||
if (HBC_File == 'index') {
|
||||
$this->smarty = new Smarty();
|
||||
$this->smarty->template_dir = './source/templates';
|
||||
$this->smarty->compile_dir = './tmp/templates';
|
||||
$this->smarty->cache_dir = './tmp/cache';
|
||||
$this->smarty->config_dir = './config';
|
||||
|
||||
$this->smarty->registerPlugin('modifier', 'formatDuration', array('RippingCluster_Main', 'formatDuration'));
|
||||
|
||||
$this->smarty->assign('version', '0.1');
|
||||
$this->smarty->assign('messages', array());
|
||||
|
||||
$this->smarty->assign('base_uri', $this->base_uri);
|
||||
}
|
||||
}
|
||||
|
||||
public function smarty() {
|
||||
return $this->smarty;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return RippingCluster_RequestParser
|
||||
*/
|
||||
public function request() {
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
77
source/lib/RippingCluster/Rips/SourceAudioTrack.class.php
Normal file
77
source/lib/RippingCluster/Rips/SourceAudioTrack.class.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Rips_SourceAudioTrack {
|
||||
|
||||
protected $id;
|
||||
protected $name;
|
||||
protected $format;
|
||||
protected $channels;
|
||||
protected $language;
|
||||
protected $samplerate;
|
||||
protected $bitrate;
|
||||
|
||||
public function __construct($id, $name, $format, $channels, $language, $samplerate, $bitrate) {
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->format = $format;
|
||||
$this->channels = $channels;
|
||||
$this->language = $language;
|
||||
$this->samplerate = $samplerate;
|
||||
$this->bitrate = $bitrate;
|
||||
}
|
||||
|
||||
public function id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function name() {
|
||||
return $this->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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
?>
|
||||
47
source/lib/RippingCluster/Rips/SourceSubtitleTrack.class.php
Normal file
47
source/lib/RippingCluster/Rips/SourceSubtitleTrack.class.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Rips_SourceSubtitleTrack {
|
||||
|
||||
protected $id;
|
||||
protected $name;
|
||||
protected $language;
|
||||
protected $format;
|
||||
|
||||
public function __construct($id, $name, $language, $format) {
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->language = $language;
|
||||
$this->format = $format;
|
||||
}
|
||||
|
||||
public function id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function name() {
|
||||
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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
?>
|
||||
147
source/lib/RippingCluster/Rips/SourceTitle.class.php
Normal file
147
source/lib/RippingCluster/Rips/SourceTitle.class.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Rips_SourceTitle {
|
||||
|
||||
protected $id;
|
||||
//protected $vts;
|
||||
//protected $ttn;
|
||||
//protected $cell_count;
|
||||
//protected $blocks;
|
||||
|
||||
protected $angles;
|
||||
protected $duration;
|
||||
protected $width;
|
||||
protected $height;
|
||||
protected $pixel_aspect;
|
||||
protected $display_aspect;
|
||||
protected $framerate;
|
||||
protected $autocrop;
|
||||
|
||||
protected $chapters = array();
|
||||
protected $audio = array();
|
||||
protected $subtitles = array();
|
||||
|
||||
public function __construct($id) {
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function angles() {
|
||||
return $this->angles;
|
||||
}
|
||||
|
||||
public function setAngles($angles) {
|
||||
$this->angles = $angles;
|
||||
}
|
||||
|
||||
public function duration() {
|
||||
return $this->duration;
|
||||
}
|
||||
|
||||
public function durationInSeconds() {
|
||||
$time = explode(":", $this->duration);
|
||||
return ($time[0] * 3600) + ($time[1] * 60) + $time[2];
|
||||
}
|
||||
|
||||
public function setDuration($duration) {
|
||||
$this->duration = $duration;
|
||||
}
|
||||
|
||||
public function width() {
|
||||
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;
|
||||
$this->pixel_aspect = $pixel_aspect;
|
||||
$this->display_aspect = $display_aspect;
|
||||
$this->framerate = $framerate;
|
||||
}
|
||||
|
||||
public function autocrop() {
|
||||
return $this->autocrop;
|
||||
}
|
||||
|
||||
public function setAutocrop($autocrop) {
|
||||
$this->autocrop = $autocrop;
|
||||
}
|
||||
|
||||
public function chapterCount() {
|
||||
return count($this->chapters);
|
||||
}
|
||||
|
||||
public function chapters() {
|
||||
return $this->chapters;
|
||||
}
|
||||
|
||||
public function addChapter($chapter_id, $duration) {
|
||||
$this->chapters[$chapter_id] = $duration;
|
||||
}
|
||||
|
||||
public function audioTrackCount() {
|
||||
return count($this->audio);
|
||||
}
|
||||
|
||||
public function audioTracks() {
|
||||
return $this->audio;
|
||||
}
|
||||
|
||||
public function addAudioTrack(RippingCluster_Rips_SourceAudioTrack $audio_track) {
|
||||
$this->audio[] = $audio_track;
|
||||
}
|
||||
|
||||
public function subtitleTrackCount() {
|
||||
return count($this->subtitles);
|
||||
}
|
||||
|
||||
public function subtitleTracks() {
|
||||
return $this->subtitles;
|
||||
}
|
||||
|
||||
public function addSubtitleTrack(RippingCluster_Rips_SourceSubtitleTrack $subtitle_track) {
|
||||
$this->subtitles[] = $subtitle_track;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
?>
|
||||
146
source/lib/RippingCluster/Source.class.php
Normal file
146
source/lib/RippingCluster/Source.class.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Source {
|
||||
|
||||
const PM_TITLE = 0;
|
||||
const PM_CHAPTER = 1;
|
||||
const PM_AUDIO = 2;
|
||||
const PM_SUBTITLE = 3;
|
||||
|
||||
protected $exists;
|
||||
protected $filename;
|
||||
protected $plugin;
|
||||
protected $titles = array();
|
||||
|
||||
public function __construct($source_filename, $plugin, $exists) {
|
||||
$this->exists = $exists;
|
||||
$this->filename = $source_filename;
|
||||
$this->plugin = $plugin;
|
||||
}
|
||||
|
||||
public static function isSourceCached($source_filename) {
|
||||
$main = RippingCluster_Main::instance();
|
||||
$cache = $main->cache();
|
||||
$config = $main->config();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
$main = RippingCluster_Main::instance();
|
||||
$cache = $main->cache();
|
||||
$config = $main->config();
|
||||
|
||||
$cache->store($this->filename, serialize($this), $config->get('rips.cache_ttl'));
|
||||
}
|
||||
|
||||
public static function encodeFilename($filename) {
|
||||
return str_replace("/", "-", base64_encode($filename));
|
||||
}
|
||||
|
||||
public function addTitle(RippingCluster_Rips_SourceTitle $title) {
|
||||
if (!$this->exists) {
|
||||
throw new RippingCluster_Exception_InvalidSourceDirectory();
|
||||
}
|
||||
|
||||
$this->titles[] = $title;
|
||||
}
|
||||
|
||||
public function longestTitle() {
|
||||
if (!$this->exists) {
|
||||
throw new RippingCluster_Exception_InvalidSourceDirectory();
|
||||
}
|
||||
|
||||
$longest_title = null;
|
||||
$maximum_duration = 0;
|
||||
|
||||
if ( ! $this->titles) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->titles as $title) {
|
||||
$duration = $title->durationInSeconds();
|
||||
if ($duration > $maximum_duration) {
|
||||
$longest_title = $title;
|
||||
$maximum_duration = $duration;
|
||||
}
|
||||
}
|
||||
|
||||
return $longest_title;
|
||||
}
|
||||
|
||||
public function longestTitleIndex() {
|
||||
if (!$this->exists) {
|
||||
throw new RippingCluster_Exception_InvalidSourceDirectory();
|
||||
}
|
||||
|
||||
$longest_index = null;
|
||||
$maximmum_duration = 0;
|
||||
|
||||
if ( ! $this->titles) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for ($i = 0, $l = count($this->titles); $i < $l; ++$i) {
|
||||
$title = $this->titles[$i];
|
||||
$duration = $title->durationInSeconds();
|
||||
if ($duration > $maximum_duration) {
|
||||
$longest_index = $i;
|
||||
$maximum_duration = $duration;
|
||||
}
|
||||
}
|
||||
|
||||
return $longest_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently deletes this source from disk
|
||||
*
|
||||
*/
|
||||
public function delete() {
|
||||
RippingCluster_Source_PluginFactory::delete($this->plugin, $this->filename);
|
||||
}
|
||||
|
||||
public function filename() {
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function filenameEncoded() {
|
||||
return self::encodeFilename($this->filename);
|
||||
}
|
||||
|
||||
public function plugin() {
|
||||
return $this->plugin;
|
||||
}
|
||||
|
||||
public function titleCount() {
|
||||
if (!$this->exists) {
|
||||
throw new RippingCluster_Exception_InvalidSourceDirectory();
|
||||
}
|
||||
|
||||
return count($this->titles);
|
||||
}
|
||||
|
||||
public function titles() {
|
||||
if (!$this->exists) {
|
||||
throw new RippingCluster_Exception_InvalidSourceDirectory();
|
||||
}
|
||||
|
||||
return $this->titles;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
?>
|
||||
61
source/lib/RippingCluster/Source/IPlugin.class.php
Normal file
61
source/lib/RippingCluster/Source/IPlugin.class.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
interface RippingCluster_Source_IPlugin extends RippingCluster_IPlugin {
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Determines 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);
|
||||
|
||||
/**
|
||||
* Permanently deletes the given source from disk
|
||||
*
|
||||
* @param RippingCluster_Source $source Source object to be deleted
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete($source_filename);
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
136
source/lib/RippingCluster/Source/Plugin/Bluray.class.php
Normal file
136
source/lib/RippingCluster/Source/Plugin/Bluray.class.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Source_Plugin_Bluray extends RippingCluster_PluginBase implements RippingCluster_Source_IPlugin {
|
||||
|
||||
/**
|
||||
* Name of this plugin
|
||||
* @var string
|
||||
*/
|
||||
const PLUGIN_NAME = "Bluray";
|
||||
|
||||
/**
|
||||
* 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('source.bluray.dir');
|
||||
|
||||
$sources = array();
|
||||
foreach ($directories as $directory) {
|
||||
if (!is_dir($directory)) {
|
||||
throw new RippingCluster_Exception_InvalidSourceDirectory($directory);
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
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('source.bluray.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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently deletes the given source from disk
|
||||
*
|
||||
* @param RippingCluster_Source $source Source object to be deleted
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete($source_filename) {
|
||||
if ( ! self::isValidSource($source_filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return RippingCluster_Main::rmdir_recursive($source_filename);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
237
source/lib/RippingCluster/Source/Plugin/HandBrake.class.php
Normal file
237
source/lib/RippingCluster/Source/Plugin/HandBrake.class.php
Normal file
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Source_Plugin_HandBrake extends RippingCluster_PluginBase implements RippingCluster_Source_IPlugin {
|
||||
|
||||
/**
|
||||
* Name of this plugin
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PLUGIN_NAME = "HandBrake";
|
||||
|
||||
const PM_TITLE = 0;
|
||||
const PM_CHAPTER = 1;
|
||||
const PM_AUDIO = 2;
|
||||
const PM_SUBTITLE = 3;
|
||||
|
||||
/**
|
||||
* 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('source.handbrake.dir');
|
||||
|
||||
$sources = array();
|
||||
foreach ($directories as $directory) {
|
||||
if (!is_dir($directory)) {
|
||||
throw new RippingCluster_Exception_InvalidSourceDirectory($directory);
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if ($scan) {
|
||||
$source_shell = escapeshellarg($source_filename);
|
||||
$handbrake_cmd = "HandBrakeCLI -i {$source_shell} -t 0";
|
||||
list($retval, $handbrake_output, $handbrake_error) = RippingCluster_ForegroundTask::execute($handbrake_cmd);
|
||||
|
||||
// Process the output
|
||||
$lines = explode("\n", $handbrake_error);
|
||||
$title = null;
|
||||
$mode = self::PM_TITLE;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
// Skip any line that doesn't begin with a + (with optional leading whitespace)
|
||||
if ( ! preg_match('/\s*\+/', $line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$matches = array();
|
||||
switch (true) {
|
||||
case preg_match('/^\+ title (?P<id>\d+):$/', $line, $matches): {
|
||||
if ($title) {
|
||||
$source->addTitle($title);
|
||||
}
|
||||
|
||||
$mode = self::PM_TITLE;
|
||||
$title = new RippingCluster_Rips_SourceTitle($matches['id']);
|
||||
} break;
|
||||
|
||||
case $title && preg_match('/^ \+ chapters:$/', $line): {
|
||||
$mode = self::PM_CHAPTER;
|
||||
} break;
|
||||
|
||||
case $title && preg_match('/^ \+ audio tracks:$/', $line): {
|
||||
$mode = self::PM_AUDIO;
|
||||
|
||||
} break;
|
||||
|
||||
case $title && preg_match('/^ \+ subtitle tracks:$/', $line): {
|
||||
$mode = self::PM_SUBTITLE;
|
||||
} break;
|
||||
|
||||
case $title && $mode == self::PM_TITLE && preg_match('/^ \+ duration: (?P<duration>\d+:\d+:\d+)$/', $line, $matches): {
|
||||
$title->setDuration($matches['duration']);
|
||||
} break;
|
||||
|
||||
case $title && $mode == self::PM_TITLE && preg_match('/^ \+ angle\(s\) (?P<angles>\d+)$/', $line, $matches): {
|
||||
$title->setAngles($matches['angles']);
|
||||
} break;
|
||||
|
||||
//" + size: 720x576, pixel aspect: 64/45, display aspect: 1.78, 25.000 fps"
|
||||
case $title && $mode == self::PM_TITLE && preg_match('/^ \+ size: (?P<width>\d+)x(?P<height>\d+), pixel aspect: (?P<pixel_aspect>\d+\/\d+), display aspect: (?P<display_aspect>[\d\.]+), (?<framerate>[\d\.]+) fps$/', $line, $matches): {
|
||||
$title->setDisplayInfo(
|
||||
$matches['width'], $matches['height'], $matches['pixel_aspect'],
|
||||
$matches['display_aspect'], $matches['framerate']
|
||||
);
|
||||
} break;
|
||||
|
||||
case $title && $mode == self::PM_TITLE && preg_match('/^ \+ autocrop: (?P<autocrop>(?:\d+\/?){4})$/', $line, $matches): {
|
||||
$title->setAutocrop($matches['autocrop']);
|
||||
} break;
|
||||
|
||||
case $title && $mode == self::PM_CHAPTER && preg_match('/^ \+ (?P<id>\d+): cells \d+->\d+, \d+ blocks, duration (?P<duration>\d+:\d+:\d+)$/', $line, $matches): {
|
||||
$title->addChapter($matches['id'], $matches['duration']);
|
||||
} break;
|
||||
|
||||
case $title && $mode == self::PM_AUDIO && preg_match('/^ \+ (?P<id>\d+), (?P<name>.+) \((?P<format>.+)\) \((?P<channels>(.+ ch|Dolby Surround))\) \((?P<language>.+)\), (?P<samplerate>\d+)Hz, (?P<bitrate>\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<id>\d+), (?P<name>.+) \((?P<language>.+)\) \((?P<format>.+)\)$/', $line, $matches): {
|
||||
$title->addSubtitleTrack(
|
||||
new RippingCluster_Rips_SourceSubtitleTrack(
|
||||
$matches['id'], $matches['name'], $matches['language'], $matches['format']
|
||||
)
|
||||
);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
// Ignore this unmatched line
|
||||
} break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the last title found as a special case
|
||||
if ($title) {
|
||||
$source->addTitle($title);
|
||||
}
|
||||
|
||||
// If requested, store the new source object in the cache
|
||||
if ($use_cache) {
|
||||
$source->cache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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('source.handbrake.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 true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently deletes the given source from disk
|
||||
*
|
||||
* @param RippingCluster_Source $source Source object to be deleted
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete($source_filename) {
|
||||
if ( ! self::isValidSource($source_filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return RippingCluster_Main::rmdir_recursive($source_filename);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
263
source/lib/RippingCluster/Source/Plugin/MkvInfo.class.php
Normal file
263
source/lib/RippingCluster/Source/Plugin/MkvInfo.class.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?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';
|
||||
|
||||
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.
|
||||
*
|
||||
* 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();
|
||||
$config = RippingCluster_Main::instance()->config();
|
||||
|
||||
// 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);
|
||||
|
||||
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<id>\d+):$/', $line, $matches): {
|
||||
$track_details['id'] = $matches['id'];
|
||||
} break;
|
||||
|
||||
case $mode == self::PM_TRACK && preg_match('/^| \+ Track type: (?P<type>.+)$/', $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<codec>.+)$/', $line, $matches): {
|
||||
$track->setFormat($matches['codec']);
|
||||
} break;
|
||||
|
||||
case $mode == self::PM_AUDIO && $track && preg_match('/^| \+ Language: (?P<language>.+)$/', $line, $matches): {
|
||||
$track->setLanguage($matches['language']);
|
||||
} break;
|
||||
|
||||
case $mode == self::PM_AUDIO && $track && preg_match('/^| \+ Sampling frequency: (?P<samplerate>.+)$/', $line, $matches): {
|
||||
$track->setSampleRate($matches['samplerate']);
|
||||
} break;
|
||||
|
||||
case $mode == self::PM_AUDIO && $track && preg_match('/^| \+ Channels: (?P<channels>.+)$/', $line, $matches): {
|
||||
$track->setFormat($matches['channels']);
|
||||
} break;
|
||||
|
||||
case $mode == self::PM_SUBTITLE && $track && preg_match('/^| \+ Language: (?P<language>.*)$/', $line): {
|
||||
$track->setLanguage($matches['language']);
|
||||
} break;
|
||||
|
||||
case $mode == self::PM_TITLE && $track && preg_match('/^ \+ Default duration: [\d\.]+ \((?P<framerate>[\d\.]+ fps for a video track)\)$/', $line, $matches): {
|
||||
$title->setFramerate($matches['framerate']);
|
||||
} break;
|
||||
|
||||
case $mode == self::PM_TITLE && $track && preg_match('/^ \+ Pixel width: (?P<width>\d+)$/', $line, $matches): {
|
||||
$title->setWidth($matches['width']);
|
||||
} break;
|
||||
|
||||
case $mode == self::PM_TITLE && $track && preg_match('/^ \+ Pixel height: (?P<height>\d+)$/', $line, $matches): {
|
||||
$title->setHeight($matches['height']);
|
||||
} break;
|
||||
|
||||
case $title && $mode == self::PM_CHAPTER && preg_match('/^ \+ (?P<id>\d+): cells \d+->\d+, \d+ blocks, duration (?P<duration>\d+:\d+:\d+)$/', $line, $matches): {
|
||||
$title->addChapter($matches['id'], $matches['duration']);
|
||||
} break;
|
||||
|
||||
case $title && $mode == self::PM_AUDIO && preg_match('/^ \+ (?P<id>\d+), (?P<name>.+) \((?P<format>.+)\) \((?P<channels>(.+ ch|Dolby Surround))\) \((?P<language>.+)\), (?P<samplerate>\d+)Hz, (?P<bitrate>\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<id>\d+), (?P<name>.+) \((?P<language>.+)\) \((?P<format>.+)\)$/', $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) {
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently deletes the given source from disk
|
||||
*
|
||||
* @param RippingCluster_Source $source Source object to be deleted
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete($source_filename) {
|
||||
if ( ! self::isValidSource($source_filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return unlink($source_filename);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
85
source/lib/RippingCluster/Source/PluginFactory.class.php
Normal file
85
source/lib/RippingCluster/Source/PluginFactory.class.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Source_PluginFactory extends RippingCluster_PluginFactory {
|
||||
|
||||
protected static $plugin_prefix = 'RippingCluster_Source_Plugin_';
|
||||
protected static $plugin_interface = 'RippingCluster_Source_IPlugin';
|
||||
protected static $plugin_dir = array(
|
||||
RippingCluster_Lib => 'RippingCluster/Source/Plugin/',
|
||||
);
|
||||
|
||||
|
||||
public static function init() {
|
||||
|
||||
}
|
||||
|
||||
public static function enumerate($plugin) {
|
||||
self::ensureScanned();
|
||||
|
||||
if ( ! self::isValidPlugin($plugin)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return call_user_func(array(self::classname($plugin), 'enumerate'));
|
||||
}
|
||||
|
||||
public static function enumerateAll() {
|
||||
self::ensureScanned();
|
||||
|
||||
$sources = array();
|
||||
foreach (self::getValidPlugins() as $plugin) {
|
||||
$sources[$plugin] = self::enumerate($plugin);
|
||||
}
|
||||
|
||||
return $sources;
|
||||
}
|
||||
|
||||
public static function load($plugin, $source_filename, $scan = true, $use_cache = true) {
|
||||
self::ensureScanned();
|
||||
|
||||
if ( ! self::isValidPlugin($plugin)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return call_user_func(array(self::classname($plugin), 'load'), $source_filename, $scan, $use_cache);
|
||||
}
|
||||
|
||||
public static function loadEncoded($plugin, $encoded_filename, $scan = true, $use_cache = true) {
|
||||
self::ensureScanned();
|
||||
|
||||
if ( ! self::isValidPlugin($plugin)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return call_user_func(array(self::classname($plugin), 'loadEncoded'), $encoded_filename, $scan, $use_cache);
|
||||
}
|
||||
|
||||
/*public static function isValidSource($plugin, $source_filename) {
|
||||
self::ensureScanned();
|
||||
|
||||
if ( ! self::isValidPlugin($plugin)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return call_user_func(array(self::classname($plugin), 'isValidSource'), source_filename);
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Permanently deletes the given source from disk
|
||||
*
|
||||
* @param string $plugin Name of the plugin used to load the source
|
||||
* @param string $source_filename Filename of the source to be deleted
|
||||
*/
|
||||
public static function delete($plugin, $source_filename) {
|
||||
self::ensureScanned();
|
||||
|
||||
if ( ! self::isValidPlugin($plugin)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return call_user_func(array(self::classname($plugin), 'delete'), $source_filename);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Utility_BlurayDirectoryIterator extends FilterIterator {
|
||||
public function accept() {
|
||||
return is_dir($this->current()->getPathname() . DIRECTORY_SEPARATOR . 'BDAV') ||
|
||||
is_dir($this->current()->getPathname() . DIRECTORY_SEPARATOR . 'BDMV');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Utility_ClassFilesIterator extends FilterIterator {
|
||||
public function accept() {
|
||||
return preg_match('/.class.php$/i', $this->current()->getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Utility_DvdDirectoryIterator extends FilterIterator {
|
||||
public function accept() {
|
||||
return is_dir($this->current()->getPathname() . DIRECTORY_SEPARATOR . 'VIDEO_TS');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Utility_MkvFileIterator extends FilterIterator {
|
||||
public function accept() {
|
||||
return preg_match('/\.mkv$/i', $this->current()->getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Utility_VisibleFilesIterator extends FilterIterator {
|
||||
public function accept() {
|
||||
return !(substr($this->current()->getFilename(), 0, 1) == '.');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Utility_VisibleFilesRecursiveIterator extends RecursiveFilterIterator {
|
||||
public function accept() {
|
||||
return !(substr($this->current()->getFilename(), 0, 1) == '.');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
47
source/lib/RippingCluster/Worker.class.php
Normal file
47
source/lib/RippingCluster/Worker.class.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Worker {
|
||||
|
||||
protected $gearman;
|
||||
|
||||
public function __construct() {
|
||||
$this->init();
|
||||
|
||||
}
|
||||
|
||||
private function init() {
|
||||
if ($this->gearman) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = RippingCluster_Main::instance()->config();
|
||||
|
||||
$this->gearman = new Net_Gearman_Worker('river.sihnon.net:4730');//$config->get('rips.job_servers'));
|
||||
|
||||
// Load all the plugin classes
|
||||
RippingCluster_Worker_PluginFactory::scan();
|
||||
$plugins = RippingCluster_Worker_PluginFactory::getValidPlugins();
|
||||
foreach ($plugins as $plugin) {
|
||||
$this->gearman->addAbility($plugin);
|
||||
|
||||
//$workerFunctions = RippingCluster_Worker_PluginFactory::getPluginWorkerFunctions($plugin);
|
||||
//foreach ($workerFunctions as $function => $callback) {
|
||||
// echo "Added ability $function\n";
|
||||
// $this->gearman->addAbility($function);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
public function start() {
|
||||
try {
|
||||
$this->gearman->beginWork();
|
||||
} catch (Net_Gearman_Exception $e) {
|
||||
// Do stuff
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
16
source/lib/RippingCluster/Worker/IPlugin.class.php
Normal file
16
source/lib/RippingCluster/Worker/IPlugin.class.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
interface RippingCluster_Worker_IPlugin extends RippingCluster_IPlugin {
|
||||
|
||||
/**
|
||||
* Returns the list of functions (and names) implemented by this plugin for registration with Gearman
|
||||
*
|
||||
* @return array(string => callback)
|
||||
*/
|
||||
//public static function workerFunctions();
|
||||
|
||||
//public static function run($args);
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
85
source/lib/RippingCluster/Worker/Plugin/Bluray.class.php
Normal file
85
source/lib/RippingCluster/Worker/Plugin/Bluray.class.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Worker_Bluray extends RippingCluster_PluginBase implements RippingCluster_Worker_IPlugin {
|
||||
|
||||
/**
|
||||
* Name of this plugin
|
||||
* @var string
|
||||
*/
|
||||
const PLUGIN_NAME = 'Bluray';
|
||||
|
||||
/**
|
||||
* 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']);
|
||||
}
|
||||
|
||||
public static function init() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function run($args) {
|
||||
//$rip = new self($job);
|
||||
//$rip->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the process for ripping the source to the final output
|
||||
*
|
||||
*/
|
||||
private function execute() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function run($args) {
|
||||
//$rip = new self($job);
|
||||
//$rip->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the process for ripping the source to the final output
|
||||
*
|
||||
*/
|
||||
private function execute() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
28
source/lib/RippingCluster/Worker/PluginFactory.class.php
Normal file
28
source/lib/RippingCluster/Worker/PluginFactory.class.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_Worker_PluginFactory extends RippingCluster_PluginFactory {
|
||||
|
||||
const PLUGIN_DIR = 'Net/Gearman/Job/';
|
||||
const PLUGIN_PREFIX = 'Net_Gearman_Job_';
|
||||
const PLUGIN_INTERFACE = 'RippingCluster_Worker_IPlugin';
|
||||
|
||||
public static function init() {
|
||||
|
||||
}
|
||||
|
||||
public static function scan() {
|
||||
$candidatePlugins = parent::findPlugins(self::PLUGIN_DIR);
|
||||
|
||||
parent::loadPlugins($candidatePlugins, self::PLUGIN_PREFIX, self::PLUGIN_INTERFACE);
|
||||
}
|
||||
|
||||
public static function getPluginWorkerFunctions($plugin) {
|
||||
if ( ! self::isValidPlugin($plugin)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return call_user_func(array(self::classname($plugin), 'workerFunctions'));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
39
source/lib/RippingCluster/WorkerLogEntry.class.php
Normal file
39
source/lib/RippingCluster/WorkerLogEntry.class.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
class RippingCluster_WorkerLogEntry extends RippingCluster_LogEntry {
|
||||
|
||||
protected $jobId;
|
||||
|
||||
protected function __construct($id, $level, $ctime, $pid, $hostname, $progname, $line, $message, $jobId) {
|
||||
parent::__construct($id, $level, $ctime, $pid, $hostname, $progname, $line, $message);
|
||||
|
||||
$this->jobId = $jobId;
|
||||
}
|
||||
|
||||
public static function fromDatabaseRow($row) {
|
||||
return new self(
|
||||
$row['id'],
|
||||
$row['level'],
|
||||
$row['ctime'],
|
||||
$row['pid'],
|
||||
$row['hostname'],
|
||||
$row['progname'],
|
||||
$row['line'],
|
||||
$row['message'],
|
||||
$row['job_id']
|
||||
);
|
||||
}
|
||||
|
||||
public static function initialise() {
|
||||
parent::$table_name = 'worker_log';
|
||||
}
|
||||
|
||||
public function jobId() {
|
||||
return $this->jobId;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
RippingCluster_WorkerLogEntry::initialise();
|
||||
|
||||
?>
|
||||
Reference in New Issue
Block a user