diff --git a/Web/Models/Entities/Traits/TAttachmentHost.php b/Web/Models/Entities/Traits/TAttachmentHost.php index db814cce..a574e657 100644 --- a/Web/Models/Entities/Traits/TAttachmentHost.php +++ b/Web/Models/Entities/Traits/TAttachmentHost.php @@ -1,6 +1,6 @@ getChildren(); + $children = iterator_to_array($this->getChildren()); $skipped = $photos = $result = []; foreach($children as $child) { - if($child instanceof Photo) { + if($child instanceof Photo || $child instanceof Video && $child->getDimensions()) { $photos[] = $child; continue; } diff --git a/Web/Models/Entities/Video.php b/Web/Models/Entities/Video.php index a4d0b898..e7a2d6e5 100644 --- a/Web/Models/Entities/Video.php +++ b/Web/Models/Entities/Video.php @@ -34,9 +34,23 @@ class Video extends Media if(sizeof($durations[1]) === 0) throw new \DomainException("$filename does not contain any meaningful video streams"); - foreach($durations[1] as $duration) - if(floatval($duration) < 1.0) + $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)))) @@ -118,6 +132,7 @@ class Video extends Media function getApiStructure(?User $user = NULL): object { $fromYoutube = $this->getType() == Video::TYPE_EMBED; + $dimensions = $this->getDimensions(); $res = (object)[ "type" => "video", "video" => [ @@ -130,7 +145,7 @@ class Video extends Media "comments" => $this->getCommentsCount(), "date" => $this->getPublicationTime()->timestamp(), "description" => $this->getDescription(), - "duration" => 0, // я хуй знает как получить длину видео + "duration" => $this->getLength(), "image" => [ [ "url" => $this->getThumbnailURL(), @@ -139,8 +154,8 @@ class Video extends Media "with_padding" => 1 ] ], - "width" => 640, - "height" => 480, + "width" => $dimensions ? $dimensions[0] : 640, + "height" => $dimensions ? $dimensions[1] : 480, "id" => $this->getVirtualId(), "owner_id" => $this->getOwner()->getId(), "user_id" => $this->getOwner()->getId(), @@ -224,6 +239,74 @@ class Video extends Media 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 { @@ -248,7 +331,7 @@ class Video extends Media $res->owner_id = $this->getOwner()->getId(); $res->title = $this->getName(); $res->description = $this->getDescription(); - $res->duration = "22"; + $res->duration = $this->getLength(); $res->link = "/video".$this->getOwner()->getId()."_".$this->getVirtualId(); $res->image = $this->getThumbnailURL(); $res->date = $this->getPublicationTime()->timestamp(); diff --git a/Web/Presenters/templates/components/attachment.xml b/Web/Presenters/templates/components/attachment.xml index d084ae63..fb59a27c 100644 --- a/Web/Presenters/templates/components/attachment.xml +++ b/Web/Presenters/templates/components/attachment.xml @@ -10,23 +10,37 @@ {/if} {elseif $attachment instanceof \openvk\Web\Models\Entities\Video} - {if $attachment->getType() === 0} -
- + {if $tilesCount <= 1} + {if $attachment->getType() === 0} +
+ +
+ {else} + {var $driver = $attachment->getVideoDriver()} + {if !$driver} + {_version_incompatibility} + {else} + {$driver->getEmbed("100%")|noescape} + {/if} + {/if} + +
+
+ {$attachment->getName()} + ({$attachment->getFormattedLength()})
{else} - {var $driver = $attachment->getVideoDriver()} - {if !$driver} - {_version_incompatibility} - {else} - {$driver->getEmbed("100%")|noescape} - {/if} - {/if} + +
+
+
+
+ {$attachment->getFormattedLength()} +
-
- - {$attachment->getName()} -
+ + + {/if} {elseif $attachment instanceof \openvk\Web\Models\Entities\Poll} {presenter "openvk!Poll->view", $attachment->getId()} {elseif $attachment instanceof \openvk\Web\Models\Entities\Note} diff --git a/Web/Presenters/templates/components/post/microblogpost.xml b/Web/Presenters/templates/components/post/microblogpost.xml index a37076b1..1c42c073 100644 --- a/Web/Presenters/templates/components/post/microblogpost.xml +++ b/Web/Presenters/templates/components/post/microblogpost.xml @@ -74,7 +74,7 @@ {if $post->getTargetWall() < 0 && $wallOwner->canBeModifiedBy($thisUser)}data-fromgroup="{(int)$post->isPostedOnBehalfOfGroup()}"{/if}> {/if}
-
+
{$post->getText()|noescape} @@ -84,8 +84,8 @@ {/if} {var $attachmentsLayout = $post->getChildrenWithLayout($width)}
-
- {include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post"} +
+ {include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post", tilesCount => sizeof($attachmentsLayout->tiles)}
diff --git a/Web/Presenters/templates/components/post/oldpost.xml b/Web/Presenters/templates/components/post/oldpost.xml index 48063747..ff4ec03d 100644 --- a/Web/Presenters/templates/components/post/oldpost.xml +++ b/Web/Presenters/templates/components/post/oldpost.xml @@ -67,7 +67,7 @@
-
+
{var $owner = $author->getId()} @@ -79,8 +79,8 @@ {/if} {var $attachmentsLayout = $post->getChildrenWithLayout($width)}
-
- {include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post"} +
+ {include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post", tilesCount => sizeof($attachmentsLayout->tiles)}
diff --git a/Web/Util/Makima/Makima.php b/Web/Util/Makima/Makima.php index c9446826..3fc7b014 100644 --- a/Web/Util/Makima/Makima.php +++ b/Web/Util/Makima/Makima.php @@ -18,7 +18,7 @@ class Makima $this->photos = $photos; } - private function getOrientation(Photo $photo, &$ratio): int + private function getOrientation($photo, &$ratio): int { [$width, $height] = $photo->getDimensions(); $ratio = $width / $height; diff --git a/Web/static/css/main.css b/Web/static/css/main.css index 4e0ec10b..3725ee70 100644 --- a/Web/static/css/main.css +++ b/Web/static/css/main.css @@ -2441,7 +2441,7 @@ a.poll-retract-vote { position: relative; } -.post-horizontal .upload-item .play-button { +.post-horizontal .upload-item .play-button, .compact_video .play-button { position: absolute; height: 30px; width: 30px; @@ -2452,7 +2452,7 @@ a.poll-retract-vote { align-items: center; } -.post-horizontal .upload-item .play-button .play-button-ico { +.post-horizontal .upload-item .play-button .play-button-ico, .compact_video .play-button .play-button-ico { background: url(/assets/packages/static/openvk/img/wall.png) no-repeat 1px 0; display: inline-block; height: 15px; @@ -2648,12 +2648,13 @@ a.poll-retract-vote { padding: 3px 0; } -.video-wowzer a::before { - content: "b"; - color: transparent; - width: 12px; - background-image: url(/assets/packages/static/openvk/img/videoico.png); - display: none; +.video-wowzer .small-video-ico { + vertical-align: bottom; + display: inline-block; + width: 11px; + height: 14px; + background: url(/assets/packages/static/openvk/img/wall.png?v=2); + background-position: -87px 0px; } /* Da search */ @@ -3511,3 +3512,26 @@ hr { width: 30px; height: 7px; } + +.compact_video { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + + position: relative; +} + +.compact_video .video-length { + text-align: center; + position: absolute; + width: 34px; + height: 14px; + bottom: 5px; + right: 5px; + background: rgba(0,0,0,0.5); + color: white; +} + diff --git a/Web/static/css/nsfw-posts.css b/Web/static/css/nsfw-posts.css index ed9841ca..1d946252 100644 --- a/Web/static/css/nsfw-posts.css +++ b/Web/static/css/nsfw-posts.css @@ -1,28 +1,28 @@ -.post-nsfw .post-content .media { - filter: saturate(0.8) blur(15px); -} - -.post-nsfw .post-content .attachment { - overflow: hidden; +.post-nsfw .post-content { position: relative; + overflow: hidden; + cursor: pointer; } -.post-nsfw .post-content .attachment:active .media { - filter: none; +.post-nsfw .post-content .text { + filter: saturate(0.8) blur(28px); + pointer-events: none; } -.post-nsfw .post-content .attachment::after { +.post-nsfw .post-content::after { position: absolute; - top: calc(50% - 16px); + top: 0; left: 0; width: 100%; - padding: 8px 0; - background-color: hsla(0, 0%, 0%, .5); + height: 100%; + z-index: 1; + background-color: hsla(0, 0%, 0%, .7); color: #fff; text-align: center; content: attr(data-localized-nsfw-text); -} -.post-nsfw .post-content .attachment:active::after { - display: none; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; } diff --git a/Web/static/img/videoico.png b/Web/static/img/videoico.png deleted file mode 100644 index 4ebe30f3..00000000 Binary files a/Web/static/img/videoico.png and /dev/null differ diff --git a/Web/static/img/wall.png b/Web/static/img/wall.png index 70cd42c0..197dca8d 100644 Binary files a/Web/static/img/wall.png and b/Web/static/img/wall.png differ diff --git a/Web/static/js/al_wall.js b/Web/static/js/al_wall.js index d3530caf..be2e51ef 100644 --- a/Web/static/js/al_wall.js +++ b/Web/static/js/al_wall.js @@ -486,6 +486,7 @@ async function __uploadToTextarea(file, textareaNode) { }) const json_response = await res.json() if(!json_response.success) { + u(`#temp_filler${rand}`).remove() fastError((tr("error_uploading_photo") + json_response.flash.message)) return } @@ -1025,6 +1026,13 @@ u(document).on('click', '.post-buttons .upload-item', (e) => { e.preventDefault() }) +u(document).on('click', '.post.post-nsfw .post-content', (e) => { + e.preventDefault() + e.stopPropagation() + + u(e.target).closest('.post-nsfw').removeClass('post-nsfw') +}) + async function repost(id, repost_type = 'post') { const repostsCount = u(`#repostsCount${id}`) const previousVal = repostsCount.length > 0 ? Number(repostsCount.html()) : 0; diff --git a/install/sqls/000XX-new-video-fields.sql b/install/sqls/000XX-new-video-fields.sql new file mode 100644 index 00000000..8f7c2ea0 --- /dev/null +++ b/install/sqls/000XX-new-video-fields.sql @@ -0,0 +1,2 @@ +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`;