mirror of
https://github.com/Yetangitu/ampache
synced 2025-10-03 09:49:30 +02:00
2029 lines
64 KiB
PHP
2029 lines
64 KiB
PHP
<?php
|
|
/* vim:set softtabstop=4 shiftwidth=4 expandtab: */
|
|
/**
|
|
*
|
|
* LICENSE: GNU General Public License, version 2 (GPLv2)
|
|
* Copyright 2001 - 2015 Ampache.org
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License v2
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* Catalog Class
|
|
*
|
|
* This class handles all actual work in regards to the catalog,
|
|
* it contains functions for creating/listing/updated the catalogs.
|
|
*
|
|
*/
|
|
abstract class Catalog extends database_object
|
|
{
|
|
/**
|
|
* @var int $id
|
|
*/
|
|
public $id;
|
|
/**
|
|
* @var string $name
|
|
*/
|
|
public $name;
|
|
/**
|
|
* @var int $last_update
|
|
*/
|
|
public $last_update;
|
|
/**
|
|
* @var int $last_add
|
|
*/
|
|
public $last_add;
|
|
/**
|
|
* @var int $last_clean
|
|
*/
|
|
public $last_clean;
|
|
/**
|
|
* @var string $key
|
|
*/
|
|
public $key;
|
|
/**
|
|
* @var string $rename_pattern
|
|
*/
|
|
public $rename_pattern;
|
|
/**
|
|
* @var string $sort_pattern
|
|
*/
|
|
public $sort_pattern;
|
|
/**
|
|
* @var string $catalog_type
|
|
*/
|
|
public $catalog_type;
|
|
/**
|
|
* @var string $gather_types
|
|
*/
|
|
public $gather_types;
|
|
|
|
/**
|
|
* @var string $f_name
|
|
*/
|
|
public $f_name;
|
|
/**
|
|
* @var string $link
|
|
*/
|
|
public $link;
|
|
/**
|
|
* @var string $f_link
|
|
*/
|
|
public $f_link;
|
|
/**
|
|
* @var string $f_update
|
|
*/
|
|
public $f_update;
|
|
/**
|
|
* @var string $f_add
|
|
*/
|
|
public $f_add;
|
|
/**
|
|
* @var string $f_clean
|
|
*/
|
|
public $f_clean;
|
|
|
|
/*
|
|
* This is a private var that's used during catalog builds
|
|
* @var array $_playlists
|
|
*/
|
|
protected $_playlists = array();
|
|
|
|
/*
|
|
* Cache all files in catalog for quick lookup during add
|
|
* @var array $_filecache
|
|
*/
|
|
protected $_filecache = array();
|
|
|
|
// Used in functions
|
|
/**
|
|
* @var array $albums
|
|
*/
|
|
protected static $albums = array();
|
|
/**
|
|
* @var array $artists
|
|
*/
|
|
protected static $artists = array();
|
|
/**
|
|
* @var array $tags
|
|
*/
|
|
protected static $tags = array();
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
abstract public function get_type();
|
|
/**
|
|
* @return string
|
|
*/
|
|
abstract public function get_description();
|
|
/**
|
|
* @return string
|
|
*/
|
|
abstract public function get_version();
|
|
/**
|
|
* @return string
|
|
*/
|
|
abstract public function get_create_help();
|
|
/**
|
|
* @return boolean
|
|
*/
|
|
abstract public function is_installed();
|
|
/**
|
|
* @return boolean
|
|
*/
|
|
abstract public function install();
|
|
abstract public function add_to_catalog($options = null);
|
|
abstract public function verify_catalog_proc();
|
|
abstract public function clean_catalog_proc();
|
|
/**
|
|
* @return array
|
|
*/
|
|
abstract public function catalog_fields();
|
|
/**
|
|
* @return string
|
|
*/
|
|
abstract public function get_rel_path($file_path);
|
|
/**
|
|
* @return media|null
|
|
*/
|
|
abstract public function prepare_media($media);
|
|
|
|
/**
|
|
* Check if the catalog is ready to perform actions (configuration completed, ...)
|
|
* @return boolean
|
|
*/
|
|
public function isReady()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Show a message to make the catalog ready.
|
|
*/
|
|
public function show_ready_process()
|
|
{
|
|
// Do nothing.
|
|
}
|
|
|
|
/**
|
|
* Perform the last step process to make the catalog ready.
|
|
*/
|
|
public function perform_ready()
|
|
{
|
|
// Do nothing.
|
|
}
|
|
|
|
/**
|
|
* uninstall
|
|
* This removes the remote catalog
|
|
* @return boolean
|
|
*/
|
|
public function uninstall()
|
|
{
|
|
$sql = "DELETE FROM `catalog` WHERE `catalog_type` = ?";
|
|
Dba::query($sql, array($this->get_type()));
|
|
|
|
$sql = "DROP TABLE `catalog_" . $this->get_type() ."`";
|
|
Dba::query($sql);
|
|
|
|
return true;
|
|
} // uninstall
|
|
|
|
/**
|
|
* Create a catalog from its id.
|
|
* @param int $id
|
|
* @return Catalog|null
|
|
*/
|
|
public static function create_from_id($id)
|
|
{
|
|
$sql = 'SELECT `catalog_type` FROM `catalog` WHERE `id` = ?';
|
|
$db_results = Dba::read($sql, array($id));
|
|
if ($results = Dba::fetch_assoc($db_results)) {
|
|
return self::create_catalog_type($results['catalog_type'], $id);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* create_catalog_type
|
|
* This function attempts to create a catalog type
|
|
* all Catalog modules should be located in /modules/catalog/<name>.class.php
|
|
* @param string $type
|
|
* @param int $id
|
|
* @return Catalog|null
|
|
*/
|
|
public static function create_catalog_type($type, $id=0)
|
|
{
|
|
if (!$type) {
|
|
return false;
|
|
}
|
|
|
|
$filename = AmpConfig::get('prefix') . '/modules/catalog/' . $type . '.catalog.php';
|
|
$include = require_once $filename;
|
|
|
|
if (!$include) {
|
|
/* Throw Error Here */
|
|
debug_event('catalog', 'Unable to load ' . $type . ' catalog type', '2');
|
|
return false;
|
|
} // include
|
|
else {
|
|
$class_name = "Catalog_" . $type;
|
|
if ($id > 0) {
|
|
$catalog = new $class_name($id);
|
|
} else {
|
|
$catalog = new $class_name();
|
|
}
|
|
if (!($catalog instanceof Catalog)) {
|
|
debug_event('catalog', $type . ' not an instance of Catalog abstract, unable to load', '1');
|
|
return false;
|
|
}
|
|
return $catalog;
|
|
}
|
|
} // create_catalog_type
|
|
|
|
/**
|
|
* Show dropdown catalog types.
|
|
* @param string $divback
|
|
*/
|
|
public static function show_catalog_types($divback = 'catalog_type_fields')
|
|
{
|
|
echo "<script language=\"javascript\" type=\"text/javascript\">" .
|
|
"var type_fields = new Array();" .
|
|
"type_fields['none'] = '';";
|
|
$seltypes = '<option value="none">[Select]</option>';
|
|
$types = self::get_catalog_types();
|
|
foreach ($types as $type) {
|
|
$catalog = self::create_catalog_type($type);
|
|
if ($catalog->is_installed()) {
|
|
$seltypes .= '<option value="' . $type . '">' . $type . '</option>';
|
|
echo "type_fields['" . $type . "'] = \"";
|
|
$fields = $catalog->catalog_fields();
|
|
$help = $catalog->get_create_help();
|
|
if (!empty($help)) {
|
|
echo "<tr><td></td><td>" . $help . "</td></tr>";
|
|
}
|
|
foreach ($fields as $key=>$field) {
|
|
echo "<tr><td style='width: 25%;'>" . $field['description'] . ":</td><td>";
|
|
|
|
switch ($field['type']) {
|
|
case 'checkbox':
|
|
echo "<input type='checkbox' name='" . $key . "' value='1' " . (($field['value']) ? 'checked' : '') . "/>";
|
|
break;
|
|
case 'password':
|
|
echo "<input type='password' name='" . $key . "' value='" . $field['value'] . "' />";
|
|
break;
|
|
default:
|
|
echo "<input type='text' name='" . $key . "' value='" . $field['value'] . "' />";
|
|
break;
|
|
}
|
|
echo "</td></tr>";
|
|
}
|
|
echo "\";";
|
|
}
|
|
}
|
|
|
|
echo "function catalogTypeChanged() {" .
|
|
"var sel = document.getElementById('catalog_type');" .
|
|
"var seltype = sel.options[sel.selectedIndex].value;" .
|
|
"var ftbl = document.getElementById('" . $divback . "');" .
|
|
"ftbl.innerHTML = '<table class=\"tabledata\" cellpadding=\"0\" cellspacing=\"0\">' + type_fields[seltype] + '</table>';" .
|
|
"} </script>" .
|
|
"<select name=\"type\" id=\"catalog_type\" onChange=\"catalogTypeChanged();\">" . $seltypes . "</select>";
|
|
}
|
|
|
|
/**
|
|
* get_catalog_types
|
|
* This returns the catalog types that are available
|
|
* @return string[]
|
|
*/
|
|
public static function get_catalog_types()
|
|
{
|
|
/* First open the dir */
|
|
$handle = opendir(AmpConfig::get('prefix') . '/modules/catalog');
|
|
|
|
if (!is_resource($handle)) {
|
|
debug_event('catalog', 'Error: Unable to read catalog types directory', '1');
|
|
return array();
|
|
}
|
|
|
|
$results = array();
|
|
|
|
while (false !== ($file = readdir($handle))) {
|
|
if (substr($file, -11, 11) != 'catalog.php') {
|
|
continue;
|
|
}
|
|
|
|
/* Make sure it isn't a dir */
|
|
if (!is_dir($file)) {
|
|
/* Get the basename and then everything before catalog */
|
|
$filename = basename($file, '.catalog.php');
|
|
$results[] = $filename;
|
|
}
|
|
} // end while
|
|
|
|
return $results;
|
|
} // get_catalog_types
|
|
|
|
/**
|
|
* Check if a file is an audio.
|
|
* @param string $file
|
|
* @return boolean
|
|
*/
|
|
public static function is_audio_file($file)
|
|
{
|
|
$pattern = "/\.(" . AmpConfig::get('catalog_file_pattern') . ")$/i";
|
|
$match = preg_match($pattern, $file);
|
|
|
|
return $match;
|
|
}
|
|
|
|
/**
|
|
* Check if a file is a video.
|
|
* @param string $file
|
|
* @return boolean
|
|
*/
|
|
public static function is_video_file($file)
|
|
{
|
|
$video_pattern = "/\.(" . AmpConfig::get('catalog_video_pattern') . ")$/i";
|
|
return preg_match($video_pattern, $file);
|
|
}
|
|
|
|
/**
|
|
* Check if a file is a playlist.
|
|
* @param string $file
|
|
* @return boolean
|
|
*/
|
|
public static function is_playlist_file($file)
|
|
{
|
|
$playlist_pattern = "/\.(" . AmpConfig::get('catalog_playlist_pattern') . ")$/i";
|
|
return preg_match($playlist_pattern, $file);
|
|
}
|
|
|
|
/**
|
|
* Get catalog info from table.
|
|
* @param int $id
|
|
* @param string $table
|
|
* @return array
|
|
*/
|
|
public function get_info($id, $table = 'catalog')
|
|
{
|
|
$info = parent::get_info($id, $table);
|
|
|
|
$table = 'catalog_' . $this->get_type();
|
|
$sql = "SELECT `id` FROM $table WHERE `catalog_id` = ?";
|
|
$db_results = Dba::read($sql, array($id));
|
|
|
|
if ($results = Dba::fetch_assoc($db_results)) {
|
|
$info_type = parent::get_info($results['id'], $table);
|
|
foreach ($info_type as $key => $value) {
|
|
if (!$info[$key]) {
|
|
$info[$key] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $info;
|
|
}
|
|
|
|
/**
|
|
* Get enable sql filter;
|
|
* @param string $type
|
|
* @param int $id
|
|
* @return string
|
|
*/
|
|
public static function get_enable_filter($type, $id)
|
|
{
|
|
$sql = "";
|
|
if ($type == "song" || $type == "album" || $type == "artist") {
|
|
if ($type == "song") {
|
|
$type = "id";
|
|
}
|
|
$sql = "(SELECT COUNT(`song_dis`.`id`) FROM `song` AS `song_dis` LEFT JOIN `catalog` AS `catalog_dis` ON `catalog_dis`.`id` = `song_dis`.`catalog` " .
|
|
"WHERE `song_dis`.`" . $type . "`=" . $id . " AND `catalog_dis`.`enabled` = '1' GROUP BY `song_dis`.`" . $type . "`) > 0";
|
|
} elseif ($type == "video") {
|
|
$sql = "(SELECT COUNT(`video_dis`.`id`) FROM `video` AS `video_dis` LEFT JOIN `catalog` AS `catalog_dis` ON `catalog_dis`.`id` = `video_dis`.`catalog` " .
|
|
"WHERE `video_dis`.`id`=" . $id . " AND `catalog_dis`.`enabled` = '1' GROUP BY `video_dis`.`id`) > 0";
|
|
}
|
|
|
|
return $sql;
|
|
}
|
|
|
|
/**
|
|
* _create_filecache
|
|
*
|
|
* This populates an array which is used to speed up the add process.
|
|
* @return boolean
|
|
*/
|
|
protected function _create_filecache()
|
|
{
|
|
if (count($this->_filecache) == 0) {
|
|
// Get _EVERYTHING_
|
|
$sql = 'SELECT `id`, `file` FROM `song` WHERE `catalog` = ?';
|
|
$db_results = Dba::read($sql, array($this->id));
|
|
|
|
// Populate the filecache
|
|
while ($results = Dba::fetch_assoc($db_results)) {
|
|
$this->_filecache[strtolower($results['file'])] = $results['id'];
|
|
}
|
|
|
|
$sql = 'SELECT `id`,`file` FROM `video` WHERE `catalog` = ?';
|
|
$db_results = Dba::read($sql, array($this->id));
|
|
|
|
while ($results = Dba::fetch_assoc($db_results)) {
|
|
$this->_filecache[strtolower($results['file'])] = 'v_' . $results['id'];
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* update_enabled
|
|
* sets the enabled flag
|
|
* @param boolean $new_enabled
|
|
* @param int $catalog_id
|
|
*/
|
|
public static function update_enabled($new_enabled, $catalog_id)
|
|
{
|
|
self::_update_item('enabled', $new_enabled, $catalog_id, '75');
|
|
} // update_enabled
|
|
|
|
/**
|
|
* _update_item
|
|
* This is a private function that should only be called from within the catalog class.
|
|
* It takes a field, value, catalog 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 $catalog_id
|
|
* @param int $level
|
|
* @return boolean
|
|
*/
|
|
private static function _update_item($field, $value, $catalog_id, $level)
|
|
{
|
|
/* Check them Rights! */
|
|
if (!Access::check('interface', $level)) {
|
|
return false;
|
|
}
|
|
|
|
/* Can't update to blank */
|
|
if (!strlen(trim($value))) {
|
|
return false;
|
|
}
|
|
|
|
$value = Dba::escape($value);
|
|
|
|
$sql = "UPDATE `catalog` SET `$field`='$value' WHERE `id`='$catalog_id'";
|
|
return Dba::write($sql);
|
|
} // _update_item
|
|
|
|
/**
|
|
* format
|
|
*
|
|
* This makes the object human-readable.
|
|
*/
|
|
public function format()
|
|
{
|
|
$this->f_name = $this->name;
|
|
$this->link = AmpConfig::get('web_path') . '/admin/catalog.php?action=show_customize_catalog&catalog_id=' . $this->id;
|
|
$this->f_link = '<a href="' . $this->link . '" title="' . scrub_out($this->name) . '">' .
|
|
scrub_out($this->f_name) . '</a>';
|
|
$this->f_update = $this->last_update
|
|
? date('d/m/Y h:i', $this->last_update)
|
|
: T_('Never');
|
|
$this->f_add = $this->last_add
|
|
? date('d/m/Y h:i', $this->last_add)
|
|
: T_('Never');
|
|
$this->f_clean = $this->last_clean
|
|
? date('d/m/Y h:i', $this->last_clean)
|
|
: T_('Never');
|
|
}
|
|
|
|
/**
|
|
* get_catalogs
|
|
*
|
|
* Pull all the current catalogs and return an array of ids
|
|
* of what you find
|
|
* @return int[]
|
|
*/
|
|
public static function get_catalogs()
|
|
{
|
|
$sql = "SELECT `id` FROM `catalog` ORDER BY `name`";
|
|
$db_results = Dba::read($sql);
|
|
|
|
$results = array();
|
|
|
|
while ($row = Dba::fetch_assoc($db_results)) {
|
|
$results[] = $row['id'];
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Get last catalogs update.
|
|
* @param int[]|null $catalogs
|
|
* @return int
|
|
*/
|
|
public static function getLastUpdate($catalogs = null)
|
|
{
|
|
$last_update = 0;
|
|
if ($catalogs == null || !is_array($catalogs)) {
|
|
$catalogs = self::get_catalogs();
|
|
}
|
|
foreach ($catalogs as $id) {
|
|
$catalog = Catalog::create_from_id($id);
|
|
if ($catalog->last_add > $last_update) {
|
|
$last_update = $catalog->last_add;
|
|
}
|
|
if ($catalog->last_update > $last_update) {
|
|
$last_update = $catalog->last_update;
|
|
}
|
|
if ($catalog->last_clean > $last_update) {
|
|
$last_update = $catalog->last_clean;
|
|
}
|
|
}
|
|
|
|
return $last_update;
|
|
}
|
|
|
|
/**
|
|
* get_stats
|
|
*
|
|
* This returns an hash with the #'s for the different
|
|
* objects that are associated with this catalog. This is used
|
|
* to build the stats box, it also calculates time.
|
|
* @param int|null $catalog_id
|
|
* @return array
|
|
*/
|
|
public static function get_stats($catalog_id = null)
|
|
{
|
|
$results = self::count_medias($catalog_id);
|
|
$results = array_merge(User::count(), $results);
|
|
$results['tags'] = self::count_tags();
|
|
|
|
$hours = floor($results['time'] / 3600);
|
|
|
|
$results['formatted_size'] = UI::format_bytes($results['size']);
|
|
|
|
$days = floor($hours / 24);
|
|
$hours = $hours % 24;
|
|
|
|
$time_text = "$days ";
|
|
$time_text .= ngettext('day', 'days', $days);
|
|
$time_text .= ", $hours ";
|
|
$time_text .= ngettext('hour', 'hours', $hours);
|
|
|
|
$results['time_text'] = $time_text;
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* create
|
|
*
|
|
* This creates a new catalog entry and associate it to current instance
|
|
* @param array $data
|
|
* @return int
|
|
*/
|
|
public static function create($data)
|
|
{
|
|
$name = $data['name'];
|
|
$type = $data['type'];
|
|
$rename_pattern = $data['rename_pattern'];
|
|
$sort_pattern = $data['sort_pattern'];
|
|
$gather_types = $data['gather_media'];
|
|
|
|
// Should it be an array? Not now.
|
|
if (!in_array($gather_types, array('music', 'clip', 'tvshow', 'movie', 'personal_video'))) {
|
|
return 0;
|
|
}
|
|
|
|
$insert_id = 0;
|
|
$filename = AmpConfig::get('prefix') . '/modules/catalog/' . $type . '.catalog.php';
|
|
$include = require_once $filename;
|
|
|
|
if ($include) {
|
|
$sql = 'INSERT INTO `catalog` (`name`, `catalog_type`, ' .
|
|
'`rename_pattern`, `sort_pattern`, `gather_types`) VALUES (?, ?, ?, ?, ?)';
|
|
Dba::write($sql, array(
|
|
$name,
|
|
$type,
|
|
$rename_pattern,
|
|
$sort_pattern,
|
|
$gather_types
|
|
));
|
|
|
|
$insert_id = Dba::insert_id();
|
|
|
|
if (!$insert_id) {
|
|
Error::add('general', T_('Catalog Insert Failed check debug logs'));
|
|
debug_event('catalog', 'Insert failed: ' . json_encode($data), 2);
|
|
return 0;
|
|
}
|
|
|
|
$classname = 'Catalog_' . $type;
|
|
if (!$classname::create_type($insert_id, $data)) {
|
|
$sql = 'DELETE FROM `catalog` WHERE `id` = ?';
|
|
Dba::write($sql, array($insert_id));
|
|
$insert_id = 0;
|
|
}
|
|
}
|
|
|
|
return $insert_id;
|
|
}
|
|
|
|
/**
|
|
* count_tags
|
|
*
|
|
* This returns the current number of unique tags in the database.
|
|
* @return int
|
|
*/
|
|
public static function count_tags()
|
|
{
|
|
// FIXME: Ignores catalog_id
|
|
$sql = "SELECT COUNT(`id`) FROM `tag`";
|
|
$db_results = Dba::read($sql);
|
|
|
|
$row = Dba::fetch_row($db_results);
|
|
return $row[0];
|
|
}
|
|
|
|
/**
|
|
* count_medias
|
|
*
|
|
* This returns the current number of songs, videos, albums, and artists
|
|
* in this catalog.
|
|
* @param int|null $catalog_id
|
|
* @return array
|
|
*/
|
|
public static function count_medias($catalog_id = null)
|
|
{
|
|
$where_sql = $catalog_id ? 'WHERE `catalog` = ?' : '';
|
|
$params = $catalog_id ? array($catalog_id) : null;
|
|
|
|
$sql = 'SELECT COUNT(`id`), SUM(`time`), SUM(`size`) FROM `song` ' .
|
|
$where_sql;
|
|
$db_results = Dba::read($sql, $params);
|
|
$data = Dba::fetch_row($db_results);
|
|
$songs = $data[0];
|
|
$time = $data[1];
|
|
$size = $data[2];
|
|
|
|
$sql = 'SELECT COUNT(`id`), SUM(`time`), SUM(`size`) FROM `video` ' .
|
|
$where_sql;
|
|
$db_results = Dba::read($sql, $params);
|
|
$data = Dba::fetch_row($db_results);
|
|
$videos = $data[0];
|
|
$time += $data[1];
|
|
$size += $data[2];
|
|
|
|
$sql = 'SELECT COUNT(DISTINCT(`album`)) FROM `song` ' . $where_sql;
|
|
$db_results = Dba::read($sql, $params);
|
|
$data = Dba::fetch_row($db_results);
|
|
$albums = $data[0];
|
|
|
|
$sql = 'SELECT COUNT(DISTINCT(`artist`)) FROM `song` ' . $where_sql;
|
|
$db_results = Dba::read($sql, $params);
|
|
$data = Dba::fetch_row($db_results);
|
|
$artists = $data[0];
|
|
|
|
$sql = 'SELECT COUNT(`id`) FROM `search`';
|
|
$db_results = Dba::read($sql, $params);
|
|
$data = Dba::fetch_row($db_results);
|
|
$smartplaylists = $data[0];
|
|
|
|
$sql = 'SELECT COUNT(`id`) FROM `playlist`';
|
|
$db_results = Dba::read($sql, $params);
|
|
$data = Dba::fetch_row($db_results);
|
|
$playlists = $data[0];
|
|
|
|
$results = array();
|
|
$results['songs'] = $songs;
|
|
$results['videos'] = $videos;
|
|
$results['albums'] = $albums;
|
|
$results['artists'] = $artists;
|
|
$results['playlists'] = $playlists;
|
|
$results['smartplaylists'] = $smartplaylists;
|
|
$results['size'] = $size;
|
|
$results['time'] = $time;
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $type
|
|
* @param int|null $user_id
|
|
* @return string
|
|
*/
|
|
public static function get_uploads_sql($type, $user_id=null)
|
|
{
|
|
if (is_null($user_id)) {
|
|
$user_id = $GLOBALS['user']->id;
|
|
}
|
|
$user_id = intval($user_id);
|
|
|
|
switch ($type) {
|
|
case 'song':
|
|
$sql = "SELECT `song`.`id` as `id` FROM `song` WHERE `song`.`user_upload` = '" . $user_id . "'";
|
|
break;
|
|
|
|
case 'album':
|
|
$sql = "SELECT `album`.`id` as `id` FROM `album` JOIN `song` ON `song`.`album` = `album`.`id` WHERE `song`.`user_upload` = '" . $user_id . "' GROUP BY `album`.`id`";
|
|
break;
|
|
|
|
case 'artist':
|
|
default:
|
|
$sql = "SELECT `artist`.`id` as `id` FROM `artist` JOIN `song` ON `song`.`artist` = `artist`.`id` WHERE `song`.`user_upload` = '" . $user_id . "' GROUP BY `artist`.`id`";
|
|
break;
|
|
}
|
|
|
|
return $sql;
|
|
}
|
|
|
|
/**
|
|
* get_album_ids
|
|
*
|
|
* This returns an array of ids of albums that have songs in this
|
|
* catalog
|
|
* @return int[]
|
|
*/
|
|
public function get_album_ids()
|
|
{
|
|
$results = array();
|
|
|
|
$sql = 'SELECT DISTINCT(`song`.`album`) FROM `song` WHERE `song`.`catalog` = ?';
|
|
$db_results = Dba::read($sql, array($this->id));
|
|
|
|
while ($r = Dba::fetch_assoc($db_results)) {
|
|
$results[] = $r['album'];
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* get_video_ids
|
|
*
|
|
* This returns an array of ids of videos in this catalog
|
|
* @param string $type
|
|
* @return int[]
|
|
*/
|
|
public function get_video_ids($type = '')
|
|
{
|
|
$results = array();
|
|
|
|
$sql = 'SELECT DISTINCT(`video`.`id`) FROM `video` ';
|
|
if (!empty($type)) {
|
|
$sql .= 'JOIN `' . $type . '` ON `' . $type . '`.`id` = `video`.`id`';
|
|
}
|
|
$sql .= 'WHERE `video`.`catalog` = ?';
|
|
$db_results = Dba::read($sql, array($this->id));
|
|
|
|
while ($r = Dba::fetch_assoc($db_results)) {
|
|
$results[] = $r['id'];
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param int[]|null $catalogs
|
|
* @param string $type
|
|
* @return \Video[]
|
|
*/
|
|
public static function get_videos($catalogs = null, $type = '')
|
|
{
|
|
if (!$catalogs) {
|
|
$catalogs = self::get_catalogs();
|
|
}
|
|
|
|
$results = array();
|
|
foreach ($catalogs as $catalog_id) {
|
|
$catalog = Catalog::create_from_id($catalog_id);
|
|
$video_ids = $catalog->get_video_ids($type);
|
|
foreach ($video_ids as $video_id) {
|
|
$results[] = Video::create_from_id($video_id);
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param int|null $catalog_id
|
|
* @param string $type
|
|
* @return int
|
|
*/
|
|
public static function get_videos_count($catalog_id = null, $type = '')
|
|
{
|
|
$sql = "SELECT COUNT(`video`.`id`) AS `video_cnt` FROM `video` ";
|
|
if (!empty($type)) {
|
|
$sql .= "JOIN `" . $type . "` ON `" . $type . "`.`id` = `video`.`id` ";
|
|
}
|
|
if ($catalog_id) {
|
|
$sql .= "WHERE `video`.`catalog` = `" . intval($catalog_id) . "`";
|
|
}
|
|
$db_results = Dba::read($sql);
|
|
$video_cnt = 0;
|
|
if ($row = Dba::fetch_row($db_results)) {
|
|
$video_cnt = $row[0];
|
|
}
|
|
|
|
return $video_cnt;
|
|
}
|
|
|
|
/**
|
|
* get_tvshow_ids
|
|
*
|
|
* This returns an array of ids of tvshows in this catalog
|
|
* @return int[]
|
|
*/
|
|
public function get_tvshow_ids()
|
|
{
|
|
$results = array();
|
|
|
|
$sql = 'SELECT DISTINCT(`tvshow`.`id`) FROM `tvshow` ';
|
|
$sql .= 'JOIN `tvshow_season` ON `tvshow_season`.`tvshow` = `tvshow`.`id` ';
|
|
$sql .= 'JOIN `tvshow_episode` ON `tvshow_episode`.`season` = `tvshow_season`.`id` ';
|
|
$sql .= 'JOIN `video` ON `video`.`id` = `tvshow_episode`.`id` ';
|
|
$sql .= 'WHERE `video`.`catalog` = ?';
|
|
|
|
$db_results = Dba::read($sql, array($this->id));
|
|
while ($r = Dba::fetch_assoc($db_results)) {
|
|
$results[] = $r['id'];
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param int[]|null $catalogs
|
|
* @return \TVShow[]
|
|
*/
|
|
public static function get_tvshows($catalogs = null)
|
|
{
|
|
if (!$catalogs) {
|
|
$catalogs = self::get_catalogs();
|
|
}
|
|
|
|
$results = array();
|
|
foreach ($catalogs as $catalog_id) {
|
|
$catalog = Catalog::create_from_id($catalog_id);
|
|
$tvshow_ids = $catalog->get_tvshow_ids();
|
|
foreach ($tvshow_ids as $tvshow_id) {
|
|
$results[] = new TVShow($tvshow_id);
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* get_artist_ids
|
|
*
|
|
* This returns an array of ids of artist that have songs in this
|
|
* catalog
|
|
* @return int[]
|
|
*/
|
|
public function get_artist_ids()
|
|
{
|
|
$results = array();
|
|
|
|
$sql = 'SELECT DISTINCT(`song`.`artist`) FROM `song` WHERE `song`.`catalog` = ?';
|
|
$db_results = Dba::read($sql, array($this->id));
|
|
|
|
while ($r = Dba::fetch_assoc($db_results)) {
|
|
$results[] = $r['artist'];
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* get_artists
|
|
*
|
|
* This returns an array of artists that have songs in the catalogs parameter
|
|
* @param array|null $catalogs
|
|
* @return \Artist[]
|
|
*/
|
|
public static function get_artists($catalogs = null, $size = 0, $offset = 0)
|
|
{
|
|
$sql_where = "";
|
|
if (is_array($catalogs) && count($catalogs)) {
|
|
$catlist = '(' . implode(',', $catalogs) . ')';
|
|
$sql_where = "WHERE `song`.`catalog` IN $catlist";
|
|
}
|
|
|
|
$sql_limit = "";
|
|
if ($offset > 0 && $size > 0) {
|
|
$sql_limit = "LIMIT " . $offset . ", " . $size;
|
|
} elseif ($size > 0) {
|
|
$sql_limit = "LIMIT " . $size;
|
|
} elseif ($offset > 0) {
|
|
// MySQL doesn't have notation for last row, so we have to use the largest possible BIGINT value
|
|
// https://dev.mysql.com/doc/refman/5.0/en/select.html
|
|
$sql_limit = "LIMIT " . $offset . ", 18446744073709551615";
|
|
}
|
|
|
|
$sql = "SELECT `artist`.id, `artist`.`name`, `artist`.`summary` FROM `song` LEFT JOIN `artist` ON `artist`.`id` = `song`.`artist` " .
|
|
$sql_where .
|
|
"GROUP BY `song`.artist ORDER BY `artist`.`name` " .
|
|
$sql_limit;
|
|
|
|
$results = array();
|
|
$db_results = Dba::read($sql);
|
|
|
|
while ($r = Dba::fetch_assoc($db_results)) {
|
|
$results[] = Artist::construct_from_array($r);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* get_albums
|
|
*
|
|
* Returns an array of ids of albums that have songs in the catalogs parameter
|
|
* @param int $size
|
|
* @param int $offset
|
|
* @param int[]|null $catalogs
|
|
* @return int[]
|
|
*/
|
|
public static function get_albums($size = 0, $offset = 0, $catalogs = null)
|
|
{
|
|
$sql_where = "";
|
|
if (is_array($catalogs) && count($catalogs)) {
|
|
$catlist = '(' . implode(',', $catalogs) . ')';
|
|
$sql_where = "WHERE `song`.`catalog` IN $catlist";
|
|
}
|
|
|
|
$sql_limit = "";
|
|
if ($offset > 0 && $size > 0) {
|
|
$sql_limit = "LIMIT $offset, $size";
|
|
} elseif ($size > 0) {
|
|
$sql_limit = "LIMIT $size";
|
|
} elseif ($offset > 0) {
|
|
// MySQL doesn't have notation for last row, so we have to use the largest possible BIGINT value
|
|
// https://dev.mysql.com/doc/refman/5.0/en/select.html
|
|
$sql_limit = "LIMIT $offset, 18446744073709551615";
|
|
}
|
|
|
|
$sql = "SELECT `album`.`id` FROM `song` LEFT JOIN `album` ON `album`.`id` = `song`.`album` $sql_where GROUP BY `song`.`album` ORDER BY `album`.`name` $sql_limit";
|
|
|
|
$db_results = Dba::read($sql);
|
|
$results = array();
|
|
while ($r = Dba::fetch_assoc($db_results)) {
|
|
$results[] = $r['id'];
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* get_albums_by_artist
|
|
*
|
|
* Returns an array of ids of albums that have songs in the catalogs parameter, grouped by artist
|
|
* @param int $size
|
|
* @oaram int $offset
|
|
* @param int[]|null $catalogs
|
|
* @return int[]
|
|
*/
|
|
public static function get_albums_by_artist($size = 0, $offset = 0, $catalogs = null)
|
|
{
|
|
$sql_where = "";
|
|
if (is_array($catalogs) && count($catalogs)) {
|
|
$catlist = '(' . implode(',', $catalogs) . ')';
|
|
$sql_where = "WHERE `song`.`catalog` IN $catlist";
|
|
}
|
|
|
|
$sql_limit = "";
|
|
if ($offset > 0 && $size > 0) {
|
|
$sql_limit = "LIMIT $offset, $size";
|
|
} elseif ($size > 0) {
|
|
$sql_limit = "LIMIT $size";
|
|
} elseif ($offset > 0) {
|
|
// MySQL doesn't have notation for last row, so we have to use the largest possible BIGINT value
|
|
// https://dev.mysql.com/doc/refman/5.0/en/select.html
|
|
$sql_limit = "LIMIT $offset, 18446744073709551615";
|
|
}
|
|
|
|
$sql = "SELECT `album`.`id` FROM `song` LEFT JOIN `album` ON `album`.`id` = `song`.`album` " .
|
|
"LEFT JOIN `artist` ON `artist`.`id` = `song`.`artist` $sql_where GROUP BY `song`.`album` ORDER BY `artist`.`name`, `artist`.`id`, `album`.`name` $sql_limit";
|
|
|
|
$db_results = Dba::read($sql);
|
|
$results = array();
|
|
while ($r = Dba::fetch_assoc($db_results)) {
|
|
$results[] = $r['id'];
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $type
|
|
* @param int $id
|
|
*/
|
|
public function gather_art_item($type, $id)
|
|
{
|
|
debug_event('gather_art', 'Gathering art for ' . $type . '/' . $id . '...', 5);
|
|
|
|
// Should be more generic !
|
|
if ($type == 'video') {
|
|
$libitem = Video::create_from_id($id);
|
|
} else {
|
|
$libitem = new $type($id);
|
|
}
|
|
$options = array();
|
|
$libitem->format();
|
|
if ($libitem->id) {
|
|
if (count($options) == 0) {
|
|
// Only search on items with default art kind as `default`.
|
|
if ($libitem->get_default_art_kind() == 'default') {
|
|
$keywords = $libitem->get_keywords();
|
|
$keyword = '';
|
|
foreach ($keywords as $key => $word) {
|
|
$options[$key] = $word['value'];
|
|
if ($word['important']) {
|
|
if (!empty($word['value'])) {
|
|
$keyword .= ' ' . $word['value'];
|
|
}
|
|
}
|
|
}
|
|
$options['keyword'] = $keyword;
|
|
}
|
|
|
|
$parent = $libitem->get_parent();
|
|
if ($parent != null) {
|
|
if (!Art::has_db($parent['object_id'], $parent['object_type'])) {
|
|
$this->gather_art_item($parent['object_type'], $parent['object_id']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$art = new Art($id, $type);
|
|
$results = $art->gather($options, 1);
|
|
|
|
if (count($results)) {
|
|
// Pull the string representation from the source
|
|
$image = Art::get_from_source($results[0], $type);
|
|
if (strlen($image) > '5') {
|
|
$art->insert($image, $results[0]['mime']);
|
|
// If they've enabled resizing of images generate a thumbnail
|
|
if (AmpConfig::get('resize_images')) {
|
|
$size = array('width' => 275, 'height' => 275);
|
|
$thumb = $art->generate_thumb($image,$size ,$results[0]['mime']);
|
|
if (is_array($thumb)) {
|
|
$art->save_thumb($thumb['thumb'], $thumb['thumb_mime'], $size);
|
|
}
|
|
}
|
|
} else {
|
|
debug_event('gather_art', 'Image less than 5 chars, not inserting', 3);
|
|
}
|
|
}
|
|
|
|
if ($type == 'video' && AmpConfig::get('generate_video_preview')) {
|
|
Video::generate_preview($id);
|
|
}
|
|
|
|
if (UI::check_ticker()) {
|
|
UI::update_text('read_art_' . $this->id, $libitem->get_fullname());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gather_art
|
|
*
|
|
* This runs through all of the albums and finds art for them
|
|
* This runs through all of the needs art albums and trys
|
|
* to find the art for them from the mp3s
|
|
* @param int[]|null $songs
|
|
* @param int[]|null $videos
|
|
*/
|
|
public function gather_art($songs = null, $videos = null)
|
|
{
|
|
// Make sure they've actually got methods
|
|
$art_order = AmpConfig::get('art_order');
|
|
if (!count($art_order)) {
|
|
debug_event('gather_art', 'art_order not set, Catalog::gather_art aborting', 3);
|
|
return true;
|
|
}
|
|
|
|
// Prevent the script from timing out
|
|
set_time_limit(0);
|
|
|
|
$search_count = 0;
|
|
$searches = array();
|
|
if ($songs == null) {
|
|
$searches['album'] = $this->get_album_ids();
|
|
$searches['artist'] = $this->get_artist_ids();
|
|
} else {
|
|
$searches['album'] = array();
|
|
$searches['artist'] = array();
|
|
foreach ($songs as $song_id) {
|
|
$song = new Song($song_id);
|
|
if ($song->id) {
|
|
if (!in_array($song->album, $searches['album'])) {
|
|
$searches['album'][] = $song->album;
|
|
}
|
|
if (!in_array($song->artist, $searches['artist'])) {
|
|
$searches['artist'][] = $song->artist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($videos == null) {
|
|
$searches['video'] = $this->get_video_ids();
|
|
} else {
|
|
$searches['video'] = $videos;
|
|
}
|
|
|
|
// Run through items and get the art!
|
|
foreach ($searches as $key => $values) {
|
|
foreach ($values as $id) {
|
|
$this->gather_art_item($key, $id);
|
|
|
|
// Stupid little cutesie thing
|
|
$search_count++;
|
|
if (UI::check_ticker()) {
|
|
UI::update_text('count_art_' . $this->id, $search_count);
|
|
}
|
|
}
|
|
}
|
|
|
|
// One last time for good measure
|
|
UI::update_text('count_art_' . $this->id, $search_count);
|
|
}
|
|
|
|
/**
|
|
* get_songs
|
|
*
|
|
* Returns an array of song objects.
|
|
* @return \Song[]
|
|
*/
|
|
public function get_songs()
|
|
{
|
|
$songs = array();
|
|
$results = array();
|
|
|
|
$sql = "SELECT `id` FROM `song` WHERE `catalog` = ? AND `enabled`='1'";
|
|
$db_results = Dba::read($sql, array($this->id));
|
|
|
|
while ($row = Dba::fetch_assoc($db_results)) {
|
|
$songs[] = $row['id'];
|
|
}
|
|
|
|
if (AmpConfig::get('memory_cache')) {
|
|
Song::build_cache($songs);
|
|
}
|
|
|
|
foreach ($songs as $song_id) {
|
|
$results[] = new Song($song_id);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* dump_album_art
|
|
*
|
|
* This runs through all of the albums and tries to dump the
|
|
* art for them into the 'folder.jpg' file in the appropriate dir.
|
|
* @param array $methods
|
|
*/
|
|
public function dump_album_art($methods = array())
|
|
{
|
|
// Get all of the albums in this catalog
|
|
$albums = $this->get_album_ids();
|
|
|
|
echo "Starting Dump Album Art...\n";
|
|
$i = 0;
|
|
|
|
// Run through them and get the art!
|
|
foreach ($albums as $album_id) {
|
|
$album = new Album($album_id);
|
|
$art = new Art($album_id, 'album');
|
|
|
|
if (!$art->get_db()) {
|
|
continue;
|
|
}
|
|
|
|
// Get the first song in the album
|
|
$songs = $album->get_songs(1);
|
|
$song = new Song($songs[0]);
|
|
$dir = dirname($song->file);
|
|
|
|
$extension = Art::extension($art->raw_mime);
|
|
|
|
// Try the preferred filename, if that fails use folder.???
|
|
$preferred_filename = AmpConfig::get('album_art_preferred_filename');
|
|
if (!$preferred_filename ||
|
|
strpos($preferred_filename, '%') !== false) {
|
|
$preferred_filename = "folder.$extension";
|
|
}
|
|
|
|
$file = $dir . DIRECTORY_SEPARATOR . $preferred_filename;
|
|
if ($file_handle = fopen($file, "w")) {
|
|
if (fwrite($file_handle, $art->raw)) {
|
|
// Also check and see if we should write
|
|
// out some metadata
|
|
if ($methods['metadata']) {
|
|
switch ($methods['metadata']) {
|
|
case 'windows':
|
|
$meta_file = $dir . '/desktop.ini';
|
|
$string = "[.ShellClassInfo]\nIconFile=$file\nIconIndex=0\nInfoTip=$album->full_name";
|
|
break;
|
|
case 'linux':
|
|
default:
|
|
$meta_file = $dir . '/.directory';
|
|
$string = "Name=$album->full_name\nIcon=$file";
|
|
break;
|
|
}
|
|
|
|
$meta_handle = fopen($meta_file, "w");
|
|
fwrite($meta_handle, $string);
|
|
fclose($meta_handle);
|
|
} // end metadata
|
|
$i++;
|
|
if (!($i%100)) {
|
|
echo "Written: $i. . .\n";
|
|
debug_event('art_write', "$album->name Art written to $file", '5');
|
|
}
|
|
} else {
|
|
debug_event('art_write', "Unable to open $file for writing", 5);
|
|
echo "Error: unable to open file for writing [$file]\n";
|
|
}
|
|
}
|
|
fclose($file_handle);
|
|
}
|
|
|
|
echo "Album Art Dump Complete\n";
|
|
}
|
|
|
|
/**
|
|
* update_last_update
|
|
* updates the last_update of the catalog
|
|
*/
|
|
protected function update_last_update()
|
|
{
|
|
$date = time();
|
|
$sql = "UPDATE `catalog` SET `last_update` = ? WHERE `id` = ?";
|
|
Dba::write($sql, array($date, $this->id));
|
|
} // update_last_update
|
|
|
|
/**
|
|
* update_last_add
|
|
* updates the last_add of the catalog
|
|
*/
|
|
public function update_last_add()
|
|
{
|
|
$date = time();
|
|
$sql = "UPDATE `catalog` SET `last_add` = ? WHERE `id` = ?";
|
|
Dba::write($sql, array($date, $this->id));
|
|
} // update_last_add
|
|
|
|
/**
|
|
* update_last_clean
|
|
* This updates the last clean information
|
|
*/
|
|
public function update_last_clean()
|
|
{
|
|
$date = time();
|
|
$sql = "UPDATE `catalog` SET `last_clean` = ? WHERE `id` = ?";
|
|
Dba::write($sql, array($date, $this->id));
|
|
} // update_last_clean
|
|
|
|
/**
|
|
* update_settings
|
|
* This function updates the basic setting of the catalog
|
|
* @param array $data
|
|
* @return boolean
|
|
*/
|
|
public static function update_settings($data)
|
|
{
|
|
$sql = "UPDATE `catalog` SET `name` = ?, `rename_pattern` = ?, `sort_pattern` = ? WHERE `id` = ?";
|
|
$params = array($data['name'], $data['rename_pattern'], $data['sort_pattern'], $data['catalog_id']);
|
|
Dba::write($sql, $params);
|
|
|
|
return true;
|
|
} // update_settings
|
|
|
|
/**
|
|
* update_single_item
|
|
* updates a single album,artist,song from the tag data
|
|
* this can be done by 75+
|
|
* @param string $type
|
|
* @param int $id
|
|
*/
|
|
public static function update_single_item($type, $id)
|
|
{
|
|
// Because single items are large numbers of things too
|
|
set_time_limit(0);
|
|
|
|
$songs = array();
|
|
|
|
switch ($type) {
|
|
case 'album':
|
|
$album = new Album($id);
|
|
$songs = $album->get_songs();
|
|
break;
|
|
case 'artist':
|
|
$artist = new Artist($id);
|
|
$songs = $artist->get_songs();
|
|
break;
|
|
case 'song':
|
|
$songs[] = $id;
|
|
break;
|
|
} // end switch type
|
|
|
|
foreach ($songs as $song_id) {
|
|
$song = new Song($song_id);
|
|
$info = self::update_media_from_tags($song, '', '');
|
|
|
|
if ($info['change']) {
|
|
$file = scrub_out($song->file);
|
|
echo "<dl>\n\t<dd>";
|
|
echo "<strong>$file " . T_('Updated') . "</strong>\n";
|
|
echo $info['text'];
|
|
echo "\t</dd>\n</dl><hr align=\"left\" width=\"50%\" />";
|
|
flush();
|
|
} // if change
|
|
else {
|
|
echo"<dl>\n\t<dd>";
|
|
echo "<strong>" . scrub_out($song->file) . "</strong><br />" . T_('No Update Needed') . "\n";
|
|
echo "\t</dd>\n</dl><hr align=\"left\" width=\"50%\" />";
|
|
flush();
|
|
}
|
|
} // foreach songs
|
|
|
|
self::gc();
|
|
} // update_single_item
|
|
|
|
/**
|
|
* update_media_from_tags
|
|
* This is a 'wrapper' function calls the update function for the media
|
|
* type in question
|
|
* @param \media $media
|
|
* @param string $sort_pattern
|
|
* @param string $rename_pattern
|
|
* @return array
|
|
*/
|
|
public static function update_media_from_tags($media, $sort_pattern='', $rename_pattern='')
|
|
{
|
|
// Check for patterns
|
|
if (!$sort_pattern or !$rename_pattern) {
|
|
$catalog = Catalog::create_from_id($media->catalog);
|
|
$sort_pattern = $catalog->sort_pattern;
|
|
$rename_pattern = $catalog->rename_pattern;
|
|
}
|
|
|
|
debug_event('tag-read', 'Reading tags from ' . $media->file, 5);
|
|
|
|
$vainfo = new vainfo($media->file, array('music'), '', '', '', $sort_pattern, $rename_pattern);
|
|
$vainfo->get_info();
|
|
|
|
$key = vainfo::get_tag_type($vainfo->tags);
|
|
|
|
$results = vainfo::clean_tag_info($vainfo->tags, $key, $media->file);
|
|
|
|
// Figure out what type of object this is and call the right
|
|
// function, giving it the stuff we've figured out above
|
|
$name = (strtolower(get_class($media)) == 'song') ? 'song' : 'video';
|
|
|
|
$function = 'update_' . $name . '_from_tags';
|
|
|
|
$return = call_user_func(array('Catalog', $function), $results, $media);
|
|
|
|
return $return;
|
|
} // update_media_from_tags
|
|
|
|
/**
|
|
* update_song_from_tags
|
|
* Updates the song info based on tags; this is called from a bunch of
|
|
* different places and passes in a full fledged song object, so it's a
|
|
* static function.
|
|
* FIXME: This is an ugly mess, this really needs to be consolidated and
|
|
* cleaned up.
|
|
* @param array $results
|
|
* @param \Song $song
|
|
* @return array
|
|
*/
|
|
public static function update_song_from_tags($results, Song $song)
|
|
{
|
|
/* Setup the vars */
|
|
$new_song = new Song();
|
|
$new_song->file = $results['file'];
|
|
$new_song->title = $results['title'];
|
|
$new_song->year = $results['year'];
|
|
$new_song->comment = $results['comment'];
|
|
$new_song->language = $results['language'];
|
|
$new_song->lyrics = str_replace(
|
|
array("\r\n", "\r", "\n"),
|
|
'<br />',
|
|
strip_tags($results['lyrics']));
|
|
$new_song->bitrate = $results['bitrate'];
|
|
$new_song->rate = $results['rate'];
|
|
$new_song->mode = ($results['mode'] == 'cbr') ? 'cbr' : 'vbr';
|
|
$new_song->size = $results['size'];
|
|
$new_song->time = $results['time'];
|
|
$new_song->mime = $results['mime'];
|
|
$new_song->track = intval($results['track']);
|
|
$new_song->mbid = $results['mb_trackid'];
|
|
$new_song->label = $results['publisher'];
|
|
$new_song->composer = $results['composer'];
|
|
$new_song->replaygain_track_gain = floatval($results['replaygain_track_gain']);
|
|
$new_song->replaygain_track_peak = floatval($results['replaygain_track_peak']);
|
|
$new_song->replaygain_album_gain = floatval($results['replaygain_album_gain']);
|
|
$new_song->replaygain_album_peak = floatval($results['replaygain_album_peak']);
|
|
$tags = Tag::get_object_tags('song', $song->id);
|
|
if ($tags) {
|
|
foreach ($tags as $tag) {
|
|
$song->tags[] = $tag['name'];
|
|
}
|
|
}
|
|
$new_song->tags = $results['genre'];
|
|
$artist = $results['artist'];
|
|
$artist_mbid = $results['mb_artistid'];
|
|
$albumartist = $results['albumartist'] ?: $results['band'];
|
|
$albumartist = $albumartist ?: null;
|
|
$albumartist_mbid = $results['mb_albumartistid'];
|
|
$album = $results['album'];
|
|
$album_mbid = $results['mb_albumid'];
|
|
$album_mbid_group = $results['mb_albumid_group'];
|
|
$disk = $results['disk'];
|
|
|
|
/*
|
|
* We have the artist/genre/album name need to check it in the tables
|
|
* If found then add & return id, else return id
|
|
*/
|
|
$new_song->artist = Artist::check($artist, $artist_mbid);
|
|
if ($albumartist) {
|
|
$new_song->albumartist = Artist::check($albumartist, $albumartist_mbid);
|
|
}
|
|
$new_song->album = Album::check($album, $new_song->year, $disk, $album_mbid, $album_mbid_group, $new_song->albumartist);
|
|
$new_song->title = self::check_title($new_song->title, $new_song->file);
|
|
|
|
/* Since we're doing a full compare make sure we fill the extended information */
|
|
$song->fill_ext_info();
|
|
|
|
$info = Song::compare_song_information($song, $new_song);
|
|
if ($info['change']) {
|
|
debug_event('update', "$song->file : differences found, updating database", 5);
|
|
|
|
// Duplicate arts if required
|
|
if ($song->artist != $new_song->artist) {
|
|
if (!Art::has_db($new_song->artist, 'artist')) {
|
|
Art::duplicate('artist', $song->artist, $new_song->artist);
|
|
}
|
|
}
|
|
if ($song->albumartist != $new_song->albumartist) {
|
|
if (!Art::has_db($new_song->albumartist, 'artist')) {
|
|
Art::duplicate('artist', $song->albumartist, $new_song->albumartist);
|
|
}
|
|
}
|
|
if ($song->album != $new_song->album) {
|
|
if (!Art::has_db($new_song->album, 'album')) {
|
|
Art::duplicate('album', $song->album, $new_song->album);
|
|
}
|
|
}
|
|
|
|
$song->update_song($song->id, $new_song);
|
|
|
|
if ($song->tags != $new_song->tags) {
|
|
Tag::update_tag_list(implode(',', $new_song->tags), 'song', $song->id, true);
|
|
self::updateAlbumTags($song);
|
|
self::updateArtistTags($song);
|
|
}
|
|
// Refine our reference
|
|
//$song = $new_song;
|
|
} else {
|
|
debug_event('update', "$song->file : no differences found", 5);
|
|
}
|
|
|
|
return $info;
|
|
} // update_song_from_tags
|
|
|
|
public function update_video_from_tags($results, Video $video)
|
|
{
|
|
// TODO: implement this
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $media_type
|
|
* @return array
|
|
*/
|
|
public function get_gather_types($media_type = '')
|
|
{
|
|
$gtypes = $this->gather_types;
|
|
if (empty($gtypes)) {
|
|
$gtypes = "music";
|
|
}
|
|
$types = explode(',', $gtypes);
|
|
|
|
if ($media_type == "video") {
|
|
$types = array_diff($types, array('music'));
|
|
}
|
|
|
|
if ($media_type == "music") {
|
|
$types = array_diff($types, array('personal_video', 'movie', 'tvshow', 'clip'));
|
|
}
|
|
|
|
return $types;
|
|
}
|
|
|
|
/**
|
|
* clean_catalog
|
|
*
|
|
* Cleans the catalog of files that no longer exist.
|
|
*/
|
|
public function clean_catalog()
|
|
{
|
|
// We don't want to run out of time
|
|
set_time_limit(0);
|
|
|
|
debug_event('clean', 'Starting on ' . $this->name, 5);
|
|
|
|
if (!defined('SSE_OUTPUT')) {
|
|
require AmpConfig::get('prefix') . '/templates/show_clean_catalog.inc.php';
|
|
ob_flush();
|
|
flush();
|
|
}
|
|
|
|
$dead_total = $this->clean_catalog_proc();
|
|
|
|
debug_event('clean', 'clean finished, ' . $dead_total . ' removed from '. $this->name, 5);
|
|
|
|
// Remove any orphaned artists/albums/etc.
|
|
self::gc();
|
|
|
|
if (!defined('SSE_OUTPUT')) {
|
|
UI::show_box_top();
|
|
}
|
|
UI::update_text('', sprintf(ngettext('Catalog Clean Done. %d file removed.', 'Catalog Clean Done. %d files removed.', $dead_total), $dead_total));
|
|
if (!defined('SSE_OUTPUT')) {
|
|
UI::show_box_bottom();
|
|
}
|
|
|
|
$this->update_last_clean();
|
|
} // clean_catalog
|
|
|
|
/**
|
|
* verify_catalog
|
|
* This function verify the catalog
|
|
*/
|
|
public function verify_catalog()
|
|
{
|
|
if (!defined('SSE_OUTPUT')) {
|
|
require AmpConfig::get('prefix') . '/templates/show_verify_catalog.inc.php';
|
|
ob_flush();
|
|
flush();
|
|
}
|
|
|
|
$verified = $this->verify_catalog_proc();
|
|
|
|
if (!defined('SSE_OUTPUT')) {
|
|
UI::show_box_top();
|
|
}
|
|
UI::update_text('', sprintf(T_('Catalog Verify Done. %d of %d files updated.'), $verified['updated'], $verified['total']));
|
|
if (!defined('SSE_OUTPUT')) {
|
|
UI::show_box_bottom();
|
|
}
|
|
|
|
return true;
|
|
} // verify_catalog
|
|
|
|
/**
|
|
* gc
|
|
*
|
|
* This is a wrapper function for all of the different cleaning
|
|
* functions, it runs them in an order that resembles correctness.
|
|
*/
|
|
public static function gc()
|
|
{
|
|
debug_event('catalog', 'Database cleanup started', 5);
|
|
Song::gc();
|
|
Album::gc();
|
|
Artist::gc();
|
|
Video::gc();
|
|
Art::gc();
|
|
Stats::gc();
|
|
Rating::gc();
|
|
Userflag::gc();
|
|
Playlist::gc();
|
|
Tmp_Playlist::gc();
|
|
Shoutbox::gc();
|
|
Tag::gc();
|
|
debug_event('catalog', 'Database cleanup ended', 5);
|
|
}
|
|
|
|
/**
|
|
* trim_prefix
|
|
* Splits the prefix from the string
|
|
* @param string $string
|
|
* @return array
|
|
*/
|
|
public static function trim_prefix($string)
|
|
{
|
|
$prefix_pattern = '/^(' . implode('\\s|', explode('|', AmpConfig::get('catalog_prefix_pattern'))) . '\\s)(.*)/i';
|
|
preg_match($prefix_pattern, $string, $matches);
|
|
|
|
if (count($matches)) {
|
|
$string = trim($matches[2]);
|
|
$prefix = trim($matches[1]);
|
|
} else {
|
|
$prefix = null;
|
|
}
|
|
|
|
return array('string' => $string, 'prefix' => $prefix);
|
|
} // trim_prefix
|
|
|
|
/**
|
|
* check_title
|
|
* this checks to make sure something is
|
|
* set on the title, if it isn't it looks at the
|
|
* filename and trys to set the title based on that
|
|
* @param string $title
|
|
* @param string $file
|
|
*/
|
|
public static function check_title($title, $file='')
|
|
{
|
|
if (strlen(trim($title)) < 1) {
|
|
$title = Dba::escape($file);
|
|
}
|
|
|
|
return $title;
|
|
} // check_title
|
|
|
|
/**
|
|
* playlist_import
|
|
* Attempts to create a Public Playlist based on the playlist file
|
|
* @param string $playlist
|
|
* @return array
|
|
*/
|
|
public static function import_playlist($playlist)
|
|
{
|
|
$data = file_get_contents($playlist);
|
|
if (substr($playlist, -3, 3) == 'm3u') {
|
|
$files = self::parse_m3u($data);
|
|
} elseif (substr($playlist, -3, 3) == 'pls') {
|
|
$files = self::parse_pls($data);
|
|
} elseif (substr($playlist, -3, 3) == 'asx') {
|
|
$files = self::parse_asx($data);
|
|
} elseif (substr($playlist, -4, 4) == 'xspf') {
|
|
$files = self::parse_xspf($data);
|
|
}
|
|
|
|
$songs = array();
|
|
$pinfo = pathinfo($playlist);
|
|
if (isset($files)) {
|
|
foreach ($files as $file) {
|
|
$file = trim($file);
|
|
// Check to see if it's a url from this ampache instance
|
|
if (substr($file, 0, strlen(AmpConfig::get('web_path'))) == AmpConfig::get('web_path')) {
|
|
$data = Stream_URL::parse($file);
|
|
$sql = 'SELECT COUNT(*) FROM `song` WHERE `id` = ?';
|
|
$db_results = Dba::read($sql, array($data['id']));
|
|
if (Dba::num_rows($db_results)) {
|
|
$songs[] = $data['id'];
|
|
}
|
|
} // end if it's an http url
|
|
else {
|
|
// Remove file:// prefix if any
|
|
if (strpos($file, "file://") !== false) {
|
|
$file = urldecode(substr($file, 7));
|
|
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
|
// Removing starting / on Windows OS.
|
|
if (substr($file, 0, 1) == '/') {
|
|
$file = substr($file, 1);
|
|
}
|
|
// Restore real directory separator
|
|
$file = str_replace("/", DIRECTORY_SEPARATOR, $file);
|
|
}
|
|
}
|
|
debug_event('catalog', 'Add file ' . $file . ' to playlist.', '5');
|
|
|
|
// First, try to found the file as absolute path
|
|
$sql = "SELECT `id` FROM `song` WHERE `file` = ?";
|
|
$db_results = Dba::read($sql, array($file));
|
|
$results = Dba::fetch_assoc($db_results);
|
|
|
|
if (isset($results['id'])) {
|
|
$songs[] = $results['id'];
|
|
} else {
|
|
// Not found in absolute path, create it from relative path
|
|
$file = $pinfo['dirname'] . DIRECTORY_SEPARATOR . $file;
|
|
// Normalize the file path. realpath requires the files to exists.
|
|
$file = realpath($file);
|
|
if ($file) {
|
|
$sql = "SELECT `id` FROM `song` WHERE `file` = ?";
|
|
$db_results = Dba::read($sql, array($file));
|
|
$results = Dba::fetch_assoc($db_results);
|
|
|
|
if (isset($results['id'])) {
|
|
$songs[] = $results['id'];
|
|
}
|
|
}
|
|
}
|
|
} // if it's a file
|
|
}
|
|
}
|
|
|
|
debug_event('import_playlist', "Parsed " . $playlist . ", found " . count($songs) . " songs", 5);
|
|
|
|
if (count($songs)) {
|
|
$name = $pinfo['extension'] . " - " . $pinfo['filename'];
|
|
$playlist_id = Playlist::create($name, 'public');
|
|
|
|
if (!$playlist_id) {
|
|
return array(
|
|
'success' => false,
|
|
'error' => T_('Failed to create playlist.'),
|
|
);
|
|
}
|
|
|
|
/* Recreate the Playlist */
|
|
$playlist = new Playlist($playlist_id);
|
|
$playlist->add_songs($songs, true);
|
|
|
|
return array(
|
|
'success' => true,
|
|
'id' => $playlist_id,
|
|
'count' => count($songs)
|
|
);
|
|
}
|
|
|
|
return array(
|
|
'success' => false,
|
|
'error' => T_('No valid songs found in playlist file.')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* parse_m3u
|
|
* this takes m3u filename and then attempts to found song filenames listed in the m3u
|
|
* @param string $data
|
|
* @return array
|
|
*/
|
|
public static function parse_m3u($data)
|
|
{
|
|
$files = array();
|
|
$results = explode("\n", $data);
|
|
|
|
foreach ($results as $value) {
|
|
$value = trim($value);
|
|
if (!empty($value) && substr($value, 0, 1) != '#') {
|
|
$files[] = $value;
|
|
}
|
|
}
|
|
|
|
return $files;
|
|
} // parse_m3u
|
|
|
|
/**
|
|
* parse_pls
|
|
* this takes pls filename and then attempts to found song filenames listed in the pls
|
|
* @param string $data
|
|
* @return array
|
|
*/
|
|
public static function parse_pls($data)
|
|
{
|
|
$files = array();
|
|
$results = explode("\n", $data);
|
|
|
|
foreach ($results as $value) {
|
|
$value = trim($value);
|
|
if (preg_match("/file[0-9]+[\s]*\=(.*)/i", $value, $matches)) {
|
|
$file = trim($matches[1]);
|
|
if (!empty($file)) {
|
|
$files[] = $file;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $files;
|
|
} // parse_pls
|
|
|
|
/**
|
|
* parse_asx
|
|
* this takes asx filename and then attempts to found song filenames listed in the asx
|
|
* @param string $data
|
|
* @return array
|
|
*/
|
|
public static function parse_asx($data)
|
|
{
|
|
$files = array();
|
|
$xml = simplexml_load_string($data);
|
|
|
|
if ($xml) {
|
|
foreach ($xml->entry as $entry) {
|
|
$file = trim($entry->ref['href']);
|
|
if (!empty($file)) {
|
|
$files[] = $file;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $files;
|
|
} // parse_asx
|
|
|
|
/**
|
|
* parse_xspf
|
|
* this takes xspf filename and then attempts to found song filenames listed in the xspf
|
|
* @param string $data
|
|
* @return array
|
|
*/
|
|
public static function parse_xspf($data)
|
|
{
|
|
$files = array();
|
|
$xml = simplexml_load_string($data);
|
|
if ($xml) {
|
|
foreach ($xml->trackList->track as $track) {
|
|
$file = trim($track->location);
|
|
if (!empty($file)) {
|
|
$files[] = $file;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $files;
|
|
} // parse_xspf
|
|
|
|
/**
|
|
* delete
|
|
* Deletes the catalog and everything associated with it
|
|
* it takes the catalog id
|
|
* @param int $catalog_id
|
|
*/
|
|
public static function delete($catalog_id)
|
|
{
|
|
// Large catalog deletion can take time
|
|
set_time_limit(0);
|
|
|
|
// First remove the songs in this catalog
|
|
$sql = "DELETE FROM `song` WHERE `catalog` = ?";
|
|
$db_results = Dba::write($sql, array($catalog_id));
|
|
|
|
// Only if the previous one works do we go on
|
|
if (!$db_results) {
|
|
return false;
|
|
}
|
|
|
|
$sql = "DELETE FROM `video` WHERE `catalog` = ?";
|
|
$db_results = Dba::write($sql, array($catalog_id));
|
|
|
|
if (!$db_results) {
|
|
return false;
|
|
}
|
|
|
|
$catalog = self::create_from_id($catalog_id);
|
|
|
|
$sql = 'DELETE FROM `catalog_' . $catalog->get_type() . '` WHERE catalog_id = ?';
|
|
$db_results = Dba::write($sql, array($catalog_id));
|
|
|
|
if (!$db_results) {
|
|
return false;
|
|
}
|
|
|
|
// Next Remove the Catalog Entry it's self
|
|
$sql = "DELETE FROM `catalog` WHERE `id` = ?";
|
|
Dba::write($sql, array($catalog_id));
|
|
|
|
// Run the cleaners...
|
|
self::gc();
|
|
} // delete
|
|
|
|
/**
|
|
* exports the catalog
|
|
* it exports all songs in the database to the given export type.
|
|
* @param string $type
|
|
* @param int|null $catalog_id
|
|
*/
|
|
public static function export($type, $catalog_id =null)
|
|
{
|
|
// Select all songs in catalog
|
|
$params = array();
|
|
if ($catalog_id) {
|
|
$sql = 'SELECT `id` FROM `song` ' .
|
|
"WHERE `catalog`= ? " .
|
|
'ORDER BY `album`, `track`';
|
|
$params[] = $catalog_id;
|
|
} else {
|
|
$sql = 'SELECT `id` FROM `song` ORDER BY `album`, `track`';
|
|
}
|
|
$db_results = Dba::read($sql, $params);
|
|
|
|
switch ($type) {
|
|
case 'itunes':
|
|
echo xml_get_header('itunes');
|
|
while ($results = Dba::fetch_assoc($db_results)) {
|
|
$song = new Song($results['id']);
|
|
$song->format();
|
|
|
|
$xml = array();
|
|
$xml['key']= $results['id'];
|
|
$xml['dict']['Track ID']= intval($results['id']);
|
|
$xml['dict']['Name'] = $song->title;
|
|
$xml['dict']['Artist'] = $song->f_artist_full;
|
|
$xml['dict']['Album'] = $song->f_album_full;
|
|
$xml['dict']['Total Time'] = intval($song->time) * 1000; // iTunes uses milliseconds
|
|
$xml['dict']['Track Number'] = intval($song->track);
|
|
$xml['dict']['Year'] = intval($song->year);
|
|
$xml['dict']['Date Added'] = date("Y-m-d\TH:i:s\Z", $song->addition_time);
|
|
$xml['dict']['Bit Rate'] = intval($song->bitrate/1000);
|
|
$xml['dict']['Sample Rate'] = intval($song->rate);
|
|
$xml['dict']['Play Count'] = intval($song->played);
|
|
$xml['dict']['Track Type'] = "URL";
|
|
$xml['dict']['Location'] = Song::play_url($song->id);
|
|
echo xoutput_from_array($xml, true, 'itunes');
|
|
// flush output buffer
|
|
} // while result
|
|
echo xml_get_footer('itunes');
|
|
|
|
break;
|
|
case 'csv':
|
|
echo "ID,Title,Artist,Album,Length,Track,Year,Date Added,Bitrate,Played,File\n";
|
|
while ($results = Dba::fetch_assoc($db_results)) {
|
|
$song = new Song($results['id']);
|
|
$song->format();
|
|
echo '"' . $song->id . '","' .
|
|
$song->title . '","' .
|
|
$song->f_artist_full . '","' .
|
|
$song->f_album_full .'","' .
|
|
$song->f_time . '","' .
|
|
$song->f_track . '","' .
|
|
$song->year .'","' .
|
|
date("Y-m-d\TH:i:s\Z", $song->addition_time) . '","' .
|
|
$song->f_bitrate .'","' .
|
|
$song->played . '","' .
|
|
$song->file . "\n";
|
|
}
|
|
break;
|
|
} // end switch
|
|
}
|
|
// export
|
|
|
|
/**
|
|
* Updates album tags from given song
|
|
* @param Song $song
|
|
*/
|
|
protected static function updateAlbumTags(Song $song)
|
|
{
|
|
$tags = self::getSongTags('album', $song->album);
|
|
Tag::update_tag_list(implode(',', $tags), 'album', $song->album, true);
|
|
}
|
|
|
|
/**
|
|
* Updates artist tags from given song
|
|
* @param Song $song
|
|
*/
|
|
protected static function updateArtistTags(Song $song)
|
|
{
|
|
$tags = self::getSongTags('artist', $song->artist);
|
|
Tag::update_tag_list(implode(',', $tags), 'artist', $song->artist, true);
|
|
}
|
|
|
|
/**
|
|
* Get all tags from all Songs from [type] (artist, album, ...)
|
|
* @param string $type
|
|
* @param integer $id
|
|
* @return array
|
|
*/
|
|
protected static function getSongTags($type, $id)
|
|
{
|
|
$tags = array();
|
|
$db_results = Dba::read('SELECT tag.name FROM tag'
|
|
. ' JOIN tag_map ON tag.id = tag_map.tag_id'
|
|
. ' JOIN song ON tag_map.object_id = song.id'
|
|
. ' WHERE song.' . $type . ' = ? AND tag_map.object_type = "song"'
|
|
. ' GROUP BY tag.id', array($id));
|
|
while ($row = Dba::fetch_assoc($db_results)) {
|
|
$tags[] = $row['name'];
|
|
}
|
|
return $tags;
|
|
}
|
|
}
|
|
|
|
// end of catalog class
|