libretime/legacy/application/models/Block.php

1887 lines
66 KiB
PHP
Raw Permalink Normal View History

<?php
2012-07-30 23:31:54 +02:00
/**
* @copyright 2010 Sourcefabric O.P.S.
2022-08-25 16:25:54 +02:00
* @license https://www.gnu.org/licenses/gpl.txt
2012-07-30 23:31:54 +02:00
*/
class Application_Model_Block implements Application_Model_LibraryEditable
{
2012-07-30 23:31:54 +02:00
/**
* propel connection object.
*/
private $con;
2012-07-30 23:31:54 +02:00
/**
* unique id for the block.
*/
private $id;
private $block;
2012-07-30 23:31:54 +02:00
/**
* info needed to insert a new block element.
*/
2021-10-11 16:10:47 +02:00
private $blockItem = [
'id' => '',
'pos' => '',
'cliplength' => '',
'cuein' => '00:00:00',
'cueout' => '00:00:00',
'fadein' => '0.0',
'fadeout' => '0.0',
'crossfadeDuration' => 0,
];
// using propel's phpNames.
2021-10-11 16:10:47 +02:00
private $categories = [
'dc:title' => 'Name',
'dc:creator' => 'Creator',
'dc:description' => 'Description',
'dcterms:extent' => 'Length',
];
private static $modifier2CriteriaMap = [
CriteriaModifier::CONTAINS => Criteria::ILIKE,
CriteriaModifier::DOES_NOT_CONTAIN => Criteria::NOT_ILIKE,
CriteriaModifier::IS => Criteria::EQUAL,
CriteriaModifier::IS_NOT => Criteria::NOT_EQUAL,
CriteriaModifier::STARTS_WITH => Criteria::ILIKE,
CriteriaModifier::ENDS_WITH => Criteria::ILIKE,
CriteriaModifier::IS_GREATER_THAN => Criteria::GREATER_THAN,
CriteriaModifier::IS_LESS_THAN => Criteria::LESS_THAN,
CriteriaModifier::IS_IN_THE_RANGE => Criteria::CUSTOM,
CriteriaModifier::BEFORE => Criteria::CUSTOM,
CriteriaModifier::AFTER => Criteria::CUSTOM,
CriteriaModifier::BETWEEN => Criteria::CUSTOM,
2021-10-11 16:10:47 +02:00
];
public function __construct($id = null, $con = null)
2012-07-30 23:31:54 +02:00
{
if (isset($id)) {
$this->block = CcBlockQuery::create()->findPk($id);
2012-07-30 23:31:54 +02:00
if (is_null($this->block)) {
throw new BlockNotFoundException();
}
} else {
$this->block = new CcBlock();
2021-10-11 16:10:47 +02:00
$this->block->setDbUTime(new DateTime('now', new DateTimeZone('UTC')));
2012-07-30 23:31:54 +02:00
$this->block->save();
}
2021-10-11 16:10:47 +02:00
$this->blockItem['fadein'] = Application_Model_Preference::GetDefaultFadeIn();
$this->blockItem['fadeout'] = Application_Model_Preference::GetDefaultFadeOut();
$this->blockItem['crossfadeDuration'] = Application_Model_Preference::GetDefaultCrossfadeDuration();
2012-07-30 23:31:54 +02:00
$this->con = isset($con) ? $con : Propel::getConnection(CcBlockPeer::DATABASE_NAME);
$this->id = $this->block->getDbId();
}
2012-07-30 23:31:54 +02:00
/**
* Return local ID of virtual file.
*
* @return int
*/
public function getId()
{
return $this->id;
}
2012-07-30 23:31:54 +02:00
/**
2021-10-11 16:10:47 +02:00
* Rename stored virtual block.
2012-07-30 23:31:54 +02:00
*
* @param string $p_newname
*/
public function setName($p_newname)
{
$this->block->setDbName($p_newname);
2021-10-11 16:10:47 +02:00
$this->block->setDbMtime(new DateTime('now', new DateTimeZone('UTC')));
2012-07-30 23:31:54 +02:00
$this->block->save($this->con);
}
2012-07-30 23:31:54 +02:00
/**
2021-10-11 16:10:47 +02:00
* Get mnemonic block name.
2012-07-30 23:31:54 +02:00
*
* @return string
*/
public function getName()
{
return $this->block->getDbName();
}
2012-07-30 23:31:54 +02:00
public function setDescription($p_description)
{
$this->block->setDbDescription($p_description);
2021-10-11 16:10:47 +02:00
$this->block->setDbMtime(new DateTime('now', new DateTimeZone('UTC')));
2012-07-30 23:31:54 +02:00
$this->block->save($this->con);
}
2012-07-30 23:31:54 +02:00
public function getDescription()
{
return $this->block->getDbDescription();
}
2012-07-30 23:31:54 +02:00
public function getCreator()
{
return $this->block->getCcSubjs()->getDbLogin();
}
2012-07-30 23:31:54 +02:00
public function getCreatorId()
{
return $this->block->getCcSubjs()->getDbId();
}
2012-07-30 23:31:54 +02:00
public function setCreator($p_id)
{
$this->block->setDbCreatorId($p_id);
2021-10-11 16:10:47 +02:00
$this->block->setDbMtime(new DateTime('now', new DateTimeZone('UTC')));
2012-07-30 23:31:54 +02:00
$this->block->save($this->con);
}
2012-07-30 23:31:54 +02:00
public function getLastModified($format = null)
{
return $this->block->getDbMtime($format);
}
2012-07-30 23:31:54 +02:00
public function getSize()
{
return $this->block->countCcBlockcontentss();
}
2012-07-30 23:31:54 +02:00
/**
* Get the entire block as a two dimensional array, sorted in order of play.
2021-10-11 16:10:47 +02:00
*
* @param bool $filterFiles if this is true, it will only return files that has
* file_exists flag set to true
*
2012-07-30 23:31:54 +02:00
* @return array
*/
2021-10-11 16:10:47 +02:00
public function getContents($filterFiles = false)
2012-07-30 23:31:54 +02:00
{
2021-10-11 16:10:47 +02:00
$sql = <<<'SQL'
2012-09-12 17:31:41 +02:00
SELECT pc.id AS id,
pc.position,
pc.cliplength AS LENGTH,
pc.cuein,
pc.cueout,
pc.fadein,
pc.fadeout,
pc.trackoffset,
2012-09-12 17:31:41 +02:00
bl.type,
f.LENGTH AS orig_length,
f.id AS item_id,
f.track_title,
f.artist_name AS creator,
f.file_exists AS EXISTS,
f.filepath AS path,
f.mime as mime
2012-09-12 17:31:41 +02:00
FROM cc_blockcontents AS pc
LEFT JOIN cc_files AS f ON pc.file_id=f.id
LEFT JOIN cc_block AS bl ON pc.block_id = bl.id
WHERE pc.block_id = :block_id
SQL;
if ($filterFiles) {
2021-10-11 16:10:47 +02:00
$sql .= <<<'SQL'
AND f.file_exists = :file_exists
2012-09-12 21:36:37 +02:00
SQL;
}
2021-10-11 16:10:47 +02:00
$sql .= <<<'SQL'
ORDER BY pc.position
SQL;
2021-10-11 16:10:47 +02:00
$params = [':block_id' => $this->id];
if ($filterFiles) {
$params[':file_exists'] = $filterFiles;
}
$rows = Application_Common_Database::prepareAndExecute($sql, $params);
$offset = 0;
foreach ($rows as &$row) {
$clipSec = Application_Common_DateHelper::playlistTimeToSeconds($row['length']);
2013-04-29 23:01:08 +02:00
$row['trackSec'] = $clipSec;
2013-04-29 23:01:08 +02:00
$row['cueInSec'] = Application_Common_DateHelper::playlistTimeToSeconds($row['cuein']);
$row['cueOutSec'] = Application_Common_DateHelper::playlistTimeToSeconds($row['cueout']);
$trackoffset = $row['trackoffset'];
$offset += $clipSec;
$offset -= $trackoffset;
$offset_cliplength = Application_Common_DateHelper::secondsToPlaylistTime($offset);
// format the length for UI.
$formatter = new LengthFormatter($row['length']);
$row['length'] = $formatter->format();
$formatter = new LengthFormatter($offset_cliplength);
$row['offset'] = $formatter->format();
// format the fades in format 00(.0)
$fades = $this->getFadeInfo($row['position']);
$row['fadein'] = $fades[0];
$row['fadeout'] = $fades[1];
// format the cues in format 00:00:00(.0)
// we need to add the '.0' for cues and not fades
// because propel takes care of this for us
// (we use propel to fetch the fades)
$row['cuein'] = str_pad(substr($row['cuein'], 0, 10), 10, '.0');
$row['cueout'] = str_pad(substr($row['cueout'], 0, 10), 10, '.0');
// format original length
$formatter = new LengthFormatter($row['orig_length']);
$row['orig_length'] = $formatter->format();
// XSS exploit prevention
2021-10-11 16:10:47 +02:00
$row['track_title'] = htmlspecialchars($row['track_title']);
$row['creator'] = htmlspecialchars($row['creator']);
}
2012-07-30 23:31:54 +02:00
return $rows;
}
2012-07-30 23:31:54 +02:00
/**
* The database stores fades in 00:00:00 Time format with optional millisecond resolution .000000
* but this isn't practical since fades shouldn't be very long usually 1 second or less. This function
2012-07-30 23:31:54 +02:00
* will normalize the fade so that it looks like 00.000000 to the user.
2021-10-11 16:10:47 +02:00
*
* @param mixed $fade
*/
2012-07-30 23:31:54 +02:00
public function normalizeFade($fade)
{
// First get rid of the first six characters 00:00: which will be added back later for db update
2012-07-30 23:31:54 +02:00
$fade = substr($fade, 6);
// Second add .000000 if the fade does't have milliseconds format already
2021-10-11 16:10:47 +02:00
$dbFadeStrPos = strpos($fade, '.');
if ($dbFadeStrPos === false) {
2012-07-30 23:31:54 +02:00
$fade .= '.000000';
2021-10-11 16:10:47 +02:00
} else {
while (strlen($fade) < 9) {
$fade .= '0';
}
}
// done, just need to set back the formated values
2012-07-30 23:31:54 +02:00
return $fade;
}
public function getUnformatedLength()
2012-07-30 23:31:54 +02:00
{
$this->block->reload();
if ($this->isStatic()) {
$length = $this->block->getDbLength();
} else {
$length = $this->getDynamicBlockLength();
}
return $length;
}
public function getLength()
{
$this->block->reload();
2021-10-11 16:10:47 +02:00
$prepend = '';
if ($this->isStatic()) {
$length = $this->block->getDbLength();
} else {
$length = $this->getDynamicBlockLength();
if (!$this->hasItemLimit()) {
2021-10-11 16:10:47 +02:00
$prepend = '~';
}
}
$formatter = new LengthFormatter($length);
2021-10-11 16:10:47 +02:00
return $prepend . $formatter->format();
}
public function getDynamicBlockLength()
{
[$value, $modifier] = $this->getLimitValueAndModifier();
2021-10-11 16:10:47 +02:00
if ($modifier == 'items') {
$length = $value . ' ' . _('items');
} else {
2021-10-11 16:10:47 +02:00
$hour = '00';
$mins = '00';
if ($modifier == 'minutes') {
$mins = $value;
2021-10-11 16:10:47 +02:00
if ($value > 59) {
$hour = intval($value / 60);
$mins = $value % 60;
}
2021-10-11 16:10:47 +02:00
} elseif ($modifier == 'hours') {
$mins = $value * 60;
2021-10-11 16:10:47 +02:00
if ($mins > 59) {
$hour = intval($mins / 60);
$hour = str_pad($hour, 2, '0', STR_PAD_LEFT);
$mins %= 60;
}
}
2021-10-11 16:10:47 +02:00
$hour = str_pad($hour, 2, '0', STR_PAD_LEFT);
$mins = str_pad($mins, 2, '0', STR_PAD_LEFT);
$length = $hour . ':' . $mins . ':00';
}
return $length;
}
public function getLimitValueAndModifier()
{
$result = CcBlockcriteriaQuery::create()->filterByDbBlockId($this->id)
2021-10-11 16:10:47 +02:00
->filterByDbCriteria('limit')->findOne();
if ($result) {
$modifier = $result->getDbModifier();
$value = $result->getDbValue();
2021-10-11 16:10:47 +02:00
return [$value, $modifier];
}
}
// this function returns sum of all track length under this block.
public function getStaticLength()
{
2021-10-11 16:10:47 +02:00
$sql = <<<'SQL'
2012-09-12 17:35:28 +02:00
SELECT SUM(cliplength) AS LENGTH
2012-12-03 17:06:56 +01:00
FROM cc_blockcontents as bc
JOIN cc_files as f ON bc.file_id = f.id
2012-09-12 17:35:28 +02:00
WHERE block_id = :block_id
2012-12-03 17:06:56 +01:00
AND f.file_exists = true
2012-09-12 17:35:28 +02:00
SQL;
2021-10-11 16:10:47 +02:00
$result = Application_Common_Database::prepareAndExecute($sql, [':block_id' => $this->id], 'all', PDO::FETCH_NUM);
return $result[0][0];
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
private function insertBlockElement($info)
{
$row = new CcBlockcontents();
$row->setDbBlockId($this->id);
2021-10-11 16:10:47 +02:00
$row->setDbFileId($info['id']);
$row->setDbPosition($info['pos']);
$row->setDbCliplength($info['cliplength']);
$row->setDbCuein($info['cuein']);
$row->setDbCueout($info['cueout']);
$row->setDbFadein(Application_Common_DateHelper::secondsToPlaylistTime($info['fadein']));
$row->setDbFadeout(Application_Common_DateHelper::secondsToPlaylistTime($info['fadeout']));
$row->setDbTrackOffset($info['crossfadeDuration']);
2012-07-30 23:31:54 +02:00
$row->save($this->con);
// above save result update on cc_block table on length column.
// but $this->block doesn't get updated automatically
// so we need to manually grab it again from DB so it has updated values
// It is something to do FORMAT_ON_DEMAND( Lazy Loading )
$this->block = CcBlockQuery::create()->findPK($this->id);
}
2012-07-30 23:31:54 +02:00
private function buildEntry($p_item, $pos)
{
$file = CcFilesQuery::create()->findPK($p_item, $this->con);
2012-11-05 16:57:18 +01:00
if (isset($file) && $file->visible()) {
2021-10-11 16:10:47 +02:00
$entry = $this->blockItem;
$entry['id'] = $file->getDbId();
$entry['pos'] = $pos;
$entry['cueout'] = $file->getDbCueout();
$entry['cuein'] = $file->getDbCuein();
$cue_out = Application_Common_DateHelper::calculateLengthInSeconds($entry['cueout']);
$cue_in = Application_Common_DateHelper::calculateLengthInSeconds($entry['cuein']);
2021-10-11 16:10:47 +02:00
$entry['cliplength'] = Application_Common_DateHelper::secondsToPlaylistTime($cue_out - $cue_in);
2012-07-30 23:31:54 +02:00
return $entry;
}
2021-10-11 16:10:47 +02:00
throw new Exception('trying to add a file that does not exist.');
2012-07-30 23:31:54 +02:00
}
public function isStatic()
{
2021-10-11 16:10:47 +02:00
return $this->block->getDbType() == 'static';
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
/*
* @param array $p_items
* an array of audioclips to add to the block
* @param int|null $p_afterItem
* item which to add the new items after in the block, null if added to the end.
* @param string (before|after) $addAfter
* whether to add the clips before or after the selected item.
*/
2021-10-11 16:10:47 +02:00
public function addAudioClips($p_items, $p_afterItem = null, $addType = 'after')
2012-07-30 23:31:54 +02:00
{
$this->con->beginTransaction();
2021-10-11 16:10:47 +02:00
$contentsToUpdate = [];
2012-07-30 23:31:54 +02:00
try {
if (is_numeric($p_afterItem)) {
2012-08-22 00:41:56 +02:00
Logging::info("Finding block content item {$p_afterItem}");
2012-07-30 23:31:54 +02:00
$afterItem = CcBlockcontentsQuery::create()->findPK($p_afterItem);
$index = $afterItem->getDbPosition();
2012-08-22 00:41:56 +02:00
Logging::info("index is {$index}");
2012-07-30 23:31:54 +02:00
$pos = ($addType == 'after') ? $index + 1 : $index;
2012-07-30 23:31:54 +02:00
$contentsToUpdate = CcBlockcontentsQuery::create()
2021-10-11 16:10:47 +02:00
->filterByDbBlockId($this->id)
->filterByDbPosition($pos, Criteria::GREATER_EQUAL)
->orderByDbPosition()
->find($this->con);
2021-10-11 16:10:47 +02:00
Logging::info('Adding to block');
2012-08-22 00:41:56 +02:00
Logging::info("at position {$pos}");
2012-07-30 23:31:54 +02:00
} else {
// add to the end of the block
2012-07-30 23:31:54 +02:00
if ($addType == 'after') {
$pos = $this->getSize();
}
// add to the beginning of the block.
2012-07-30 23:31:54 +02:00
else {
$pos = 0;
2012-07-30 23:31:54 +02:00
$contentsToUpdate = CcBlockcontentsQuery::create()
2021-10-11 16:10:47 +02:00
->filterByDbBlockId($this->id)
->orderByDbPosition()
->find($this->con);
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
$contentsToUpdate = CcBlockcontentsQuery::create()
2021-10-11 16:10:47 +02:00
->filterByDbBlockId($this->id)
->filterByDbPosition($pos, Criteria::GREATER_EQUAL)
->orderByDbPosition()
->find($this->con);
2021-10-11 16:10:47 +02:00
Logging::info('Adding to block');
2012-08-22 00:41:56 +02:00
Logging::info("at position {$pos}");
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
foreach ($p_items as $ac) {
// Logging::info("Adding audio file {$ac[0]}");
try {
if (is_array($ac) && $ac[1] == 'audioclip') {
$res = $this->insertBlockElement($this->buildEntry($ac[0], $pos));
// update is_playlist flag in cc_files to indicate the
// file belongs to a playlist or block (in this case a block)
$db_file = CcFilesQuery::create()->findPk($ac[0], $this->con);
$db_file->setDbIsPlaylist(true)->save($this->con);
++$pos;
} elseif (!is_array($ac)) {
$res = $this->insertBlockElement($this->buildEntry($ac, $pos));
++$pos;
$db_file = CcFilesQuery::create()->findPk($ac, $this->con);
$db_file->setDbIsPlaylist(true)->save($this->con);
}
} catch (Exception $e) {
2012-09-04 23:22:21 +02:00
Logging::info($e->getMessage());
}
2012-07-30 23:31:54 +02:00
}
// reset the positions of the remaining items.
2021-10-11 16:10:47 +02:00
for ($i = 0; $i < count($contentsToUpdate); ++$i) {
2012-07-30 23:31:54 +02:00
$contentsToUpdate[$i]->setDbPosition($pos);
$contentsToUpdate[$i]->save($this->con);
++$pos;
2012-07-30 23:31:54 +02:00
}
2021-10-11 16:10:47 +02:00
$this->block->setDbMtime(new DateTime('now', new DateTimeZone('UTC')));
2012-07-30 23:31:54 +02:00
$this->block->save($this->con);
2012-07-30 23:31:54 +02:00
$this->con->commit();
$this->updateBlockLengthInAllPlaylist();
2012-07-30 23:31:54 +02:00
} catch (Exception $e) {
$this->con->rollback();
2021-10-11 16:10:47 +02:00
2012-07-30 23:31:54 +02:00
throw $e;
}
}
2012-07-30 23:31:54 +02:00
/**
2021-10-11 16:10:47 +02:00
* Move audioClip to the new position in the block.
2012-07-30 23:31:54 +02:00
*
* @param array $p_items
2021-10-11 16:10:47 +02:00
* array of unique ids of the selected items
* @param int $p_afterItem
* unique id of the item to move the clip after
2012-07-30 23:31:54 +02:00
*/
2021-10-11 16:10:47 +02:00
public function moveAudioClips($p_items, $p_afterItem = null)
2012-07-30 23:31:54 +02:00
{
$this->con->beginTransaction();
2012-07-30 23:31:54 +02:00
try {
$contentsToMove = CcBlockcontentsQuery::create()
2021-10-11 16:10:47 +02:00
->filterByDbId($p_items, Criteria::IN)
->orderByDbPosition()
->find($this->con);
2012-07-30 23:31:54 +02:00
$otherContent = CcBlockcontentsQuery::create()
2021-10-11 16:10:47 +02:00
->filterByDbId($p_items, Criteria::NOT_IN)
->filterByDbBlockId($this->id)
->orderByDbPosition()
->find($this->con);
2012-07-30 23:31:54 +02:00
$pos = 0;
// moving items to beginning of the block.
2012-07-30 23:31:54 +02:00
if (is_null($p_afterItem)) {
2021-10-11 16:10:47 +02:00
Logging::info('moving items to beginning of block');
2012-07-30 23:31:54 +02:00
foreach ($contentsToMove as $item) {
2012-08-22 00:41:56 +02:00
Logging::info("item {$item->getDbId()} to pos {$pos}");
2012-07-30 23:31:54 +02:00
$item->setDbPosition($pos);
$item->save($this->con);
++$pos;
2012-07-30 23:31:54 +02:00
}
foreach ($otherContent as $item) {
2012-08-22 00:41:56 +02:00
Logging::info("item {$item->getDbId()} to pos {$pos}");
2012-07-30 23:31:54 +02:00
$item->setDbPosition($pos);
$item->save($this->con);
++$pos;
2012-07-30 23:31:54 +02:00
}
} else {
2012-08-22 00:41:56 +02:00
Logging::info("moving items after {$p_afterItem}");
2012-07-30 23:31:54 +02:00
foreach ($otherContent as $item) {
2012-08-22 00:41:56 +02:00
Logging::info("item {$item->getDbId()} to pos {$pos}");
2012-07-30 23:31:54 +02:00
$item->setDbPosition($pos);
$item->save($this->con);
++$pos;
2012-07-30 23:31:54 +02:00
if ($item->getDbId() == $p_afterItem) {
foreach ($contentsToMove as $move) {
2012-08-22 00:41:56 +02:00
Logging::info("item {$move->getDbId()} to pos {$pos}");
2012-07-30 23:31:54 +02:00
$move->setDbPosition($pos);
$move->save($this->con);
++$pos;
2012-07-30 23:31:54 +02:00
}
}
}
}
2012-07-30 23:31:54 +02:00
$this->con->commit();
} catch (Exception $e) {
$this->con->rollback();
2021-10-11 16:10:47 +02:00
2012-07-30 23:31:54 +02:00
throw $e;
}
2012-07-30 23:31:54 +02:00
$this->block = CcBlockQuery::create()->findPK($this->id);
2021-10-11 16:10:47 +02:00
$this->block->setDbMtime(new DateTime('now', new DateTimeZone('UTC')));
2012-07-30 23:31:54 +02:00
$this->block->save($this->con);
}
2012-07-30 23:31:54 +02:00
/**
2021-10-11 16:10:47 +02:00
* Remove audioClip from block.
2012-07-30 23:31:54 +02:00
*
* @param array $p_items
2021-10-11 16:10:47 +02:00
* array of unique item ids to remove from the block..
2012-07-30 23:31:54 +02:00
*/
public function delAudioClips($p_items)
{
$this->con->beginTransaction();
2012-07-30 23:31:54 +02:00
try {
// we need to get the file id of the item we are deleting
// before the item gets deleted from the block
$itemsToDelete = CcBlockcontentsQuery::create()
->filterByPrimaryKeys($p_items)
->filterByDbFileId(null, Criteria::NOT_EQUAL)
->find($this->con);
2012-07-30 23:31:54 +02:00
CcBlockcontentsQuery::create()
2021-10-11 16:10:47 +02:00
->findPKs($p_items)
->delete($this->con);
// now that the items have been deleted we can update the
// is_playlist flag in cc_files
Application_Model_StoredFile::setIsPlaylist($itemsToDelete, 'block', false);
2012-07-30 23:31:54 +02:00
$contents = CcBlockcontentsQuery::create()
2021-10-11 16:10:47 +02:00
->filterByDbBlockId($this->id)
->orderByDbPosition()
->find($this->con);
// reset the positions of the remaining items.
2021-10-11 16:10:47 +02:00
for ($i = 0; $i < count($contents); ++$i) {
2012-07-30 23:31:54 +02:00
$contents[$i]->setDbPosition($i);
$contents[$i]->save($this->con);
}
2021-10-11 16:10:47 +02:00
$this->block->setDbMtime(new DateTime('now', new DateTimeZone('UTC')));
2012-07-30 23:31:54 +02:00
$this->block->save($this->con);
2012-07-30 23:31:54 +02:00
$this->con->commit();
$this->updateBlockLengthInAllPlaylist();
2012-07-30 23:31:54 +02:00
} catch (Exception $e) {
$this->con->rollback();
2021-10-11 16:10:47 +02:00
2012-07-30 23:31:54 +02:00
throw $e;
}
}
2012-07-30 23:31:54 +02:00
public function getFadeInfo($pos)
{
// Logging::info("Getting fade info for pos {$pos}");
2012-07-30 23:31:54 +02:00
$row = CcBlockcontentsQuery::create()
2021-10-11 16:10:47 +02:00
->joinWith(CcFilesPeer::OM_CLASS)
->filterByDbBlockId($this->id)
->filterByDbPosition($pos)
->findOne();
// Propel returns values in form 00.000000 format which is for only seconds.
// We only want to display 1 decimal
$fadeIn = substr($row->getDbFadein(), 0, 4);
$fadeOut = substr($row->getDbFadeout(), 0, 4);
2021-10-11 16:10:47 +02:00
return [$fadeIn, $fadeOut];
2012-07-30 23:31:54 +02:00
}
2013-04-30 21:34:20 +02:00
/*
* create a crossfade from item in cc_playlist_contents with $id1 to item $id2.
*
* $fadeOut length of fade out in seconds if $id1
* $fadeIn length of fade in in seconds of $id2
* $offset time in seconds from end of $id1 that $id2 will begin to play.
*/
public function createCrossfade($id1, $fadeOut, $id2, $fadeIn, $offset)
{
2021-10-11 16:10:47 +02:00
$this->con->beginTransaction();
if (!isset($offset)) {
$offset = Application_Model_Preference::GetDefaultCrossfadeDuration();
}
try {
if (isset($id1)) {
$this->changeFadeInfo($id1, null, $fadeOut);
}
if (isset($id2)) {
$this->changeFadeInfo($id2, $fadeIn, null, $offset);
}
$this->con->commit();
} catch (Exception $e) {
$this->con->rollback();
throw $e;
}
}
2012-07-30 23:31:54 +02:00
/**
2021-10-11 16:10:47 +02:00
* Change fadeIn and fadeOut values for block Element.
*
* @param string $fadeIn
* new value in ss.ssssss or extent format
* @param string $fadeOut
* new value in ss.ssssss or extent format
* @param mixed $id
* @param null|mixed $offset
*
* @return bool
*/
public function changeFadeInfo($id, $fadeIn, $fadeOut, $offset = null)
2012-07-30 23:31:54 +02:00
{
// See issue CC-2065, pad the fadeIn and fadeOut so that it is TIME compatable with the DB schema
// For the top level PlayList either fadeIn or fadeOut will sometimes be Null so need a gaurd against
// setting it to nonNull for checks down below
2021-10-11 16:10:47 +02:00
$fadeIn = $fadeIn ? '00:00:' . $fadeIn : $fadeIn;
$fadeOut = $fadeOut ? '00:00:' . $fadeOut : $fadeOut;
2012-07-30 23:31:54 +02:00
$this->con->beginTransaction();
2012-07-30 23:31:54 +02:00
try {
$row = CcBlockcontentsQuery::create()->findPK($id);
2012-07-30 23:31:54 +02:00
if (is_null($row)) {
2021-10-11 16:10:47 +02:00
throw new Exception('Block item does not exist.');
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
$clipLength = $row->getDbCliplength();
2012-07-30 23:31:54 +02:00
if (!is_null($fadeIn)) {
2021-10-11 16:10:47 +02:00
$sql = 'SELECT :fade_in::INTERVAL > :clip_length::INTERVAL';
$params = [
':fade_in' => $fadeIn,
2021-10-11 16:10:47 +02:00
':clip_length' => $clipLength,
];
$result = Application_Common_Database::prepareAndExecute($sql, $params, 'column');
if ($result) {
// "Fade In can't be larger than overall playlength.";
$fadeIn = $clipLength;
2012-07-30 23:31:54 +02:00
}
$row->setDbFadein($fadeIn);
2013-04-30 21:34:20 +02:00
if (!is_null($offset)) {
2021-10-11 16:10:47 +02:00
$row->setDbTrackOffset($offset);
Logging::info("Setting offset {$offset} on item {$id}");
$row->save($this->con);
}
2012-07-30 23:31:54 +02:00
}
if (!is_null($fadeOut)) {
2021-10-11 16:10:47 +02:00
$sql = 'SELECT :fade_out::INTERVAL > :clip_length::INTERVAL';
$params = [
':fade_out' => $fadeOut,
2021-10-11 16:10:47 +02:00
':clip_length' => $clipLength,
];
$result = Application_Common_Database::prepareAndExecute($sql, $params, 'column');
if ($result) {
// "Fade Out can't be larger than overall playlength.";
$fadeOut = $clipLength;
2012-07-30 23:31:54 +02:00
}
$row->setDbFadeout($fadeOut);
}
2012-07-30 23:31:54 +02:00
$row->save($this->con);
2021-10-11 16:10:47 +02:00
$this->block->setDbMtime(new DateTime('now', new DateTimeZone('UTC')));
2012-07-30 23:31:54 +02:00
$this->block->save($this->con);
2012-07-30 23:31:54 +02:00
$this->con->commit();
} catch (Exception $e) {
$this->con->rollback();
2021-10-11 16:10:47 +02:00
2012-07-30 23:31:54 +02:00
throw $e;
}
2021-10-11 16:10:47 +02:00
return ['fadeIn' => $fadeIn, 'fadeOut' => $fadeOut];
2012-07-30 23:31:54 +02:00
}
public function setfades($fadein, $fadeout)
2012-07-30 23:31:54 +02:00
{
if (isset($fadein)) {
2012-08-22 00:41:56 +02:00
Logging::info("Setting block fade in {$fadein}");
2012-07-30 23:31:54 +02:00
$row = CcBlockcontentsQuery::create()
2021-10-11 16:10:47 +02:00
->filterByDbBlockId($this->id)
->filterByDbPosition(0)
->findOne($this->con);
2012-07-30 23:31:54 +02:00
$this->changeFadeInfo($row->getDbId(), $fadein, null);
}
2012-07-30 23:31:54 +02:00
if (isset($fadeout)) {
2012-08-22 00:41:56 +02:00
Logging::info("Setting block fade out {$fadeout}");
2012-07-30 23:31:54 +02:00
$row = CcBlockcontentsQuery::create()
2021-10-11 16:10:47 +02:00
->filterByDbBlockId($this->id)
->filterByDbPosition($this->getSize() - 1)
->findOne($this->con);
2012-07-30 23:31:54 +02:00
$this->changeFadeInfo($row->getDbId(), null, $fadeout);
}
}
2012-07-30 23:31:54 +02:00
/**
2021-10-11 16:10:47 +02:00
* Change cueIn/cueOut values for block element.
*
* @param string $cueIn
* new value in ss.ssssss or extent format
* @param string $cueOut
* new value in ss.ssssss or extent format
* @param mixed $id
*
* @return bool or pear error object
*/
2012-07-30 23:31:54 +02:00
public function changeClipLength($id, $cueIn, $cueOut)
{
$this->con->beginTransaction();
2021-10-11 16:10:47 +02:00
$errArray = [];
2012-07-30 23:31:54 +02:00
try {
if (is_null($cueIn) && is_null($cueOut)) {
2021-10-11 16:10:47 +02:00
$errArray['error'] = _('Cue in and cue out are null.');
2012-07-30 23:31:54 +02:00
return $errArray;
}
2012-07-30 23:31:54 +02:00
$row = CcBlockcontentsQuery::create()
2021-10-11 16:10:47 +02:00
->joinWith(CcFilesPeer::OM_CLASS)
->filterByPrimaryKey($id)
->findOne($this->con);
2012-07-30 23:31:54 +02:00
if (is_null($row)) {
2021-10-11 16:10:47 +02:00
throw new Exception('Block item does not exist.');
2012-07-30 23:31:54 +02:00
}
2021-10-11 16:10:47 +02:00
$oldCueIn = $row->getDBCuein();
2012-07-30 23:31:54 +02:00
$oldCueOut = $row->getDbCueout();
2021-10-11 16:10:47 +02:00
$fadeIn = $row->getDbFadein();
$fadeOut = $row->getDbFadeout();
2012-07-30 23:31:54 +02:00
$file = $row->getCcFiles($this->con);
$origLength = $file->getDbLength();
2012-07-30 23:31:54 +02:00
if (!is_null($cueIn) && !is_null($cueOut)) {
2021-10-11 16:10:47 +02:00
if ($cueOut === '') {
2012-07-30 23:31:54 +02:00
$cueOut = $origLength;
}
2021-10-11 16:10:47 +02:00
$sql = 'SELECT :cue_out::INTERVAL > :orig_length::INTERVAL';
$params = [
':cue_out' => $cueOut,
2021-10-11 16:10:47 +02:00
':orig_length' => $origLength,
];
$result = Application_Common_Database::prepareAndExecute($sql, $params, 'column');
if ($result) {
2021-10-11 16:10:47 +02:00
$errArray['error'] = _("Can't set cue out to be greater than file length.");
return $errArray;
2012-07-30 23:31:54 +02:00
}
2021-10-11 16:10:47 +02:00
$sql = 'SELECT :cue_in::INTERVAL > :cue_out::INTERVAL';
$params = [
':cue_in' => $cueIn,
2021-10-11 16:10:47 +02:00
':cue_out' => $cueOut,
];
$result = Application_Common_Database::prepareAndExecute($sql, $params, 'column');
if ($result) {
2021-10-11 16:10:47 +02:00
$errArray['error'] = _("Can't set cue in to be larger than cue out.");
return $errArray;
2012-07-30 23:31:54 +02:00
}
2021-10-11 16:10:47 +02:00
$sql = 'SELECT :cue_out::INTERVAL - :cue_in::INTERVAL';
$result = Application_Common_Database::prepareAndExecute($sql, $params, 'column');
$cliplength = $result;
2012-07-30 23:31:54 +02:00
$row->setDbCuein($cueIn);
$row->setDbCueout($cueOut);
$row->setDBCliplength($cliplength);
} elseif (!is_null($cueIn)) {
2021-10-11 16:10:47 +02:00
$sql = 'SELECT :cue_in::INTERVAL > :old_cue_out::INTERVAL';
$params = [
':cue_in' => $cueIn,
2021-10-11 16:10:47 +02:00
':old_cue_out' => $oldCueOut,
];
$result = Application_Common_Database::prepareAndExecute($sql, $params, 'column');
if ($result) {
2021-10-11 16:10:47 +02:00
$errArray['error'] = _("Can't set cue in to be larger than cue out.");
return $errArray;
2012-07-30 23:31:54 +02:00
}
2021-10-11 16:10:47 +02:00
$sql = 'SELECT :old_cue_out::INTERVAL - :cue_in::INTERVAL';
$result = Application_Common_Database::prepareAndExecute($sql, $params, 'column');
$cliplength = $result;
2012-07-30 23:31:54 +02:00
$row->setDbCuein($cueIn);
$row->setDBCliplength($cliplength);
} elseif (!is_null($cueOut)) {
2021-10-11 16:10:47 +02:00
if ($cueOut === '') {
2012-07-30 23:31:54 +02:00
$cueOut = $origLength;
}
2021-10-11 16:10:47 +02:00
$sql = 'SELECT :cue_out::INTERVAL > :orig_length::INTERVAL';
$params = [
':cue_out' => $cueOut,
2021-10-11 16:10:47 +02:00
':orig_length' => $origLength,
];
$result = Application_Common_Database::prepareAndExecute($sql, $params, 'column');
if ($result) {
2021-10-11 16:10:47 +02:00
$errArray['error'] = _("Can't set cue out to be greater than file length.");
return $errArray;
}
2021-10-11 16:10:47 +02:00
$sql = 'SELECT :cue_out::INTERVAL < :old_cue_in::INTERVAL';
$params = [
':cue_out' => $cueOut,
2021-10-11 16:10:47 +02:00
':old_cue_in' => $oldCueIn,
];
$result = Application_Common_Database::prepareAndExecute($sql, $params, 'column');
if ($result) {
2021-10-11 16:10:47 +02:00
$errArray['error'] = _("Can't set cue out to be smaller than cue in.");
return $errArray;
2012-07-30 23:31:54 +02:00
}
2021-10-11 16:10:47 +02:00
$sql = 'SELECT :cue_out::INTERVAL - :old_cue_in::INTERVAL';
$result = Application_Common_Database::prepareAndExecute($sql, $params, 'column');
$cliplength = $result;
2012-07-30 23:31:54 +02:00
$row->setDbCueout($cueOut);
$row->setDBCliplength($cliplength);
}
2012-07-30 23:31:54 +02:00
$cliplength = $row->getDbCliplength();
2021-10-11 16:10:47 +02:00
$sql = 'SELECT :fade_in::INTERVAL > :clip_length::INTERVAL';
$params = [
':fade_in' => $fadeIn,
2021-10-11 16:10:47 +02:00
':clip_length' => $cliplength,
];
$result = Application_Common_Database::prepareAndExecute($sql, $params, 'column');
if ($result) {
$fadeIn = $cliplength;
$row->setDbFadein($fadeIn);
2012-07-30 23:31:54 +02:00
}
2021-10-11 16:10:47 +02:00
$sql = 'SELECT :fade_out::INTERVAL > :clip_length::INTERVAL';
$params = [
':fade_out' => $fadeOut,
2021-10-11 16:10:47 +02:00
':clip_length' => $cliplength,
];
$result = Application_Common_Database::prepareAndExecute($sql, $params, 'column');
if ($result) {
$fadeOut = $cliplength;
$row->setDbFadein($fadeOut);
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
$row->save($this->con);
2021-10-11 16:10:47 +02:00
$this->block->setDbMtime(new DateTime('now', new DateTimeZone('UTC')));
2012-07-30 23:31:54 +02:00
$this->block->save($this->con);
2012-07-30 23:31:54 +02:00
$this->con->commit();
} catch (Exception $e) {
$this->con->rollback();
2021-10-11 16:10:47 +02:00
2012-07-30 23:31:54 +02:00
throw $e;
}
2022-07-07 20:01:15 +02:00
return [
'cliplength' => $cliplength, 'cueIn' => $cueIn, 'cueOut' => $cueOut, 'length' => $this->getUnformatedLength(),
'fadeIn' => $fadeIn, 'fadeOut' => $fadeOut,
];
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
public function getAllPLMetaData()
{
$categories = $this->categories;
2021-10-11 16:10:47 +02:00
$md = [];
2012-07-30 23:31:54 +02:00
foreach ($categories as $key => $val) {
$method = 'get' . $val;
2021-10-11 16:10:47 +02:00
$md[$key] = $this->{$method}();
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
return $md;
}
2012-07-30 23:31:54 +02:00
public function getMetaData($category)
{
$cat = $this->categories[$category];
$method = 'get' . $cat;
2021-10-11 16:10:47 +02:00
return $this->{$method}();
2012-07-30 23:31:54 +02:00
}
public function setMetadata($category, $value)
2012-07-30 23:31:54 +02:00
{
$cat = $this->categories[$category];
2012-07-30 23:31:54 +02:00
$method = 'set' . $cat;
2021-10-11 16:10:47 +02:00
$this->{$method}($value);
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
public static function getBlockCount()
{
2021-10-11 16:10:47 +02:00
$sql = 'SELECT count(*) as cnt FROM cc_playlist';
2021-10-11 16:10:47 +02:00
return Application_Common_Database::prepareAndExecute(
$sql,
[],
Application_Common_Database::COLUMN
);
}
2012-07-30 23:31:54 +02:00
/**
2021-10-11 16:10:47 +02:00
* Delete the file from all blocks.
*
* @param string $p_fileId
*/
2012-07-30 23:31:54 +02:00
public static function DeleteFileFromAllBlocks($p_fileId)
{
2021-10-11 16:10:47 +02:00
CcBlockcontentsQuery::create()->filterByDbFileId($p_fileId)->delete();
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
/**
2021-10-11 16:10:47 +02:00
* Delete blocks that match the ids..
*
* @param array $p_ids
* @param mixed $p_userId
*/
2012-07-30 23:31:54 +02:00
public static function deleteBlocks($p_ids, $p_userId)
{
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new Application_Model_User($userInfo->id);
2021-10-11 16:10:47 +02:00
$isAdminOrPM = $user->isUserType([UTYPE_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER]);
// get only the files from the blocks
// we are about to delete
$itemsToDelete = CcBlockcontentsQuery::create()
->filterByDbBlockId($p_ids)
->filterByDbFileId(null, Criteria::NOT_EQUAL)
->find();
$updateIsPlaylistFlag = false;
if (!$isAdminOrPM) {
$leftOver = self::blocksNotOwnedByUser($p_ids, $p_userId);
if (count($leftOver) == 0) {
CcBlockQuery::create()->findPKs($p_ids)->delete();
$updateIsPlaylistFlag = true;
} else {
2021-10-11 16:10:47 +02:00
throw new BlockNoPermissionException();
}
2012-07-30 23:31:54 +02:00
} else {
CcBlockQuery::create()->findPKs($p_ids)->delete();
$updateIsPlaylistFlag = true;
}
if ($updateIsPlaylistFlag) {
// update is_playlist flag in cc_files
Application_Model_StoredFile::setIsPlaylist(
$itemsToDelete,
'block',
false
);
2012-07-30 23:31:54 +02:00
}
}
2012-07-30 23:31:54 +02:00
// This function returns that are not owen by $p_user_id among $p_ids
private static function blocksNotOwnedByUser($p_ids, $p_userId)
2012-07-30 23:31:54 +02:00
{
$ownedByUser = CcBlockQuery::create()->filterByDbCreatorId($p_userId)->find()->getData();
$selectedPls = $p_ids;
2021-10-11 16:10:47 +02:00
$ownedPls = [];
2012-07-30 23:31:54 +02:00
foreach ($ownedByUser as $pl) {
if (in_array($pl->getDbId(), $selectedPls)) {
$ownedPls[] = $pl->getDbId();
}
}
2021-10-11 16:10:47 +02:00
return array_diff($selectedPls, $ownedPls);
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
/**
2021-10-11 16:10:47 +02:00
* Delete all files from block.
*/
2012-07-30 23:31:54 +02:00
public function deleteAllFilesFromBlock()
{
// get only the files from the playlist
// we are about to clear out
$itemsToDelete = CcBlockcontentsQuery::create()
->filterByDbBlockId($this->id)
->filterByDbFileId(null, Criteria::NOT_EQUAL)
->find();
2012-07-30 23:31:54 +02:00
CcBlockcontentsQuery::create()->findByDbBlockId($this->id)->delete();
// update is_playlist flag in cc_files
Application_Model_StoredFile::setIsPlaylist(
$itemsToDelete,
'block',
false
);
// $this->block->reload();
2021-10-11 16:10:47 +02:00
$this->block->setDbMtime(new DateTime('now', new DateTimeZone('UTC')));
$this->block->save($this->con);
$this->con->commit();
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
// smart block functions start
public function shuffleSmartBlock()
{
2012-07-30 23:31:54 +02:00
// if it here that means it's static pl
2021-10-11 16:10:47 +02:00
$this->saveType('static');
2012-07-30 23:31:54 +02:00
$contents = CcBlockcontentsQuery::create()
2021-10-11 16:10:47 +02:00
->filterByDbBlockId($this->id)
->orderByDbPosition()
->find();
2021-10-11 16:10:47 +02:00
$shuffledPos = range(0, count($contents) - 1);
2012-07-30 23:31:54 +02:00
shuffle($shuffledPos);
foreach ($contents as $item) {
$item->setDbPosition(array_shift($shuffledPos));
$item->save();
}
2021-10-11 16:10:47 +02:00
return ['result' => 0];
}
2012-07-30 23:31:54 +02:00
public function saveType($p_blockType)
{
// saving dynamic/static flag
CcBlockQuery::create()->findPk($this->id)->setDbType($p_blockType)->save();
}
public function setLength($value)
{
$this->block->setDbLength($value);
$this->block->save($this->con);
$this->updateBlockLengthInAllPlaylist();
}
2012-07-30 23:31:54 +02:00
/**
2021-10-11 16:10:47 +02:00
* Saves smart block criteria.
*
2012-07-30 23:31:54 +02:00
* @param array $p_criteria
*/
public function saveSmartBlockCriteria($p_criteria)
{
$data = $this->organizeSmartPlaylistCriteria($p_criteria);
2012-07-30 23:31:54 +02:00
// saving dynamic/static flag
2021-10-11 16:10:47 +02:00
$blockType = $data['etc']['sp_type'] == 0 ? 'dynamic' : 'static';
2012-07-30 23:31:54 +02:00
$this->saveType($blockType);
$this->storeCriteriaIntoDb($data);
// if the block is dynamic, put null to the length
// as it cannot be calculated
if ($blockType == 'dynamic') {
if ($this->hasItemLimit()) {
$this->setLength(null);
2012-07-30 23:31:54 +02:00
} else {
$this->setLength($this->getDynamicBlockLength());
2012-07-30 23:31:54 +02:00
}
} else {
$length = $this->getStaticLength();
if (!$length) {
2021-10-11 16:10:47 +02:00
$length = '00:00:00';
2012-07-30 23:31:54 +02:00
}
$this->setLength($length);
2012-07-30 23:31:54 +02:00
}
$this->updateBlockLengthInAllPlaylist();
2012-07-30 23:31:54 +02:00
}
public function hasItemLimit()
{
[$value, $modifier] = $this->getLimitValueAndModifier();
2021-10-11 16:10:47 +02:00
return $modifier == 'items';
}
public function storeCriteriaIntoDb($p_criteriaData)
{
2012-07-30 23:31:54 +02:00
// delete criteria under $p_blockId
CcBlockcriteriaQuery::create()->findByDbBlockId($this->id)->delete();
// Logging::info($p_criteriaData);
// insert modifier rows
if (isset($p_criteriaData['criteria'])) {
$critKeys = array_keys($p_criteriaData['criteria']);
2021-10-11 16:10:47 +02:00
for ($i = 0; $i < count($critKeys); ++$i) {
// in order to maintain separation of different criteria to preserve AND statements for criteria
// that might contradict itself we group them based upon their original position on the form
$criteriaGroup = $i;
foreach ($p_criteriaData['criteria'][$critKeys[$i]] as $d) {
2021-10-11 16:10:47 +02:00
$field = $d['sp_criteria_field'];
$value = $d['sp_criteria_value'];
$modifier = $d['sp_criteria_modifier'];
if (isset($d['sp_criteria_extra'])) {
$extra = $d['sp_criteria_extra'];
}
if (isset($d['sp_criteria_datetime_select'])) {
$datetimeunit = $d['sp_criteria_datetime_select'];
}
if (isset($d['sp_criteria_extra_datetime_select'])) {
$extradatetimeunit = $d['sp_criteria_extra_datetime_select'];
}
2021-10-11 16:10:47 +02:00
if ($field == 'utime' || $field == 'mtime' || $field == 'lptime') {
// if the date isn't relative we want to convert the value to a specific UTC date
2022-07-11 17:07:30 +02:00
if (!in_array($modifier, ['before', 'after', 'between'])) {
2021-10-11 16:10:47 +02:00
$value = Application_Common_DateHelper::UserTimezoneStringToUTCString($value);
} else {
$value = $value . ' ' . $datetimeunit . ' ago';
// Logging::info($value);
}
2021-10-11 16:10:47 +02:00
}
$qry = new CcBlockcriteria();
$qry->setDbCriteria($field)
2021-10-11 16:10:47 +02:00
->setDbModifier($d['sp_criteria_modifier'])
->setDbValue($value)
->setDbBlockId($this->id);
if (isset($d['sp_criteria_extra'])) {
2021-10-11 16:10:47 +02:00
if ($field == 'utime' || $field == 'mtime' || $field == 'lptime') {
// if the date isn't relative we want to convert the value to a specific UTC date
2022-07-11 17:07:30 +02:00
if (!in_array($modifier, ['before', 'after', 'between'])) {
$extra = Application_Common_DateHelper::UserTimezoneStringToUTCString($extra);
2021-10-11 16:10:47 +02:00
} else {
$extra = $extra . ' ' . $extradatetimeunit . ' ago';
}
2021-10-11 16:10:47 +02:00
}
$qry->setDbExtra($extra);
}
// save the criteria group so separation via new modifiers AND can be preserved vs. lumping
// them all into a single or later on
if (isset($criteriaGroup)) {
$qry->setDbCriteriaGroup($criteriaGroup);
}
$qry->save();
}
}
2012-07-30 23:31:54 +02:00
}
// insert sort info
$qry = new CcBlockcriteria();
2021-10-11 16:10:47 +02:00
$qry->setDbCriteria('sort')
->setDbModifier('N/A')
->setDbValue($p_criteriaData['etc']['sp_sort_options'])
->setDbBlockId($this->id)
->save();
2012-07-30 23:31:54 +02:00
// insert limit info
$qry = new CcBlockcriteria();
2021-10-11 16:10:47 +02:00
$qry->setDbCriteria('limit')
->setDbModifier($p_criteriaData['etc']['sp_limit_options'])
->setDbValue($p_criteriaData['etc']['sp_limit_value'])
->setDbBlockId($this->id)
->save();
// insert repeat track option
$qry = new CcBlockcriteria();
2021-10-11 16:10:47 +02:00
$qry->setDbCriteria('repeat_tracks')
->setDbModifier('N/A')
->setDbValue($p_criteriaData['etc']['sp_repeat_tracks'])
->setDbBlockId($this->id)
->save();
// insert overflow track option
$qry = new CcBlockcriteria();
2021-10-11 16:10:47 +02:00
$qry->setDbCriteria('overflow_tracks')
->setDbModifier('N/A')
->setDbValue($p_criteriaData['etc']['sp_overflow_tracks'])
->setDbBlockId($this->id)
->save();
2012-07-30 23:31:54 +02:00
}
2012-07-30 23:31:54 +02:00
/**
* generate list of tracks. This function saves criteria and generate
2012-07-30 23:31:54 +02:00
* tracks.
2021-10-11 16:10:47 +02:00
*
2012-07-30 23:31:54 +02:00
* @param array $p_criteria
2021-10-11 16:10:47 +02:00
* @param mixed $returnList
2012-07-30 23:31:54 +02:00
*/
2021-10-11 16:10:47 +02:00
public function generateSmartBlock($p_criteria, $returnList = false)
2012-07-30 23:31:54 +02:00
{
2012-08-16 21:40:36 +02:00
$this->saveSmartBlockCriteria($p_criteria);
$insertList = $this->getListOfFilesUnderLimit();
$this->deleteAllFilesFromBlock();
// construct id array
2021-10-11 16:10:47 +02:00
$ids = [];
foreach ($insertList as $ele) {
$ids[] = $ele['id'];
}
$this->addAudioClips(array_values($ids));
2012-08-16 21:40:36 +02:00
// update length in playlist contents.
$this->updateBlockLengthInAllPlaylist();
2021-10-11 16:10:47 +02:00
return ['result' => 0];
2012-07-30 23:31:54 +02:00
}
public function updateBlockLengthInAllPlaylist()
{
$blocks = CcPlaylistcontentsQuery::create()->filterByDbBlockId($this->id)->find();
$blocks->getFirst();
$iterator = $blocks->getIterator();
while ($iterator->valid()) {
$length = $this->getUnformatedLength();
2021-10-11 16:10:47 +02:00
if (!preg_match('/^[0-9]{2}:[0-9]{2}:[0-9]{2}/', $length)) {
$iterator->current()->setDbClipLength(null);
} else {
$iterator->current()->setDbClipLength($length);
}
$iterator->current()->save();
$iterator->next();
}
}
public function getListOfFilesUnderLimit($show = null)
2012-07-30 23:31:54 +02:00
{
2021-10-11 16:10:47 +02:00
$info = $this->getListofFilesMeetCriteria($show);
$files = $info['files'];
$limit = $info['limit'];
feat(legacy): implement subset sum solution to show scheduling (#3019) ### Description When running a radio station it is generally a good idea to reduce dead air time. The current algorithm for adding tracks to a block/show can leave a lot of dead air time at the end as it doesn't use a very good algorithm. Adding tracks to a show until it is full while making it as full as possible is a well known problem in computer science. It is the [Subset Sum Problem](https://en.wikipedia.org/wiki/Subset_sum_problem). This PR implements a Randomized Greedy with Local Improvement (RGLI) approximation solution for the Subset Sum Problem. The new algorithm is only used when sort type is random and overflow is not enabled and there is no limit on the number of tracks that can be used. **This is a new feature**: Improvement on an existing feature. **I have not updated the documentation to reflect these changes**: I did not update the documentation because the current scheduling algorithm is not currently documented and its existing features have not changed. ### Testing Notes **What I did:** I first attempted a fully polynomial time approximation scheme solution, however it is really bad at finding good solutions for high density values and can kinda slow the more tracks/time you have. So I instead implemented an RGLI which is O(nlogn) and has been giving much better results. I implemented the solution in a separate project and tested it and timed the values with a normal distribution of 500 songs with a mean of 3 minutes and a standard deviation of 1 minute. With a show size of 1 hour the algorithm took around 10-15 ms to run. When adjusting the block size and track size the algorithm still was pretty quick to run. Am going to be testing on an instance with lots of tracks later, will update PR when I have done that. **How you can replicate my testing:** _How can the reviewer validate this PR?_ ### **Links** Closes #3018
2024-10-13 15:31:08 +02:00
$repeat = $info['repeat_tracks'] == 1;
$overflow = $info['overflow_tracks'] == 1;
$isRandomSort = $info['sort_type'] == 'random';
$blockTime = $limit['time'];
$blockItems = $limit['items'];
if ($files->isEmpty()) {
feat(legacy): implement subset sum solution to show scheduling (#3019) ### Description When running a radio station it is generally a good idea to reduce dead air time. The current algorithm for adding tracks to a block/show can leave a lot of dead air time at the end as it doesn't use a very good algorithm. Adding tracks to a show until it is full while making it as full as possible is a well known problem in computer science. It is the [Subset Sum Problem](https://en.wikipedia.org/wiki/Subset_sum_problem). This PR implements a Randomized Greedy with Local Improvement (RGLI) approximation solution for the Subset Sum Problem. The new algorithm is only used when sort type is random and overflow is not enabled and there is no limit on the number of tracks that can be used. **This is a new feature**: Improvement on an existing feature. **I have not updated the documentation to reflect these changes**: I did not update the documentation because the current scheduling algorithm is not currently documented and its existing features have not changed. ### Testing Notes **What I did:** I first attempted a fully polynomial time approximation scheme solution, however it is really bad at finding good solutions for high density values and can kinda slow the more tracks/time you have. So I instead implemented an RGLI which is O(nlogn) and has been giving much better results. I implemented the solution in a separate project and tested it and timed the values with a normal distribution of 500 songs with a mean of 3 minutes and a standard deviation of 1 minute. With a show size of 1 hour the algorithm took around 10-15 ms to run. When adjusting the block size and track size the algorithm still was pretty quick to run. Am going to be testing on an instance with lots of tracks later, will update PR when I have done that. **How you can replicate my testing:** _How can the reviewer validate this PR?_ ### **Links** Closes #3018
2024-10-13 15:31:08 +02:00
return [];
}
2012-07-30 23:31:54 +02:00
// this moves the pointer to the first element in the collection
$files->getFirst();
$iterator = $files->getIterator();
feat(legacy): implement subset sum solution to show scheduling (#3019) ### Description When running a radio station it is generally a good idea to reduce dead air time. The current algorithm for adding tracks to a block/show can leave a lot of dead air time at the end as it doesn't use a very good algorithm. Adding tracks to a show until it is full while making it as full as possible is a well known problem in computer science. It is the [Subset Sum Problem](https://en.wikipedia.org/wiki/Subset_sum_problem). This PR implements a Randomized Greedy with Local Improvement (RGLI) approximation solution for the Subset Sum Problem. The new algorithm is only used when sort type is random and overflow is not enabled and there is no limit on the number of tracks that can be used. **This is a new feature**: Improvement on an existing feature. **I have not updated the documentation to reflect these changes**: I did not update the documentation because the current scheduling algorithm is not currently documented and its existing features have not changed. ### Testing Notes **What I did:** I first attempted a fully polynomial time approximation scheme solution, however it is really bad at finding good solutions for high density values and can kinda slow the more tracks/time you have. So I instead implemented an RGLI which is O(nlogn) and has been giving much better results. I implemented the solution in a separate project and tested it and timed the values with a normal distribution of 500 songs with a mean of 3 minutes and a standard deviation of 1 minute. With a show size of 1 hour the algorithm took around 10-15 ms to run. When adjusting the block size and track size the algorithm still was pretty quick to run. Am going to be testing on an instance with lots of tracks later, will update PR when I have done that. **How you can replicate my testing:** _How can the reviewer validate this PR?_ ### **Links** Closes #3018
2024-10-13 15:31:08 +02:00
/**
* @var Track[] $tracks
*/
$tracks = [];
$maxItems = 500;
while ($iterator->valid() && count($tracks) < $maxItems) {
2012-07-30 23:31:54 +02:00
$id = $iterator->current()->getDbId();
$fileLength = $iterator->current()->getCueLength();
$length = Application_Common_DateHelper::calculateLengthInSeconds($fileLength);
2021-10-11 16:10:47 +02:00
feat(legacy): implement subset sum solution to show scheduling (#3019) ### Description When running a radio station it is generally a good idea to reduce dead air time. The current algorithm for adding tracks to a block/show can leave a lot of dead air time at the end as it doesn't use a very good algorithm. Adding tracks to a show until it is full while making it as full as possible is a well known problem in computer science. It is the [Subset Sum Problem](https://en.wikipedia.org/wiki/Subset_sum_problem). This PR implements a Randomized Greedy with Local Improvement (RGLI) approximation solution for the Subset Sum Problem. The new algorithm is only used when sort type is random and overflow is not enabled and there is no limit on the number of tracks that can be used. **This is a new feature**: Improvement on an existing feature. **I have not updated the documentation to reflect these changes**: I did not update the documentation because the current scheduling algorithm is not currently documented and its existing features have not changed. ### Testing Notes **What I did:** I first attempted a fully polynomial time approximation scheme solution, however it is really bad at finding good solutions for high density values and can kinda slow the more tracks/time you have. So I instead implemented an RGLI which is O(nlogn) and has been giving much better results. I implemented the solution in a separate project and tested it and timed the values with a normal distribution of 500 songs with a mean of 3 minutes and a standard deviation of 1 minute. With a show size of 1 hour the algorithm took around 10-15 ms to run. When adjusting the block size and track size the algorithm still was pretty quick to run. Am going to be testing on an instance with lots of tracks later, will update PR when I have done that. **How you can replicate my testing:** _How can the reviewer validate this PR?_ ### **Links** Closes #3018
2024-10-13 15:31:08 +02:00
$tracks[] = new Track($id, $length);
2012-07-30 23:31:54 +02:00
$iterator->next();
}
feat(legacy): implement subset sum solution to show scheduling (#3019) ### Description When running a radio station it is generally a good idea to reduce dead air time. The current algorithm for adding tracks to a block/show can leave a lot of dead air time at the end as it doesn't use a very good algorithm. Adding tracks to a show until it is full while making it as full as possible is a well known problem in computer science. It is the [Subset Sum Problem](https://en.wikipedia.org/wiki/Subset_sum_problem). This PR implements a Randomized Greedy with Local Improvement (RGLI) approximation solution for the Subset Sum Problem. The new algorithm is only used when sort type is random and overflow is not enabled and there is no limit on the number of tracks that can be used. **This is a new feature**: Improvement on an existing feature. **I have not updated the documentation to reflect these changes**: I did not update the documentation because the current scheduling algorithm is not currently documented and its existing features have not changed. ### Testing Notes **What I did:** I first attempted a fully polynomial time approximation scheme solution, however it is really bad at finding good solutions for high density values and can kinda slow the more tracks/time you have. So I instead implemented an RGLI which is O(nlogn) and has been giving much better results. I implemented the solution in a separate project and tested it and timed the values with a normal distribution of 500 songs with a mean of 3 minutes and a standard deviation of 1 minute. With a show size of 1 hour the algorithm took around 10-15 ms to run. When adjusting the block size and track size the algorithm still was pretty quick to run. Am going to be testing on an instance with lots of tracks later, will update PR when I have done that. **How you can replicate my testing:** _How can the reviewer validate this PR?_ ### **Links** Closes #3018
2024-10-13 15:31:08 +02:00
/**
* @var Track[] $insertList
*/
$insertList = [];
$totalTime = 0;
if ($isRandomSort && !$overflow && $blockItems === null) {
$minTrackLength = min(array_map(fn (Track $item) => $item->length, $tracks));
do {
$solution = SSPSolution::solve($tracks, $blockTime - $totalTime);
$insertList = array_merge($insertList, $solution->tracks);
$totalTime += $solution->sum;
} while ($repeat && ($blockTime - $totalTime) > $minTrackLength);
shuffle($insertList);
} else {
$isFull = function () use (&$blockItems, &$insertList, &$totalTime, &$blockTime) {
return $blockItems !== null && count($insertList) >= $blockItems || $totalTime > $blockTime;
};
$addTrack = function (Track $track) use ($overflow, $blockTime, &$insertList, &$totalTime) {
if ($overflow) {
$insertList[] = $track;
$totalTime += $track->length;
2021-10-11 16:10:47 +02:00
} else {
feat(legacy): implement subset sum solution to show scheduling (#3019) ### Description When running a radio station it is generally a good idea to reduce dead air time. The current algorithm for adding tracks to a block/show can leave a lot of dead air time at the end as it doesn't use a very good algorithm. Adding tracks to a show until it is full while making it as full as possible is a well known problem in computer science. It is the [Subset Sum Problem](https://en.wikipedia.org/wiki/Subset_sum_problem). This PR implements a Randomized Greedy with Local Improvement (RGLI) approximation solution for the Subset Sum Problem. The new algorithm is only used when sort type is random and overflow is not enabled and there is no limit on the number of tracks that can be used. **This is a new feature**: Improvement on an existing feature. **I have not updated the documentation to reflect these changes**: I did not update the documentation because the current scheduling algorithm is not currently documented and its existing features have not changed. ### Testing Notes **What I did:** I first attempted a fully polynomial time approximation scheme solution, however it is really bad at finding good solutions for high density values and can kinda slow the more tracks/time you have. So I instead implemented an RGLI which is O(nlogn) and has been giving much better results. I implemented the solution in a separate project and tested it and timed the values with a normal distribution of 500 songs with a mean of 3 minutes and a standard deviation of 1 minute. With a show size of 1 hour the algorithm took around 10-15 ms to run. When adjusting the block size and track size the algorithm still was pretty quick to run. Am going to be testing on an instance with lots of tracks later, will update PR when I have done that. **How you can replicate my testing:** _How can the reviewer validate this PR?_ ### **Links** Closes #3018
2024-10-13 15:31:08 +02:00
$projectedTime = $totalTime + $track->length;
if ($projectedTime <= $blockTime) {
$insertList[] = $track;
$totalTime += $track->length;
}
}
};
foreach ($tracks as $track) {
$addTrack($track);
if ($isFull()) {
break;
}
}
feat(legacy): implement subset sum solution to show scheduling (#3019) ### Description When running a radio station it is generally a good idea to reduce dead air time. The current algorithm for adding tracks to a block/show can leave a lot of dead air time at the end as it doesn't use a very good algorithm. Adding tracks to a show until it is full while making it as full as possible is a well known problem in computer science. It is the [Subset Sum Problem](https://en.wikipedia.org/wiki/Subset_sum_problem). This PR implements a Randomized Greedy with Local Improvement (RGLI) approximation solution for the Subset Sum Problem. The new algorithm is only used when sort type is random and overflow is not enabled and there is no limit on the number of tracks that can be used. **This is a new feature**: Improvement on an existing feature. **I have not updated the documentation to reflect these changes**: I did not update the documentation because the current scheduling algorithm is not currently documented and its existing features have not changed. ### Testing Notes **What I did:** I first attempted a fully polynomial time approximation scheme solution, however it is really bad at finding good solutions for high density values and can kinda slow the more tracks/time you have. So I instead implemented an RGLI which is O(nlogn) and has been giving much better results. I implemented the solution in a separate project and tested it and timed the values with a normal distribution of 500 songs with a mean of 3 minutes and a standard deviation of 1 minute. With a show size of 1 hour the algorithm took around 10-15 ms to run. When adjusting the block size and track size the algorithm still was pretty quick to run. Am going to be testing on an instance with lots of tracks later, will update PR when I have done that. **How you can replicate my testing:** _How can the reviewer validate this PR?_ ### **Links** Closes #3018
2024-10-13 15:31:08 +02:00
$sizeOfInsert = count($insertList);
while (!$isFull() && $repeat && $sizeOfInsert > 0) {
Logging::debug('adding repeated tracks.');
Logging::debug('total time = ' . $totalTime);
$track = $insertList[array_rand(array_slice($insertList, 0, $sizeOfInsert))];
$addTrack($track);
}
}
feat(legacy): implement subset sum solution to show scheduling (#3019) ### Description When running a radio station it is generally a good idea to reduce dead air time. The current algorithm for adding tracks to a block/show can leave a lot of dead air time at the end as it doesn't use a very good algorithm. Adding tracks to a show until it is full while making it as full as possible is a well known problem in computer science. It is the [Subset Sum Problem](https://en.wikipedia.org/wiki/Subset_sum_problem). This PR implements a Randomized Greedy with Local Improvement (RGLI) approximation solution for the Subset Sum Problem. The new algorithm is only used when sort type is random and overflow is not enabled and there is no limit on the number of tracks that can be used. **This is a new feature**: Improvement on an existing feature. **I have not updated the documentation to reflect these changes**: I did not update the documentation because the current scheduling algorithm is not currently documented and its existing features have not changed. ### Testing Notes **What I did:** I first attempted a fully polynomial time approximation scheme solution, however it is really bad at finding good solutions for high density values and can kinda slow the more tracks/time you have. So I instead implemented an RGLI which is O(nlogn) and has been giving much better results. I implemented the solution in a separate project and tested it and timed the values with a normal distribution of 500 songs with a mean of 3 minutes and a standard deviation of 1 minute. With a show size of 1 hour the algorithm took around 10-15 ms to run. When adjusting the block size and track size the algorithm still was pretty quick to run. Am going to be testing on an instance with lots of tracks later, will update PR when I have done that. **How you can replicate my testing:** _How can the reviewer validate this PR?_ ### **Links** Closes #3018
2024-10-13 15:31:08 +02:00
return array_map(fn (Track $track) => ['id' => $track->id, 'length' => $track->length], $insertList);
2012-07-30 23:31:54 +02:00
}
/**
* Parses each row in the database for the criteria associated with this block and renders human readable labels.
* Returns it as an array with each criteria_name and modifier_name added based upon options array lookup.
*/
public function getCriteria()
{
$allCriteria = BlockCriteria::criteriaMap();
$allOptions = CriteriaModifier::mapToDisplay();
// Load criteria from db
$out = CcBlockcriteriaQuery::create()->orderByDbCriteria()->findByDbBlockId($this->id);
2021-10-11 16:10:47 +02:00
$storedCrit = [];
foreach ($out as $crit) {
$criteria = $crit->getDbCriteria();
$modifier = $crit->getDbModifier();
$value = $crit->getDbValue();
$extra = $crit->getDbExtra();
$criteriagroup = $crit->getDbCriteriaGroup();
2021-10-11 16:10:47 +02:00
if ($criteria == 'limit') {
$storedCrit['limit'] = [
'value' => $value,
'modifier' => $modifier,
2022-07-07 20:01:15 +02:00
'display_modifier' => _($modifier),
];
2021-10-11 16:10:47 +02:00
} elseif ($criteria == 'repeat_tracks') {
$storedCrit['repeat_tracks'] = ['value' => $value];
} elseif ($criteria == 'overflow_tracks') {
$storedCrit['overflow_tracks'] = ['value' => $value];
} elseif ($criteria == 'sort') {
$storedCrit['sort'] = ['value' => $value];
} else {
$c = $allCriteria[$criteria];
2021-10-11 16:10:47 +02:00
$storedCrit['crit'][$criteria][] = [
'criteria' => $criteria,
'value' => $value,
'modifier' => $modifier,
'extra' => $extra,
'criteria_group' => $criteriagroup,
'display_name' => $c->display,
'display_modifier' => $allOptions[$modifier],
2022-07-07 20:01:15 +02:00
];
}
}
return $storedCrit;
}
2019-01-20 20:12:52 +01:00
/**
* Parses each row in the database for the criteria associated with this block and renders human readable labels.
* Returns it as an array with each criteria_name and modifier_name added based upon options array lookup.
2021-10-11 16:10:47 +02:00
* Maintains original separation of similar criteria that were separated by and statements.
2019-01-20 20:12:52 +01:00
*/
public function getCriteriaGrouped()
{
$criteriaOptions = BlockCriteria::displayCriteria();
$modifierOptions = CriteriaModifier::mapToDisplay();
2019-01-20 20:12:52 +01:00
// Load criteria from db
$out = CcBlockcriteriaQuery::create()->orderByDbCriteria()->findByDbBlockId($this->id);
2021-10-11 16:10:47 +02:00
$storedCrit = [];
2019-01-20 20:12:52 +01:00
foreach ($out as $crit) {
$criteria = $crit->getDbCriteria();
$modifier = $crit->getDbModifier();
$value = $crit->getDbValue();
$extra = $crit->getDbExtra();
$criteriagroup = $crit->getDbCriteriaGroup();
2021-10-11 16:10:47 +02:00
if ($criteria == 'limit') {
$storedCrit['limit'] = [
'value' => $value,
'modifier' => $modifier,
2022-07-07 20:01:15 +02:00
'display_modifier' => _($modifier),
];
2021-10-11 16:10:47 +02:00
} elseif ($criteria == 'repeat_tracks') {
$storedCrit['repeat_tracks'] = ['value' => $value];
} elseif ($criteria == 'overflow_tracks') {
$storedCrit['overflow_tracks'] = ['value' => $value];
} elseif ($criteria == 'sort') {
$storedCrit['sort'] = ['value' => $value];
2019-01-20 20:12:52 +01:00
} else {
2021-10-11 16:10:47 +02:00
$storedCrit['crit'][$criteria . $criteriagroup][] = [
'criteria' => $criteria,
'value' => $value,
'modifier' => $modifier,
'extra' => $extra,
'display_name' => $criteriaOptions[$criteria],
2022-07-07 20:01:15 +02:00
'display_modifier' => $modifierOptions[$modifier],
];
2019-01-20 20:12:52 +01:00
}
}
2021-10-11 16:10:47 +02:00
return $storedCrit;
2019-01-20 20:12:52 +01:00
}
private function resolveDate($value)
{
if (!is_string($value)) {
return $value;
}
return preg_replace_callback(
'/now{(.*?)}/',
fn ($matches) => date($matches[1]),
$value
);
}
2012-07-30 23:31:54 +02:00
// this function return list of propel object
public function getListofFilesMeetCriteria($showLimit = null)
2012-07-30 23:31:54 +02:00
{
$storedCrit = $this->getCriteria();
2012-07-30 23:31:54 +02:00
$qry = CcFilesQuery::create();
2021-10-11 16:10:47 +02:00
$qry->useFkOwnerQuery('subj', 'left join');
$allCriteria = BlockCriteria::criteriaMap();
// Logging::info($storedCrit);
2021-10-11 16:10:47 +02:00
if (isset($storedCrit['crit'])) {
foreach ($storedCrit['crit'] as $crit) {
$i = 0;
$prevgroup = null;
$group = null;
// now we need to sort based upon extra which contains the and grouping from the form
2021-10-11 16:10:47 +02:00
usort($crit, function ($a, $b) {
return $a['criteria_group'] - $b['criteria_group'];
});
// we need to run the following loop separately for each criteria group inside of each array
foreach ($crit as $criteria) {
$group = $criteria['criteria_group'];
$spCriteria = $criteria['criteria'];
$spCriteriaModifier = $criteria['modifier'];
$column = CcFilesPeer::getTableMap()->getColumnByPhpName($allCriteria[$spCriteria]->peer);
// data should already be in UTC, do we have to do anything special here anymore?
if ($column->getType() == PropelColumnTypes::TIMESTAMP) {
$spCriteriaValue = $criteria['value'];
if (isset($criteria['extra'])) {
$spCriteriaExtra = $criteria['extra'];
}
2021-10-11 16:10:47 +02:00
} elseif ($spCriteria == 'bit_rate' || $spCriteria == 'sample_rate') {
// multiply 1000 because we store only number value
// e.g 192kps is stored as 192000
$spCriteriaValue = $criteria['value'] * 1000;
if (isset($criteria['extra'])) {
$spCriteriaExtra = $criteria['extra'] * 1000;
}
/*
* If user is searching for an exact match of length we need to
* search as if it starts with the specified length because the
* user only sees the rounded version (i.e. 4:02.7 is 4:02.761625
* in the database)
*/
2021-10-11 16:10:47 +02:00
} elseif (in_array($spCriteria, ['length', 'cuein', 'cueout']) && $spCriteriaModifier == 'is') {
$spCriteriaModifier = 'starts with';
$spCriteria .= '::text';
$spCriteriaValue = $criteria['value'];
} else {
/* Propel does not escape special characters properly when using LIKE/ILIKE
* We have to add extra slashes in these cases
*/
$tempModifier = trim(self::$modifier2CriteriaMap[$spCriteriaModifier]);
if ($tempModifier == 'ILIKE') {
$spCriteriaValue = addslashes($criteria['value']);
// addslashes() does not esapce '%' so we have to do it manually
$spCriteriaValue = str_replace('%', '\%', $spCriteriaValue);
} else {
2022-07-11 17:07:30 +02:00
$spCriteriaValue = $criteria['value'];
}
$spCriteriaExtra = $criteria['extra'];
}
$spCriteriaValue = $this->resolveDate($spCriteriaValue);
if ($spCriteriaModifier == CriteriaModifier::STARTS_WITH) {
2021-10-11 16:10:47 +02:00
$spCriteriaValue = "{$spCriteriaValue}%";
} elseif ($spCriteriaModifier == CriteriaModifier::ENDS_WITH) {
2021-10-11 16:10:47 +02:00
$spCriteriaValue = "%{$spCriteriaValue}";
} elseif ($spCriteriaModifier == CriteriaModifier::CONTAINS || $spCriteriaModifier == CriteriaModifier::DOES_NOT_CONTAIN) {
2021-10-11 16:10:47 +02:00
$spCriteriaValue = "%{$spCriteriaValue}%";
} elseif ($spCriteriaModifier == CriteriaModifier::IS_IN_THE_RANGE) {
2021-10-11 16:10:47 +02:00
$spCriteriaValue = "{$spCriteria} >= '{$spCriteriaValue}' AND {$spCriteria} <= '{$spCriteriaExtra}'";
} elseif ($spCriteriaModifier == CriteriaModifier::BEFORE) {
// need to pull in the current time and subtract the value or figure out how to make it relative
$relativedate = new DateTime($spCriteriaValue);
$dt = $relativedate->format(DateTime::ISO8601);
$spCriteriaValue = "COALESCE({$spCriteria}, DATE '-infinity') <= '{$dt}'";
} elseif ($spCriteriaModifier == CriteriaModifier::AFTER) {
$relativedate = new DateTime($spCriteriaValue);
$dt = $relativedate->format(DateTime::ISO8601);
$spCriteriaValue = "COALESCE({$spCriteria}, DATE '-infinity') >= '{$dt}'";
} elseif ($spCriteriaModifier == CriteriaModifier::BETWEEN) {
$fromrelativedate = new DateTime($spCriteriaValue);
$fdt = $fromrelativedate->format(DateTime::ISO8601);
$torelativedate = new DateTime($spCriteriaExtra);
$tdt = $torelativedate->format(DateTime::ISO8601);
$spCriteriaValue = "COALESCE({$spCriteria}, DATE '-infinity') >= '{$fdt}' AND COALESCE({$spCriteria}, DATE '-infinity') <= '{$tdt}'";
}
$spCriteriaModifier = self::$modifier2CriteriaMap[$spCriteriaModifier];
2012-08-24 03:43:55 +02:00
try {
2021-10-11 16:10:47 +02:00
if ($spCriteria == 'owner_id') {
$spCriteria = 'subj.login';
}
if ($spCriteria == 'filepath') {
$spCriteria = "reverse(split_part(reverse(filepath), '/', 1))";
}
if ($i > 0 && $prevgroup == $group) {
$qry->addOr($spCriteria, $spCriteriaValue, $spCriteriaModifier);
} else {
$qry->addAnd($spCriteria, $spCriteriaValue, $spCriteriaModifier);
}
// only add this NOT LIKE null if you aren't also matching on another criteria
if ($i == 0) {
2021-10-11 16:10:47 +02:00
if ($spCriteriaModifier == Criteria::NOT_ILIKE || $spCriteriaModifier == Criteria::NOT_EQUAL) {
$qry->addOr($spCriteria, null, Criteria::ISNULL);
}
}
2012-08-24 03:43:55 +02:00
} catch (Exception $e) {
2012-08-22 00:41:56 +02:00
Logging::info($e);
}
$prevgroup = $group;
2021-10-11 16:10:47 +02:00
++$i;
2012-07-30 23:31:54 +02:00
}
}
2015-09-02 22:25:30 +02:00
}
2015-09-02 22:25:30 +02:00
// check if file exists
2021-10-11 16:10:47 +02:00
$qry->add('file_exists', 'true', Criteria::EQUAL);
$qry->add('hidden', 'false', Criteria::EQUAL);
2015-09-02 22:25:30 +02:00
$sortTracks = 'random';
if (isset($storedCrit['sort'])) {
$sortTracks = $storedCrit['sort']['value'];
}
if ($sortTracks == 'newest') {
$qry->addDescendingOrderByColumn('utime');
2021-10-11 16:10:47 +02:00
} elseif ($sortTracks == 'oldest') {
2015-09-02 22:25:30 +02:00
$qry->addAscendingOrderByColumn('utime');
} elseif ($sortTracks == 'mostrecentplay') {
$qry->addAscendingOrderByColumn('lptime DESC NULLS LAST, filepath');
2021-10-11 16:10:47 +02:00
} elseif ($sortTracks == 'leastrecentplay') {
$qry->addAscendingOrderByColumn('lptime ASC NULLS FIRST, filepath');
2021-10-11 16:10:47 +02:00
} elseif ($sortTracks == 'random') {
2015-09-02 22:25:30 +02:00
$qry->addAscendingOrderByColumn('random()');
} else {
2021-10-11 16:10:47 +02:00
Logging::warning('Unimplemented sortTracks type in ' . __FILE__);
2015-09-02 22:25:30 +02:00
}
2012-07-30 23:31:54 +02:00
// construct limit restriction
2021-10-11 16:10:47 +02:00
$limits = [];
2012-07-30 23:31:54 +02:00
if (isset($storedCrit['limit'])) {
2021-10-11 16:10:47 +02:00
if ($storedCrit['limit']['modifier'] == 'items') {
2012-07-30 23:31:54 +02:00
$limits['time'] = 1440 * 60;
$limits['items'] = $storedCrit['limit']['value'];
2022-07-11 17:07:30 +02:00
} elseif ($storedCrit['limit']['modifier'] == 'remaining') {
// show will be null unless being called inside a show instance
2022-07-11 17:07:30 +02:00
if (!is_null($showLimit)) {
$limits['time'] = $showLimit;
$limits['items'] = null;
2021-10-11 16:10:47 +02:00
} else {
2018-12-12 22:36:49 +01:00
$limits['time'] = 60 * 60;
$limits['items'] = null;
}
2012-07-30 23:31:54 +02:00
} else {
2021-10-11 16:10:47 +02:00
$limits['time'] = $storedCrit['limit']['modifier'] == 'hours' ?
intval(floatval($storedCrit['limit']['value']) * 60 * 60) :
intval($storedCrit['limit']['value'] * 60);
2012-07-30 23:31:54 +02:00
$limits['items'] = null;
}
}
$repeatTracks = 0;
$overflowTracks = 0;
if (isset($storedCrit['repeat_tracks'])) {
$repeatTracks = $storedCrit['repeat_tracks']['value'];
}
if (isset($storedCrit['overflow_tracks'])) {
$overflowTracks = $storedCrit['overflow_tracks']['value'];
}
2012-08-24 03:43:55 +02:00
try {
2012-07-30 23:31:54 +02:00
$out = $qry->setFormatter(ModelCriteria::FORMAT_ON_DEMAND)->find();
2021-10-11 16:10:47 +02:00
feat(legacy): implement subset sum solution to show scheduling (#3019) ### Description When running a radio station it is generally a good idea to reduce dead air time. The current algorithm for adding tracks to a block/show can leave a lot of dead air time at the end as it doesn't use a very good algorithm. Adding tracks to a show until it is full while making it as full as possible is a well known problem in computer science. It is the [Subset Sum Problem](https://en.wikipedia.org/wiki/Subset_sum_problem). This PR implements a Randomized Greedy with Local Improvement (RGLI) approximation solution for the Subset Sum Problem. The new algorithm is only used when sort type is random and overflow is not enabled and there is no limit on the number of tracks that can be used. **This is a new feature**: Improvement on an existing feature. **I have not updated the documentation to reflect these changes**: I did not update the documentation because the current scheduling algorithm is not currently documented and its existing features have not changed. ### Testing Notes **What I did:** I first attempted a fully polynomial time approximation scheme solution, however it is really bad at finding good solutions for high density values and can kinda slow the more tracks/time you have. So I instead implemented an RGLI which is O(nlogn) and has been giving much better results. I implemented the solution in a separate project and tested it and timed the values with a normal distribution of 500 songs with a mean of 3 minutes and a standard deviation of 1 minute. With a show size of 1 hour the algorithm took around 10-15 ms to run. When adjusting the block size and track size the algorithm still was pretty quick to run. Am going to be testing on an instance with lots of tracks later, will update PR when I have done that. **How you can replicate my testing:** _How can the reviewer validate this PR?_ ### **Links** Closes #3018
2024-10-13 15:31:08 +02:00
return ['files' => $out, 'limit' => $limits, 'repeat_tracks' => $repeatTracks, 'overflow_tracks' => $overflowTracks, 'count' => $out->count(), 'sort_type' => $sortTracks];
2012-08-24 03:43:55 +02:00
} catch (Exception $e) {
2012-08-22 00:41:56 +02:00
Logging::info($e);
2012-07-30 23:31:54 +02:00
}
}
2021-10-11 16:10:47 +02:00
public static function organizeSmartPlaylistCriteria($p_criteria)
{
2021-10-11 16:10:47 +02:00
$fieldNames = ['sp_criteria_field', 'sp_criteria_modifier', 'sp_criteria_value', 'sp_criteria_extra', 'sp_criteria_datetime_select', 'sp_criteria_extra_datetime_select'];
$output = [];
foreach ($p_criteria as $ele) {
$index = strrpos($ele['name'], '_');
/* Strip field name of modifier index
* Ex: sp_criteria_field_0_0 -> sp_criteria_field_0
*/
$fieldName = substr($ele['name'], 0, $index);
// Get criteria row index.
$tempName = $ele['name'];
// Get the last digit in the field name
preg_match('/^\D*(?=\d)/', $tempName, $r);
if (isset($r[0])) {
$critIndexPos = strlen($r[0]);
$critIndex = $tempName[$critIndexPos];
}
$lastChar = substr($ele['name'], -1);
// If lastChar is an integer we should strip it off
2021-10-11 16:10:47 +02:00
if (!preg_match('/^[a-zA-Z]$/', $lastChar)) {
/* Strip field name of criteria index
* Ex: sp_criteria_field_0 -> sp_criteria_field
* We do this to check if the field name is a criteria
* or the block type
*/
$n = strrpos($fieldName, '_');
$fieldName = substr($fieldName, 0, $n);
}
if (in_array($fieldName, $fieldNames)) {
$output['criteria'][$critIndex][$lastChar][$fieldName] = trim($ele['value']);
} else {
$output['etc'][$ele['name']] = $ele['value'];
2012-07-30 23:31:54 +02:00
}
}
2021-10-11 16:10:47 +02:00
2012-07-30 23:31:54 +02:00
return $output;
}
2021-10-11 16:10:47 +02:00
public static function getAllBlockFiles()
{
2021-10-11 16:10:47 +02:00
$sql = <<<'SQL'
SELECT distinct(file_id)
FROM cc_blockcontents
SQL;
2021-10-11 16:10:47 +02:00
$files = Application_Common_Database::prepareAndExecute($sql, []);
2021-10-11 16:10:47 +02:00
$real_files = [];
foreach ($files as $f) {
$real_files[] = $f['file_id'];
}
2021-10-11 16:10:47 +02:00
return $real_files;
}
// smart block functions end
}
feat(legacy): implement subset sum solution to show scheduling (#3019) ### Description When running a radio station it is generally a good idea to reduce dead air time. The current algorithm for adding tracks to a block/show can leave a lot of dead air time at the end as it doesn't use a very good algorithm. Adding tracks to a show until it is full while making it as full as possible is a well known problem in computer science. It is the [Subset Sum Problem](https://en.wikipedia.org/wiki/Subset_sum_problem). This PR implements a Randomized Greedy with Local Improvement (RGLI) approximation solution for the Subset Sum Problem. The new algorithm is only used when sort type is random and overflow is not enabled and there is no limit on the number of tracks that can be used. **This is a new feature**: Improvement on an existing feature. **I have not updated the documentation to reflect these changes**: I did not update the documentation because the current scheduling algorithm is not currently documented and its existing features have not changed. ### Testing Notes **What I did:** I first attempted a fully polynomial time approximation scheme solution, however it is really bad at finding good solutions for high density values and can kinda slow the more tracks/time you have. So I instead implemented an RGLI which is O(nlogn) and has been giving much better results. I implemented the solution in a separate project and tested it and timed the values with a normal distribution of 500 songs with a mean of 3 minutes and a standard deviation of 1 minute. With a show size of 1 hour the algorithm took around 10-15 ms to run. When adjusting the block size and track size the algorithm still was pretty quick to run. Am going to be testing on an instance with lots of tracks later, will update PR when I have done that. **How you can replicate my testing:** _How can the reviewer validate this PR?_ ### **Links** Closes #3018
2024-10-13 15:31:08 +02:00
class Track
{
public int $id;
public float $length;
public function __construct($id, $length)
{
$this->id = $id;
$this->length = $length;
}
public function __toString(): string
{
return (string) $this->id;
}
}
/**
* Using a randomized greedy with local improvement approximation solution for the Subset Sum Problem.
*
* https://web.stevens.edu/algebraic/Files/SubsetSum/przydatek99fast.pdf
*/
class SSPSolution
{
/**
* @var Track[]
*/
public array $tracks;
public float $sum = 0.0;
public function __construct($tracks = [], $sum = null)
{
$this->tracks = $tracks;
if ($sum !== null) {
$this->sum = $sum;
} else {
foreach ($this->tracks as $track) {
$this->sum += $track->length;
}
}
}
public function add(Track $track): SSPSolution
{
$new = $this->tracks;
$new[] = $track;
return new SSPSolution($new, $this->sum + $track->length);
}
public function replace(Track $old, Track $new): SSPSolution
{
return new SSPSolution(array_map(fn (Track $it) => $it === $old ? $new : $it, $this->tracks));
}
public static function isCloseEnough(float $delta): bool
{
return $delta < 0.25;
}
public static function maxByOrNull(array $array, callable $callback)
{
$max = null;
$v = null;
foreach ($array as $item) {
$value = $callback($item);
if ($max === null || $v < $value) {
$max = $item;
$v = $value;
}
}
return $max;
}
/**
* @param Track[] $tracks
*/
public static function solve(array $tracks, float $target, int $trials = 50): SSPSolution
{
$best = new SSPSolution();
for ($trial = 0; $trial < $trials; ++$trial) {
shuffle($tracks);
$initial = array_reduce($tracks, function (SSPSolution $solution, Track $track) use ($target) {
$new = $solution->add($track);
if ($new->sum <= $target) {
return $new;
}
return $solution;
}, new SSPSolution());
if (count($initial->tracks) == count($tracks)) {
return $initial;
}
$acceptedItems = $initial->tracks;
shuffle($acceptedItems);
$localImprovement = array_reduce($acceptedItems, function (SSPSolution $solution, Track $track) use ($target, $tracks) {
$delta = $target - $solution->sum;
if (self::isCloseEnough($delta)) {
return $solution;
}
$replacement = self::maxByOrNull(
array_filter(
array_diff($tracks, $solution->tracks),
fn (Track $it) => $it->length > $track->length && $it->length - $track->length <= $delta,
),
fn (Track $it) => $it->length,
);
if ($replacement === null) {
return $solution;
}
return $solution->replace($track, $replacement);
}, $initial);
if ($localImprovement->sum > $best->sum) {
$best = $localImprovement;
}
if ($best->sum === 0.0) {
return $best;
}
}
return $best;
}
}
class BlockNotFoundException extends Exception {}
class BlockNoPermissionException extends Exception {}
class BlockOutDatedException extends Exception {}
class BlockDyanmicException extends Exception {}