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 = "" . $error . "

" . $code . " " . $error . "

"; 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; } 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/"; if (strpos($url, $localrs) !== false) { $url = "http://127.0.0.1:" . Plex_XML_Data::getServerPort() . "/" . substr($url, strlen($localrs)); } if ($width && $height && $url) { $request = Requests::get($url); 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_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 )); curl_exec($ch); 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; } }