Add something related with videos

- Теперь видосы работают как аудио, пользователи могут добавлять и удалять видео из коллекции. Но, правда, после обновления пользователи потеряют все свои видео, потом подумаю как исправить
- Ещё теперь видео можно загружать в группу, жесть. И на странице группы теперь показывается 2 случайных видео из группы
- Возможно, исправлена загрузка видео под виндовс (а может я её сломал)
- У видосов теперь сохраняется ширина и высота, а так же длина
- У прикреплённого видео рядом с названием показывается его длина
- Видео теперь размещаются в masonry layout. Если помимо видео у поста есть другие фотографии или другие видео, то показывается только обложка видео и кнопка проигрывания
- В класс video в api добавлена поддержка просмотра видеозаписей из групп
This commit is contained in:
lalka2018 2023-11-21 20:14:38 +03:00
parent 93bfe6e886
commit 791c36416d
20 changed files with 365 additions and 69 deletions

View file

@ -36,21 +36,22 @@ final class Video extends VKAPIRequestHandler
]; ];
} else { } else {
if ($owner_id > 0) if ($owner_id > 0)
$user = (new UsersRepo)->get($owner_id); $owner = (new UsersRepo)->get($owner_id);
else else
$this->fail(1, "Not implemented"); $owner = (new ClubsRepo)->get(abs($owner_id));
if(!$user->getPrivacyPermission('videos.read', $this->getUser())) { if(!$owner)
$this->fail(20, "Invalid user");
if($owner_id > 0 && !$owner->getPrivacyPermission('videos.read', $this->getUser()))
$this->fail(20, "Access denied: this user chose to hide his videos"); $this->fail(20, "Access denied: this user chose to hide his videos");
}
$videos = (new VideosRepo)->getByUser($user, $offset + 1, $count); $videos = (new VideosRepo)->getByEntityId($owner_id, $offset, $count);
$videosCount = (new VideosRepo)->getUserVideosCount($user); $videosCount = (new VideosRepo)->getVideosCountByEntityId($owner_id);
$items = []; $items = [];
foreach ($videos as $video) { foreach ($videos as $video)
$items[] = $video->getApiStructure($this->getUser()); $items[] = $video->getApiStructure($this->getUser());
}
return (object) [ return (object) [
"count" => $videosCount, "count" => $videosCount,

View file

@ -414,6 +414,11 @@ class Club extends RowModel
return (bool) $this->getRecord()->everyone_can_upload_audios; return (bool) $this->getRecord()->everyone_can_upload_audios;
} }
function isEveryoneCanUploadVideos(): bool
{
return false;
}
function canUploadAudio(?User $user): bool function canUploadAudio(?User $user): bool
{ {
if(!$user) if(!$user)
@ -422,6 +427,14 @@ class Club extends RowModel
return $this->isEveryoneCanUploadAudios() || $this->canBeModifiedBy($user); return $this->isEveryoneCanUploadAudios() || $this->canBeModifiedBy($user);
} }
function canUploadVideo(?User $user): bool
{
if(!$user)
return NULL;
return $this->isEveryoneCanUploadAudios() || $this->canBeModifiedBy($user);
}
function getAudiosCollectionSize() function getAudiosCollectionSize()
{ {
return (new \openvk\Web\Models\Repositories\Audios)->getClubCollectionSize($this); return (new \openvk\Web\Models\Repositories\Audios)->getClubCollectionSize($this);

View file

@ -1,6 +1,6 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Traits; namespace openvk\Web\Models\Entities\Traits;
use openvk\Web\Models\Entities\{Attachable, Photo}; use openvk\Web\Models\Entities\{Attachable, Photo, Video};
use openvk\Web\Util\Makima\Makima; use openvk\Web\Util\Makima\Makima;
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
@ -36,10 +36,10 @@ trait TAttachmentHost
if($h < 0) if($h < 0)
$h = $w; $h = $w;
$children = $this->getChildren(); $children = iterator_to_array($this->getChildren());
$skipped = $photos = $result = []; $skipped = $photos = $result = [];
foreach($children as $child) { foreach($children as $child) {
if($child instanceof Photo) { if($child instanceof Photo || $child instanceof Video && $child->getDimensions()) {
$photos[] = $child; $photos[] = $child;
continue; continue;
} }
@ -68,6 +68,7 @@ trait TAttachmentHost
"height" => $height . "px", "height" => $height . "px",
"tiles" => $result, "tiles" => $result,
"extras" => $skipped, "extras" => $skipped,
"count" => sizeof($children),
]; ];
} }

View file

@ -4,6 +4,7 @@ use openvk\Web\Util\Shell\Shell;
use openvk\Web\Util\Shell\Exceptions\{ShellUnavailableException, UnknownCommandException}; use openvk\Web\Util\Shell\Exceptions\{ShellUnavailableException, UnknownCommandException};
use openvk\Web\Models\VideoDrivers\VideoDriver; use openvk\Web\Models\VideoDrivers\VideoDriver;
use Nette\InvalidStateException as ISE; use Nette\InvalidStateException as ISE;
use Chandler\Database\DatabaseConnection;
define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<", false); define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<", false);
@ -34,10 +35,25 @@ class Video extends Media
if(sizeof($durations[1]) === 0) if(sizeof($durations[1]) === 0)
throw new \DomainException("$filename does not contain any meaningful video streams"); throw new \DomainException("$filename does not contain any meaningful video streams");
foreach($durations[1] as $duration) $length = 0;
if(floatval($duration) < 1.0) foreach($durations[1] as $duration) {
$duration = floatval($duration);
if($duration < 1.0)
throw new \DomainException("$filename does not contain any meaningful video streams"); 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("width", $height[1]);
}
try { try {
if(!is_dir($dirId = dirname($this->pathFromHash($hash)))) if(!is_dir($dirId = dirname($this->pathFromHash($hash))))
mkdir($dirId); mkdir($dirId);
@ -45,7 +61,11 @@ class Video extends Media
$dir = $this->getBaseDir(); $dir = $this->getBaseDir();
$ext = Shell::isPowershell() ? "ps1" : "sh"; $ext = Shell::isPowershell() ? "ps1" : "sh";
$cmd = Shell::isPowershell() ? "powershell" : "bash"; $cmd = Shell::isPowershell() ? "powershell" : "bash";
Shell::$cmd(__DIR__ . "/../shell/processVideo.$ext", OPENVK_ROOT, $filename, $dir, $hash)->start(); #async :DDD
if($cmd == "bash")
Shell::$cmd(__DIR__ . "/../shell/processVideo.$ext", OPENVK_ROOT, $filename, $dir, $hash)->start(); # async :DDD
else
Shell::$cmd(__DIR__ . "/../shell/processVideo.$ext", OPENVK_ROOT, $filename, $dir, $hash)->execute($err); # под виндой только execute
} catch(ShellUnavailableException $suex) { } catch(ShellUnavailableException $suex) {
exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "Shell is unavailable" : VIDEOS_FRIENDLY_ERROR); exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "Shell is unavailable" : VIDEOS_FRIENDLY_ERROR);
} catch(UnknownCommandException $ucex) { } catch(UnknownCommandException $ucex) {
@ -118,11 +138,12 @@ class Video extends Media
function getApiStructure(?User $user = NULL): object function getApiStructure(?User $user = NULL): object
{ {
$fromYoutube = $this->getType() == Video::TYPE_EMBED; $fromYoutube = $this->getType() == Video::TYPE_EMBED;
$dimensions = $this->getDimensions();
$res = (object)[ $res = (object)[
"type" => "video", "type" => "video",
"video" => [ "video" => [
"can_comment" => 1, "can_comment" => 1,
"can_like" => 1, // we don't h-have wikes in videos "can_like" => 1,
"can_repost" => 1, "can_repost" => 1,
"can_subscribe" => 1, "can_subscribe" => 1,
"can_add_to_faves" => 0, "can_add_to_faves" => 0,
@ -130,7 +151,7 @@ class Video extends Media
"comments" => $this->getCommentsCount(), "comments" => $this->getCommentsCount(),
"date" => $this->getPublicationTime()->timestamp(), "date" => $this->getPublicationTime()->timestamp(),
"description" => $this->getDescription(), "description" => $this->getDescription(),
"duration" => 0, // я хуй знает как получить длину видео "duration" => $this->getLength(),
"image" => [ "image" => [
[ [
"url" => $this->getThumbnailURL(), "url" => $this->getThumbnailURL(),
@ -139,8 +160,8 @@ class Video extends Media
"with_padding" => 1 "with_padding" => 1
] ]
], ],
"width" => 640, "width" => $dimensions ? NULL : $dimensions[0],
"height" => 480, "height" => $dimensions ? NULL : $dimensions[1],
"id" => $this->getVirtualId(), "id" => $this->getVirtualId(),
"owner_id" => $this->getOwner()->getId(), "owner_id" => $this->getOwner()->getId(),
"user_id" => $this->getOwner()->getId(), "user_id" => $this->getOwner()->getId(),
@ -194,10 +215,7 @@ class Video extends Media
function isDeleted(): bool function isDeleted(): bool
{ {
if ($this->getRecord()->deleted == 1) return $this->getRecord()->deleted == 1;
return TRUE;
else
return FALSE;
} }
function deleteVideo(): void function deleteVideo(): void
@ -205,6 +223,8 @@ class Video extends Media
$this->setDeleted(1); $this->setDeleted(1);
$this->unwire(); $this->unwire();
$this->save(); $this->save();
$ctx->table("video_relations")->where("video", $this->getId())->delete();
} }
static function fastMake(int $owner, string $name = "Unnamed Video.ogv", string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video static function fastMake(int $owner, string $name = "Unnamed Video.ogv", string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video
@ -221,7 +241,7 @@ class Video extends Media
$video->setFile($file); $video->setFile($file);
$video->setUnlisted($unlisted); $video->setUnlisted($unlisted);
$video->save(); $video->save();
return $video; return $video;
} }
@ -243,4 +263,126 @@ class Video extends Media
return $res; return $res;
} }
function isInLibraryOf($entity): bool
{
return sizeof(DatabaseConnection::i()->getContext()->table("video_relations")->where([
"entity" => $entity->getId() * ($entity instanceof Club ? -1 : 1),
"video" => $this->getId(),
])) != 0;
}
function add($entity): bool
{
if($this->isInLibraryOf($entity))
return false;
$entityId = $entity->getId() * ($entity instanceof Club ? -1 : 1);
$audioRels = DatabaseConnection::i()->getContext()->table("video_relations");
$audioRels->insert([
"entity" => $entityId,
"video" => $this->getId(),
]);
return true;
}
function remove($entity): bool
{
if(!$this->isInLibraryOf($entity))
return false;
DatabaseConnection::i()->getContext()->table("video_relations")->where([
"entity" => $entity->getId() * ($entity instanceof Club ? -1 : 1),
"video" => $this->getId(),
])->delete();
return true;
}
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 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);
#exit(var_dump($path));
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 NULL;
$width = $this->getRecord()->width;
$height = $this->getRecord()->height;
if(!$width) $this->fillDimensions();
return $width != 0 ? [$width, $height] : NULL;
}
function delete(bool $softly = true): void
{
$ctx = DatabaseConnection::i()->getContext();
$ctx->table("video_relations")->where("video", $this->getId())->delete();
parent::delete($softly);
}
} }

