1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-05-31 23:09:48 +02:00

adding persons support for frontend

This commit is contained in:
Patrik J. Braun 2019-01-13 20:18:18 +01:00
parent 550d8d4f5f
commit 49bea44b05
14 changed files with 117 additions and 53 deletions

View File

@ -113,7 +113,7 @@ export class ObjectManagerRepository {
const UserManager = require('./sql/UserManager').UserManager; const UserManager = require('./sql/UserManager').UserManager;
const SearchManager = require('./sql/SearchManager').SearchManager; const SearchManager = require('./sql/SearchManager').SearchManager;
const SharingManager = require('./sql/SharingManager').SharingManager; const SharingManager = require('./sql/SharingManager').SharingManager;
const IndexingTaskManager = require('./sql/IndexingManager').IndexingTaskManager; const IndexingTaskManager = require('./sql/IndexingTaskManager').IndexingTaskManager;
const IndexingManager = require('./sql/IndexingManager').IndexingManager; const IndexingManager = require('./sql/IndexingManager').IndexingManager;
const PersonManager = require('./sql/PersonManager').PersonManager; const PersonManager = require('./sql/PersonManager').PersonManager;
ObjectManagerRepository.getInstance().GalleryManager = new GalleryManager(); ObjectManagerRepository.getInstance().GalleryManager = new GalleryManager();

View File

@ -6,6 +6,8 @@ import {PhotoEntity} from './enitites/PhotoEntity';
import {DirectoryEntity} from './enitites/DirectoryEntity'; import {DirectoryEntity} from './enitites/DirectoryEntity';
import {MediaEntity} from './enitites/MediaEntity'; import {MediaEntity} from './enitites/MediaEntity';
import {VideoEntity} from './enitites/VideoEntity'; import {VideoEntity} from './enitites/VideoEntity';
import {PersonEntry} from './enitites/PersonEntry';
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
export class SearchManager implements ISearchManager { export class SearchManager implements ISearchManager {
@ -29,7 +31,7 @@ export class SearchManager implements ISearchManager {
let result: AutoCompleteItem[] = []; let result: AutoCompleteItem[] = [];
const photoRepository = connection.getRepository(PhotoEntity); const photoRepository = connection.getRepository(PhotoEntity);
const videoRepository = connection.getRepository(VideoEntity); const videoRepository = connection.getRepository(VideoEntity);
const mediaRepository = connection.getRepository(MediaEntity); const personRepository = connection.getRepository(PersonEntry);
const directoryRepository = connection.getRepository(DirectoryEntity); const directoryRepository = connection.getRepository(DirectoryEntity);
@ -45,6 +47,14 @@ export class SearchManager implements ISearchManager {
.filter(k => k.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchTypes.keyword)); .filter(k => k.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchTypes.keyword));
}); });
result = result.concat(this.encapsulateAutoComplete((await personRepository
.createQueryBuilder('person')
.select('DISTINCT(person.name)')
.where('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(5)
.getRawMany())
.map(r => r.name), SearchTypes.person));
(await photoRepository (await photoRepository
.createQueryBuilder('photo') .createQueryBuilder('photo')
.select('photo.metadata.positionData.country as country, ' + .select('photo.metadata.positionData.country as country, ' +
@ -112,16 +122,19 @@ export class SearchManager implements ISearchManager {
resultOverflow: false resultOverflow: false
}; };
let repostiroy = connection.getRepository(MediaEntity); let repository = connection.getRepository(MediaEntity);
const faceRepository = connection.getRepository(FaceRegionEntry);
if (searchType === SearchTypes.photo) { if (searchType === SearchTypes.photo) {
repostiroy = connection.getRepository(PhotoEntity); repository = connection.getRepository(PhotoEntity);
} else if (searchType === SearchTypes.video) { } else if (searchType === SearchTypes.video) {
repostiroy = connection.getRepository(VideoEntity); repository = connection.getRepository(VideoEntity);
} }
const query = repostiroy.createQueryBuilder('media') const query = repository.createQueryBuilder('media')
.innerJoinAndSelect('media.directory', 'directory') .leftJoinAndSelect('media.directory', 'directory')
.leftJoin('media.metadata.faces', 'faces')
.leftJoin('faces.person', 'person')
.orderBy('media.metadata.creationDate', 'ASC'); .orderBy('media.metadata.creationDate', 'ASC');
@ -136,6 +149,9 @@ export class SearchManager implements ISearchManager {
if (!searchType || searchType === SearchTypes.photo) { if (!searchType || searchType === SearchTypes.photo) {
query.orWhere('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}); query.orWhere('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
} }
if (!searchType || searchType === SearchTypes.person) {
query.orWhere('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
}
if (!searchType || searchType === SearchTypes.position) { if (!searchType || searchType === SearchTypes.position) {
query.orWhere('media.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) query.orWhere('media.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
@ -147,9 +163,20 @@ export class SearchManager implements ISearchManager {
query.orWhere('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}); query.orWhere('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
} }
result.media = await query
.limit(2001) result.media = (await query
.getMany(); .limit(5000).getMany()).slice(0, 2001);
for (let i = 0; i < result.media.length; i++) {
const faces = (await faceRepository
.createQueryBuilder('faces')
.leftJoinAndSelect('faces.person', 'person')
.where('faces.media = :media', {media: result.media[i].id})
.getMany()).map(fE => ({name: fE.person.name, box: fE.box}));
if (faces.length > 0) {
result.media[i].metadata.faces = faces;
}
}
if (result.media.length > 2000) { if (result.media.length > 2000) {
result.resultOverflow = true; result.resultOverflow = true;
@ -181,6 +208,8 @@ export class SearchManager implements ISearchManager {
resultOverflow: false resultOverflow: false
}; };
const faceRepository = connection.getRepository(FaceRegionEntry);
result.media = await connection result.media = await connection
.getRepository(MediaEntity) .getRepository(MediaEntity)
.createQueryBuilder('media') .createQueryBuilder('media')
@ -191,10 +220,23 @@ export class SearchManager implements ISearchManager {
.orWhere('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .orWhere('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .orWhere('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'}) .orWhere('media.metadata.caption LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('person.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.innerJoinAndSelect('media.directory', 'directory') .innerJoinAndSelect('media.directory', 'directory')
.leftJoin('media.metadata.faces', 'faces')
.leftJoin('faces.person', 'person')
.limit(10) .limit(10)
.getMany(); .getMany();
for (let i = 0; i < result.media.length; i++) {
const faces = (await faceRepository
.createQueryBuilder('faces')
.leftJoinAndSelect('faces.person', 'person')
.where('faces.media = :media', {media: result.media[i].id})
.getMany()).map(fE => ({name: fE.person.name, box: fE.box}));
if (faces.length > 0) {
result.media[i].metadata.faces = faces;
}
}
result.directories = await connection result.directories = await connection
.getRepository(DirectoryEntity) .getRepository(DirectoryEntity)

View File

@ -4,7 +4,7 @@ import {Config} from '../../../common/config/private/Config';
import {Logger} from '../../Logger'; import {Logger} from '../../Logger';
import * as fs from 'fs'; import * as fs from 'fs';
import * as sizeOf from 'image-size'; import * as sizeOf from 'image-size';
import {OrientationTypes, ExifParserFactory} from 'ts-exif-parser'; import {ExifParserFactory, OrientationTypes} from 'ts-exif-parser';
import {IptcParser} from 'ts-node-iptc'; import {IptcParser} from 'ts-node-iptc';
import {FFmpegFactory} from '../FFmpegFactory'; import {FFmpegFactory} from '../FFmpegFactory';
import {FfprobeData} from 'fluent-ffmpeg'; import {FfprobeData} from 'fluent-ffmpeg';
@ -147,10 +147,16 @@ export class MetadataLoader {
try { try {
const iptcData = IptcParser.parse(data); const iptcData = IptcParser.parse(data);
if (iptcData.country_or_primary_location_name || iptcData.province_or_state || iptcData.city) { if (iptcData.country_or_primary_location_name) {
metadata.positionData = metadata.positionData || {}; metadata.positionData = metadata.positionData || {};
metadata.positionData.country = iptcData.country_or_primary_location_name.replace(/\0/g, '').trim(); metadata.positionData.country = iptcData.country_or_primary_location_name.replace(/\0/g, '').trim();
}
if (iptcData.province_or_state) {
metadata.positionData = metadata.positionData || {};
metadata.positionData.state = iptcData.province_or_state.replace(/\0/g, '').trim(); metadata.positionData.state = iptcData.province_or_state.replace(/\0/g, '').trim();
}
if (iptcData.city) {
metadata.positionData = metadata.positionData || {};
metadata.positionData.city = iptcData.city.replace(/\0/g, '').trim(); metadata.positionData.city = iptcData.city.replace(/\0/g, '').trim();
} }
if (iptcData.caption) { if (iptcData.caption) {
@ -160,7 +166,7 @@ export class MetadataLoader {
metadata.creationDate = <number>(iptcData.date_time ? iptcData.date_time.getTime() : metadata.creationDate); metadata.creationDate = <number>(iptcData.date_time ? iptcData.date_time.getTime() : metadata.creationDate);
} catch (err) { } 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; metadata.creationDate = metadata.creationDate || 0;

View File

@ -1,9 +1,10 @@
export enum SearchTypes { export enum SearchTypes {
directory = 1, directory = 1,
keyword = 2, person = 2,
position = 3, keyword = 3,
photo = 4, position = 5,
video = 5 photo = 6,
video = 7
} }
export class AutoCompleteItem { export class AutoCompleteItem {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 695 KiB

After

Width:  |  Height:  |  Size: 696 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 883 KiB

After

Width:  |  Height:  |  Size: 884 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

After

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 KiB

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 KiB

After

Width:  |  Height:  |  Size: 530 KiB

View File

@ -92,7 +92,7 @@ a {
width: 100%; width: 100%;
} }
.video-indicator{ .video-indicator {
font-size: large; font-size: large;
padding: 5px; padding: 5px;
position: absolute; position: absolute;
@ -102,12 +102,16 @@ a {
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
color: white; color: white;
transition: background-color .3s ease-out; transition: background-color .3s ease-out;
-moz-transition: background-color .3s ease-out; -moz-transition: background-color .3s ease-out;
-webkit-transition: background-color .3s ease-out; -webkit-transition: background-color .3s ease-out;
-o-transition: background-color .3s ease-out; -o-transition: background-color .3s ease-out;
-ms-transition: background-color .3s ease-out; -ms-transition: background-color .3s ease-out;
} }
.photo-container:hover .video-indicator{ .photo-container:hover .video-indicator {
background-color: rgba(0, 0, 0, 0.8); background-color: rgba(0, 0, 0, 0.8);
} }
.photo-keywords .oi-person{
margin-right: 2px;
}

View File

@ -36,12 +36,18 @@
</ng-template> </ng-template>
</div> </div>
<div class="photo-keywords" *ngIf="gridPhoto.media.metadata.keywords && gridPhoto.media.metadata.keywords.length"> <div class="photo-keywords" *ngIf="keywords">
<ng-template ngFor let-keyword [ngForOf]="gridPhoto.media.metadata.keywords" let-last="last"> <ng-template ngFor let-keyword [ngForOf]="keywords" let-last="last">
<a *ngIf="searchEnabled" <a *ngIf="searchEnabled"
[routerLink]="['/search', keyword, {type: SearchTypes[SearchTypes.keyword]}]">#{{keyword}}</a> [routerLink]="['/search', keyword.value, {type: SearchTypes[keyword.type]}]" [ngSwitch]="keyword.type">
<span *ngIf="!searchEnabled">#{{keyword}}</span> <ng-template [ngSwitchCase]="SearchTypes.keyword">#</ng-template><!--
<ng-template [ngIf]="!last">, </ng-template> --><ng-template [ngSwitchCase]="SearchTypes.person"><span class="oi oi-person"></span></ng-template><!--
-->{{keyword.value}}</a>
<span *ngIf="!searchEnabled" [ngSwitch]="keyword.type">
<ng-template [ngSwitchCase]="SearchTypes.keyword">#</ng-template><!--
--><ng-template [ngSwitchCase]="SearchTypes.person"><span class="oi oi-person"></span></ng-template><!--
-->{{keyword.value}}</span>
<ng-template [ngIf]="!last">,</ng-template>
</ng-template> </ng-template>
</div> </div>

View File

@ -5,9 +5,8 @@ import {SearchTypes} from '../../../../../common/entities/AutoCompleteItem';
import {RouterLink} from '@angular/router'; import {RouterLink} from '@angular/router';
import {Thumbnail, ThumbnailManagerService} from '../../thumbnailManager.service'; import {Thumbnail, ThumbnailManagerService} from '../../thumbnailManager.service';
import {Config} from '../../../../../common/config/public/Config'; import {Config} from '../../../../../common/config/public/Config';
import {AnimationBuilder} from '@angular/animations';
import {PageHelper} from '../../../model/page.helper'; import {PageHelper} from '../../../model/page.helper';
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO'; import {PhotoDTO, PhotoMetadata} from '../../../../../common/entities/PhotoDTO';
@Component({ @Component({
selector: 'app-gallery-grid-photo', selector: 'app-gallery-grid-photo',
@ -22,6 +21,7 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
@ViewChild('photoContainer') container: ElementRef; @ViewChild('photoContainer') container: ElementRef;
thumbnail: Thumbnail; thumbnail: Thumbnail;
keywords: { value: string, type: SearchTypes }[] = null;
infoBar = { infoBar = {
marginTop: 0, marginTop: 0,
visible: false, visible: false,
@ -34,17 +34,40 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
wasInView: boolean = null; wasInView: boolean = null;
constructor(private thumbnailService: ThumbnailManagerService, constructor(private thumbnailService: ThumbnailManagerService) {
private _animationBuilder: AnimationBuilder) {
this.SearchTypes = SearchTypes; this.SearchTypes = SearchTypes;
this.searchEnabled = Config.Client.Search.enabled; this.searchEnabled = Config.Client.Search.enabled;
} }
ngOnInit() { get ScrollListener(): boolean {
this.thumbnail = this.thumbnailService.getThumbnail(this.gridPhoto); return !this.thumbnail.Available && !this.thumbnail.Error;
} }
get Title(): string {
if (Config.Client.Other.captionFirstNaming === false) {
return this.gridPhoto.media.name;
}
if ((<PhotoDTO>this.gridPhoto.media).metadata.caption) {
if ((<PhotoDTO>this.gridPhoto.media).metadata.caption.length > 20) {
return (<PhotoDTO>this.gridPhoto.media).metadata.caption.substring(0, 17) + '...';
}
return (<PhotoDTO>this.gridPhoto.media).metadata.caption;
}
return this.gridPhoto.media.name;
}
ngOnInit() {
this.thumbnail = this.thumbnailService.getThumbnail(this.gridPhoto);
const metadata = this.gridPhoto.media.metadata as PhotoMetadata;
if ((metadata.keywords && metadata.keywords.length > 0) ||
(metadata.faces && metadata.faces.length > 0)) {
this.keywords = (metadata.faces || []).map(f => ({value: f.name, type: SearchTypes.person}))
.concat((metadata.keywords || []).map(k => ({value: k, type: SearchTypes.keyword})));
}
}
ngOnDestroy() { ngOnDestroy() {
this.thumbnail.destroy(); this.thumbnail.destroy();
@ -53,16 +76,11 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
} }
} }
isInView(): boolean { isInView(): boolean {
return PageHelper.ScrollY < this.container.nativeElement.offsetTop + this.container.nativeElement.clientHeight return PageHelper.ScrollY < this.container.nativeElement.offsetTop + this.container.nativeElement.clientHeight
&& PageHelper.ScrollY + window.innerHeight > this.container.nativeElement.offsetTop; && PageHelper.ScrollY + window.innerHeight > this.container.nativeElement.offsetTop;
} }
get ScrollListener(): boolean {
return !this.thumbnail.Available && !this.thumbnail.Error;
}
onScroll() { onScroll() {
if (this.thumbnail.Available === true || this.thumbnail.Error === true) { if (this.thumbnail.Available === true || this.thumbnail.Error === true) {
return; return;
@ -74,7 +92,6 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
} }
} }
getPositionText(): string { getPositionText(): string {
if (!this.gridPhoto || !this.gridPhoto.isPhoto()) { if (!this.gridPhoto || !this.gridPhoto.isPhoto()) {
return ''; return '';
@ -84,7 +101,6 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
(<PhotoDTO>this.gridPhoto.media).metadata.positionData.country; (<PhotoDTO>this.gridPhoto.media).metadata.positionData.country;
} }
mouseOver() { mouseOver() {
this.infoBar.visible = true; this.infoBar.visible = true;
if (this.animationTimer != null) { if (this.animationTimer != null) {
@ -124,19 +140,6 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
} }
get Title(): string {
if (Config.Client.Other.captionFirstNaming === false) {
return this.gridPhoto.media.name;
}
if ((<PhotoDTO>this.gridPhoto.media).metadata.caption) {
if ((<PhotoDTO>this.gridPhoto.media).metadata.caption.length > 20) {
return (<PhotoDTO>this.gridPhoto.media).metadata.caption.substring(0, 17) + '...';
}
return (<PhotoDTO>this.gridPhoto.media).metadata.caption;
}
return this.gridPhoto.media.name;
}
/* /*
onImageLoad() { onImageLoad() {
this.loading.show = false; this.loading.show = false;

View File

@ -15,6 +15,7 @@
<span *ngSwitchCase="SearchTypes.video" class="oi oi-video"></span> <span *ngSwitchCase="SearchTypes.video" class="oi oi-video"></span>
<span *ngSwitchCase="SearchTypes.directory" class="oi oi-folder"></span> <span *ngSwitchCase="SearchTypes.directory" class="oi oi-folder"></span>
<span *ngSwitchCase="SearchTypes.keyword" class="oi oi-tag"></span> <span *ngSwitchCase="SearchTypes.keyword" class="oi oi-tag"></span>
<span *ngSwitchCase="SearchTypes.person" class="oi oi-person"></span>
<span *ngSwitchCase="SearchTypes.position" class="oi oi-map-marker"></span> <span *ngSwitchCase="SearchTypes.position" class="oi oi-map-marker"></span>
</span> </span>
<strong> {{searchResult.searchText}}</strong> <strong> {{searchResult.searchText}}</strong>

View File

@ -25,6 +25,7 @@
<span *ngSwitchCase="SearchTypes.video" class="oi oi-video"></span> <span *ngSwitchCase="SearchTypes.video" class="oi oi-video"></span>
<span *ngSwitchCase="SearchTypes.directory" class="oi oi-folder"></span> <span *ngSwitchCase="SearchTypes.directory" class="oi oi-folder"></span>
<span *ngSwitchCase="SearchTypes.keyword" class="oi oi-tag"></span> <span *ngSwitchCase="SearchTypes.keyword" class="oi oi-tag"></span>
<span *ngSwitchCase="SearchTypes.person" class="oi oi-person"></span>
<span *ngSwitchCase="SearchTypes.position" class="oi oi-map-marker"></span> <span *ngSwitchCase="SearchTypes.position" class="oi oi-map-marker"></span>
</span> </span>
{{item.preText}}<strong>{{item.highLightText}}</strong>{{item.postText}} {{item.preText}}<strong>{{item.highLightText}}</strong>{{item.postText}}