Load(); $mime_types = new MimeTypes(); if ($signed_in) { // Signed in $video_path = APP_ROOT . "/usercontent/videos/$id/video.mp4"; } else { // not signed in $rand = rand(1, 48); $video_path = APP_ROOT . "/usercontent/fake_videos/$rand/video.mp4"; } CORSHelper(); if (file_exists($video_path)) { $video_contents = file_get_contents($video_path); $video_size = filesize($video_path); $mime = $mime_types->getMimeType(pathinfo($video_path, PATHINFO_EXTENSION)); $response = response(); $response->header("Content-Type: $mime"); $response->header("Content-Length: $video_size"); $response->header("Cache-Control: max-age=86400, public"); return $video_contents; } else { throw new VideoNotFoundException($id, 404); } } public static function getInfo(string $id): string { $video_information = new Video($id); $video_information->Load(); $author_information = new Account($video_information->author_id); $author_information->Load(); CORSHelper(); return api_json([ "id" => $video_information->id, "title" => $video_information->title, "description" => $video_information->description, "likes" => $video_information->likes, "dislikes" => $video_information->dislikes, "comments" => $video_information->comments, "shares" => $video_information->shares, "author" => [ "id" => $author_information->id, "pictureHash" => $author_information->picture_hash, "username" => $author_information->username ], ]); } public static function search(): string { $query = input("query"); $video = new Video(title: $query); $videos = $video->LoadMany(); // idk if this is the greatest thing to do but jetbrains phpfart ai recommended it and it looks fine to me /shurg $accounts = Hajeebtok::$Database->Query("SELECT * FROM accounts WHERE id IN (SELECT author_id FROM videos WHERE title LIKE :title)", ["title" => "%$query%"]); if (empty($accounts)) throw new AccountNotFoundException(0, 404); $data = []; foreach ($videos as $vid) { $data[] = [ "id" => $vid["id"], "title" => $vid["title"], "description" => $vid["description"], "likes" => $vid["likes"], "dislikes" => $vid["dislikes"], "comments" => Hajeebtok::$Database->Single("SELECT COUNT(*) FROM comments WHERE video_id = :id", ["id" => $vid["id"]]), "shares" => Hajeebtok::$Database->Single("SELECT COUNT(*) FROM messages INNER JOIN videos ON messages.video_id = videos.id WHERE videos.id = :id", ["id" => $vid["id"]]), "author" => [ "id" => $vid["author_id"], "pictureHash" => $accounts[$vid["author_id"] - 1]["picture_hash"], // kinda scuffed fix "username" => $accounts[$vid["author_id"] - 1]["username"] ], ]; } CORSHelper(); return api_json($data); } public static function getThumbnail(string $id): string { $signed_in = signed_in(request()); $video_information = new Video($id); $video_information->Load(); if ($signed_in) { // Signed in $seconds = 2; $video_path = APP_ROOT . "/usercontent/videos/$id/video.mp4"; $frame_path = APP_ROOT . "/usercontent/videos/$id/thumbnail.png"; } else { // not signed in $rand = rand(1, 47); $seconds = 6; $video_path = APP_ROOT . "/usercontent/fake_videos/$rand/video.mp4"; $frame_path = APP_ROOT . "/usercontent/fake_videos/$rand/thumbnail.png"; } $mime_types = new MimeTypes(); if (!file_exists($video_path)) throw new VideoNotFoundException($id, 404); if (!file_exists($frame_path)) { Logger::Debug("Generating thumbnail for video $id"); $ffmpeg = FFMpeg\FFMpeg::create(); $video = $ffmpeg->open($video_path); $frame = $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds($seconds)); $frame->save($frame_path); } CORSHelper(); $mime = $mime_types->getMimeType(pathinfo($frame_path, PATHINFO_EXTENSION)); $response = response(); $response->header("Content-Type: $mime"); $response->header("Cache-Control: max-age=86400, public"); return file_get_contents($frame_path); } public static function getFeed(): string { // todo: implement offset & limit $signed_in = signed_in(request()); if (!$signed_in) throw new AccountNotFoundException(0, 404); $id = get_token_id(request()); $feed = []; $videos = Hajeebtok::$Database->Query("SELECT * FROM videos WHERE id NOT IN (SELECT video_id FROM views WHERE account_id = :account_id AND video_id > :offset) ORDER BY id DESC", ["account_id" => $id, "offset" => $offset]); foreach ($videos as $video) { $account = new Account($video["author_id"]); $account->Load(); $feed[] = [ "id" => $video["id"], "title" => $video["title"], "description" => $video["description"], "likes" => $video["likes"], "dislikes" => $video["dislikes"], "comments" => Hajeebtok::$Database->Single("SELECT COUNT(*) FROM comments WHERE video_id = :id", ["id" => $video["id"]]), "shares" => Hajeebtok::$Database->Single("SELECT COUNT(*) FROM messages INNER JOIN videos ON messages.video_id = videos.id WHERE videos.id = :id", ["id" => $video["id"]]), "author" => [ "id" => $video["author_id"], "pictureHash" => $account->picture_hash, "username" => $account->username, ], ]; } CORSHelper(); return api_json($feed); } public static function uploadVideo(): string { if (!signed_in(request())) throw new UnauthenticatedException(0, 401); $video_file = input()->file("video"); $title = input("title"); $description = input("description"); $author_id = get_token_id(request()); if (empty($video_file) || empty($title) || empty($author_id)) throw new InvalidRequestException(0, 400); if (!file_exists($video_file->getTmpName())) throw new InvalidRequestException(400); // save database object $video = new Video(title: $title, description: $description, author_id: $author_id); $video->Save(); // Save file $video_folder = APP_ROOT . "/usercontent/videos/" . $video->id; // check video file type $mimey = new MimeTypes(); $mime_type = $mimey->getMimeType($video_file->getExtension()); if ($mime_type != "video/mp4" && $mime_type != "video/ogg" && $mime_type != "video/webm" && $mime_type != "video/x-matroska") { $video->Delete(); throw new InvalidVideoException($video->id, 415); // media type unsupported } // move video mkdir($video_folder); Logger::Debug($video_file->getFilename() . " -> " . $video_folder . "/video.mp4"); $video_file->move($video_folder . "/video.mp4"); CORSHelper(); return api_json([ "id" => $video->id, "title" => $video->title, "description" => $video->description, "likes" => $video->likes, "dislikes" => $video->dislikes, "comments" => 0, "shares" => 0, "author" => [ "id" => $author_id, ] ]); } public static function RegisterRoutes(): void { SimpleRouter::group([ "prefix" => "/video", ], function () { SimpleRouter::get("/{id}/get", [VideoController::class, "getVideo"]); SimpleRouter::get("/{id}/info", [VideoController::class, "getInfo"]); SimpleRouter::get("/{id}/thumbnail", [VideoController::class, "getThumbnail"]); SimpleRouter::post("/upload", [VideoController::class, "uploadVideo"]); SimpleRouter::post("/search", [VideoController::class, "search"]); SimpleRouter::get("/feed", [VideoController::class, "getFeed"]); SimpleRouter::options("/{id}/get", "CORSHelper"); SimpleRouter::options("/{id}/info", "CORSHelper"); SimpleRouter::options("/{id}/thumbnail", "CORSHelper"); SimpleRouter::options("/upload", "CORSHelper"); SimpleRouter::options("/search", "CORSHelper"); }); } }