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

implementing basic video support

This commit is contained in:
Patrik J. Braun 2018-11-04 19:28:32 +01:00
parent 56e570cf3c
commit f13f333d49
52 changed files with 764 additions and 500 deletions

View File

@ -59,8 +59,8 @@ export class GalleryMWs {
if (cw.notModified === true) {
return next();
}
const removeDirs = (dir) => {
dir.photos.forEach((photo: PhotoDTO) => {
const removeDirs = (dir: DirectoryDTO) => {
dir.media.forEach((photo: PhotoDTO) => {
photo.directory = null;
});
@ -119,28 +119,28 @@ export class GalleryMWs {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'No photo found'));
}
req.params.imagePath = path.join(photo.directory.path, photo.directory.name, photo.name);
req.params.mediaPath = path.join(photo.directory.path, photo.directory.name, photo.name);
return next();
}
public static loadImage(req: Request, res: Response, next: NextFunction) {
if (!(req.params.imagePath)) {
public static loadMedia(req: Request, res: Response, next: NextFunction) {
if (!(req.params.mediaPath)) {
return next();
}
const fullImagePath = path.join(ProjectPath.ImageFolder, req.params.imagePath);
const fullMediaPath = path.join(ProjectPath.ImageFolder, req.params.mediaPath);
// check if thumbnail already exist
if (fs.existsSync(fullImagePath) === false) {
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'no such file:' + fullImagePath));
if (fs.existsSync(fullMediaPath) === false) {
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'no such file:' + fullMediaPath));
}
if (fs.statSync(fullImagePath).isDirectory()) {
if (fs.statSync(fullMediaPath).isDirectory()) {
return next();
}
req.resultPipe = fullImagePath;
req.resultPipe = fullMediaPath;
return next();
}
@ -161,7 +161,7 @@ export class GalleryMWs {
try {
const result = await ObjectManagerRepository.getInstance().SearchManager.search(req.params.text, type);
result.directories.forEach(dir => dir.photos = dir.photos || []);
result.directories.forEach(dir => dir.media = dir.media || []);
req.resultPipe = new ContentWrapper(null, result);
return next();
} catch (err) {
@ -181,7 +181,7 @@ export class GalleryMWs {
try {
const result = await ObjectManagerRepository.getInstance().SearchManager.instantSearch(req.params.text);
result.directories.forEach(dir => dir.photos = dir.photos || []);
result.directories.forEach(dir => dir.media = dir.media || []);
req.resultPipe = new ContentWrapper(null, result);
return next();
} catch (err) {

View File

@ -12,8 +12,9 @@ import {PhotoDTO} from '../../../common/entities/PhotoDTO';
import {Config} from '../../../common/config/private/Config';
import {ThumbnailProcessingLib} from '../../../common/config/private/IPrivateConfig';
import {ThumbnailTH} from '../../model/threading/ThreadPool';
import {RendererInput} from '../../model/threading/ThumbnailWorker';
import {RendererInput, ThumbnailSourceType} from '../../model/threading/ThumbnailWorker';
import {ITaskQue, TaskQue} from '../../model/threading/TaskQue';
import {MediaDTO} from '../../../common/entities/MediaDTO';
export class ThumbnailGeneratorMWs {
@ -60,7 +61,7 @@ export class ThumbnailGeneratorMWs {
ThumbnailGeneratorMWs.addThInfoTODir(<DirectoryDTO>cw.directory);
}
if (cw.searchResult) {
ThumbnailGeneratorMWs.addThInfoToPhotos(cw.searchResult.photos);
ThumbnailGeneratorMWs.addThInfoToPhotos(cw.searchResult.media);
}
@ -68,46 +69,47 @@ export class ThumbnailGeneratorMWs {
}
public static generateThumbnail(req: Request, res: Response, next: NextFunction) {
if (!req.resultPipe) {
return next();
}
public static generateThumbnailFactory(sourceType: ThumbnailSourceType) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.resultPipe) {
return next();
}
// load parameters
const imagePath = req.resultPipe;
let size: number = parseInt(req.params.size, 10) || Config.Client.Thumbnail.thumbnailSizes[0];
// validate size
if (Config.Client.Thumbnail.thumbnailSizes.indexOf(size) === -1) {
size = Config.Client.Thumbnail.thumbnailSizes[0];
}
ThumbnailGeneratorMWs.generateImage(imagePath, size, false, req, res, next);
// load parameters
const mediaPath = req.resultPipe;
let size: number = parseInt(req.params.size, 10) || Config.Client.Thumbnail.thumbnailSizes[0];
// validate size
if (Config.Client.Thumbnail.thumbnailSizes.indexOf(size) === -1) {
size = Config.Client.Thumbnail.thumbnailSizes[0];
}
ThumbnailGeneratorMWs.generateImage(mediaPath, size, sourceType, false, req, res, next);
};
}
public static generateIcon(req: Request, res: Response, next: NextFunction) {
if (!req.resultPipe) {
return next();
}
// load parameters
const imagePath = req.resultPipe;
const size: number = Config.Client.Thumbnail.iconSize;
ThumbnailGeneratorMWs.generateImage(imagePath, size, true, req, res, next);
public static generateIconFactory(sourceType: ThumbnailSourceType) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.resultPipe) {
return next();
}
// load parameters
const mediaPath = req.resultPipe;
const size: number = Config.Client.Thumbnail.iconSize;
ThumbnailGeneratorMWs.generateImage(mediaPath, size, sourceType, true, req, res, next);
};
}
private static addThInfoTODir(directory: DirectoryDTO) {
if (typeof directory.photos === 'undefined') {
directory.photos = [];
if (typeof directory.media === 'undefined') {
directory.media = [];
}
if (typeof directory.directories === 'undefined') {
directory.directories = [];
}
ThumbnailGeneratorMWs.addThInfoToPhotos(directory.photos);
ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media);
for (let i = 0; i < directory.directories.length; i++) {
ThumbnailGeneratorMWs.addThInfoTODir(directory.directories[i]);
@ -115,13 +117,13 @@ export class ThumbnailGeneratorMWs {
}
private static addThInfoToPhotos(photos: Array<PhotoDTO>) {
private static addThInfoToPhotos(photos: MediaDTO[]) {
const thumbnailFolder = ProjectPath.ThumbnailFolder;
for (let i = 0; i < photos.length; i++) {
const fullImagePath = path.join(ProjectPath.ImageFolder, photos[i].directory.path, photos[i].directory.name, photos[i].name);
const fullMediaPath = path.join(ProjectPath.ImageFolder, photos[i].directory.path, photos[i].directory.name, photos[i].name);
for (let j = 0; j < Config.Client.Thumbnail.thumbnailSizes.length; j++) {
const size = Config.Client.Thumbnail.thumbnailSizes[j];
const thPath = path.join(thumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(fullImagePath, size));
const thPath = path.join(thumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(fullMediaPath, size));
if (fs.existsSync(thPath) === true) {
if (typeof photos[i].readyThumbnails === 'undefined') {
photos[i].readyThumbnails = [];
@ -130,7 +132,7 @@ export class ThumbnailGeneratorMWs {
}
}
const iconPath = path.join(thumbnailFolder,
ThumbnailGeneratorMWs.generateThumbnailName(fullImagePath, Config.Client.Thumbnail.iconSize));
ThumbnailGeneratorMWs.generateThumbnailName(fullMediaPath, Config.Client.Thumbnail.iconSize));
if (fs.existsSync(iconPath) === true) {
photos[i].readyIcon = true;
}
@ -138,12 +140,13 @@ export class ThumbnailGeneratorMWs {
}
}
private static async generateImage(imagePath: string,
private static async generateImage(mediaPath: string,
size: number,
sourceType: ThumbnailSourceType,
makeSquare: boolean,
req: Request, res: Response, next: NextFunction) {
// generate thumbnail path
const thPath = path.join(ProjectPath.ThumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(imagePath, size));
const thPath = path.join(ProjectPath.ThumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(mediaPath, size));
req.resultPipe = thPath;
@ -160,7 +163,8 @@ export class ThumbnailGeneratorMWs {
// run on other thread
const input = <RendererInput>{
imagePath: imagePath,
type: sourceType,
mediaPath: mediaPath,
size: size,
thPath: thPath,
makeSquare: makeSquare,
@ -170,12 +174,12 @@ export class ThumbnailGeneratorMWs {
await this.taskQue.execute(input);
return next();
} catch (error) {
return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR, 'Error during generating thumbnail: ' + input.imagePath, error));
return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR, 'Error during generating thumbnail: ' + input.mediaPath, error));
}
}
private static generateThumbnailName(imagePath: string, size: number): string {
return crypto.createHash('md5').update(imagePath).digest('hex') + '_' + size + '.jpg';
private static generateThumbnailName(mediaPath: string, size: number): string {
return crypto.createHash('md5').update(mediaPath).digest('hex') + '_' + size + '.jpg';
}
}

View File

@ -204,9 +204,9 @@ export class ConfigDiagnostics {
await ConfigDiagnostics.testRandomPhotoConfig(Config.Client.Sharing, Config);
} catch (ex) {
const err: Error = ex;
NotificationManager.warning('Random Photo is not supported with these settings. Disabling temporally. ' +
NotificationManager.warning('Random Media is not supported with these settings. Disabling temporally. ' +
'Please adjust the config properly.', err.toString());
Logger.warn(LOG_TAG, 'Random Photo is not supported with these settings, switching off..', err.toString());
Logger.warn(LOG_TAG, 'Random Media is not supported with these settings, switching off..', err.toString());
Config.Client.Sharing.enabled = false;
}

View File

@ -28,7 +28,7 @@ export class DiskManager {
directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName);
}
const addDirs = (dir: DirectoryDTO) => {
dir.photos.forEach((ph) => {
dir.media.forEach((ph) => {
ph.directory = dir;
});
dir.directories.forEach((d) => {

View File

@ -25,6 +25,6 @@ export class GalleryManager implements IGalleryManager {
}
getRandomPhoto(RandomQuery): Promise<PhotoDTO> {
throw new Error('Random photo is not supported without database');
throw new Error('Random media is not supported without database');
}
}

View File

@ -53,30 +53,30 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
return null;
}
}
if (dir.photos) {
for (let i = 0; i < dir.photos.length; i++) {
dir.photos[i].directory = dir;
dir.photos[i].readyThumbnails = [];
dir.photos[i].readyIcon = false;
if (dir.media) {
for (let i = 0; i < dir.media.length; i++) {
dir.media[i].directory = dir;
dir.media[i].readyThumbnails = [];
dir.media[i].readyIcon = false;
}
}
if (dir.directories) {
for (let i = 0; i < dir.directories.length; i++) {
dir.directories[i].photos = await connection
dir.directories[i].media = await connection
.getRepository(PhotoEntity)
.createQueryBuilder('photo')
.where('photo.directory = :dir', {
.createQueryBuilder('media')
.where('media.directory = :dir', {
dir: dir.directories[i].id
})
.orderBy('photo.metadata.creationDate', 'ASC')
.orderBy('media.metadata.creationDate', 'ASC')
.limit(Config.Server.indexing.folderPreviewSize)
.getMany();
dir.directories[i].isPartial = true;
for (let j = 0; j < dir.directories[i].photos.length; j++) {
dir.directories[i].photos[j].directory = dir.directories[i];
dir.directories[i].photos[j].readyThumbnails = [];
dir.directories[i].photos[j].readyIcon = false;
for (let j = 0; j < dir.directories[i].media.length; j++) {
dir.directories[i].media[j].directory = dir.directories[i];
dir.directories[i].media[j].readyThumbnails = [];
dir.directories[i].media[j].readyIcon = false;
}
}
}
@ -113,7 +113,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
const scannedDirectory = await DiskManager.scanDirectory(relativeDirectoryName);
// returning with the result
scannedDirectory.photos.forEach(p => p.readyThumbnails = []);
scannedDirectory.media.forEach(p => p.readyThumbnails = []);
resolve(scannedDirectory);
await this.saveToDB(scannedDirectory);
@ -169,18 +169,18 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
if (directory != null) { // update existing directory
if (!directory.parent || !directory.parent.id) { // set parent if not set yet
directory.parent = currentDir;
delete directory.photos;
delete directory.media;
await directoryRepository.save(directory);
}
} else {
scannedDirectory.directories[i].parent = currentDir;
(<DirectoryEntity>scannedDirectory.directories[i]).lastScanned = null; // new child dir, not fully scanned yet
const d = await directoryRepository.save(<DirectoryEntity>scannedDirectory.directories[i]);
for (let j = 0; j < scannedDirectory.directories[i].photos.length; j++) {
scannedDirectory.directories[i].photos[j].directory = d;
for (let j = 0; j < scannedDirectory.directories[i].media.length; j++) {
scannedDirectory.directories[i].media[j].directory = d;
}
await photosRepository.save(scannedDirectory.directories[i].photos);
await photosRepository.save(scannedDirectory.directories[i].media);
}
}
@ -188,38 +188,38 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
await directoryRepository.remove(childDirectories);
const indexedPhotos = await photosRepository.createQueryBuilder('photo')
.where('photo.directory = :dir', {
const indexedPhotos = await photosRepository.createQueryBuilder('media')
.where('media.directory = :dir', {
dir: currentDir.id
}).getMany();
const photosToSave = [];
for (let i = 0; i < scannedDirectory.photos.length; i++) {
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.photos[i].name) {
if (indexedPhotos[j].name === scannedDirectory.media[i].name) {
photo = indexedPhotos[j];
indexedPhotos.splice(j, 1);
break;
}
}
if (photo == null) {
scannedDirectory.photos[i].directory = null;
photo = Utils.clone(scannedDirectory.photos[i]);
scannedDirectory.photos[i].directory = scannedDirectory;
scannedDirectory.media[i].directory = null;
photo = Utils.clone(scannedDirectory.media[i]);
scannedDirectory.media[i].directory = scannedDirectory;
photo.directory = currentDir;
}
if (photo.metadata.keywords !== scannedDirectory.photos[i].metadata.keywords ||
photo.metadata.cameraData !== scannedDirectory.photos[i].metadata.cameraData ||
photo.metadata.positionData !== scannedDirectory.photos[i].metadata.positionData ||
photo.metadata.size !== scannedDirectory.photos[i].metadata.size) {
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.photos[i].metadata.keywords;
photo.metadata.cameraData = scannedDirectory.photos[i].metadata.cameraData;
photo.metadata.positionData = scannedDirectory.photos[i].metadata.positionData;
photo.metadata.size = scannedDirectory.photos[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);
}
}
@ -233,8 +233,8 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
const connection = await SQLConnection.getConnection();
const photosRepository = connection.getRepository(PhotoEntity);
const query = photosRepository.createQueryBuilder('photo');
query.innerJoinAndSelect('photo.directory', 'directory');
const query = photosRepository.createQueryBuilder('media');
query.innerJoinAndSelect('media.directory', 'directory');
if (queryFilter.directory) {
const directoryName = path.basename(queryFilter.directory);
@ -253,31 +253,31 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
}
if (queryFilter.fromDate) {
query.andWhere('photo.metadata.creationDate >= :fromDate', {
query.andWhere('media.metadata.creationDate >= :fromDate', {
fromDate: queryFilter.fromDate.getTime()
});
}
if (queryFilter.toDate) {
query.andWhere('photo.metadata.creationDate <= :toDate', {
query.andWhere('media.metadata.creationDate <= :toDate', {
toDate: queryFilter.toDate.getTime()
});
}
if (queryFilter.minResolution) {
query.andWhere('photo.metadata.size.width * photo.metadata.size.height >= :minRes', {
query.andWhere('media.metadata.size.width * media.metadata.size.height >= :minRes', {
minRes: queryFilter.minResolution * 1000 * 1000
});
}
if (queryFilter.maxResolution) {
query.andWhere('photo.metadata.size.width * photo.metadata.size.height <= :maxRes', {
query.andWhere('media.metadata.size.width * media.metadata.size.height <= :maxRes', {
maxRes: queryFilter.maxResolution * 1000 * 1000
});
}
if (queryFilter.orientation === OrientationType.landscape) {
query.andWhere('photo.metadata.size.width >= photo.metadata.size.height');
query.andWhere('media.metadata.size.width >= media.metadata.size.height');
}
if (queryFilter.orientation === OrientationType.portrait) {
query.andWhere('photo.metadata.size.width <= photo.metadata.size.height');
query.andWhere('media.metadata.size.width <= media.metadata.size.height');
}

View File

@ -30,9 +30,9 @@ export class SearchManager implements ISearchManager {
(await photoRepository
.createQueryBuilder('photo')
.select('DISTINCT(photo.metadata.keywords)')
.where('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.createQueryBuilder('media')
.select('DISTINCT(media.metadata.keywords)')
.where('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(5)
.getRawMany())
.map(r => <Array<string>>r.metadataKeywords.split(','))
@ -43,13 +43,13 @@ export class SearchManager implements ISearchManager {
(await photoRepository
.createQueryBuilder('photo')
.select('photo.metadata.positionData.country as country,' +
' photo.metadata.positionData.state as state, photo.metadata.positionData.city as city')
.where('photo.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('photo.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('photo.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.groupBy('photo.metadata.positionData.country, photo.metadata.positionData.state, photo.metadata.positionData.city')
.createQueryBuilder('media')
.select('media.metadata.positionData.country as country,' +
'mediao.metadata.positionData.state as state, media.metadata.positionData.city as city')
.where('media.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('media.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.groupBy('media.metadata.positionData.country, media.metadata.positionData.state, media.metadata.positionData.city')
.limit(5)
.getRawMany())
.filter(pm => !!pm)
@ -60,9 +60,9 @@ export class SearchManager implements ISearchManager {
});
result = result.concat(this.encapsulateAutoComplete((await photoRepository
.createQueryBuilder('photo')
.select('DISTINCT(photo.name)')
.where('photo.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.createQueryBuilder('media')
.select('DISTINCT(media.name)')
.where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.limit(5)
.getRawMany())
.map(r => r.name), SearchTypes.image));
@ -86,15 +86,15 @@ export class SearchManager implements ISearchManager {
searchText: text,
searchType: searchType,
directories: [],
photos: [],
media: [],
resultOverflow: false
};
const query = connection
.getRepository(PhotoEntity)
.createQueryBuilder('photo')
.innerJoinAndSelect('photo.directory', 'directory')
.orderBy('photo.metadata.creationDate', 'ASC');
.createQueryBuilder('media')
.innerJoinAndSelect('media.directory', 'directory')
.orderBy('media.metadata.creationDate', 'ASC');
if (!searchType || searchType === SearchTypes.directory) {
@ -102,24 +102,24 @@ export class SearchManager implements ISearchManager {
}
if (!searchType || searchType === SearchTypes.image) {
query.orWhere('photo.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
query.orWhere('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
}
if (!searchType || searchType === SearchTypes.position) {
query.orWhere('photo.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('photo.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('photo.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
query.orWhere('media.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('media.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
}
if (!searchType || searchType === SearchTypes.keyword) {
query.orWhere('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
query.orWhere('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
}
result.photos = await query
result.media = await query
.limit(2001)
.getMany();
if (result.photos.length > 2000) {
if (result.media.length > 2000) {
result.resultOverflow = true;
}
@ -144,20 +144,20 @@ export class SearchManager implements ISearchManager {
searchText: text,
// searchType:undefined, not adding this
directories: [],
photos: [],
media: [],
resultOverflow: false
};
result.photos = await connection
result.media = await connection
.getRepository(PhotoEntity)
.createQueryBuilder('photo')
.orderBy('photo.metadata.creationDate', 'ASC')
.where('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('photo.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('photo.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('photo.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('photo.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.innerJoinAndSelect('photo.directory', 'directory')
.createQueryBuilder('media')
.orderBy('media.metadata.creationDate', 'ASC')
.where('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('media.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
.orWhere('media.metadata.positionData.state 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 + '%'})
.innerJoinAndSelect('media.directory', 'directory')
.limit(10)
.getMany();

View File

@ -15,13 +15,13 @@ export class DirectoryEntity implements DirectoryDTO {
path: string;
/**
* last time the directory was modified (from outside, eg.: a new photo was added)
* last time the directory was modified (from outside, eg.: a new media was added)
*/
@Column('bigint')
public lastModified: number;
/**
* Last time the directory was fully scanned, not only for a few photos to create a preview
* Last time the directory was fully scanned, not only for a few media to create a preview
*/
@Column({type: 'bigint', nullable: true})
public lastScanned: number;
@ -35,6 +35,6 @@ export class DirectoryEntity implements DirectoryDTO {
public directories: Array<DirectoryEntity>;
@OneToMany(type => PhotoEntity, photo => photo.directory)
public photos: Array<PhotoEntity>;
public media: Array<PhotoEntity>;
}

View File

@ -1,7 +1,8 @@
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm';
import {CameraMetadata, GPSMetadata, ImageSize, PhotoDTO, PhotoMetadata, PositionMetaData} from '../../../../common/entities/PhotoDTO';
import {CameraMetadata, PhotoDTO, PhotoMetadata} from '../../../../common/entities/PhotoDTO';
import {DirectoryEntity} from './DirectoryEntity';
import {OrientationTypes} from 'ts-exif-parser';
import {GPSMetadata, MediaDimension, PositionMetaData} from '../../../../common/entities/MediaDTO';
@Entity()
export class CameraMetadataEntity implements CameraMetadata {
@ -41,7 +42,7 @@ export class GPSMetadataEntity implements GPSMetadata {
}
@Entity()
export class ImageSizeEntity implements ImageSize {
export class ImageSizeEntity implements MediaDimension {
@Column('int')
width: number;
@ -103,7 +104,7 @@ export class PhotoEntity implements PhotoDTO {
@Column('text')
name: string;
@ManyToOne(type => DirectoryEntity, directory => directory.photos, {onDelete: 'CASCADE'})
@ManyToOne(type => DirectoryEntity, directory => directory.media, {onDelete: 'CASCADE'})
directory: DirectoryEntity;
@Column(type => PhotoMetadataEntity)

View File

@ -1,12 +1,16 @@
import * as fs from 'fs';
import * as path from 'path';
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
import {CameraMetadata, GPSMetadata, ImageSize, PhotoDTO, PhotoMetadata} from '../../../common/entities/PhotoDTO';
import {CameraMetadata, 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 {ProjectPath} from '../../ProjectPath';
import {Config} from '../../../common/config/private/Config';
import {VideoDTO} from '../../../common/entities/VideoDTO';
import {GPSMetadata, MediaDimension, MediaMetadata} from '../../../common/entities/MediaDTO';
const LOG_TAG = '[DiskManagerTask]';
@ -27,6 +31,16 @@ export class DiskMangerWorker {
return extensions.indexOf(extension) !== -1;
}
private static isVideo(fullPath: string) {
const extensions = [
'.mp4',
'.webm'
];
const extension = path.extname(fullPath).toLowerCase();
return extensions.indexOf(extension) !== -1;
}
public static scanDirectory(relativeDirectoryName: string, maxPhotos: number = null, photosOnly: boolean = false): Promise<DirectoryDTO> {
return new Promise<DirectoryDTO>((resolve, reject) => {
const directoryName = path.basename(relativeDirectoryName);
@ -41,9 +55,9 @@ export class DiskMangerWorker {
lastScanned: Date.now(),
directories: [],
isPartial: false,
photos: []
media: []
};
fs.readdir(absoluteDirectoryName, async (err, list) => {
fs.readdir(absoluteDirectoryName, async (err, list: string[]) => {
if (err) {
return reject(err);
}
@ -60,13 +74,23 @@ export class DiskMangerWorker {
d.isPartial = true;
directory.directories.push(d);
} else if (DiskMangerWorker.isImage(fullFilePath)) {
directory.photos.push(<PhotoDTO>{
directory.media.push(<PhotoDTO>{
name: file,
directory: null,
metadata: await DiskMangerWorker.loadPhotoMetadata(fullFilePath)
});
if (maxPhotos != null && directory.photos.length > maxPhotos) {
if (maxPhotos != null && directory.media.length > maxPhotos) {
break;
}
} else if (DiskMangerWorker.isVideo(fullFilePath)) {
directory.media.push(<VideoDTO>{
name: file,
directory: null,
metadata: await DiskMangerWorker.loadPVideoMetadata(fullFilePath)
});
if (maxPhotos != null && directory.media.length > maxPhotos) {
break;
}
}
@ -82,6 +106,44 @@ export class DiskMangerWorker {
}
private static loadPVideoMetadata(fullPath: string): Promise<MediaMetadata> {
return new Promise<MediaMetadata>((resolve, reject) => {
const metadata: MediaMetadata = <MediaMetadata>{
keywords: [],
positionData: null,
size: {
width: 0,
height: 0
},
orientation: OrientationTypes.TOP_LEFT,
creationDate: 0,
fileSize: 0
};
try {
const stat = fs.statSync(fullPath);
metadata.fileSize = stat.size;
} catch (err) {
}
ffmpeg(fullPath).ffprobe((err: any, data: FfprobeData) => {
if (!!err || data === null) {
return reject(err);
}
metadata.size = {
width: data.streams[0].width,
height: data.streams[0].height
};
try {
metadata.creationDate = data.streams[0].tags.creation_time;
} catch (err) {
}
return resolve(metadata);
});
});
}
private static loadPhotoMetadata(fullPath: string): Promise<PhotoMetadata> {
return new Promise<PhotoMetadata>((resolve, reject) => {
fs.readFile(fullPath, (err, data) => {
@ -135,15 +197,15 @@ export class DiskMangerWorker {
if (exif.imageSize) {
metadata.size = <ImageSize> {width: exif.imageSize.width, height: exif.imageSize.height};
metadata.size = <MediaDimension> {width: exif.imageSize.width, height: exif.imageSize.height};
} else if (exif.tags.RelatedImageWidth && exif.tags.RelatedImageHeight) {
metadata.size = <ImageSize> {width: exif.tags.RelatedImageWidth, height: exif.tags.RelatedImageHeight};
metadata.size = <MediaDimension> {width: exif.tags.RelatedImageWidth, height: exif.tags.RelatedImageHeight};
} else {
metadata.size = <ImageSize> {width: 1, height: 1};
metadata.size = <MediaDimension> {width: 1, height: 1};
}
} catch (err) {
Logger.debug(LOG_TAG, 'Error parsing exif', fullPath, err);
metadata.size = <ImageSize> {width: 1, height: 1};
metadata.size = <MediaDimension> {width: 1, height: 1};
}
try {

View File

@ -1,43 +1,103 @@
import {Metadata, Sharp} from 'sharp';
import {Dimensions, State} from 'gm';
import {Logger} from '../../Logger';
import {FfmpegCommand, FfprobeData} from 'fluent-ffmpeg';
import {ThumbnailProcessingLib} from '../../../common/config/private/IPrivateConfig';
export class ThumbnailWorker {
private static renderer: (input: RendererInput) => Promise<void> = null;
private static imageRenderer: (input: RendererInput) => Promise<void> = null;
private static videoRenderer: (input: RendererInput) => Promise<void> = null;
private static rendererType = null;
public static render(input: RendererInput, renderer: ThumbnailProcessingLib): Promise<void> {
if (input.type === ThumbnailSourceType.Image) {
return this.renderFromImage(input, renderer);
}
return this.renderFromVideo(input);
}
public static renderFromImage(input: RendererInput, renderer: ThumbnailProcessingLib): Promise<void> {
if (ThumbnailWorker.rendererType !== renderer) {
ThumbnailWorker.renderer = RendererFactory.build(renderer);
ThumbnailWorker.imageRenderer = ImageRendererFactory.build(renderer);
ThumbnailWorker.rendererType = renderer;
}
return ThumbnailWorker.renderer(input);
return ThumbnailWorker.imageRenderer(input);
}
public static renderFromVideo(input: RendererInput): Promise<void> {
if (ThumbnailWorker.videoRenderer === null) {
ThumbnailWorker.videoRenderer = VideoRendererFactory.build();
}
return ThumbnailWorker.videoRenderer(input);
}
}
export enum ThumbnailSourceType {
Image, Video
}
export interface RendererInput {
imagePath: string;
type: ThumbnailSourceType;
mediaPath: string;
size: number;
makeSquare: boolean;
thPath: string;
qualityPriority: boolean;
}
export class RendererFactory {
export class VideoRendererFactory {
public static build(): (input: RendererInput) => Promise<void> {
const ffmpeg = require('fluent-ffmpeg');
return (input: RendererInput): Promise<void> => {
return new Promise((resolve, reject) => {
Logger.silly('[FFmpeg] rendering thumbnail: ' + input.mediaPath);
ffmpeg(input.mediaPath).ffprobe((err: any, data: FfprobeData) => {
if (!!err || data === null) {
return reject(err);
}
const ratio = data.streams[0].height / data.streams[0].width;
const command: FfmpegCommand = ffmpeg(input.mediaPath);
command
.on('end', () => {
resolve();
})
.on('error', (e) => {
reject(e);
})
.outputOptions(['-qscale:v 4']);
if (input.makeSquare === false) {
const newWidth = Math.round(Math.sqrt((input.size * input.size) / ratio));
command.takeScreenshots({
timemarks: ['10%'], size: newWidth + 'x?', filename: input.thPath
});
} else {
command.takeScreenshots({
timemarks: ['10%'], size: input.size + 'x' + input.size, filename: input.thPath
});
}
});
});
};
}
}
export class ImageRendererFactory {
public static build(renderer: ThumbnailProcessingLib): (input: RendererInput) => Promise<void> {
switch (renderer) {
case ThumbnailProcessingLib.Jimp:
return RendererFactory.Jimp();
return ImageRendererFactory.Jimp();
case ThumbnailProcessingLib.gm:
return RendererFactory.Gm();
return ImageRendererFactory.Gm();
case ThumbnailProcessingLib.sharp:
return RendererFactory.Sharp();
return ImageRendererFactory.Sharp();
}
throw new Error('unknown renderer');
}
@ -46,8 +106,8 @@ export class RendererFactory {
const Jimp = require('jimp');
return async (input: RendererInput): Promise<void> => {
// generate thumbnail
Logger.silly('[JimpThRenderer] rendering thumbnail:' + input.imagePath);
const image = await Jimp.read(input.imagePath);
Logger.silly('[JimpThRenderer] rendering thumbnail:' + input.mediaPath);
const image = await Jimp.read(input.mediaPath);
/**
* newWidth * newHeight = size*size
* newHeight/newWidth = height/width
@ -86,8 +146,8 @@ export class RendererFactory {
const sharp = require('sharp');
return async (input: RendererInput): Promise<void> => {
Logger.silly('[SharpThRenderer] rendering thumbnail:' + input.imagePath);
const image: Sharp = sharp(input.imagePath);
Logger.silly('[SharpThRenderer] rendering thumbnail:' + input.mediaPath);
const image: Sharp = sharp(input.mediaPath);
const metadata: Metadata = await image.metadata();
/**
@ -124,8 +184,8 @@ export class RendererFactory {
const gm = require('gm');
return (input: RendererInput): Promise<void> => {
return new Promise((resolve, reject) => {
Logger.silly('[GMThRenderer] rendering thumbnail:' + input.imagePath);
let image: State = gm(input.imagePath);
Logger.silly('[GMThRenderer] rendering thumbnail:' + input.mediaPath);
let image: State = gm(input.mediaPath);
image.size((err, value: Dimensions) => {
if (err) {
return reject(err);

View File

@ -3,13 +3,16 @@ import {GalleryMWs} from '../middlewares/GalleryMWs';
import {RenderingMWs} from '../middlewares/RenderingMWs';
import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs';
import {UserRoles} from '../../common/entities/UserDTO';
import {ThumbnailSourceType} from '../model/threading/ThumbnailWorker';
export class GalleryRouter {
public static route(app: any) {
this.addGetImageIcon(app);
this.addGetImageThumbnail(app);
this.addGetVideoThumbnail(app);
this.addGetImage(app);
this.addGetVideo(app);
this.addRandom(app);
this.addDirectoryList(app);
@ -31,10 +34,19 @@ export class GalleryRouter {
private static addGetImage(app) {
app.get(['/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))'],
app.get(['/api/gallery/content/:mediaPath(*\.(jpg|bmp|png|gif|jpeg))'],
AuthenticationMWs.authenticate,
// TODO: authorize path
GalleryMWs.loadImage,
GalleryMWs.loadMedia,
RenderingMWs.renderFile
);
}
private static addGetVideo(app) {
app.get(['/api/gallery/content/:mediaPath(*\.(mp4))'],
AuthenticationMWs.authenticate,
// TODO: authorize path
GalleryMWs.loadMedia,
RenderingMWs.renderFile
);
}
@ -45,27 +57,37 @@ export class GalleryRouter {
AuthenticationMWs.authorise(UserRoles.Guest),
// TODO: authorize path
GalleryMWs.getRandomImage,
GalleryMWs.loadImage,
GalleryMWs.loadMedia,
RenderingMWs.renderFile
);
}
private static addGetImageThumbnail(app) {
app.get('/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))/thumbnail/:size?',
app.get('/api/gallery/content/:mediaPath(*\.(jpg|bmp|png|gif|jpeg))/thumbnail/:size?',
AuthenticationMWs.authenticate,
// TODO: authorize path
GalleryMWs.loadImage,
ThumbnailGeneratorMWs.generateThumbnail,
GalleryMWs.loadMedia,
ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Image),
RenderingMWs.renderFile
);
}
private static addGetVideoThumbnail(app) {
app.get('/api/gallery/content/:mediaPath(*\.(mp4))/thumbnail/:size?',
AuthenticationMWs.authenticate,
// TODO: authorize path
GalleryMWs.loadMedia,
ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Video),
RenderingMWs.renderFile
);
}
private static addGetImageIcon(app) {
app.get('/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))/icon',
app.get('/api/gallery/content/:mediaPath(*\.(jpg|bmp|png|gif|jpeg))/icon',
AuthenticationMWs.authenticate,
// TODO: authorize path
GalleryMWs.loadImage,
ThumbnailGeneratorMWs.generateIcon,
GalleryMWs.loadMedia,
ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Image),
RenderingMWs.renderFile
);
}

View File

@ -1,4 +1,4 @@
import {PhotoDTO} from './PhotoDTO';
import {MediaDTO} from './MediaDTO';
export interface DirectoryDTO {
id: number;
@ -9,12 +9,12 @@ export interface DirectoryDTO {
isPartial?: boolean;
parent: DirectoryDTO;
directories: Array<DirectoryDTO>;
photos: Array<PhotoDTO>;
media: MediaDTO[];
}
export module DirectoryDTO {
export const addReferences = (dir: DirectoryDTO): void => {
dir.photos.forEach((photo: PhotoDTO) => {
dir.media.forEach((photo: MediaDTO) => {
photo.directory = dir;
});

View File

@ -0,0 +1,78 @@
import {DirectoryDTO} from './DirectoryDTO';
import {PhotoDTO} from './PhotoDTO';
import {OrientationTypes} from 'ts-exif-parser';
export interface MediaDTO {
id: number;
name: string;
directory: DirectoryDTO;
metadata: MediaMetadata;
readyThumbnails: Array<number>;
readyIcon: boolean;
}
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;
}
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));
};
export const isSideWay = (media: MediaDTO): boolean => {
if (!(<PhotoDTO>media).metadata.orientation) {
return false;
}
const photo = <PhotoDTO>media;
return photo.metadata.orientation === OrientationTypes.LEFT_TOP ||
photo.metadata.orientation === OrientationTypes.RIGHT_TOP ||
photo.metadata.orientation === OrientationTypes.LEFT_BOTTOM ||
photo.metadata.orientation === OrientationTypes.RIGHT_BOTTOM;
};
export const getRotatedSize = (photo: MediaDTO): MediaDimension => {
if (isSideWay(photo)) {
// noinspection JSSuspiciousNameCombination
return {width: photo.metadata.size.height, height: photo.metadata.size.width};
}
return photo.metadata.size;
};
export const calcRotatedAspectRatio = (photo: MediaDTO): number => {
const size = getRotatedSize(photo);
return size.width / size.height;
};
}

View File

@ -1,8 +1,8 @@
import {DirectoryDTO} from './DirectoryDTO';
import {ImageSize} from './PhotoDTO';
import {OrientationTypes} from 'ts-exif-parser';
import {MediaDTO, MediaMetadata, MediaDimension, PositionMetaData} from './MediaDTO';
export interface PhotoDTO {
export interface PhotoDTO extends MediaDTO {
id: number;
name: string;
directory: DirectoryDTO;
@ -11,21 +11,16 @@ export interface PhotoDTO {
readyIcon: boolean;
}
export interface PhotoMetadata {
export interface PhotoMetadata extends MediaMetadata {
keywords: Array<string>;
cameraData: CameraMetadata;
positionData: PositionMetaData;
orientation: OrientationTypes;
size: ImageSize;
size: MediaDimension;
creationDate: number;
fileSize: number;
}
export interface ImageSize {
width: number;
height: number;
}
export interface CameraMetadata {
ISO?: number;
model?: string;
@ -35,50 +30,3 @@ export interface CameraMetadata {
focalLength?: number;
lens?: string;
}
export interface PositionMetaData {
GPSData?: GPSMetadata;
country?: string;
state?: string;
city?: string;
}
export interface GPSMetadata {
latitude?: number;
longitude?: number;
altitude?: number;
}
export module PhotoDTO {
export const hasPositionData = (photo: PhotoDTO): boolean => {
return !!photo.metadata.positionData &&
!!(photo.metadata.positionData.city ||
photo.metadata.positionData.state ||
photo.metadata.positionData.country ||
(photo.metadata.positionData.GPSData &&
photo.metadata.positionData.GPSData.altitude &&
photo.metadata.positionData.GPSData.latitude &&
photo.metadata.positionData.GPSData.longitude));
};
export const isSideWay = (photo: PhotoDTO): boolean => {
return photo.metadata.orientation === OrientationTypes.LEFT_TOP ||
photo.metadata.orientation === OrientationTypes.RIGHT_TOP ||
photo.metadata.orientation === OrientationTypes.LEFT_BOTTOM ||
photo.metadata.orientation === OrientationTypes.RIGHT_BOTTOM;
};
export const getRotatedSize = (photo: PhotoDTO): ImageSize => {
if (isSideWay(photo)) {
// noinspection JSSuspiciousNameCombination
return {width: photo.metadata.size.height, height: photo.metadata.size.width};
}
return photo.metadata.size;
};
export const calcRotatedAspectRatio = (photo: PhotoDTO): number => {
const size = getRotatedSize(photo);
return size.width / size.height;
};
}

View File

@ -6,6 +6,6 @@ export interface SearchResultDTO {
searchText: string;
searchType: SearchTypes;
directories: Array<DirectoryDTO>;
photos: Array<PhotoDTO>;
media: Array<PhotoDTO>;
resultOverflow: boolean;
}

View File

@ -0,0 +1,9 @@
import {DirectoryDTO} from './DirectoryDTO';
import {MediaDTO, MediaMetadata} from './MediaDTO';
export interface VideoDTO extends MediaDTO {
id: number;
name: string;
directory: DirectoryDTO;
metadata: MediaMetadata;
}

View File

@ -2,7 +2,7 @@ import {Pipe, PipeTransform} from '@angular/core';
import {OrientationTypes} from 'ts-exif-parser';
/**
* This pipe is used to fix thumbnail and photo orientation based on their exif orientation tag
* This pipe is used to fix thumbnail and media orientation based on their exif orientation tag
*/
@Pipe({name: 'fixOrientation'})

View File

@ -1,50 +0,0 @@
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
import {Utils} from '../../../common/Utils';
import {Config} from '../../../common/config/public/Config';
export class IconPhoto {
protected replacementSizeCache: number | boolean = false;
constructor(public photo: PhotoDTO) {
}
iconLoaded() {
this.photo.readyIcon = true;
}
isIconAvailable() {
return this.photo.readyIcon;
}
getIconPath() {
return Utils.concatUrls(Config.Client.urlBase,
'/api/gallery/content/',
this.photo.directory.path, this.photo.directory.name, this.photo.name, 'icon');
}
getPhotoPath() {
return Utils.concatUrls(Config.Client.urlBase,
'/api/gallery/content/',
this.photo.directory.path, this.photo.directory.name, this.photo.name);
}
equals(other: PhotoDTO | IconPhoto): boolean {
// is gridphoto
if (other instanceof IconPhoto) {
return this.photo.directory.path === other.photo.directory.path &&
this.photo.directory.name === other.photo.directory.name && this.photo.name === other.photo.name;
}
// is photo
if (other.directory) {
return this.photo.directory.path === other.directory.path &&
this.photo.directory.name === other.directory.name && this.photo.name === other.name;
}
return false;
}
}

View File

@ -1,20 +1,21 @@
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
import {Utils} from '../../../common/Utils';
import {IconPhoto} from './IconPhoto';
import {MediaIcon} from './MediaIcon';
import {Config} from '../../../common/config/public/Config';
import {MediaDTO} from '../../../common/entities/MediaDTO';
export class Photo extends IconPhoto {
export class Media extends MediaIcon {
constructor(photo: PhotoDTO, public renderWidth: number, public renderHeight: number) {
super(photo);
constructor(media: MediaDTO, public renderWidth: number, public renderHeight: number) {
super(media);
}
thumbnailLoaded() {
if (!this.isThumbnailAvailable()) {
this.photo.readyThumbnails = this.photo.readyThumbnails || [];
this.photo.readyThumbnails.push(this.getThumbnailSize());
this.media.readyThumbnails = this.media.readyThumbnails || [];
this.media.readyThumbnails.push(this.getThumbnailSize());
}
}
@ -29,10 +30,10 @@ export class Photo extends IconPhoto {
this.replacementSizeCache = null;
const size = this.getThumbnailSize();
if (!!this.photo.readyThumbnails) {
for (let i = 0; i < this.photo.readyThumbnails.length; i++) {
if (this.photo.readyThumbnails[i] < size) {
this.replacementSizeCache = this.photo.readyThumbnails[i];
if (!!this.media.readyThumbnails) {
for (let i = 0; i < this.media.readyThumbnails.length; i++) {
if (this.media.readyThumbnails[i] < size) {
this.replacementSizeCache = this.media.readyThumbnails[i];
break;
}
}
@ -46,26 +47,26 @@ export class Photo extends IconPhoto {
}
isThumbnailAvailable() {
return this.photo.readyThumbnails && this.photo.readyThumbnails.indexOf(this.getThumbnailSize()) !== -1;
return this.media.readyThumbnails && this.media.readyThumbnails.indexOf(this.getThumbnailSize()) !== -1;
}
getReplacementThumbnailPath() {
const size = this.getReplacementThumbnailSize();
return Utils.concatUrls(Config.Client.urlBase,
'/api/gallery/content/',
this.photo.directory.path, this.photo.directory.name, this.photo.name, 'thumbnail', size.toString());
this.media.directory.path, this.media.directory.name, this.media.name, 'thumbnail', size.toString());
}
hasPositionData(): boolean {
return PhotoDTO.hasPositionData(this.photo);
return MediaDTO.hasPositionData(this.media);
}
getThumbnailPath() {
const size = this.getThumbnailSize();
return Utils.concatUrls(Config.Client.urlBase,
'/api/gallery/content/',
this.photo.directory.path, this.photo.directory.name, this.photo.name, 'thumbnail', size.toString());
this.media.directory.path, this.media.directory.name, this.media.name, 'thumbnail', size.toString());
}

View File

@ -0,0 +1,51 @@
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
import {Utils} from '../../../common/Utils';
import {Config} from '../../../common/config/public/Config';
import {MediaDTO} from '../../../common/entities/MediaDTO';
export class MediaIcon {
protected replacementSizeCache: number | boolean = false;
constructor(public media: MediaDTO) {
}
iconLoaded() {
this.media.readyIcon = true;
}
isIconAvailable() {
return this.media.readyIcon;
}
getIconPath() {
return Utils.concatUrls(Config.Client.urlBase,
'/api/gallery/content/',
this.media.directory.path, this.media.directory.name, this.media.name, 'icon');
}
getPhotoPath() {
return Utils.concatUrls(Config.Client.urlBase,
'/api/gallery/content/',
this.media.directory.path, this.media.directory.name, this.media.name);
}
equals(other: PhotoDTO | MediaIcon): boolean {
// is gridphoto
if (other instanceof MediaIcon) {
return this.media.directory.path === other.media.directory.path &&
this.media.directory.name === other.media.directory.name && this.media.name === other.media.name;
}
// is media
if (other.directory) {
return this.media.directory.path === other.directory.path &&
this.media.directory.name === other.directory.name && this.media.name === other.name;
}
return false;
}
}

View File

@ -5,6 +5,7 @@ import {Utils} from '../../../common/Utils';
import {Config} from '../../../common/config/public/Config';
import {AutoCompleteItem, SearchTypes} from '../../../common/entities/AutoCompleteItem';
import {SearchResultDTO} from '../../../common/entities/SearchResultDTO';
import {MediaDTO} from '../../../common/entities/MediaDTO';
interface CacheItem<T> {
timestamp: number;
@ -132,24 +133,24 @@ export class GalleryCacheService {
}
/**
* Update photo state at cache too (Eg.: thumbnail rendered)
* @param photo
* Update media state at cache too (Eg.: thumbnail rendered)
* @param media
*/
public photoUpdated(photo: PhotoDTO): void {
public mediaUpdated(media: MediaDTO): void {
if (Config.Client.Other.enableCache === false) {
return;
}
const directoryName = Utils.concatUrls(photo.directory.path, photo.directory.name);
const directoryName = Utils.concatUrls(media.directory.path, media.directory.name);
const value = localStorage.getItem(directoryName);
if (value != null) {
const directory: DirectoryDTO = JSON.parse(value);
directory.photos.forEach((p) => {
if (p.name === photo.name) {
directory.media.forEach((p) => {
if (p.name === media.name) {
// update data
p.metadata = photo.metadata;
p.readyThumbnails = photo.readyThumbnails;
p.metadata = media.metadata;
p.readyThumbnails = media.readyThumbnails;
// save changes
localStorage.setItem(directoryName, JSON.stringify(directory));

View File

@ -55,7 +55,7 @@ a:hover .photo-container {
white-space: normal;
}
/* transforming photo, based on exif orientation*/
/* transforming media, based on exif orientation*/
.photo-orientation-1 {
}
.photo-orientation-2 {

View File

@ -3,11 +3,12 @@ import {DomSanitizer} from '@angular/platform-browser';
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
import {RouterLink} from '@angular/router';
import {Utils} from '../../../../common/Utils';
import {Photo} from '../Photo';
import {Media} from '../Media';
import {Thumbnail, ThumbnailManagerService} from '../thumnailManager.service';
import {PageHelper} from '../../model/page.helper';
import {QueryService} from '../../model/query.service';
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
import {MediaDTO} from '../../../../common/entities/MediaDTO';
@Component({
selector: 'app-gallery-directory',
@ -28,9 +29,9 @@ export class GalleryDirectoryComponent implements OnInit, OnDestroy {
size: number = null;
public get SamplePhoto(): PhotoDTO {
if (this.directory.photos.length > 0) {
return this.directory.photos[0];
public get SamplePhoto(): MediaDTO {
if (this.directory.media.length > 0) {
return this.directory.media[0];
}
return null;
}
@ -57,8 +58,8 @@ export class GalleryDirectoryComponent implements OnInit, OnDestroy {
}
ngOnInit() {
if (this.directory.photos.length > 0) {
this.thumbnail = this.thumbnailService.getThumbnail(new Photo(this.SamplePhoto, this.calcSize(), this.calcSize()));
if (this.directory.media.length > 0) {
this.thumbnail = this.thumbnailService.getThumbnail(new Media(this.SamplePhoto, this.calcSize(), this.calcSize()));
}
}

View File

@ -34,8 +34,8 @@
[directory]="directory"></app-gallery-directory>
</div>
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
[photos]="_galleryService.content.value.directory.photos"></app-gallery-map>
<app-gallery-grid [photos]="_galleryService.content.value.directory.photos"
[photos]="_galleryService.content.value.directory.media"></app-gallery-map>
<app-gallery-grid [photos]="_galleryService.content.value.directory.media"
[lightbox]="lightbox"></app-gallery-grid>
</div>
@ -58,13 +58,13 @@
</ol>
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
[photos]="_galleryService.content.value.searchResult.photos"></app-gallery-map>
[photos]="_galleryService.content.value.searchResult.media"></app-gallery-map>
<div class="directories">
<app-gallery-directory *ngFor="let directory of directories"
[directory]="directory"></app-gallery-directory>
</div>
<app-gallery-grid [photos]="_galleryService.content.value.searchResult.photos"
<app-gallery-grid [photos]="_galleryService.content.value.searchResult.media"
[lightbox]="lightbox"></app-gallery-grid>
</div>

View File

@ -122,16 +122,16 @@ export class GalleryComponent implements OnInit, OnDestroy {
const tmp = <DirectoryDTO | SearchResultDTO>(content.searchResult || content.directory || {
directories: [],
photos: []
media: []
});
this.directories = tmp.directories;
this.sortDirectories();
this.isPhotoWithLocation = false;
for (let i = 0; i < tmp.photos.length; i++) {
if (tmp.photos[i].metadata &&
tmp.photos[i].metadata.positionData &&
tmp.photos[i].metadata.positionData.GPSData &&
tmp.photos[i].metadata.positionData.GPSData.longitude
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
) {
this.isPhotoWithLocation = true;
break;

View File

@ -147,7 +147,7 @@ export class GalleryService {
this.content.next(cw);
// if instant search do not have a result, do not do a search
if (cw.searchResult.photos.length === 0 && cw.searchResult.directories.length === 0) {
if (cw.searchResult.media.length === 0 && cw.searchResult.directories.length === 0) {
if (this.searchId != null) {
clearTimeout(this.searchId);
}

View File

@ -0,0 +1,26 @@
import {Media} from '../Media';
import {MediaDTO} from '../../../../common/entities/MediaDTO';
import {OrientationTypes} from 'ts-exif-parser';
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
export class GridMedia extends Media {
constructor(media: MediaDTO, renderWidth: number, renderHeight: number, public rowId: number) {
super(media, renderWidth, renderHeight);
}
public get Orientation(): OrientationTypes {
return (<PhotoDTO>this.media).metadata.orientation || OrientationTypes.TOP_LEFT;
}
isPhoto(): boolean {
return typeof (<PhotoDTO>this.media).metadata.cameraData !== 'undefined';
}
isVideo(): boolean {
return typeof (<PhotoDTO>this.media).metadata.cameraData === 'undefined';
}
}

View File

@ -1,12 +0,0 @@
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
import {Photo} from '../Photo';
export class GridPhoto extends Photo {
constructor(photo: PhotoDTO, renderWidth: number, renderHeight: number, public rowId: number) {
super(photo, renderWidth, renderHeight);
}
}

View File

@ -1,10 +1,11 @@
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
import {MediaDTO} from '../../../../common/entities/MediaDTO';
export class GridRowBuilder {
private photoRow: PhotoDTO[] = [];
private photoIndex = 0; // index of the last pushed photo to the photoRow
private photoIndex = 0; // index of the last pushed media to the photoRow
constructor(private photos: PhotoDTO[],
@ -52,7 +53,7 @@ export class GridRowBuilder {
while (this.calcRowHeight() < minHeight && this.removePhoto() === true) { // roo too small -> remove images
}
// keep at least one photo int thr row
// keep at least one media int thr row
if (this.photoRow.length <= 0) {
this.addPhoto();
}
@ -61,7 +62,7 @@ export class GridRowBuilder {
public calcRowHeight(): number {
let width = 0;
for (let i = 0; i < this.photoRow.length; i++) {
const size = PhotoDTO.getRotatedSize(this.photoRow[i]);
const size = MediaDTO.getRotatedSize(this.photoRow[i]);
width += (size.width / size.height); // summing up aspect ratios
}
const height = (this.containerWidth - this.photoRow.length * (this.photoMargin * 2) - 1) / width; // cant be equal -> width-1

View File

@ -3,7 +3,7 @@
*ngFor="let gridPhoto of photosToRender"
[routerLink]="[]"
[queryParams]="queryService.getParams(gridPhoto.photo)"
[queryParams]="queryService.getParams(gridPhoto.media)"
[gridPhoto]="gridPhoto"
[style.width.px]="gridPhoto.renderWidth"
[style.height.px]="gridPhoto.renderHeight"

View File

@ -15,7 +15,7 @@ import {
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
import {GridRowBuilder} from './GridRowBuilder';
import {GalleryLightboxComponent} from '../lightbox/lightbox.gallery.component';
import {GridPhoto} from './GridPhoto';
import {GridMedia} from './GridMedia';
import {GalleryPhotoComponent} from './photo/photo.grid.gallery.component';
import {OverlayService} from '../overlay.service';
import {Config} from '../../../../common/config/public/Config';
@ -25,6 +25,7 @@ import {ActivatedRoute, Params, Router} from '@angular/router';
import {QueryService} from '../../model/query.service';
import {GalleryService} from '../gallery.service';
import {SortingMethods} from '../../../../common/entities/SortingMethods';
import {MediaDTO} from '../../../../common/entities/MediaDTO';
@Component({
selector: 'app-gallery-grid',
@ -40,7 +41,7 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
@Input() photos: Array<PhotoDTO>;
@Input() lightbox: GalleryLightboxComponent;
photosToRender: Array<GridPhoto> = [];
photosToRender: Array<GridMedia> = [];
containerWidth = 0;
screenHeight = 0;
@ -189,8 +190,8 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
const imageHeight = rowHeight - (this.IMAGE_MARGIN * 2);
photoRowBuilder.getPhotoRow().forEach((photo) => {
const imageWidth = imageHeight * PhotoDTO.calcRotatedAspectRatio(photo);
this.photosToRender.push(new GridPhoto(photo, imageWidth, imageHeight, this.renderedPhotoIndex));
const imageWidth = imageHeight * MediaDTO.calcRotatedAspectRatio(photo);
this.photosToRender.push(new GridMedia(photo, imageWidth, imageHeight, this.renderedPhotoIndex));
});
this.renderedPhotoIndex += photoRowBuilder.getPhotoRow().length;
@ -248,7 +249,7 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
let lastRowId = null;
for (let i = 0; i < this.photos.length && i < this.photosToRender.length; ++i) {
// If a photo changed the whole row has to be removed
// If a media changed the whole row has to be removed
if (this.photosToRender[i].rowId !== lastRowId) {
lastSameIndex = i;
lastRowId = this.photosToRender[i].rowId;
@ -316,7 +317,7 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
this.renderedPhotoIndex < numberOfPhotos)) {
const ret = this.renderARow();
if (ret === null) {
throw new Error('Grid photos rendering failed');
throw new Error('Grid media rendering failed');
}
renderedContentHeight += ret;
}

View File

@ -1,5 +1,5 @@
<div #photoContainer class="photo-container" (mouseover)="mouseOver()" (mouseout)="mouseOut()">
<img #img [src]="thumbnail.Src | fixOrientation:gridPhoto.photo.metadata.orientation | async"
<img #img [src]="thumbnail.Src | fixOrientation:gridPhoto.media.metadata.orientation | async"
*ngIf="thumbnail.Available">
<app-gallery-grid-photo-loading
@ -14,7 +14,7 @@
[style.margin-top.px]="infoBar.marginTop"
[style.background]="infoBar.background"
[style.width.px]="container.nativeElement.offsetWidth">
<div class="photo-name">{{gridPhoto.photo.name}}</div>
<div class="photo-name">{{gridPhoto.media.name}}</div>
<div class="photo-position" *ngIf="gridPhoto.hasPositionData()">
<span class="oi oi-map-marker"></span>
@ -27,8 +27,8 @@
</ng-template>
</div>
<div class="photo-keywords" *ngIf="gridPhoto.photo.metadata.keywords && gridPhoto.photo.metadata.keywords.length">
<ng-template ngFor let-keyword [ngForOf]="gridPhoto.photo.metadata.keywords" let-last="last">
<div class="photo-keywords" *ngIf="gridPhoto.media.metadata.keywords && gridPhoto.media.metadata.keywords.length">
<ng-template ngFor let-keyword [ngForOf]="gridPhoto.media.metadata.keywords" let-last="last">
<a *ngIf="searchEnabled"
[routerLink]="['/search', keyword, {type: SearchTypes[SearchTypes.keyword]}]">#{{keyword}}</a>
<span *ngIf="!searchEnabled">#{{keyword}}</span>

View File

@ -1,6 +1,6 @@
import {Component, ElementRef, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {Dimension, IRenderable} from '../../../model/IRenderable';
import {GridPhoto} from '../GridPhoto';
import {GridMedia} from '../GridMedia';
import {SearchTypes} from '../../../../../common/entities/AutoCompleteItem';
import {RouterLink} from '@angular/router';
import {Thumbnail, ThumbnailManagerService} from '../../thumnailManager.service';
@ -15,7 +15,7 @@ import {PageHelper} from '../../../model/page.helper';
providers: [RouterLink]
})
export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
@Input() gridPhoto: GridPhoto;
@Input() gridPhoto: GridMedia;
@ViewChild('img') imageRef: ElementRef;
@ViewChild('info') infoDiv: ElementRef;
@ViewChild('photoContainer') container: ElementRef;
@ -78,9 +78,9 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
if (!this.gridPhoto) {
return '';
}
return this.gridPhoto.photo.metadata.positionData.city ||
this.gridPhoto.photo.metadata.positionData.state ||
this.gridPhoto.photo.metadata.positionData.country;
return this.gridPhoto.media.metadata.positionData.city ||
this.gridPhoto.media.metadata.positionData.state ||
this.gridPhoto.media.metadata.positionData.country;
}

View File

@ -11,12 +11,12 @@
</div>
<div class="col-10">
<div class="details-main">
{{photo.name}}
{{media.name}}
</div>
<div class="details-sub row">
<div class="col-4">{{photo.metadata.size.width}} x {{photo.metadata.size.height}}</div>
<div class="col-4">{{media.metadata.size.width}} x {{media.metadata.size.height}}</div>
<div class="col-4">{{calcMpx()}}MP</div>
<div class="col-4" *ngIf="photo.metadata.fileSize">{{calcFileSize()}}</div>
<div class="col-4" *ngIf="media.metadata.fileSize">{{calcFileSize()}}</div>
</div>
</div>
</div>
@ -27,32 +27,32 @@
</div>
<div class="col-10">
<div class="details-main">
{{ photo.metadata.creationDate | date: (isThisYear() ? 'MMMM d': 'longDate')}}
{{ media.metadata.creationDate | date: (isThisYear() ? 'MMMM d': 'longDate')}}
</div>
<div class="details-sub row">
<div class="col-12">{{ photo.metadata.creationDate | date :'EEEE'}}, {{getTime()}}</div>
<div class="col-12">{{ media.metadata.creationDate | date :'EEEE'}}, {{getTime()}}</div>
</div>
</div>
</div>
<div class="row">
<div class="row" *ngIf="CameraData">
<div class="col-2">
<span class="details-icon oi oi-camera-slr"></span>
</div>
<div class="col-10">
<div class="details-main">
{{photo.metadata.cameraData.model || photo.metadata.cameraData.make || "Camera"}}
{{CameraData.model || CameraData.make || "Camera"}}
</div>
<div class="details-sub row">
<div class="col-3" *ngIf="photo.metadata.cameraData.ISO">ISO{{photo.metadata.cameraData.ISO}}</div>
<div class="col-3" *ngIf="photo.metadata.cameraData.fStop">f/{{photo.metadata.cameraData.fStop}}</div>
<div class="col-3" *ngIf="photo.metadata.cameraData.exposure">
{{toFraction(photo.metadata.cameraData.exposure)}}s
<div class="col-3" *ngIf="CameraData.ISO">ISO{{CameraData.ISO}}</div>
<div class="col-3" *ngIf="CameraData.fStop">f/{{CameraData.fStop}}</div>
<div class="col-3" *ngIf="CameraData.exposure">
{{toFraction(CameraData.exposure)}}s
</div>
<div class="col-3" *ngIf="photo.metadata.cameraData.focalLength">
{{photo.metadata.cameraData.focalLength}}mm
<div class="col-3" *ngIf="CameraData.focalLength">
{{CameraData.focalLength}}mm
</div>
<div class="col-12" *ngIf="photo.metadata.cameraData.lens">{{photo.metadata.cameraData.lens}}</div>
<div class="col-12" *ngIf="CameraData.lens">{{CameraData.lens}}</div>
</div>
</div>
</div>
@ -67,8 +67,8 @@
</div>
<div class="details-sub row" *ngIf="hasGPS()">
<div class="col-12">
{{photo.metadata.positionData.GPSData.latitude.toFixed(3)}},
{{photo.metadata.positionData.GPSData.longitude.toFixed(3)}}
{{media.metadata.positionData.GPSData.latitude.toFixed(3)}},
{{media.metadata.positionData.GPSData.longitude.toFixed(3)}}
</div>
</div>
</div>
@ -80,11 +80,11 @@
[zoomControl]="false"
[streetViewControl]="false"
[zoom]="10"
[latitude]="photo.metadata.positionData.GPSData.latitude"
[longitude]="photo.metadata.positionData.GPSData.longitude">
[latitude]="media.metadata.positionData.GPSData.latitude"
[longitude]="media.metadata.positionData.GPSData.longitude">
<agm-marker
[latitude]="photo.metadata.positionData.GPSData.latitude"
[longitude]="photo.metadata.positionData.GPSData.longitude">
[latitude]="media.metadata.positionData.GPSData.latitude"
[longitude]="media.metadata.positionData.GPSData.longitude">
</agm-marker>
</agm-map>
</div>

View File

@ -1,6 +1,7 @@
import {Component, ElementRef, EventEmitter, Input, Output} from '@angular/core';
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
import {CameraMetadata, PhotoDTO} from '../../../../../common/entities/PhotoDTO';
import {Config} from '../../../../../common/config/public/Config';
import {MediaDTO} from '../../../../../common/entities/MediaDTO';
@Component({
selector: 'app-info-panel',
@ -8,7 +9,7 @@ import {Config} from '../../../../../common/config/public/Config';
templateUrl: './info-panel.lightbox.gallery.component.html',
})
export class InfoPanelLightboxComponent {
@Input() photo: PhotoDTO;
@Input() media: MediaDTO;
@Output('onClose') onClose = new EventEmitter();
public mapEnabled = true;
@ -17,13 +18,13 @@ export class InfoPanelLightboxComponent {
}
calcMpx() {
return (this.photo.metadata.size.width * this.photo.metadata.size.height / 1000000).toFixed(2);
return (this.media.metadata.size.width * this.media.metadata.size.height / 1000000).toFixed(2);
}
calcFileSize() {
const postFixes = ['B', 'KB', 'MB', 'GB', 'TB'];
let index = 0;
let size = this.photo.metadata.fileSize;
let size = this.media.metadata.fileSize;
while (size > 1000 && index < postFixes.length - 1) {
size /= 1000;
index++;
@ -33,12 +34,12 @@ export class InfoPanelLightboxComponent {
isThisYear() {
return (new Date()).getFullYear() ===
(new Date(this.photo.metadata.creationDate)).getFullYear();
(new Date(this.media.metadata.creationDate)).getFullYear();
}
getTime() {
const date = new Date(this.photo.metadata.creationDate);
const date = new Date(this.media.metadata.creationDate);
return date.toTimeString().split(' ')[0];
}
@ -51,29 +52,33 @@ export class InfoPanelLightboxComponent {
}
hasPositionData(): boolean {
return PhotoDTO.hasPositionData(this.photo);
return MediaDTO.hasPositionData(this.media);
}
hasGPS() {
return this.photo.metadata.positionData && this.photo.metadata.positionData.GPSData &&
this.photo.metadata.positionData.GPSData.latitude && this.photo.metadata.positionData.GPSData.longitude;
return this.media.metadata.positionData && this.media.metadata.positionData.GPSData &&
this.media.metadata.positionData.GPSData.latitude && this.media.metadata.positionData.GPSData.longitude;
}
getPositionText(): string {
if (!this.photo.metadata.positionData) {
if (!this.media.metadata.positionData) {
return '';
}
let str = this.photo.metadata.positionData.city ||
this.photo.metadata.positionData.state || '';
let str = this.media.metadata.positionData.city ||
this.media.metadata.positionData.state || '';
if (str.length !== 0) {
str += ', ';
}
str += this.photo.metadata.positionData.country || '';
str += this.media.metadata.positionData.country || '';
return str;
}
get CameraData(): CameraMetadata {
return (<PhotoDTO>this.media).metadata.cameraData;
}
close() {
this.onClose.emit();
}

View File

@ -5,8 +5,8 @@
</div>
<div class="lightbox" #lightbox>
<app-gallery-lightbox-photo [gridPhoto]="activePhoto ? activePhoto.gridPhoto : null"
[loadImage]="!animating"
<app-gallery-lightbox-photo [gridMedia]="activePhoto ? activePhoto.gridPhoto : null"
[loadMedia]="!animating"
[windowAspect]="getWindowAspectRatio()"
#photo>
</app-gallery-lightbox-photo>
@ -20,7 +20,7 @@
<a *ngIf="activePhoto"
class="highlight control-button"
[href]="activePhoto.gridPhoto.getPhotoPath()"
[download]="activePhoto.gridPhoto.photo.name">
[download]="activePhoto.gridPhoto.media.name">
<span class="oi oi-data-transfer-download"
title="download" i18n-title></span>
</a>
@ -84,7 +84,7 @@
<app-info-panel *ngIf="activePhoto && infoPanelVisible"
id="info-panel"
[style.width.px]="infoPanelWidth"
[photo]="activePhoto.gridPhoto.photo"
[media]="activePhoto.gridPhoto.photo"
(onClose)="hideInfoPanel()">
</app-info-panel>

View File

@ -22,6 +22,7 @@ import {filter} from 'rxjs/operators';
import {ActivatedRoute, Params, Router} from '@angular/router';
import {PageHelper} from '../../model/page.helper';
import {QueryService} from '../../model/query.service';
import {MediaDTO} from '../../../../common/entities/MediaDTO';
export enum LightboxStates {
Open = 1,
@ -111,15 +112,15 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
}
onNavigateTo(photoName: string) {
if (this.activePhoto && this.activePhoto.gridPhoto.photo.name === photoName) {
if (this.activePhoto && this.activePhoto.gridPhoto.media.name === photoName) {
return;
}
const photo = this.gridPhotoQL.find(i => i.gridPhoto.photo.name === photoName);
const photo = this.gridPhotoQL.find(i => i.gridPhoto.media.name === photoName);
if (!photo) {
return this.delayedPhotoShow = photoName;
}
if (this.status === LightboxStates.Closed) {
this.showLigthbox(photo.gridPhoto.photo);
this.showLigthbox(photo.gridPhoto.media);
} else {
this.showPhoto(this.gridPhotoQL.toArray().indexOf(photo));
}
@ -175,7 +176,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
private navigateToPhoto(photoIndex: number) {
this.router.navigate([],
{queryParams: this.queryService.getParams(this.gridPhotoQL.toArray()[photoIndex].gridPhoto.photo)});
{queryParams: this.queryService.getParams(this.gridPhotoQL.toArray()[photoIndex].gridPhoto.media)});
/*
this.activePhoto = null;
this.changeDetector.detectChanges();
@ -188,7 +189,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
this.updateActivePhoto(photoIndex, resize);
}
public showLigthbox(photo: PhotoDTO) {
public showLigthbox(photo: MediaDTO) {
this.controllersVisible = true;
this.showControls();
this.status = LightboxStates.Open;
@ -200,7 +201,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
const lightboxDimension = selectedPhoto.getDimension();
lightboxDimension.top -= PageHelper.ScrollY;
this.animating = true;
this.animatePhoto(selectedPhoto.getDimension(), this.calcLightBoxPhotoDimension(selectedPhoto.gridPhoto.photo)).onDone(() => {
this.animatePhoto(selectedPhoto.getDimension(), this.calcLightBoxPhotoDimension(selectedPhoto.gridPhoto.media)).onDone(() => {
this.animating = false;
});
this.animateLightbox(
@ -261,7 +262,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
lightboxDimension.top -= PageHelper.ScrollY;
this.blackCanvasOpacity = 0;
this.animatePhoto(this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo), this.activePhoto.getDimension());
this.animatePhoto(this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.media), this.activePhoto.getDimension());
this.animateLightbox(<Dimension>{
top: 0,
left: 0,
@ -325,9 +326,9 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
this.infoPanelVisible = false;
}, 1000);
const starPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
const starPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.media);
this.infoPanelWidth = 0;
const endPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
const endPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.media);
if (_animate) {
this.animatePhoto(starPhotoPos, endPhotoPos);
}
@ -365,9 +366,9 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
showInfoPanel() {
this.infoPanelVisible = true;
const starPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
const starPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.media);
this.infoPanelWidth = 400;
const endPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
const endPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.media);
this.animatePhoto(starPhotoPos, endPhotoPos);
this.animateLightbox(<Dimension>{
top: 0,
@ -419,13 +420,13 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
if (photoIndex < 0 || photoIndex > this.gridPhotoQL.length) {
throw new Error('Can\'t find the photo');
throw new Error('Can\'t find the media');
}
this.activePhotoId = photoIndex;
this.activePhoto = pcList[photoIndex];
if (resize) {
this.animatePhoto(this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo));
this.animatePhoto(this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.media));
}
this.navigation.hasPrev = photoIndex > 0;
this.navigation.hasNext = photoIndex + 1 < pcList.length;
@ -466,17 +467,17 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
this.playBackState = 0;
}
private findPhotoComponent(photo: any): GalleryPhotoComponent {
private findPhotoComponent(media: MediaDTO): GalleryPhotoComponent {
const galleryPhotoComponents = this.gridPhotoQL.toArray();
for (let i = 0; i < galleryPhotoComponents.length; i++) {
if (galleryPhotoComponents[i].gridPhoto.photo === photo) {
if (galleryPhotoComponents[i].gridPhoto.media === media) {
return galleryPhotoComponents[i];
}
}
return null;
}
private calcLightBoxPhotoDimension(photo: PhotoDTO): Dimension {
private calcLightBoxPhotoDimension(photo: MediaDTO): Dimension {
let width = 0;
let height = 0;
const photoAspect = photo.metadata.size.width / photo.metadata.size.height;

View File

@ -1,4 +1,5 @@
.imgContainer img {
.imgContainer img,
.imgContainer video {
position: absolute;
top: 50%;
left: 50%;

View File

@ -4,13 +4,26 @@
[style.height.%]="imageSize.height"
[src]="thumbnailSrc"/>
<img *ngIf="gridPhoto !== null && loadImage && photoSrc"
<img *ngIf="gridMedia !== null && gridMedia.isPhoto() && loadMedia && photoSrc"
[style.width.%]="imageSize.width"
[style.height.%]="imageSize.height"
[src]="photoSrc"
(load)="onImageLoad()"
(error)="onImageError()"/>
<video *ngIf="gridMedia !== null && gridMedia.isVideo() && loadMedia"
[style.width.%]="imageSize.width"
[style.height.%]="imageSize.height"
(loadeddata)="logevent($event)"
(loadedmetadata)="logevent($event)"
(durationchange)="logevent($event)"
(loadstart)="logevent($event)"
autoplay
controls
(error)="onImageError()">
<source [src]="gridMedia.getPhotoPath()" type="video/mp4">
</video>
</div>

View File

@ -1,7 +1,8 @@
import {Component, ElementRef, Input, OnChanges} from '@angular/core';
import {GridPhoto} from '../../grid/GridPhoto';
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',
@ -10,8 +11,8 @@ import {FixOrientationPipe} from '../../FixOrientationPipe';
})
export class GalleryLightboxPhotoComponent implements OnChanges {
@Input() gridPhoto: GridPhoto;
@Input() loadImage = false;
@Input() gridMedia: GridMedia;
@Input() loadMedia = false;
@Input() windowAspect = 1;
prevGirdPhoto = null;
@ -30,28 +31,33 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
this.imageLoaded = false;
this.imageLoadFinished = false;
this.setImageSize();
if (this.prevGirdPhoto !== this.gridPhoto) {
this.prevGirdPhoto = this.gridPhoto;
if (this.prevGirdPhoto !== this.gridMedia) {
this.prevGirdPhoto = this.gridMedia;
this.thumbnailSrc = null;
this.photoSrc = null;
}
if (this.thumbnailSrc == null && this.gridPhoto && this.ThumbnailUrl !== null) {
FixOrientationPipe.transform(this.ThumbnailUrl, this.gridPhoto.photo.metadata.orientation)
if (this.thumbnailSrc == null && this.gridMedia && this.ThumbnailUrl !== null) {
FixOrientationPipe.transform(this.ThumbnailUrl, this.gridMedia.Orientation)
.then((src) => this.thumbnailSrc = src);
}
if (this.photoSrc == null && this.gridPhoto && this.loadImage) {
FixOrientationPipe.transform(this.gridPhoto.getPhotoPath(), this.gridPhoto.photo.metadata.orientation)
.then((src) => this.thumbnailSrc = src);
if (this.photoSrc == null && this.gridMedia && this.loadMedia) {
FixOrientationPipe.transform(this.gridMedia.getPhotoPath(), this.gridMedia.Orientation)
.then((src) => this.photoSrc = src);
}
}
onImageError() {
// TODO:handle error
this.imageLoadFinished = true;
console.error('Error: cannot load image for lightbox url: ' + this.gridPhoto.getPhotoPath());
console.error('Error: cannot load image for lightbox url: ' + this.gridMedia.getPhotoPath());
}
logevent(ev) {
console.log(ev);
this.imageLoadFinished = true;
this.imageLoaded = true;
}
onImageLoad() {
this.imageLoadFinished = true;
@ -59,34 +65,34 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
}
private get ThumbnailUrl(): string {
if (this.gridPhoto.isThumbnailAvailable() === true) {
return this.gridPhoto.getThumbnailPath();
if (this.gridMedia.isThumbnailAvailable() === true) {
return this.gridMedia.getThumbnailPath();
}
if (this.gridPhoto.isReplacementThumbnailAvailable() === true) {
return this.gridPhoto.getReplacementThumbnailPath();
if (this.gridMedia.isReplacementThumbnailAvailable() === true) {
return this.gridMedia.getReplacementThumbnailPath();
}
return null;
}
public get PhotoSrc(): string {
return this.gridPhoto.getPhotoPath();
return this.gridMedia.getPhotoPath();
}
public showThumbnail(): boolean {
return this.gridPhoto &&
return this.gridMedia &&
!this.imageLoaded &&
this.thumbnailSrc !== null &&
(this.gridPhoto.isThumbnailAvailable() || this.gridPhoto.isReplacementThumbnailAvailable());
(this.gridMedia.isThumbnailAvailable() || this.gridMedia.isReplacementThumbnailAvailable());
}
private setImageSize() {
if (!this.gridPhoto) {
if (!this.gridMedia) {
return;
}
const photoAspect = PhotoDTO.calcRotatedAspectRatio(this.gridPhoto.photo);
const photoAspect = MediaDTO.calcRotatedAspectRatio(this.gridMedia.media);
if (photoAspect < this.windowAspect) {
this.imageSize.height = '100';

View File

@ -4,10 +4,11 @@ import {Dimension} from '../../../model/IRenderable';
import {FullScreenService} from '../../fullscreen.service';
import {AgmMap, LatLngBounds, MapsAPILoader} from '@agm/core';
import {IconThumbnail, Thumbnail, ThumbnailManagerService} from '../../thumnailManager.service';
import {IconPhoto} from '../../IconPhoto';
import {Photo} from '../../Photo';
import {MediaIcon} from '../../MediaIcon';
import {Media} from '../../Media';
import {PageHelper} from '../../../model/page.helper';
import {OrientationTypes} from 'ts-exif-parser';
import {MediaDTO} from '../../../../../common/entities/MediaDTO';
@Component({
@ -108,13 +109,13 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
}).map(p => {
let width = 500;
let height = 500;
const rotatedSize = PhotoDTO.getRotatedSize(p);
const rotatedSize = MediaDTO.getRotatedSize(p);
if (rotatedSize.width > rotatedSize.height) {
height = width * (rotatedSize.height / rotatedSize.width);
} else {
width = height * (rotatedSize.width / rotatedSize.height);
}
const iconTh = this.thumbnailService.getIcon(new IconPhoto(p));
const iconTh = this.thumbnailService.getIcon(new MediaIcon(p));
iconTh.Visible = true;
const obj: MapPhoto = {
latitude: p.metadata.positionData.GPSData.latitude,
@ -124,7 +125,7 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
preview: {
width: width,
height: height,
thumbnail: this.thumbnailService.getLazyThumbnail(new Photo(p, width, height))
thumbnail: this.thumbnailService.getLazyThumbnail(new Media(p, width, height))
}
};

View File

@ -8,10 +8,10 @@
</ol>
<div class="right-side">
<div class="photos-count" *ngIf="directory.photos.length > 0 && config.Client.Other.NavBar.showItemCount">
{{directory.photos.length}} <span i18n>items</span>
<div class="photos-count" *ngIf="directory.media.length > 0 && config.Client.Other.NavBar.showItemCount">
{{directory.media.length}} <span i18n>items</span>
</div>
<div class="divider" *ngIf="directory.photos.length > 0 && config.Client.Other.NavBar.showItemCount">&nbsp;</div>
<div class="divider" *ngIf="directory.media.length > 0 && config.Client.Other.NavBar.showItemCount">&nbsp;</div>
<div class="btn-group" dropdown placement="bottom right">
<button id="button-alignment" dropdownToggle type="button"
class="btn btn-default dropdown-toggle" aria-controls="dropdown-alignment"

View File

@ -1,9 +1,10 @@
import {Injectable} from '@angular/core';
import {GalleryCacheService} from './cache.gallery.service';
import {Photo} from './Photo';
import {IconPhoto} from './IconPhoto';
import {Media} from './Media';
import {MediaIcon} from './MediaIcon';
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
import {Config} from '../../../common/config/public/Config';
import {MediaDTO} from '../../../common/entities/MediaDTO';
export enum ThumbnailLoadingPriority {
extraHigh = 4, high = 3, medium = 2, low = 1
@ -35,7 +36,7 @@ export class ThumbnailLoaderService {
const curImg = new Image();
curImg.onload = () => {
task.onLoaded();
this.galleryCacheService.photoUpdated(task.photo);
this.galleryCacheService.mediaUpdated(task.media);
task.taskEntities.forEach((te: ThumbnailTaskEntity) => te.listener.onLoad());
this.taskReady(task);
@ -73,7 +74,7 @@ export class ThumbnailLoaderService {
}
loadIcon(photo: IconPhoto, priority: ThumbnailLoadingPriority, listener: ThumbnailLoadingListener): ThumbnailTaskEntity {
loadIcon(photo: MediaIcon, priority: ThumbnailLoadingPriority, listener: ThumbnailLoadingListener): ThumbnailTaskEntity {
let thTask: ThumbnailTask = null;
// is image already qued?
for (let i = 0; i < this.que.length; i++) {
@ -84,7 +85,7 @@ export class ThumbnailLoaderService {
}
if (thTask == null) {
thTask = {
photo: photo.photo,
media: photo.media,
inProgress: false,
taskEntities: [],
onLoaded: () => {
@ -106,25 +107,25 @@ export class ThumbnailLoaderService {
return thumbnailTaskEntity;
}
loadImage(photo: Photo, priority: ThumbnailLoadingPriority, listener: ThumbnailLoadingListener): ThumbnailTaskEntity {
loadImage(media: Media, priority: ThumbnailLoadingPriority, listener: ThumbnailLoadingListener): ThumbnailTaskEntity {
let thTask: ThumbnailTask = null;
// is image already qued?
for (let i = 0; i < this.que.length; i++) {
if (this.que[i].path === photo.getThumbnailPath()) {
if (this.que[i].path === media.getThumbnailPath()) {
thTask = this.que[i];
break;
}
}
if (thTask == null) {
thTask = {
photo: photo.photo,
media: media.media,
inProgress: false,
taskEntities: [],
onLoaded: () => {
photo.thumbnailLoaded();
media.thumbnailLoaded();
},
path: photo.getThumbnailPath()
path: media.getThumbnailPath()
};
this.que.push(thTask);
}
@ -193,7 +194,7 @@ export interface ThumbnailTaskEntity {
}
interface ThumbnailTask {
photo: PhotoDTO;
media: MediaDTO;
inProgress: boolean;
taskEntities: Array<ThumbnailTaskEntity>;
path: string;

View File

@ -1,7 +1,7 @@
import {Injectable} from '@angular/core';
import {ThumbnailLoaderService, ThumbnailLoadingListener, ThumbnailLoadingPriority, ThumbnailTaskEntity} from './thumnailLoader.service';
import {Photo} from './Photo';
import {IconPhoto} from './IconPhoto';
import {Media} from './Media';
import {MediaIcon} from './MediaIcon';
@Injectable()
@ -11,16 +11,16 @@ export class ThumbnailManagerService {
constructor(private thumbnailLoader: ThumbnailLoaderService) {
}
public getThumbnail(photo: Photo): Thumbnail {
public getThumbnail(photo: Media): Thumbnail {
return new Thumbnail(photo, this.thumbnailLoader);
}
public getLazyThumbnail(photo: Photo): Thumbnail {
public getLazyThumbnail(photo: Media): Thumbnail {
return new Thumbnail(photo, this.thumbnailLoader, false);
}
public getIcon(photo: IconPhoto) {
public getIcon(photo: MediaIcon) {
return new IconThumbnail(photo, this.thumbnailLoader);
}
}
@ -73,19 +73,19 @@ export abstract class ThumbnailBase {
export class IconThumbnail extends ThumbnailBase {
constructor(private photo: IconPhoto, thumbnailService: ThumbnailLoaderService) {
constructor(private media: MediaIcon, thumbnailService: ThumbnailLoaderService) {
super(thumbnailService);
this.src = '';
this.error = false;
if (this.photo.isIconAvailable()) {
this.src = this.photo.getIconPath();
if (this.media.isIconAvailable()) {
this.src = this.media.getIconPath();
this.available = true;
if (this.onLoad) {
this.onLoad();
}
}
if (!this.photo.isIconAvailable()) {
if (!this.media.isIconAvailable()) {
setTimeout(() => {
const listener: ThumbnailLoadingListener = {
@ -93,7 +93,7 @@ export class IconThumbnail extends ThumbnailBase {
this.loading = true;
},
onLoad: () => {// onLoaded
this.src = this.photo.getIconPath();
this.src = this.media.getIconPath();
if (this.onLoad) {
this.onLoad();
}
@ -107,7 +107,7 @@ export class IconThumbnail extends ThumbnailBase {
this.error = true;
}
};
this.thumbnailTask = this.thumbnailService.loadIcon(this.photo, ThumbnailLoadingPriority.high, listener);
this.thumbnailTask = this.thumbnailService.loadIcon(this.media, ThumbnailLoadingPriority.high, listener);
}, 0);
@ -132,16 +132,16 @@ export class IconThumbnail extends ThumbnailBase {
export class Thumbnail extends ThumbnailBase {
constructor(private photo: Photo, thumbnailService: ThumbnailLoaderService, autoLoad: boolean = true) {
constructor(private media: Media, thumbnailService: ThumbnailLoaderService, autoLoad: boolean = true) {
super(thumbnailService);
if (this.photo.isThumbnailAvailable()) {
this.src = this.photo.getThumbnailPath();
if (this.media.isThumbnailAvailable()) {
this.src = this.media.getThumbnailPath();
this.available = true;
if (this.onLoad) {
this.onLoad();
}
} else if (this.photo.isReplacementThumbnailAvailable()) {
this.src = this.photo.getReplacementThumbnailPath();
} else if (this.media.isReplacementThumbnailAvailable()) {
this.src = this.media.getReplacementThumbnailPath();
this.available = true;
}
if (autoLoad) {
@ -154,13 +154,13 @@ export class Thumbnail extends ThumbnailBase {
return;
}
if (value === true) {
if (this.photo.isReplacementThumbnailAvailable()) {
if (this.media.isReplacementThumbnailAvailable()) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.extraHigh;
}
} else {
if (this.photo.isReplacementThumbnailAvailable()) {
if (this.media.isReplacementThumbnailAvailable()) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.low;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
@ -173,13 +173,13 @@ export class Thumbnail extends ThumbnailBase {
return;
}
if (visible === true) {
if (this.photo.isReplacementThumbnailAvailable()) {
if (this.media.isReplacementThumbnailAvailable()) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.high;
}
} else {
if (this.photo.isReplacementThumbnailAvailable()) {
if (this.media.isReplacementThumbnailAvailable()) {
this.thumbnailTask.priority = ThumbnailLoadingPriority.low;
} else {
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
@ -188,14 +188,14 @@ export class Thumbnail extends ThumbnailBase {
}
public load() {
if (!this.photo.isThumbnailAvailable() && this.thumbnailTask == null) {
if (!this.media.isThumbnailAvailable() && this.thumbnailTask == null) {
// setTimeout(() => {
const listener: ThumbnailLoadingListener = {
onStartedLoading: () => { // onLoadStarted
this.loading = true;
},
onLoad: () => {// onLoaded
this.src = this.photo.getThumbnailPath();
this.src = this.media.getThumbnailPath();
if (this.onLoad) {
this.onLoad();
}
@ -209,10 +209,10 @@ export class Thumbnail extends ThumbnailBase {
this.error = true;
}
};
if (this.photo.isReplacementThumbnailAvailable()) {
this.thumbnailTask = this.thumbnailService.loadImage(this.photo, ThumbnailLoadingPriority.medium, listener);
if (this.media.isReplacementThumbnailAvailable()) {
this.thumbnailTask = this.thumbnailService.loadImage(this.media, ThumbnailLoadingPriority.medium, listener);
} else {
this.thumbnailTask = this.thumbnailService.loadImage(this.photo, ThumbnailLoadingPriority.high, listener);
this.thumbnailTask = this.thumbnailService.loadImage(this.media, ThumbnailLoadingPriority.high, listener);
}
// }, 0);
}

View File

@ -1,6 +1,7 @@
import {Injectable} from '@angular/core';
import {ShareService} from '../gallery/share.service';
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
import {MediaDTO} from '../../../common/entities/MediaDTO';
@Injectable()
export class QueryService {
@ -10,10 +11,10 @@ export class QueryService {
constructor(private shareService: ShareService) {
}
getParams(photo?: PhotoDTO): { [key: string]: string } {
getParams(media?: MediaDTO): { [key: string]: string } {
const query = {};
if (photo) {
query[QueryService.PHOTO_PARAM] = photo.name;
if (media) {
query[QueryService.PHOTO_PARAM] = media.name;
}
if (this.shareService.isSharing()) {
query['sk'] = this.shareService.getSharingKey();

View File

@ -21,7 +21,7 @@ export class RandomPhotoSettingsComponent extends SettingsComponent<ClientConfig
_settingsService: RandomPhotoSettingsService,
notification: NotificationService,
i18n: I18n) {
super(i18n('Random Photo'), _authService, _navigation, _settingsService, notification, i18n, s => s.Client.RandomPhoto);
super(i18n('Random Media'), _authService, _navigation, _settingsService, notification, i18n, s => s.Client.RandomPhoto);
}

View File

@ -34,6 +34,7 @@
"cookie-session": "2.0.0-beta.3",
"ejs": "2.6.1",
"express": "4.16.4",
"fluent-ffmpeg": "^2.1.2",
"jimp": "0.5.6",
"locale": "0.1.0",
"reflect-metadata": "0.1.12",
@ -65,6 +66,7 @@
"@types/chai": "4.1.7",
"@types/cookie-session": "2.0.36",
"@types/express": "4.16.0",
"@types/fluent-ffmpeg": "^2.1.8",
"@types/gm": "1.18.1",
"@types/jasmine": "2.8.9",
"@types/node": "10.12.2",

28
sandbox.ts Normal file
View File

@ -0,0 +1,28 @@
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

@ -61,7 +61,7 @@ describe('Typeorm integration', () => {
d.lastModified = Date.now();
d.lastScanned = null;
d.parent = null;
d.photos = [];
d.media = [];
d.directories = [];
return d;
};
@ -98,7 +98,7 @@ describe('Typeorm integration', () => {
const d = new PhotoEntity();
d.name = 'test photo.jpg';
d.name = 'test media.jpg';
d.directory = null;
d.metadata = m;
return d;
@ -142,7 +142,7 @@ describe('Typeorm integration', () => {
expect((await dr.find()).length).to.equal(1);
});
it('should add a photo', async () => {
it('should add a media', async () => {
const conn = await SQLConnection.getConnection();
const pr = conn.getRepository(PhotoEntity);
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
@ -152,7 +152,7 @@ describe('Typeorm integration', () => {
expect((await pr.find()).length).to.equal(1);
});
it('should find a photo', async () => {
it('should find a media', async () => {
const conn = await SQLConnection.getConnection();
const pr = conn.getRepository(PhotoEntity);
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
@ -161,10 +161,10 @@ describe('Typeorm integration', () => {
await pr.save(photo);
let photos = await pr
.createQueryBuilder('photo')
.orderBy('photo.metadata.creationDate', 'ASC')
.where('photo.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + photo.metadata.positionData.city + '%'})
.innerJoinAndSelect('photo.directory', 'directory')
.createQueryBuilder('media')
.orderBy('media.metadata.creationDate', 'ASC')
.where('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + photo.metadata.positionData.city + '%'})
.innerJoinAndSelect('media.directory', 'directory')
.limit(10)
.getMany();
@ -173,7 +173,7 @@ describe('Typeorm integration', () => {
});
it('should not find a photo', async () => {
it('should not find a media', async () => {
const conn = await SQLConnection.getConnection();
const pr = conn.getRepository(PhotoEntity);
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
@ -183,17 +183,17 @@ describe('Typeorm integration', () => {
photo.metadata.positionData = null;
await pr.save(photo);
let photos = await pr
.createQueryBuilder('photo')
.orderBy('photo.metadata.creationDate', 'ASC')
.where('photo.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + city + '%'})
.innerJoinAndSelect('photo.directory', 'directory')
.createQueryBuilder('media')
.orderBy('media.metadata.creationDate', 'ASC')
.where('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + city + '%'})
.innerJoinAndSelect('media.directory', 'directory')
.limit(10)
.getMany();
expect(photos.length).to.equal(0);
});
it('should open and close connection twice with photo added ', async () => {
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();

View File

@ -60,7 +60,7 @@ describe('SearchManager', () => {
const d = new PhotoEntity();
d.name = 'test photo.jpg';
d.name = 'test media.jpg';
d.directory = dir;
d.metadata = m;
return d;
@ -159,7 +159,7 @@ describe('SearchManager', () => {
searchText: 'sw',
searchType: null,
directories: [],
photos: [p, p2],
media: [p, p2],
resultOverflow: false
});
@ -167,7 +167,7 @@ describe('SearchManager', () => {
searchText: 'Tatooine',
searchType: SearchTypes.position,
directories: [],
photos: [p],
media: [p],
resultOverflow: false
});
@ -175,7 +175,7 @@ describe('SearchManager', () => {
searchText: 'ortm',
searchType: SearchTypes.keyword,
directories: [],
photos: [p2],
media: [p2],
resultOverflow: false
});
@ -183,7 +183,7 @@ describe('SearchManager', () => {
searchText: 'ortm',
searchType: SearchTypes.keyword,
directories: [],
photos: [p2],
media: [p2],
resultOverflow: false
});
@ -191,7 +191,7 @@ describe('SearchManager', () => {
searchText: 'wa',
searchType: SearchTypes.keyword,
directories: [dir],
photos: [p, p2],
media: [p, p2],
resultOverflow: false
});
});

View File

@ -3,6 +3,7 @@ import {DiskMangerWorker} from '../../../../../backend/model/threading/DiskMange
import * as path from 'path';
import {Config} from '../../../../../common/config/private/Config';
import {ProjectPath} from '../../../../../backend/ProjectPath';
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
describe('DiskMangerWorker', () => {
@ -10,12 +11,12 @@ describe('DiskMangerWorker', () => {
Config.Server.imagesFolder = path.join(__dirname, '/../../assets');
ProjectPath.ImageFolder = path.join(__dirname, '/../../assets');
const dir = await DiskMangerWorker.scanDirectory('/');
expect(dir.photos.length).to.be.equals(1);
expect(dir.photos[0].name).to.be.equals('test image öüóőúéáű-.,.jpg');
expect(dir.photos[0].metadata.keywords).to.deep.equals(['Berkley', 'USA', 'űáéúőóüö ŰÁÉÚŐÓÜÖ']);
expect(dir.photos[0].metadata.fileSize).to.deep.equals(62392);
expect(dir.photos[0].metadata.size).to.deep.equals({width: 140, height: 93});
expect(dir.photos[0].metadata.cameraData).to.deep.equals({
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(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({
ISO: 3200,
model: 'óüöúőűáé ÓÜÖÚŐŰÁÉ',
make: 'Canon',
@ -25,7 +26,7 @@ describe('DiskMangerWorker', () => {
lens: 'EF-S15-85mm f/3.5-5.6 IS USM'
});
expect(dir.photos[0].metadata.positionData).to.deep.equals({
expect(dir.media[0].metadata.positionData).to.deep.equals({
GPSData: {
latitude: 37.871093333333334,
longitude: -122.25678,
@ -36,7 +37,7 @@ describe('DiskMangerWorker', () => {
city: 'óüöúőűáé ÓÜÖÚŐŰÁ'
});
expect(dir.photos[0].metadata.creationDate).to.be.equals(1434018566000);
expect(dir.media[0].metadata.creationDate).to.be.equals(1434018566000);
});