Пример #1
0
 public static function load($force = false)
 {
     set_time_limit(300);
     Debug::startTimer('Load remote data');
     $tracks = array();
     $first_page = self::loadPage(1);
     if (!$first_page) {
         return FALSE;
     }
     $total_pages = (int) $first_page['total_pages'];
     $tracks[1] = $first_page['tracks'];
     for ($i = 2; $i <= $total_pages; $i++) {
         $next_page = self::loadPage($i);
         if ($next_page) {
             $tracks[$i] = $next_page['tracks'];
         }
     }
     // Loop through tracks.
     $new_songs = array();
     foreach ($tracks as $page_num => $result) {
         foreach ((array) $result as $row) {
             $processed = External::processRemote($row);
             $processed['hash'] = Song::getSongHash($processed);
             $new_songs[$processed['hash']] = $processed;
         }
     }
     Debug::endTimer('Load remote data');
     return External::import($new_songs, $force);
 }
Пример #2
0
 public function newAction()
 {
     $new_threshold = strtotime('-2 weeks');
     $song_adapters = Song::getExternalAdapters();
     $new_songs_raw = $this->em->createQuery('SELECT s, ex_eqbeats, ex_btunes, ex_pfm
         FROM Entity\\Song s
         LEFT JOIN s.external_eqbeats AS ex_eqbeats
         LEFT JOIN s.external_bronytunes AS ex_btunes
         LEFT JOIN s.external_ponyfm AS ex_pfm
         WHERE (ex_eqbeats.created >= :threshold OR ex_btunes.created >= :threshold OR ex_pfm.created >= :threshold)')->setParameter('threshold', $new_threshold)->getArrayResult();
     $new_songs = array();
     foreach ($new_songs_raw as $song) {
         $timestamps = array();
         foreach ($song_adapters as $adapter_key => $adapter_class) {
             $local_key = 'external_' . $adapter_key;
             if (!empty($song[$local_key])) {
                 $timestamps[] = $song[$local_key]['created'];
             }
         }
         $song['created'] = max($timestamps);
         $song['filename'] = $song['artist'] . ' - ' . $song['title'];
         $new_songs[] = $song;
     }
     $new_songs = Utilities::irsort($new_songs, 'created');
     $this->view->new_songs = $new_songs;
 }
Пример #3
0
 public function indexAction()
 {
     $id = $this->getParam('id');
     if (empty($id)) {
         $this->redirectHome();
     }
     $record = Song::find($id);
     if (!$record instanceof Song) {
         throw new \DF\Exception\DisplayOnly('Song not found!');
     }
     $song_info = array();
     $song_info['record'] = $record;
     // Get external provider information.
     $song_info['external'] = $record->getExternal();
     // Get album art and lyrics from all providers.
     $adapters = Song::getExternalAdapters();
     $external_fields = array('lyrics', 'purchase_url', 'description');
     foreach ($external_fields as $field_name) {
         $song_info[$field_name] = NULL;
         foreach ($adapters as $adapter_name => $adapter_class) {
             if (!empty($song_info['external'][$adapter_name][$field_name])) {
                 $song_info[$field_name] = $song_info['external'][$adapter_name][$field_name];
                 break;
             }
         }
     }
     $song_info['image_url'] = $record->image_url;
     if (!$song_info['image_url']) {
         $song_info['image_url'] = \DF\Url::content('images/song_generic.png');
     }
     $song_info['description'] = $this->_cleanUpText($song_info['description']);
     $song_info['lyrics'] = $this->_cleanUpText($song_info['lyrics']);
     // Get most recent playback information.
     $history_raw = $this->em->createQuery('
         SELECT sh, st
         FROM Entity\\SongHistory sh JOIN sh.station st
         WHERE sh.song_id = :song_id AND st.category IN (:categories) AND sh.timestamp >= :threshold
         ORDER BY sh.timestamp DESC')->setParameter('song_id', $record->id)->setParameter('categories', array('audio', 'video'))->setParameter('threshold', strtotime('-1 week'))->getArrayResult();
     $history = array();
     $last_row = NULL;
     foreach ($history_raw as $i => $row) {
         if ($last_row && $row['station_id'] == $last_row['station_id']) {
             $timestamp_diff = abs($row['timestamp'] - $last_row['timestamp']);
             if ($timestamp_diff < 60) {
                 continue;
             }
         }
         $history[] = $row;
         $last_row = $row;
     }
     $song_info['recent_history'] = $history;
     // Get requestable locations.
     $song_info['request_on'] = $this->em->createQuery('
         SELECT sm, st
         FROM Entity\\StationMedia sm JOIN sm.station st
         WHERE sm.song_id = :song_id
         GROUP BY sm.station_id')->setParameter('song_id', $record->id)->getArrayResult();
     $this->view->song = $song_info;
 }
Пример #4
0
 public static function import($new_songs, $force = false)
 {
     $db_stats = array('skipped' => 0, 'updated' => 0, 'inserted' => 0, 'deleted' => 0);
     if (empty($new_songs)) {
         return false;
     }
     Debug::startTimer('Import data into database');
     $em = self::getEntityManager();
     $existing_hashes = self::getHashes();
     $existing_ids = self::getIds();
     $unused_hashes = $existing_hashes;
     $song_ids = Song::getIds();
     $i = 0;
     foreach ($new_songs as $song_hash => $processed) {
         if (!in_array($song_hash, $song_ids)) {
             Song::getOrCreate($processed);
         }
         if (isset($existing_hashes[$song_hash])) {
             if ($force && $existing_hashes[$song_hash] == $processed['id']) {
                 $db_stats['updated']++;
                 $record = self::find($processed['id']);
             } else {
                 $db_stats['skipped']++;
                 $record = null;
             }
         } else {
             if (isset($existing_ids[$processed['id']])) {
                 $db_stats['updated']++;
                 $record = self::find($processed['id']);
             } else {
                 $db_stats['inserted']++;
                 $record = new self();
             }
         }
         if ($record instanceof self) {
             $existing_ids[$processed['id']] = $processed['hash'];
             $existing_hashes[$processed['hash']] = $processed['id'];
             $record->fromArray($processed);
             $em->persist($record);
         }
         unset($unused_hashes[$song_hash]);
         $i++;
         if ($i % 200 == 0) {
             $em->flush();
             $em->clear();
         }
     }
     $em->flush();
     $em->clear();
     // Clear out any songs not found.
     $hashes_remaining = array_keys($unused_hashes);
     $db_stats['deleted'] = count($hashes_remaining);
     $em->createQuery('DELETE FROM ' . __CLASS__ . ' e WHERE e.hash IN (:hashes)')->setParameter('hashes', $hashes_remaining)->execute();
     Debug::endTimer('Import data into database');
     Debug::print_r($db_stats);
     return $db_stats;
 }
Пример #5
0
 public function searchAction()
 {
     if (!$this->hasParam('q')) {
         return $this->returnError('No query provided.');
     }
     $q = trim($this->getParam('q'));
     $results_raw = $this->em->createQuery('SELECT s FROM Entity\\Song s WHERE (s.text LIKE :q OR s.id = :q_exact) ORDER BY s.text ASC')->setParameter('q', '%' . addcslashes($q, "%_") . '%')->setParameter('q_exact', $q)->setMaxResults(50)->getArrayResult();
     $results = array();
     foreach ($results_raw as $row) {
         $results[$row['id']] = Song::api($row);
     }
     return $this->returnSuccess($results);
 }
Пример #6
0
 protected static function _querySearch($song)
 {
     $base_url = 'https://eqbeats.org/tracks/search/json';
     $url = $base_url . '?' . http_build_query(array('q' => $song->artist . ' ' . $song->title, 'client' => 'ponyvillelive'));
     Debug::log('Query Search: ' . $url);
     $result = file_get_contents($url);
     if ($result) {
         $rows = json_decode($result, TRUE);
         foreach ($rows as $row) {
             $song_hash = Song::getSongHash(array('artist' => $row['user']['name'], 'title' => $row['title']));
             if (strcmp($song_hash, $song->id) == 0) {
                 return $row;
             }
         }
     }
     return NULL;
 }
Пример #7
0
 public static function load($force = false)
 {
     set_time_limit(300);
     Debug::startTimer('Load remote data');
     $remote_url = 'https://bronytunes.com/retrieve_songs.php?client_type=ponyvillelive';
     $result_raw = @file_get_contents($remote_url);
     Debug::endTimer('Load remote data');
     if ($result_raw) {
         $result = json_decode($result_raw, TRUE);
         $new_songs = array();
         foreach ((array) $result as $row) {
             $processed = External::processRemote($row);
             $processed['hash'] = Song::getSongHash($processed);
             $new_songs[$processed['hash']] = $processed;
         }
         return External::import($new_songs, $force);
     }
     return false;
 }
Пример #8
0
 /**
  * Process a single audio stream's NowPlaying info.
  *
  * @param StationStream $stream
  * @param Station $station
  * @return array Structured NowPlaying Data
  */
 public static function processAudioStream(StationStream $stream, Station $station, $force = false)
 {
     $current_np_data = (array) $stream->nowplaying_data;
     // Only process non-default streams on odd-numbered "segments" to improve performance.
     if (!$stream->is_default && !$force && NOWPLAYING_SEGMENT % 2 == 0 && !empty($current_np_data)) {
         return $current_np_data;
     }
     $np = StationStream::api($stream);
     $custom_class = Station::getStationClassName($station->name);
     $custom_adapter = '\\PVL\\RadioAdapter\\' . $custom_class;
     if (class_exists($custom_adapter)) {
         $np_adapter = new $custom_adapter($stream, $station);
     } elseif ($stream->type == "icecast") {
         $np_adapter = new \PVL\RadioAdapter\IceCast($stream, $station);
     } elseif ($stream->type == "icebreath") {
         $np_adapter = new \PVL\RadioAdapter\IceBreath($stream, $station);
     } elseif ($stream->type == "shoutcast2") {
         $np_adapter = new \PVL\RadioAdapter\ShoutCast2($stream, $station);
     } elseif ($stream->type == "shoutcast1") {
         $np_adapter = new \PVL\RadioAdapter\ShoutCast1($stream, $station);
     } else {
         return array();
     }
     Debug::log('Adapter Class: ' . get_class($np_adapter));
     $stream_np = $np_adapter->process();
     $np = array_merge($np, $stream_np['meta']);
     $np['listeners'] = $stream_np['listeners'];
     // Pull from current NP data if song details haven't changed.
     $current_song_hash = Song::getSongHash($stream_np['current_song']);
     if (strcmp($current_song_hash, $current_np_data['current_song']['id']) == 0) {
         $np['current_song'] = $current_np_data['current_song'];
         $np['song_history'] = $current_np_data['song_history'];
     } else {
         if (empty($stream_np['current_song']['text'])) {
             $np['current_song'] = array();
             $np['song_history'] = $station->getRecentHistory($stream);
         } else {
             // Register a new item in song history.
             $np['current_song'] = array();
             $np['song_history'] = $station->getRecentHistory($stream);
             // Determine whether to log this song play for analytics.
             $log_radio_play = $stream->is_default && $station->category == 'audio';
             $song_obj = Song::getOrCreate($stream_np['current_song'], $log_radio_play);
             $sh_obj = SongHistory::register($song_obj, $station, $stream, $np);
             // Compose "current_song" object for API.
             $current_song = Song::api($song_obj);
             $current_song['sh_id'] = $sh_obj->id;
             $current_song['score'] = SongVote::getScoreForStation($song_obj, $station);
             $vote_urls = array();
             $vote_functions = array('like', 'dislike', 'clearvote');
             foreach ($vote_functions as $vote_function) {
                 $vote_urls[$vote_function] = \PVL\Url::api(array('module' => 'api', 'controller' => 'song', 'action' => $vote_function, 'sh_id' => $sh_obj->id));
             }
             $current_song['vote_urls'] = $vote_urls;
             $external = $song_obj->getExternal();
             if ($external) {
                 $current_song['external'] = $song_obj->getExternal();
             }
             $np['current_song'] = $current_song;
         }
     }
     $stream->nowplaying_data = $np;
     return $np;
 }
Пример #9
0
 /** @PrePersist */
 public function preSave()
 {
     $this->song = Song::getOrCreate(array('text' => $this->artist . ' - ' . $this->title, 'artist' => $this->artist, 'title' => $this->title));
 }
Пример #10
0
 public function votesAction()
 {
     $threshold = strtotime('-2 weeks');
     $votes_raw = $this->em->createQuery('SELECT sv.song_id, SUM(sv.vote) AS vote_total FROM Entity\\SongVote sv WHERE sv.station_id = :station_id AND sv.timestamp >= :threshold GROUP BY sv.song_id')->setParameter('station_id', $this->station->id)->setParameter('threshold', $threshold)->getArrayResult();
     $ignored_songs = $this->_getIgnoredSongs();
     $votes_raw = array_filter($votes_raw, function ($value) use($ignored_songs) {
         return !isset($ignored_songs[$value['song_id']]);
     });
     \PVL\Utilities::orderBy($votes_raw, 'vote_total DESC');
     $votes = array();
     foreach ($votes_raw as $row) {
         $row['song'] = Song::find($row['song_id']);
         $votes[] = $row;
     }
     $this->view->votes = $votes;
 }
Пример #11
0
 public function votesAction()
 {
     $threshold = strtotime('-1 week');
     $votes_raw = $this->em->createQuery('SELECT sv.song_id, SUM(sv.vote) AS vote_total FROM Entity\\SongVote sv WHERE sv.timestamp >= :threshold GROUP BY sv.song_id')->setParameter('threshold', $threshold)->getArrayResult();
     Utilities::orderBy($votes_raw, 'vote_total DESC');
     $votes = array();
     foreach ($votes_raw as $row) {
         $row['song'] = Song::find($row['song_id']);
         $votes[] = $row;
     }
     $this->view->votes = $votes;
 }
Пример #12
0
 public function getRecentHistory(StationStream $stream, $num_entries = 5)
 {
     $em = self::getEntityManager();
     $history = $em->createQuery('SELECT sh, s FROM Entity\\SongHistory sh JOIN sh.song s WHERE sh.station_id = :station_id AND sh.stream_id = :stream_id ORDER BY sh.id DESC')->setParameter('station_id', $this->id)->setParameter('stream_id', $stream->id)->setMaxResults($num_entries)->getArrayResult();
     $return = array();
     foreach ($history as $sh) {
         $history = array('played_at' => $sh['timestamp'], 'song' => Song::api($sh['song']));
         $return[] = $history;
     }
     return $return;
 }
Пример #13
0
 public static function sync()
 {
     if (DF_APPLICATION_ENV !== 'production') {
         return false;
     }
     $db = self::getDatabase();
     $em = self::getEntityManager();
     $settings = self::getSettings();
     // Force correct account settings (enable global unified request system).
     $account_values = array('allowrequests' => '1', 'autoqueuerequests' => '1', 'requestprobability' => '50', 'requestdelay' => '0', 'emailunknownrequests' => '0');
     $db->update('accounts', $account_values, array('expectedstate' => 'up'));
     // Clear out old logs.
     $threshold = strtotime('-1 month');
     $threshold_date = date('Y-m-d', $threshold) . ' 00:00:00';
     $db->executeQuery('DELETE FROM playbackstats_tracks WHERE endtime <= ?', array($threshold_date));
     $db->executeQuery('DELETE FROM visitorstats_sessions WHERE endtime <= ?', array($threshold_date));
     // Delete old requests still listed as pending.
     $requesttime = new \DateTime('NOW');
     $requesttime->modify('-3 hours');
     $requesttime->setTimezone(new \DateTimeZone($settings['timezone']));
     $threshold_requests = $requesttime->format('Y-m-d h:i:s');
     $db->executeQuery('DELETE FROM playlist_tracks_requests WHERE requesttime <= ?', array($threshold_requests));
     // Force playlist enabling for existing pending requests.
     $request_playlists_raw = $db->fetchAll('SELECT DISTINCT ptr.playlistid AS pid FROM playlist_tracks_requests AS ptr');
     foreach ($request_playlists_raw as $pl) {
         $pl_id = $pl['pid'];
         $db->update('playlists', array('status' => 'enabled'), array('id' => $pl_id));
     }
     // Preload all station media locally.
     $stations = $em->createQuery('SELECT s FROM Entity\\Station s WHERE s.requests_enabled = 1')->execute();
     foreach ($stations as $station) {
         $account_id = self::getStationID($station);
         if (!$account_id) {
             continue;
         }
         // Clear existing items.
         $existing_ids_raw = $em->createQuery('SELECT sm FROM Entity\\StationMedia sm WHERE sm.station_id = :station_id')->setParameter('station_id', $station->id)->execute();
         $existing_records = array();
         foreach ($existing_ids_raw as $row) {
             $existing_records[$row['id']] = $row;
         }
         // Find all tracks in active playlists.
         $new_records_raw = self::fetchTracks($station);
         $records_by_hash = array();
         foreach ($new_records_raw as $track_info) {
             if ($track_info['length'] < 60) {
                 continue;
             }
             if ($track_info['playlist_status'] !== 'enabled') {
                 continue;
             }
             $row = array('id' => $track_info['id'], 'title' => $track_info['title'], 'artist' => $track_info['artist'], 'album' => $track_info['album'], 'length' => $track_info['length']);
             $song_hash = Song::getSongHash(array('text' => $row['artist'] . ' - ' . $row['title'], 'artist' => $row['artist'], 'title' => $row['title']));
             $records_by_hash[$song_hash] = $row;
         }
         $new_records = array();
         foreach ($records_by_hash as $row) {
             $new_records[$row['id']] = $row;
         }
         // Reconcile differences.
         $existing_guids = array_keys($existing_records);
         $new_guids = array_keys($new_records);
         $guids_to_delete = array_diff($existing_guids, $new_guids);
         if ($guids_to_delete) {
             foreach ($guids_to_delete as $guid) {
                 $record = $existing_records[$guid];
                 $em->remove($record);
             }
         }
         $guids_to_add = array_diff($new_guids, $existing_guids);
         if ($guids_to_add) {
             foreach ($guids_to_add as $guid) {
                 $record = new StationMedia();
                 $record->station = $station;
                 $record->fromArray($new_records[$guid]);
                 $em->persist($record);
             }
         }
         $em->flush();
     }
     return true;
 }
Пример #14
0
 public function songconfirmAction()
 {
     // Handle files submitted directly to page.
     $ignore_files = (int) $this->getParam('ignore_files');
     $request = $this->di->get('request');
     if ($request->hasFiles() && !$ignore_files) {
         $this->_processSongUpload();
     }
     // Validate song identifier token.
     $token = $this->_getSongHashToken();
     if (!$this->_validateSongHash($token)) {
         return $this->redirectFromHere(array('action' => 'song'));
     }
     // Check that any stations were selected
     if (!$this->hasParam('stations')) {
         throw new \DF\Exception\DisplayOnly('You did not specify any stations!');
     }
     // Check for uploaded songs.
     $temp_dir_name = 'song_uploads';
     $temp_dir = DF_INCLUDE_TEMP . DIRECTORY_SEPARATOR . $temp_dir_name;
     $all_files = glob($temp_dir . DIRECTORY_SEPARATOR . $token . '*.mp3');
     if (empty($all_files)) {
         throw new \DF\Exception\DisplayOnly('No files were uploaded!');
     }
     $songs = array();
     $getId3 = new GetId3();
     $getId3->encoding = 'UTF-8';
     foreach ($all_files as $song_file_base) {
         $song_file_path = $temp_dir . DIRECTORY_SEPARATOR . basename($song_file_base);
         // Attempt to analyze the MP3.
         $audio = $getId3->analyze($song_file_path);
         if (isset($audio['error'])) {
             @unlink($song_file_path);
             throw new \DF\Exception\DisplayOnly(sprintf('Error at reading audio properties with GetId3: %s.', $audio['error'][0]));
         }
         if (isset($audio['tags']['id3v1']['title'])) {
             $song_data = array('title' => $audio['tags']['id3v1']['title'][0], 'artist' => $audio['tags']['id3v1']['artist'][0]);
         } elseif (isset($audio['tags']['id3v2']['title'])) {
             $song_data = array('title' => $audio['tags']['id3v2']['title'][0], 'artist' => $audio['tags']['id3v2']['artist'][0]);
         } else {
             @unlink($song_file_path);
             continue;
         }
         // Check if existing submission exists.
         $song = Song::getOrCreate($song_data);
         $existing_submission = SongSubmission::getRepository()->findOneBy(array('hash' => $song->id));
         if ($existing_submission instanceof SongSubmission) {
             @unlink($song_file_path);
             continue;
         }
         // Create record in database.
         $metadata = array('File Format' => strtoupper($audio['fileformat']), 'Play Time' => $audio['playtime_string'], 'Bitrate' => round($audio['audio']['bitrate'] / 1024) . 'kbps', 'Bitrate Mode' => strtoupper($audio['audio']['bitrate_mode']), 'Channels' => $audio['audio']['channels'], 'Sample Rate' => $audio['audio']['sample_rate']);
         $record = new SongSubmission();
         $record->song = $song;
         $auth = $this->di->get('auth');
         $record->user = $auth->getLoggedInUser();
         $record->title = $song_data['title'];
         $record->artist = $song_data['artist'];
         $record->song_metadata = $metadata;
         $record->stations = $this->getParam('stations');
         $song_download_url = $record->uploadSong($song_file_path);
         $record->save();
         // Append information to e-mail to stations.
         $song_row = array('Download URL' => '<a href="' . $song_download_url . '" target="_blank">' . $song_download_url . '</a>', 'Title' => $song_data['title'], 'Artist' => $song_data['artist']) + $metadata;
         $songs[] = $song_row;
     }
     if (!empty($songs)) {
         // Notify all existing managers.
         $network_administrators = Action::getUsersWithAction('administer all');
         $email_to = Utilities::ipull($network_administrators, 'email');
         // Pull list of station managers for the specified stations.
         $station_managers = array();
         $short_names = Station::getShortNameLookup();
         foreach ($this->getParam('stations') as $station_key) {
             if (isset($short_names[$station_key])) {
                 $station_id = $short_names[$station_key]['id'];
                 $station = Station::find($station_id);
                 foreach ($station->managers as $manager) {
                     $station_managers[] = $manager->email;
                 }
             }
         }
         $email_to = array_merge($email_to, $station_managers);
         // Trigger e-mail notice.
         if (!empty($email_to)) {
             \DF\Messenger::send(array('to' => $email_to, 'subject' => 'New Song(s) Submitted to Station', 'template' => 'newsong', 'vars' => array('songs' => $songs)));
         }
     }
     // Have to manually call view because a view was already rendered (e-mail was sent).
     // TODO: Fix this. It's dumb.
     $this->view->songs = $songs;
     return $this->view->render('submit', 'songconfirm');
 }