the new Zendified URLs. Fixed bugs in the daemontool scripts. The installed scheduler is still not working at this point, but getting close.
580 lines
No EOL
20 KiB
PHP
580 lines
No EOL
20 KiB
PHP
<?php
|
|
require_once("StoredFile.php");
|
|
require_once("BasicStor.php");
|
|
|
|
class ScheduleGroup {
|
|
|
|
private $groupId;
|
|
|
|
public function __construct($p_groupId = null) {
|
|
$this->groupId = $p_groupId;
|
|
}
|
|
|
|
/**
|
|
* Return true if the schedule group exists in the DB.
|
|
* @return boolean
|
|
*/
|
|
public function exists() {
|
|
global $CC_CONFIG, $CC_DBC;
|
|
$sql = "SELECT COUNT(*) FROM ".$CC_CONFIG['scheduleTable']
|
|
." WHERE group_id=".$this->groupId;
|
|
$result = $CC_DBC->GetOne($sql);
|
|
if (PEAR::isError($result)) {
|
|
return $result;
|
|
}
|
|
return $result != "0";
|
|
}
|
|
|
|
/**
|
|
* Convert a date to an ID by stripping out all characters
|
|
* and padding with zeros.
|
|
*
|
|
* @param string $p_dateStr
|
|
*/
|
|
public static function dateToId($p_dateStr) {
|
|
$p_dateStr = str_replace(":", "", $p_dateStr);
|
|
$p_dateStr = str_replace(" ", "", $p_dateStr);
|
|
$p_dateStr = str_replace(".", "", $p_dateStr);
|
|
$p_dateStr = str_replace("-", "", $p_dateStr);
|
|
$p_dateStr = substr($p_dateStr, 0, 17);
|
|
$p_dateStr = str_pad($p_dateStr, 17, "0");
|
|
return $p_dateStr;
|
|
}
|
|
|
|
/**
|
|
* Add the two times together, return the result.
|
|
*
|
|
* @param string $p_baseTime
|
|
* Specified as YYYY-MM-DD HH:MM:SS
|
|
*
|
|
* @param string $p_addTime
|
|
* Specified as HH:MM:SS.nnnnnn
|
|
*
|
|
* @return string
|
|
* The end time, to the nearest second.
|
|
*/
|
|
// protected function calculateEndTime($p_startTime, $p_trackTime) {
|
|
// $p_trackTime = substr($p_startTime, 0, );
|
|
// $start = new DateTime();
|
|
// $interval = new DateInterval()
|
|
//
|
|
// }
|
|
|
|
/**
|
|
* Add a music clip or playlist to the schedule.
|
|
*
|
|
* @param $p_datetime
|
|
* In the format YYYY-MM-DD HH:MM:SS.mmmmmm
|
|
* @param $p_audioFileId
|
|
* (optional, either this or $p_playlistId must be set) DB ID of the audio file
|
|
* @param $p_playlistId
|
|
* (optional, either this of $p_audioFileId must be set) DB ID of the playlist
|
|
* @param $p_options
|
|
* Does nothing at the moment.
|
|
*
|
|
* @return int|PEAR_Error
|
|
* Return PEAR_Error if the item could not be added.
|
|
* Error code 555 is a scheduling conflict.
|
|
*/
|
|
public function add($p_datetime, $p_audioFileId = null, $p_playlistId = null, $p_options = null) {
|
|
global $CC_CONFIG, $CC_DBC;
|
|
if (!is_null($p_audioFileId)) {
|
|
// Schedule a single audio track
|
|
|
|
// Load existing track
|
|
$track = StoredFile::Recall($p_audioFileId);
|
|
if (is_null($track)) {
|
|
return new PEAR_Error("Could not find audio track.");
|
|
}
|
|
|
|
// Check if there are any conflicts with existing entries
|
|
$metadata = $track->getMetadata();
|
|
$length = trim($metadata["length"]);
|
|
if (empty($length)) {
|
|
return new PEAR_Error("Length is empty.");
|
|
}
|
|
if (!Schedule::isScheduleEmptyInRange($p_datetime, $length)) {
|
|
return new PEAR_Error("Schedule conflict.", 555);
|
|
}
|
|
|
|
// Insert into the table
|
|
$this->groupId = $CC_DBC->GetOne("SELECT nextval('schedule_group_id_seq')");
|
|
$id = $this->dateToId($p_datetime);
|
|
$sql = "INSERT INTO ".$CC_CONFIG["scheduleTable"]
|
|
." (id, playlist_id, starts, ends, clip_length, group_id, file_id)"
|
|
." VALUES ($id, 0, TIMESTAMP '$p_datetime', "
|
|
." (TIMESTAMP '$p_datetime' + INTERVAL '$length'),"
|
|
." '$length',"
|
|
." {$this->groupId}, $p_audioFileId)";
|
|
$result = $CC_DBC->query($sql);
|
|
if (PEAR::isError($result)) {
|
|
//var_dump($sql);
|
|
return $result;
|
|
}
|
|
return $this->groupId;
|
|
|
|
} elseif (!is_null($p_playlistId)){
|
|
// Schedule a whole playlist
|
|
|
|
// Load existing playlist
|
|
$playlist = Playlist::Recall($p_playlistId);
|
|
if (is_null($playlist)) {
|
|
return new PEAR_Error("Could not find playlist.");
|
|
}
|
|
|
|
// Check if there are any conflicts with existing entries
|
|
$length = trim($playlist->getLength());
|
|
//var_dump($length);
|
|
if (empty($length)) {
|
|
return new PEAR_Error("Length is empty.");
|
|
}
|
|
if (!Schedule::isScheduleEmptyInRange($p_datetime, $length)) {
|
|
return new PEAR_Error("Schedule conflict.", 555);
|
|
}
|
|
|
|
// Insert all items into the schedule
|
|
$this->groupId = $CC_DBC->GetOne("SELECT nextval('schedule_group_id_seq')");
|
|
$id = $this->dateToId($p_datetime);
|
|
$itemStartTime = $p_datetime;
|
|
|
|
$plItems = $playlist->getContents();
|
|
//var_dump($plItems);
|
|
foreach ($plItems as $row) {
|
|
$trackLength = $row["cliplength"];
|
|
//var_dump($trackLength);
|
|
$sql = "INSERT INTO ".$CC_CONFIG["scheduleTable"]
|
|
." (id, playlist_id, starts, ends, group_id, file_id,"
|
|
." clip_length, cue_in, cue_out, fade_in, fade_out)"
|
|
." VALUES ($id, $p_playlistId, TIMESTAMP '$itemStartTime', "
|
|
." (TIMESTAMP '$itemStartTime' + INTERVAL '$trackLength'),"
|
|
." '{$this->groupId}', '{$row['file_id']}', '$trackLength', '{$row['cuein']}',"
|
|
." '{$row['cueout']}', '{$row['fadein']}','{$row['fadeout']}')";
|
|
$result = $CC_DBC->query($sql);
|
|
if (PEAR::isError($result)) {
|
|
//var_dump($sql);
|
|
return $result;
|
|
}
|
|
$itemStartTime = $CC_DBC->getOne("SELECT TIMESTAMP '$itemStartTime' + INTERVAL '$trackLength'");
|
|
$id = $this->dateToId($itemStartTime);
|
|
}
|
|
return $this->groupId;
|
|
}
|
|
}
|
|
|
|
public function addAfter($p_groupId, $p_audioFileId) {
|
|
global $CC_CONFIG, $CC_DBC;
|
|
// Get the end time for the given entry
|
|
$sql = "SELECT ends FROM ".$CC_CONFIG["scheduleTable"]
|
|
." WHERE group_id=$p_groupId";
|
|
$startTime = $CC_DBC->GetOne($sql);
|
|
return $this->add($startTime, $p_audioFileId);
|
|
}
|
|
|
|
public function update() {
|
|
|
|
}
|
|
|
|
/**
|
|
* Remove the group from the schedule.
|
|
* Note: does not check if it is in the past, you can remove anything.
|
|
*
|
|
* @return boolean
|
|
* TRUE on success, false if there is no group ID defined.
|
|
*/
|
|
public function remove() {
|
|
global $CC_CONFIG, $CC_DBC;
|
|
if (is_null($this->groupId) || !is_numeric($this->groupId)) {
|
|
return false;
|
|
}
|
|
$sql = "DELETE FROM ".$CC_CONFIG["scheduleTable"]
|
|
." WHERE group_id = ".$this->groupId;
|
|
//echo $sql;
|
|
return $CC_DBC->query($sql);
|
|
}
|
|
|
|
/**
|
|
* Return the number of items in this group.
|
|
* @return string
|
|
*/
|
|
public function count() {
|
|
global $CC_CONFIG, $CC_DBC;
|
|
$sql = "SELECT COUNT(*) FROM {$CC_CONFIG['scheduleTable']}"
|
|
." WHERE group_id={$this->groupId}";
|
|
return $CC_DBC->GetOne($sql);
|
|
}
|
|
|
|
/*
|
|
* Return the list of items in this group as a 2D array.
|
|
* @return array
|
|
*/
|
|
public function getItems() {
|
|
global $CC_CONFIG, $CC_DBC;
|
|
$sql = "SELECT * FROM {$CC_CONFIG['scheduleTable']}"
|
|
." WHERE group_id={$this->groupId}";
|
|
return $CC_DBC->GetAll($sql);
|
|
}
|
|
|
|
public function reschedule($toDateTime) {
|
|
global $CC_CONFIG, $CC_DBC;
|
|
// $sql = "UPDATE ".$CC_CONFIG["scheduleTable"]. " SET id=, starts=,ends="
|
|
}
|
|
|
|
public function notifyGroupStartPlay() {
|
|
global $CC_CONFIG, $CC_DBC;
|
|
$sql = "UPDATE ".$CC_CONFIG['scheduleTable']
|
|
." SET schedule_group_played=TRUE"
|
|
." WHERE group_id=".$this->groupId;
|
|
return $CC_DBC->query($sql);
|
|
}
|
|
|
|
public function notifyMediaItemStartPlay($p_fileId) {
|
|
global $CC_CONFIG, $CC_DBC;
|
|
$sql = "UPDATE ".$CC_CONFIG['scheduleTable']
|
|
." SET media_item_played=TRUE"
|
|
." WHERE group_id=".$this->groupId
|
|
." AND file_id=".pg_escape_string($p_fileId);
|
|
return $CC_DBC->query($sql);
|
|
}
|
|
}
|
|
|
|
class Schedule {
|
|
|
|
function __construct() {
|
|
|
|
}
|
|
|
|
/**
|
|
* Return true if there is nothing in the schedule for the given start time
|
|
* up to the length of time after that.
|
|
*
|
|
* @param string $p_datetime
|
|
* In the format YYYY-MM-DD HH:MM:SS.mmmmmm
|
|
* @param string $p_length
|
|
* In the format HH:MM:SS.mmmmmm
|
|
* @return boolean|PEAR_Error
|
|
*/
|
|
public static function isScheduleEmptyInRange($p_datetime, $p_length) {
|
|
global $CC_CONFIG, $CC_DBC;
|
|
if (empty($p_length)) {
|
|
return new PEAR_Error("Schedule::isSchedulerEmptyInRange: param p_length is empty.");
|
|
}
|
|
$sql = "SELECT COUNT(*) FROM ".$CC_CONFIG["scheduleTable"]
|
|
." WHERE (starts >= '$p_datetime') "
|
|
." AND (ends <= (TIMESTAMP '$p_datetime' + INTERVAL '$p_length'))";
|
|
//$_SESSION["debug"] = $sql;
|
|
//var_dump($sql);
|
|
$count = $CC_DBC->GetOne($sql);
|
|
//var_dump($count);
|
|
return ($count == '0');
|
|
}
|
|
|
|
// public function onAddTrackToPlaylist($playlistId, $audioTrackId) {
|
|
//
|
|
// }
|
|
//
|
|
// public function onRemoveTrackFromPlaylist($playlistId, $audioTrackId) {
|
|
//
|
|
// }
|
|
|
|
/**
|
|
* Return TRUE if file is going to be played in the future.
|
|
*
|
|
* @param string $p_fileId
|
|
*/
|
|
public function IsFileScheduledInTheFuture($p_fileId)
|
|
{
|
|
global $CC_CONFIG, $CC_DBC;
|
|
$sql = "SELECT COUNT(*) FROM ".$CC_CONFIG["scheduleTable"]
|
|
." WHERE file_id = {$p_fileId} AND starts > NOW()";
|
|
$count = $CC_DBC->GetOne($sql);
|
|
if (is_numeric($count) && ($count != '0')) {
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns array indexed numberically of:
|
|
* "playlistId"/"playlist_id" (aliases to the same thing)
|
|
* "start"/"starts" (aliases to the same thing) as YYYY-MM-DD HH:MM:SS.nnnnnn
|
|
* "end"/"ends" (aliases to the same thing) as YYYY-MM-DD HH:MM:SS.nnnnnn
|
|
* "group_id"/"id" (aliases to the same thing)
|
|
* "clip_length" (for audio clips this is the length of the audio clip,
|
|
* for playlists this is the length of the entire playlist)
|
|
* "name" (playlist only)
|
|
* "creator" (playlist only)
|
|
* "file_id" (audioclip only)
|
|
* "count" (number of items in the playlist, always 1 for audioclips.
|
|
* Note that playlists with one item will also have count = 1.
|
|
*
|
|
* @param string $p_fromDateTime
|
|
* In the format YYYY-MM-DD HH:MM:SS.nnnnnn
|
|
* @param string $p_toDateTime
|
|
* In the format YYYY-MM-DD HH:MM:SS.nnnnnn
|
|
* @param boolean $p_playlistsOnly
|
|
* Retreive playlists as a single item.
|
|
* @return array
|
|
* Returns empty array if nothing found
|
|
*/
|
|
public static function GetItems($p_fromDateTime, $p_toDateTime, $p_playlistsOnly = true) {
|
|
global $CC_CONFIG, $CC_DBC;
|
|
$rows = array();
|
|
if (!$p_playlistsOnly) {
|
|
$sql = "SELECT * FROM ".$CC_CONFIG["scheduleTable"]
|
|
." WHERE (starts >= TIMESTAMP '$p_fromDateTime') "
|
|
." AND (ends <= TIMESTAMP '$p_toDateTime')";
|
|
$rows = $CC_DBC->GetAll($sql);
|
|
foreach ($rows as &$row) {
|
|
$row["count"] = "1";
|
|
$row["playlistId"] = $row["playlist_id"];
|
|
$row["start"] = $row["starts"];
|
|
$row["end"] = $row["ends"];
|
|
$row["id"] = $row["group_id"];
|
|
}
|
|
} else {
|
|
$sql = "SELECT MIN(name) AS name, MIN(creator) AS creator, group_id, "
|
|
." SUM(clip_length) AS clip_length,"
|
|
." MIN(file_id) AS file_id, COUNT(*) as count,"
|
|
." MIN(playlist_id) AS playlist_id, MIN(starts) AS starts, MAX(ends) AS ends"
|
|
." FROM ".$CC_CONFIG["scheduleTable"]
|
|
." LEFT JOIN ".$CC_CONFIG["playListTable"]." ON playlist_id = ".$CC_CONFIG["playListTable"].".id"
|
|
." WHERE (starts >= TIMESTAMP '$p_fromDateTime') AND (ends <= TIMESTAMP '$p_toDateTime')"
|
|
." GROUP BY group_id"
|
|
." ORDER BY starts";
|
|
//var_dump($sql);
|
|
$rows = $CC_DBC->GetAll($sql);
|
|
if (!PEAR::isError($rows)) {
|
|
foreach ($rows as &$row) {
|
|
$row["playlistId"] = $row["playlist_id"];
|
|
$row["start"] = $row["starts"];
|
|
$row["end"] = $row["ends"];
|
|
$row["id"] = $row["group_id"];
|
|
}
|
|
}
|
|
}
|
|
return $rows;
|
|
}
|
|
|
|
public function getSchedulerTime() {
|
|
|
|
}
|
|
|
|
public function getCurrentlyPlaying() {
|
|
|
|
}
|
|
|
|
public function getNextItem($nextCount = 1) {
|
|
|
|
}
|
|
|
|
public function getStatus() {
|
|
|
|
}
|
|
|
|
/**
|
|
* Convert a time string in the format "YYYY-MM-DD HH:mm:SS"
|
|
* to "YYYY-MM-DD-HH-mm-SS".
|
|
*
|
|
* @param string $p_time
|
|
* @return string
|
|
*/
|
|
private static function CcTimeToPypoTime($p_time)
|
|
{
|
|
$p_time = substr($p_time, 0, 19);
|
|
$p_time = str_replace(" ", "-", $p_time);
|
|
$p_time = str_replace(":", "-", $p_time);
|
|
return $p_time;
|
|
}
|
|
|
|
/**
|
|
* Convert a time string in the format "YYYY-MM-DD-HH-mm-SS" to
|
|
* "YYYY-MM-DD HH:mm:SS".
|
|
*
|
|
* @param string $p_time
|
|
* @return string
|
|
*/
|
|
private static function PypoTimeToCcTime($p_time)
|
|
{
|
|
$t = explode("-", $p_time);
|
|
return $t[0]."-".$t[1]."-".$t[2]." ".$t[3].":".$t[4].":00";
|
|
}
|
|
|
|
/**
|
|
* Return true if the input string is in the format YYYY-MM-DD-HH-mm
|
|
*
|
|
* @param string $p_time
|
|
* @return boolean
|
|
*/
|
|
public static function ValidPypoTimeFormat($p_time)
|
|
{
|
|
$t = explode("-", $p_time);
|
|
if (count($t) != 5) {
|
|
return false;
|
|
}
|
|
foreach ($t as $part) {
|
|
if (!is_numeric($part)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Converts a time value as a string (with format HH:MM:SS.mmmmmm) to
|
|
* millisecs.
|
|
*
|
|
* @param string $p_time
|
|
* @return int
|
|
*/
|
|
private static function WallTimeToMillisecs($p_time)
|
|
{
|
|
$t = explode(":", $p_time);
|
|
$millisecs = 0;
|
|
if (strpos($t[2], ".")) {
|
|
$secParts = explode(".", $t[2]);
|
|
$millisecs = $secParts[1];
|
|
$millisecs = substr($millisecs, 0, 3);
|
|
$millisecs = intval($millisecs);
|
|
$seconds = intval($secParts[0]);
|
|
} else {
|
|
$seconds = intval($t[2]);
|
|
}
|
|
$ret = $millisecs + ($seconds * 1000) + ($t[1] * 60 * 1000) + ($t[0] * 60 * 60 * 1000);
|
|
return $ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Compute the difference between two times in the format "HH:MM:SS.mmmmmm".
|
|
* Note: currently only supports calculating millisec differences.
|
|
*
|
|
* @param string $p_time1
|
|
* @param string $p_time2
|
|
* @return double
|
|
*/
|
|
private static function TimeDiff($p_time1, $p_time2)
|
|
{
|
|
$parts1 = explode(".", $p_time1);
|
|
$parts2 = explode(".", $p_time2);
|
|
$diff = 0;
|
|
if ( (count($parts1) > 1) && (count($parts2) > 1) ) {
|
|
$millisec1 = substr($parts1[1], 0, 3);
|
|
$millisec1 = str_pad($millisec1, 3, "0");
|
|
$millisec1 = intval($millisec1);
|
|
$millisec2 = substr($parts2[1], 0, 3);
|
|
$millisec2 = str_pad($millisec2, 3, "0");
|
|
$millisec2 = intval($millisec2);
|
|
$diff = abs(millisec1 - millisec2)/1000;
|
|
}
|
|
return $diff;
|
|
}
|
|
|
|
|
|
/**
|
|
* Export the schedule in json formatted for pypo (the liquidsoap scheduler)
|
|
*
|
|
* @param string $range
|
|
* In the format "YYYY-MM-DD HH:mm:ss"
|
|
* @param string $source
|
|
* In the format "YYYY-MM-DD HH:mm:ss"
|
|
*/
|
|
public static function ExportRangeAsJson($p_fromDateTime, $p_toDateTime)
|
|
{
|
|
global $CC_CONFIG, $CC_DBC;
|
|
$range_start = Schedule::PypoTimeToCcTime($p_fromDateTime);
|
|
$range_end = Schedule::PypoTimeToCcTime($p_toDateTime);
|
|
$range_dt = array('start' => $range_start, 'end' => $range_end);
|
|
//var_dump($range_dt);
|
|
|
|
// Scheduler wants everything in a playlist
|
|
$data = Schedule::GetItems($range_start, $range_end, true);
|
|
//echo "<pre>";var_dump($data);
|
|
$playlists = array();
|
|
|
|
if (is_array($data) && count($data) > 0)
|
|
{
|
|
foreach ($data as $dx)
|
|
{
|
|
// Is this the first item in the playlist?
|
|
$start = $dx['start'];
|
|
// chop off subseconds
|
|
$start = substr($start, 0, 19);
|
|
|
|
// Start time is the array key, needs to be in the format "YYYY-MM-DD-HH-mm-ss"
|
|
$pkey = Schedule::CcTimeToPypoTime($start);
|
|
$timestamp = strtotime($start);
|
|
$playlists[$pkey]['source'] = "PLAYLIST";
|
|
$playlists[$pkey]['x_ident'] = $dx["playlist_id"];
|
|
$playlists[$pkey]['subtype'] = '1'; // Just needs to be between 1 and 4 inclusive
|
|
$playlists[$pkey]['timestamp'] = $timestamp;
|
|
$playlists[$pkey]['duration'] = $dx['clip_length'];
|
|
$playlists[$pkey]['played'] = '0';
|
|
$playlists[$pkey]['schedule_id'] = $dx['group_id'];
|
|
$playlists[$pkey]['user_id'] = 0;
|
|
$playlists[$pkey]['id'] = $dx["playlist_id"];
|
|
$playlists[$pkey]['start'] = Schedule::CcTimeToPypoTime($dx["start"]);
|
|
$playlists[$pkey]['end'] = Schedule::CcTimeToPypoTime($dx["end"]);
|
|
}
|
|
}
|
|
|
|
foreach ($playlists as &$playlist)
|
|
{
|
|
$scheduleGroup = new ScheduleGroup($playlist["schedule_id"]);
|
|
$items = $scheduleGroup->getItems();
|
|
$medias = array();
|
|
$playlist['subtype'] = '1';
|
|
foreach ($items as $item)
|
|
{
|
|
$storedFile = StoredFile::Recall($item["file_id"]);
|
|
$uri = $storedFile->getFileUrl();
|
|
|
|
// For pypo, a cueout of zero means no cueout
|
|
$cueOut = "0";
|
|
if (Schedule::TimeDiff($item["cue_out"], $item["clip_length"]) > 0.001) {
|
|
$cueOut = Schedule::WallTimeToMillisecs($item["cue_out"]);
|
|
}
|
|
$medias[] = array(
|
|
'id' => $storedFile->getGunid(), //$item["file_id"],
|
|
'uri' => $uri,
|
|
'fade_in' => Schedule::WallTimeToMillisecs($item["fade_in"]),
|
|
'fade_out' => Schedule::WallTimeToMillisecs($item["fade_out"]),
|
|
'fade_cross' => 0,
|
|
'cue_in' => Schedule::WallTimeToMillisecs($item["cue_in"]),
|
|
'cue_out' => $cueOut,
|
|
'export_source' => 'scheduler'
|
|
);
|
|
}
|
|
$playlist['medias'] = $medias;
|
|
}
|
|
|
|
$result = array();
|
|
$result['status'] = array('range' => $range_dt, 'version' => "0.2");
|
|
$result['playlists'] = $playlists;
|
|
$result['check'] = 1;
|
|
|
|
print json_encode($result);
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove all items from the schedule in the given range.
|
|
*
|
|
* @param string $p_start
|
|
* In the format YYYY-MM-DD HH:MM:SS.nnnnnn
|
|
* @param string $p_end
|
|
* In the format YYYY-MM-DD HH:MM:SS.nnnnnn
|
|
*/
|
|
public static function RemoveItemsInRange($p_start, $p_end)
|
|
{
|
|
$items = Schedule::GetItems($p_start, $p_end);
|
|
foreach ($items as $item) {
|
|
$scheduleGroup = new ScheduleGroup($item["group_id"]);
|
|
$scheduleGroup->remove();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
?>
|