View file

@ -1,6 +1,6 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories; namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\User; use openvk\Web\Models\Entities\{User, Club};
use openvk\Web\Models\Entities\Video; use openvk\Web\Models\Entities\Video;
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
@ -8,11 +8,13 @@ class Videos
{ {
private $context; private $context;
private $videos; private $videos;
private $rels;
function __construct() function __construct()
{ {
$this->context = DatabaseConnection::i()->getContext(); $this->context = DatabaseConnection::i()->getContext();
$this->videos = $this->context->table("videos"); $this->videos = $this->context->table("videos");
$this->rels = $this->context->table("video_relations");
} }
function get(int $id): ?Video function get(int $id): ?Video
@ -33,17 +35,66 @@ class Videos
return new Video($videos); return new Video($videos);
} }
function getByEntityId(int $entity, int $offset = 0, ?int $limit = NULL): \Traversable
{
$limit ??= OPENVK_DEFAULT_PER_PAGE;
$iter = $this->rels->where("entity", $entity)->limit($limit, $offset)->order("id DESC");
foreach($iter as $rel) {
$vid = $this->get($rel->video);
if(!$vid || $vid->isDeleted()) {
continue;
}
yield $vid;
}
}
function getVideosCountByEntityId(int $id)
{
return sizeof($this->rels->where("entity", $id));
}
function getByUser(User $user, int $page = 1, ?int $perPage = NULL): \Traversable function getByUser(User $user, int $page = 1, ?int $perPage = NULL): \Traversable
{ {
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; return $this->getByEntityId($user->getId(), ($perPage * ($page - 1)), $perPage);
foreach($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->page($page, $perPage)->order("created DESC") as $video) }
yield new Video($video);
function getByClub(Club $club, int $page = 1, ?int $perPage = NULL): \Traversable
{
return $this->getByEntityId($club->getRealId(), ($perPage * ($page - 1)), $perPage);
} }
function getUserVideosCount(User $user): int function getUserVideosCount(User $user): int
{ {
return sizeof($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])); return sizeof($this->rels->where("entity", $user->getId()));
}
function getRandomTwoVideosByEntityId(int $id): Array
{
$iter = $this->rels->where("entity", $id);
$ids = [];
foreach($iter as $it)
$ids[] = $it->video;
$shuffleSeed = openssl_random_pseudo_bytes(6);
$shuffleSeed = hexdec(bin2hex($shuffleSeed));
$ids = knuth_shuffle($ids, $shuffleSeed);
$ids = array_slice($ids, 0, 3);
$videos = [];
foreach($ids as $id) {
$video = $this->get((int)$id);
if(!$video || $video->isDeleted())
continue;
$videos[] = $video;
}
return $videos;
} }
function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream

