id = intval($id); if ($info = $this->_get_info($limit_threshold)) { foreach ($info as $key => $value) { $this->$key = $value; } $data = pathinfo($this->file); $this->type = strtolower($data['extension']); $this->mime = self::type_to_mime($this->type); } else { $this->id = null; return false; } return true; } // constructor /** * insert * * This inserts the song described by the passed array * @param array $results * @return int|boolean */ public static function insert(array $results) { $catalog = $results['catalog']; $file = $results['file']; $title = trim($results['title']) ?: $file; $artist = $results['artist']; $album = $results['album']; $albumartist = $results['albumartist'] ?: $results['band']; $albumartist = $albumartist ?: null; $bitrate = $results['bitrate'] ?: 0; $rate = $results['rate'] ?: 0; $mode = $results['mode']; $size = $results['size'] ?: 0; $time = $results['time'] ?: 0; $track = $results['track']; $track_mbid = $results['mb_trackid'] ?: $results['mbid']; $track_mbid = $track_mbid ?: null; $album_mbid = $results['mb_albumid']; $album_mbid_group = $results['mb_albumid_group']; $artist_mbid = $results['mb_artistid']; $albumartist_mbid = $results['mb_albumartistid']; $disk = $results['disk'] ?: 0; $year = $results['year'] ?: 0; $comment = $results['comment']; $tags = $results['genre']; // multiple genre support makes this an array $lyrics = $results['lyrics']; $user_upload = isset($results['user_upload']) ? $results['user_upload'] : null; $license = isset($results['license']) ? $results['license'] : null; $composer = isset($results['composer']) ? $results['composer'] : null; $label = isset($results['publisher']) ? $results['publisher'] : null; $catalog_number = isset($results['catalog_number']) ? $results['catalog_number'] : null; $language = isset($results['language']) ? $results['language'] : null; $channels = $results['channels'] ?: 0; $release_type = isset($results['release_type']) ? $results['release_type'] : null; $replaygain_track_gain = isset($results['replaygain_track_gain']) ? $results['replaygain_track_gain'] : null; $replaygain_track_peak = isset($results['replaygain_track_peak']) ? $results['replaygain_track_peak'] : null; $replaygain_album_gain = isset($results['replaygain_album_gain']) ? $results['replaygain_album_gain'] : null; $replaygain_album_peak = isset($results['replaygain_album_peak']) ? $results['replaygain_album_peak'] : null; $albumartist_id = null; if (!isset($results['albumartist_id'])) { if ($albumartist) { $albumartist_id = Artist::check($albumartist, $albumartist_mbid); } } else { $albumartist_id = intval($results['albumartist_id']); } $artist_id = null; if (!isset($results['artist_id'])) { $artist_id = Artist::check($artist, $artist_mbid); } else { $artist_id = intval($results['artist_id']); } $album_id = null; if (!isset($results['album_id'])) { $album_id = Album::check($album, $year, $disk, $album_mbid, $album_mbid_group, $albumartist_id, $release_type); } else { $album_id = intval($results['album_id']); } $sql = 'INSERT INTO `song` (`file`, `catalog`, `album`, `artist`, ' . '`title`, `bitrate`, `rate`, `mode`, `size`, `time`, `track`, ' . '`addition_time`, `year`, `mbid`, `user_upload`, `license`, ' . '`composer`, `channels`) ' . 'VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; $db_results = Dba::write($sql, array( $file, $catalog, $album_id, $artist_id, $title, $bitrate, $rate, $mode, $size, $time, $track, time(), $year, $track_mbid, $user_upload, $license, $composer, $channels)); if (!$db_results) { debug_event('song', 'Unable to insert ' . $file, 2); return false; } $song_id = Dba::insert_id(); if (is_array($tags)) { // Allow scripts to populate new tags when injecting user uploads if (!defined('NO_SESSION')) { if ($user_upload && !Access::check('interface', 50, $user_upload)) { $tags = Tag::clean_to_existing($tags); } } foreach ($tags as $tag) { $tag = trim($tag); if (!empty($tag)) { Tag::add('song', $song_id, $tag, false); Tag::add('album', $album_id, $tag, false); Tag::add('artist', $artist_id, $tag, false); } } } $sql = 'INSERT INTO `song_data` (`song_id`, `comment`, `lyrics`, `label`, `language`, `catalog_number`, `replaygain_track_gain`, `replaygain_track_peak`, `replaygain_album_gain`, `replaygain_album_peak`) ' . 'VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; Dba::write($sql, array($song_id, $comment, $lyrics, $label, $language, $catalog_number, $replaygain_track_gain, $replaygain_track_peak, $replaygain_album_gain, $replaygain_album_peak)); return $song_id; } /** * gc * * Cleans up the song_data table */ public static function gc() { Dba::write('DELETE FROM `song_data` USING `song_data` LEFT JOIN `song` ON `song`.`id` = `song_data`.`song_id` WHERE `song`.`id` IS NULL'); } /** * build_cache * * This attempts to reduce queries by asking for everything in the * browse all at once and storing it in the cache, this can help if the * db connection is the slow point. * @param int[] $song_ids * @return boolean */ public static function build_cache($song_ids, $limit_threshold = '') { if (!is_array($song_ids) || !count($song_ids)) { return false; } $idlist = '(' . implode(',', $song_ids) . ')'; // Callers might have passed array(false) because they are dumb if ($idlist == '()') { return false; } // Song data cache $sql = 'SELECT `song`.`id`, `file`, `catalog`, `album`, ' . '`year`, `artist`, `title`, `bitrate`, `rate`, ' . '`mode`, `size`, `time`, `track`, `played`, ' . '`song`.`enabled`, `update_time`, `tag_map`.`tag_id`, '. '`mbid`, `addition_time`, `license`, `composer`, `user_upload` ' . 'FROM `song` LEFT JOIN `tag_map` ' . 'ON `tag_map`.`object_id`=`song`.`id` ' . "AND `tag_map`.`object_type`='song' "; if (AmpConfig::get('catalog_disable')) { $sql .= "LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog` "; } $sql .= "WHERE `song`.`id` IN $idlist "; if (AmpConfig::get('catalog_disable')) { $sql .= "AND `catalog`.`enabled` = '1' "; } $db_results = Dba::read($sql); $artists = array(); $albums = array(); $tags = array(); while ($row = Dba::fetch_assoc($db_results)) { if (AmpConfig::get('show_played_times')) { $row['object_cnt'] = Stats::get_object_count('song', $row['id'], $limit_threshold); } parent::add_to_cache('song', $row['id'], $row); $artists[$row['artist']] = $row['artist']; $albums[$row['album']] = $row['album']; if ($row['tag_id']) { $tags[$row['tag_id']] = $row['tag_id']; } } Artist::build_cache($artists); Album::build_cache($albums); Tag::build_cache($tags); Tag::build_map_cache('song', $song_ids); Art::build_cache($albums); // If we're rating this then cache them as well if (AmpConfig::get('ratings')) { Rating::build_cache('song', $song_ids); } if (AmpConfig::get('userflags')) { Userflag::build_cache('song', $song_ids); } // Build a cache for the song's extended table $sql = "SELECT * FROM `song_data` WHERE `song_id` IN $idlist"; $db_results = Dba::read($sql); while ($row = Dba::fetch_assoc($db_results)) { parent::add_to_cache('song_data', $row['song_id'], $row); } return true; } // build_cache /** * _get_info * @return array|boolean */ private function _get_info($limit_threshold = '') { $id = $this->id; if (parent::is_cached('song', $id)) { return parent::get_from_cache('song', $id); } $sql = 'SELECT `song`.`id`, `song`.`file`, `song`.`catalog`, `song`.`album`, `album`.`album_artist` AS `albumartist`, `song`.`year`, `song`.`artist`,' . '`song`.`title`, `song`.`bitrate`, `song`.`rate`, `song`.`mode`, `song`.`size`, `song`.`time`, `song`.`track`, ' . '`song`.`played`, `song`.`enabled`, `song`.`update_time`, `song`.`mbid`, `song`.`addition_time`, `song`.`license`, ' . '`song`.`composer`, `song`.`user_upload`, `album`.`mbid` AS `album_mbid`, `artist`.`mbid` AS `artist_mbid`, `album_artist`.`mbid` AS `albumartist_mbid` ' . 'FROM `song` LEFT JOIN `album` ON `album`.`id` = `song`.`album` LEFT JOIN `artist` ON `artist`.`id` = `song`.`artist` ' . 'LEFT JOIN `artist` AS `album_artist` ON `album_artist`.`id` = `album`.`album_artist` ' . 'WHERE `song`.`id` = ?'; $db_results = Dba::read($sql, array($id)); $results = Dba::fetch_assoc($db_results); if (isset($results['id'])) { if (AmpConfig::get('show_played_times')) { $results['object_cnt'] = Stats::get_object_count('song', $results['id'], $limit_threshold); } parent::add_to_cache('song', $id, $results); return $results; } return false; } /** * _get_ext_info * This function gathers information from the song_ext_info table and adds it to the * current object * @return array */ public function _get_ext_info() { $id = intval($this->id); if (parent::is_cached('song_data',$id)) { return parent::get_from_cache('song_data',$id); } $sql = "SELECT * FROM song_data WHERE `song_id` = ?"; $db_results = Dba::read($sql, array($id)); $results = Dba::fetch_assoc($db_results); parent::add_to_cache('song_data',$id,$results); return $results; } // _get_ext_info /** * fill_ext_info * This calls the _get_ext_info and then sets the correct vars */ public function fill_ext_info() { $info = $this->_get_ext_info(); foreach ($info as $key=>$value) { if ($key != 'song_id') { $this->$key = $value; } } // end foreach } // fill_ext_info /** * type_to_mime * * Returns the mime type for the specified file extension/type * @param string $type * @return string */ public static function type_to_mime($type) { // FIXME: This should really be done the other way around. // Store the mime type in the database, and provide a function // to make it a human-friendly type. switch ($type) { case 'spx': case 'ogg': return 'application/ogg'; case 'wma': case 'asf': return 'audio/x-ms-wma'; case 'mp3': case 'mpeg3': return 'audio/mpeg'; case 'rm': case 'ra': return 'audio/x-realaudio'; case 'flac'; return 'audio/x-flac'; case 'wv': return 'audio/x-wavpack'; case 'aac': case 'mp4': case 'm4a': return 'audio/mp4'; case 'aacp': return 'audio/aacp'; case 'mpc': return 'audio/x-musepack'; case 'mkv': return 'audio/x-matroska'; default: return 'audio/mpeg'; } } /** * get_disabled * * Gets a list of the disabled songs for and returns an array of Songs * @param int $count * @return int[] */ public static function get_disabled($count = 0) { $results = array(); $sql = "SELECT `id` FROM `song` WHERE `enabled`='0'"; if ($count) { $sql .= " LIMIT $count"; } $db_results = Dba::read($sql); while ($r = Dba::fetch_assoc($db_results)) { $results[] = new Song($r['id']); } return $results; } /** * find_duplicates * * This function takes a search type and returns a list of probable * duplicates * @param string $search_type * @return array */ public static function find_duplicates($search_type) { $where_sql = $_REQUEST['search_disabled'] ? '' : "WHERE `enabled` != '0'"; $sql = 'SELECT `id`, `artist`, `album`, `title`, ' . 'COUNT(`title`) FROM `song` ' . $where_sql . ' GROUP BY `title`'; if ($search_type == 'artist_title' || $search_type == 'artist_album_title') { $sql .= ',`artist`'; } if ($search_type == 'artist_album_title') { $sql .= ',`album`'; } $sql .= ' HAVING COUNT(`title`) > 1 ORDER BY `title`'; $db_results = Dba::read($sql); $results = array(); while ($item = Dba::fetch_assoc($db_results)) { $results[] = $item; } // end while return $results; } /** * Get duplicate information. * @param array $dupe * @param string $search_type * @return int[] */ public static function get_duplicate_info($dupe, $search_type) { $sql = 'SELECT `id` FROM `song` ' . "WHERE `title`='" . Dba::escape($dupe['title']) . "' "; if ($search_type == 'artist_title' || $search_type == 'artist_album_title') { $sql .= "AND `artist`='" . Dba::escape($dupe['artist']) . "' "; } if ($search_type == 'artist_album_title') { $sql .= "AND `album` = '" . Dba::escape($dupe['album']) . "' "; } $sql .= 'ORDER BY `time`,`bitrate`,`size`'; $db_results = Dba::read($sql); $results = array(); while ($item = Dba::fetch_assoc($db_results)) { $results[] = $item['id']; } // end while return $results; } /** * get_album_name * gets the name of $this->album, allows passing of id * @param int $album_id * @return string */ public function get_album_name($album_id=0) { if (!$album_id) { $album_id = $this->album; } $album = new Album($album_id); if ($album->prefix) return $album->prefix . " " . $album->name; else return $album->name; } // get_album_name /** * get_artist_name * gets the name of $this->artist, allows passing of id * @param int $artist_id * @return string */ public function get_artist_name($artist_id=0) { if (!$artist_id) { $artist_id = $this->artist; } $artist = new Artist($artist_id); if ($artist->prefix) return $artist->prefix . " " . $artist->name; else return $artist->name; } // get_artist_name /** * get_album_artist_name * gets the name of $this->albumartist, allows passing of id * @param int $album_artist_id * @return string */ public function get_album_artist_name($album_artist_id=0) { if (!$album_artist_id) { $album_artist_id = $this->albumartist; } $album_artist = new Artist($album_artist_id); if ($album_artist->prefix) return $album_artist->prefix . " " . $album_artist->name; else return $album_artist->name; } // get_album_artist_name /** * set_played * this checks to see if the current object has been played * if not then it sets it to played. In any case it updates stats. * @param int $user * @param string $agent * @param array $location * @return boolean */ public function set_played($user, $agent, $location) { Stats::insert('song', $this->id, $user, $agent, $location); Stats::insert('album', $this->album, $user, $agent, $location); Stats::insert('artist', $this->artist, $user, $agent, $location); if ($this->played) { return true; } /* If it hasn't been played, set it! */ self::update_played(true, $this->id); return true; } // set_played /** * compare_song_information * this compares the new ID3 tags of a file against * the ones in the database to see if they have changed * it returns false if nothing has changes, or the true * if they have. Static because it doesn't need this * @param \Song $song * @param \Song $new_song * @return array */ public static function compare_song_information(Song $song, Song $new_song) { // Remove some stuff we don't care about unset($song->catalog,$song->played,$song->enabled,$song->addition_time,$song->update_time,$song->type); $array = array(); $string_array = array('title','comment','lyrics'); $skip_array = array('id','tag_id','mime','artist_mbid','album_mbid','albumartist_mbid','albumartist','mbid','mb_albumid_group','waveform'); // Pull out all the currently set vars $fields = get_object_vars($song); // Foreach them foreach ($fields as $key=>$value) { if (in_array($key,$skip_array)) { continue; } // If it's a stringie thing if (in_array($key,$string_array)) { if (trim(stripslashes($song->$key)) != trim(stripslashes($new_song->$key))) { $array['change'] = true; $array['element'][$key] = 'OLD: ' . $song->$key . ' --> ' . $new_song->$key; } } // in array of stringies else { if ($song->$key != $new_song->$key) { $array['change'] = true; $array['element'][$key] = 'OLD:' . $song->$key . ' --> ' . $new_song->$key; } } // end else } // end foreach if ($array['change']) { debug_event('song-diff', json_encode($array['element']), 5); } return $array; } // compare_song_information /** * update * This takes a key'd array of data does any cleaning it needs to * do and then calls the helper functions as needed. * @param array $data * @return int */ public function update(array $data) { foreach ($data as $key=>$value) { debug_event('song.class.php', $key.'='.$value, '5'); switch ($key) { case 'artist_name': // Need to create new artist according the name $new_artist_id = Artist::check($value); $this->artist = $new_artist_id; self::update_artist($new_artist_id, $this->id); break; case 'album_name': // Need to create new album according the name $new_album_id = Album::check($value); $this->album = $new_album_id; self::update_album($new_album_id, $this->id); break; case 'year': case 'title': case 'track': case 'artist': case 'album': case 'mbid': case 'license': case 'composer': case 'label': case 'language': case 'comment': // Check to see if it needs to be updated if ($value != $this->$key) { $function = 'update_' . $key; self::$function($value, $this->id); $this->$key = $value; } break; case 'edit_tags': Tag::update_tag_list($value, 'song', $this->id, true); $this->tags = Tag::get_top_tags('song', $this->id); break; default: break; } // end whitelist } // end foreach $this->write_id3(); return $this->id; } // update /** * write_id3 * Write the current song id3 metadata to the file */ public function write_id3() { if (AmpConfig::get('write_id3')) { $catalog = Catalog::create_from_id($this->catalog); if ($catalog->get_type() == 'local') { debug_event('song', 'Writing id3 metadata to file ' . $this->file, 5); $meta = $this->get_metadata(); $id3 = new vainfo($this->file); $id3->write_id3($meta); } } } /** * write_id3_for_song * Write id3 metadata to the file for the excepted song id * @param int $song_id */ public static function write_id3_for_song($song_id) { $song = new Song($song_id); if ($song->id) { $song->format(); $song->write_id3(); } } /** * update_song * this is the main updater for a song it actually * calls a whole bunch of mini functions to update * each little part of the song... lastly it updates * the "update_time" of the song * @param int $song_id * @param \Song $new_song */ public static function update_song($song_id, Song $new_song) { $update_time = time(); $sql = "UPDATE `song` SET `album` = ?, `year` = ?, `artist` = ?, " . "`title` = ?, `bitrate` = ?, `rate` = ?, `mode` = ?, " . "`size` = ?, `time` = ?, `track` = ?, `mbid` = ?, " . "`update_time` = ? WHERE `id` = ?"; Dba::write($sql, array($new_song->album, $new_song->year, $new_song->artist, $new_song->title, $new_song->bitrate, $new_song->rate, $new_song->mode, $new_song->size, $new_song->time, $new_song->track, $new_song->mbid, $update_time, $song_id)); $sql = "UPDATE `song_data` SET `lyrics` = ?, `language` = ?, `comment` = ?, `replaygain_track_gain` = ?, `replaygain_track_peak` = ?, " . "`replaygain_album_gain` = ?, `replaygain_album_peak` = ? " . "WHERE `song_id` = ?"; Dba::write($sql, array($new_song->lyrics, $new_song->language, $new_song->comment, $new_song->replaygain_track_gain, $new_song->replaygain_track_peak, $new_song->replaygain_album_gain, $new_song->replaygain_album_peak, $song_id)); } // update_song /** * update_year * update the year tag * @param int $new_year * @param int $song_id */ public static function update_year($new_year, $song_id) { self::_update_item('year', $new_year, $song_id, 50, true); } // update_year /** * update_label * This updates the label tag of the song * @param string $new_value * @param int $song_id */ public static function update_label($new_value, $song_id) { self::_update_ext_item('label', $new_value, $song_id, 50, true); } // update_label /** * update_language * This updates the language tag of the song * @param string $new_lang * @param int $song_id */ public static function update_language($new_lang, $song_id) { self::_update_ext_item('language', $new_lang, $song_id, 50, true); } // update_language /** * update_comment * updates the comment field * @param string $new_comment * @param int $song_id */ public static function update_comment($new_comment, $song_id) { self::_update_ext_item('comment', $new_comment, $song_id, 50, true); } // update_comment /** * update_lyrics * updates the lyrics field * @param string $new_lyrics * @param int $song_id */ public static function update_lyrics($new_lyrics, $song_id) { self::_update_ext_item('lyrics', $new_lyrics, $song_id, 50, true); } // update_lyrics /** * update_title * updates the title field * @param string $new_title * @param int $song_id */ public static function update_title($new_title,$song_id) { self::_update_item('title', $new_title, $song_id, 50, true); } // update_title /** * update_composer * updates the composer field * @param string $new_value * @param int $song_id */ public static function update_composer($new_value, $song_id) { self::_update_item('composer', $new_value, $song_id, 50, true); } // update_composer /** * update_publisher * updates the publisher field * @param string $new_value * @param int $song_id */ public static function update_publisher($new_value, $song_id) { self::_update_item('publisher', $new_value, $song_id, 50, true); } // update_publisher /** * update_bitrate * updates the bitrate field * @param int $new_bitrate * @param int $song_id */ public static function update_bitrate($new_bitrate, $song_id) { self::_update_item('bitrate', $new_bitrate, $song_id, 50, true); } // update_bitrate /** * update_rate * updates the rate field * @param int $new_rate * @param int $song_id */ public static function update_rate($new_rate, $song_id) { self::_update_item('rate', $new_rate, $song_id, 50, true); } // update_rate /** * update_mode * updates the mode field * @param string $new_mode * @param int $song_id */ public static function update_mode($new_mode, $song_id) { self::_update_item('mode', $new_mode, $song_id, 50, true); } // update_mode /** * update_size * updates the size field * @param int $new_size * @param int $song_id */ public static function update_size($new_size, $song_id) { self::_update_item('size', $new_size, $song_id, 50); } // update_size /** * update_time * updates the time field * @param int $new_time * @param int $song_id */ public static function update_time($new_time, $song_id) { self::_update_item('time', $new_time, $song_id, 50, true); } // update_time /** * update_track * this updates the track field * @param int $new_track * @param int $song_id */ public static function update_track($new_track, $song_id) { self::_update_item('track', $new_track, $song_id, 50, true); } // update_track /** * update_mbid * updates mbid field * @param string $new_mbid * @param int $song_id */ public static function update_mbid($new_mbid, $song_id) { self::_update_item('mbid', $new_mbid, $song_id, 50); } // update_mbid /** * update_license * updates license field * @param string $new_license * @param int $song_id */ public static function update_license($new_license, $song_id) { self::_update_item('license', $new_license, $song_id, 50, true); } // update_license /** * update_artist * updates the artist field * @param int $new_artist * @param int $song_id */ public static function update_artist($new_artist, $song_id) { self::_update_item('artist', $new_artist, $song_id, 50); } // update_artist /** * update_album * updates the album field * @param int $new_album * @param int $song_id */ public static function update_album($new_album, $song_id) { self::_update_item('album', $new_album, $song_id, 50, true); } // update_album /** * update_utime * sets a new update time * @param int $song_id * @param int $time */ public static function update_utime($song_id, $time=0) { if (!$time) { $time = time(); } self::_update_item('update_time', $time, $song_id, 75, true); } // update_utime /** * update_played * sets the played flag * @param boolean $new_played * @param int $song_id */ public static function update_played($new_played, $song_id) { self::_update_item('played', ($new_played ? 1 : 0), $song_id, 25); } // update_played /** * update_enabled * sets the enabled flag * @param boolean $new_enabled * @param int $song_id */ public static function update_enabled($new_enabled, $song_id) { self::_update_item('enabled', ($new_enabled ? 1 : 0), $song_id, 75, true); } // update_enabled /** * _update_item * This is a private function that should only be called from within the song class. * It takes a field, value song id and level. first and foremost it checks the level * against $GLOBALS['user'] to make sure they are allowed to update this record * it then updates it and sets $this->{$field} to the new value * @param string $field * @param mixed $value * @param int $song_id * @param int $level * @param boolean $check_owner * @return boolean */ private static function _update_item($field, $value, $song_id, $level, $check_owner = false) { if ($check_owner) { $item = new Song($song_id); if ($item->id && $item->get_user_owner() == $GLOBALS['user']->id) { $level = 25; } } /* Check them Rights! */ if (!Access::check('interface', $level)) { return false; } /* Can't update to blank */ if (!strlen(trim($value)) && $field != 'comment') { return false; } $sql = "UPDATE `song` SET `$field` = ? WHERE `id` = ?"; return Dba::write($sql, array($value, $song_id)); } // _update_item /** * _update_ext_item * This updates a song record that is housed in the song_ext_info table * These are items that aren't used normally, and often large/informational only * @param string $field * @param mixed $value * @param int $song_id * @param int $level * @param boolean $check_owner * @return boolean */ private static function _update_ext_item($field, $value, $song_id, $level, $check_owner = false) { if ($check_owner) { $item = new Song($song_id); if ($item->id && $item->get_user_owner() == $GLOBALS['user']->id) { $level = 25; } } /* Check them rights boy! */ if (!Access::check('interface',$level)) { return false; } $sql = "UPDATE `song_data` SET `$field` = ? WHERE `song_id` = ?"; return Dba::write($sql, array($value, $song_id)); } // _update_ext_item /** * format * This takes the current song object * and does a ton of formating on it creating f_??? variables on the current * object */ public function format($details = true) { if ($details) { $this->fill_ext_info(); // Get the top tags $this->tags = Tag::get_top_tags('song', $this->id); $this->f_tags = Tag::get_display($this->tags, true, 'song'); } // Format the album name $this->f_album_full = $this->get_album_name(); $this->f_album = $this->f_album_full; // Format the artist name $this->f_artist_full = $this->get_artist_name(); $this->f_artist = $this->f_artist_full; // Format the album_artist name $this->f_albumartist_full = $this->get_album_artist_name(); // Format the title $this->f_title_full = $this->title; $this->f_title = $this->title; // Create Links for the different objects $this->link = AmpConfig::get('web_path') . "/song.php?action=show_song&song_id=" . $this->id; $this->f_link = "link) . "\" title=\"" . scrub_out($this->f_artist) . " - " . scrub_out($this->title) . "\"> " . scrub_out($this->f_title) . ""; $this->f_album_link = "album . "\" title=\"" . scrub_out($this->f_album_full) . "\"> " . scrub_out($this->f_album) . ""; $this->f_artist_link = "artist . "\" title=\"" . scrub_out($this->f_artist_full) . "\"> " . scrub_out($this->f_artist) . ""; if (!empty($this->albumartist)) { $this->f_albumartist_link = "albumartist . "\" title=\"" . scrub_out($this->f_albumartist_full) . "\"> " . scrub_out($this->f_albumartist_full) . ""; } // Format the Bitrate $this->f_bitrate = intval($this->bitrate/1000) . "-" . strtoupper($this->mode); // Format the Time $min = floor($this->time/60); $sec = sprintf("%02d", ($this->time%60)); $this->f_time = $min . ":" . $sec; $hour = sprintf("%02d", floor($min/60)); $min_h = sprintf("%02d", ($min%60)); $this->f_time_h = $hour . ":" . $min_h . ":" . $sec; // Format the track (there isn't really anything to do here) $this->f_track = (string) $this->track; // Format the size $this->f_size = UI::format_bytes($this->size); $this->f_lyrics = "title) . "\" href=\"" . AmpConfig::get('web_path') . "/song.php?action=show_lyrics&song_id=" . $this->id . "\">" . T_('Show Lyrics') . ""; $this->f_file = $this->f_artist . ' - '; if ($this->track) { $this->f_file .= $this->track . ' - '; } $this->f_file .= $this->f_title . '.' . $this->type; $this->f_publisher = $this->label; $this->f_composer = $this->composer; } // format /** * Get item keywords for metadata searches. * @return array */ public function get_keywords() { $keywords = array(); $keywords['mb_trackid'] = array('important' => false, 'label' => T_('Track MusicBrainzID'), 'value' => $this->mbid); $keywords['artist'] = array('important' => true, 'label' => T_('Artist'), 'value' => $this->f_artist); $keywords['title'] = array('important' => true, 'label' => T_('Title'), 'value' => $this->f_title); return $keywords; } /** * Get item fullname. * @return string */ public function get_fullname() { return $this->f_title; } /** * Get parent item description. * @return array|null */ public function get_parent() { return array('object_type' => 'album', 'object_id' => $this->album); } /** * Get item childrens. * @return array */ public function get_childrens() { return array(); } /** * Search for item childrens. * @param string $name * @return array */ public function search_childrens($name) { return array(); } /** * Get all childrens and sub-childrens medias. * @param string $filter_type * @return array */ public function get_medias($filter_type = null) { $medias = array(); if (!$filter_type || $filter_type == 'song') { $medias[] = array( 'object_type' => 'song', 'object_id' => $this->id ); } return $medias; } /** * get_catalogs * * Get all catalog ids related to this item. * @return int[] */ public function get_catalogs() { return array($this->catalog); } /** * Get item's owner. * @return int|null */ public function get_user_owner() { if ($this->user_upload) { return $this->user_upload; } return null; } /** * Get default art kind for this item. * @return string */ public function get_default_art_kind() { return 'default'; } public function get_description() { if (!empty($this->comment)) return $this->comment; $album = new Album($this->album); $album->format(); return $album->get_description(); } public function display_art($thumb = 2) { $id = null; $type = null; if (Art::has_db($this->id, 'song')) { $id = $this->id; $type = 'song'; } else if (Art::has_db($this->album, 'album')) { $id = $this->album; $type = 'album'; } else if (Art::has_db($this->artist, 'artist')) { $id = $this->artist; $type = 'artist'; } if ($id !== null && $type !== null) { Art::display($type, $id, $this->get_fullname(), $thumb, $this->link); } } /** * get_fields * This returns all of the 'data' fields for this object, we need to filter out some that we don't * want to present to a user, and add some that don't exist directly on the object but are related * @return array */ public static function get_fields() { $fields = get_class_vars('Song'); unset($fields['id'],$fields['_transcoded'],$fields['_fake'],$fields['cache_hit'],$fields['mime'],$fields['type']); // Some additional fields $fields['tag'] = true; $fields['catalog'] = true; //FIXME: These are here to keep the ideas, don't want to have to worry about them for now // $fields['rating'] = true; // $fields['recently Played'] = true; return $fields; } // get_fields /** * get_from_path * This returns all of the songs that exist under the specified path * @param string $path * @return int[] */ public static function get_from_path($path) { $path = Dba::escape($path); $sql = "SELECT * FROM `song` WHERE `file` LIKE '$path%'"; $db_results = Dba::read($sql); $songs = array(); while ($row = Dba::fetch_assoc($db_results)) { $songs[] = $row['id']; } return $songs; } // get_from_path /** * @function get_rel_path * @discussion returns the path of the song file stripped of the catalog path * used for mpd playback * @param string $file_path * @param int $catalog_id * @return string */ public function get_rel_path($file_path=null,$catalog_id=0) { $info = null; if (!$file_path) { $info = $this->_get_info(); $file_path = $info['file']; } if (!$catalog_id) { if (!is_array($info)) { $info = $this->_get_info(); } $catalog_id = $info['catalog']; } $catalog = Catalog::create_from_id( $catalog_id ); return $catalog->get_rel_path($file_path); } // get_rel_path /** * Generate generic play url. * @param string $object_type * @param int $object_id * @param string $additional_params * @param boolean $local * @return string */ public static function generic_play_url($object_type, $object_id, $additional_params, $player=null, $local=false) { $media = new $object_type($object_id); if (!$media->id) return null; $uid = $GLOBALS['user']->id ? scrub_out($GLOBALS['user']->id) : '-1'; $type = $media->type; // Checking if the media is gonna be transcoded into another type // Some players doesn't allow a type streamed into another without giving the right extension $transcode_cfg = AmpConfig::get('transcode'); $valid_types = Song::get_stream_types_for_type($type, $player); if ($transcode_cfg == 'always' || ($transcode_cfg != 'never' && !in_array('native', $valid_types))) { $transcode_settings = $media->get_transcode_settings(null); if ($transcode_settings) { debug_event("media", "Changing play url type from {".$type."} to {".$transcode_settings['format']."} due to encoding settings...", 5); $type = $transcode_settings['format']; } } $media_name = $media->get_stream_name() . "." . $type; $media_name = str_replace("/", "-", $media_name); $media_name = str_replace("?", "", $media_name); $media_name = str_replace("#", "", $media_name); $media_name = rawurlencode($media_name); $url = Stream::get_base_url($local) . "type=" . $object_type . "&oid=" . $object_id . "&uid=" . $uid . $additional_params; if ($player) { $url .= "&player=" . $player; } $url .= "&name=" . $media_name; return Stream_URL::format($url); } /** * play_url * This function takes all the song information and correctly formats a * a stream URL taking into account the downsmapling mojo and everything * else, this is the true function * @param int $oid * @param string $additional_params * @param boolean $local * @return string */ public static function play_url($oid, $additional_params='', $player=null, $local=false) { return self::generic_play_url('song', $oid, $additional_params, $player, $local); } /** * Get stream name. * @return string */ public function get_stream_name() { return $this->get_artist_name() . " - " . $this->title; } /** * get_recently_played * This function returns the last X songs that have been played * it uses the popular threshold to figure out how many to pull * it will only return unique object * @param int $user_id * @return array */ public static function get_recently_played($user_id=0) { $user_id = intval($user_id); $sql = "SELECT `object_id`, `user`, `object_type`, `date`, `agent`, `geo_latitude`, `geo_longitude`, `geo_name` " . "FROM `object_count` WHERE `object_type` = 'song' AND `count_type` = 'stream' "; if (AmpConfig::get('catalog_disable')) { $sql .= "AND " . Catalog::get_enable_filter('song', '`object_id`') . " "; } if ($user_id) { // If user is not empty, we're looking directly to user personal info (admin view) $sql .= "AND `user`='$user_id' "; } else if (!Access::check('interface','100')) { // If user identifier is empty, we need to retrieve only users which have allowed view of personnal info $personal_info_id = Preference::id_from_name('allow_personal_info_recent'); if ($personal_info_id) { $current_user = $GLOBALS['user']->id; $sql .= "AND `user` IN (SELECT `user` FROM `user_preference` WHERE (`preference`='$personal_info_id' AND `value`='1') OR `user`='$current_user') "; } } $sql .= "ORDER BY `date` DESC "; $db_results = Dba::read($sql); $results = array(); while ($row = Dba::fetch_assoc($db_results)) { if (empty($row['geo_name']) && $row['latitude'] && $row['longitude']) { $row['geo_name'] = Stats::get_cached_place_name($row['latitude'], $row['longitude']); } $results[] = $row; if (count($results) >= AmpConfig::get('popular_threshold')) { break; } } return $results; } // get_recently_played /** * Get stream types. * @return array */ public function get_stream_types($player = null) { return Song::get_stream_types_for_type($this->type, $player); } /** * Get stream types for media type. * @param string $type * @return string */ public static function get_stream_types_for_type($type, $player = null) { $types = array(); $transcode = AmpConfig::get('transcode_' . $type); if ($player) { $player_transcode = AmpConfig::get('transcode_player_' . $player . '_' . $type); if ($player_transcode) { $transcode = $player_transcode; } } if ($transcode != 'required') { $types[] = 'native'; } if (make_bool($transcode)) { $types[] = 'transcode'; } return $types; } /** * Get transcode settings for media. * @param string $source * @param string $target * @param string $media_type * @param array $options * @return array|boolean */ public static function get_transcode_settings_for_media($source, $target = null, $player = null, $media_type = 'song', $options=array()) { $setting_target = 'encode_target'; if ($media_type != 'song') { $setting_target = 'encode_' . $media_type . '_target'; } if ($player) { $player_setting_target = 'encode_player_' . $player . '_target'; if ($media_type != 'song') { $player_setting_target = 'encode_' . $media_type . '_player_' . $player . '_target'; } if (AmpConfig::get($player_setting_target)) { $setting_target = $player_setting_target; } } if ($target) { debug_event('media', 'Explicit format request {' . $target . '}', 5); } else if ($target = AmpConfig::get('encode_target_' . $source)) { debug_event('media', 'Defaulting to configured target format for ' . $source, 5); } else if ($target = AmpConfig::get($setting_target)) { debug_event('media', 'Using default target format', 5); } else { $target = $source; debug_event('media', 'No default target for ' . $source . ', choosing to resample', 5); } debug_event('media', 'Transcode settings: from ' . $source . ' to ' . $target, 5); $cmd = AmpConfig::get('transcode_cmd_' . $source) ?: AmpConfig::get('transcode_cmd'); $args = ''; if (AmpConfig::get('encode_ss_frame') && isset($options['frame'])) { $args .= ' ' . AmpConfig::get('encode_ss_frame'); } if (AmpConfig::get('encode_ss_duration') && isset($options['duration'])) { $args .= ' ' . AmpConfig::get('encode_ss_duration'); } $args .= ' ' . AmpConfig::get('transcode_input'); if (AmpConfig::get('encode_srt') && $options['subtitle']) { debug_event('media', 'Using subtitle ' . $options['subtitle'], 5); $args .= ' ' . AmpConfig::get('encode_srt'); } $argst = AmpConfig::get('encode_args_' . $target); if (!$args) { debug_event('media', 'Target format ' . $target . ' is not properly configured', 2); return false; } $args .= ' ' . $argst; debug_event('media', 'Command: ' . $cmd . ' Arguments:' . $args, 5); return array('format' => $target, 'command' => $cmd . $args); } /** * Get transcode settings. * @param string $target * @param array $options * @return array|boolean */ public function get_transcode_settings($target = null, $player = null, $options=array()) { return Song::get_transcode_settings_for_media($this->type, $target, $player, 'song', $options); } /** * Get lyrics. * @return array */ public function get_lyrics() { if ($this->lyrics) { return array('text' => $this->lyrics); } foreach (Plugin::get_plugins('get_lyrics') as $plugin_name) { $plugin = new Plugin($plugin_name); if ($plugin->load($GLOBALS['user'])) { $lyrics = $plugin->_plugin->get_lyrics($this); if ($lyrics != false) { return $lyrics; } } } return null; } /** * Run custom play action. * @param int $action_index * @param string $codec * @return array */ public function run_custom_play_action($action_index, $codec='') { $transcoder = array(); $actions = Song::get_custom_play_actions(); if ($action_index <= count($actions)) { $action = $actions[$action_index - 1]; if (!$codec) { $codec = $this->type; } $run = str_replace("%f", $this->file, $action['run']); $run = str_replace("%c", $codec, $run); $run = str_replace("%a", $this->f_artist, $run); $run = str_replace("%A", $this->f_album, $run); $run = str_replace("%t", $this->f_title, $run); debug_event('song', "Running custom play action: " . $run, 3); $descriptors = array(1 => array('pipe', 'w')); if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { // Windows doesn't like to provide stderr as a pipe $descriptors[2] = array('pipe', 'w'); } $process = proc_open($run, $descriptors, $pipes); $transcoder['process'] = $process; $transcoder['handle'] = $pipes[1]; $transcoder['stderr'] = $pipes[2]; $transcoder['format'] = $codec; } return $transcoder; } /** * Show custom play actions. */ public function show_custom_play_actions() { $actions = Song::get_custom_play_actions(); foreach ($actions as $action) { echo Ajax::button('?page=stream&action=directplay&object_type=song&object_id=' . $this->id . '&custom_play_action=' . $action['index'], $action['icon'], T_($action['title']), $action['icon'] . '_song_' . $this->id); } } /** * Get custom play actions. * @return array */ public static function get_custom_play_actions() { $actions = array(); $i = 0; while (AmpConfig::get('custom_play_action_title_' . $i)) { $actions[] = array( 'index' => ($i + 1), 'title' => AmpConfig::get('custom_play_action_title_' . $i), 'icon' => AmpConfig::get('custom_play_action_icon_' . $i), 'run' => AmpConfig::get('custom_play_action_run_' . $i), ); ++$i; } return $actions; } /* * get_metadata * Get an array of song metadata * @return array */ public function get_metadata() { $meta = array(); $meta['file'] = $this->file; $meta['bitrate'] = $this->bitrate; $meta['rate'] = $this->rate; $meta['mode'] = $this->mode; $meta['year'] = $this->year; $meta['size'] = $this->size; $meta['time'] = $this->time; $meta['mime'] = $this->mime; $meta['title'] = $this->title; $meta['album'] = $this->f_album_full; $meta['artist'] = $this->f_artist_full; $meta['band'] = $meta['albumartist'] = $this->f_albumartist_full; $meta['mb_trackid'] = $this->mbid; $meta['mb_albumid'] = $this->album_mbid; $meta['mb_artistid'] = $this->artist_mbid; $meta['mb_albumartistid'] = $this->albumartist_mbid; $meta['tracknumber'] = $meta['track'] = $this->track; $meta['replaygain_track_gain'] = $this->replaygain_track_gain; $meta['replaygain_track_peak'] = $this->replaygain_track_peak; $meta['replaygain_album_gain'] = $this->replaygain_album_gain; $meta['replaygain_album_peak'] = $this->replaygain_album_peak; $meta['genre'] = array(); if ($this->tags) { foreach ($this->tags as $tag) { if (!in_array($tag['name'], $meta['genre'])) { $meta['genre'][] = $tag['name']; } } } $meta['genre'] = implode(',', $meta['genre']); return $meta; } /** * Remove the song from disk. */ public function remove_from_disk() { if (file_exists($this->file)) { $deleted = unlink($this->file); } else { $deleted = true; } if ($deleted === true) { $sql = "DELETE FROM `song` WHERE `id` = ?"; $deleted = Dba::write($sql, array($this->id)); if ($deleted) { Art::gc('song', $this->id); Userflag::gc('song', $this->id); Rating::gc('song', $this->id); Shoutbox::gc('song', $this->id); } } else { debug_event('song', 'Cannot delete ' . $this->file . 'file. Please check permissions.', 1); } return $deleted; } } // end of song class