mirror of
https://github.com/openvk/openvk
synced 2024-12-23 17:12:01 +03:00
344 lines
12 KiB
PHP
344 lines
12 KiB
PHP
<?php declare(strict_types=1);
|
|
namespace openvk\Web\Models\Entities;
|
|
use openvk\Web\Util\Shell\Shell;
|
|
use openvk\Web\Util\Shell\Exceptions\{ShellUnavailableException, UnknownCommandException};
|
|
use openvk\Web\Models\VideoDrivers\VideoDriver;
|
|
use Nette\InvalidStateException as ISE;
|
|
|
|
define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<", false);
|
|
|
|
class Video extends Media
|
|
{
|
|
const TYPE_DIRECT = 0;
|
|
const TYPE_EMBED = 1;
|
|
|
|
protected $tableName = "videos";
|
|
protected $fileExtension = "mp4";
|
|
|
|
protected $processingPlaceholder = "video/rendering";
|
|
|
|
protected function saveFile(string $filename, string $hash): bool
|
|
{
|
|
if(!Shell::commandAvailable("ffmpeg") || !Shell::commandAvailable("ffprobe"))
|
|
exit(VIDEOS_FRIENDLY_ERROR);
|
|
|
|
$error = NULL;
|
|
$streams = Shell::ffprobe("-i", $filename, "-show_streams", "-select_streams v", "-loglevel error")->execute($error);
|
|
if($error !== 0)
|
|
throw new \DomainException("$filename is not a valid video file");
|
|
else if(empty($streams) || ctype_space($streams))
|
|
throw new \DomainException("$filename does not contain any video streams");
|
|
|
|
$durations = [];
|
|
preg_match_all('%duration=([0-9\.]++)%', $streams, $durations);
|
|
if(sizeof($durations[1]) === 0)
|
|
throw new \DomainException("$filename does not contain any meaningful video streams");
|
|
|
|
$length = 0;
|
|
foreach($durations[1] as $duration) {
|
|
$duration = floatval($duration);
|
|
if($duration < 1.0)
|
|
throw new \DomainException("$filename does not contain any meaningful video streams");
|
|
else
|
|
$length = max($length, $duration);
|
|
}
|
|
|
|
$this->stateChanges("length", (int) round($length, 0, PHP_ROUND_HALF_EVEN));
|
|
|
|
preg_match('%width=([0-9\.]++)%', $streams, $width);
|
|
preg_match('%height=([0-9\.]++)%', $streams, $height);
|
|
if(!empty($width) && !empty($height)) {
|
|
$this->stateChanges("width", $width[1]);
|
|
$this->stateChanges("height", $height[1]);
|
|
}
|
|
|
|
try {
|
|
if(!is_dir($dirId = dirname($this->pathFromHash($hash))))
|
|
mkdir($dirId);
|
|
|
|
$dir = $this->getBaseDir();
|
|
$ext = Shell::isPowershell() ? "ps1" : "sh";
|
|
$cmd = Shell::isPowershell() ? "powershell" : "bash";
|
|
Shell::$cmd(__DIR__ . "/../shell/processVideo.$ext", OPENVK_ROOT, $filename, $dir, $hash)->start(); #async :DDD
|
|
} catch(ShellUnavailableException $suex) {
|
|
exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "Shell is unavailable" : VIDEOS_FRIENDLY_ERROR);
|
|
} catch(UnknownCommandException $ucex) {
|
|
exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "bash is not installed" : VIDEOS_FRIENDLY_ERROR);
|
|
}
|
|
|
|
usleep(200100);
|
|
return true;
|
|
}
|
|
|
|
protected function checkIfFileIsProcessed(): bool
|
|
{
|
|
if($this->getType() != Video::TYPE_DIRECT)
|
|
return true;
|
|
|
|
if(!file_exists($this->getFileName())) {
|
|
if((time() - $this->getRecord()->last_checked) > 3600) {
|
|
# TODO notify that video processor is probably dead
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function getName(): string
|
|
{
|
|
return $this->getRecord()->name;
|
|
}
|
|
|
|
function getType(): int
|
|
{
|
|
if(!is_null($this->getRecord()->hash))
|
|
return Video::TYPE_DIRECT;
|
|
else if(!is_null($this->getRecord()->link))
|
|
return Video::TYPE_EMBED;
|
|
}
|
|
|
|
function getVideoDriver(): ?VideoDriver
|
|
{
|
|
if($this->getType() !== Video::TYPE_EMBED)
|
|
return NULL;
|
|
|
|
[$videoDriver, $pointer] = explode(":", $this->getRecord()->link);
|
|
$videoDriver = "openvk\\Web\\Models\\VideoDrivers\\$videoDriver" . "VideoDriver";
|
|
if(!class_exists($videoDriver))
|
|
return NULL;
|
|
|
|
return new $videoDriver($pointer);
|
|
}
|
|
|
|
function getThumbnailURL(): string
|
|
{
|
|
if($this->getType() === Video::TYPE_DIRECT) {
|
|
if(!$this->isProcessed())
|
|
return "/assets/packages/static/openvk/video/rendering.apng";
|
|
|
|
return preg_replace("%\.[A-z0-9]++$%", ".gif", $this->getURL());
|
|
} else {
|
|
return $this->getVideoDriver()->getThumbnailURL();
|
|
}
|
|
}
|
|
|
|
function getOwnerVideo(): int
|
|
{
|
|
return $this->getRecord()->owner;
|
|
}
|
|
|
|
function getApiStructure(?User $user = NULL): object
|
|
{
|
|
$fromYoutube = $this->getType() == Video::TYPE_EMBED;
|
|
$dimensions = $this->getDimensions();
|
|
$res = (object)[
|
|
"type" => "video",
|
|
"video" => [
|
|
"can_comment" => 1,
|
|
"can_like" => 1, // we don't h-have wikes in videos
|
|
"can_repost" => 1,
|
|
"can_subscribe" => 1,
|
|
"can_add_to_faves" => 0,
|
|
"can_add" => 0,
|
|
"comments" => $this->getCommentsCount(),
|
|
"date" => $this->getPublicationTime()->timestamp(),
|
|
"description" => $this->getDescription(),
|
|
"duration" => $this->getLength(),
|
|
"image" => [
|
|
[
|
|
"url" => $this->getThumbnailURL(),
|
|
"width" => 320,
|
|
"height" => 240,
|
|
"with_padding" => 1
|
|
]
|
|
],
|
|
"width" => $dimensions ? $dimensions[0] : 640,
|
|
"height" => $dimensions ? $dimensions[1] : 480,
|
|
"id" => $this->getVirtualId(),
|
|
"owner_id" => $this->getOwner()->getId(),
|
|
"user_id" => $this->getOwner()->getId(),
|
|
"title" => $this->getName(),
|
|
"is_favorite" => false,
|
|
"player" => !$fromYoutube ? $this->getURL() : $this->getVideoDriver()->getURL(),
|
|
"files" => !$fromYoutube ? [
|
|
"mp4_480" => $this->getURL()
|
|
] : NULL,
|
|
"platform" => $fromYoutube ? "youtube" : NULL,
|
|
"added" => 0,
|
|
"repeat" => 0,
|
|
"type" => "video",
|
|
"views" => 0,
|
|
"is_processed" => $this->isProcessed(),
|
|
"reposts" => [
|
|
"count" => 0,
|
|
"user_reposted" => 0
|
|
]
|
|
]
|
|
];
|
|
|
|
if(!is_null($user)) {
|
|
$res->video["likes"] = [
|
|
"count" => $this->getLikesCount(),
|
|
"user_likes" => $this->hasLikeFrom($user)
|
|
];
|
|
}
|
|
|
|
return $res;
|
|
}
|
|
|
|
function toVkApiStruct(?User $user): object
|
|
{
|
|
return $this->getApiStructure($user);
|
|
}
|
|
|
|
function setLink(string $link): string
|
|
{
|
|
if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/youtube.txt"), $link, $matches)) {
|
|
$pointer = "YouTube:$matches[1]";
|
|
/*} else if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/vimeo.txt"), $link, $matches)) {
|
|
$pointer = "Vimeo:$matches[1]";*/
|
|
} else {
|
|
throw new ISE("Invalid link");
|
|
}
|
|
|
|
$this->stateChanges("link", $pointer);
|
|
|
|
return $pointer;
|
|
}
|
|
|
|
function isDeleted(): bool
|
|
{
|
|
if ($this->getRecord()->deleted == 1)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
function deleteVideo(): void
|
|
{
|
|
$this->setDeleted(1);
|
|
$this->unwire();
|
|
$this->save();
|
|
}
|
|
|
|
static function fastMake(int $owner, string $name = "Unnamed Video.ogv", string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video
|
|
{
|
|
if(OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading'])
|
|
exit(VIDEOS_FRIENDLY_ERROR);
|
|
|
|
$video = new Video;
|
|
$video->setOwner($owner);
|
|
$video->setName(ovk_proc_strtr($name, 61));
|
|
$video->setDescription(ovk_proc_strtr($description, 300));
|
|
$video->setAnonymous($anon);
|
|
$video->setCreated(time());
|
|
$video->setFile($file);
|
|
$video->setUnlisted($unlisted);
|
|
$video->save();
|
|
|
|
return $video;
|
|
}
|
|
|
|
function fillDimensions()
|
|
{
|
|
$hash = $this->getRecord()->hash;
|
|
$path = $this->pathFromHash($hash);
|
|
if(!file_exists($path)) {
|
|
$this->stateChanges("width", 0);
|
|
$this->stateChanges("height", 0);
|
|
$this->stateChanges("length", 0);
|
|
$this->save();
|
|
return false;
|
|
}
|
|
|
|
$streams = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute($error);
|
|
$durations = [];
|
|
preg_match_all('%duration=([0-9\.]++)%', $streams, $durations);
|
|
|
|
$length = 0;
|
|
foreach($durations[1] as $duration) {
|
|
$duration = floatval($duration);
|
|
if($duration < 1.0)
|
|
continue;
|
|
else
|
|
$length = max($length, $duration);
|
|
}
|
|
$this->stateChanges("length", (int) round($length, 0, PHP_ROUND_HALF_EVEN));
|
|
|
|
preg_match('%width=([0-9\.]++)%', $streams, $width);
|
|
preg_match('%height=([0-9\.]++)%', $streams, $height);
|
|
|
|
if(!empty($width) && !empty($height)) {
|
|
$this->stateChanges("width", $width[1]);
|
|
$this->stateChanges("height", $height[1]);
|
|
}
|
|
|
|
$this->save();
|
|
|
|
return true;
|
|
}
|
|
|
|
function getDimensions()
|
|
{
|
|
if($this->getType() == Video::TYPE_EMBED) return [320, 180];
|
|
|
|
$width = $this->getRecord()->width;
|
|
$height = $this->getRecord()->height;
|
|
|
|
if(!$width) return NULL;
|
|
return $width != 0 ? [$width, $height] : NULL;
|
|
}
|
|
|
|
function getLength()
|
|
{
|
|
return $this->getRecord()->length;
|
|
}
|
|
|
|
function getFormattedLength(): string
|
|
{
|
|
$len = $this->getLength();
|
|
if(!$len) return "00:00";
|
|
$mins = floor($len / 60);
|
|
$secs = $len - ($mins * 60);
|
|
return (
|
|
str_pad((string) $mins, 2, "0", STR_PAD_LEFT)
|
|
. ":" .
|
|
str_pad((string) $secs, 2, "0", STR_PAD_LEFT)
|
|
);
|
|
}
|
|
|
|
function canBeViewedBy(?User $user = NULL): bool
|
|
{
|
|
if($this->isDeleted() || $this->getOwner()->isDeleted()) {
|
|
return false;
|
|
}
|
|
|
|
if(get_class($this->getOwner()) == "openvk\\Web\\Models\\Entities\\User") {
|
|
return $this->getOwner()->canBeViewedBy($user) && $this->getOwner()->getPrivacyPermission('videos.read', $user);
|
|
} else {
|
|
# Groups doesn't have videos but ok
|
|
return $this->getOwner()->canBeViewedBy($user);
|
|
}
|
|
}
|
|
|
|
function toNotifApiStruct()
|
|
{
|
|
$fromYoutube = $this->getType() == Video::TYPE_EMBED;
|
|
$res = (object)[];
|
|
|
|
$res->id = $this->getVirtualId();
|
|
$res->owner_id = $this->getOwner()->getId();
|
|
$res->title = $this->getName();
|
|
$res->description = $this->getDescription();
|
|
$res->duration = $this->getLength();
|
|
$res->link = "/video".$this->getOwner()->getId()."_".$this->getVirtualId();
|
|
$res->image = $this->getThumbnailURL();
|
|
$res->date = $this->getPublicationTime()->timestamp();
|
|
$res->views = 0;
|
|
$res->player = !$fromYoutube ? $this->getURL() : $this->getVideoDriver()->getURL();
|
|
|
|
return $res;
|
|
}
|
|
}
|