View file

@ -9,12 +9,12 @@ $temp2 = [System.IO.Path]::GetTempFileName()
$shell = Get-WmiObject Win32_process -filter "ProcessId = $PID" $shell = Get-WmiObject Win32_process -filter "ProcessId = $PID"
$shell.SetPriority(16384) $shell.SetPriority(16384)
Remove-Item $temp
Move-Item $file $temp Move-Item $file $temp
# video stub logic was implicitly deprecated, so we start processing at once # video stub logic was implicitly deprecated, so we start processing at once
ffmpeg -i $temp -ss 00:00:01.000 -vframes 1 "$dir$hashT/$hash.gif" ffmpeg -i $temp -ss 00:00:01.000 -y -vframes 1 "$dir$hashT/$hash.gif"
ffmpeg -i $temp -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2,setsar=1" -y $temp2 ffmpeg -i $temp -c:v libx264 -f mp4 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2,setsar=1" -y $temp2
Move-Item $temp2 "$dir$hashT/$hash.mp4" Move-Item $temp2 "$dir$hashT/$hash.mp4" -Force
Remove-Item $temp
Remove-Item $temp2 Remove-Item $temp2

View file

@ -3,7 +3,7 @@ namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Club, Photo, Post}; use openvk\Web\Models\Entities\{Club, Photo, Post};
use Nette\InvalidStateException; use Nette\InvalidStateException;
use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification; use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification;
use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics, Audios, Posts}; use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics, Audios, Posts, Videos};
use Chandler\Security\Authenticator; use Chandler\Security\Authenticator;
final class GroupPresenter extends OpenVKPresenter final class GroupPresenter extends OpenVKPresenter
@ -31,6 +31,8 @@ final class GroupPresenter extends OpenVKPresenter
$this->template->albumsCount = (new Albums)->getClubAlbumsCount($club); $this->template->albumsCount = (new Albums)->getClubAlbumsCount($club);
$this->template->topics = (new Topics)->getLastTopics($club, 3); $this->template->topics = (new Topics)->getLastTopics($club, 3);
$this->template->topicsCount = (new Topics)->getClubTopicsCount($club); $this->template->topicsCount = (new Topics)->getClubTopicsCount($club);
$this->template->videos = (new Videos)->getRandomTwoVideosByEntityId($club->getRealId());
$this->template->videosCount = (new Videos)->getVideosCountByEntityId($club->getRealId());
$this->template->audios = (new Audios)->getRandomThreeAudiosByEntityId($club->getRealId()); $this->template->audios = (new Audios)->getRandomThreeAudiosByEntityId($club->getRealId());
$this->template->audiosCount = (new Audios)->getClubCollectionSize($club); $this->template->audiosCount = (new Audios)->getClubCollectionSize($club);
} }

