timeline: show video duration
parent
c6766833b5
commit
06d226432f
|
@ -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;
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)`;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
Loading…
Reference in New Issue