timeline: show video duration

pull/221/head
Varun Patil 2022-11-09 19:48:03 -08:00
parent c6766833b5
commit 06d226432f
7 changed files with 135 additions and 10 deletions

View File

@ -102,7 +102,7 @@ trait TimelineQueryDays
// We don't actually use m.datetaken here, but postgres // We don't actually use m.datetaken here, but postgres
// needs that all fields in ORDER BY are also in SELECT // needs that all fields in ORDER BY are also in SELECT
// when using DISTINCT on selected fields // when using DISTINCT on selected fields
$query->select($fileid, 'm.isvideo', 'm.datetaken', 'm.dayid', 'm.w', 'm.h') $query->select($fileid, 'm.isvideo', 'm.video_duration', 'm.datetaken', 'm.dayid', 'm.w', 'm.h')
->from('memories', 'm') ->from('memories', 'm')
; ;
@ -198,11 +198,13 @@ trait TimelineQueryDays
// Convert field types // Convert field types
$row['fileid'] = (int) $row['fileid']; $row['fileid'] = (int) $row['fileid'];
$row['isvideo'] = (int) $row['isvideo']; $row['isvideo'] = (int) $row['isvideo'];
$row['video_duration'] = (int) $row['video_duration'];
$row['dayid'] = (int) $row['dayid']; $row['dayid'] = (int) $row['dayid'];
$row['w'] = (int) $row['w']; $row['w'] = (int) $row['w'];
$row['h'] = (int) $row['h']; $row['h'] = (int) $row['h'];
if (!$row['isvideo']) { if (!$row['isvideo']) {
unset($row['isvideo']); unset($row['isvideo']);
unset($row['video_duration']);
} }
if ($row['categoryid']) { if ($row['categoryid']) {
$row['isfavorite'] = 1; $row['isfavorite'] = 1;

View File

@ -97,6 +97,12 @@ class TimelineWrite
$dateTaken = gmdate('Y-m-d H:i:s', $dateTaken); $dateTaken = gmdate('Y-m-d H:i:s', $dateTaken);
[$w, $h] = Exif::getDimensions($exif); [$w, $h] = Exif::getDimensions($exif);
// Video parameters
$videoDuration = 0;
if ($isvideo) {
$videoDuration = round($exif['Duration'] ?? $exif['TrackDuration'] ?? 0);
}
// Store raw metadata in the database // Store raw metadata in the database
// We need to remove blacklisted fields to prevent leaking info // We need to remove blacklisted fields to prevent leaking info
unset($exif['SourceFile'], $exif['FileName'], $exif['ExifToolVersion'], $exif['Directory'], $exif['FileSize'], $exif['FileModifyDate'], $exif['FileAccessDate'], $exif['FileInodeChangeDate'], $exif['FilePermissions']); unset($exif['SourceFile'], $exif['FileName'], $exif['ExifToolVersion'], $exif['Directory'], $exif['FileSize'], $exif['FileModifyDate'], $exif['FileAccessDate'], $exif['FileInodeChangeDate'], $exif['FilePermissions']);
@ -124,6 +130,7 @@ class TimelineWrite
->set('datetaken', $query->createNamedParameter($dateTaken, IQueryBuilder::PARAM_STR)) ->set('datetaken', $query->createNamedParameter($dateTaken, IQueryBuilder::PARAM_STR))
->set('mtime', $query->createNamedParameter($mtime, IQueryBuilder::PARAM_INT)) ->set('mtime', $query->createNamedParameter($mtime, IQueryBuilder::PARAM_INT))
->set('isvideo', $query->createNamedParameter($isvideo, IQueryBuilder::PARAM_INT)) ->set('isvideo', $query->createNamedParameter($isvideo, IQueryBuilder::PARAM_INT))
->set('video_duration', $query->createNamedParameter($videoDuration, IQueryBuilder::PARAM_INT))
->set('w', $query->createNamedParameter($w, IQueryBuilder::PARAM_INT)) ->set('w', $query->createNamedParameter($w, IQueryBuilder::PARAM_INT))
->set('h', $query->createNamedParameter($h, IQueryBuilder::PARAM_INT)) ->set('h', $query->createNamedParameter($h, IQueryBuilder::PARAM_INT))
->set('exif', $query->createNamedParameter($exifJson, IQueryBuilder::PARAM_STR)) ->set('exif', $query->createNamedParameter($exifJson, IQueryBuilder::PARAM_STR))
@ -141,6 +148,7 @@ class TimelineWrite
'datetaken' => $query->createNamedParameter($dateTaken, IQueryBuilder::PARAM_STR), 'datetaken' => $query->createNamedParameter($dateTaken, IQueryBuilder::PARAM_STR),
'mtime' => $query->createNamedParameter($mtime, IQueryBuilder::PARAM_INT), 'mtime' => $query->createNamedParameter($mtime, IQueryBuilder::PARAM_INT),
'isvideo' => $query->createNamedParameter($isvideo, IQueryBuilder::PARAM_INT), 'isvideo' => $query->createNamedParameter($isvideo, IQueryBuilder::PARAM_INT),
'video_duration' => $query->createNamedParameter($videoDuration, IQueryBuilder::PARAM_INT),
'w' => $query->createNamedParameter($w, IQueryBuilder::PARAM_INT), 'w' => $query->createNamedParameter($w, IQueryBuilder::PARAM_INT),
'h' => $query->createNamedParameter($h, IQueryBuilder::PARAM_INT), 'h' => $query->createNamedParameter($h, IQueryBuilder::PARAM_INT),
'exif' => $query->createNamedParameter($exifJson, IQueryBuilder::PARAM_STR), 'exif' => $query->createNamedParameter($exifJson, IQueryBuilder::PARAM_STR),

View File

@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Your name <your@email.com>
* @author Your name <your@email.com>
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace OCA\Memories\Migration;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
/**
* Auto-generated migration step: Please modify to your needs!
*/
class Version400700Date20221110030909 extends SimpleMigrationStep
{
/**
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
*/
public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
{
}
/**
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
*/
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options): ?ISchemaWrapper
{
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('memories');
$table->addColumn('video_duration', Types::INTEGER, [
'notnull' => true,
'default' => 0,
]);
return $schema;
}
/**
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
*/
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
{
}
}

View File

@ -184,7 +184,6 @@ class VideoContentSetup {
updateRotation(content, val?: number) { updateRotation(content, val?: number) {
const rotation = val ?? Number(content.data.exif?.Rotation); const rotation = val ?? Number(content.data.exif?.Rotation);
const shouldRotate = content.videojs?.src().includes("m3u8"); const shouldRotate = content.videojs?.src().includes("m3u8");
console.log("Video.js: Rotation", rotation, shouldRotate);
if (rotation && shouldRotate) { if (rotation && shouldRotate) {
let transform = `rotate(${rotation}deg)`; let transform = `rotate(${rotation}deg)`;

View File

@ -16,7 +16,13 @@
@click="toggleSelect" @click="toggleSelect"
/> />
<Video :size="22" v-if="data.flag & c.FLAG_IS_VIDEO" /> <div class="video" v-if="data.flag & c.FLAG_IS_VIDEO">
<span v-if="data.video_duration" class="time">
{{ videoDuration }}
</span>
<Video :size="22" />
</div>
<Star :size="22" v-if="data.flag & c.FLAG_IS_FAVORITE" /> <Star :size="22" v-if="data.flag & c.FLAG_IS_FAVORITE" />
<div <div
@ -43,14 +49,17 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Emit, Mixins, Prop, Watch } from "vue-property-decorator";
import GlobalMixin from "../../mixins/GlobalMixin";
import { getPreviewUrl } from "../../services/FileUtils";
import { IDay, IPhoto } from "../../types";
import * as utils from "../../services/Utils";
import errorsvg from "../../assets/error.svg";
import CheckCircle from "vue-material-design-icons/CheckCircle.vue"; import CheckCircle from "vue-material-design-icons/CheckCircle.vue";
import Star from "vue-material-design-icons/Star.vue"; import Star from "vue-material-design-icons/Star.vue";
import Video from "vue-material-design-icons/PlayCircleOutline.vue"; import Video from "vue-material-design-icons/PlayCircleOutline.vue";
import { Component, Emit, Mixins, Prop, Watch } from "vue-property-decorator";
import errorsvg from "../../assets/error.svg";
import GlobalMixin from "../../mixins/GlobalMixin";
import { getPreviewUrl } from "../../services/FileUtils";
import { IDay, IPhoto } from "../../types";
@Component({ @Component({
components: { components: {
@ -91,6 +100,13 @@ export default class Photo extends Mixins(GlobalMixin) {
this.refresh(); this.refresh();
} }
get videoDuration() {
if (this.data.video_duration) {
return utils.getDurationStr(this.data.video_duration);
}
return null;
}
async refresh() { async refresh() {
this.src = await this.getSrc(); this.src = await this.getSrc();
} }
@ -274,7 +290,7 @@ $icon-size: $icon-half-size * 2;
color: var(--color-primary); color: var(--color-primary);
} }
} }
.play-circle-outline-icon, .video,
.star-icon { .star-icon {
position: absolute; position: absolute;
z-index: 100; z-index: 100;
@ -282,12 +298,23 @@ $icon-size: $icon-half-size * 2;
transition: transform 0.15s ease; transition: transform 0.15s ease;
filter: invert(1) brightness(100); filter: invert(1) brightness(100);
} }
.play-circle-outline-icon { .video {
position: absolute;
top: var(--icon-dist); top: var(--icon-dist);
right: var(--icon-dist); right: var(--icon-dist);
.p-outer.selected > & { .p-outer.selected > & {
transform: translate(-$icon-size, $icon-size); transform: translate(-$icon-size, $icon-size);
} }
display: flex;
align-items: center;
justify-content: center;
.time {
font-size: 0.75em;
font-weight: bold;
margin-right: 3px;
}
} }
.star-icon { .star-icon {
bottom: var(--icon-dist); bottom: var(--icon-dist);

View File

@ -69,6 +69,26 @@ export function getFromNowStr(date: Date) {
return text.charAt(0).toUpperCase() + text.slice(1); return text.charAt(0).toUpperCase() + text.slice(1);
} }
/** Convert number of seconds to time string */
export function getDurationStr(sec: number) {
let hours = Math.floor(sec / 3600);
let minutes: number | string = Math.floor((sec - hours * 3600) / 60);
let seconds: number | string = sec - hours * 3600 - minutes * 60;
if (seconds < 10) {
seconds = "0" + seconds;
}
if (hours > 0) {
if (minutes < 10) {
minutes = "0" + minutes;
}
return `${hours}:${minutes}:${seconds}`;
}
return `${minutes}:${seconds}`;
}
/** /**
* Returns a hash code from a string * Returns a hash code from a string
* @param {String} str The string to hash. * @param {String} str The string to hash.

View File

@ -77,6 +77,8 @@ export type IPhoto = {
/** Video flag from server */ /** Video flag from server */
isvideo?: boolean; isvideo?: boolean;
/** Video duration from server */
video_duration?: number;
/** Favorite flag from server */ /** Favorite flag from server */
isfavorite?: boolean; isfavorite?: boolean;
/** Is this a folder */ /** Is this a folder */