367 lines
13 KiB
PHP
367 lines
13 KiB
PHP
<?php
|
|
namespace Uiharu\APIs;
|
|
|
|
use stdClass;
|
|
use DOMDocument;
|
|
use Exception;
|
|
use InvalidArgumentException;
|
|
use Uiharu\Colour;
|
|
use Uiharu\Config;
|
|
use Uiharu\FFMPEG;
|
|
use Uiharu\IHasMediaInfo;
|
|
use Uiharu\MediaTypeExts;
|
|
use Uiharu\UihContext;
|
|
use Uiharu\Url;
|
|
use Uiharu\Lookup\EEPROMLookupResult;
|
|
use Uiharu\Lookup\TwitterLookupResult;
|
|
use Uiharu\Lookup\TwitterLookupTweetResult;
|
|
use Uiharu\Lookup\TwitterLookupUserResult;
|
|
use Uiharu\Lookup\YouTubeLookupResult;
|
|
use Uiharu\Lookup\NicoNicoLookupResult;
|
|
use Index\MediaType;
|
|
use Index\Data\IDbConnection;
|
|
use Index\Http\HttpFx;
|
|
use Index\Performance\Stopwatch;
|
|
|
|
final class v1_0 implements \Uiharu\IApi {
|
|
private UihContext $ctx;
|
|
private IDbConnection $db;
|
|
|
|
public function __construct(UihContext $ctx) {
|
|
$this->ctx = $ctx;
|
|
$this->db = $ctx->getDatabase();
|
|
}
|
|
|
|
public function match(string $url): string {
|
|
return !str_starts_with($url, '/v');
|
|
}
|
|
|
|
public function register(HttpFx $router): void {
|
|
$router->get('/metadata', [$this, 'getMetadata']);
|
|
$router->post('/metadata', [$this, 'postMetadata']);
|
|
$router->get('/metadata/batch', [$this, 'getMetadataBatch']);
|
|
$router->post('/metadata/batch', [$this, 'postMetadataBatch']);
|
|
$router->get('/metadata/thumb/audio', [$this, 'getThumbAudio']);
|
|
$router->get('/metadata/thumb/video', [$this, 'getThumbVideo']);
|
|
}
|
|
|
|
public function getThumbAudio($response, $request) {
|
|
$targetUrl = (string)$request->getParam('url');
|
|
|
|
if(empty($targetUrl))
|
|
return 400;
|
|
|
|
try {
|
|
$parsedUrl = Url::parse($targetUrl);
|
|
} catch(InvalidArgumentException $ex) {
|
|
return 404;
|
|
}
|
|
|
|
if(!$parsedUrl->isWeb())
|
|
return 400;
|
|
|
|
$response->setContentType('image/png');
|
|
$response->setCacheControl('public', 'max-age=31536000', 'immutable');
|
|
$response->setContent(FFMPEG::grabFirstAudioCover($parsedUrl));
|
|
}
|
|
|
|
public function getThumbVideo($response, $request) {
|
|
$targetUrl = (string)$request->getParam('url');
|
|
|
|
if(empty($targetUrl))
|
|
return 400;
|
|
|
|
try {
|
|
$parsedUrl = Url::parse($targetUrl);
|
|
} catch(InvalidArgumentException $ex) {
|
|
return 404;
|
|
}
|
|
|
|
if(!$parsedUrl->isWeb())
|
|
return 400;
|
|
|
|
$response->setContentType('image/png');
|
|
$response->setCacheControl('public', 'max-age=31536000', 'immutable');
|
|
$response->setContent(FFMPEG::grabFirstVideoFrame($parsedUrl));
|
|
}
|
|
|
|
public function getMetadata($response, $request) {
|
|
if($request->getMethod() === 'HEAD') {
|
|
$response->setTypeJson();
|
|
return;
|
|
}
|
|
|
|
return $this->handleMetadata(
|
|
$response, $request,
|
|
(string)$request->getParam('url')
|
|
);
|
|
}
|
|
|
|
public function postMetadata($response, $request) {
|
|
if(!$request->isStreamContent())
|
|
return 400;
|
|
|
|
return $this->handleMetadata(
|
|
$response, $request,
|
|
$request->getContent()->getStream()->read(1000)
|
|
);
|
|
}
|
|
|
|
public function getMetadataBatch($response, $request) {
|
|
if($request->getMethod() === 'HEAD') {
|
|
$response->setTypeJson();
|
|
return;
|
|
}
|
|
|
|
return $this->handleMetadataBatch(
|
|
$response, $request,
|
|
$request->getParam('url', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY)
|
|
);
|
|
}
|
|
|
|
public function postMetadataBatch($response, $request) {
|
|
if(!$request->isFormContent())
|
|
return 400;
|
|
|
|
return $this->handleMetadataBatch(
|
|
$response, $request,
|
|
$request->getContent()->getParam('url', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY)
|
|
);
|
|
}
|
|
|
|
private function metadataLookup(string $targetUrl, bool $enableCache, bool $includeRawResult) {
|
|
$sw = Stopwatch::startNew();
|
|
$resp = new stdClass;
|
|
|
|
if(empty($targetUrl))
|
|
return 400;
|
|
|
|
try {
|
|
$parsedUrl = Url::parse($targetUrl);
|
|
} catch(InvalidArgumentException $ex) {
|
|
$resp->status = 400;
|
|
$resp->error = 'metadata:uri';
|
|
return $resp;
|
|
}
|
|
|
|
// if no scheme is specified, try https
|
|
if(!$parsedUrl->hasScheme())
|
|
$parsedUrl->setScheme('https');
|
|
|
|
$resp->uri = $parsedUrl->toV1();
|
|
$resp->url = (string)$parsedUrl;
|
|
|
|
$urlHash = $parsedUrl->calculateHash(false);
|
|
|
|
if($enableCache) {
|
|
$cacheFetch = $this->db->prepare('SELECT `metadata_resp` FROM `uih_metadata_cache` WHERE `metadata_url` = UNHEX(?) AND `metadata_created` > NOW() - INTERVAL 10 MINUTE');
|
|
$cacheFetch->addParameter(1, $urlHash);
|
|
$cacheFetch->execute();
|
|
$cacheResult = $cacheFetch->getResult();
|
|
if($cacheResult->next()) {
|
|
$cacheResp = json_decode($cacheResult->getString(0));
|
|
if($cacheResp !== null)
|
|
$resp = $cacheResp;
|
|
}
|
|
}
|
|
|
|
if(empty($resp->type)) {
|
|
$lookup = $this->ctx->matchLookup($parsedUrl);
|
|
|
|
if($lookup !== null)
|
|
try {
|
|
$result = $lookup->lookup($parsedUrl);
|
|
|
|
$resp->uri = $result->getUrl()->toV1();
|
|
$resp->type = $result->getObjectType();
|
|
|
|
if($result->hasMediaType())
|
|
$resp->content_type = MediaTypeExts::toV1($result->getMediaType());
|
|
if($result->hasColour())
|
|
$resp->color = Colour::toHexString($result->getColour());
|
|
if($result->hasTitle())
|
|
$resp->title = $result->getTitle();
|
|
if($result->hasSiteName())
|
|
$resp->site_name = $result->getSiteName();
|
|
if($result->hasDescription())
|
|
$resp->description = $result->getDescription();
|
|
if($result->hasPreviewImage())
|
|
$resp->image = $result->getPreviewImage();
|
|
|
|
if($result instanceof TwitterLookupResult) {
|
|
if($result instanceof TwitterLookupTweetResult)
|
|
$resp->tweet_id = $result->getTwitterTweetId();
|
|
|
|
if($result instanceof TwitterLookupUserResult)
|
|
$resp->twitter_user_name = $result->getTwitterUserName();
|
|
|
|
if(UIH_DEBUG)
|
|
$resp->dbg_twitter_info = $result->getTwitterResult();
|
|
}
|
|
|
|
if($result instanceof YouTubeLookupResult) {
|
|
$resp->youtube_video_id = $result->getYouTubeVideoId();
|
|
|
|
if($result->hasYouTubeVideoStartTime())
|
|
$resp->youtube_start_time = $result->getYouTubeVideoStartTime();
|
|
if($result->hasYouTubePlayListId())
|
|
$resp->youtube_playlist = $result->getYouTubePlayListId();
|
|
if($result->hasYouTubePlayListIndex())
|
|
$resp->youtube_playlist_index = $result->getYouTubePlayListIndex();
|
|
|
|
if(UIH_DEBUG) {
|
|
$resp->dbg_youtube_info = $result->getYouTubeVideoInfo();
|
|
$resp->dbg_youtube_query = $result->getYouTubeUrlQuery();
|
|
}
|
|
}
|
|
|
|
if($result instanceof NicoNicoLookupResult) {
|
|
$resp->nicovideo_video_id = $result->getNicoNicoVideoId();
|
|
|
|
if($result->hasNicoNicoVideoStartTime())
|
|
$resp->nicovideo_start_time = $result->getNicoNicoVideoStartTime();
|
|
|
|
if(UIH_DEBUG) {
|
|
$resp->dbg_nicovideo_thumb_info = $result->getNicoNicoThumbInfo()->ownerDocument->saveXML();
|
|
$resp->dbg_nicovideo_query = $result->getNicoNicoUrlQuery();
|
|
}
|
|
}
|
|
|
|
if($result instanceof IHasMediaInfo) {
|
|
if($result->isMedia()) {
|
|
$resp->is_image = $result->isImage();
|
|
$resp->is_audio = $result->isAudio();
|
|
$resp->is_video = $result->isVideo();
|
|
|
|
if($result->hasDimensions()) {
|
|
$resp->width = $result->getWidth();
|
|
$resp->height = $result->getHeight();
|
|
}
|
|
|
|
$resp->media = new stdClass;
|
|
$resp->media->confidence = $result->getConfidence();
|
|
|
|
if($result->hasAspectRatio())
|
|
$resp->media->aspect_ratio = $result->getAspectRatio();
|
|
if($result->hasDuration())
|
|
$resp->media->duration = $result->getDuration();
|
|
if($result->hasSize())
|
|
$resp->media->size = $result->getSize();
|
|
if($result->hasBitRate())
|
|
$resp->media->bitrate = $result->getBitRate();
|
|
|
|
if($result->hasAudioTags()) {
|
|
$audioTags = $result->getAudioTags();
|
|
$resp->media->tags = new stdClass;
|
|
if($audioTags->hasTitle())
|
|
$resp->media->tags->title = $audioTags->getTitle();
|
|
if($audioTags->hasArtist())
|
|
$resp->media->tags->artist = $audioTags->getArtist();
|
|
if($audioTags->hasAlbum())
|
|
$resp->media->tags->album = $audioTags->getAlbum();
|
|
if($audioTags->hasDate())
|
|
$resp->media->tags->date = $audioTags->getDate();
|
|
if($audioTags->hasComment())
|
|
$resp->media->tags->comment = $audioTags->getComment();
|
|
if($audioTags->hasGenre())
|
|
$resp->media->tags->genre = $audioTags->getGenre();
|
|
}
|
|
}
|
|
|
|
if($result instanceof EEPROMLookupResult) {
|
|
$resp->eeprom_file_id = $result->getEEPROMId();
|
|
$resp->eeprom_file_info = $result->getEEPROMInfo();
|
|
}
|
|
|
|
if(UIH_DEBUG && $result->hasMediaInfo())
|
|
$resp->dbg_media_info = $result->getMediaInfo();
|
|
}
|
|
} catch(Exception $ex) {
|
|
$resp->status = 500;
|
|
$resp->error = 'metadata:lookup';
|
|
if(UIH_DEBUG) {
|
|
$resp->dbg_msg = $ex->getMessage();
|
|
$resp->dbg_ex = (string)$ex;
|
|
}
|
|
return $resp;
|
|
}
|
|
|
|
$sw->stop();
|
|
$resp->took = $sw->getElapsedTime() / 1000;
|
|
$respJson = json_encode($resp);
|
|
$replaceCache = $this->db->prepare('REPLACE INTO `uih_metadata_cache` (`metadata_url`, `metadata_resp`) VALUES (UNHEX(?), ?)');
|
|
$replaceCache->addParameter(1, $urlHash);
|
|
$replaceCache->addParameter(2, $respJson);
|
|
$replaceCache->execute();
|
|
}
|
|
|
|
if(!empty($respJson))
|
|
return $respJson;
|
|
|
|
return $resp;
|
|
}
|
|
|
|
private function handleMetadata($response, $request, string $targetUrl) {
|
|
$enableCache = !UIH_DEBUG || $request->hasParam('_cache');
|
|
$includeRawResult = UIH_DEBUG || $request->hasParam('include_raw');
|
|
|
|
$result = $this->metadataLookup($targetUrl, $enableCache, $includeRawResult);
|
|
if(is_int($result))
|
|
return $result;
|
|
|
|
if(is_object($result)) {
|
|
if(!empty($result->status))
|
|
$response->setStatusCode($result->status);
|
|
} elseif(is_string($result)) {
|
|
$response->setTypeJson();
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function handleMetadataBatch($response, $request, array $urls) {
|
|
$sw = Stopwatch::startNew();
|
|
|
|
if(count($urls) > 20)
|
|
return 400;
|
|
|
|
$enableCache = !UIH_DEBUG || $request->hasParam('_cache');
|
|
$includeRawResult = UIH_DEBUG || $request->hasParam('include_raw');
|
|
|
|
$handled = [];
|
|
$results = [];
|
|
|
|
foreach($urls as $url) {
|
|
if(!is_string($url))
|
|
continue;
|
|
|
|
$cleanUrl = trim($url, '/?&# ');
|
|
if(in_array($cleanUrl, $handled))
|
|
continue;
|
|
$handled[] = $cleanUrl;
|
|
|
|
$result = $this->metadataLookup($url, $enableCache, $includeRawResult);
|
|
if(is_int($result)) {
|
|
$status = $result;
|
|
$result = new stdClass;
|
|
$result->status = $status;
|
|
$result->error = 'batch:status';
|
|
} elseif(is_string($result)) {
|
|
$result = json_decode($result);
|
|
}
|
|
|
|
$results[] = [
|
|
'url' => $url,
|
|
'info' => $result,
|
|
];
|
|
}
|
|
|
|
$sw->stop();
|
|
|
|
return [
|
|
'took' => $sw->getElapsedTime() / 1000,
|
|
'results' => $results,
|
|
];
|
|
}
|
|
}
|