1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

implementing database support for videos

This commit is contained in:
Patrik J. Braun 2018-11-17 19:32:31 +01:00
parent f4df4a638c
commit 6b5508c9e6
24 changed files with 269 additions and 207 deletions

View File

@ -13,7 +13,10 @@ import {ISQLGalleryManager} from './IGalleryManager';
import {ReIndexingSensitivity} from '../../../common/config/private/IPrivateConfig';
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
import {OrientationType} from '../../../common/entities/RandomQueryDTO';
import {Brackets} from 'typeorm';
import {Connection, Brackets} from 'typeorm';
import {MediaEntity} from './enitites/MediaEntity';
import {MediaDTO} from '../../../common/entities/MediaDTO';
import {VideoEntity} from './enitites/VideoEntity';
export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
@ -35,7 +38,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
path: directoryParent
})
.leftJoinAndSelect('directory.directories', 'directories')
.leftJoinAndSelect('directory.photos', 'photos')
.leftJoinAndSelect('directory.media', 'media')
.getOne();
@ -63,7 +66,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
if (dir.directories) {
for (let i = 0; i < dir.directories.length; i++) {
dir.directories[i].media = await connection
.getRepository(PhotoEntity)
.getRepository(MediaEntity)
.createQueryBuilder('media')
.where('media.directory = :dir', {
dir: dir.directories[i].id
@ -132,10 +135,10 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
// saving to db
const directoryRepository = connection.getRepository(DirectoryEntity);
const photosRepository = connection.getRepository(PhotoEntity);
const mediaRepository = connection.getRepository(MediaEntity);
let currentDir = await directoryRepository.createQueryBuilder('directory')
let currentDir: DirectoryEntity = await directoryRepository.createQueryBuilder('directory')
.where('directory.name = :name AND directory.path = :path', {
name: scannedDirectory.name,
path: scannedDirectory.path
@ -144,10 +147,18 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
if (!!currentDir) {// Updated parent dir (if it was in the DB previously)
currentDir.lastModified = scannedDirectory.lastModified;
currentDir.lastScanned = scannedDirectory.lastScanned;
const media: MediaEntity[] = currentDir.media;
delete currentDir.media;
currentDir = await directoryRepository.save(currentDir);
media.forEach(m => m.directory = currentDir);
currentDir.media = await this.saveMedia(connection, media);
} else {
const media = scannedDirectory.media;
delete scannedDirectory.media;
(<DirectoryEntity>scannedDirectory).lastScanned = scannedDirectory.lastScanned;
currentDir = await directoryRepository.save(<DirectoryEntity>scannedDirectory);
media.forEach(m => m.directory = currentDir);
currentDir.media = await this.saveMedia(connection, media);
}
const childDirectories = await directoryRepository.createQueryBuilder('directory')
@ -180,7 +191,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
scannedDirectory.directories[i].media[j].directory = d;
}
await photosRepository.save(scannedDirectory.directories[i].media);
await this.saveMedia(connection, scannedDirectory.directories[i].media);
}
}
@ -188,53 +199,49 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
await directoryRepository.remove(childDirectories);
const indexedPhotos = await photosRepository.createQueryBuilder('media')
const indexedMedia = await mediaRepository.createQueryBuilder('media')
.where('media.directory = :dir', {
dir: currentDir.id
}).getMany();
const photosToSave = [];
const mediaToSave = [];
for (let i = 0; i < scannedDirectory.media.length; i++) {
let photo = null;
for (let j = 0; j < indexedPhotos.length; j++) {
if (indexedPhotos[j].name === scannedDirectory.media[i].name) {
photo = indexedPhotos[j];
indexedPhotos.splice(j, 1);
let media = null;
for (let j = 0; j < indexedMedia.length; j++) {
if (indexedMedia[j].name === scannedDirectory.media[i].name) {
media = indexedMedia[j];
indexedMedia.splice(j, 1);
break;
}
}
if (photo == null) {
if (media == null) {
scannedDirectory.media[i].directory = null;
photo = Utils.clone(scannedDirectory.media[i]);
media = Utils.clone(scannedDirectory.media[i]);
scannedDirectory.media[i].directory = scannedDirectory;
photo.directory = currentDir;
media.directory = currentDir;
}
if (photo.metadata.keywords !== scannedDirectory.media[i].metadata.keywords ||
photo.metadata.cameraData !== (<PhotoDTO>scannedDirectory.media[i]).metadata.cameraData ||
photo.metadata.positionData !== scannedDirectory.media[i].metadata.positionData ||
photo.metadata.size !== scannedDirectory.media[i].metadata.size) {
photo.metadata.keywords = scannedDirectory.media[i].metadata.keywords;
photo.metadata.cameraData = (<PhotoDTO>scannedDirectory.media[i]).metadata.cameraData;
photo.metadata.positionData = scannedDirectory.media[i].metadata.positionData;
photo.metadata.size = scannedDirectory.media[i].metadata.size;
photosToSave.push(photo);
if (!Utils.equalsFilter(media.metadata, scannedDirectory.media[i].metadata)) {
media.metadata = (<PhotoDTO>scannedDirectory.media[i]).metadata;
mediaToSave.push(media);
}
}
await photosRepository.save(photosToSave);
await photosRepository.remove(indexedPhotos);
await this.saveMedia(connection, mediaToSave);
await mediaRepository.remove(indexedMedia);
}
private async saveMedia(connection: Connection, mediaList: MediaDTO[]): Promise<MediaEntity[]> {
const list = await connection.getRepository(VideoEntity).save(<VideoEntity[]>mediaList.filter(m => MediaDTO.isVideo(m)));
return list.concat(await connection.getRepository(PhotoEntity).save(<PhotoEntity[]>mediaList.filter(m => MediaDTO.isPhoto(m))));
}
async getRandomPhoto(queryFilter: RandomQuery): Promise<PhotoDTO> {
const connection = await SQLConnection.getConnection();
const photosRepository = connection.getRepository(PhotoEntity);
const query = photosRepository.createQueryBuilder('media');
query.innerJoinAndSelect('media.directory', 'directory');
const query = photosRepository.createQueryBuilder('photo');
query.innerJoinAndSelect('photo.directory', 'directory');
if (queryFilter.directory) {
const directoryName = path.basename(queryFilter.directory);

View File

@ -11,6 +11,8 @@ import {PasswordHelper} from '../PasswordHelper';
import {ProjectPath} from '../../ProjectPath';
import {VersionEntity} from './enitites/VersionEntity';
import {Logger} from '../../Logger';
import {MediaEntity} from './enitites/MediaEntity';
import {VideoEntity} from './enitites/VideoEntity';
export class SQLConnection {
@ -30,7 +32,9 @@ export class SQLConnection {
options.name = 'main';
options.entities = [
UserEntity,
MediaEntity,
PhotoEntity,
VideoEntity,
DirectoryEntity,
SharingEntity,
VersionEntity
@ -53,7 +57,9 @@ export class SQLConnection {
options.name = 'test';
options.entities = [
UserEntity,
MediaEntity,
PhotoEntity,
VideoEntity,
DirectoryEntity,
SharingEntity,
VersionEntity

View File

@ -1,6 +1,6 @@
import {Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn} from 'typeorm';
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
import {PhotoEntity} from './PhotoEntity';
import {MediaEntity} from './MediaEntity';
@Entity()
export class DirectoryEntity implements DirectoryDTO {
@ -32,9 +32,9 @@ export class DirectoryEntity implements DirectoryDTO {
public parent: DirectoryEntity;
@OneToMany(type => DirectoryEntity, dir => dir.parent)
public directories: Array<DirectoryEntity>;
public directories: DirectoryEntity[];
@OneToMany(type => PhotoEntity, photo => photo.directory)
public media: Array<PhotoEntity>;
@OneToMany(type => MediaEntity, media => media.directory)
public media: MediaEntity[];
}

View File

@ -0,0 +1,70 @@
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance} from 'typeorm';
import {DirectoryEntity} from './DirectoryEntity';
import {MediaDimension, MediaDTO, MediaMetadata} from '../../../../common/entities/MediaDTO';
import {OrientationTypes} from 'ts-exif-parser';
import {CameraMetadataEntity, PositionMetaDataEntity} from './PhotoEntity';
export class MediaDimensionEntity implements MediaDimension {
@Column('int')
width: number;
@Column('int')
height: number;
}
export class MediaMetadataEntity implements MediaMetadata {
@Column(type => MediaDimensionEntity)
size: MediaDimensionEntity;
@Column('bigint')
creationDate: number;
@Column('int')
fileSize: number;
@Column('simple-array')
keywords: string[];
@Column(type => CameraMetadataEntity)
cameraData: CameraMetadataEntity;
@Column(type => PositionMetaDataEntity)
positionData: PositionMetaDataEntity;
@Column('tinyint', {default: OrientationTypes.TOP_LEFT})
orientation: OrientationTypes;
@Column('int')
bitRate: number;
@Column('bigint')
duration: number;
}
// TODO: fix inheritance once its working in typeorm
@Entity()
@TableInheritance({column: {type: 'varchar', name: 'type'}})
export abstract class MediaEntity implements MediaDTO {
@PrimaryGeneratedColumn()
id: number;
@Column('text')
name: string;
@ManyToOne(type => DirectoryEntity, directory => directory.media, {onDelete: 'CASCADE'})
directory: DirectoryEntity;
@Column(type => MediaMetadataEntity)
metadata: MediaMetadataEntity;
readyThumbnails: number[] = [];
readyIcon = false;
}

View File

@ -1,13 +1,11 @@
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm';
import {CameraMetadata, PhotoDTO, PhotoMetadata} from '../../../../common/entities/PhotoDTO';
import {DirectoryEntity} from './DirectoryEntity';
import {Column, Entity, ChildEntity} from 'typeorm';
import {CameraMetadata, GPSMetadata, PhotoDTO, PhotoMetadata, PositionMetaData} from '../../../../common/entities/PhotoDTO';
import {OrientationTypes} from 'ts-exif-parser';
import {GPSMetadata, MediaDimension, PositionMetaData} from '../../../../common/entities/MediaDTO';
import {MediaEntity, MediaMetadataEntity} from './MediaEntity';
@Entity()
export class CameraMetadataEntity implements CameraMetadata {
@Column('text', {nullable: true})
@Column('int', {nullable: true})
ISO: number;
@Column('text', {nullable: true})
@ -30,7 +28,6 @@ export class CameraMetadataEntity implements CameraMetadata {
}
@Entity()
export class GPSMetadataEntity implements GPSMetadata {
@Column('int', {nullable: true})
@ -41,18 +38,6 @@ export class GPSMetadataEntity implements GPSMetadata {
altitude: number;
}
@Entity()
export class ImageSizeEntity implements MediaDimension {
@Column('int')
width: number;
@Column('int')
height: number;
}
@Entity()
export class PositionMetaDataEntity implements PositionMetaData {
@Column(type => GPSMetadataEntity)
@ -69,11 +54,10 @@ export class PositionMetaDataEntity implements PositionMetaData {
}
@Entity()
export class PhotoMetadataEntity implements PhotoMetadata {
export class PhotoMetadataEntity extends MediaMetadataEntity implements PhotoMetadata {
@Column('simple-array')
keywords: Array<string>;
keywords: string[];
@Column(type => CameraMetadataEntity)
cameraData: CameraMetadataEntity;
@ -84,34 +68,11 @@ export class PhotoMetadataEntity implements PhotoMetadata {
@Column('tinyint', {default: OrientationTypes.TOP_LEFT})
orientation: OrientationTypes;
@Column(type => ImageSizeEntity)
size: ImageSizeEntity;
@Column('bigint')
creationDate: number;
@Column('int')
fileSize: number;
}
@Entity()
export class PhotoEntity implements PhotoDTO {
@PrimaryGeneratedColumn()
id: number;
@Column('text')
name: string;
@ManyToOne(type => DirectoryEntity, directory => directory.media, {onDelete: 'CASCADE'})
directory: DirectoryEntity;
@ChildEntity()
export class PhotoEntity extends MediaEntity implements PhotoDTO {
@Column(type => PhotoMetadataEntity)
metadata: PhotoMetadataEntity;
readyThumbnails: Array<number> = [];
readyIcon = false;
}

View File

@ -0,0 +1,21 @@
import {Column, Entity, ChildEntity} from 'typeorm';
import { MediaEntity, MediaMetadataEntity} from './MediaEntity';
import {VideoDTO, VideoMetadata} from '../../../../common/entities/VideoDTO';
export class VideoMetadataEntity extends MediaMetadataEntity implements VideoMetadata {
@Column('int')
bitRate: number;
@Column('bigint')
duration: number;
}
@ChildEntity()
export class VideoEntity extends MediaEntity implements VideoDTO {
@Column(type => VideoMetadataEntity)
metadata: VideoMetadataEntity;
}

View File

@ -1,16 +1,16 @@
import * as fs from 'fs';
import * as path from 'path';
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
import {CameraMetadata, PhotoDTO, PhotoMetadata} from '../../../common/entities/PhotoDTO';
import {CameraMetadata, GPSMetadata, PhotoDTO, PhotoMetadata} from '../../../common/entities/PhotoDTO';
import {Logger} from '../../Logger';
import {IptcParser} from 'ts-node-iptc';
import {ExifParserFactory, OrientationTypes} from 'ts-exif-parser';
import * as ffmpeg from 'fluent-ffmpeg';
import {FfmpegCommand, FfprobeData} from 'fluent-ffmpeg';
import {FfprobeData} from 'fluent-ffmpeg';
import {ProjectPath} from '../../ProjectPath';
import {Config} from '../../../common/config/private/Config';
import {VideoDTO} from '../../../common/entities/VideoDTO';
import {GPSMetadata, MediaDimension, MediaMetadata} from '../../../common/entities/MediaDTO';
import {VideoDTO, VideoMetadata} from '../../../common/entities/VideoDTO';
import {MediaDimension, MediaMetadata} from '../../../common/entities/MediaDTO';
const LOG_TAG = '[DiskManagerTask]';
@ -87,7 +87,7 @@ export class DiskMangerWorker {
directory.media.push(<VideoDTO>{
name: file,
directory: null,
metadata: await DiskMangerWorker.loadPVideoMetadata(fullFilePath)
metadata: await DiskMangerWorker.loadVideoMetadata(fullFilePath)
});
if (maxPhotos != null && directory.media.length > maxPhotos) {
@ -106,16 +106,15 @@ export class DiskMangerWorker {
}
private static loadPVideoMetadata(fullPath: string): Promise<MediaMetadata> {
return new Promise<MediaMetadata>((resolve, reject) => {
const metadata: MediaMetadata = <MediaMetadata>{
keywords: [],
positionData: null,
public static loadVideoMetadata(fullPath: string): Promise<VideoMetadata> {
return new Promise<VideoMetadata>((resolve, reject) => {
const metadata: VideoMetadata = <VideoMetadata>{
size: {
width: 0,
height: 0
},
orientation: OrientationTypes.TOP_LEFT,
bitRate: 0,
duration: 0,
creationDate: 0,
fileSize: 0
};
@ -129,13 +128,20 @@ export class DiskMangerWorker {
return reject(err);
}
metadata.size = {
width: data.streams[0].width,
height: data.streams[0].height
};
if (!data.streams[0]) {
return resolve(metadata);
}
try {
metadata.creationDate = data.streams[0].tags.creation_time;
metadata.size = {
width: data.streams[0].width,
height: data.streams[0].height
};
metadata.duration = Math.floor(data.streams[0].duration * 1000);
metadata.bitRate = data.streams[0].bit_rate;
metadata.creationDate = Date.parse(data.streams[0].tags.creation_time);
} catch (err) {
}
@ -144,7 +150,7 @@ export class DiskMangerWorker {
});
}
private static loadPhotoMetadata(fullPath: string): Promise<PhotoMetadata> {
public static loadPhotoMetadata(fullPath: string): Promise<PhotoMetadata> {
return new Promise<PhotoMetadata>((resolve, reject) => {
fs.readFile(fullPath, (err, data) => {
if (err) {
@ -220,7 +226,7 @@ export class DiskMangerWorker {
metadata.creationDate = <number> (iptcData.date_time ? iptcData.date_time.getTime() : metadata.creationDate);
} catch (err) {
// Logger.debug(LOG_TAG, "Error parsing iptc data", fullPath, err);
Logger.debug(LOG_TAG, 'Error parsing iptc data', fullPath, err);
}
metadata.creationDate = metadata.creationDate || 0;

View File

@ -1,6 +1,7 @@
import {DirectoryDTO} from './DirectoryDTO';
import {PhotoDTO} from './PhotoDTO';
import {OrientationTypes} from 'ts-exif-parser';
import {VideoDTO} from './VideoDTO';
export interface MediaDTO {
id: number;
@ -13,27 +14,12 @@ export interface MediaDTO {
export interface MediaMetadata {
keywords: string[];
positionData: PositionMetaData;
size: MediaDimension;
creationDate: number;
fileSize: number;
}
export interface PositionMetaData {
GPSData?: GPSMetadata;
country?: string;
state?: string;
city?: string;
}
export interface GPSMetadata {
latitude?: number;
longitude?: number;
altitude?: number;
}
export interface MediaDimension {
width: number;
height: number;
@ -41,14 +27,14 @@ export interface MediaDimension {
export module MediaDTO {
export const hasPositionData = (media: MediaDTO): boolean => {
return !!media.metadata.positionData &&
!!(media.metadata.positionData.city ||
media.metadata.positionData.state ||
media.metadata.positionData.country ||
(media.metadata.positionData.GPSData &&
media.metadata.positionData.GPSData.altitude &&
media.metadata.positionData.GPSData.latitude &&
media.metadata.positionData.GPSData.longitude));
return !!(<PhotoDTO>media).metadata.positionData &&
!!((<PhotoDTO>media).metadata.positionData.city ||
(<PhotoDTO>media).metadata.positionData.state ||
(<PhotoDTO>media).metadata.positionData.country ||
((<PhotoDTO>media).metadata.positionData.GPSData &&
(<PhotoDTO>media).metadata.positionData.GPSData.altitude &&
(<PhotoDTO>media).metadata.positionData.GPSData.latitude &&
(<PhotoDTO>media).metadata.positionData.GPSData.longitude));
};
export const isSideWay = (media: MediaDTO): boolean => {
@ -63,6 +49,14 @@ export module MediaDTO {
};
export const isPhoto = (media: MediaDTO): boolean => {
return typeof (<PhotoDTO>media).metadata.keywords !== 'undefined' && (<PhotoDTO>media).metadata.keywords !== null;
};
export const isVideo = (media: MediaDTO): boolean => {
return !MediaDTO.isPhoto(media);
};
export const getRotatedSize = (photo: MediaDTO): MediaDimension => {
if (isSideWay(photo)) {
// noinspection JSSuspiciousNameCombination

View File

@ -1,6 +1,6 @@
import {DirectoryDTO} from './DirectoryDTO';
import {OrientationTypes} from 'ts-exif-parser';
import {MediaDTO, MediaMetadata, MediaDimension, PositionMetaData} from './MediaDTO';
import {MediaDTO, MediaMetadata, MediaDimension} from './MediaDTO';
export interface PhotoDTO extends MediaDTO {
id: number;
@ -21,6 +21,21 @@ export interface PhotoMetadata extends MediaMetadata {
fileSize: number;
}
export interface PositionMetaData {
GPSData?: GPSMetadata;
country?: string;
state?: string;
city?: string;
}
export interface GPSMetadata {
latitude?: number;
longitude?: number;
altitude?: number;
}
export interface CameraMetadata {
ISO?: number;
model?: string;

View File

@ -1,9 +1,18 @@
import {DirectoryDTO} from './DirectoryDTO';
import {MediaDTO, MediaMetadata} from './MediaDTO';
import {MediaDimension, MediaDTO, MediaMetadata} from './MediaDTO';
export interface VideoDTO extends MediaDTO {
id: number;
name: string;
directory: DirectoryDTO;
metadata: MediaMetadata;
metadata: VideoMetadata;
}
export interface VideoMetadata extends MediaMetadata {
size: MediaDimension;
creationDate: number;
bitRate: number;
duration: number; // in milliseconds
fileSize: number;
}

View File

@ -20,7 +20,7 @@ import {FullScreenService} from './gallery/fullscreen.service';
import {AuthenticationService} from './model/network/authentication.service';
import {UserMangerSettingsComponent} from './settings/usermanager/usermanager.settings.component';
import {FrameComponent} from './frame/frame.component';
import {GalleryLightboxPhotoComponent} from './gallery/lightbox/photo/photo.lightbox.gallery.component';
import {GalleryLightboxMediaComponent} from './gallery/lightbox/media/media.lightbox.gallery.component';
import {GalleryPhotoLoadingComponent} from './gallery/grid/photo/loading/loading.photo.grid.gallery.component';
import {GalleryNavigatorComponent} from './gallery/navigator/navigator.gallery.component';
import {GallerySearchComponent} from './gallery/search/search.gallery.component';
@ -139,7 +139,7 @@ export function translationsFactory(locale: string) {
FrameComponent,
LanguageComponent,
// Gallery
GalleryLightboxPhotoComponent,
GalleryLightboxMediaComponent,
GalleryPhotoLoadingComponent,
GalleryGridComponent,
GalleryDirectoryComponent,

View File

@ -127,11 +127,12 @@ export class GalleryComponent implements OnInit, OnDestroy {
this.directories = tmp.directories;
this.sortDirectories();
this.isPhotoWithLocation = false;
for (let i = 0; i < tmp.media.length; i++) {
if (tmp.media[i].metadata &&
tmp.media[i].metadata.positionData &&
tmp.media[i].metadata.positionData.GPSData &&
tmp.media[i].metadata.positionData.GPSData.longitude
if ((<PhotoDTO>tmp.media[i]).metadata &&
(<PhotoDTO>tmp.media[i]).metadata.positionData &&
(<PhotoDTO>tmp.media[i]).metadata.positionData.GPSData &&
(<PhotoDTO>tmp.media[i]).metadata.positionData.GPSData.longitude
) {
this.isPhotoWithLocation = true;
break;

View File

@ -15,11 +15,11 @@ export class GridMedia extends Media {
}
isPhoto(): boolean {
return typeof (<PhotoDTO>this.media).metadata.cameraData !== 'undefined';
return MediaDTO.isPhoto(this.media);
}
isVideo(): boolean {
return typeof (<PhotoDTO>this.media).metadata.cameraData === 'undefined';
return MediaDTO.isVideo(this.media);
}

View File

@ -7,6 +7,7 @@ import {Thumbnail, ThumbnailManagerService} from '../../thumnailManager.service'
import {Config} from '../../../../../common/config/public/Config';
import {AnimationBuilder} from '@angular/animations';
import {PageHelper} from '../../../model/page.helper';
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
@Component({
selector: 'app-gallery-grid-photo',
@ -75,12 +76,12 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
getPositionText(): string {
if (!this.gridPhoto) {
if (!this.gridPhoto || !this.gridPhoto.isPhoto()) {
return '';
}
return this.gridPhoto.media.metadata.positionData.city ||
this.gridPhoto.media.metadata.positionData.state ||
this.gridPhoto.media.metadata.positionData.country;
return (<PhotoDTO>this.gridPhoto.media).metadata.positionData.city ||
(<PhotoDTO>this.gridPhoto.media).metadata.positionData.state ||
(<PhotoDTO>this.gridPhoto.media).metadata.positionData.country;
}

View File

@ -10,7 +10,8 @@ import {MediaDTO} from '../../../../../common/entities/MediaDTO';
})
export class InfoPanelLightboxComponent {
@Input() media: MediaDTO;
@Output('onClose') onClose = new EventEmitter();
@Output() closed = new EventEmitter();
public mapEnabled = true;
constructor(public elementRef: ElementRef) {
@ -56,21 +57,21 @@ export class InfoPanelLightboxComponent {
}
hasGPS() {
return this.media.metadata.positionData && this.media.metadata.positionData.GPSData &&
this.media.metadata.positionData.GPSData.latitude && this.media.metadata.positionData.GPSData.longitude;
return (<PhotoDTO>this.media).metadata.positionData && (<PhotoDTO>this.media).metadata.positionData.GPSData &&
(<PhotoDTO>this.media).metadata.positionData.GPSData.latitude && (<PhotoDTO>this.media).metadata.positionData.GPSData.longitude;
}
getPositionText(): string {
if (!this.media.metadata.positionData) {
if (!(<PhotoDTO>this.media).metadata.positionData) {
return '';
}
let str = this.media.metadata.positionData.city ||
this.media.metadata.positionData.state || '';
let str = (<PhotoDTO>this.media).metadata.positionData.city ||
(<PhotoDTO>this.media).metadata.positionData.state || '';
if (str.length !== 0) {
str += ', ';
}
str += this.media.metadata.positionData.country || '';
str += (<PhotoDTO>this.media).metadata.positionData.country || '';
return str;
}
@ -80,7 +81,7 @@ export class InfoPanelLightboxComponent {
}
close() {
this.onClose.emit();
this.closed.emit();
}
}

View File

@ -5,11 +5,11 @@
</div>
<div class="lightbox" #lightbox>
<app-gallery-lightbox-photo [gridMedia]="activePhoto ? activePhoto.gridPhoto : null"
<app-gallery-lightbox-media [gridMedia]="activePhoto ? activePhoto.gridPhoto : null"
[loadMedia]="!animating"
[windowAspect]="getWindowAspectRatio()"
#photo>
</app-gallery-lightbox-photo>
</app-gallery-lightbox-media>
</div>
<div
*ngIf="controllersVisible"
@ -110,7 +110,7 @@
id="info-panel"
[style.width.px]="infoPanelWidth"
[media]="activePhoto.gridPhoto.photo"
(onClose)="hideInfoPanel()">
(closed)="hideInfoPanel()">
</app-info-panel>
</div>

View File

@ -16,7 +16,7 @@ import {Dimension} from '../../model/IRenderable';
import {FullScreenService} from '../fullscreen.service';
import {OverlayService} from '../overlay.service';
import {animate, AnimationBuilder, AnimationPlayer, style} from '@angular/animations';
import {GalleryLightboxPhotoComponent} from './photo/photo.lightbox.gallery.component';
import {GalleryLightboxMediaComponent} from './media/media.lightbox.gallery.component';
import {Observable, Subscription, timer} from 'rxjs';
import {filter} from 'rxjs/operators';
import {ActivatedRoute, Params, Router} from '@angular/router';
@ -37,7 +37,7 @@ export enum LightboxStates {
})
export class GalleryLightboxComponent implements OnDestroy, OnInit {
@ViewChild('photo') photoElement: GalleryLightboxPhotoComponent;
@ViewChild('photo') mediaElement: GalleryLightboxMediaComponent;
@ViewChild('lightbox') lightboxElement: ElementRef;
public navigation = {hasPrev: true, hasNext: true};
@ -245,7 +245,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
break;
case ' ': // space
if (this.activePhoto && this.activePhoto.gridPhoto.isVideo()) {
this.photoElement.playPause();
this.mediaElement.playPause();
}
break;
}
@ -292,7 +292,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
animate(300,
style(<any>Dimension.toString(to)))
])
.create(this.photoElement.elementRef.nativeElement);
.create(this.mediaElement.elementRef.nativeElement);
elem.play();
return elem;
}
@ -356,12 +356,12 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
public play() {
this.pause();
this.timerSub = this.timer.pipe(filter(t => t % 2 === 0)).subscribe(() => {
if (this.photoElement.imageLoadFinished === false) {
if (this.mediaElement.imageLoadFinished === false) {
return;
}
// do not skip video if its playing
if (this.activePhoto && this.activePhoto.gridPhoto.isVideo() &&
!this.photoElement.Paused) {
!this.mediaElement.Paused) {
return;
}
if (this.navigation.hasNext) {
@ -400,11 +400,11 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
public fastForward() {
this.pause();
this.timerSub = this.timer.subscribe(() => {
if (this.photoElement.imageLoadFinished === false) {
if (this.mediaElement.imageLoadFinished === false) {
return;
}
if (this.activePhoto && this.activePhoto.gridPhoto.isVideo() &&
!this.photoElement.Paused) {
!this.mediaElement.Paused) {
return;
}
if (this.navigation.hasNext) {

View File

@ -1,15 +1,14 @@
import {Component, ElementRef, Input, Output, OnChanges, ViewChild} from '@angular/core';
import {GridMedia} from '../../grid/GridMedia';
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
import {FixOrientationPipe} from '../../FixOrientationPipe';
import {MediaDTO} from '../../../../../common/entities/MediaDTO';
@Component({
selector: 'app-gallery-lightbox-photo',
styleUrls: ['./photo.lightbox.gallery.component.css'],
templateUrl: './photo.lightbox.gallery.component.html'
selector: 'app-gallery-lightbox-media',
styleUrls: ['./media.lightbox.gallery.component.css'],
templateUrl: './media.lightbox.gallery.component.html'
})
export class GalleryLightboxPhotoComponent implements OnChanges {
export class GalleryLightboxMediaComponent implements OnChanges {
@Input() gridMedia: GridMedia;
@Input() loadMedia = false;
@ -26,7 +25,7 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
thumbnailSrc: string = null;
photoSrc: string = null;
private videoProgress: number = 0;
private videoProgress = 0;
constructor(public elementRef: ElementRef) {
}
@ -49,10 +48,9 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
FixOrientationPipe.transform(this.gridMedia.getPhotoPath(), this.gridMedia.Orientation)
.then((src) => this.photoSrc = src);
}
}
/** Video **/
private onVideoProgress() {
this.videoProgress = (100 / this.video.nativeElement.duration) * this.video.nativeElement.currentTime;
}
@ -124,7 +122,7 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
onImageError() {
// TODO:handle error
this.imageLoadFinished = true;
console.error('Error: cannot load image for lightbox url: ' + this.gridMedia.getPhotoPath());
console.error('Error: cannot load media for lightbox url: ' + this.gridMedia.getPhotoPath());
}

View File

@ -1,28 +0,0 @@
import * as FfmpegCommand from 'fluent-ffmpeg';
const run = async () => {
const command = FfmpegCommand('demo/images/fulbright_mediumbr.mp4');
// command.setFfmpegPath('ffmpeg/ffmpeg.exe');
// command.setFfprobePath('ffmpeg/ffprobe.exe');
// command.setFlvtoolPath('ffmpeg/ffplay.exe');
FfmpegCommand('demo/images/fulbright_mediumbr.mp4').ffprobe((err,data) => {
console.log(data);
});
command // setup event handlers
.on('filenames', function (filenames) {
console.log('screenshots are ' + filenames.join(', '));
})
.on('end', function () {
console.log('screenshots were saved');
})
.on('error', function (err) {
console.log('an error happened: ' + err.message);
})
.outputOptions(['-qscale:v 4'])
// take 2 screenshots at predefined timemarks and size
.takeScreenshots({timemarks: ['10%'], size: '450x?', filename: 'thumbnail2-at-%s-seconds.jpg'});
};
run();

View File

@ -11,11 +11,11 @@ import {DirectoryEntity} from '../../../../../backend/model/sql/enitites/Directo
import {
CameraMetadataEntity,
GPSMetadataEntity,
ImageSizeEntity,
PhotoEntity,
PhotoMetadataEntity,
PositionMetaDataEntity
} from '../../../../../backend/model/sql/enitites/PhotoEntity';
import {MediaDimensionEntity} from '../../../../../backend/model/sql/enitites/MediaEntity';
describe('Typeorm integration', () => {
@ -68,7 +68,7 @@ describe('Typeorm integration', () => {
const getPhoto = () => {
const sd = new ImageSizeEntity();
const sd = new MediaDimensionEntity();
sd.height = 200;
sd.width = 200;
const gps = new GPSMetadataEntity();
@ -146,7 +146,7 @@ describe('Typeorm integration', () => {
const conn = await SQLConnection.getConnection();
const pr = conn.getRepository(PhotoEntity);
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
let photo = getPhoto();
const photo = getPhoto();
photo.directory = dir;
await pr.save(photo);
expect((await pr.find()).length).to.equal(1);
@ -156,11 +156,11 @@ describe('Typeorm integration', () => {
const conn = await SQLConnection.getConnection();
const pr = conn.getRepository(PhotoEntity);
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
let photo = getPhoto();
const photo = getPhoto();
photo.directory = dir;
await pr.save(photo);
let photos = await pr
const photos = await pr
.createQueryBuilder('media')
.orderBy('media.metadata.creationDate', 'ASC')
.where('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + photo.metadata.positionData.city + '%'})
@ -177,12 +177,12 @@ describe('Typeorm integration', () => {
const conn = await SQLConnection.getConnection();
const pr = conn.getRepository(PhotoEntity);
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
let photo = getPhoto();
const photo = getPhoto();
photo.directory = dir;
const city = photo.metadata.positionData.city;
photo.metadata.positionData = null;
await pr.save(photo);
let photos = await pr
const photos = await pr
.createQueryBuilder('media')
.orderBy('media.metadata.creationDate', 'ASC')
.where('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + city + '%'})
@ -196,10 +196,10 @@ describe('Typeorm integration', () => {
it('should open and close connection twice with media added ', async () => {
let conn = await SQLConnection.getConnection();
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
let dir2 = getDir();
const dir2 = getDir();
dir2.parent = dir;
await conn.getRepository(DirectoryEntity).save(dir2);
let photo = getPhoto();
const photo = getPhoto();
photo.directory = dir2;
await await conn.getRepository(PhotoEntity).save(photo);
await SQLConnection.close();

View File

@ -7,7 +7,6 @@ import {SQLConnection} from '../../../../../backend/model/sql/SQLConnection';
import {
CameraMetadataEntity,
GPSMetadataEntity,
ImageSizeEntity,
PhotoEntity,
PhotoMetadataEntity,
PositionMetaDataEntity
@ -16,6 +15,7 @@ import {SearchManager} from '../../../../../backend/model/sql/SearchManager';
import {AutoCompleteItem, SearchTypes} from '../../../../../common/entities/AutoCompleteItem';
import {SearchResultDTO} from '../../../../../common/entities/SearchResultDTO';
import {DirectoryEntity} from '../../../../../backend/model/sql/enitites/DirectoryEntity';
import {MediaDimensionEntity} from '../../../../../backend/model/sql/enitites/MediaEntity';
describe('SearchManager', () => {
@ -30,7 +30,7 @@ describe('SearchManager', () => {
dir.lastScanned = null;
const getPhoto = () => {
const sd = new ImageSizeEntity();
const sd = new MediaDimensionEntity();
sd.height = 200;
sd.width = 200;
const gps = new GPSMetadataEntity();

View File

@ -13,7 +13,7 @@ describe('DiskMangerWorker', () => {
const dir = await DiskMangerWorker.scanDirectory('/');
expect(dir.media.length).to.be.equals(1);
expect(dir.media[0].name).to.be.equals('test image öüóőúéáű-.,.jpg');
expect(dir.media[0].metadata.keywords).to.deep.equals(['Berkley', 'USA', 'űáéúőóüö ŰÁÉÚŐÓÜÖ']);
expect((<PhotoDTO>dir.media[0]).metadata.keywords).to.deep.equals(['Berkley', 'USA', 'űáéúőóüö ŰÁÉÚŐÓÜÖ']);
expect(dir.media[0].metadata.fileSize).to.deep.equals(62392);
expect(dir.media[0].metadata.size).to.deep.equals({width: 140, height: 93});
expect((<PhotoDTO>dir.media[0]).metadata.cameraData).to.deep.equals({
@ -26,7 +26,7 @@ describe('DiskMangerWorker', () => {
lens: 'EF-S15-85mm f/3.5-5.6 IS USM'
});
expect(dir.media[0].metadata.positionData).to.deep.equals({
expect((<PhotoDTO>dir.media[0]).metadata.positionData).to.deep.equals({
GPSData: {
latitude: 37.871093333333334,
longitude: -122.25678,