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

Implementing model for advanced searching. #58

This commit is contained in:
Patrik J. Braun 2021-01-16 16:59:59 +01:00
parent 928f282311
commit 9a923aa8ab
13 changed files with 1607 additions and 268 deletions

33
package-lock.json generated
View File

@ -6729,6 +6729,16 @@
}
}
},
"deep-equal-in-any-order": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/deep-equal-in-any-order/-/deep-equal-in-any-order-1.0.28.tgz",
"integrity": "sha512-qq3jffpGmAG9kGpZGKusjRwoGxmFgIqNW076HQmV9rNdrFsgTcpuCyp6dBhzdVCWgQDkgRmvZLYAilV4u2BsfQ==",
"dev": true,
"requires": {
"lodash.mapvalues": "^4.6.0",
"sort-any": "^1.1.21"
}
},
"deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@ -12245,6 +12255,12 @@
"lodash.isarray": "^3.0.0"
}
},
"lodash.mapvalues": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz",
"integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=",
"dev": true
},
"lodash.restparam": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
@ -17448,6 +17464,23 @@
"socks": "~2.2.0"
}
},
"sort-any": {
"version": "1.1.23",
"resolved": "https://registry.npmjs.org/sort-any/-/sort-any-1.1.23.tgz",
"integrity": "sha512-aY92w1RkjIyJd1l+O4btCwfAIfZm2r+zA6+cfKbKUO5D5MEZlqY27B7QyHHIsEShBsvx+Ur1Oq3v/gfR6wxD/w==",
"dev": true,
"requires": {
"lodash": "^4.17.15"
},
"dependencies": {
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true
}
}
},
"sort-keys": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",

View File

@ -94,6 +94,7 @@
"codelyzer": "6.0.1",
"core-js": "3.8.2",
"coveralls": "3.1.0",
"deep-equal-in-any-order": "^1.0.28",
"ejs-loader": "0.5.0",
"gulp": "4.0.2",
"gulp-json-editor": "2.5.4",

View File