View file

@ -1,7 +1,7 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\Video; use openvk\Web\Models\Entities\Video;
use openvk\Web\Models\Repositories\{Users, Videos}; use openvk\Web\Models\Repositories\{Users, Videos, Clubs};
use Nette\InvalidStateException as ISE; use Nette\InvalidStateException as ISE;
final class VideosPresenter extends OpenVKPresenter final class VideosPresenter extends OpenVKPresenter
@ -20,14 +20,19 @@ final class VideosPresenter extends OpenVKPresenter
function renderList(int $id): void function renderList(int $id): void
{ {
$user = $this->users->get($id); if($id > 0)
if(!$user) $this->notFound(); $owner = $this->users->get($id);
if(!$user->getPrivacyPermission('videos.read', $this->user->identity ?? NULL)) else
$owner = (new Clubs)->get(abs($id));
if(!$owner) $this->notFound();
if($id > 0 && !$owner->getPrivacyPermission('videos.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); $this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$this->template->user = $user; $this->template->owner = $owner;
$this->template->videos = $this->videos->getByUser($user, (int) ($this->queryParam("p") ?? 1)); $this->template->canUpload = $id > 0 ? $id == $this->user->id : $owner->canBeModifiedBy($this->user->identity);
$this->template->count = $this->videos->getUserVideosCount($user); $this->template->videos = $id > 0 ? $this->videos->getByUser($owner, (int) ($this->queryParam("p") ?? 1)) : $this->videos->getByClub($owner, (int) ($this->queryParam("p") ?? 1));
$this->template->count = $this->videos->getVideosCountByEntityId($owner->getRealId());
$this->template->paginatorConf = (object) [ $this->template->paginatorConf = (object) [
"count" => $this->template->count, "count" => $this->template->count,
"page" => (int) ($this->queryParam("p") ?? 1), "page" => (int) ($this->queryParam("p") ?? 1),
@ -60,6 +65,14 @@ final class VideosPresenter extends OpenVKPresenter
if(OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) if(OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading'])
$this->flashFail("err", tr("error"), tr("video_uploads_disabled")); $this->flashFail("err", tr("error"), tr("video_uploads_disabled"));
if($this->queryParam("gid")) {
$club = (new Clubs)->get((int)$this->queryParam("gid"));
if(!$club || !$club->canUploadVideo($this->user->identity))
$this->notFound();
}
$this->template->owner = $club ?: $this->user->identity;
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
if(!empty($this->postParam("name"))) { if(!empty($this->postParam("name"))) {
$video = new Video; $video = new Video;
@ -82,6 +95,8 @@ final class VideosPresenter extends OpenVKPresenter
} }
$video->save(); $video->save();
$video->add(!isset($club) ? $this->user->identity : $club);
$this->redirect("/video" . $video->getPrettyId()); $this->redirect("/video" . $video->getPrettyId());
} else { } else {

View file

@ -299,6 +299,32 @@
</div> </div>
</div> </div>
</div> </div>
<div n:if="$videosCount > 0 || ($thisUser && $club->canBeModifiedBy($thisUser))">
<div class="content_title_expanded" onclick="hidePanel(this, {$videosCount});">
{_videos}
</div>
<div>
<div class="content_subtitle">
{tr("videos", $videosCount)}
<div style="float: right;">
<a href="/videos-{$club->getId()}">{_all_title}</a>
</div>
</div>
<div style="padding: 5px;">
<div class="ovk-video" n:foreach="$videos as $video">
<a href="/video{$video->getPrettyId()}" class="preview" align="center" id="videoOpen" data-id="{$video->getId()}">
<img
src="{$video->getThumbnailURL()}"
style="max-width: 170px; max-height: 127px; margin: auto;" />
</a>
<div>
<b><a href="/video{$video->getPrettyId()}" id="videoOpen" data-id="{$video->getId()}">{ovk_proc_strtr($video->getName(), 30)}</a></b><br>
<span style="font-size: 10px;">{$video->getPublicationTime()} | {_comments} ({$video->getCommentsCount()})</span>
</div>
</div>
</div>
</div>
</div>
<div n:if="($topicsCount > 0 || $club->isEveryoneCanCreateTopics() || ($thisUser && $club->canBeModifiedBy($thisUser))) && !$club->isDisplayTopicsAboveWallEnabled()"> <div n:if="($topicsCount > 0 || $club->isEveryoneCanCreateTopics() || ($thisUser && $club->canBeModifiedBy($thisUser))) && !$club->isDisplayTopicsAboveWallEnabled()">
<div class="content_title_expanded" onclick="hidePanel(this, {$topicsCount});"> <div class="content_title_expanded" onclick="hidePanel(this, {$topicsCount});">
{_discussions} {_discussions}

View file

@ -3,10 +3,10 @@
{var $count = $paginatorConf->count} {var $count = $paginatorConf->count}
{var $page = $paginatorConf->page} {var $page = $paginatorConf->page}
{block title}{_videos} {$user->getCanonicalName()}{/block} {block title}{_videos} {$owner->getRealId() > 0 ? $owner->getCanonicalName() : tr("group_genetive")}{/block}
{block header} {block header}
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> <a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
» {_videos} » {_videos}
{/block} {/block}
@ -14,9 +14,9 @@
<div style="padding-bottom: 0px; padding-top: 0;" class="summaryBar"> <div style="padding-bottom: 0px; padding-top: 0;" class="summaryBar">
<div class="summary"> <div class="summary">
{tr("videos", $count)} {tr("videos", $count)}
<span n:if="isset($thisUser) && $thisUser->getId() == $user->getId() && !OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']"> <span n:if="$canUpload">
&nbsp;|&nbsp; &nbsp;|&nbsp;
<a href="/videos/upload">{_upload_video}</a> <a href="/videos/upload{if $owner->getRealId() < 0}?gid={$owner->getId()}{/if}">{_upload_video}</a>
</span> </span>
</div> </div>
</div> </div>

View file

@ -2,9 +2,9 @@
{block title}{_upload_video}{/block} {block title}{_upload_video}{/block}
{block header} {block header}
<a href="{$thisUser->getURL()}">{$thisUser->getCanonicalName()}</a> <a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
» »
<a href="/videos{$thisUser->getId()}">{_videos}</a> <a href="/videos{$owner->getRealId()}">{_videos}</a>
» »
{_upload_video} {_upload_video}
{/block} {/block}

View file

@ -10,27 +10,31 @@
</a> </a>
{/if} {/if}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Video} {elseif $attachment instanceof \openvk\Web\Models\Entities\Video}
{if !$attachment->isDeleted()} {if $count && $count < 2}
{if $attachment->getType() === 0} {if $attachment->getType() === 0}
<div class="bsdn media" data-name="{$attachment->getName()}" data-author="{$attachment->getOwner()->getCanonicalName()}"> <div class="bsdn media" data-name="{$attachment->getName()}" data-author="{$attachment->getOwner()->getCanonicalName()}">
<video class="media" src="{$attachment->getURL()}"></video> <video class="media" src="{$attachment->getURL()}"></video>
</div>
{else}
{var $driver = $attachment->getVideoDriver()}
{if !$driver}
<span style="color:red;">{_version_incompatibility}</span>
{else}
{$driver->getEmbed("100%")|noescape}
{/if}
{/if}
<div class="video-wowzer">
<img src="/assets/packages/static/openvk/img/videoico.png" />
<a href="/video{$attachment->getPrettyId()}" id="videoOpen" data-id="{$attachment->getId()}">{$attachment->getName()}</a>
{if $attachment->getLength()}
({$attachment->getFormattedLength()})
{/if}
</div> </div>
{else} {else}
{var $driver = $attachment->getVideoDriver()} <div class="compactVideo" id="videoOpen" data-id="{$attachment->getId()}">
{if !$driver} <img src="{$attachment->getThumbnailURL()}" alt="{$attachment->getName()}"/>
<span style="color:red;">{_version_incompatibility}</span> </div>
{else}
{$driver->getEmbed("100%")|noescape}
{/if}
{/if}
<div class="video-wowzer">
<img src="/assets/packages/static/openvk/img/videoico.png" />
<a href="/video{$attachment->getPrettyId()}" id="videoOpen" data-id="{$attachment->getId()}">{$attachment->getName()}</a>
</div>
{else}
<span style="color:gray;">{_video_is_deleted}</span>
{/if} {/if}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Poll} {elseif $attachment instanceof \openvk\Web\Models\Entities\Poll}
{presenter "openvk!Poll->view", $attachment->getId()} {presenter "openvk!Poll->view", $attachment->getId()}

View file

@ -25,7 +25,7 @@
{var $attachmentsLayout = $comment->getChildrenWithLayout(288)} {var $attachmentsLayout = $comment->getChildrenWithLayout(288)}
<div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};"> <div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};">
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}"> <div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}">
{include "attachment.xml", attachment => $attachment[2], parent => $comment, parentType => "comment"} {include "attachment.xml", attachment => $attachment[2], parent => $comment, parentType => "comment", count => $attachmentsLayout->count}
</div> </div>
</div> </div>

View file

@ -81,7 +81,7 @@
{var $attachmentsLayout = $post->getChildrenWithLayout($width)} {var $attachmentsLayout = $post->getChildrenWithLayout($width)}
<div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};"> <div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};">
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}"> <div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}">
{include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post"} {include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post", count => $attachmentsLayout->count}
</div> </div>
</div> </div>

View file

@ -73,7 +73,7 @@
{var $attachmentsLayout = $post->getChildrenWithLayout($width)} {var $attachmentsLayout = $post->getChildrenWithLayout($width)}
<div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};"> <div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};">
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}"> <div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}">
{include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post"} {include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post", count => $attachmentsLayout->count}
</div> </div>
</div> </div>

