Playlist.php 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161
  1. <?php
  2. require_once 'formatters/LengthFormatter.php';
  3. /**
  4. *
  5. * @package Airtime
  6. * @copyright 2010 Sourcefabric O.P.S.
  7. * @license http://www.gnu.org/licenses/gpl.txt
  8. */
  9. class Application_Model_Playlist implements Application_Model_LibraryEditable
  10. {
  11. const CUE_ALL_ERROR = 0;
  12. const CUE_IN_ERROR = 1;
  13. const CUE_OUT_ERROR = 2;
  14. /**
  15. * propel connection object.
  16. */
  17. private $con;
  18. /**
  19. * unique id for the playlist.
  20. */
  21. private $id;
  22. /**
  23. * propel object for this playlist.
  24. */
  25. private $pl;
  26. /**
  27. * info needed to insert a new playlist element.
  28. */
  29. private $plItem = array(
  30. "id" => "",
  31. "pos" => "",
  32. "cliplength" => "",
  33. "cuein" => "00:00:00",
  34. "cueout" => "00:00:00",
  35. "fadein" => "0.0",
  36. "fadeout" => "0.0",
  37. "crossfadeDuration" => 0
  38. );
  39. //using propel's phpNames.
  40. private $categories = array(
  41. "dc:title" => "Name",
  42. "dc:creator" => "Creator",
  43. "dc:description" => "Description",
  44. "dcterms:extent" => "Length"
  45. );
  46. public function __construct($id=null, $con=null)
  47. {
  48. if (isset($id)) {
  49. $this->pl = CcPlaylistQuery::create()->findPK($id);
  50. if (is_null($this->pl)) {
  51. throw new PlaylistNotFoundException();
  52. }
  53. } else {
  54. $this->pl = new CcPlaylist();
  55. $this->pl->setDbUTime(new DateTime("now", new DateTimeZone("UTC")));
  56. $this->pl->save();
  57. }
  58. $this->plItem["fadein"] = Application_Model_Preference::GetDefaultFadeIn();
  59. $this->plItem["fadeout"] = Application_Model_Preference::GetDefaultFadeOut();
  60. $this->plItem["crossfadeDuration"] = Application_Model_Preference::GetDefaultCrossfadeDuration();
  61. $this->con = isset($con) ? $con : Propel::getConnection(CcPlaylistPeer::DATABASE_NAME);
  62. $this->id = $this->pl->getDbId();
  63. }
  64. /**
  65. * Return local ID of virtual file.
  66. *
  67. * @return int
  68. */
  69. public function getId()
  70. {
  71. return $this->id;
  72. }
  73. /**
  74. * Rename stored virtual playlist
  75. *
  76. * @param string $p_newname
  77. */
  78. public function setName($p_newname)
  79. {
  80. $this->pl->setDbName($p_newname);
  81. $this->pl->setDbMtime(new DateTime("now", new DateTimeZone("UTC")));
  82. $this->pl->save($this->con);
  83. }
  84. /**
  85. * Get mnemonic playlist name
  86. *
  87. * @return string
  88. */
  89. public function getName()
  90. {
  91. return $this->pl->getDbName();
  92. }
  93. public function setDescription($p_description)
  94. {
  95. $this->pl->setDbDescription($p_description);
  96. $this->pl->setDbMtime(new DateTime("now", new DateTimeZone("UTC")));
  97. $this->pl->save($this->con);
  98. }
  99. public function getDescription()
  100. {
  101. return $this->pl->getDbDescription();
  102. }
  103. public function getCreator()
  104. {
  105. return $this->pl->getCcSubjs()->getDbLogin();
  106. }
  107. public function getCreatorId()
  108. {
  109. return $this->pl->getCcSubjs()->getDbId();
  110. }
  111. public function setCreator($p_id)
  112. {
  113. $this->pl->setDbCreatorId($p_id);
  114. $this->pl->setDbMtime(new DateTime("now", new DateTimeZone("UTC")));
  115. $this->pl->save($this->con);
  116. }
  117. public function getLastModified($format = null)
  118. {
  119. //Logging::info($this->pl->getDbMtime($format));
  120. //Logging::info($this->pl);
  121. return $this->pl->getDbMtime($format);
  122. }
  123. public function getSize()
  124. {
  125. return $this->pl->countCcPlaylistcontentss();
  126. }
  127. /**
  128. * Get the entire playlist as a two dimentional array, sorted in order of play.
  129. * @param boolean $filterFiles if this is true, it will only return files that has
  130. * file_exists flag set to true
  131. * @return array
  132. */
  133. public function getContents($filterFiles=false)
  134. {
  135. $sql = <<<SQL
  136. SELECT *
  137. FROM (
  138. (SELECT pc.id AS id,
  139. pc.type,
  140. pc.position,
  141. pc.cliplength AS LENGTH,
  142. pc.cuein,
  143. pc.cueout,
  144. pc.fadein,
  145. pc.fadeout,
  146. pc.trackoffset,
  147. f.id AS item_id,
  148. f.track_title,
  149. f.artist_name AS creator,
  150. f.file_exists AS EXISTS,
  151. f.filepath AS path,
  152. f.length AS orig_length,
  153. f.mime AS mime
  154. FROM cc_playlistcontents AS pc
  155. JOIN cc_files AS f ON pc.file_id=f.id
  156. WHERE pc.playlist_id = :playlist_id1
  157. SQL;
  158. if ($filterFiles) {
  159. $sql .= <<<SQL
  160. AND f.file_exists = :file_exists
  161. SQL;
  162. }
  163. $sql .= <<<SQL
  164. AND TYPE = 0)
  165. UNION ALL
  166. (SELECT pc.id AS id,
  167. pc.TYPE, pc.position,
  168. pc.cliplength AS LENGTH,
  169. pc.cuein,
  170. pc.cueout,
  171. pc.fadein,
  172. pc.fadeout,
  173. pc.trackoffset,
  174. ws.id AS item_id,
  175. (ws.name || ': ' || ws.url) AS title,
  176. sub.login AS creator,
  177. 't'::boolean AS EXISTS,
  178. ws.url AS path,
  179. ws.length AS orig_length,
  180. ws.mime as mime
  181. FROM cc_playlistcontents AS pc
  182. JOIN cc_webstream AS ws ON pc.stream_id=ws.id
  183. LEFT JOIN cc_subjs AS sub ON sub.id = ws.creator_id
  184. WHERE pc.playlist_id = :playlist_id2
  185. AND pc.TYPE = 1)
  186. UNION ALL
  187. (SELECT pc.id AS id,
  188. pc.TYPE, pc.position,
  189. pc.cliplength AS LENGTH,
  190. pc.cuein,
  191. pc.cueout,
  192. pc.fadein,
  193. pc.fadeout,
  194. pc.trackoffset,
  195. bl.id AS item_id,
  196. bl.name AS title,
  197. sbj.login AS creator,
  198. 't'::boolean AS EXISTS,
  199. NULL::text AS path,
  200. bl.length AS orig_length,
  201. NULL::text as mime
  202. FROM cc_playlistcontents AS pc
  203. JOIN cc_block AS bl ON pc.block_id=bl.id
  204. JOIN cc_subjs AS sbj ON bl.creator_id=sbj.id
  205. WHERE pc.playlist_id = :playlist_id3
  206. AND pc.TYPE = 2)) AS temp
  207. ORDER BY temp.position;
  208. SQL;
  209. //Logging::info($sql);
  210. $params = array(
  211. ':playlist_id1'=>$this->id, ':playlist_id2'=>$this->id, ':playlist_id3'=>$this->id);
  212. if ($filterFiles) {
  213. $params[':file_exists'] = $filterFiles;
  214. }
  215. $rows = Application_Common_Database::prepareAndExecute($sql, $params);
  216. $offset = 0;
  217. foreach ($rows as &$row) {
  218. //Logging::info($row);
  219. $clipSec = Application_Common_DateHelper::playlistTimeToSeconds($row['length']);
  220. $row['trackSec'] = $clipSec;
  221. $row['cueInSec'] = Application_Common_DateHelper::playlistTimeToSeconds($row['cuein']);
  222. $row['cueOutSec'] = Application_Common_DateHelper::playlistTimeToSeconds($row['cueout']);
  223. $trackoffset = $row['trackoffset'];
  224. $offset += $clipSec;
  225. $offset -= $trackoffset;
  226. $offset_cliplength = Application_Common_DateHelper::secondsToPlaylistTime($offset);
  227. //format the length for UI.
  228. if ($row['type'] == 2) {
  229. $bl = new Application_Model_Block($row['item_id']);
  230. $formatter = new LengthFormatter($bl->getLength());
  231. } else {
  232. $formatter = new LengthFormatter($row['length']);
  233. }
  234. $row['length'] = $formatter->format();
  235. $formatter = new LengthFormatter($offset_cliplength);
  236. $row['offset'] = $formatter->format();
  237. //format the fades in format 00(.000000)
  238. $fades = $this->getFadeInfo($row['position']);
  239. $row['fadein'] = $fades[0];
  240. $row['fadeout'] = $fades[1];
  241. // format the cues in format 00:00:00(.0)
  242. // we need to add the '.0' for cues and not fades
  243. // because propel takes care of this for us
  244. // (we use propel to fetch the fades)
  245. $row['cuein'] = str_pad(substr($row['cuein'], 0, 10), 10, '.0');
  246. $row['cueout'] = str_pad(substr($row['cueout'], 0, 10), 10, '.0');
  247. //format original length
  248. $formatter = new LengthFormatter($row['orig_length']);
  249. $row['orig_length'] = $formatter->format();
  250. // XSS exploit prevention
  251. $row["track_title"] = htmlspecialchars($row["track_title"]);
  252. $row["creator"] = htmlspecialchars($row["creator"]);
  253. }
  254. return $rows;
  255. }
  256. /**
  257. * The database stores fades in 00:00:00 Time format with optional millisecond resolution .000000
  258. * but this isn't practical since fades shouldn't be very long usuall 1 second or less. This function
  259. * will normalize the fade so that it looks like 00.000000 to the user.
  260. **/
  261. public function normalizeFade($fade)
  262. {
  263. //First get rid of the first six characters 00:00: which will be added back later for db update
  264. $fade = substr($fade, 6);
  265. //Second add .000000 if the fade does't have milliseconds format already
  266. $dbFadeStrPos = strpos( $fade, '.' );
  267. if ($dbFadeStrPos === false) {
  268. $fade .= '.000000';
  269. } else {
  270. while (strlen($fade) < 9) {
  271. $fade .= '0';
  272. }
  273. }
  274. //done, just need to set back the formated values
  275. return $fade;
  276. }
  277. // returns true/false and ids of dynamic blocks
  278. public function hasDynamicBlock()
  279. {
  280. $ids = $this->getIdsOfDynamicBlocks();
  281. if (count($ids) > 0) {
  282. return true;
  283. } else {
  284. return false;
  285. }
  286. }
  287. public function getIdsOfDynamicBlocks()
  288. {
  289. $sql = "SELECT bl.id FROM cc_playlistcontents as pc
  290. JOIN cc_block as bl ON pc.type=2 AND pc.block_id=bl.id AND bl.type='dynamic'
  291. WHERE playlist_id=:playlist_id AND pc.type=2";
  292. $result = Application_Common_Database::prepareAndExecute($sql, array(':playlist_id'=>$this->id));
  293. return $result;
  294. }
  295. //aggregate column on playlistcontents cliplength column.
  296. public function getLength()
  297. {
  298. if ($this->hasDynamicBlock()) {
  299. $ids = $this->getIdsOfDynamicBlocks();
  300. $length = $this->pl->getDbLength();
  301. foreach ($ids as $id) {
  302. $bl = new Application_Model_Block($id['id']);
  303. if ($bl->hasItemLimit()) {
  304. return "N/A";
  305. }
  306. }
  307. $formatter = new LengthFormatter($length);
  308. return "~".$formatter->format();
  309. } else {
  310. return $this->pl->getDbLength();
  311. }
  312. }
  313. private function insertPlaylistElement($info)
  314. {
  315. $row = new CcPlaylistcontents();
  316. $row->setDbPlaylistId($this->id);
  317. $row->setDbPosition($info["pos"]);
  318. $row->setDbCliplength($info["cliplength"]);
  319. $row->setDbCuein($info["cuein"]);
  320. $row->setDbCueout($info["cueout"]);
  321. $row->setDbFadein(Application_Common_DateHelper::secondsToPlaylistTime($info["fadein"]));
  322. $row->setDbFadeout(Application_Common_DateHelper::secondsToPlaylistTime($info["fadeout"]));
  323. if ($info["ftype"] == "audioclip") {
  324. $row->setDbFileId($info["id"]);
  325. $row->setDbTrackOffset($info["crossfadeDuration"]);
  326. $type = 0;
  327. } elseif ($info["ftype"] == "stream") {
  328. $row->setDbStreamId($info["id"]);
  329. $type = 1;
  330. } elseif ($info["ftype"] == "block") {
  331. $row->setDbBlockId($info["id"]);
  332. $type = 2;
  333. }
  334. $row->setDbType($type);
  335. $row->save($this->con);
  336. // above save result update on cc_playlist table on length column.
  337. // but $this->pl doesn't get updated automatically
  338. // so we need to manually grab it again from DB so it has updated values
  339. // It is something to do FORMAT_ON_DEMAND( Lazy Loading )
  340. $this->pl = CcPlaylistQuery::create()->findPK($this->id);
  341. }
  342. /*
  343. *
  344. */
  345. private function buildEntry($p_item, $pos)
  346. {
  347. $objType = $p_item[1];
  348. $objId = $p_item[0];
  349. if ($objType == 'audioclip') {
  350. $obj = CcFilesQuery::create()->findPK($objId, $this->con);
  351. } elseif ($objType == "stream") {
  352. $obj = CcWebstreamQuery::create()->findPK($objId, $this->con);
  353. } elseif ($objType == "block") {
  354. $obj = CcBlockQuery::create()->findPK($objId, $this->con);
  355. } else {
  356. throw new Exception("Unknown file type");
  357. }
  358. if (isset($obj)) {
  359. if (($obj instanceof CcFiles && $obj->visible())
  360. || $obj instanceof CcWebstream ||
  361. $obj instanceof CcBlock) {
  362. $entry = $this->plItem;
  363. $entry["id"] = $obj->getDbId();
  364. $entry["pos"] = $pos;
  365. $entry["cliplength"] = $obj->getDbLength();
  366. if ($obj instanceof CcFiles && $obj) {
  367. $entry["cuein"] = isset($p_item['cuein']) ?
  368. $p_item['cuein'] : $obj->getDbCuein();
  369. $entry["cueout"] = isset($p_item['cueout']) ?
  370. $p_item['cueout'] : $obj->getDbCueout();
  371. $cue_in = isset($p_item['cueInSec']) ?
  372. $p_item['cueInSec'] : Application_Common_DateHelper::calculateLengthInSeconds($entry['cuein']);
  373. $cue_out = isset($p_item['cueOutSec']) ?
  374. $p_item['cueOutSec'] : Application_Common_DateHelper::calculateLengthInSeconds($entry['cueout']);
  375. $entry["cliplength"] = isset($p_item['length']) ?
  376. $p_item['length'] : Application_Common_DateHelper::secondsToPlaylistTime($cue_out-$cue_in);
  377. }
  378. elseif ($obj instanceof CcWebstream && $obj) {
  379. $entry["cuein"] = "00:00:00";
  380. $entry["cueout"] = $entry["cliplength"];
  381. }
  382. $entry["ftype"] = $objType;
  383. $entry["fadein"] = isset($p_item['fadein']) ?
  384. $p_item['fadein'] : $entry["fadein"];
  385. $entry["fadeout"] = isset($p_item['fadeout']) ?
  386. $p_item['fadeout'] : $entry["fadeout"];
  387. }
  388. return $entry;
  389. }
  390. else {
  391. throw new Exception("trying to add a object that does not exist.");
  392. }
  393. }
  394. /*
  395. * @param array $p_items
  396. * an array of audioclips to add to the playlist
  397. * @param int|null $p_afterItem
  398. * item which to add the new items after in the playlist, null if added to the end.
  399. * @param string (before|after) $addAfter
  400. * whether to add the clips before or after the selected item.
  401. */
  402. public function addAudioClips($p_items, $p_afterItem=null, $addType = 'after')
  403. {
  404. $this->con->beginTransaction();
  405. $contentsToUpdate = array();
  406. try {
  407. if (is_numeric($p_afterItem)) {
  408. $afterItem = CcPlaylistcontentsQuery::create()->findPK($p_afterItem);
  409. $index = $afterItem->getDbPosition();
  410. $pos = ($addType == 'after') ? $index + 1 : $index;
  411. $contentsToUpdate = CcPlaylistcontentsQuery::create()
  412. ->filterByDbPlaylistId($this->id)
  413. ->filterByDbPosition($pos, Criteria::GREATER_EQUAL)
  414. ->orderByDbPosition()
  415. ->find($this->con);
  416. } else {
  417. //add to the end of the playlist
  418. if ($addType == 'after') {
  419. $pos = $this->getSize();
  420. }
  421. //add to the beginning of the playlist.
  422. else {
  423. $pos = 0;
  424. $contentsToUpdate = CcPlaylistcontentsQuery::create()
  425. ->filterByDbPlaylistId($this->id)
  426. ->orderByDbPosition()
  427. ->find($this->con);
  428. }
  429. $contentsToUpdate = CcPlaylistcontentsQuery::create()
  430. ->filterByDbPlaylistId($this->id)
  431. ->filterByDbPosition($pos, Criteria::GREATER_EQUAL)
  432. ->orderByDbPosition()
  433. ->find($this->con);
  434. }
  435. foreach ($p_items as $ac) {
  436. $res = $this->insertPlaylistElement($this->buildEntry($ac, $pos));
  437. // update is_playlist flag in cc_files to indicate the
  438. // file belongs to a playlist or block (in this case a playlist)
  439. if ($ac[1] == "audioclip") {
  440. $db_file = CcFilesQuery::create()->findPk($ac[0], $this->con);
  441. $db_file->setDbIsPlaylist(true)->save($this->con);
  442. }
  443. $pos = $pos + 1;
  444. }
  445. //reset the positions of the remaining items.
  446. for ($i = 0; $i < count($contentsToUpdate); $i++) {
  447. $contentsToUpdate[$i]->setDbPosition($pos);
  448. $contentsToUpdate[$i]->save($this->con);
  449. $pos = $pos + 1;
  450. }
  451. $this->pl->setDbMtime(new DateTime("now", new DateTimeZone("UTC")));
  452. $this->pl->save($this->con);
  453. $this->con->commit();
  454. } catch (Exception $e) {
  455. $this->con->rollback();
  456. throw $e;
  457. }
  458. }
  459. /**
  460. * Move audioClip to the new position in the playlist
  461. *
  462. * @param array $p_items
  463. * array of unique ids of the selected items
  464. * @param int $p_afterItem
  465. * unique id of the item to move the clip after
  466. */
  467. public function moveAudioClips($p_items, $p_afterItem=NULL)
  468. {
  469. $this->con->beginTransaction();
  470. try {
  471. $contentsToMove = CcPlaylistcontentsQuery::create()
  472. ->filterByDbId($p_items, Criteria::IN)
  473. ->orderByDbPosition()
  474. ->find($this->con);
  475. $otherContent = CcPlaylistcontentsQuery::create()
  476. ->filterByDbId($p_items, Criteria::NOT_IN)
  477. ->filterByDbPlaylistId($this->id)
  478. ->orderByDbPosition()
  479. ->find($this->con);
  480. $pos = 0;
  481. //moving items to beginning of the playlist.
  482. if (is_null($p_afterItem)) {
  483. Logging::info("moving items to beginning of playlist");
  484. foreach ($contentsToMove as $item) {
  485. Logging::info("item {$item->getDbId()} to pos {$pos}");
  486. $item->setDbPosition($pos);
  487. $item->save($this->con);
  488. $pos = $pos + 1;
  489. }
  490. foreach ($otherContent as $item) {
  491. Logging::info("item {$item->getDbId()} to pos {$pos}");
  492. $item->setDbPosition($pos);
  493. $item->save($this->con);
  494. $pos = $pos + 1;
  495. }
  496. } else {
  497. Logging::info("moving items after {$p_afterItem}");
  498. foreach ($otherContent as $item) {
  499. Logging::info("item {$item->getDbId()} to pos {$pos}");
  500. $item->setDbPosition($pos);
  501. $item->save($this->con);
  502. $pos = $pos + 1;
  503. if ($item->getDbId() == $p_afterItem) {
  504. foreach ($contentsToMove as $move) {
  505. Logging::info("item {$move->getDbId()} to pos {$pos}");
  506. $move->setDbPosition($pos);
  507. $move->save($this->con);
  508. $pos = $pos + 1;
  509. }
  510. }
  511. }
  512. }
  513. $this->con->commit();
  514. } catch (Exception $e) {
  515. $this->con->rollback();
  516. throw $e;
  517. }
  518. $this->pl = CcPlaylistQuery::create()->findPK($this->id);
  519. $this->pl->setDbMtime(new DateTime("now", new DateTimeZone("UTC")));
  520. $this->pl->save($this->con);
  521. }
  522. /**
  523. * Remove audioClip from playlist
  524. *
  525. * @param array $p_items
  526. * array of unique item ids to remove from the playlist..
  527. */
  528. public function delAudioClips($p_items)
  529. {
  530. $this->con->beginTransaction();
  531. try {
  532. // we need to get the file id of the item we are deleting
  533. // before the item gets deleted from the playlist
  534. $itemsToDelete = CcPlaylistcontentsQuery::create()
  535. ->filterByPrimaryKeys($p_items)
  536. ->filterByDbFileId(null, Criteria::NOT_EQUAL)
  537. ->find($this->con);
  538. CcPlaylistcontentsQuery::create()
  539. ->findPKs($p_items)
  540. ->delete($this->con);
  541. // now that the items have been deleted we can update the
  542. // is_playlist flag in cc_files
  543. Application_Model_StoredFile::setIsPlaylist($itemsToDelete, 'playlist', false);
  544. $contents = CcPlaylistcontentsQuery::create()
  545. ->filterByDbPlaylistId($this->id)
  546. ->orderByDbPosition()
  547. ->find($this->con);
  548. //reset the positions of the remaining items.
  549. for ($i = 0; $i < count($contents); $i++) {
  550. $contents[$i]->setDbPosition($i);
  551. $contents[$i]->save($this->con);
  552. }
  553. $this->pl->setDbMtime(new DateTime("now", new DateTimeZone("UTC")));
  554. $this->pl->save($this->con);
  555. $this->con->commit();
  556. } catch (Exception $e) {
  557. $this->con->rollback();
  558. throw $e;
  559. }
  560. }
  561. public function getFadeInfo($pos)
  562. {
  563. $row = CcPlaylistcontentsQuery::create()
  564. ->joinWith(CcFilesPeer::OM_CLASS)
  565. ->filterByDbPlaylistId($this->id)
  566. ->filterByDbPosition($pos)
  567. ->findOne();
  568. if (!$row) {
  569. return NULL;
  570. }
  571. //Propel returns values in form 00.000000 format which is for only seconds.
  572. //We only want to display 1 decimal
  573. $fadeIn = substr($row->getDbFadein(), 0, 4);
  574. $fadeOut = substr($row->getDbFadeout(), 0, 4);
  575. return array($fadeIn, $fadeOut);
  576. }
  577. /*
  578. * create a crossfade from item in cc_playlist_contents with $id1 to item $id2.
  579. *
  580. * $fadeOut length of fade out in seconds if $id1
  581. * $fadeIn length of fade in in seconds of $id2
  582. * $offset time in seconds from end of $id1 that $id2 will begin to play.
  583. */
  584. public function createCrossfade($id1, $fadeOut, $id2, $fadeIn, $offset)
  585. {
  586. $this->con->beginTransaction();
  587. if (!isset($offset)) {
  588. $offset = Application_Model_Preference::GetDefaultCrossfadeDuration();
  589. }
  590. try {
  591. if (isset($id1)) {
  592. $this->changeFadeInfo($id1, null, $fadeOut);
  593. }
  594. if (isset($id2)) {
  595. $this->changeFadeInfo($id2, $fadeIn, null, $offset);
  596. }
  597. $this->con->commit();
  598. } catch (Exception $e) {
  599. $this->con->rollback();
  600. throw $e;
  601. }
  602. }
  603. /**
  604. * Change fadeIn and fadeOut values for playlist Element
  605. *
  606. * @param int $id
  607. * id of audioclip in playlist contents table.
  608. * @param string $fadeIn
  609. * new value in ss.ssssss or extent format
  610. * @param string $fadeOut
  611. * new value in ss.ssssss or extent format
  612. * @return boolean
  613. */
  614. public function changeFadeInfo($id, $fadeIn, $fadeOut, $offset=null)
  615. {
  616. //See issue CC-2065, pad the fadeIn and fadeOut so that it is TIME compatable with the DB schema
  617. //For the top level PlayList either fadeIn or fadeOut will sometimes be Null so need a gaurd against
  618. //setting it to nonNull for checks down below
  619. $fadeIn = $fadeIn?'00:00:'.$fadeIn:$fadeIn;
  620. $fadeOut = $fadeOut?'00:00:'.$fadeOut:$fadeOut;
  621. $this->con->beginTransaction();
  622. try {
  623. $row = CcPlaylistcontentsQuery::create()->findPK($id);
  624. if (is_null($row)) {
  625. throw new Exception("Playlist item does not exist.");
  626. }
  627. $clipLength = $row->getDbCliplength();
  628. if (!is_null($fadeIn)) {
  629. $sql = "SELECT :fadein::INTERVAL > INTERVAL '{$clipLength}'";
  630. if (Application_Common_Database::prepareAndExecute($sql, array(':fadein'=>$fadeIn), 'column')) {
  631. //"Fade In can't be larger than overall playlength.";
  632. $fadeIn = $clipLength;
  633. }
  634. $row->setDbFadein($fadeIn);
  635. if (!is_null($offset)) {
  636. $row->setDbTrackOffset($offset);
  637. $row->save($this->con);
  638. }
  639. }
  640. if (!is_null($fadeOut)) {
  641. $sql = "SELECT :fadeout::INTERVAL > INTERVAL '{$clipLength}'";
  642. if (Application_Common_Database::prepareAndExecute($sql, array(':fadeout'=>$fadeOut), 'column')) {
  643. //Fade Out can't be larger than overall playlength.";
  644. $fadeOut = $clipLength;
  645. }
  646. $row->setDbFadeout($fadeOut);
  647. }
  648. $this->pl->setDbMtime(new DateTime("now", new DateTimeZone("UTC")));
  649. $this->pl->save($this->con);
  650. $this->con->commit();
  651. } catch (Exception $e) {
  652. $this->con->rollback();
  653. throw $e;
  654. }
  655. return array("fadeIn" => $fadeIn, "fadeOut" => $fadeOut);
  656. }
  657. public function setfades($fadein, $fadeout)
  658. {
  659. if (isset($fadein)) {
  660. Logging::info("Setting playlist fade in {$fadein}");
  661. $row = CcPlaylistcontentsQuery::create()
  662. ->filterByDbPlaylistId($this->id)
  663. ->filterByDbPosition(0)
  664. ->findOne($this->con);
  665. $this->changeFadeInfo($row->getDbId(), $fadein, null);
  666. }
  667. if (isset($fadeout)) {
  668. Logging::info("Setting playlist fade out {$fadeout}");
  669. $row = CcPlaylistcontentsQuery::create()
  670. ->filterByDbPlaylistId($this->id)
  671. ->filterByDbPosition($this->getSize()-1)
  672. ->findOne($this->con);
  673. $this->changeFadeInfo($row->getDbId(), null, $fadeout);
  674. }
  675. }
  676. /**
  677. * Change cueIn/cueOut values for playlist element
  678. *
  679. * @param int $pos
  680. * position of audioclip in playlist
  681. * @param string $cueIn
  682. * new value in ss.ssssss or extent format
  683. * @param string $cueOut
  684. * new value in ss.ssssss or extent format
  685. * @return boolean or pear error object
  686. */
  687. public function changeClipLength($id, $cueIn, $cueOut)
  688. {
  689. $this->con->beginTransaction();
  690. $errArray= array();
  691. try {
  692. if (is_null($cueIn) && is_null($cueOut)) {
  693. $errArray["error"] = _("Cue in and cue out are null.");
  694. $errArray["type"] = self::CUE_ALL_ERROR;
  695. return $errArray;
  696. }
  697. $row = CcPlaylistcontentsQuery::create()
  698. ->joinWith(CcFilesPeer::OM_CLASS)
  699. ->filterByPrimaryKey($id)
  700. ->findOne($this->con);
  701. if (is_null($row)) {
  702. throw new Exception("Playlist item does not exist.");
  703. }
  704. $oldCueIn = $row->getDBCuein();
  705. $oldCueOut = $row->getDbCueout();
  706. $fadeIn = $row->getDbFadein();
  707. $fadeOut = $row->getDbFadeout();
  708. $file = $row->getCcFiles($this->con);
  709. $origLength = $file->getDbLength();
  710. if (!is_null($cueIn) && !is_null($cueOut)) {
  711. if ($cueOut === "") {
  712. $cueOut = $origLength;
  713. }
  714. $sql = "SELECT :cueIn::INTERVAL > :cueOut::INTERVAL";
  715. if (Application_Common_Database::prepareAndExecute($sql, array(':cueIn'=>$cueIn, ':cueOut'=>$cueOut), 'column')) {
  716. $errArray["error"] = _("Can't set cue in to be larger than cue out.");
  717. $errArray["type"] = self::CUE_IN_ERROR;
  718. return $errArray;
  719. }
  720. $sql = "SELECT :cueOut::INTERVAL > :origLength::INTERVAL";
  721. if (Application_Common_Database::prepareAndExecute($sql, array(':cueOut'=>$cueOut, ':origLength'=>$origLength), 'column')) {
  722. $errArray["error"] = _("Can't set cue out to be greater than file length.");
  723. $errArray["type"] = self::CUE_OUT_ERROR;
  724. return $errArray;
  725. }
  726. $sql = "SELECT :cueOut::INTERVAL - :cueIn::INTERVAL";
  727. $cliplength = Application_Common_Database::prepareAndExecute($sql, array(':cueOut'=>$cueOut, ':cueIn'=>$cueIn), 'column');
  728. $row->setDbCuein($cueIn);
  729. $row->setDbCueout($cueOut);
  730. $row->setDBCliplength($cliplength);
  731. } elseif (!is_null($cueIn)) {
  732. $sql = "SELECT :cueIn::INTERVAL > :oldCueOut::INTERVAL";
  733. if (Application_Common_Database::prepareAndExecute($sql, array(':cueIn'=>$cueIn, ':oldCueOut'=>$oldCueOut), 'column')) {
  734. $errArray["error"] = _("Can't set cue in to be larger than cue out.");
  735. $errArray["type"] = self::CUE_IN_ERROR;
  736. return $errArray;
  737. }
  738. $sql = "SELECT :oldCueOut::INTERVAL - :cueIn::INTERVAL";
  739. $cliplength = Application_Common_Database::prepareAndExecute($sql, array(':cueIn'=>$cueIn, ':oldCueOut'=>$oldCueOut), 'column');
  740. $row->setDbCuein($cueIn);
  741. $row->setDBCliplength($cliplength);
  742. } elseif (!is_null($cueOut)) {
  743. if ($cueOut === "") {
  744. $cueOut = $origLength;
  745. }
  746. $sql = "SELECT :cueOut::INTERVAL < :oldCueIn::INTERVAL";
  747. if (Application_Common_Database::prepareAndExecute($sql, array(':cueOut'=>$cueOut, ':oldCueIn'=>$oldCueIn), 'column')) {
  748. $errArray["error"] = _("Can't set cue out to be smaller than cue in.");
  749. $errArray["type"] = self::CUE_OUT_ERROR;
  750. return $errArray;
  751. }
  752. $sql = "SELECT :cueOut::INTERVAL > :origLength::INTERVAL";
  753. if (Application_Common_Database::prepareAndExecute($sql, array(':cueOut'=>$cueOut, ':origLength'=>$origLength), 'column')) {
  754. $errArray["error"] = _("Can't set cue out to be greater than file length.");
  755. $errArray["type"] = self::CUE_OUT_ERROR;
  756. return $errArray;
  757. }
  758. $sql = "SELECT :cueOut::INTERVAL - :oldCueIn::INTERVAL";
  759. $cliplength = Application_Common_Database::prepareAndExecute($sql, array(':cueOut'=>$cueOut, ':oldCueIn'=>$oldCueIn), 'column');
  760. $row->setDbCueout($cueOut);
  761. $row->setDBCliplength($cliplength);
  762. }
  763. $cliplength = $row->getDbCliplength();
  764. $sql = "SELECT :fadeIn::INTERVAL > :cliplength::INTERVAL";
  765. if (Application_Common_Database::prepareAndExecute($sql, array(':fadeIn'=>$fadeIn, ':cliplength'=>$cliplength), 'column')) {
  766. $fadeIn = $cliplength;
  767. $row->setDbFadein($fadeIn);
  768. }
  769. $sql = "SELECT :fadeOut::INTERVAL > :cliplength::INTERVAL";
  770. if (Application_Common_Database::prepareAndExecute($sql, array(':fadeOut'=>$fadeOut, ':cliplength'=>$cliplength), 'column')) {
  771. $fadeOut = $cliplength;
  772. $row->setDbFadein($fadeOut);
  773. }
  774. $row->save($this->con);
  775. $this->pl->setDbMtime(new DateTime("now", new DateTimeZone("UTC")));
  776. $this->pl->save($this->con);
  777. $this->con->commit();
  778. } catch (Exception $e) {
  779. $this->con->rollback();
  780. throw $e;
  781. }
  782. return array("cliplength"=> $cliplength, "cueIn"=> $cueIn, "cueOut"=> $cueOut, "length"=> $this->getLength(),
  783. "fadeIn"=> $fadeIn, "fadeOut"=> $fadeOut);
  784. }
  785. public function getAllPLMetaData()
  786. {
  787. $categories = $this->categories;
  788. $md = array();
  789. foreach ($categories as $key => $val) {
  790. $method = 'get' . $val;
  791. $md[$key] = $this->$method();
  792. }
  793. return $md;
  794. }
  795. public function getMetaData($category)
  796. {
  797. $cat = $this->categories[$category];
  798. $method = 'get' . $cat;
  799. return $this->$method();
  800. }
  801. public function setMetadata($category, $value)
  802. {
  803. $cat = $this->categories[$category];
  804. $method = 'set' . $cat;
  805. $this->$method($value);
  806. }
  807. public static function getPlaylistCount()
  808. {
  809. $sql = 'SELECT count(*) as cnt FROM cc_playlist';
  810. return Application_Common_Database::prepareAndExecute($sql, array(),
  811. Application_Common_Database::COLUMN);
  812. }
  813. /**
  814. * Delete the file from all playlists.
  815. * @param string $p_fileId
  816. */
  817. public static function DeleteFileFromAllPlaylists($p_fileId)
  818. {
  819. CcPlaylistcontentsQuery::create()->filterByDbFileId($p_fileId)->delete();
  820. }
  821. /**
  822. * Delete playlists that match the ids..
  823. * @param array $p_ids
  824. */
  825. public static function deletePlaylists($p_ids, $p_userId)
  826. {
  827. $userInfo = Zend_Auth::getInstance()->getStorage()->read();
  828. $user = new Application_Model_User($userInfo->id);
  829. $isAdminOrPM = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
  830. // get only the files from the playlists
  831. // we are about to delete
  832. $itemsToDelete = CcPlaylistcontentsQuery::create()
  833. ->filterByDbPlaylistId($p_ids)
  834. ->filterByDbFileId(null, Criteria::NOT_EQUAL)
  835. ->find();
  836. $updateIsPlaylistFlag = false;
  837. if (!$isAdminOrPM) {
  838. $leftOver = self::playlistsNotOwnedByUser($p_ids, $p_userId);
  839. if (count($leftOver) == 0) {
  840. CcPlaylistQuery::create()->findPKs($p_ids)->delete();
  841. $updateIsPlaylistFlag = true;
  842. } else {
  843. throw new PlaylistNoPermissionException;
  844. }
  845. } else {
  846. CcPlaylistQuery::create()->findPKs($p_ids)->delete();
  847. $updateIsPlaylistFlag = true;
  848. }
  849. if ($updateIsPlaylistFlag) {
  850. // update is_playlist flag in cc_files
  851. Application_Model_StoredFile::setIsPlaylist(
  852. $itemsToDelete,
  853. 'playlist',
  854. false
  855. );
  856. }
  857. }
  858. // This function returns that are not owen by $p_user_id among $p_ids
  859. private static function playlistsNotOwnedByUser($p_ids, $p_userId)
  860. {
  861. $ownedByUser = CcPlaylistQuery::create()->filterByDbCreatorId($p_userId)->find()->getData();
  862. $selectedPls = $p_ids;
  863. $ownedPls = array();
  864. foreach ($ownedByUser as $pl) {
  865. if (in_array($pl->getDbId(), $selectedPls)) {
  866. $ownedPls[] = $pl->getDbId();
  867. }
  868. }
  869. $leftOvers = array_diff($selectedPls, $ownedPls);
  870. return $leftOvers;
  871. }
  872. /**
  873. * Delete all files from playlist
  874. * @param int $p_playlistId
  875. */
  876. public function deleteAllFilesFromPlaylist()
  877. {
  878. // get only the files from the playlist
  879. // we are about to clear out
  880. $itemsToDelete = CcPlaylistcontentsQuery::create()
  881. ->filterByDbPlaylistId($this->id)
  882. ->filterByDbFileId(null, Criteria::NOT_EQUAL)
  883. ->find();
  884. CcPlaylistcontentsQuery::create()->findByDbPlaylistId($this->id)->delete();
  885. // update is_playlist flag in cc_files
  886. Application_Model_StoredFile::setIsPlaylist(
  887. $itemsToDelete,
  888. 'playlist',
  889. false
  890. );
  891. $this->pl->setDbMtime(new DateTime("now", new DateTimeZone("UTC")));
  892. $this->pl->save($this->con);
  893. $this->con->commit();
  894. }
  895. public function shuffle()
  896. {
  897. $sql = <<<SQL
  898. SELECT max(position) from cc_playlistcontents WHERE playlist_id=:p1
  899. SQL;
  900. $out = Application_Common_Database::prepareAndExecute($sql, array("p1"=>$this->id));
  901. $maxPosition = $out[0]['max'];
  902. $map = range(0, $maxPosition);
  903. shuffle($map);
  904. $currentPos = implode(',', array_keys($map));
  905. $sql = "UPDATE cc_playlistcontents SET position = CASE position ";
  906. foreach ($map as $current => $after) {
  907. $sql .= sprintf("WHEN %d THEN %d ", $current, $after);
  908. }
  909. $sql .= "END WHERE position IN ($currentPos) and playlist_id=:p1";
  910. Application_Common_Database::prepareAndExecute($sql, array("p1"=>$this->id));
  911. return array('result' => 0);
  912. }
  913. public static function getAllPlaylistFiles()
  914. {
  915. $sql = <<<SQL
  916. SELECT distinct(file_id)
  917. FROM cc_playlistcontents
  918. WHERE file_id is not null
  919. SQL;
  920. $files = Application_Common_Database::prepareAndExecute($sql);
  921. $real_files = array();
  922. foreach ($files as $f) {
  923. $real_files[] = $f['file_id'];
  924. }
  925. return $real_files;
  926. }
  927. public static function getAllPlaylistStreams()
  928. {
  929. $sql = <<<SQL
  930. SELECT distinct(stream_id)
  931. FROM cc_playlistcontents
  932. WHERE stream_id is not null
  933. SQL;
  934. $streams = Application_Common_Database::prepareAndExecute($sql);
  935. $real_streams = array();
  936. foreach ($streams as $s) {
  937. $real_streams[] = $s['stream_id'];
  938. }
  939. return $real_streams;
  940. }
  941. /** Find out if a playlist contains any files that have been deleted from disk.
  942. * This function relies on the "file_exists" column in the database being accurate and true,
  943. * which it should.
  944. * @return boolean true if there are missing files in this playlist, false otherwise.
  945. */
  946. public function containsMissingFiles()
  947. {
  948. $playlistContents = $this->pl->getCcPlaylistcontentss("type = 0"); //type=0 is only files, not other types of media
  949. //Slightly faster than the other Propel version below (this one does an INNER JOIN):
  950. $missingFiles = CcFilesQuery::create()
  951. ->join("CcFiles.CcPlaylistcontents")
  952. ->where("CcPlaylistcontents.DbPlaylistId = ?", $this->pl->getDbId())
  953. ->where("CcFiles.DbFileExists = ?", 'false')
  954. ->find();
  955. //Nicer Propel version but slightly slower because it generates a LEFT JOIN:
  956. /*
  957. $missingFiles = CcPlaylistcontentsQuery::create()
  958. ->filterByDbPlaylistId($this->pl->getDbId())
  959. ->useCcFilesQuery()
  960. ->filterByDbFileExists(false)
  961. ->endUse()
  962. ->find();
  963. */
  964. if (!$missingFiles->isEmpty())
  965. {
  966. return true;
  967. }
  968. return false;
  969. }
  970. } // class Playlist
  971. class PlaylistNotFoundException extends Exception {}
  972. class PlaylistNoPermissionException extends Exception {}
  973. class PlaylistOutDatedException extends Exception {}