mirror of
https://github.com/Yetangitu/ampache
synced 2025-10-03 09:49:30 +02:00
2184 lines
77 KiB
PHP
2184 lines
77 KiB
PHP
<?php
|
|
/* vim:set softtabstop=4 shiftwidth=4 expandtab: */
|
|
/**
|
|
*
|
|
* LICENSE: GNU Affero General Public License, version 3 (AGPLv3)
|
|
* Copyright 2001 - 2015 Ampache.org
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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 Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* Subsonic Class
|
|
*
|
|
* This class wrap Ampache to Subsonic API functions. See http://www.subsonic.org/pages/api.jsp
|
|
* These are all static calls.
|
|
*
|
|
* @SuppressWarnings("unused")
|
|
*/
|
|
class Subsonic_Api
|
|
{
|
|
/**
|
|
* constructor
|
|
* This really isn't anything to do here, so it's private
|
|
*/
|
|
private function __construct()
|
|
{
|
|
}
|
|
|
|
public static function check_version($input, $version = "1.0.0", $addheader = false)
|
|
{
|
|
// We cannot check client version unfortunately. Most Subsonic client sent a dummy client version...
|
|
/*if (version_compare($input['v'], $version) < 0) {
|
|
ob_end_clean();
|
|
if ($addheader) self::setHeader($input['f']);
|
|
self::apiOutput($input, Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_APIVERSION_CLIENT));
|
|
exit;
|
|
}*/
|
|
}
|
|
|
|
public static function check_parameter($input, $parameter, $addheader = false)
|
|
{
|
|
if (empty($input[$parameter])) {
|
|
ob_end_clean();
|
|
if ($addheader) {
|
|
self::setHeader($input['f']);
|
|
}
|
|
self::apiOutput($input, Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_MISSINGPARAM));
|
|
exit;
|
|
}
|
|
|
|
return $input[$parameter];
|
|
}
|
|
|
|
public static function decrypt_password($password)
|
|
{
|
|
// Decode hex-encoded password
|
|
$encpwd = strpos($password, "enc:");
|
|
if ($encpwd !== false) {
|
|
$hex = substr($password, 4);
|
|
$decpwd = '';
|
|
for ($i=0; $i<strlen($hex); $i+=2) {
|
|
$decpwd .= chr(hexdec(substr($hex,$i,2)));
|
|
}
|
|
$password = $decpwd;
|
|
}
|
|
return $password;
|
|
}
|
|
|
|
public static function output_body($ch, $data)
|
|
{
|
|
echo $data;
|
|
ob_flush();
|
|
|
|
return strlen($data);
|
|
}
|
|
|
|
public static function output_header($ch, $header)
|
|
{
|
|
$rheader = trim($header);
|
|
$rhpart = explode(':', $rheader);
|
|
if (!empty($rheader) && count($rhpart) > 1) {
|
|
if ($rhpart[0] != "Transfer-Encoding") {
|
|
header($rheader);
|
|
}
|
|
} else {
|
|
if (substr($header, 0, 5) === "HTTP/") {
|
|
// if $header starts with HTTP/ assume it's the status line
|
|
http_response_code(curl_getinfo($ch,CURLINFO_HTTP_CODE));
|
|
}
|
|
}
|
|
return strlen($header);
|
|
}
|
|
|
|
public static function follow_stream($url)
|
|
{
|
|
set_time_limit(0);
|
|
ob_end_clean();
|
|
|
|
header("Access-Control-Allow-Origin: *");
|
|
if (function_exists('curl_version')) {
|
|
$headers = apache_request_headers();
|
|
$reqheaders = array();
|
|
$reqheaders[] = "User-Agent: " . $headers['User-Agent'];
|
|
if (isset($headers['Range'])) {
|
|
$reqheaders[] = "Range: " . $headers['Range'];
|
|
}
|
|
// Curl support, we stream transparently to avoid redirect. Redirect can fail on few clients
|
|
debug_event('subsonic', 'Stream proxy: ' . $url, 5);
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, array(
|
|
CURLOPT_HTTPHEADER => $reqheaders,
|
|
CURLOPT_HEADER => false,
|
|
CURLOPT_RETURNTRANSFER => false,
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
CURLOPT_WRITEFUNCTION => array('Subsonic_Api', 'output_body'),
|
|
CURLOPT_HEADERFUNCTION => array('Subsonic_Api', 'output_header'),
|
|
// Ignore invalid certificate
|
|
// Default trusted chain is crap anyway and currently no custom CA option
|
|
CURLOPT_SSL_VERIFYPEER => false,
|
|
CURLOPT_SSL_VERIFYHOST => false,
|
|
CURLOPT_TIMEOUT => 0
|
|
));
|
|
curl_exec($ch);
|
|
curl_close($ch);
|
|
} else {
|
|
// Stream media using http redirect if no curl support
|
|
|
|
// Bug fix for android clients looking for /rest/ in destination url
|
|
// Warning: external catalogs will not work!
|
|
$url = str_replace('/play/', '/rest/fake/', $url);
|
|
header("Location: " . $url);
|
|
}
|
|
}
|
|
|
|
public static function setHeader($f)
|
|
{
|
|
if (strtolower($f) == "json") {
|
|
header("Content-type: application/json; charset=" . AmpConfig::get('site_charset'));
|
|
Subsonic_XML_Data::$enable_json_checks = true;
|
|
} elseif (strtolower($f) == "jsonp") {
|
|
header("Content-type: text/javascript; charset=" . AmpConfig::get('site_charset'));
|
|
Subsonic_XML_Data::$enable_json_checks = true;
|
|
} else {
|
|
header("Content-type: text/xml; charset=" . AmpConfig::get('site_charset'));
|
|
}
|
|
header("Access-Control-Allow-Origin: *");
|
|
}
|
|
|
|
public static function apiOutput($input, $xml)
|
|
{
|
|
$f = $input['f'];
|
|
$callback = $input['callback'];
|
|
self::apiOutput2(strtolower($f), $xml, $callback);
|
|
}
|
|
|
|
public static function apiOutput2($f, $xml, $callback='')
|
|
{
|
|
if ($f == "json") {
|
|
$output = json_encode(self::xml2json($xml), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);
|
|
} else {
|
|
if ($f == "jsonp") {
|
|
$output = $callback . '(' . json_encode(self::xml2json($xml), JSON_PRETTY_PRINT) . ')';
|
|
} else {
|
|
$xmlstr = $xml->asXml();
|
|
// Format xml output
|
|
$dom = new DOMDocument();
|
|
$dom->loadXML($xmlstr);
|
|
$dom->formatOutput = true;
|
|
$output = $dom->saveXML();
|
|
}
|
|
}
|
|
|
|
echo $output;
|
|
}
|
|
|
|
/**
|
|
* xml2json based from http://outlandish.com/blog/xml-to-json/
|
|
* Because we cannot use only json_encode to respect JSON Subsonic API
|
|
*/
|
|
private static function xml2json($xml, $options = array())
|
|
{
|
|
$defaults = array(
|
|
'namespaceSeparator' => ':',//you may want this to be something other than a colon
|
|
'attributePrefix' => '', //to distinguish between attributes and nodes with the same name
|
|
'alwaysArray' => array(), //array of xml tag names which should always become arrays
|
|
'autoArray' => true, //only create arrays for tags which appear more than once
|
|
'textContent' => 'value', //key used for the text content of elements
|
|
'autoText' => true, //skip textContent key if node has no attributes or child nodes
|
|
'keySearch' => false, //optional search and replace on tag and attribute names
|
|
'keyReplace' => false, //replace values for above search values (as passed to str_replace())
|
|
'boolean' => true //replace true and false string with boolean values
|
|
);
|
|
$options = array_merge($defaults, $options);
|
|
$namespaces = $xml->getDocNamespaces();
|
|
$namespaces[''] = null; //add base (empty) namespace
|
|
|
|
//get attributes from all namespaces
|
|
$attributesArray = array();
|
|
foreach ($namespaces as $prefix => $namespace) {
|
|
foreach ($xml->attributes($namespace) as $attributeName => $attribute) {
|
|
//replace characters in attribute name
|
|
if ($options['keySearch']) {
|
|
$attributeName =
|
|
str_replace($options['keySearch'], $options['keyReplace'], $attributeName);
|
|
}
|
|
$attributeKey = $options['attributePrefix']
|
|
. ($prefix ? $prefix . $options['namespaceSeparator'] : '')
|
|
. $attributeName;
|
|
$strattr = (string) $attribute;
|
|
if ($options['boolean'] && ($strattr == "true" || $strattr == "false")) {
|
|
$vattr = ($strattr == "true");
|
|
} else {
|
|
$vattr = $strattr;
|
|
}
|
|
$attributesArray[$attributeKey] = $vattr;
|
|
}
|
|
}
|
|
|
|
//get child nodes from all namespaces
|
|
$tagsArray = array();
|
|
foreach ($namespaces as $prefix => $namespace) {
|
|
foreach ($xml->children($namespace) as $childXml) {
|
|
//recurse into child nodes
|
|
$childArray = self::xml2json($childXml, $options);
|
|
list($childTagName, $childProperties) = each($childArray);
|
|
|
|
//replace characters in tag name
|
|
if ($options['keySearch']) {
|
|
$childTagName =
|
|
str_replace($options['keySearch'], $options['keyReplace'], $childTagName);
|
|
}
|
|
//add namespace prefix, if any
|
|
if ($prefix) {
|
|
$childTagName = $prefix . $options['namespaceSeparator'] . $childTagName;
|
|
}
|
|
|
|
if (!isset($tagsArray[$childTagName])) {
|
|
//only entry with this key
|
|
//test if tags of this type should always be arrays, no matter the element count
|
|
$tagsArray[$childTagName] =
|
|
in_array($childTagName, $options['alwaysArray']) || !$options['autoArray']
|
|
? array($childProperties) : $childProperties;
|
|
} elseif (
|
|
is_array($tagsArray[$childTagName]) && array_keys($tagsArray[$childTagName])
|
|
=== range(0, count($tagsArray[$childTagName]) - 1)
|
|
) {
|
|
//key already exists and is integer indexed array
|
|
$tagsArray[$childTagName][] = $childProperties;
|
|
} else {
|
|
//key exists so convert to integer indexed array with previous value in position 0
|
|
$tagsArray[$childTagName] = array($tagsArray[$childTagName], $childProperties);
|
|
}
|
|
}
|
|
}
|
|
|
|
//get text content of node
|
|
$textContentArray = array();
|
|
$plainText = trim((string) $xml);
|
|
if ($plainText !== '') {
|
|
$textContentArray[$options['textContent']] = $plainText;
|
|
}
|
|
|
|
//stick it all together
|
|
$propertiesArray = !$options['autoText'] || $attributesArray || $tagsArray || ($plainText === '')
|
|
? array_merge($attributesArray, $tagsArray, $textContentArray) : $plainText;
|
|
|
|
if (isset($propertiesArray['xmlns'])) {
|
|
unset($propertiesArray['xmlns']);
|
|
}
|
|
//return node as array
|
|
return array(
|
|
$xml->getName() => $propertiesArray
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* ping
|
|
* Simple server ping to test connectivity with the server.
|
|
* Takes no parameter.
|
|
*/
|
|
public static function ping($input)
|
|
{
|
|
// Don't check client API version here. Some client give version 0.0.0 for ping command
|
|
|
|
self::apiOutput($input, Subsonic_XML_Data::createSuccessResponse());
|
|
}
|
|
|
|
/**
|
|
* getLicense
|
|
* Get details about the software license. Always return a valid default license.
|
|
* Takes no parameter.
|
|
*/
|
|
public static function getlicense($input)
|
|
{
|
|
self::check_version($input);
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addLicense($r);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getMusicFolders
|
|
* Get all configured top-level music folders (= ampache catalogs).
|
|
* Takes no parameter.
|
|
*/
|
|
public static function getmusicfolders($input)
|
|
{
|
|
self::check_version($input);
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addMusicFolders($r, Catalog::get_catalogs());
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getIndexes
|
|
* Get an indexed structure of all artists.
|
|
* Takes optional musicFolderId and optional ifModifiedSince in parameters.
|
|
*/
|
|
public static function getindexes($input)
|
|
{
|
|
self::check_version($input);
|
|
set_time_limit(300);
|
|
|
|
$musicFolderId = $input['musicFolderId'];
|
|
$ifModifiedSince = $input['ifModifiedSince'];
|
|
|
|
$catalogs = array();
|
|
if (!empty($musicFolderId) && $musicFolderId != '-1') {
|
|
$catalogs[] = $musicFolderId;
|
|
} else {
|
|
$catalogs = Catalog::get_catalogs();
|
|
}
|
|
|
|
$lastmodified = 0;
|
|
$fcatalogs = array();
|
|
|
|
foreach ($catalogs as $id) {
|
|
$clastmodified = 0;
|
|
$catalog = Catalog::create_from_id($id);
|
|
|
|
if ($catalog->last_update > $clastmodified) {
|
|
$clastmodified = $catalog->last_update;
|
|
}
|
|
if ($catalog->last_add > $clastmodified) {
|
|
$clastmodified = $catalog->last_add;
|
|
}
|
|
if ($catalog->last_clean > $clastmodified) {
|
|
$clastmodified = $catalog->last_clean;
|
|
}
|
|
|
|
if ($clastmodified > $lastmodified) {
|
|
$lastmodified = $clastmodified;
|
|
}
|
|
if (!empty($ifModifiedSince) && $clastmodified > ($ifModifiedSince / 1000)) {
|
|
$fcatalogs[] = $id;
|
|
}
|
|
}
|
|
if (empty($ifModifiedSince)) {
|
|
$fcatalogs = $catalogs;
|
|
}
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
if (count($fcatalogs) > 0) {
|
|
$artists = Catalog::get_artists($fcatalogs);
|
|
Subsonic_XML_Data::addArtistsIndexes($r, $artists, $lastmodified);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getMusicDirectory
|
|
* Get a list of all files in a music directory.
|
|
* Takes the directory id in parameters.
|
|
*/
|
|
public static function getmusicdirectory($input)
|
|
{
|
|
self::check_version($input);
|
|
|
|
$id = self::check_parameter($input, 'id');
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
if (Subsonic_XML_Data::isArtist($id)) {
|
|
$artist = new Artist(Subsonic_XML_Data::getAmpacheId($id));
|
|
Subsonic_XML_Data::addArtistDirectory($r, $artist);
|
|
} else {
|
|
if (Subsonic_XML_Data::isAlbum($id)) {
|
|
$album = new Album(Subsonic_XML_Data::getAmpacheId($id));
|
|
Subsonic_XML_Data::addAlbumDirectory($r, $album);
|
|
}
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getGenres
|
|
* Get all genres.
|
|
* Takes no parameter.
|
|
*/
|
|
public static function getgenres($input)
|
|
{
|
|
self::check_version($input, "1.9.0");
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addGenres($r, Tag::get_tags('song'));
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getArtists
|
|
* Get all artists.
|
|
* Takes no parameter.
|
|
*/
|
|
public static function getartists($input)
|
|
{
|
|
self::check_version($input, "1.7.0");
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
$artists = Catalog::get_artists(Catalog::get_catalogs());
|
|
Subsonic_XML_Data::addArtistsRoot($r, $artists, true);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getArtist
|
|
* Get details fro an artist, including a list of albums.
|
|
* Takes the artist id in parameter.
|
|
*/
|
|
public static function getartist($input)
|
|
{
|
|
self::check_version($input, "1.7.0");
|
|
|
|
$artistid = self::check_parameter($input, 'id');
|
|
|
|
$artist = new Artist(Subsonic_XML_Data::getAmpacheId($artistid));
|
|
if (empty($artist->name)) {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND, "Artist not found.");
|
|
} else {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addArtist($r, $artist, true, true);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getAlbum
|
|
* Get details for an album, including a list of songs.
|
|
* Takes the album id in parameter.
|
|
*/
|
|
public static function getalbum($input)
|
|
{
|
|
self::check_version($input, "1.7.0");
|
|
|
|
$albumid = self::check_parameter($input, 'id');
|
|
|
|
$addAmpacheInfo = ($input['ampache'] == "1");
|
|
|
|
$album = new Album(Subsonic_XML_Data::getAmpacheId($albumid));
|
|
if (empty($album->name)) {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND, "Album not found.");
|
|
} else {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addAlbum($r, $album, true, $addAmpacheInfo);
|
|
}
|
|
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getVideos
|
|
* Get all videos.
|
|
* Takes no parameter.
|
|
*/
|
|
public static function getvideos($input)
|
|
{
|
|
self::check_version($input, "1.7.0");
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
$videos = Catalog::get_videos();
|
|
Subsonic_XML_Data::addVideos($r, $videos);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getAlbumList
|
|
* Get a list of random, newest, highest rated etc. albums.
|
|
* Takes the list type with optional size and offset in parameters.
|
|
*/
|
|
public static function getalbumlist($input, $elementName="albumList")
|
|
{
|
|
self::check_version($input, "1.2.0");
|
|
|
|
$type = self::check_parameter($input, 'type');
|
|
|
|
$size = $input['size'];
|
|
$offset = $input['offset'];
|
|
$musicFolderId = $input['musicFolderId'] ?: 0;
|
|
|
|
// Get albums from all catalogs by default
|
|
// Catalog filter is not supported for all request type for now.
|
|
$catalogs = null;
|
|
if ($musicFolderId > 0) {
|
|
$catalogs = array();
|
|
$catalogs[] = $musicFolderId;
|
|
}
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
$errorOccured = false;
|
|
$albums = array();
|
|
|
|
if ($type == "random") {
|
|
$albums = Album::get_random($size);
|
|
} else {
|
|
if ($type == "newest") {
|
|
$albums = Stats::get_newest("album", $size, $offset, $musicFolderId);
|
|
} else {
|
|
if ($type == "highest") {
|
|
$albums = Rating::get_highest("album", $size, $offset);
|
|
} else {
|
|
if ($type == "frequent") {
|
|
$albums = Stats::get_top("album", $size, '', $offset);
|
|
} else {
|
|
if ($type == "recent") {
|
|
$albums = Stats::get_recent("album", $size, $offset);
|
|
} else {
|
|
if ($type == "starred") {
|
|
$albums = Userflag::get_latest('album');
|
|
} else {
|
|
if ($type == "alphabeticalByName") {
|
|
$albums = Catalog::get_albums($size, $offset, $catalogs);
|
|
} else {
|
|
if ($type == "alphabeticalByArtist") {
|
|
$albums = Catalog::get_albums_by_artist($size, $offset, $catalogs);
|
|
} else {
|
|
if ($type == "byYear") {
|
|
$fromYear = $input['fromYear'];
|
|
$toYear = $input['toYear'];
|
|
|
|
if ($fromYear || $toYear) {
|
|
$search = array();
|
|
$search['limit'] = $size;
|
|
$search['offset'] = $offset;
|
|
$search['type'] = "album";
|
|
$i = 0;
|
|
if ($fromYear) {
|
|
$search['rule_' . $i . '_input'] = $fromYear;
|
|
$search['rule_' . $i . '_operator'] = 0;
|
|
$search['rule_' . $i . ''] = "year";
|
|
++$i;
|
|
}
|
|
if ($toYear) {
|
|
$search['rule_' . $i . '_input'] = $toYear;
|
|
$search['rule_' . $i . '_operator'] = 1;
|
|
$search['rule_' . $i . ''] = "year";
|
|
++$i;
|
|
}
|
|
|
|
$query = new Search(null, 'album');
|
|
$albums = $query->run($search);
|
|
}
|
|
} else {
|
|
if ($type == "byGenre") {
|
|
$genre = self::check_parameter($input, 'genre');
|
|
|
|
$tag_id = Tag::tag_exists($genre);
|
|
if ($tag_id) {
|
|
$albums = Tag::get_tag_objects('album', $tag_id, $size, $offset);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_GENERIC, "Invalid list type: " . scrub_out($type));
|
|
$errorOccured = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$errorOccured) {
|
|
Subsonic_XML_Data::addAlbumList($r, $albums, $elementName);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getAlbumList2
|
|
* See getAlbumList.
|
|
*/
|
|
public static function getalbumlist2($input)
|
|
{
|
|
self::check_version($input, "1.7.0");
|
|
self::getAlbumList($input, "albumList2");
|
|
}
|
|
|
|
/**
|
|
* getRandomSongs
|
|
* Get random songs matching the given criteria.
|
|
* Takes the optional size, genre, fromYear, toYear and music folder id in parameters.
|
|
*/
|
|
public static function getrandomsongs($input)
|
|
{
|
|
self::check_version($input, "1.2.0");
|
|
|
|
$size = $input['size'];
|
|
if (!$size) {
|
|
$size = 10;
|
|
}
|
|
$genre = $input['genre'];
|
|
$fromYear = $input['fromYear'];
|
|
$toYear = $input['toYear'];
|
|
$musicFolderId = $input['musicFolderId'];
|
|
|
|
$search = array();
|
|
$search['limit'] = $size;
|
|
$search['random'] = $size;
|
|
$search['type'] = "song";
|
|
$i = 0;
|
|
if ($genre) {
|
|
$search['rule_' . $i . '_input'] = $genre;
|
|
$search['rule_' . $i . '_operator'] = 0;
|
|
$search['rule_' . $i . ''] = "tag";
|
|
++$i;
|
|
}
|
|
if ($fromYear) {
|
|
$search['rule_' . $i . '_input'] = $fromYear;
|
|
$search['rule_' . $i . '_operator'] = 0;
|
|
$search['rule_' . $i . ''] = "year";
|
|
++$i;
|
|
}
|
|
if ($toYear) {
|
|
$search['rule_' . $i . '_input'] = $toYear;
|
|
$search['rule_' . $i . '_operator'] = 1;
|
|
$search['rule_' . $i . ''] = "year";
|
|
++$i;
|
|
}
|
|
if ($musicFolderId) {
|
|
if (Subsonic_XML_Data::isArtist($musicFolderId)) {
|
|
$artist = new Artist(Subsonic_XML_Data::getAmpacheId($musicFolderId));
|
|
$finput = $artist->name;
|
|
$operator = 4;
|
|
$ftype = "artist";
|
|
} else {
|
|
if (Subsonic_XML_Data::isAlbum($musicFolderId)) {
|
|
$album = new Album(Subsonic_XML_Data::getAmpacheId($musicFolderId));
|
|
$finput = $album->name;
|
|
$operator = 4;
|
|
$ftype = "artist";
|
|
} else {
|
|
$finput = intval($musicFolderId);
|
|
$operator = 0;
|
|
$ftype = "catalog";
|
|
}
|
|
}
|
|
$search['rule_' . $i . '_input'] = $finput;
|
|
$search['rule_' . $i . '_operator'] = $operator;
|
|
$search['rule_' . $i . ''] = $ftype;
|
|
++$i;
|
|
}
|
|
if ($i > 0) {
|
|
$songs = Random::advanced("song", $search);
|
|
} else {
|
|
$songs = Random::get_default($size);
|
|
}
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addRandomSongs($r, $songs);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getSong
|
|
* Get details for a song
|
|
* Takes the song id in parameter.
|
|
*/
|
|
public static function getsong($input)
|
|
{
|
|
self::check_version($input, "1.7.0");
|
|
|
|
$songid = self::check_parameter($input, 'id');
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
$song = new Song(Subsonic_XML_Data::getAmpacheId($songid));
|
|
Subsonic_XML_Data::addSong($r, $song);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getSongsByGenre
|
|
* Get songs in a given genre.
|
|
* Takes the genre with optional count and offset in parameters.
|
|
*/
|
|
public static function getsongsbygenre($input)
|
|
{
|
|
self::check_version($input, "1.9.0");
|
|
|
|
$genre = self::check_parameter($input, 'genre');
|
|
$count = $input['count'];
|
|
$offset = $input['offset'];
|
|
|
|
$tag = Tag::construct_from_name($genre);
|
|
if ($tag->id) {
|
|
$songs = Tag::get_tag_objects("song", $tag->id, $count, $offset);
|
|
} else {
|
|
$songs = array();
|
|
}
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addSongsByGenre($r, $songs);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getNowPlaying
|
|
* Get what is currently being played by all users.
|
|
* Takes no parameter.
|
|
*/
|
|
public static function getnowplaying($input)
|
|
{
|
|
self::check_version($input);
|
|
|
|
$data = Stream::get_now_playing();
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addNowPlaying($r, $data);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* search2
|
|
* Get albums, artists and songs matching the given criteria.
|
|
* Takes query with optional artist count, artist offset, album count, album offset, song count and song offset in parameters.
|
|
*/
|
|
public static function search2($input, $elementName="searchResult2")
|
|
{
|
|
self::check_version($input, "1.2.0");
|
|
|
|
$query = self::check_parameter($input, 'query');
|
|
|
|
$operator = 0;
|
|
if (strlen($query) > 1) {
|
|
if (substr($query, -1) == "*") {
|
|
$query = substr($query, 0, -1);
|
|
$operator = 2; // Start with
|
|
}
|
|
}
|
|
|
|
$artistCount = isset($input['artistCount']) ? $input['artistCount'] : 20;
|
|
$artistOffset = $input['artistOffset'];
|
|
$albumCount = isset($input['albumCount']) ? $input['albumCount'] : 20;
|
|
$albumOffset = $input['albumOffset'];
|
|
$songCount = isset($input['songCount']) ? $input['songCount'] : 20;
|
|
$songOffset = $input['songOffset'];
|
|
|
|
$sartist = array();
|
|
$sartist['limit'] = $artistCount;
|
|
if ($artistOffset) {
|
|
$sartist['offset'] = $artistOffset;
|
|
}
|
|
$sartist['rule_1_input'] = $query;
|
|
$sartist['rule_1_operator'] = $operator;
|
|
$sartist['rule_1'] = "name";
|
|
$sartist['type'] = "artist";
|
|
if ($artistCount > 0) {
|
|
$artists = Search::run($sartist);
|
|
}
|
|
|
|
$salbum = array();
|
|
$salbum['limit'] = $albumCount;
|
|
if ($albumOffset) {
|
|
$salbum['offset'] = $albumOffset;
|
|
}
|
|
$salbum['rule_1_input'] = $query;
|
|
$salbum['rule_1_operator'] = $operator;
|
|
$salbum['rule_1'] = "title";
|
|
$salbum['type'] = "album";
|
|
if ($albumCount > 0) {
|
|
$albums = Search::run($salbum);
|
|
}
|
|
|
|
$ssong = array();
|
|
$ssong['limit'] = $songCount;
|
|
if ($songOffset) {
|
|
$ssong['offset'] = $songOffset;
|
|
}
|
|
$ssong['rule_1_input'] = $query;
|
|
$ssong['rule_1_operator'] = $operator;
|
|
$ssong['rule_1'] = "anywhere";
|
|
$ssong['type'] = "song";
|
|
if ($songCount > 0) {
|
|
$songs = Search::run($ssong);
|
|
}
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addSearchResult($r, $artists, $albums, $songs, $elementName);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* search3
|
|
* See search2.
|
|
*/
|
|
public static function search3($input)
|
|
{
|
|
self::check_version($input, "1.7.0");
|
|
self::search2($input, "searchResult3");
|
|
}
|
|
|
|
/**
|
|
* getPlaylists
|
|
* Get all playlists a user is allowed to play.
|
|
* Takes optional user in parameter.
|
|
*/
|
|
public static function getplaylists($input)
|
|
{
|
|
self::check_version($input);
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
$username = $input['username'];
|
|
|
|
// Don't allow playlist listing for another user
|
|
if (empty($username) || $username == $GLOBALS['user']->username) {
|
|
Subsonic_XML_Data::addPlaylists($r, Playlist::get_playlists(), Search::get_searches());
|
|
} else {
|
|
$user = User::get_from_username($username);
|
|
if ($user->id) {
|
|
Subsonic_XML_Data::addPlaylists($r, Playlist::get_users($user->id));
|
|
} else {
|
|
Subsonic_XML_Data::addPlaylists($r, array());
|
|
}
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getPlaylist
|
|
* Get the list of files in a saved playlist.
|
|
* Takes the playlist id in parameters.
|
|
*/
|
|
public static function getplaylist($input)
|
|
{
|
|
self::check_version($input);
|
|
|
|
$playlistid = self::check_parameter($input, 'id');
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
if (Subsonic_XML_Data::isSmartPlaylist($playlistid)) {
|
|
$playlist = new Search(Subsonic_XML_Data::getAmpacheId($playlistid), 'song');
|
|
Subsonic_XML_Data::addSmartPlaylist($r, $playlist, true);
|
|
} else {
|
|
$playlist = new Playlist($playlistid);
|
|
Subsonic_XML_Data::addPlaylist($r, $playlist, true);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* createPlaylist
|
|
* Create (or updates) a playlist.
|
|
* Takes playlist id in parameter if updating, name in parameter if creating and a list of song id for the playlist.
|
|
*/
|
|
public static function createplaylist($input)
|
|
{
|
|
self::check_version($input, "1.2.0");
|
|
|
|
$playlistId = $input['playlistId'];
|
|
$name = $input['name'];
|
|
$songId = $input['songId'];
|
|
|
|
if ($playlistId) {
|
|
self::_updatePlaylist($playlistId, $name, $songId);
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
if (!empty($name)) {
|
|
$playlistId = Playlist::create($name, 'private');
|
|
if (count($songId) > 0) {
|
|
self::_updatePlaylist($playlistId, "", $songId);
|
|
}
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_MISSINGPARAM);
|
|
}
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
private static function _updatePlaylist($id, $name, $songsIdToAdd = array(), $songIndexToRemove = array(), $public = true)
|
|
{
|
|
$playlist = new Playlist($id);
|
|
|
|
$newdata = array();
|
|
$newdata['name'] = (!empty($name)) ? $name : $playlist->name;
|
|
$newdata['pl_type'] = ($public) ? "public" : "private";
|
|
$playlist->update($newdata);
|
|
|
|
if ($songsIdToAdd) {
|
|
if (!is_array($songsIdToAdd)) {
|
|
$songsIdToAdd = array($songsIdToAdd);
|
|
}
|
|
if (count($songsIdToAdd) > 0) {
|
|
for ($i = 0; $i < count($songsIdToAdd); ++$i) {
|
|
$songsIdToAdd[$i] = Subsonic_XML_Data::getAmpacheId($songsIdToAdd[$i]);
|
|
}
|
|
$playlist->add_songs($songsIdToAdd);
|
|
}
|
|
}
|
|
|
|
if ($songIndexToRemove) {
|
|
if (!is_array($songIndexToRemove)) {
|
|
$songIndexToRemove = array($songIndexToRemove);
|
|
}
|
|
if (count($songIndexToRemove) > 0) {
|
|
foreach ($songIndexToRemove as $track) {
|
|
$playlist->delete_track_number($track);
|
|
}
|
|
$playlist->regenerate_track_numbers();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* updatePlaylist
|
|
* Update a playlist.
|
|
* Takes playlist id in parameter with optional name, comment, public level and a list of song id to add/remove.
|
|
*/
|
|
public static function updateplaylist($input)
|
|
{
|
|
self::check_version($input, "1.7.0");
|
|
|
|
$playlistId = self::check_parameter($input, 'playlistId');
|
|
|
|
$name = $input['name'];
|
|
$comment = $input['comment']; // Not supported.
|
|
$public = ($input['public'] === "true");
|
|
|
|
if (!Subsonic_XML_Data::isSmartPlaylist($playlistId)) {
|
|
$songIdToAdd = $input['songIdToAdd'];
|
|
$songIndexToRemove = $input['songIndexToRemove'];
|
|
|
|
self::_updatePlaylist(Subsonic_XML_Data::getAmpacheId($playlistId), $name, $songIdToAdd, $songIndexToRemove, $public);
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED, 'Cannot edit a smart playlist.');
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* deletePlaylist
|
|
* Delete a saved playlist.
|
|
* Takes playlist id in parameter.
|
|
*/
|
|
public static function deleteplaylist($input)
|
|
{
|
|
self::check_version($input, "1.2.0");
|
|
|
|
$playlistId = self::check_parameter($input, 'id');
|
|
|
|
if (Subsonic_XML_Data::isSmartPlaylist($playlistId)) {
|
|
$playlist = new Search(Subsonic_XML_Data::getAmpacheId($playlistId), 'song');
|
|
$playlist->delete();
|
|
} else {
|
|
$playlist = new Playlist(Subsonic_XML_Data::getAmpacheId($playlistId));
|
|
$playlist->delete();
|
|
}
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* stream
|
|
* Streams a given media file.
|
|
* Takes the file id in parameter with optional max bit rate, file format, time offset, size and estimate content length option.
|
|
*/
|
|
public static function stream($input)
|
|
{
|
|
self::check_version($input, "1.0.0", true);
|
|
|
|
$fileid = self::check_parameter($input, 'id', true);
|
|
|
|
$maxBitRate = $input['maxBitRate'];
|
|
$format = $input['format']; // mp3, flv or raw
|
|
$timeOffset = $input['timeOffset'];
|
|
$size = $input['size']; // For video streaming. Not supported.
|
|
$estimateContentLength = $input['estimateContentLength']; // Force content-length guessing if transcode
|
|
|
|
$params = '&client=' . rawurlencode($input['c']) . '&noscrobble=1';
|
|
if ($estimateContentLength == 'true') {
|
|
$params .= '&content_length=required';
|
|
}
|
|
if ($format && $format != "raw") {
|
|
$params .= '&transcode_to=' . $format;
|
|
}
|
|
if ($maxBitRate) {
|
|
$params .= '&bitrate=' . $maxBitRate;
|
|
}
|
|
if ($timeOffset) {
|
|
$params .= '&frame=' . $timeOffset;
|
|
}
|
|
|
|
$url = '';
|
|
if (Subsonic_XML_Data::isVideo($fileid)) {
|
|
$url = Video::play_url(Subsonic_XML_Data::getAmpacheId($fileid), $params, 'api', function_exists('curl_version'));
|
|
} elseif (Subsonic_XML_Data::isSong($fileid)) {
|
|
$url = Song::play_url(Subsonic_XML_Data::getAmpacheId($fileid), $params, 'api', function_exists('curl_version'));
|
|
} elseif (Subsonic_XML_Data::isPodcastEp($fileid)) {
|
|
$url = Podcast_Episode::play_url(Subsonic_XML_Data::getAmpacheId($fileid), $params, 'api', function_exists('curl_version'));
|
|
}
|
|
|
|
if (!empty($url)) {
|
|
self::follow_stream($url);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* download
|
|
* Downloads a given media file.
|
|
* Takes the file id in parameter.
|
|
*/
|
|
public static function download($input)
|
|
{
|
|
self::check_version($input, "1.0.0", true);
|
|
|
|
$fileid = self::check_parameter($input, 'id', true);
|
|
|
|
$url = Song::play_url(Subsonic_XML_Data::getAmpacheId($fileid), '&action=download' . '&client=' . rawurlencode($input['c']) . '&noscrobble=1', 'api', function_exists('curl_version'));
|
|
self::follow_stream($url);
|
|
}
|
|
|
|
/**
|
|
* hls
|
|
* Create an HLS playlist.
|
|
* Takes the file id in parameter with optional max bit rate.
|
|
*/
|
|
public static function hls($input)
|
|
{
|
|
self::check_version($input, "1.7.0", true);
|
|
|
|
$fileid = self::check_parameter($input, 'id', true);
|
|
|
|
$bitRate = $input['bitRate'];
|
|
|
|
$media = array();
|
|
$media['object_type'] = 'song';
|
|
$media['object_id'] = Subsonic_XML_Data::getAmpacheId($fileid);
|
|
|
|
$medias = array();
|
|
$medias[] = $media;
|
|
$stream = new Stream_Playlist();
|
|
$additional_params = '';
|
|
if ($bitRate) {
|
|
$additional_params .= '&bitrate=' . $bitRate;
|
|
}
|
|
//$additional_params .= '&transcode_to=ts';
|
|
$stream->add($medias, $additional_params);
|
|
|
|
header('Content-Type: application/vnd.apple.mpegurl;');
|
|
$stream->create_m3u();
|
|
}
|
|
|
|
/**
|
|
* getCoverArt
|
|
* Get a cover art image.
|
|
* Takes the cover art id in parameter.
|
|
*/
|
|
public static function getcoverart($input)
|
|
{
|
|
self::check_version($input, "1.0.0", true);
|
|
|
|
$id = self::check_parameter($input, 'id', true);
|
|
$size = $input['size'];
|
|
|
|
$art = null;
|
|
if (Subsonic_XML_Data::isArtist($id)) {
|
|
$art = new Art(Subsonic_XML_Data::getAmpacheId($id), "artist");
|
|
} elseif (Subsonic_XML_Data::isAlbum($id)) {
|
|
$art = new Art(Subsonic_XML_Data::getAmpacheId($id), "album");
|
|
} elseif (Subsonic_XML_Data::isSong($id)) {
|
|
$art = new Art(Subsonic_XML_Data::getAmpacheId($id), "song");
|
|
if ($art != null && $art->id == null) {
|
|
// in most cases the song doesn't have a picture, but the album where it belongs to has
|
|
// if this is the case, we take the album art
|
|
$song = new Song(Subsonic_XML_Data::getAmpacheId(Subsonic_XML_Data::getAmpacheId($id)));
|
|
$art = new Art(Subsonic_XML_Data::getAmpacheId($song->album), "album");
|
|
}
|
|
} elseif (Subsonic_XML_Data::isPodcast($id)) {
|
|
$art = new Art(Subsonic_XML_Data::getAmpacheId($id), "podcast");
|
|
}
|
|
|
|
header("Access-Control-Allow-Origin: *");
|
|
if ($art != null) {
|
|
$art->get_db();
|
|
if ($size && AmpConfig::get('resize_images')) {
|
|
$dim = array();
|
|
$dim['width'] = $size;
|
|
$dim['height'] = $size;
|
|
$thumb = $art->get_thumb($dim);
|
|
if ($thumb) {
|
|
header('Content-type: ' . $thumb['thumb_mime']);
|
|
header('Content-Length: ' . strlen($thumb['thumb']));
|
|
echo $thumb['thumb'];
|
|
return;
|
|
}
|
|
}
|
|
|
|
header('Content-type: ' . $art->raw_mime);
|
|
header('Content-Length: ' . strlen($art->raw));
|
|
echo $art->raw;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* setRating
|
|
* Sets the rating for a music file.
|
|
* Takes the file id and rating in parameters.
|
|
*/
|
|
public static function setrating($input)
|
|
{
|
|
self::check_version($input, "1.6.0");
|
|
|
|
$id = self::check_parameter($input, 'id');
|
|
$rating = $input['rating'];
|
|
|
|
$robj = null;
|
|
if (Subsonic_XML_Data::isArtist($id)) {
|
|
$robj = new Rating(Subsonic_XML_Data::getAmpacheId($id), "artist");
|
|
} else {
|
|
if (Subsonic_XML_Data::isAlbum($id)) {
|
|
$robj = new Rating(Subsonic_XML_Data::getAmpacheId($id), "album");
|
|
} else {
|
|
if (Subsonic_XML_Data::isSong($id)) {
|
|
$robj = new Rating(Subsonic_XML_Data::getAmpacheId($id), "song");
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($robj != null) {
|
|
$robj->set_rating($rating);
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND, "Media not found.");
|
|
}
|
|
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getStarred
|
|
* Get starred songs, albums and artists.
|
|
* Takes no parameter.
|
|
* Not supported.
|
|
*/
|
|
public static function getstarred($input, $elementName="starred")
|
|
{
|
|
self::check_version($input, "1.7.0");
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addStarred($r, Userflag::get_latest('artist',null,99999), Userflag::get_latest('album',null,99999), Userflag::get_latest('song',null,99999), $elementName);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
|
|
/**
|
|
* getStarred2
|
|
* See getStarred.
|
|
*/
|
|
public static function getstarred2($input)
|
|
{
|
|
self::getStarred($input, "starred2");
|
|
}
|
|
|
|
/**
|
|
* star
|
|
* Attaches a star to a song, album or artist.
|
|
* Takes the optional file id, album id or artist id in parameters.
|
|
* Not supported.
|
|
*/
|
|
public static function star($input)
|
|
{
|
|
self::check_version($input, "1.7.0");
|
|
|
|
self::_setStar($input, true);
|
|
}
|
|
|
|
/**
|
|
* unstar
|
|
* Removes the star from a song, album or artist.
|
|
* Takes the optional file id, album id or artist id in parameters.
|
|
* Not supported.
|
|
*/
|
|
public static function unstar($input)
|
|
{
|
|
self::check_version($input, "1.7.0");
|
|
|
|
self::_setStar($input, false);
|
|
}
|
|
|
|
private static function _setStar($input, $star)
|
|
{
|
|
$id = $input['id'];
|
|
$albumId = $input['albumId'];
|
|
$artistId = $input['artistId'];
|
|
|
|
// Normalize all in one array
|
|
$ids = array();
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
if ($id) {
|
|
if (!is_array($id)) {
|
|
$id = array($id);
|
|
}
|
|
foreach ($id as $i) {
|
|
$aid = Subsonic_XML_Data::getAmpacheId($i);
|
|
if (Subsonic_XML_Data::isArtist($i)) {
|
|
$type = 'artist';
|
|
} else {
|
|
if (Subsonic_XML_Data::isAlbum($i)) {
|
|
$type = 'album';
|
|
} else {
|
|
if (Subsonic_XML_Data::isSong($i)) {
|
|
$type = 'song';
|
|
} else {
|
|
$type = "";
|
|
}
|
|
}
|
|
}
|
|
$ids[] = array('id' => $aid, 'type' => $type);
|
|
}
|
|
} else {
|
|
if ($albumId) {
|
|
if (!is_array($albumId)) {
|
|
$albumId = array($albumId);
|
|
}
|
|
foreach ($albumId as $i) {
|
|
$aid = Subsonic_XML_Data::getAmpacheId($i);
|
|
$ids[] = array('id' => $aid, 'album');
|
|
}
|
|
} else {
|
|
if ($artistId) {
|
|
if (!is_array($artistId)) {
|
|
$artistId = array($artistId);
|
|
}
|
|
foreach ($artistId as $i) {
|
|
$aid = Subsonic_XML_Data::getAmpacheId($i);
|
|
$ids[] = array('id' => $aid, 'artist');
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_MISSINGPARAM);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($ids as $i) {
|
|
$flag = new Userflag($i['id'], $i['type']);
|
|
$flag->set_flag($star);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getUser
|
|
* Get details about a given user.
|
|
* Takes the username in parameter.
|
|
* Not supported.
|
|
*/
|
|
public static function getuser($input)
|
|
{
|
|
self::check_version($input, "1.3.0");
|
|
|
|
$username = self::check_parameter($input, 'username');
|
|
|
|
if ($GLOBALS['user']->access >= 100 || $GLOBALS['user']->username == $username) {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
if ($GLOBALS['user']->username == $username) {
|
|
$user = $GLOBALS['user'];
|
|
} else {
|
|
$user = User::get_from_username($username);
|
|
}
|
|
Subsonic_XML_Data::addUser($r, $user);
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED, $GLOBALS['user']->username . ' is not authorized to get details for other users.');
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getUsers
|
|
* Get details about a given user.
|
|
* Takes no parameter.
|
|
* Not supported.
|
|
*/
|
|
public static function getusers($input)
|
|
{
|
|
self::check_version($input, "1.7.0");
|
|
|
|
if ($GLOBALS['user']->access >= 100) {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
$users = User::get_valid_users();
|
|
Subsonic_XML_Data::addUsers($r, $users);
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED, $GLOBALS['user']->username . ' is not authorized to get details for other users.');
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getAvatar
|
|
* Return the user avatar in bytes.
|
|
*/
|
|
public static function getavatar($input)
|
|
{
|
|
$username = self::check_parameter($input, 'username');
|
|
|
|
$r = null;
|
|
if ($GLOBALS['user']->access >= 100 || $GLOBALS['user']->username == $username) {
|
|
if ($GLOBALS['user']->username == $username) {
|
|
$user = $GLOBALS['user'];
|
|
} else {
|
|
$user = User::get_from_username($username);
|
|
}
|
|
|
|
if ($user !== null) {
|
|
$avatar = $user->get_avatar(true);
|
|
if (isset($avatar['url']) && !empty($avatar['url'])) {
|
|
$request = Requests::get($avatar['url'], array(), Core::requests_options());
|
|
header("Content-Type: " . $request->headers['Content-Type']);
|
|
echo $request->body;
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED, $GLOBALS['user']->username . ' is not authorized to get avatar for other users.');
|
|
}
|
|
|
|
if ($r != null) {
|
|
self::apiOutput($input, $r);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* getInternetRadioStations
|
|
* Get all internet radio stations
|
|
* Takes no parameter.
|
|
*/
|
|
public static function getinternetradiostations($input)
|
|
{
|
|
self::check_version($input, "1.9.0");
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
$radios = Live_Stream::get_all_radios();
|
|
Subsonic_XML_Data::addRadios($r, $radios);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getShares
|
|
* Get information about shared media this user is allowed to manage.
|
|
* Takes no parameter.
|
|
*/
|
|
public static function getshares($input)
|
|
{
|
|
self::check_version($input, "1.6.0");
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
$shares = Share::get_share_list();
|
|
Subsonic_XML_Data::addShares($r, $shares);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* createShare
|
|
* Create a public url that can be used by anyone to stream media.
|
|
* Takes the file id with optional description and expires parameters.
|
|
*/
|
|
public static function createshare($input)
|
|
{
|
|
self::check_version($input, "1.6.0");
|
|
|
|
$id = self::check_parameter($input, 'id');
|
|
$description = $input['description'];
|
|
|
|
if (AmpConfig::get('share')) {
|
|
if (isset($input['expires'])) {
|
|
$expires = $input['expires'];
|
|
// Parse as a string to work on 32-bit computers
|
|
if (strlen($expires) > 3) {
|
|
$expires = intval(substr($expires, 0, - 3));
|
|
}
|
|
$expire_days = round(($expires - time()) / 86400, 0, PHP_ROUND_HALF_EVEN);
|
|
} else {
|
|
$expire_days = AmpConfig::get('share_expire');
|
|
}
|
|
|
|
$object_id = Subsonic_XML_Data::getAmpacheId($id);
|
|
if (Subsonic_XML_Data::isAlbum($id)) {
|
|
$object_type = 'album';
|
|
} else {
|
|
if (Subsonic_XML_Data::isSong($id)) {
|
|
$object_type = 'song';
|
|
}
|
|
}
|
|
|
|
if (!empty($object_type)) {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
$shares = array();
|
|
$shares[] = Share::create_share($object_type, $object_id, true, Access::check_function('download'), $expire_days, Share::generate_secret(), 0, $description);
|
|
Subsonic_XML_Data::addShares($r, $shares);
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* deleteShare
|
|
* Delete an existing share.
|
|
* Takes the share id to delete in parameters.
|
|
*/
|
|
public static function deleteshare($input)
|
|
{
|
|
self::check_version($input, "1.6.0");
|
|
|
|
$id = self::check_parameter($input, 'id');
|
|
|
|
if (AmpConfig::get('share')) {
|
|
if (Share::delete_share($id)) {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* updateShare
|
|
* Update the description and/or expiration date for an existing share.
|
|
* Takes the share id to update with optional description and expires parameters.
|
|
* Not supported.
|
|
*/
|
|
public static function updateshare($input)
|
|
{
|
|
self::check_version($input, "1.6.0");
|
|
|
|
$id = self::check_parameter($input, 'id');
|
|
$description = $input['description'];
|
|
|
|
if (AmpConfig::get('share')) {
|
|
$share = new Share($id);
|
|
if ($share->id > 0) {
|
|
$expires = $share->expire_days;
|
|
if (isset($input['expires'])) {
|
|
// Parse as a string to work on 32-bit computers
|
|
$expires = $input['expires'];
|
|
if (strlen($expires) > 3) {
|
|
$expires = intval(substr($expires, 0, - 3));
|
|
}
|
|
if ($expires > 0) {
|
|
$expires = ($expires - $share->creation_date) / 86400;
|
|
$expires = ceil($expires);
|
|
}
|
|
}
|
|
|
|
$data = array(
|
|
'max_counter' => $share->max_counter,
|
|
'expire' => $expires,
|
|
'allow_stream' => $share->allow_stream,
|
|
'allow_download' => $share->allow_download,
|
|
'description' => $description ?: $share->description,
|
|
);
|
|
if ($share->update($data)) {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* createUser
|
|
* Create a new user.
|
|
* Takes the username, password and email with optional roles in parameters.
|
|
*/
|
|
public static function createuser($input)
|
|
{
|
|
self::check_version($input, "1.1.0");
|
|
|
|
$username = self::check_parameter($input, 'username');
|
|
$password = self::check_parameter($input, 'password');
|
|
$email = self::check_parameter($input, 'email');
|
|
$ldapAuthenticated = $input['ldapAuthenticated'];
|
|
$adminRole = ($input['adminRole'] == 'true');
|
|
//$settingsRole = $input['settingsRole'];
|
|
//$streamRole = $input['streamRole'];
|
|
//$jukeboxRole = $input['jukeboxRole'];
|
|
$downloadRole = ($input['downloadRole'] == 'true');
|
|
$uploadRole = ($input['uploadRole'] == 'true');
|
|
//$playlistRole = $input['playlistRole'];
|
|
$coverArtRole = ($input['coverArtRole'] == 'true');
|
|
//$commentRole = $input['commentRole'];
|
|
//$podcastRole = $input['podcastRole'];
|
|
$shareRole = ($input['shareRole'] == 'true');
|
|
|
|
if (Access::check('interface', 100)) {
|
|
$access = 25;
|
|
if ($adminRole) {
|
|
$access = 100;
|
|
} elseif ($coverArtRole) {
|
|
$access = 75;
|
|
}
|
|
$password = self::decrypt_password($password);
|
|
$user_id = User::create($username, $username, $email, null, $password, $access);
|
|
if ($user_id > 0) {
|
|
if ($downloadRole) {
|
|
Preference::update('download', $user_id, '1');
|
|
}
|
|
if ($uploadRole) {
|
|
Preference::update('allow_upload', $user_id, '1');
|
|
}
|
|
if ($shareRole) {
|
|
Preference::update('share', $user_id, '1');
|
|
}
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* deleteUser
|
|
* Delete an existing user.
|
|
* Takes the username in parameter.
|
|
*/
|
|
public static function deleteuser($input)
|
|
{
|
|
self::check_version($input, "1.3.0");
|
|
|
|
$username = self::check_parameter($input, 'username');
|
|
if (Access::check('interface', 100)) {
|
|
$user = User::get_from_username($username);
|
|
if ($user->id) {
|
|
$user->delete();
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* changePassword
|
|
* Change the password of an existing user.
|
|
* Takes the username with new password in parameters.
|
|
*/
|
|
public static function changepassword($input)
|
|
{
|
|
self::check_version($input, "1.1.0");
|
|
|
|
$username = self::check_parameter($input, 'username');
|
|
$password = self::check_parameter($input, 'password');
|
|
$password = self::decrypt_password($password);
|
|
|
|
if ($GLOBALS['user']->username == $username || Access::check('interface', 100)) {
|
|
$user = User::get_from_username($username);
|
|
if ($user->id) {
|
|
$user->update_password($password);
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* jukeboxControl
|
|
* Control the jukebox.
|
|
* Takes the action with optional index, offset, song id and volume gain in parameters.
|
|
* Not supported.
|
|
*/
|
|
public static function jukeboxcontrol($input)
|
|
{
|
|
self::check_version($input, "1.2.0");
|
|
$action = self::check_parameter($input, 'action');
|
|
$id = $input['id'];
|
|
$gain = $input['gain'];
|
|
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
debug_event('subsonic', 'Using Localplay controller: ' . AmpConfig::get('localplay_controller'), 5);
|
|
$localplay = new Localplay(AmpConfig::get('localplay_controller'));
|
|
|
|
if ($localplay->connect()) {
|
|
$ret = false;
|
|
switch ($action) {
|
|
case 'get':
|
|
case 'status':
|
|
$ret = true;
|
|
break;
|
|
case 'start':
|
|
$ret = $localplay->play();
|
|
break;
|
|
case 'stop':
|
|
$ret = $localplay->stop();
|
|
break;
|
|
case 'skip':
|
|
if (isset($input['index'])) {
|
|
if ($localplay->skip($input['index'])) {
|
|
$ret = $localplay->play();
|
|
}
|
|
} elseif (isset($input['offset'])) {
|
|
debug_event('subsonic', 'Skip with offset is not supported on JukeboxControl.', 5);
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_MISSINGPARAM);
|
|
}
|
|
break;
|
|
case 'set':
|
|
$localplay->delete_all();
|
|
case 'add':
|
|
if ($id) {
|
|
if (!is_array($id)) {
|
|
$rid = array();
|
|
$rid[] = $id;
|
|
$id = $rid;
|
|
}
|
|
|
|
foreach ($id as $i) {
|
|
$url = null;
|
|
if (Subsonic_XML_Data::isSong($i)) {
|
|
$url = Song::generic_play_url('song', Subsonic_XML_Data::getAmpacheId($i), '', 'api');
|
|
} elseif (Subsonic_XML_Data::isVideo($i)) {
|
|
$url = Song::generic_play_url('video', Subsonic_XML_Data::getAmpacheId($i), '', 'api');
|
|
}
|
|
|
|
if ($url) {
|
|
debug_event('subsonic', 'Adding ' . $url, 5);
|
|
$stream = array();
|
|
$stream['url'] = $url;
|
|
$ret = $localplay->add_url(new Stream_URL($stream));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 'clear':
|
|
$ret = $localplay->delete_all();
|
|
break;
|
|
case 'remove':
|
|
if (isset($input['index'])) {
|
|
$ret = $localplay->delete_track($input['index']);
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_MISSINGPARAM);
|
|
}
|
|
break;
|
|
case 'shuffle':
|
|
$ret = $localplay->random(true);
|
|
break;
|
|
case 'setGain':
|
|
$ret = $localplay->volume_set($gain * 100);
|
|
break;
|
|
}
|
|
|
|
if ($ret) {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
if ($action == 'get') {
|
|
Subsonic_XML_Data::addJukeboxPlaylist($r, $localplay);
|
|
} else {
|
|
Subsonic_XML_Data::createJukeboxStatus($r, $localplay);
|
|
}
|
|
}
|
|
}
|
|
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* scrobble
|
|
* Scrobbles a given music file on last.fm.
|
|
* Takes the file id with optional time and submission parameters.
|
|
*/
|
|
public static function scrobble($input)
|
|
{
|
|
self::check_version($input, "1.5.0");
|
|
|
|
$id = self::check_parameter($input, 'id');
|
|
$submission = $input['submission'];
|
|
//$time = $input['time'];
|
|
|
|
if ($submission === 'false' || $submission === '0') {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
self::apiOutput($input, $r);
|
|
} else {
|
|
if (!is_array($id)) {
|
|
$rid = array();
|
|
$rid[] = $id;
|
|
$id = $rid;
|
|
}
|
|
|
|
foreach ($id as $i) {
|
|
$aid = Subsonic_XML_Data::getAmpacheId($i);
|
|
if (Subsonic_XML_Data::isVideo($i)) {
|
|
$type = 'video';
|
|
} else {
|
|
$type = 'song';
|
|
}
|
|
|
|
$media = new $type($aid);
|
|
$media->format();
|
|
User::save_mediaplay($GLOBALS['user'], $media);
|
|
}
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
self::apiOutput($input, $r);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* getLyrics
|
|
* Searches and returns lyrics for a given song.
|
|
* Takes the optional artist and title in parameters.
|
|
*/
|
|
public static function getlyrics($input)
|
|
{
|
|
self::check_version($input, "1.2.0");
|
|
|
|
$artist = $input['artist'];
|
|
$title = $input['title'];
|
|
|
|
if (!$artist && !$title) {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_MISSINGPARAM);
|
|
} else {
|
|
$search = array();
|
|
$search['limit'] = 1;
|
|
$search['offset'] = 0;
|
|
$search['type'] = "song";
|
|
|
|
$i = 0;
|
|
if ($artist) {
|
|
$search['rule_' . $i . '_input'] = $artist;
|
|
$search['rule_' . $i . '_operator'] = 5;
|
|
$search['rule_' . $i . ''] = "artist";
|
|
++$i;
|
|
}
|
|
if ($title) {
|
|
$search['rule_' . $i . '_input'] = $title;
|
|
$search['rule_' . $i . '_operator'] = 5;
|
|
$search['rule_' . $i . ''] = "title";
|
|
++$i;
|
|
}
|
|
|
|
$query = new Search(null, 'song');
|
|
$songs = $query->run($search);
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
if (count($songs) > 0) {
|
|
Subsonic_XML_Data::addLyrics($r, $artist, $title, $songs[0]);
|
|
}
|
|
}
|
|
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getArtistInfo
|
|
* Returns artist info with biography, image URLs and similar artists, using data from last.fm.
|
|
* Takes artist id in parameter with optional similar artist count and if not present similar artist should be returned.
|
|
*/
|
|
public static function getartistinfo($input)
|
|
{
|
|
$id = self::check_parameter($input, 'id');
|
|
$count = $input['count'] ?: 20;
|
|
$includeNotPresent = ($input['includeNotPresent'] === "true");
|
|
|
|
if (Subsonic_XML_Data::isArtist($id)) {
|
|
$artist_id = Subsonic_XML_Data::getAmpacheId($id);
|
|
$info = Recommendation::get_artist_info($artist_id);
|
|
$similars = Recommendation::get_artists_like($artist_id, $count, !$includeNotPresent);
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addArtistInfo($r, $info, $similars);
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getArtistInfo2
|
|
* See getArtistInfo.
|
|
*/
|
|
public static function getartistinfo2($input)
|
|
{
|
|
return self::getartistinfo($input);
|
|
}
|
|
|
|
/**
|
|
* getSimilarSongs
|
|
* Returns a random collection of songs from the given artist and similar artists, using data from last.fm. Typically used for artist radio features.
|
|
* Takes song/album/artist id in parameter with optional similar songs count.
|
|
*/
|
|
public static function getsimilarsongs($input)
|
|
{
|
|
$id = self::check_parameter($input, 'id');
|
|
$count = $input['count'] ?: 50;
|
|
|
|
$songs = null;
|
|
if (Subsonic_XML_Data::isArtist($id)) {
|
|
// TODO: support similar songs for artists
|
|
} elseif (Subsonic_XML_Data::isAlbum($id)) {
|
|
// TODO: support similar songs for albums
|
|
} elseif (Subsonic_XML_Data::isSong($id)) {
|
|
if (AmpConfig::get('show_similar')) {
|
|
$songs = Recommendation::get_songs_like(Subsonic_XML_Data::getAmpacheId($id));
|
|
}
|
|
}
|
|
|
|
if ($songs === null) {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
} else {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addSimilarSongs($r, $songs);
|
|
}
|
|
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getSimilarSongs2
|
|
* See getSimilarSongs.
|
|
*/
|
|
public static function getsimilarsongs2($input)
|
|
{
|
|
return self::getsimilarsongs($input);
|
|
}
|
|
|
|
/**
|
|
* getPodcasts
|
|
* Get all podcast channels.
|
|
* Takes the optional includeEpisodes and channel id in parameters
|
|
*/
|
|
public static function getpodcasts($input)
|
|
{
|
|
self::check_version($input, "1.6.0");
|
|
$id = $input['id'];
|
|
$includeEpisodes = isset($input['includeEpisodes']) ? $input['includeEpisodes'] : true;
|
|
|
|
if (AmpConfig::get('podcast')) {
|
|
if ($id) {
|
|
$podcast = new Podcast(Subsonic_XML_Data::getAmpacheId($id));
|
|
if ($podcast->id) {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addPodcasts($r, array($podcast), $includeEpisodes);
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
} else {
|
|
$podcasts = Catalog::get_podcasts();
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
Subsonic_XML_Data::addPodcasts($r, $podcasts, $includeEpisodes);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getNewestPodcasts
|
|
* Get the most recently published podcast episodes.
|
|
* Takes the optional count in parameters
|
|
*/
|
|
public static function getnewestpodcasts($input)
|
|
{
|
|
self::check_version($input, "1.13.0");
|
|
$count = $input['count'] ?: 20;
|
|
|
|
if (AmpConfig::get('podcast')) {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
$episodes = Catalog::get_newest_podcasts($count);
|
|
Subsonic_XML_Data::addNewestPodcastEpisodes($r, $episodes);
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* refreshPodcasts
|
|
* Request the server to check for new podcast episodes.
|
|
* Takes no parameters.
|
|
*/
|
|
public static function refreshpodcasts($input)
|
|
{
|
|
self::check_version($input, "1.9.0");
|
|
|
|
if (AmpConfig::get('podcast') && Access::check('interface', 75)) {
|
|
$podcasts = Catalog::get_podcasts();
|
|
foreach ($podcasts as $podcast) {
|
|
$podcast->sync_episodes(true);
|
|
}
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* createPodcastChannel
|
|
* Add a new podcast channel.
|
|
* Takes the podcast url in parameter.
|
|
*/
|
|
public static function createpodcastchannel($input)
|
|
{
|
|
self::check_version($input, "1.9.0");
|
|
$url = self::check_parameter($input, 'url');
|
|
|
|
if (AmpConfig::get('podcast') && Access::check('interface', 75)) {
|
|
$catalogs = Catalog::get_catalogs('podcast');
|
|
if (count($catalogs) > 0) {
|
|
$data = array();
|
|
$data['feed'] = $url;
|
|
$data['catalog'] = $catalogs[0];
|
|
if (Podcast::create($data)) {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_GENERIC);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* deletePodcastChannel
|
|
* Delete an existing podcast channel
|
|
* Takes the podcast id in parameter.
|
|
*/
|
|
public static function deletepodcastchannel($input)
|
|
{
|
|
self::check_version($input, "1.9.0");
|
|
$id = self::check_parameter($input, 'id');
|
|
|
|
if (AmpConfig::get('podcast') && Access::check('interface', 75)) {
|
|
$podcast = new Podcast(Subsonic_XML_Data::getAmpacheId($id));
|
|
if ($podcast->id) {
|
|
if ($podcast->remove()) {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_GENERIC);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* deletePodcastEpisode
|
|
* Delete a podcast episode
|
|
* Takes the podcast episode id in parameter.
|
|
*/
|
|
public static function deletepodcastepisode($input)
|
|
{
|
|
self::check_version($input, "1.9.0");
|
|
$id = self::check_parameter($input, 'id');
|
|
|
|
if (AmpConfig::get('podcast') && Access::check('interface', 75)) {
|
|
$episode = new Podcast_Episode(Subsonic_XML_Data::getAmpacheId($id));
|
|
if ($episode->id) {
|
|
if ($episode->remove()) {
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_GENERIC);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* downloadPodcastEpisode
|
|
* Request the server to download a podcast episode
|
|
* Takes the podcast episode id in parameter.
|
|
*/
|
|
public static function downloadpodcastepisode($input)
|
|
{
|
|
self::check_version($input, "1.9.0");
|
|
$id = self::check_parameter($input, 'id');
|
|
|
|
if (AmpConfig::get('podcast') && Access::check('interface', 75)) {
|
|
$episode = new Podcast_Episode(Subsonic_XML_Data::getAmpacheId($id));
|
|
if ($episode->id) {
|
|
$episode->gather();
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_UNAUTHORIZED);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getBookmarks
|
|
* Get all user bookmarks.
|
|
* Takes no parameter.
|
|
* Not supported.
|
|
*/
|
|
public static function getbookmarks($input)
|
|
{
|
|
self::check_version($input, "1.9.0");
|
|
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
$bookmarks = Bookmark::get_bookmarks();
|
|
Subsonic_XML_Data::addBookmarks($r, $bookmarks);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* createBookmark
|
|
* Creates or updates a bookmark.
|
|
* Takes the file id and position with optional comment in parameters.
|
|
* Not supported.
|
|
*/
|
|
public static function createbookmark($input)
|
|
{
|
|
self::check_version($input, "1.9.0");
|
|
$id = self::check_parameter($input, 'id');
|
|
$position = self::check_parameter($input, 'position');
|
|
$comment = $input['comment'];
|
|
$type = Subsonic_XML_Data::getAmpacheType($id);
|
|
|
|
if (!empty($type)) {
|
|
$bookmark = new Bookmark(Subsonic_XML_Data::getAmpacheId($id), $type);
|
|
if ($bookmark->id) {
|
|
$bookmark->update($position);
|
|
} else {
|
|
Bookmark::create(array(
|
|
'object_id' => Subsonic_XML_Data::getAmpacheId($id),
|
|
'object_type' => $type,
|
|
'comment' => $comment,
|
|
'position' => $position
|
|
));
|
|
}
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* deleteBookmark
|
|
* Delete an existing bookmark.
|
|
* Takes the file id in parameter.
|
|
* Not supported.
|
|
*/
|
|
public static function deletebookmark($input)
|
|
{
|
|
self::check_version($input, "1.9.0");
|
|
$id = self::check_parameter($input, 'id');
|
|
$type = Subsonic_XML_Data::getAmpacheType($id);
|
|
|
|
$bookmark = new Bookmark(Subsonic_XML_Data::getAmpacheId($id), $type);
|
|
if ($bookmark->id) {
|
|
$bookmark->remove();
|
|
$r = Subsonic_XML_Data::createSuccessResponse();
|
|
} else {
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
}
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**** CURRENT UNSUPPORTED FUNCTIONS ****/
|
|
|
|
/**
|
|
* getChatMessages
|
|
* Get the current chat messages.
|
|
* Takes no parameter.
|
|
* Not supported.
|
|
*/
|
|
public static function getchatmessages($input)
|
|
{
|
|
self::check_version($input, "1.2.0");
|
|
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* addChatMessages
|
|
* Add a message to the chat.
|
|
* Takes the message in parameter.
|
|
* Not supported.
|
|
*/
|
|
public static function addchatmessages($input)
|
|
{
|
|
self::check_version($input, "1.2.0");
|
|
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* getPlayQueue
|
|
* Geturns the state of the play queue for the authenticated user.
|
|
* Takes no parameter.
|
|
* Not supported.
|
|
*/
|
|
public static function getplayqueue($input)
|
|
{
|
|
self::check_version($input, "1.12.0");
|
|
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
|
|
/**
|
|
* savePlayQueue
|
|
* Save the state of the play queue for the authenticated user.
|
|
* Takes multiple song id in parameter with optional current id playing sond and position.
|
|
* Not supported.
|
|
*/
|
|
public static function saveplayqueue($input)
|
|
{
|
|
self::check_version($input, "1.12.0");
|
|
|
|
$r = Subsonic_XML_Data::createError(Subsonic_XML_Data::SSERROR_DATA_NOTFOUND);
|
|
self::apiOutput($input, $r);
|
|
}
|
|
}
|