@ -8,6 +8,7 @@ import {IIndexingManager} from './database/interfaces/IIndexingManager';
import {IPersonManager} from './database/interfaces/IPersonManager';
import {IVersionManager} from './database/interfaces/IVersionManager';
import {IJobManager} from './database/interfaces/IJobManager';
import {LocationManager} from './database/LocationManager';
export class ObjectManagers {
@ -21,6 +22,7 @@ export class ObjectManagers {
private _personManager: IPersonManager;
private _versionManager: IVersionManager;
private _jobManager: IJobManager;
private _locationManager: LocationManager;
get VersionManager(): IVersionManager {
@ -31,6 +33,14 @@ export class ObjectManagers {
this._versionManager = value;
}
get LocationManager(): LocationManager {
return this._locationManager;
}
set LocationManager(value: LocationManager) {
this._locationManager = value;
}
get PersonManager(): IPersonManager {
return this._personManager;
}
@ -129,6 +139,7 @@ export class ObjectManagers {
ObjectManagers.getInstance().IndexingManager = new IndexingManager();
ObjectManagers.getInstance().PersonManager = new PersonManager();
ObjectManagers.getInstance().VersionManager = new VersionManager();
ObjectManagers.getInstance().LocationManager = new LocationManager();
this.InitCommonManagers();
}
@ -149,6 +160,7 @@ export class ObjectManagers {
ObjectManagers.getInstance().IndexingManager = new IndexingManager();
ObjectManagers.getInstance().PersonManager = new PersonManager();
ObjectManagers.getInstance().VersionManager = new VersionManager();
ObjectManagers.getInstance().LocationManager = new LocationManager();
this.InitCommonManagers();
Logger.debug('SQL DB inited');
}

View File

@ -0,0 +1,14 @@
import {GPSMetadata} from '../../../common/entities/PhotoDTO';
export class LocationManager {
async getGPSData(text: string): Promise<GPSMetadata> {
return {
longitude: 0,
latitude: 0,
altitude: 0
};
}
}

View File

@ -24,20 +24,24 @@ const LOG_TAG = '[GalleryManager]';
export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
public static parseRelativeDirePath(relativeDirectoryName: string): { name: string, parent: string } {
relativeDirectoryName = DiskMangerWorker.normalizeDirPath(relativeDirectoryName);
return {
name: path.basename(relativeDirectoryName),
parent: path.join(path.dirname(relativeDirectoryName), path.sep),
};
}
public async listDirectory(relativeDirectoryName: string,
knownLastModified?: number,
knownLastScanned?: number): Promise<DirectoryDTO> {
relativeDirectoryName = DiskMangerWorker.normalizeDirPath(relativeDirectoryName);
const directoryName = path.basename(relativeDirectoryName);
const directoryParent = path.join(path.dirname(relativeDirectoryName), path.sep);
const directoryPath = GalleryManager.parseRelativeDirePath(relativeDirectoryName);
const connection = await SQLConnection.getConnection();
const stat = fs.statSync(path.join(ProjectPath.ImageFolder, relativeDirectoryName));
const lastModified = DiskMangerWorker.calcLastModified(stat);
const dir = await this.selectParentDir(connection, directoryName, directoryParent);
const dir = await this.selectParentDir(connection, directoryPath.name, directoryPath.parent);
if (dir && dir.lastScanned != null) {
// If it seems that the content did not changed, do not work on it
if (knownLastModified && knownLastScanned

View File

@ -8,8 +8,24 @@ import {MediaEntity} from './enitites/MediaEntity';
import {VideoEntity} from './enitites/VideoEntity';
import {PersonEntry} from './enitites/PersonEntry';
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
import {SelectQueryBuilder} from 'typeorm';
import {Brackets, SelectQueryBuilder, WhereExpression} from 'typeorm';
import {Config} from '../../../../common/config/private/Config';
import {
ANDSearchQuery,
DateSearch,
DistanceSearch,
OrientationSearch,
OrientationSearchTypes,
ORSearchQuery,
RatingSearch,
ResolutionSearch,
SearchQueryDTO,
SearchQueryTypes,
TextSearch,
TextSearchQueryTypes
} from '../../../../common/entities/SearchQueryDTO';
import {GalleryManager} from './GalleryManager';
import {ObjectManagers} from '../../ObjectManagers';
export class SearchManager implements ISearchManager {
@ -113,6 +129,75 @@ export class SearchManager implements ISearchManager {
return SearchManager.autoCompleteItemsUnique(result);
}
async getGPSData(query: SearchQueryDTO) {
if ((query as ANDSearchQuery | ORSearchQuery).list) {
for (let i = 0; i < (query as ANDSearchQuery | ORSearchQuery).list.length; ++i) {
(query as ANDSearchQuery | ORSearchQuery).list[i] =
await this.getGPSData((query as ANDSearchQuery | ORSearchQuery).list[i]);
}
}
if (query.type === SearchQueryTypes.distance && (<DistanceSearch>query).from.text) {
(<DistanceSearch>query).from.GPSData =
await ObjectManagers.getInstance().LocationManager.getGPSData((<DistanceSearch>query).from.text);
}
return query;
}
async aSearch(query: SearchQueryDTO) {
query = this.flattenSameOfQueries(query);
query = await this.getGPSData(query);
const connection = await SQLConnection.getConnection();
const result: SearchResultDTO = {
searchText: null,
searchType: null,
directories: [],
media: [],
metaFile: [],
resultOverflow: false
};
const sqlQuery = await connection.getRepository(MediaEntity).createQueryBuilder('media')
.innerJoin(q => {
const subQuery = q.from(MediaEntity, 'media')
.select('distinct media.id')
.limit(Config.Client.Search.maxMediaResult + 1);
subQuery.leftJoin('media.directory', 'directory')
.leftJoin('media.metadata.faces', 'faces')
.leftJoin('faces.person', 'person')
.where(this.buildWhereQuery(query));
return subQuery;
},
'innerMedia',
'media.id=innerMedia.id')
.leftJoinAndSelect('media.directory', 'directory')
.leftJoinAndSelect('media.metadata.faces', 'faces')
.leftJoinAndSelect('faces.person', 'person');
result.media = await this.loadMediaWithFaces(sqlQuery);
if (result.media.length > Config.Client.Search.maxMediaResult) {
result.resultOverflow = true;
}
/* result.directories = await connection
.getRepository(DirectoryEntity)
.createQueryBuilder('dir')
.where('dir.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(Config.Client.Search.maxMediaResult + 1)
.getMany();
if (result.directories.length > Config.Client.Search.maxDirectoryResult) {
result.resultOverflow = true;
}*/
return result;
}
async search(text: string, searchType: SearchTypes): Promise<SearchResultDTO> {
const connection = await SQLConnection.getConnection();
@ -245,6 +330,195 @@ export class SearchManager implements ISearchManager {
return result;
}
private buildWhereQuery(query: SearchQueryDTO, paramCounter = {value: 0}): Brackets {
switch (query.type) {
case SearchQueryTypes.AND:
return new Brackets(q => {
(<ANDSearchQuery>query).list.forEach(sq => q.andWhere(this.buildWhereQuery(sq, paramCounter)));
return q;
});
case SearchQueryTypes.OR:
return new Brackets(q => {
(<ANDSearchQuery>query).list.forEach(sq => q.orWhere(this.buildWhereQuery(sq, paramCounter)));
return q;
});
case SearchQueryTypes.distance:
/**
* This is a best effort calculation, not fully accurate in order to have higher performance.
* see: https://stackoverflow.com/a/50506609
*/
const earth = 6378.137, // radius of the earth in kilometer
latDelta = (1 / ((2 * Math.PI / 360) * earth)), // 1 km in degree
lonDelta = (1 / ((2 * Math.PI / 360) * earth)); // 1 km in degree
const minLat = (<DistanceSearch>query).from.GPSData.latitude - ((<DistanceSearch>query).distance * latDelta),
maxLat = (<DistanceSearch>query).from.GPSData.latitude + ((<DistanceSearch>query).distance * latDelta),
minLon = (<DistanceSearch>query).from.GPSData.latitude -
((<DistanceSearch>query).distance * lonDelta) / Math.cos(minLat * (Math.PI / 180)),
maxLon = (<DistanceSearch>query).from.GPSData.latitude +
((<DistanceSearch>query).distance * lonDelta) / Math.cos(maxLat * (Math.PI / 180));
return new Brackets(q => {
const textParam: any = {};
paramCounter.value++;
textParam['maxLat' + paramCounter.value] = maxLat;
textParam['minLat' + paramCounter.value] = minLat;
textParam['maxLon' + paramCounter.value] = maxLon;
textParam['minLon' + paramCounter.value] = minLon;
q.where(`media.metadata.positionData.GPSData.latitude < :maxLat${paramCounter.value}`, textParam);
q.andWhere(`media.metadata.positionData.GPSData.latitude > :minLat${paramCounter.value}`, textParam);
q.andWhere(`media.metadata.positionData.GPSData.longitude < :maxLon${paramCounter.value}`, textParam);
q.andWhere(`media.metadata.positionData.GPSData.longitude > :minLon${paramCounter.value}`, textParam);
return q;
});
case SearchQueryTypes.date:
return new Brackets(q => {
if (typeof (<DateSearch>query).before === 'undefined' && typeof (<DateSearch>query).after === 'undefined') {
throw new Error('Invalid search query: Date Query should contain before or after value');
}
if (typeof (<DateSearch>query).before !== 'undefined') {
const textParam: any = {};
textParam['before' + paramCounter.value] = (<DateSearch>query).before;
q.where(`media.metadata.creationDate <= :before${paramCounter.value}`, textParam);
}
if (typeof (<DateSearch>query).after !== 'undefined') {
const textParam: any = {};
textParam['after' + paramCounter.value] = (<DateSearch>query).after;
q.andWhere(`media.metadata.creationDate >= :after${paramCounter.value}`, textParam);
}
paramCounter.value++;
return q;
});
case SearchQueryTypes.rating:
return new Brackets(q => {
if (typeof (<RatingSearch>query).min === 'undefined' && typeof (<RatingSearch>query).max === 'undefined') {
throw new Error('Invalid search query: Rating Query should contain min or max value');
}
if (typeof (<RatingSearch>query).min !== 'undefined') {
const textParam: any = {};
textParam['min' + paramCounter.value] = (<RatingSearch>query).min;
q.where(`media.metadata.rating >= :min${paramCounter.value}`, textParam);
}
if (typeof (<RatingSearch>query).max !== 'undefined') {
const textParam: any = {};
textParam['max' + paramCounter.value] = (<RatingSearch>query).max;
q.andWhere(`media.metadata.rating <= :max${paramCounter.value}`, textParam);
}
paramCounter.value++;
return q;
});
case SearchQueryTypes.resolution:
return new Brackets(q => {
if (typeof (<ResolutionSearch>query).min === 'undefined' && typeof (<ResolutionSearch>query).max === 'undefined') {
throw new Error('Invalid search query: Rating Query should contain min or max value');
}
if (typeof (<ResolutionSearch>query).min !== 'undefined') {
const textParam: any = {};
textParam['min' + paramCounter.value] = (<RatingSearch>query).min * 1000 * 1000;
q.where(`media.metadata.size.width * media.metadata.size.height >= :min${paramCounter.value}`, textParam);
}
if (typeof (<ResolutionSearch>query).max !== 'undefined') {
const textParam: any = {};
textParam['max' + paramCounter.value] = (<RatingSearch>query).max * 1000 * 1000;
q.andWhere(`media.metadata.size.width * media.metadata.size.height <= :max${paramCounter.value}`, textParam);
}
paramCounter.value++;
return q;
});
case SearchQueryTypes.orientation:
return new Brackets(q => {
if ((<OrientationSearch>query).orientation === OrientationSearchTypes.landscape) {
q.where('media.metadata.size.width >= media.metadata.size.height');
}
if ((<OrientationSearch>query).orientation === OrientationSearchTypes.portrait) {
q.andWhere('media.metadata.size.width <= media.metadata.size.height');
}
paramCounter.value++;
return q;
});
case SearchQueryTypes.SOME_OF:
throw new Error('Some of not supported');
}
return new Brackets((q: WhereExpression) => {
const createMatchString = (str: string) => {
return (<TextSearch>query).matchType === TextSearchQueryTypes.exact_match ? str : `%${str}%`;
};
const textParam: any = {};
paramCounter.value++;
textParam['text' + paramCounter.value] = createMatchString((<TextSearch>query).text);
if (query.type === SearchQueryTypes.any_text ||
query.type === SearchQueryTypes.directory) {
const dirPathStr = ((<TextSearch>query).text).replace(new RegExp('\\\\', 'g'), '/');
textParam['fullPath' + paramCounter.value] = createMatchString(dirPathStr);
q.orWhere(`directory.path LIKE :fullPath${paramCounter.value} COLLATE utf8_general_ci`,
textParam);
const directoryPath = GalleryManager.parseRelativeDirePath(dirPathStr);
q.orWhere(new Brackets(dq => {
textParam['dirName' + paramCounter.value] = createMatchString(directoryPath.name);
dq.where(`directory.name LIKE :dirName${paramCounter.value} COLLATE utf8_general_ci`,
textParam);
if (dirPathStr.includes('/')) {
textParam['parentName' + paramCounter.value] = createMatchString(directoryPath.parent);
dq.andWhere(`directory.path LIKE :parentName${paramCounter.value} COLLATE utf8_general_ci`,
textParam);
}
return dq;
}));
}
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.file_name) {
q.orWhere(`media.name LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
textParam);
}
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.caption) {
q.orWhere(`media.metadata.caption LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
textParam);
}
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.person) {
q.orWhere(`person.name LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
textParam);
}
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.position) {
q.orWhere(`media.metadata.positionData.country LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
textParam)
.orWhere(`media.metadata.positionData.state LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
textParam)
.orWhere(`media.metadata.positionData.city LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
textParam);
}
if (query.type === SearchQueryTypes.any_text || query.type === SearchQueryTypes.keyword) {
q.orWhere(`media.metadata.keywords LIKE :text${paramCounter.value} COLLATE utf8_general_ci`,
textParam);
}
return q;
});
}
private flattenSameOfQueries(query: SearchQueryDTO): SearchQueryDTO {
return query;
}
private encapsulateAutoComplete(values: string[], type: SearchTypes): Array<AutoCompleteItem> {
const res: AutoCompleteItem[] = [];
values.forEach((value) => {
@ -259,11 +533,27 @@ export class SearchManager implements ISearchManager {
let rawIndex = 0;
for (let i = 0; i < media.length; i++) {
if (rawAndEntities.raw[rawIndex].faces_id === null ||
rawAndEntities.raw[rawIndex].media_id !== media[i].id) {
if (rawAndEntities.raw[rawIndex].media_id !== media[i].id) {
throw new Error('index mismatch');
}
// media without a face
if (rawAndEntities.raw[rawIndex].faces_id === null) {
delete media[i].metadata.faces;
rawIndex++;
continue;
}
/*
if (rawAndEntities.raw[rawIndex].faces_id === null ||
rawAndEntities.raw[rawIndex].media_id !== media[i].id) {
delete media[i].metadata.faces;
continue;
}*/
// process all faces for one media
media[i].metadata.faces = [];
while (rawAndEntities.raw[rawIndex].media_id === media[i].id) {

View File

@ -1,5 +1,6 @@
import {MediaDTO} from './MediaDTO';
import {FileDTO} from './FileDTO';
import {PhotoDTO} from './PhotoDTO';
export interface DirectoryDTO<S extends FileDTO = MediaDTO> {
id: number;
@ -54,4 +55,10 @@ export module DirectoryDTO {
}
};
export const filterPhotos = (dir: DirectoryDTO): PhotoDTO[] => {
return <PhotoDTO[]>dir.media.filter(m => MediaDTO.isPhoto(m));
};
export const filterVideos = (dir: DirectoryDTO): PhotoDTO[] => {
return <PhotoDTO[]>dir.media.filter(m => MediaDTO.isPhoto(m));
};
}

View File

@ -2,6 +2,7 @@ export enum OrientationType {
any = 0, portrait = 1, landscape = 2
}
// TODO replace it with advanced search
export interface RandomQueryDTO {
directory?: string;
recursive?: boolean;

View File

@ -0,0 +1,106 @@
import {GPSMetadata} from './PhotoDTO';
export enum SearchQueryTypes {
AND = 1, OR, SOME_OF,
// non-text metadata
date = 10,
rating,
distance,
resolution,
orientation,
// TEXT search types
any_text = 100,
person,
keyword,
position,
caption,
file_name,
directory,
}
export enum TextSearchQueryTypes {
exact_match = 1, like = 2
}
export enum OrientationSearchTypes {
portrait = 1, landscape = 2
}
export interface SearchQueryDTO {
type: SearchQueryTypes;
}
export interface ANDSearchQuery extends SearchQueryDTO {
type: SearchQueryTypes.AND;
list: SearchQueryDTO[];
}
export interface ORSearchQuery extends SearchQueryDTO {
type: SearchQueryTypes.OR;
list: SearchQueryDTO[];
}
export interface SomeOfSearchQuery extends SearchQueryDTO, RangeSearchQuery {
type: SearchQueryTypes.SOME_OF;
list: NegatableSearchQuery[];
min?: number; // at least this amount of items
max?: number; // maximum this amount of items
}
export interface NegatableSearchQuery extends SearchQueryDTO {
negate?: boolean; // if true negates the expression
}
export interface RangeSearchQuery extends SearchQueryDTO {
min?: number;
max?: number;
}
export interface TextSearch extends NegatableSearchQuery {
type: SearchQueryTypes.any_text |
SearchQueryTypes.person |
SearchQueryTypes.keyword |
SearchQueryTypes.position |
SearchQueryTypes.caption |
SearchQueryTypes.file_name |
SearchQueryTypes.directory;
matchType: TextSearchQueryTypes;
text: string;
}
export interface DistanceSearch extends NegatableSearchQuery {
type: SearchQueryTypes.distance;
from: {
text?: string;
GPSData?: GPSMetadata;
};
distance: number; // in kms
}
export interface DateSearch extends NegatableSearchQuery {
type: SearchQueryTypes.date;
after?: number;
before?: number;
}
export interface RatingSearch extends NegatableSearchQuery, RangeSearchQuery {
type: SearchQueryTypes.rating;
min?: number;
max?: number;
}
export interface ResolutionSearch extends NegatableSearchQuery, RangeSearchQuery {
type: SearchQueryTypes.resolution;
min?: number; // in megapixels
max?: number; // in megapixels
}
export interface OrientationSearch extends NegatableSearchQuery {
type: SearchQueryTypes.orientation;
orientation: OrientationSearchTypes;
}

View File

@ -1,13 +1,13 @@
import {DirectoryDTO} from './DirectoryDTO';
import {PhotoDTO} from './PhotoDTO';
import {SearchTypes} from './AutoCompleteItem';
import {FileDTO} from './FileDTO';
import {MediaDTO} from './MediaDTO';
export interface SearchResultDTO {
searchText: string;
searchType?: SearchTypes;
directories: DirectoryDTO[];
media: PhotoDTO[];
media: MediaDTO[];
metaFile: FileDTO[];
resultOverflow: boolean;
}

View File

@ -8,11 +8,12 @@ import {ProjectPath} from '../../src/backend/ProjectPath';
declare let describe: any;
const savedDescribe = describe;
export class SQLTestHelper {
static enable = {
sqlite: true,
mysql: true
mysql: process.env.TEST_MYSQL !== 'false'
};
public static readonly savedDescribe = savedDescribe;
tempDir: string;
@ -40,6 +41,7 @@ export class SQLTestHelper {
});
}
public async initDB() {
if (this.dbType === ServerConfig.DatabaseType.sqlite) {
await this.initSQLite();

File diff suppressed because it is too large Load Diff

View File

@ -24,19 +24,26 @@ import {DiskMangerWorker} from '../../../../../src/backend/model/threading/DiskM
export class TestHelper {
public static getDirectoryEntry() {
public static getDirectoryEntry(parent: DirectoryDTO = null, name = 'wars dir') {
const dir = new DirectoryEntity();
dir.name = 'wars dir';
dir.path = '.';
dir.name = name;
dir.path = DiskMangerWorker.pathFromParent({path: '', name: '.'});
dir.mediaCount = 0;
dir.directories = [];
dir.metaFile = [];
dir.media = [];
dir.lastModified = Date.now();
dir.lastScanned = null;
dir.lastScanned = Date.now();
// dir.parent = null;
if (parent !== null) {
dir.path = DiskMangerWorker.pathFromParent(parent);
parent.directories.push(dir);
}
return dir;
}
public static getPhotoEntry(dir: DirectoryEntity) {
public static getPhotoEntry(dir: DirectoryDTO) {
const sd = new MediaDimensionEntity();
sd.height = 200;
sd.width = 200;
@ -66,7 +73,7 @@ export class TestHelper {
m.creationDate = Date.now();
m.fileSize = 123456789;
m.orientation = OrientationTypes.TOP_LEFT;
m.rating = 2;
// m.rating = 0; no rating by default
// TODO: remove when typeorm is fixed
m.duration = null;
@ -75,13 +82,13 @@ export class TestHelper {
const d = new PhotoEntity();
d.name = 'test media.jpg';
d.directory = dir;
dir.media.push(d);
d.metadata = m;
dir.mediaCount++;
return d;
}
public static getVideoEntry(dir: DirectoryEntity) {
public static getVideoEntry(dir: DirectoryDTO) {
const sd = new MediaDimensionEntity();
sd.height = 200;
sd.width = 200;
@ -99,20 +106,32 @@ export class TestHelper {
const d = new VideoEntity();
d.name = 'test video.jpg';
d.directory = dir;
d.name = 'test video.mp4';
dir.media.push(d);
d.metadata = m;
return d;
}
public static getPhotoEntry1(dir: DirectoryEntity) {
public static getVideoEntry1(dir: DirectoryDTO) {
const p = TestHelper.getVideoEntry(dir);
p.name = 'swVideo.mp4';
return p;
}
public static getPhotoEntry1(dir: DirectoryDTO) {
const p = TestHelper.getPhotoEntry(dir);
p.metadata.caption = 'Han Solo\'s dice';
p.metadata.keywords = ['Boba Fett', 'star wars', 'Anakin', 'death star'];
p.metadata.positionData.city = 'Mos Eisley';
p.metadata.positionData.country = 'Tatooine';
p.name = 'sw1';
p.name = 'sw1.jpg';
p.metadata.positionData.GPSData.latitude = 10;
p.metadata.positionData.GPSData.longitude = 10;
p.metadata.creationDate = Date.now() - 1000;
p.metadata.rating = 1;
p.metadata.size.height = 1000;
p.metadata.size.width = 1000;
p.metadata.faces = [<FaceRegion>{
box: {height: 10, width: 10, left: 10, top: 10},
@ -133,20 +152,23 @@ export class TestHelper {
return p;
}
public static getVideoEntry1(dir: DirectoryEntity) {
const p = TestHelper.getVideoEntry(dir);
p.name = 'swVideo';
return p;
}
public static getPhotoEntry2(dir: DirectoryEntity) {
public static getPhotoEntry2(dir: DirectoryDTO) {
const p = TestHelper.getPhotoEntry(dir);
p.metadata.keywords = ['Padmé Amidala', 'star wars', 'Natalie Portman', 'death star'];
p.metadata.caption = 'Light saber';
p.metadata.keywords = ['Padmé Amidala', 'star wars', 'Natalie Portman', 'death star', 'wookiee'];
p.metadata.positionData.city = 'Derem City';
p.metadata.positionData.state = 'Research City';
p.metadata.positionData.country = 'Kamino';
p.name = 'sw2';
p.name = 'sw2.jpg';
p.metadata.positionData.GPSData.latitude = -10;
p.metadata.positionData.GPSData.longitude = -10;
p.metadata.creationDate = Date.now() - 2000;
p.metadata.rating = 2;
p.metadata.size.height = 2000;
p.metadata.size.width = 1000;
p.metadata.faces = [<FaceRegion>{
box: {height: 10, width: 10, left: 10, top: 10},
name: 'Padmé Amidala'
@ -160,6 +182,61 @@ export class TestHelper {
return p;
}
public static getPhotoEntry3(dir: DirectoryDTO) {
const p = TestHelper.getPhotoEntry(dir);
p.metadata.caption = 'Amber stone';
p.metadata.keywords = ['star wars', 'wookiees'];
p.metadata.positionData.city = 'Castilon';
p.metadata.positionData.state = 'Devaron';
p.metadata.positionData.country = 'Ajan Kloss';
p.name = 'sw3.jpg';
p.metadata.positionData.GPSData.latitude = 10;
p.metadata.positionData.GPSData.longitude = 15;
p.metadata.creationDate = Date.now() - 3000;
p.metadata.rating = 3;
p.metadata.size.height = 1000;
p.metadata.size.width = 2000;
p.metadata.faces = [<FaceRegion>{
box: {height: 10, width: 10, left: 10, top: 10},
name: 'Kylo Ren'
}, <FaceRegion>{
box: {height: 10, width: 10, left: 101, top: 101},
name: 'Leia Organa'
}, <FaceRegion>{
box: {height: 10, width: 10, left: 103, top: 103},
name: 'Han Solo'
}] as any[];
return p;
}
public static getPhotoEntry4(dir: DirectoryDTO) {
const p = TestHelper.getPhotoEntry(dir);
p.metadata.caption = 'Millennium falcon';
p.metadata.keywords = ['star wars', 'ewoks'];
p.metadata.positionData.city = 'Tipoca City';
p.metadata.positionData.state = 'Exegol';
p.metadata.positionData.country = 'Jedha';
p.name = 'sw4.jpg';
p.metadata.positionData.GPSData.latitude = 15;
p.metadata.positionData.GPSData.longitude = 10;
p.metadata.creationDate = Date.now() - 4000;
p.metadata.size.height = 3000;
p.metadata.size.width = 2000;
p.metadata.faces = [<FaceRegion>{
box: {height: 10, width: 10, left: 10, top: 10},
name: 'Kylo Ren'
}, <FaceRegion>{
box: {height: 10, width: 10, left: 101, top: 101},
name: 'Anakin Skywalker'
}, <FaceRegion>{
box: {height: 10, width: 10, left: 101, top: 101},
name: 'Obivan Kenobi'
}] as any[];
return p;
}
public static getRandomizedDirectoryEntry(parent: DirectoryDTO = null, forceStr: string = null) {