View file

@ -18,7 +18,7 @@ class Makima
$this->photos = $photos; $this->photos = $photos;
} }
private function getOrientation(Photo $photo, &$ratio): int private function getOrientation($photo, &$ratio): int
{ {
[$width, $height] = $photo->getDimensions(); [$width, $height] = $photo->getDimensions();
$ratio = $width / $height; $ratio = $width / $height;

View file

@ -3056,3 +3056,32 @@ hr {
background-position: 50% !important; background-position: 50% !important;
} }
.compactVideo {
object-fit: contain;
cursor: pointer;
background: black;
width: calc(100% - 2px);
height: calc(100% - 2px); /* kostyls */
display: flex;
position: relative;
min-height: 28px;
}
.compactVideo img {
object-fit: contain;
margin-left: auto;
margin-right: auto;
}
.compactVideo::before {
content: " ";
background: url("/assets/packages/static/openvk/img/video_controls.png");
width: 28px;
height: 28px;
position: absolute;
left: 0;
right: 0;
margin: auto;
top: 0;
bottom: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,10 @@
CREATE TABLE `video_relations` (
`entity` BIGINT(20) NOT NULL,
`video` BIGINT(20) UNSIGNED NOT NULL,
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE utf8mb4_unicode_520_ci;
ALTER TABLE `openvk`.`video_relations` ADD INDEX `entity_video` (`entity`, `video`);
ALTER TABLE `videos` ADD `length` SMALLINT(5) UNSIGNED NULL DEFAULT NULL AFTER `name`, ADD INDEX `length` (`length`);
ALTER TABLE `videos` ADD `height` SMALLINT(5) UNSIGNED NULL DEFAULT NULL AFTER `length`, ADD `width` SMALLINT(5) UNSIGNED NULL DEFAULT NULL AFTER `height`;

View file

@ -749,6 +749,8 @@
"no_videos" = "У вас нет видео."; "no_videos" = "У вас нет видео.";
"no_videos_results" = "Нет результатов."; "no_videos_results" = "Нет результатов.";
"group_genetive" = "группы";
/* Audios */ /* Audios */
"audios" = "Аудиозаписи"; "audios" = "Аудиозаписи";