1
0
Fork 0
mirror of https://github.com/Yetangitu/ampache synced 2025-10-03 09:49:30 +02:00
ampache/lib/class/plex_api.class.php
2015-02-05 14:03:09 +01:00

1521 lines
54 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
* as published by the Free Software Foundation; version 2
* of the License.
*
* 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.
*
*/
/**
* Plex Class
*
* This class wrap Ampache to Plex API library functions. See http://wiki.plexapp.com/index.php/HTTP_API
* These are all static calls.
*
* @SuppressWarnings("unused")
*/
class Plex_Api
{
/**
* constructor
* This really isn't anything to do here, so it's private
*/
private function __construct()
{
}
protected static function is_local()
{
$local = false;
$local_auth = AmpConfig::get('plex_local_auth');
if (!$local_auth) {
$ip = $_SERVER['REMOTE_ADDR'];
$lip = ip2long($ip);
$rangs = array(
array('127.0.0.1', '127.0.0.1'),
array('10.0.0.1', '10.255.255.254'),
array('172.16.0.1', '172.31.255.254'),
array('192.168.0.1', '192.168.255.254'),
);
foreach ($rangs as $rang) {
$ld = ip2long($rang[0]);
$lu = ip2long($rang[1]);
if ($lip <= $lu && $ld <= $lip) {
debug_event('Access Control', 'Local client ip address (' . $ip . '), bypass authentication.', '3');
$local = true;
break;
}
}
}
return $local;
}
public static function auth_user()
{
$isLocal = self::is_local();
$headers = apache_request_headers();
$myplex_token = $headers['X-Plex-Token'];
if (empty($myplex_token)) {
$myplex_token = $_REQUEST['X-Plex-Token'];
}
if (!$isLocal) {
$match_users = AmpConfig::get('plex_match_email');
$myplex_username = $headers['X-Plex-Username'];
if (empty($myplex_token)) {
// Never fail OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
self::setPlexHeader($headers);
exit();
} else {
debug_event('Access Control', 'Authentication token is missing.', '3');
self::createError(401);
}
}
$createSession = false;
Session::gc();
$username = "";
$email = trim(Session::read((string) $myplex_token));
if (empty($email)) {
$createSession = true;
$xml = self::get_server_authtokens();
$validToken = false;
foreach ($xml->access_token as $tk) {
if ((string) $tk['token'] == $myplex_token) {
$username = (string) $tk['username'];
// We should apply filter and access restriction to shared sections only, but that's not easily possible with current Ampache architecture
$validToken = true;
break;
}
}
if (!$validToken) {
debug_event('Access Control', 'Auth-Token ' . $myplex_token . ' invalid for this server.', '3');
self::createError(401);
}
}
// Need to get a match between Plex and Ampache users
if ($match_users) {
if (!AmpConfig::get('access_control')) {
debug_event('Access Control', 'Error Attempted to use Plex with Access Control turned off and plex/ampache link enabled.','3');
self::createError(401);
}
if (empty($email)) {
$xml = self::get_users_account();
if ((string) $xml->username == $username) {
$email = (string) $xml->email;
} else {
$xml = self::get_server_friends();
foreach ($xml->User as $xuser) {
if ((string) $xuser['username'] == $username) {
$email = (string) $xuser['email'];
}
}
}
}
if (!empty($email)) {
$user = User::get_from_email($email);
}
if (!isset($user) || !$user->id) {
debug_event('Access Denied', 'Unable to get an Ampache user match for email ' . $email, '3');
self::createError(401);
} else {
$username = $user->username;
if (!Access::check_network('init-api', $username, 5)) {
debug_event('Access Denied', 'Unauthorized access attempt to Plex [' . $_SERVER['REMOTE_ADDR'] . ']', '3');
self::createError(401);
} else {
$GLOBALS['user'] = $user;
$GLOBALS['user']->load_playlist();
}
}
} else {
$email = $username;
$username = null;
$GLOBALS['user'] = new User();
$GLOBALS['user']->load_playlist();
}
if ($createSession) {
// Create an Ampache session from Plex authtoken
Session::create(array(
'type' => 'api',
'sid' => $myplex_token,
'username' => $username,
'value' => $email
));
}
} else {
AmpConfig::set('cookie_path', '/', true);
$sid = $_COOKIE[AmpConfig::get('session_name')];
if (!$sid) {
$sid = $myplex_token;
if ($sid) {
session_id($sid);
Session::create_cookie();
}
}
if (!empty($sid) && Session::exists('api', $sid)) {
Session::check();
$GLOBALS['user'] = User::get_from_username($_SESSION['userdata']['username']);
} else {
$GLOBALS['user'] = new User();
$data = array(
'type' => 'api',
'sid' => $sid,
);
Session::create($data);
Session::check();
}
$GLOBALS['user']->load_playlist();
}
}
protected static function check_access($level)
{
if (self::is_local()) {
// Promote all users as content manager if local
if ($GLOBALS['user']->access < 50) {
$GLOBALS['user']->access = 50;
}
}
if (($GLOBALS['user']->access < $level || AmpConfig::get('demo_mode'))) {
debug_event('plex', 'User ' . $GLOBALS['user']->username . ' is unauthorized to complete the action.', '3');
self::createError(401);
exit;
}
}
public static function setHeader($f)
{
header("HTTP/1.1 200 OK", true, 200);
header("Connection: close", true);
header_remove("x-powered-by");
if (strtolower($f) == "xml") {
header("Cache-Control: no-cache", true);
header("Content-type: text/xml; charset=" . AmpConfig::get('site_charset'), true);
} elseif (substr(strtolower($f), 0, 6) == "image/") {
header("Cache-Control: public, max-age=604800", true);
header("Content-type: " . $f, true);
} else {
header("Content-type: " . $f, true);
}
}
public static function setPlexHeader($reqheaders)
{
header("X-Plex-Protocol: 1.0");
header('Access-Control-Allow-Origin: *');
$acm = $reqheaders['Access-Control-Request-Method'];
if ($acm) {
header('Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT, HEAD');
}
$ach = $reqheaders['Access-Control-Request-Headers'];
if ($ach) {
//$filter = explode(',', $ach);
$filter = null;
$headers = self::getPlexHeaders(true, $filter);
$headerkeys = array();
foreach ($headers as $key => $value) {
$headerkeys[] = $key;
}
header('Access-Control-Allow-Headers: ' . implode(',', $headerkeys));
}
if ($acm || $ach) {
header('Access-Control-Max-Age: 1209600');
} else {
header('Access-Control-Expose-Headers: Location');
}
}
public static function apiOutputXml($xml)
{
// Format xml output
$dom = new DOMDocument();
$dom->loadXML($xml);
$dom->formatOutput = true;
self::apiOutput($dom->saveXML());
}
public static function apiOutput($string)
{
if ($_SERVER['REQUEST_METHOD'] != 'OPTIONS') {
ob_clean();
ob_start('ob_gzhandler');
echo $string;
ob_end_flush();
$reqheaders = getallheaders();
if ($reqheaders['Accept-Encoding']) {
header("X-Plex-Content-Compressed-Length: " . ob_get_length(), true);
header("X-Plex-Content-Original-Length: " . strlen($string), true);
}
} else {
header("Content-type: text/plain", true);
//header("Content-length: 0", true);
}
}
public static function createError($code)
{
$error = "";
switch ($code) {
case 404:
$error = "Not Found";
break;
case 401:
$error = "Unauthorized";
break;
}
header("Content-type: text/html", true);
header("HTTP/1.0 ". $code . " " . $error, true, $code);
$html = "<html><head><title>" . $error . "</title></head><body><h1>" . $code . " " . $error . "</h1></body></html>";
self::apiOutput($html);
exit();
}
public static function validateMyPlex($myplex_username, $myplex_password)
{
$options = array(
CURLOPT_USERPWD => $myplex_username . ':' . $myplex_password,
//CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
CURLOPT_POST => true,
);
$headers = array(
'Content-Length: 0'
);
$action = 'users/sign_in.xml';
$res = self::myPlexRequest($action, $options, $headers);;
return $res['xml']['authenticationToken'];
}
public static function getPublicIp()
{
$action = 'pms/:/ip';
$res = self::myPlexRequest($action);
return trim($res['raw']);
}
public static function registerMyPlex($authtoken)
{
$headers = array (
'Content-Type: text/xml'
);
$action = 'servers.xml?auth_token=' . $authtoken;
$r = Plex_XML_Data::createContainer();
Plex_XML_Data::setServerInfo($r, Catalog::get_catalogs());
Plex_XML_Data::setContainerSize($r);
$curlopts = array(
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $r->asXML()
);
return self::myPlexRequest($action, $curlopts, $headers, true);
}
public static function publishDeviceConnection($authtoken)
{
$headers = array ();
$action = 'devices/' . Plex_XML_Data::getMachineIdentifier() . '?Connection[][uri]=' . Plex_XML_Data::getServerUri() . '&X-Plex-Token=' . $authtoken;
$curlopts = array(
CURLOPT_CUSTOMREQUEST => "PUT"
);
return self::myPlexRequest($action, $curlopts, $headers);
}
public static function unregisterMyPlex($authtoken)
{
$headers = array (
'Content-Type: text/xml'
);
$action = 'servers/' . Plex_XML_Data::getMachineIdentifier() . '.xml?auth_token=' . $authtoken;
$curlopts = array(
CURLOPT_CUSTOMREQUEST => "DELETE"
);
return self::myPlexRequest($action, $curlopts, $headers);
}
protected static function get_server_authtokens()
{
$action = 'servers/' . Plex_XML_Data::getMachineIdentifier() . '/access_tokens.xml?auth_token=' . Plex_XML_Data::getMyPlexAuthToken();
$res = self::myPlexRequest($action);
return $res['xml'];
}
protected static function get_server_friends()
{
$action = 'pms/friends/all?auth_token=' . Plex_XML_Data::getMyPlexAuthToken();
$res = self::myPlexRequest($action);
return $res['xml'];
}
protected static function getPlexHeaders($private = false, $filters = null)
{
$headers = array(
'X-Plex-Client-Identifier' => Plex_XML_Data::getClientIdentifier(),
'X-Plex-Product' => 'Plex Media Server',
'X-Plex-Version' => Plex_XML_Data::getPlexVersion(),
'X-Plex-Platform' => Plex_XML_Data::getPlexPlatform(),
'X-Plex-Platform-Version' => Plex_XML_Data::getPlexPlatformVersion(),
'X-Plex-Client-Platform' => Plex_XML_Data::getPlexPlatform(),
'X-Plex-Protocol' => 1.0,
'X-Plex-Device' => 'Ampache',
'X-Plex-Device-Name' => 'Ampache',
'X-Plex-Provides' => 'server'
);
if ($private) {
if (Plex_XML_Data::getMyPlexUsername()) {
$headers['X-Plex-Username'] = Plex_XML_Data::getMyPlexUsername();
}
if (Plex_XML_Data::getMyPlexUsername()) {
$headers['X-Plex-Token'] = Plex_XML_Data::getMyPlexAuthToken();
}
}
if ($filters) {
$fheaders = array();
foreach ($headers as $key => $value) {
if (array_search(strtolower($key), $filters)) {
$fheaders[$key] = $value;
}
}
$headers = $fheaders;
}
return $headers;
}
public static $request_headers = array();
public static function request_output_header($ch, $header)
{
self::$request_headers[] = $header;
return strlen($header);
}
public static function replay_header($ch, $header)
{
$rheader = trim($header);
$rhpart = explode(':', $rheader);
if (!empty($rheader) && count($rhpart) > 1) {
if ($rhpart[0] != "Transfer-Encoding") {
header($rheader);
}
}
return strlen($header);
}
public static function replay_body($ch, $data)
{
if (connection_status() != 0) {
curl_close($ch);
debug_event('plex', 'Stream cancelled.', 5);
exit;
}
echo $data;
ob_flush();
return strlen($data);
}
protected static function myPlexRequest($action, $curlopts = array(), $headers = array(), $proxy = false)
{
$server = Plex_XML_Data::getServerUri();
$allheaders = array();
if (!$proxy) {
$allheadersarr = self::getPlexHeaders();
foreach ($allheadersarr as $key => $value) {
$allheaders[] = $key . ': ' . $value;
}
$allheaders += array(
'Origin: ' . $server,
'Referer: ' . $server . '/web/index.html',
);
if (!$curlopts[CURLOPT_POST]) {
$allheaders[] = 'Content-length: 0';
}
}
$allheaders = array_merge($allheaders, $headers);
$url = 'https://my.plexapp.com/' . $action;
debug_event('plex', 'Calling ' . $url, '5');
$options = array(
CURLOPT_HEADER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADERFUNCTION => array('Plex_Api', 'request_output_header'),
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HTTPHEADER => $allheaders,
);
$options += $curlopts;
$ch = curl_init($url);
curl_setopt_array($ch, $options);
$r = curl_exec($ch);
$res = array();
$res['status'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$res['headers'] = self::$request_headers;
$res['raw'] = $r;
try {
$res['xml'] = simplexml_load_string($r);
} catch (Exception $e) {
// If exception, wrong data returned (Plex API changes?)
}
return $res;
}
public static function root()
{
$r = Plex_XML_Data::createContainer();
Plex_XML_Data::setRootContent($r, Catalog::get_catalogs());
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function library($params)
{
$r = Plex_XML_Data::createLibContainer();
Plex_XML_Data::setLibraryContent($r);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function system($params)
{
$r = Plex_XML_Data::createSysContainer();
Plex_XML_Data::setSystemContent($r);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function clients($params)
{
$r = Plex_XML_Data::createContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function channels($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function photos($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function photo($params)
{
if (count($params) == 2) {
if ($params[0] == ':' && $params[1] == 'transcode') {
$width = $_REQUEST['width'];
$height = $_REQUEST['height'];
$url = $_REQUEST['url'];
// Replace 32400 request port with the real listening port
// *** To `Plex Inc`: ***
// Please allow listening port server configuration for your Plex server
// and fix your clients to not request resources so hard-coded on 127.0.0.1:32400.
// May be ok on Apple & UPnP world but that's really ugly for a server...
// Yes, it's a little hack but it works.
$localrs = "http://127.0.0.1:32400/";
$options = Core::requests_options();
if (strpos($url, $localrs) !== false) {
$options = array(); // In case proxy is set, no proxy for local addresses
$url = "http://127.0.0.1:" . Plex_XML_Data::getServerPort() . "/" . substr($url, strlen($localrs));
}
if ($width && $height && $url) {
$request = Requests::get($url, array(), $options);
if ($request->status_code == 200) {
ob_clean();
$mime = $request->headers['content-type'];
self::setHeader($mime);
$art = new Art(0);
$art->raw = $request->body;
$thumb = $art->generate_thumb($art->raw, array('width' => $width, 'height' => $height), $mime);
echo $thumb['thumb'];
exit();
}
}
}
}
}
public static function music($params)
{
if (count($params) > 2) {
if ($params[0] == ':' && $params[1] == 'transcode') {
if (count($params) == 3) {
$format = $_REQUEST['format'] ?: pathinfo($params[2], PATHINFO_EXTENSION);
$url = $_REQUEST['url'];
$br = $_REQUEST['audioBitrate'];
if (preg_match("/\/parts\/([0-9]+)\//", $url, $matches)) {
$song_id = Plex_XML_Data::getAmpacheId($matches[1]);
}
} elseif (count($params) == 4 && $params[2] == 'universal') {
$format = pathinfo($params[3], PATHINFO_EXTENSION);
$path = $_REQUEST['path'];
// Should be the maximal allowed bitrate, not necessary the bitrate used but Ampache doesn't support this kind of option yet
$br = $_REQUEST['maxAudioBitrate'];
if (preg_match("/\/metadata\/([0-9]+)/", $path, $matches)) {
$song_id = Plex_XML_Data::getAmpacheId($matches[1]);
}
}
if (!empty($format) && !empty($song_id)) {
$urlparams = '&transcode_to=' . $format;
if (!empty($br)) {
$urlparams .= '&bitrate=' . $br;
}
$url = Song::play_url($song_id, $urlparams, true);
self::stream_url($url);
}
}
}
}
public static function video($params)
{
if (count($params) > 1 && $params[0] == ':' && $params[1] == 'transcode') {
array_shift($params);
array_shift($params);
self::video_transcode($params);
} else {
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
}
private static function video_transcode($params)
{
$n = count($params);
if ($n == 2) {
$transcode_to = $params[0];
$action = $params[1];
$id = '';
$path = $_GET['path'];
$protocol = $_GET['protocol'];
$offset = $_GET['offset'];
// Transcode arguments.
$videoQuality = $_GET['videoQuality'];
$videoResolution = $_GET['videoResolution'];
$maxVideoBitrate = $_GET['maxVideoBitrate'];
$subtitleSize = $_GET['subtitleSize'];
$audioBoost = $_GET['audioBoost'];
$additional_params = '&vsettings=';
if ($videoResolution) {
$additional_params .= 'resolution-' . $videoResolution . '-';
}
if ($maxVideoBitrate) {
$additional_params .= 'maxbitrate-' . $maxVideoBitrate . '-';
}
if ($videoQuality) {
$additional_params .= 'quality-' . $videoQuality . '-';
}
if ($offset) {
$additional_params .= '&frame=' . $offset;
}
// Several Media and Part per Video is not supported
//$mediaIndex = $_GET['mediaIndex'];
//$partIndex = $_GET['partIndex'];
// What's that?
//$fastSeek = $_GET['fastSeek'];
//$directPlay = $_GET['directPlay'];
//$directStream = $_GET['directStream'];
$uriroot = '/library/metadata/';
$upos = strrpos($path, $uriroot);
if ($upos !== false) {
$id = substr($path, $upos + strlen($uriroot));
}
$session = $_GET['session'];
if ($action == "stop") {
// We should kill associated transcode session here
} elseif (strpos($action, "start") === 0) {
if (empty($protocol) || $protocol == "hls") {
header('Content-Type: application/vnd.apple.mpegurl');
$videoResolution = $_GET['videoResolution'];
$maxVideoBitrate = $_GET['maxVideoBitrate'];
if (!$maxVideoBitrate)
$maxVideoBitrate = 8175;
echo "#EXTM3U\n";
echo "#EXT-X-STREAM-INF:PROGRAM-ID=1";
if ($maxVideoBitrate)
echo ",BANDWIDTH=" . ($maxVideoBitrate * 1000);
if ($videoResolution)
echo ",RESOLUTION=" . $videoResolution;
echo "\n";
echo "hls.m3u8?" . substr($_SERVER['QUERY_STRING'], strpos($_SERVER['QUERY_STRING'], '&') + 1);
} elseif ($protocol == "http") {
$url = null;
if ($transcode_to == 'universal') {
$additional_params .= '&transcode_to=webm';
if (AmpConfig::get('encode_args_webm')) {
debug_event('plex', 'Universal transcoder requested but `webm` transcode settings not configured. This will probably failed.', 3);
}
}
if ($id) {
if (Plex_XML_Data::isSong($id)) {
$url = Song::play_url(Plex_XML_Data::getAmpacheId($id), $additional_params);
} elseif (Plex_XML_Data::isVideo($id)) {
$url = Video::play_url(Plex_XML_Data::getAmpacheId($id), $additional_params);
}
if ($url) {
self::stream_url($url);
}
}
}
} elseif ($action == "hls.m3u8") {
if ($id) {
$pl = new Stream_Playlist();
$media = null;
if (Plex_XML_Data::isSong($id)) {
$media = array(
'object_type' => 'song',
'object_id' => Plex_XML_Data::getAmpacheId($id),
);
} elseif (Plex_XML_Data::isVideo($id)) {
$media = array(
'object_type' => 'video',
'object_id' => Plex_XML_Data::getAmpacheId($id),
);
}
if ($media != null) {
$pl->add(array($media), $additional_params);
}
$pl->generate_playlist('hls');
}
}
}
}
public static function applications($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function library_sections($params)
{
$r = Plex_XML_Data::createLibContainer();
$n = count($params);
if ($n == 0) {
Plex_XML_Data::setSections($r, Catalog::get_catalogs());
} else {
$key = $params[0];
$catalog = Catalog::create_from_id($key);
if (!$catalog) {
self::createError(404);
}
if ($n == 1) {
Plex_XML_Data::setSectionContent($r, $catalog);
} elseif ($n == 2) {
$view = $params[1];
$gtypes = $catalog->get_gather_types();
if ($gtypes[0] == 'music') {
$type = 'artist';
if ($_GET['type']) {
$type = Plex_XML_Data::getAmpacheType($_GET['type']);
}
if ($view == "all") {
switch ($type) {
case 'artist':
Plex_XML_Data::setSectionAll_Artists($r, $catalog);
break;
case 'album':
Plex_XML_Data::setSectionAll_Albums($r, $catalog);
break;
}
} elseif ($view == "albums") {
Plex_XML_Data::setSectionAlbums($r, $catalog);
} elseif ($view == "recentlyadded") {
Plex_XML_Data::setCustomSectionView($r, $catalog, Stats::get_recent('album', 25, $key));
} elseif ($view == "genre") {
Plex_XML_Data::setSectionTags($r, $catalog, 'song');
}
} elseif ($gtypes[0] == "tvshow") {
$type = 'tvshow';
if ($_GET['type']) {
$type = Plex_XML_Data::getAmpacheType($_GET['type']);
}
if ($view == "all") {
switch ($type) {
case 'tvshow':
Plex_XML_Data::setSectionAll_TVShows($r, $catalog);
break;
case 'season':
Plex_XML_Data::setSectionAll_Seasons($r, $catalog);
break;
case 'episode':
Plex_XML_Data::setSectionAll_Episodes($r, $catalog);
break;
}
} elseif ($view == "recentlyadded") {
Plex_XML_Data::setCustomSectionView($r, $catalog, Stats::get_recent('tvshow_episode', 25, $key));
} elseif ($view == "genre") {
Plex_XML_Data::setSectionTags($r, $catalog, 'video');
}
} elseif ($gtypes[0] == "movie") {
$type = 'tvshow';
if ($_GET['type']) {
$type = Plex_XML_Data::getAmpacheType($_GET['type']);
}
if ($view == "all") {
Plex_XML_Data::setSectionAll_Movies($r, $catalog);
} elseif ($view == "recentlyadded") {
Plex_XML_Data::setCustomSectionView($r, $catalog, Stats::get_recent('movie', 25, $key));
} elseif ($view == "genre") {
Plex_XML_Data::setSectionTags($r, $catalog, 'video');
}
}
}
}
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function library_metadata($params)
{
$r = Plex_XML_Data::createLibContainer();
$n = count($params);
$litem = null;
$createMode = ($_SERVER['REQUEST_METHOD'] == 'POST');
$editMode = ($_SERVER['REQUEST_METHOD'] == 'PUT');
if ($n > 0) {
$key = $params[0];
$id = Plex_XML_Data::getAmpacheId($key);
if ($editMode) {
self::check_access(50);
}
if ($n == 1) {
// Should we check that files still exists here?
$checkFiles = $_REQUEST['checkFiles'];
$extra = $_REQUEST['includeExtra'];
if (Plex_XML_Data::isArtist($key)) {
$litem = new Artist($id);
$litem->format();
if ($editMode) {
$dmap = array(
'title' => 'name',
'summary' => null,
);
$litem->update(self::get_data_from_map($dmap));
}
Plex_XML_Data::addArtist($r, $litem);
} elseif (Plex_XML_Data::isAlbum($key)) {
$litem = new Album($id);
$litem->format();
if ($editMode) {
$dmap = array(
'title' => 'name',
'year' => null,
);
$litem->update(self::get_data_from_map($dmap));
}
Plex_XML_Data::addAlbum($r, $litem);
} elseif (Plex_XML_Data::isTrack($key)) {
$litem = new Song($id);
$litem->format();
if ($editMode) {
$dmap = array(
'title' => null,
);
$litem->update(self::get_data_from_map($dmap));
}
Plex_XML_Data::addSong($r, $litem);
} elseif (Plex_XML_Data::isTVShow($key)) {
$litem = new TVShow($id);
$litem->format();
if ($editMode) {
$dmap = array(
'title' => 'name',
'year' => null,
'summary' => null,
);
$litem->update(self::get_data_from_map($dmap));
}
Plex_XML_Data::addTVShow($r, $litem);
} elseif (Plex_XML_Data::isTVShowSeason($key)) {
$litem = new TVShow_Season($id);
$litem->format();
Plex_XML_Data::addTVShowSeason($r, $litem);
} elseif (Plex_XML_Data::isVideo($key)) {
$litem = Video::create_from_id($id);
if ($editMode) {
$dmap = array(
'title' => null,
'year' => null,
'originallyAvailableAt' => 'release_date',
'originalTitle' => 'original_name',
'summary' => null,
);
$litem->update(self::get_data_from_map($dmap));
}
$litem->format();
$subtype = strtolower(get_class($litem));
if ($subtype == 'tvshow_episode') {
Plex_XML_Data::addEpisode($r, $litem, true);
} elseif ($subtype == 'movie') {
Plex_XML_Data::addMovie($r, $litem, true);
}
} elseif (Plex_XML_Data::isPlaylist($key)) {
$litem = new Playlist($id);
$litem->format();
if ($editMode) {
$dmap = array(
'title' => 'name',
);
$litem->update(self::get_data_from_map($dmap));
}
Plex_XML_Data::addPlaylist($r, $litem);
}
} else {
$subact = $params[1];
if ($subact == "children") {
if (Plex_XML_Data::isArtist($key)) {
$litem = new Artist($id);
$litem->format();
Plex_XML_Data::setArtistRoot($r, $litem);
} else if (Plex_XML_Data::isAlbum($key)) {
$litem = new Album($id);
$litem->format();
Plex_XML_Data::setAlbumRoot($r, $litem);
} else if (Plex_XML_Data::isTVShow($key)) {
$litem = new TVShow($id);
$litem->format();
Plex_XML_Data::setTVShowRoot($r, $litem);
} else if (Plex_XML_Data::isTVShowSeason($key)) {
$litem = new TVShow_Season($id);
$litem->format();
Plex_XML_Data::setTVShowSeasonRoot($r, $litem);
}
} elseif ($subact == "thumbs" || $subact == "posters" || $subact == "arts" || $subact == 'backgrounds') {
$kind = Plex_XML_Data::getPhotoKind($subact);
if ($createMode) {
// Upload art
$litem = Plex_XML_Data::createLibraryItem($key);
if ($litem != null) {
$uri = Plex_XML_Data::getMetadataUri($key) . '/' . Plex_XML_Data::getPhotoPlexKind($kind) . '/' . $key;
if (is_a($litem, 'video')) {
$type = 'video';
} else {
$type = get_class($litem);
}
$art = new Art($litem->id, $type, $kind);
$raw = file_get_contents("php://input");
$art->insert($raw);
header('Content-Type: text/html');
echo $uri;
exit;
}
}
Plex_XML_Data::addPhotos($r, $key, $kind);
} elseif ($subact == "thumb" || $subact == "poster" || $subact == "art" || $subact == "background") {
if ($n == 3) {
$kind = Plex_XML_Data::getPhotoKind($subact);
// Ignore art id as we can only have 1 thumb
$art = null;
if (Plex_XML_Data::isArtist($key)) {
$art = new Art($id, "artist", $kind);
} else if (Plex_XML_Data::isAlbum($key)) {
$art = new Art($id, "album", $kind);
} else if (Plex_XML_Data::isTrack($key)) {
$art = new Art($id, "song", $kind);
} else if (Plex_XML_Data::isTVShow($key)) {
$art = new Art($id, "tvshow", $kind);
} else if (Plex_XML_Data::isTVShowSeason($key)) {
$art = new Art($id, "tvshow_season", $kind);
} else if (Plex_XML_Data::isVideo($key)) {
$art = new Art($id, "video", $kind);
}
if ($art != null) {
$art->get_db();
ob_clean();
if (!isset($size)) {
self::setHeader($art->raw_mime);
echo $art->raw;
} else {
$dim = array();
$dim['width'] = $size;
$dim['height'] = $size;
$thumb = $art->get_thumb($dim);
self::setHeader($art->thumb_mime);
echo $thumb['thumb'];
}
exit();
}
}
}
}
}
if ($litem != null) {
$catalog_ids = $litem->get_catalogs();
if (count($catalog_ids) > 0) {
$catalog = Catalog::create_from_id($catalog_ids[0]);
Plex_XML_Data::addCatalogIdentity($r, $catalog);
}
}
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
protected static function stream_url($url)
{
// header("Location: " . $url);
set_time_limit(0);
ob_end_clean();
$headers = apache_request_headers();
$reqheaders = array();
if (isset($headers['Range'])) {
$reqheaders[] = "Range: " . $headers['Range'];
}
$ch = curl_init($url);
curl_setopt_array($ch, array(
CURLOPT_HTTPHEADER => $reqheaders,
CURLOPT_HEADER => false,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_RETURNTRANSFER => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_WRITEFUNCTION => array('Plex_Api', 'replay_body'),
CURLOPT_HEADERFUNCTION => array('Plex_Api', 'replay_header'),
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_TIMEOUT => 0
));
if (curl_exec($ch) === false) {
debug_event('plex-api', 'Curl error: ' . curl_error($ch),1);
}
curl_close($ch);
}
public static function library_parts($params)
{
$n = count($params);
if ($n > 0) {
$key = $params[0];
if ($n == 2) {
$file = $params[1];
$id = Plex_XML_Data::getAmpacheId($key);
if (Plex_XML_Data::isSong($key)) {
$media = new Song($id);
if ($media->id) {
$url = Song::play_url($id, '', true);
self::stream_url($url);
} else {
self::createError(404);
}
} elseif (Plex_XML_Data::isVideo($key)) {
$media = new Video($id);
if ($media->id) {
$url = Video::play_url($id, '', true);
self::stream_url($url);
} else {
self::createError(404);
}
}
} elseif ($n == 1) {
if ($_SERVER['REQUEST_METHOD'] == 'PUT') {
if (isset($_GET['subtitleStreamID'])) {
$lang_code = dechex(hex2bin(substr($_GET['subtitleStreamID'], 0, 2)));
$_SESSION['iframe']['subtitle'] = $lang_code;
}
}
}
}
}
public static function library_recentlyadded($params)
{
$data = array();
$data['album'] = Stats::get_newest('album', 25);
$r = Plex_XML_Data::createLibContainer();
Plex_XML_Data::setCustomView($r, $data);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function library_ondeck($params)
{
$data = array();
$data['album'] = Stats::get_recent('album', 25);
$r = Plex_XML_Data::createLibContainer();
Plex_XML_Data::setCustomView($r, $data);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function system_library_sections($params)
{
$r = Plex_XML_Data::createSysContainer();
Plex_XML_Data::setSysSections($r, Catalog::get_catalogs());
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function manage_frameworks_ekspinner_resources($params)
{
// Image file used to 'ping' the server
if ($params[0] == "small_black_7.png") {
header("Content-type: image/png", true);
echo file_get_contents(AmpConfig::get('prefix') . '/plex/resources/small_black_7.png');
exit;
}
}
public static function myplex_account($params)
{
$r = Plex_XML_Data::createMyPlexAccount();
self::apiOutputXml($r->asXML());
}
public static function system_agents($params)
{
$r = Plex_XML_Data::createSysContainer();
$addcontributors = false;
$mediaType = $_REQUEST['mediaType'];
if (count($params) >= 3 && $params[1] == 'config') {
$mediaType = $params[2];
$addcontributors = true;
}
if ($mediaType) {
switch ($mediaType) {
case '1':
Plex_XML_Data::setSysMovieAgents($r);
break;
case '2':
Plex_XML_Data::setSysTVShowAgents($r);
break;
case '13':
Plex_XML_Data::setSysPhotoAgents($r);
break;
case '8':
Plex_XML_Data::setSysMusicAgents($r);
break;
case '9':
Plex_XML_Data::setSysMusicAgents($r, 'Albums');
break;
default:
self::createError(404);
break;
}
} else {
Plex_XML_Data::setSysAgents($r);
}
if ($addcontributors) {
Plex_XML_Data::setAgentsContributors($r, $mediaType, 'com.plexapp.agents.none');
}
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function system_agents_contributors($params)
{
$mediaType = $_REQUEST['mediaType'];
$primaryAgent = $_REQUEST['primaryAgent'];
$r = Plex_XML_Data::createSysContainer();
Plex_XML_Data::setAgentsContributors($r, $mediaType, $primaryAgent);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function system_agents_attribution($params)
{
$identifier = $_REQUEST['identifier'];
self::createError(404);
}
public static function system_scanners($params)
{
if (count($params) > 0) {
$type = $params[0];
$r = Plex_XML_Data::createSysContainer();
Plex_XML_Data::setScanners($r, $type);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
} else {
self::createError(404);
}
}
public static function system_appstore($params)
{
$r = Plex_XML_Data::createAppStore();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function accounts($params)
{
$userid = '';
if (isset($params[0])) {
$userid = $params[0];
}
// Not supported yet
if ($userid > 1) { self::createError(404); }
$r = Plex_XML_Data::createAccountContainer();
Plex_XML_Data::setAccounts($r, $userid);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function status($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setStatus($r);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function status_sessions($params)
{
self::createError(403);
}
public static function prefs($params)
{
$r = Plex_XML_Data::createContainer();
Plex_XML_Data::setPrefs($r);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function help($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function plexonline($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function plugins($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function services($params)
{
$r = Plex_XML_Data::createPluginContainer();
Plex_XML_Data::setServices($r);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function services_browse($params)
{
self::check_access(75);
$r = Plex_XML_Data::createContainer();
Plex_XML_Data::setBrowseService($r, $params[0]);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function timeline($params)
{
$ratingKey = $_REQUEST['ratingKey'];
$key = $_REQUEST['key'];
$state = $_REQUEST['state'];
$time = $_REQUEST['time'];
$duration = $_REQUEST['duration'];
// Not supported right now (maybe in a future for broadcast?)
header('Content-Type: text/html');
}
public static function rate($params)
{
$id = $_REQUEST['key'];
$identifier = $_REQUEST['identifier'];
$rating = $_REQUEST['rating'];
if ($identifier == 'com.plexapp.plugins.library') {
$robj = new Rating(Plex_XML_Data::getAmpacheId($id), Plex_XML_Data::getLibraryItemType($id));
$robj->set_rating($rating / 2);
}
}
protected static function get_users_account($authtoken='')
{
if (empty($authtoken)) {
$authtoken = Plex_XML_Data::getMyPlexAuthToken();
}
$action = 'users/account?auth_token=' . $authtoken;
$res = self::myPlexRequest($action);
return $res['xml'];
}
public static function users_account($params)
{
$xml = self::get_users_account();
Plex_XML_Data::setMyPlexSubscription($xml);
self::apiOutput($xml->asXML());
}
/**
myPlex server function to grant plexpass access dynamically.
Used for testing purpose only.
*/
public static function users($params)
{
if ($params[0] == 'sign_in.xml') {
$curlopts = array();
$headers = array();
$res = self::myPlexRequest('users/sign_in.xml', $curlopts, $headers, true);
foreach ($res['headers'] as $header) {
header($header);
}
if ($res['status'] == '201') {
Plex_XML_Data::setMyPlexSubscription($res['xml']);
self::apiOutput($res['xml']->asXML());
} else { self::createError($res['status']); }
}
}
public static function servers($params)
{
$r = Plex_XML_Data::createContainer();
Plex_XML_Data::setLocalServerInfo($r);
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function playlists($params)
{
$r = Plex_XML_Data::createContainer();
$n = count($params);
$createMode = ($_SERVER['REQUEST_METHOD'] == 'POST');
$editMode = ($_SERVER['REQUEST_METHOD'] == 'PUT');
$delMode = ($_SERVER['REQUEST_METHOD'] == 'DELETE');
if ($createMode || $editMode || $delMode) {
self::check_access(50);
}
if ($n <= 1) {
$plid = 0;
if ($n == 0 && $createMode) {
// Create a new playlist
//$type = $_GET['type'];
$title = $_GET['title'];
//$smart = $_GET['smart'];
//$summary = $_GET['summary'];
$uri = $_GET['uri'];
$plid = Playlist::create($title, 'public');
$playlist = new Playlist($plid);
$key = Plex_XML_Data::getKeyFromFullUri($uri);
$id = Plex_XML_Data::getKeyFromMetadataUri($key);
if ($id) {
$item = Plex_XML_Data::createLibraryItem($id);
$medias = $item->get_medias();
$playlist->add_medias($medias);
}
$plid = Plex_XML_Data::getPlaylistId($plid);
} else {
if ($n == 1 && $params[0] != "all") {
$plid = $params[0];
}
}
if ($plid) {
if (Plex_XML_Data::isPlaylist($plid)) {
$playlist = new Playlist(Plex_XML_Data::getAmpacheId($plid));
if ($playlist->id) {
if ($delMode) {
// Delete playlist
$playlist->delete();
} else {
// Display playlist information
Plex_XML_Data::addPlaylist($r, $playlist);
}
}
}
} else {
// List all playlists
Plex_XML_Data::setPlaylists($r);
}
} elseif ($n >= 2) {
$plid = $params[0];
if (Plex_XML_Data::isPlaylist($plid) && $params[1] == "items") {
$playlist = new Playlist(Plex_XML_Data::getAmpacheId($plid));
if ($playlist->id) {
if ($n == 2) {
if ($editMode) {
// Add a new item to playlist
$uri = $_GET['uri'];
$key = Plex_XML_Data::getKeyFromFullUri($uri);
$id = Plex_XML_Data::getKeyFromMetadataUri($key);
if ($id) {
$item = Plex_XML_Data::createLibraryItem($id);
$medias = $item->get_medias();
$playlist->add_medias($medias);
Plex_XML_Data::addPlaylist($r, $playlist);
}
} else {
Plex_XML_Data::setPlaylistItems($r, $playlist);
}
} elseif ($n == 3) {
$index = intval($params[2]);
if ($delMode) {
$playlist->delete_track_number($index);
$playlist->regenerate_track_numbers();
exit;
}
}
}
}
}
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
public static function playqueues($params)
{
$n = count($params);
$r = Plex_XML_Data::createLibContainer();
if ($n == 1) {
$playlistID = $params[0];
Plex_XML_Data::setTmpPlayQueue($r, $playlistID);
} else {
$type = $_GET['type'];
$playlistID = $_GET['playlistID'];
$uri = $_GET['uri'];
$key = $_GET['key'];
$shuffle = $_GET['shuffle'];
Plex_XML_Data::setPlayQueue($r, $type, $playlistID, $uri, $key, $shuffle);
}
Plex_XML_Data::setContainerSize($r);
self::apiOutputXml($r->asXML());
}
private static function get_data_from_map($dmap)
{
$data = array();
foreach ($dmap as $key=>$value) {
if (isset($_GET[$key])) {
if ($value == null) {
$value = $key;
}
$data[$value] = $_GET[$key];
}
}
if (isset($_GET['genre'])) {
$data['edit_tags'] = implode(',', $_GET['genre']);
}
return $data;
}
}