mirror of
https://github.com/bpatrik/pigallery2.git
synced 2025-04-25 12:24:34 +02:00
implementing basic video support
This commit is contained in:
parent
56e570cf3c
commit
f13f333d49
@ -59,8 +59,8 @@ export class GalleryMWs {
|
|||||||
if (cw.notModified === true) {
|
if (cw.notModified === true) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
const removeDirs = (dir) => {
|
const removeDirs = (dir: DirectoryDTO) => {
|
||||||
dir.photos.forEach((photo: PhotoDTO) => {
|
dir.media.forEach((photo: PhotoDTO) => {
|
||||||
photo.directory = null;
|
photo.directory = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -119,28 +119,28 @@ export class GalleryMWs {
|
|||||||
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'No photo found'));
|
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();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static loadImage(req: Request, res: Response, next: NextFunction) {
|
public static loadMedia(req: Request, res: Response, next: NextFunction) {
|
||||||
if (!(req.params.imagePath)) {
|
if (!(req.params.mediaPath)) {
|
||||||
return next();
|
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
|
// check if thumbnail already exist
|
||||||
if (fs.existsSync(fullImagePath) === false) {
|
if (fs.existsSync(fullMediaPath) === false) {
|
||||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'no such file:' + fullImagePath));
|
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'no such file:' + fullMediaPath));
|
||||||
}
|
}
|
||||||
if (fs.statSync(fullImagePath).isDirectory()) {
|
if (fs.statSync(fullMediaPath).isDirectory()) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
req.resultPipe = fullImagePath;
|
req.resultPipe = fullMediaPath;
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ export class GalleryMWs {
|
|||||||
try {
|
try {
|
||||||
const result = await ObjectManagerRepository.getInstance().SearchManager.search(req.params.text, type);
|
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);
|
req.resultPipe = new ContentWrapper(null, result);
|
||||||
return next();
|
return next();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -181,7 +181,7 @@ export class GalleryMWs {
|
|||||||
try {
|
try {
|
||||||
const result = await ObjectManagerRepository.getInstance().SearchManager.instantSearch(req.params.text);
|
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);
|
req.resultPipe = new ContentWrapper(null, result);
|
||||||
return next();
|
return next();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -12,8 +12,9 @@ import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
|||||||
import {Config} from '../../../common/config/private/Config';
|
import {Config} from '../../../common/config/private/Config';
|
||||||
import {ThumbnailProcessingLib} from '../../../common/config/private/IPrivateConfig';
|
import {ThumbnailProcessingLib} from '../../../common/config/private/IPrivateConfig';
|
||||||
import {ThumbnailTH} from '../../model/threading/ThreadPool';
|
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 {ITaskQue, TaskQue} from '../../model/threading/TaskQue';
|
||||||
|
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
||||||
|
|
||||||
|
|
||||||
export class ThumbnailGeneratorMWs {
|
export class ThumbnailGeneratorMWs {
|
||||||
@ -60,7 +61,7 @@ export class ThumbnailGeneratorMWs {
|
|||||||
ThumbnailGeneratorMWs.addThInfoTODir(<DirectoryDTO>cw.directory);
|
ThumbnailGeneratorMWs.addThInfoTODir(<DirectoryDTO>cw.directory);
|
||||||
}
|
}
|
||||||
if (cw.searchResult) {
|
if (cw.searchResult) {
|
||||||
ThumbnailGeneratorMWs.addThInfoToPhotos(cw.searchResult.photos);
|
ThumbnailGeneratorMWs.addThInfoToPhotos(cw.searchResult.media);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -68,13 +69,14 @@ export class ThumbnailGeneratorMWs {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static generateThumbnail(req: Request, res: Response, next: NextFunction) {
|
public static generateThumbnailFactory(sourceType: ThumbnailSourceType) {
|
||||||
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
if (!req.resultPipe) {
|
if (!req.resultPipe) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// load parameters
|
// load parameters
|
||||||
const imagePath = req.resultPipe;
|
const mediaPath = req.resultPipe;
|
||||||
let size: number = parseInt(req.params.size, 10) || Config.Client.Thumbnail.thumbnailSizes[0];
|
let size: number = parseInt(req.params.size, 10) || Config.Client.Thumbnail.thumbnailSizes[0];
|
||||||
|
|
||||||
// validate size
|
// validate size
|
||||||
@ -82,32 +84,32 @@ export class ThumbnailGeneratorMWs {
|
|||||||
size = Config.Client.Thumbnail.thumbnailSizes[0];
|
size = Config.Client.Thumbnail.thumbnailSizes[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
ThumbnailGeneratorMWs.generateImage(imagePath, size, false, req, res, next);
|
ThumbnailGeneratorMWs.generateImage(mediaPath, size, sourceType, false, req, res, next);
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static generateIcon(req: Request, res: Response, next: NextFunction) {
|
public static generateIconFactory(sourceType: ThumbnailSourceType) {
|
||||||
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
if (!req.resultPipe) {
|
if (!req.resultPipe) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// load parameters
|
// load parameters
|
||||||
const imagePath = req.resultPipe;
|
const mediaPath = req.resultPipe;
|
||||||
const size: number = Config.Client.Thumbnail.iconSize;
|
const size: number = Config.Client.Thumbnail.iconSize;
|
||||||
ThumbnailGeneratorMWs.generateImage(imagePath, size, true, req, res, next);
|
ThumbnailGeneratorMWs.generateImage(mediaPath, size, sourceType, true, req, res, next);
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static addThInfoTODir(directory: DirectoryDTO) {
|
private static addThInfoTODir(directory: DirectoryDTO) {
|
||||||
if (typeof directory.photos === 'undefined') {
|
if (typeof directory.media === 'undefined') {
|
||||||
directory.photos = [];
|
directory.media = [];
|
||||||
}
|
}
|
||||||
if (typeof directory.directories === 'undefined') {
|
if (typeof directory.directories === 'undefined') {
|
||||||
directory.directories = [];
|
directory.directories = [];
|
||||||
}
|
}
|
||||||
ThumbnailGeneratorMWs.addThInfoToPhotos(directory.photos);
|
ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media);
|
||||||
|
|
||||||
for (let i = 0; i < directory.directories.length; i++) {
|
for (let i = 0; i < directory.directories.length; i++) {
|
||||||
ThumbnailGeneratorMWs.addThInfoTODir(directory.directories[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;
|
const thumbnailFolder = ProjectPath.ThumbnailFolder;
|
||||||
for (let i = 0; i < photos.length; i++) {
|
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++) {
|
for (let j = 0; j < Config.Client.Thumbnail.thumbnailSizes.length; j++) {
|
||||||
const size = Config.Client.Thumbnail.thumbnailSizes[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 (fs.existsSync(thPath) === true) {
|
||||||
if (typeof photos[i].readyThumbnails === 'undefined') {
|
if (typeof photos[i].readyThumbnails === 'undefined') {
|
||||||
photos[i].readyThumbnails = [];
|
photos[i].readyThumbnails = [];
|
||||||
@ -130,7 +132,7 @@ export class ThumbnailGeneratorMWs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const iconPath = path.join(thumbnailFolder,
|
const iconPath = path.join(thumbnailFolder,
|
||||||
ThumbnailGeneratorMWs.generateThumbnailName(fullImagePath, Config.Client.Thumbnail.iconSize));
|
ThumbnailGeneratorMWs.generateThumbnailName(fullMediaPath, Config.Client.Thumbnail.iconSize));
|
||||||
if (fs.existsSync(iconPath) === true) {
|
if (fs.existsSync(iconPath) === true) {
|
||||||
photos[i].readyIcon = 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,
|
size: number,
|
||||||
|
sourceType: ThumbnailSourceType,
|
||||||
makeSquare: boolean,
|
makeSquare: boolean,
|
||||||
req: Request, res: Response, next: NextFunction) {
|
req: Request, res: Response, next: NextFunction) {
|
||||||
// generate thumbnail path
|
// 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;
|
req.resultPipe = thPath;
|
||||||
@ -160,7 +163,8 @@ export class ThumbnailGeneratorMWs {
|
|||||||
|
|
||||||
// run on other thread
|
// run on other thread
|
||||||
const input = <RendererInput>{
|
const input = <RendererInput>{
|
||||||
imagePath: imagePath,
|
type: sourceType,
|
||||||
|
mediaPath: mediaPath,
|
||||||
size: size,
|
size: size,
|
||||||
thPath: thPath,
|
thPath: thPath,
|
||||||
makeSquare: makeSquare,
|
makeSquare: makeSquare,
|
||||||
@ -170,12 +174,12 @@ export class ThumbnailGeneratorMWs {
|
|||||||
await this.taskQue.execute(input);
|
await this.taskQue.execute(input);
|
||||||
return next();
|
return next();
|
||||||
} catch (error) {
|
} 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 {
|
private static generateThumbnailName(mediaPath: string, size: number): string {
|
||||||
return crypto.createHash('md5').update(imagePath).digest('hex') + '_' + size + '.jpg';
|
return crypto.createHash('md5').update(mediaPath).digest('hex') + '_' + size + '.jpg';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,9 +204,9 @@ export class ConfigDiagnostics {
|
|||||||
await ConfigDiagnostics.testRandomPhotoConfig(Config.Client.Sharing, Config);
|
await ConfigDiagnostics.testRandomPhotoConfig(Config.Client.Sharing, Config);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
const err: Error = 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());
|
'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;
|
Config.Client.Sharing.enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ export class DiskManager {
|
|||||||
directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName);
|
directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName);
|
||||||
}
|
}
|
||||||
const addDirs = (dir: DirectoryDTO) => {
|
const addDirs = (dir: DirectoryDTO) => {
|
||||||
dir.photos.forEach((ph) => {
|
dir.media.forEach((ph) => {
|
||||||
ph.directory = dir;
|
ph.directory = dir;
|
||||||
});
|
});
|
||||||
dir.directories.forEach((d) => {
|
dir.directories.forEach((d) => {
|
||||||
|
@ -25,6 +25,6 @@ export class GalleryManager implements IGalleryManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRandomPhoto(RandomQuery): Promise<PhotoDTO> {
|
getRandomPhoto(RandomQuery): Promise<PhotoDTO> {
|
||||||
throw new Error('Random photo is not supported without database');
|
throw new Error('Random media is not supported without database');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,30 +53,30 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dir.photos) {
|
if (dir.media) {
|
||||||
for (let i = 0; i < dir.photos.length; i++) {
|
for (let i = 0; i < dir.media.length; i++) {
|
||||||
dir.photos[i].directory = dir;
|
dir.media[i].directory = dir;
|
||||||
dir.photos[i].readyThumbnails = [];
|
dir.media[i].readyThumbnails = [];
|
||||||
dir.photos[i].readyIcon = false;
|
dir.media[i].readyIcon = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dir.directories) {
|
if (dir.directories) {
|
||||||
for (let i = 0; i < dir.directories.length; i++) {
|
for (let i = 0; i < dir.directories.length; i++) {
|
||||||
dir.directories[i].photos = await connection
|
dir.directories[i].media = await connection
|
||||||
.getRepository(PhotoEntity)
|
.getRepository(PhotoEntity)
|
||||||
.createQueryBuilder('photo')
|
.createQueryBuilder('media')
|
||||||
.where('photo.directory = :dir', {
|
.where('media.directory = :dir', {
|
||||||
dir: dir.directories[i].id
|
dir: dir.directories[i].id
|
||||||
})
|
})
|
||||||
.orderBy('photo.metadata.creationDate', 'ASC')
|
.orderBy('media.metadata.creationDate', 'ASC')
|
||||||
.limit(Config.Server.indexing.folderPreviewSize)
|
.limit(Config.Server.indexing.folderPreviewSize)
|
||||||
.getMany();
|
.getMany();
|
||||||
dir.directories[i].isPartial = true;
|
dir.directories[i].isPartial = true;
|
||||||
|
|
||||||
for (let j = 0; j < dir.directories[i].photos.length; j++) {
|
for (let j = 0; j < dir.directories[i].media.length; j++) {
|
||||||
dir.directories[i].photos[j].directory = dir.directories[i];
|
dir.directories[i].media[j].directory = dir.directories[i];
|
||||||
dir.directories[i].photos[j].readyThumbnails = [];
|
dir.directories[i].media[j].readyThumbnails = [];
|
||||||
dir.directories[i].photos[j].readyIcon = false;
|
dir.directories[i].media[j].readyIcon = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
|||||||
const scannedDirectory = await DiskManager.scanDirectory(relativeDirectoryName);
|
const scannedDirectory = await DiskManager.scanDirectory(relativeDirectoryName);
|
||||||
|
|
||||||
// returning with the result
|
// returning with the result
|
||||||
scannedDirectory.photos.forEach(p => p.readyThumbnails = []);
|
scannedDirectory.media.forEach(p => p.readyThumbnails = []);
|
||||||
resolve(scannedDirectory);
|
resolve(scannedDirectory);
|
||||||
|
|
||||||
await this.saveToDB(scannedDirectory);
|
await this.saveToDB(scannedDirectory);
|
||||||
@ -169,18 +169,18 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
|||||||
if (directory != null) { // update existing directory
|
if (directory != null) { // update existing directory
|
||||||
if (!directory.parent || !directory.parent.id) { // set parent if not set yet
|
if (!directory.parent || !directory.parent.id) { // set parent if not set yet
|
||||||
directory.parent = currentDir;
|
directory.parent = currentDir;
|
||||||
delete directory.photos;
|
delete directory.media;
|
||||||
await directoryRepository.save(directory);
|
await directoryRepository.save(directory);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scannedDirectory.directories[i].parent = currentDir;
|
scannedDirectory.directories[i].parent = currentDir;
|
||||||
(<DirectoryEntity>scannedDirectory.directories[i]).lastScanned = null; // new child dir, not fully scanned yet
|
(<DirectoryEntity>scannedDirectory.directories[i]).lastScanned = null; // new child dir, not fully scanned yet
|
||||||
const d = await directoryRepository.save(<DirectoryEntity>scannedDirectory.directories[i]);
|
const d = await directoryRepository.save(<DirectoryEntity>scannedDirectory.directories[i]);
|
||||||
for (let j = 0; j < scannedDirectory.directories[i].photos.length; j++) {
|
for (let j = 0; j < scannedDirectory.directories[i].media.length; j++) {
|
||||||
scannedDirectory.directories[i].photos[j].directory = d;
|
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);
|
await directoryRepository.remove(childDirectories);
|
||||||
|
|
||||||
|
|
||||||
const indexedPhotos = await photosRepository.createQueryBuilder('photo')
|
const indexedPhotos = await photosRepository.createQueryBuilder('media')
|
||||||
.where('photo.directory = :dir', {
|
.where('media.directory = :dir', {
|
||||||
dir: currentDir.id
|
dir: currentDir.id
|
||||||
}).getMany();
|
}).getMany();
|
||||||
|
|
||||||
|
|
||||||
const photosToSave = [];
|
const photosToSave = [];
|
||||||
for (let i = 0; i < scannedDirectory.photos.length; i++) {
|
for (let i = 0; i < scannedDirectory.media.length; i++) {
|
||||||
let photo = null;
|
let photo = null;
|
||||||
for (let j = 0; j < indexedPhotos.length; j++) {
|
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];
|
photo = indexedPhotos[j];
|
||||||
indexedPhotos.splice(j, 1);
|
indexedPhotos.splice(j, 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (photo == null) {
|
if (photo == null) {
|
||||||
scannedDirectory.photos[i].directory = null;
|
scannedDirectory.media[i].directory = null;
|
||||||
photo = Utils.clone(scannedDirectory.photos[i]);
|
photo = Utils.clone(scannedDirectory.media[i]);
|
||||||
scannedDirectory.photos[i].directory = scannedDirectory;
|
scannedDirectory.media[i].directory = scannedDirectory;
|
||||||
photo.directory = currentDir;
|
photo.directory = currentDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (photo.metadata.keywords !== scannedDirectory.photos[i].metadata.keywords ||
|
if (photo.metadata.keywords !== scannedDirectory.media[i].metadata.keywords ||
|
||||||
photo.metadata.cameraData !== scannedDirectory.photos[i].metadata.cameraData ||
|
photo.metadata.cameraData !== (<PhotoDTO>scannedDirectory.media[i]).metadata.cameraData ||
|
||||||
photo.metadata.positionData !== scannedDirectory.photos[i].metadata.positionData ||
|
photo.metadata.positionData !== scannedDirectory.media[i].metadata.positionData ||
|
||||||
photo.metadata.size !== scannedDirectory.photos[i].metadata.size) {
|
photo.metadata.size !== scannedDirectory.media[i].metadata.size) {
|
||||||
|
|
||||||
photo.metadata.keywords = scannedDirectory.photos[i].metadata.keywords;
|
photo.metadata.keywords = scannedDirectory.media[i].metadata.keywords;
|
||||||
photo.metadata.cameraData = scannedDirectory.photos[i].metadata.cameraData;
|
photo.metadata.cameraData = (<PhotoDTO>scannedDirectory.media[i]).metadata.cameraData;
|
||||||
photo.metadata.positionData = scannedDirectory.photos[i].metadata.positionData;
|
photo.metadata.positionData = scannedDirectory.media[i].metadata.positionData;
|
||||||
photo.metadata.size = scannedDirectory.photos[i].metadata.size;
|
photo.metadata.size = scannedDirectory.media[i].metadata.size;
|
||||||
photosToSave.push(photo);
|
photosToSave.push(photo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,8 +233,8 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
|||||||
const connection = await SQLConnection.getConnection();
|
const connection = await SQLConnection.getConnection();
|
||||||
const photosRepository = connection.getRepository(PhotoEntity);
|
const photosRepository = connection.getRepository(PhotoEntity);
|
||||||
|
|
||||||
const query = photosRepository.createQueryBuilder('photo');
|
const query = photosRepository.createQueryBuilder('media');
|
||||||
query.innerJoinAndSelect('photo.directory', 'directory');
|
query.innerJoinAndSelect('media.directory', 'directory');
|
||||||
|
|
||||||
if (queryFilter.directory) {
|
if (queryFilter.directory) {
|
||||||
const directoryName = path.basename(queryFilter.directory);
|
const directoryName = path.basename(queryFilter.directory);
|
||||||
@ -253,31 +253,31 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (queryFilter.fromDate) {
|
if (queryFilter.fromDate) {
|
||||||
query.andWhere('photo.metadata.creationDate >= :fromDate', {
|
query.andWhere('media.metadata.creationDate >= :fromDate', {
|
||||||
fromDate: queryFilter.fromDate.getTime()
|
fromDate: queryFilter.fromDate.getTime()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (queryFilter.toDate) {
|
if (queryFilter.toDate) {
|
||||||
query.andWhere('photo.metadata.creationDate <= :toDate', {
|
query.andWhere('media.metadata.creationDate <= :toDate', {
|
||||||
toDate: queryFilter.toDate.getTime()
|
toDate: queryFilter.toDate.getTime()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (queryFilter.minResolution) {
|
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
|
minRes: queryFilter.minResolution * 1000 * 1000
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queryFilter.maxResolution) {
|
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
|
maxRes: queryFilter.maxResolution * 1000 * 1000
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (queryFilter.orientation === OrientationType.landscape) {
|
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) {
|
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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,9 +30,9 @@ export class SearchManager implements ISearchManager {
|
|||||||
|
|
||||||
|
|
||||||
(await photoRepository
|
(await photoRepository
|
||||||
.createQueryBuilder('photo')
|
.createQueryBuilder('media')
|
||||||
.select('DISTINCT(photo.metadata.keywords)')
|
.select('DISTINCT(media.metadata.keywords)')
|
||||||
.where('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
.where('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||||
.limit(5)
|
.limit(5)
|
||||||
.getRawMany())
|
.getRawMany())
|
||||||
.map(r => <Array<string>>r.metadataKeywords.split(','))
|
.map(r => <Array<string>>r.metadataKeywords.split(','))
|
||||||
@ -43,13 +43,13 @@ export class SearchManager implements ISearchManager {
|
|||||||
|
|
||||||
|
|
||||||
(await photoRepository
|
(await photoRepository
|
||||||
.createQueryBuilder('photo')
|
.createQueryBuilder('media')
|
||||||
.select('photo.metadata.positionData.country as country,' +
|
.select('media.metadata.positionData.country as country,' +
|
||||||
' photo.metadata.positionData.state as state, photo.metadata.positionData.city as city')
|
'mediao.metadata.positionData.state as state, media.metadata.positionData.city as city')
|
||||||
.where('photo.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
.where('media.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('media.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('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||||
.groupBy('photo.metadata.positionData.country, photo.metadata.positionData.state, photo.metadata.positionData.city')
|
.groupBy('media.metadata.positionData.country, media.metadata.positionData.state, media.metadata.positionData.city')
|
||||||
.limit(5)
|
.limit(5)
|
||||||
.getRawMany())
|
.getRawMany())
|
||||||
.filter(pm => !!pm)
|
.filter(pm => !!pm)
|
||||||
@ -60,9 +60,9 @@ export class SearchManager implements ISearchManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
result = result.concat(this.encapsulateAutoComplete((await photoRepository
|
result = result.concat(this.encapsulateAutoComplete((await photoRepository
|
||||||
.createQueryBuilder('photo')
|
.createQueryBuilder('media')
|
||||||
.select('DISTINCT(photo.name)')
|
.select('DISTINCT(media.name)')
|
||||||
.where('photo.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
.where('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||||
.limit(5)
|
.limit(5)
|
||||||
.getRawMany())
|
.getRawMany())
|
||||||
.map(r => r.name), SearchTypes.image));
|
.map(r => r.name), SearchTypes.image));
|
||||||
@ -86,15 +86,15 @@ export class SearchManager implements ISearchManager {
|
|||||||
searchText: text,
|
searchText: text,
|
||||||
searchType: searchType,
|
searchType: searchType,
|
||||||
directories: [],
|
directories: [],
|
||||||
photos: [],
|
media: [],
|
||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const query = connection
|
const query = connection
|
||||||
.getRepository(PhotoEntity)
|
.getRepository(PhotoEntity)
|
||||||
.createQueryBuilder('photo')
|
.createQueryBuilder('media')
|
||||||
.innerJoinAndSelect('photo.directory', 'directory')
|
.innerJoinAndSelect('media.directory', 'directory')
|
||||||
.orderBy('photo.metadata.creationDate', 'ASC');
|
.orderBy('media.metadata.creationDate', 'ASC');
|
||||||
|
|
||||||
|
|
||||||
if (!searchType || searchType === SearchTypes.directory) {
|
if (!searchType || searchType === SearchTypes.directory) {
|
||||||
@ -102,24 +102,24 @@ export class SearchManager implements ISearchManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!searchType || searchType === SearchTypes.image) {
|
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) {
|
if (!searchType || searchType === SearchTypes.position) {
|
||||||
query.orWhere('photo.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
query.orWhere('media.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||||
.orWhere('photo.metadata.positionData.state LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
.orWhere('media.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('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'});
|
||||||
|
|
||||||
}
|
}
|
||||||
if (!searchType || searchType === SearchTypes.keyword) {
|
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)
|
.limit(2001)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
if (result.photos.length > 2000) {
|
if (result.media.length > 2000) {
|
||||||
result.resultOverflow = true;
|
result.resultOverflow = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,20 +144,20 @@ export class SearchManager implements ISearchManager {
|
|||||||
searchText: text,
|
searchText: text,
|
||||||
// searchType:undefined, not adding this
|
// searchType:undefined, not adding this
|
||||||
directories: [],
|
directories: [],
|
||||||
photos: [],
|
media: [],
|
||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
};
|
};
|
||||||
|
|
||||||
result.photos = await connection
|
result.media = await connection
|
||||||
.getRepository(PhotoEntity)
|
.getRepository(PhotoEntity)
|
||||||
.createQueryBuilder('photo')
|
.createQueryBuilder('media')
|
||||||
.orderBy('photo.metadata.creationDate', 'ASC')
|
.orderBy('media.metadata.creationDate', 'ASC')
|
||||||
.where('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
.where('media.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||||
.orWhere('photo.metadata.positionData.country LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
.orWhere('media.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('media.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('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||||
.orWhere('photo.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
.orWhere('media.name LIKE :text COLLATE utf8_general_ci', {text: '%' + text + '%'})
|
||||||
.innerJoinAndSelect('photo.directory', 'directory')
|
.innerJoinAndSelect('media.directory', 'directory')
|
||||||
.limit(10)
|
.limit(10)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
|
@ -15,13 +15,13 @@ export class DirectoryEntity implements DirectoryDTO {
|
|||||||
path: string;
|
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')
|
@Column('bigint')
|
||||||
public lastModified: number;
|
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})
|
@Column({type: 'bigint', nullable: true})
|
||||||
public lastScanned: number;
|
public lastScanned: number;
|
||||||
@ -35,6 +35,6 @@ export class DirectoryEntity implements DirectoryDTO {
|
|||||||
public directories: Array<DirectoryEntity>;
|
public directories: Array<DirectoryEntity>;
|
||||||
|
|
||||||
@OneToMany(type => PhotoEntity, photo => photo.directory)
|
@OneToMany(type => PhotoEntity, photo => photo.directory)
|
||||||
public photos: Array<PhotoEntity>;
|
public media: Array<PhotoEntity>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm';
|
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 {DirectoryEntity} from './DirectoryEntity';
|
||||||
import {OrientationTypes} from 'ts-exif-parser';
|
import {OrientationTypes} from 'ts-exif-parser';
|
||||||
|
import {GPSMetadata, MediaDimension, PositionMetaData} from '../../../../common/entities/MediaDTO';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class CameraMetadataEntity implements CameraMetadata {
|
export class CameraMetadataEntity implements CameraMetadata {
|
||||||
@ -41,7 +42,7 @@ export class GPSMetadataEntity implements GPSMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class ImageSizeEntity implements ImageSize {
|
export class ImageSizeEntity implements MediaDimension {
|
||||||
|
|
||||||
@Column('int')
|
@Column('int')
|
||||||
width: number;
|
width: number;
|
||||||
@ -103,7 +104,7 @@ export class PhotoEntity implements PhotoDTO {
|
|||||||
@Column('text')
|
@Column('text')
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@ManyToOne(type => DirectoryEntity, directory => directory.photos, {onDelete: 'CASCADE'})
|
@ManyToOne(type => DirectoryEntity, directory => directory.media, {onDelete: 'CASCADE'})
|
||||||
directory: DirectoryEntity;
|
directory: DirectoryEntity;
|
||||||
|
|
||||||
@Column(type => PhotoMetadataEntity)
|
@Column(type => PhotoMetadataEntity)
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
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 {Logger} from '../../Logger';
|
||||||
import {IptcParser} from 'ts-node-iptc';
|
import {IptcParser} from 'ts-node-iptc';
|
||||||
import {ExifParserFactory, OrientationTypes} from 'ts-exif-parser';
|
import {ExifParserFactory, OrientationTypes} from 'ts-exif-parser';
|
||||||
|
import * as ffmpeg from 'fluent-ffmpeg';
|
||||||
|
import {FfmpegCommand, FfprobeData} from 'fluent-ffmpeg';
|
||||||
import {ProjectPath} from '../../ProjectPath';
|
import {ProjectPath} from '../../ProjectPath';
|
||||||
import {Config} from '../../../common/config/private/Config';
|
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]';
|
const LOG_TAG = '[DiskManagerTask]';
|
||||||
|
|
||||||
@ -27,6 +31,16 @@ export class DiskMangerWorker {
|
|||||||
return extensions.indexOf(extension) !== -1;
|
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> {
|
public static scanDirectory(relativeDirectoryName: string, maxPhotos: number = null, photosOnly: boolean = false): Promise<DirectoryDTO> {
|
||||||
return new Promise<DirectoryDTO>((resolve, reject) => {
|
return new Promise<DirectoryDTO>((resolve, reject) => {
|
||||||
const directoryName = path.basename(relativeDirectoryName);
|
const directoryName = path.basename(relativeDirectoryName);
|
||||||
@ -41,9 +55,9 @@ export class DiskMangerWorker {
|
|||||||
lastScanned: Date.now(),
|
lastScanned: Date.now(),
|
||||||
directories: [],
|
directories: [],
|
||||||
isPartial: false,
|
isPartial: false,
|
||||||
photos: []
|
media: []
|
||||||
};
|
};
|
||||||
fs.readdir(absoluteDirectoryName, async (err, list) => {
|
fs.readdir(absoluteDirectoryName, async (err, list: string[]) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
@ -60,13 +74,23 @@ export class DiskMangerWorker {
|
|||||||
d.isPartial = true;
|
d.isPartial = true;
|
||||||
directory.directories.push(d);
|
directory.directories.push(d);
|
||||||
} else if (DiskMangerWorker.isImage(fullFilePath)) {
|
} else if (DiskMangerWorker.isImage(fullFilePath)) {
|
||||||
directory.photos.push(<PhotoDTO>{
|
directory.media.push(<PhotoDTO>{
|
||||||
name: file,
|
name: file,
|
||||||
directory: null,
|
directory: null,
|
||||||
metadata: await DiskMangerWorker.loadPhotoMetadata(fullFilePath)
|
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;
|
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> {
|
private static loadPhotoMetadata(fullPath: string): Promise<PhotoMetadata> {
|
||||||
return new Promise<PhotoMetadata>((resolve, reject) => {
|
return new Promise<PhotoMetadata>((resolve, reject) => {
|
||||||
fs.readFile(fullPath, (err, data) => {
|
fs.readFile(fullPath, (err, data) => {
|
||||||
@ -135,15 +197,15 @@ export class DiskMangerWorker {
|
|||||||
|
|
||||||
|
|
||||||
if (exif.imageSize) {
|
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) {
|
} 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 {
|
} else {
|
||||||
metadata.size = <ImageSize> {width: 1, height: 1};
|
metadata.size = <MediaDimension> {width: 1, height: 1};
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Logger.debug(LOG_TAG, 'Error parsing exif', fullPath, 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 {
|
try {
|
||||||
|
@ -1,43 +1,103 @@
|
|||||||
import {Metadata, Sharp} from 'sharp';
|
import {Metadata, Sharp} from 'sharp';
|
||||||
import {Dimensions, State} from 'gm';
|
import {Dimensions, State} from 'gm';
|
||||||
import {Logger} from '../../Logger';
|
import {Logger} from '../../Logger';
|
||||||
|
import {FfmpegCommand, FfprobeData} from 'fluent-ffmpeg';
|
||||||
import {ThumbnailProcessingLib} from '../../../common/config/private/IPrivateConfig';
|
import {ThumbnailProcessingLib} from '../../../common/config/private/IPrivateConfig';
|
||||||
|
|
||||||
export class ThumbnailWorker {
|
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;
|
private static rendererType = null;
|
||||||
|
|
||||||
public static render(input: RendererInput, renderer: ThumbnailProcessingLib): Promise<void> {
|
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) {
|
if (ThumbnailWorker.rendererType !== renderer) {
|
||||||
ThumbnailWorker.renderer = RendererFactory.build(renderer);
|
ThumbnailWorker.imageRenderer = ImageRendererFactory.build(renderer);
|
||||||
ThumbnailWorker.rendererType = 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 {
|
export interface RendererInput {
|
||||||
imagePath: string;
|
type: ThumbnailSourceType;
|
||||||
|
mediaPath: string;
|
||||||
size: number;
|
size: number;
|
||||||
makeSquare: boolean;
|
makeSquare: boolean;
|
||||||
thPath: string;
|
thPath: string;
|
||||||
qualityPriority: boolean;
|
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> {
|
public static build(renderer: ThumbnailProcessingLib): (input: RendererInput) => Promise<void> {
|
||||||
switch (renderer) {
|
switch (renderer) {
|
||||||
case ThumbnailProcessingLib.Jimp:
|
case ThumbnailProcessingLib.Jimp:
|
||||||
return RendererFactory.Jimp();
|
return ImageRendererFactory.Jimp();
|
||||||
case ThumbnailProcessingLib.gm:
|
case ThumbnailProcessingLib.gm:
|
||||||
return RendererFactory.Gm();
|
return ImageRendererFactory.Gm();
|
||||||
case ThumbnailProcessingLib.sharp:
|
case ThumbnailProcessingLib.sharp:
|
||||||
return RendererFactory.Sharp();
|
return ImageRendererFactory.Sharp();
|
||||||
}
|
}
|
||||||
throw new Error('unknown renderer');
|
throw new Error('unknown renderer');
|
||||||
}
|
}
|
||||||
@ -46,8 +106,8 @@ export class RendererFactory {
|
|||||||
const Jimp = require('jimp');
|
const Jimp = require('jimp');
|
||||||
return async (input: RendererInput): Promise<void> => {
|
return async (input: RendererInput): Promise<void> => {
|
||||||
// generate thumbnail
|
// generate thumbnail
|
||||||
Logger.silly('[JimpThRenderer] rendering thumbnail:' + input.imagePath);
|
Logger.silly('[JimpThRenderer] rendering thumbnail:' + input.mediaPath);
|
||||||
const image = await Jimp.read(input.imagePath);
|
const image = await Jimp.read(input.mediaPath);
|
||||||
/**
|
/**
|
||||||
* newWidth * newHeight = size*size
|
* newWidth * newHeight = size*size
|
||||||
* newHeight/newWidth = height/width
|
* newHeight/newWidth = height/width
|
||||||
@ -86,8 +146,8 @@ export class RendererFactory {
|
|||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
return async (input: RendererInput): Promise<void> => {
|
return async (input: RendererInput): Promise<void> => {
|
||||||
|
|
||||||
Logger.silly('[SharpThRenderer] rendering thumbnail:' + input.imagePath);
|
Logger.silly('[SharpThRenderer] rendering thumbnail:' + input.mediaPath);
|
||||||
const image: Sharp = sharp(input.imagePath);
|
const image: Sharp = sharp(input.mediaPath);
|
||||||
const metadata: Metadata = await image.metadata();
|
const metadata: Metadata = await image.metadata();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -124,8 +184,8 @@ export class RendererFactory {
|
|||||||
const gm = require('gm');
|
const gm = require('gm');
|
||||||
return (input: RendererInput): Promise<void> => {
|
return (input: RendererInput): Promise<void> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Logger.silly('[GMThRenderer] rendering thumbnail:' + input.imagePath);
|
Logger.silly('[GMThRenderer] rendering thumbnail:' + input.mediaPath);
|
||||||
let image: State = gm(input.imagePath);
|
let image: State = gm(input.mediaPath);
|
||||||
image.size((err, value: Dimensions) => {
|
image.size((err, value: Dimensions) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
|
@ -3,13 +3,16 @@ import {GalleryMWs} from '../middlewares/GalleryMWs';
|
|||||||
import {RenderingMWs} from '../middlewares/RenderingMWs';
|
import {RenderingMWs} from '../middlewares/RenderingMWs';
|
||||||
import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs';
|
import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs';
|
||||||
import {UserRoles} from '../../common/entities/UserDTO';
|
import {UserRoles} from '../../common/entities/UserDTO';
|
||||||
|
import {ThumbnailSourceType} from '../model/threading/ThumbnailWorker';
|
||||||
|
|
||||||
export class GalleryRouter {
|
export class GalleryRouter {
|
||||||
public static route(app: any) {
|
public static route(app: any) {
|
||||||
|
|
||||||
this.addGetImageIcon(app);
|
this.addGetImageIcon(app);
|
||||||
this.addGetImageThumbnail(app);
|
this.addGetImageThumbnail(app);
|
||||||
|
this.addGetVideoThumbnail(app);
|
||||||
this.addGetImage(app);
|
this.addGetImage(app);
|
||||||
|
this.addGetVideo(app);
|
||||||
this.addRandom(app);
|
this.addRandom(app);
|
||||||
this.addDirectoryList(app);
|
this.addDirectoryList(app);
|
||||||
|
|
||||||
@ -31,10 +34,19 @@ export class GalleryRouter {
|
|||||||
|
|
||||||
|
|
||||||
private static addGetImage(app) {
|
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,
|
AuthenticationMWs.authenticate,
|
||||||
// TODO: authorize path
|
// 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
|
RenderingMWs.renderFile
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -45,27 +57,37 @@ export class GalleryRouter {
|
|||||||
AuthenticationMWs.authorise(UserRoles.Guest),
|
AuthenticationMWs.authorise(UserRoles.Guest),
|
||||||
// TODO: authorize path
|
// TODO: authorize path
|
||||||
GalleryMWs.getRandomImage,
|
GalleryMWs.getRandomImage,
|
||||||
GalleryMWs.loadImage,
|
GalleryMWs.loadMedia,
|
||||||
RenderingMWs.renderFile
|
RenderingMWs.renderFile
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static addGetImageThumbnail(app) {
|
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,
|
AuthenticationMWs.authenticate,
|
||||||
// TODO: authorize path
|
// TODO: authorize path
|
||||||
GalleryMWs.loadImage,
|
GalleryMWs.loadMedia,
|
||||||
ThumbnailGeneratorMWs.generateThumbnail,
|
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
|
RenderingMWs.renderFile
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static addGetImageIcon(app) {
|
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,
|
AuthenticationMWs.authenticate,
|
||||||
// TODO: authorize path
|
// TODO: authorize path
|
||||||
GalleryMWs.loadImage,
|
GalleryMWs.loadMedia,
|
||||||
ThumbnailGeneratorMWs.generateIcon,
|
ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Image),
|
||||||
RenderingMWs.renderFile
|
RenderingMWs.renderFile
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {PhotoDTO} from './PhotoDTO';
|
import {MediaDTO} from './MediaDTO';
|
||||||
|
|
||||||
export interface DirectoryDTO {
|
export interface DirectoryDTO {
|
||||||
id: number;
|
id: number;
|
||||||
@ -9,12 +9,12 @@ export interface DirectoryDTO {
|
|||||||
isPartial?: boolean;
|
isPartial?: boolean;
|
||||||
parent: DirectoryDTO;
|
parent: DirectoryDTO;
|
||||||
directories: Array<DirectoryDTO>;
|
directories: Array<DirectoryDTO>;
|
||||||
photos: Array<PhotoDTO>;
|
media: MediaDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export module DirectoryDTO {
|
export module DirectoryDTO {
|
||||||
export const addReferences = (dir: DirectoryDTO): void => {
|
export const addReferences = (dir: DirectoryDTO): void => {
|
||||||
dir.photos.forEach((photo: PhotoDTO) => {
|
dir.media.forEach((photo: MediaDTO) => {
|
||||||
photo.directory = dir;
|
photo.directory = dir;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
78
common/entities/MediaDTO.ts
Normal file
78
common/entities/MediaDTO.ts
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
import {DirectoryDTO} from './DirectoryDTO';
|
import {DirectoryDTO} from './DirectoryDTO';
|
||||||
import {ImageSize} from './PhotoDTO';
|
|
||||||
import {OrientationTypes} from 'ts-exif-parser';
|
import {OrientationTypes} from 'ts-exif-parser';
|
||||||
|
import {MediaDTO, MediaMetadata, MediaDimension, PositionMetaData} from './MediaDTO';
|
||||||
|
|
||||||
export interface PhotoDTO {
|
export interface PhotoDTO extends MediaDTO {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
directory: DirectoryDTO;
|
directory: DirectoryDTO;
|
||||||
@ -11,21 +11,16 @@ export interface PhotoDTO {
|
|||||||
readyIcon: boolean;
|
readyIcon: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PhotoMetadata {
|
export interface PhotoMetadata extends MediaMetadata {
|
||||||
keywords: Array<string>;
|
keywords: Array<string>;
|
||||||
cameraData: CameraMetadata;
|
cameraData: CameraMetadata;
|
||||||
positionData: PositionMetaData;
|
positionData: PositionMetaData;
|
||||||
orientation: OrientationTypes;
|
orientation: OrientationTypes;
|
||||||
size: ImageSize;
|
size: MediaDimension;
|
||||||
creationDate: number;
|
creationDate: number;
|
||||||
fileSize: number;
|
fileSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ImageSize {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CameraMetadata {
|
export interface CameraMetadata {
|
||||||
ISO?: number;
|
ISO?: number;
|
||||||
model?: string;
|
model?: string;
|
||||||
@ -35,50 +30,3 @@ export interface CameraMetadata {
|
|||||||
focalLength?: number;
|
focalLength?: number;
|
||||||
lens?: string;
|
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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -6,6 +6,6 @@ export interface SearchResultDTO {
|
|||||||
searchText: string;
|
searchText: string;
|
||||||
searchType: SearchTypes;
|
searchType: SearchTypes;
|
||||||
directories: Array<DirectoryDTO>;
|
directories: Array<DirectoryDTO>;
|
||||||
photos: Array<PhotoDTO>;
|
media: Array<PhotoDTO>;
|
||||||
resultOverflow: boolean;
|
resultOverflow: boolean;
|
||||||
}
|
}
|
||||||
|
9
common/entities/VideoDTO.ts
Normal file
9
common/entities/VideoDTO.ts
Normal 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;
|
||||||
|
}
|
@ -2,7 +2,7 @@ import {Pipe, PipeTransform} from '@angular/core';
|
|||||||
import {OrientationTypes} from 'ts-exif-parser';
|
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'})
|
@Pipe({name: 'fixOrientation'})
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +1,21 @@
|
|||||||
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||||
import {Utils} from '../../../common/Utils';
|
import {Utils} from '../../../common/Utils';
|
||||||
import {IconPhoto} from './IconPhoto';
|
import {MediaIcon} from './MediaIcon';
|
||||||
import {Config} from '../../../common/config/public/Config';
|
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) {
|
constructor(media: MediaDTO, public renderWidth: number, public renderHeight: number) {
|
||||||
super(photo);
|
super(media);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
thumbnailLoaded() {
|
thumbnailLoaded() {
|
||||||
if (!this.isThumbnailAvailable()) {
|
if (!this.isThumbnailAvailable()) {
|
||||||
this.photo.readyThumbnails = this.photo.readyThumbnails || [];
|
this.media.readyThumbnails = this.media.readyThumbnails || [];
|
||||||
this.photo.readyThumbnails.push(this.getThumbnailSize());
|
this.media.readyThumbnails.push(this.getThumbnailSize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,10 +30,10 @@ export class Photo extends IconPhoto {
|
|||||||
this.replacementSizeCache = null;
|
this.replacementSizeCache = null;
|
||||||
|
|
||||||
const size = this.getThumbnailSize();
|
const size = this.getThumbnailSize();
|
||||||
if (!!this.photo.readyThumbnails) {
|
if (!!this.media.readyThumbnails) {
|
||||||
for (let i = 0; i < this.photo.readyThumbnails.length; i++) {
|
for (let i = 0; i < this.media.readyThumbnails.length; i++) {
|
||||||
if (this.photo.readyThumbnails[i] < size) {
|
if (this.media.readyThumbnails[i] < size) {
|
||||||
this.replacementSizeCache = this.photo.readyThumbnails[i];
|
this.replacementSizeCache = this.media.readyThumbnails[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,26 +47,26 @@ export class Photo extends IconPhoto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isThumbnailAvailable() {
|
isThumbnailAvailable() {
|
||||||
return this.photo.readyThumbnails && this.photo.readyThumbnails.indexOf(this.getThumbnailSize()) !== -1;
|
return this.media.readyThumbnails && this.media.readyThumbnails.indexOf(this.getThumbnailSize()) !== -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
getReplacementThumbnailPath() {
|
getReplacementThumbnailPath() {
|
||||||
const size = this.getReplacementThumbnailSize();
|
const size = this.getReplacementThumbnailSize();
|
||||||
return Utils.concatUrls(Config.Client.urlBase,
|
return Utils.concatUrls(Config.Client.urlBase,
|
||||||
'/api/gallery/content/',
|
'/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 {
|
hasPositionData(): boolean {
|
||||||
return PhotoDTO.hasPositionData(this.photo);
|
return MediaDTO.hasPositionData(this.media);
|
||||||
}
|
}
|
||||||
|
|
||||||
getThumbnailPath() {
|
getThumbnailPath() {
|
||||||
const size = this.getThumbnailSize();
|
const size = this.getThumbnailSize();
|
||||||
return Utils.concatUrls(Config.Client.urlBase,
|
return Utils.concatUrls(Config.Client.urlBase,
|
||||||
'/api/gallery/content/',
|
'/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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
51
frontend/app/gallery/MediaIcon.ts
Normal file
51
frontend/app/gallery/MediaIcon.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import {Utils} from '../../../common/Utils';
|
|||||||
import {Config} from '../../../common/config/public/Config';
|
import {Config} from '../../../common/config/public/Config';
|
||||||
import {AutoCompleteItem, SearchTypes} from '../../../common/entities/AutoCompleteItem';
|
import {AutoCompleteItem, SearchTypes} from '../../../common/entities/AutoCompleteItem';
|
||||||
import {SearchResultDTO} from '../../../common/entities/SearchResultDTO';
|
import {SearchResultDTO} from '../../../common/entities/SearchResultDTO';
|
||||||
|
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
||||||
|
|
||||||
interface CacheItem<T> {
|
interface CacheItem<T> {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
@ -132,24 +133,24 @@ export class GalleryCacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update photo state at cache too (Eg.: thumbnail rendered)
|
* Update media state at cache too (Eg.: thumbnail rendered)
|
||||||
* @param photo
|
* @param media
|
||||||
*/
|
*/
|
||||||
public photoUpdated(photo: PhotoDTO): void {
|
public mediaUpdated(media: MediaDTO): void {
|
||||||
|
|
||||||
if (Config.Client.Other.enableCache === false) {
|
if (Config.Client.Other.enableCache === false) {
|
||||||
return;
|
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);
|
const value = localStorage.getItem(directoryName);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
const directory: DirectoryDTO = JSON.parse(value);
|
const directory: DirectoryDTO = JSON.parse(value);
|
||||||
directory.photos.forEach((p) => {
|
directory.media.forEach((p) => {
|
||||||
if (p.name === photo.name) {
|
if (p.name === media.name) {
|
||||||
// update data
|
// update data
|
||||||
p.metadata = photo.metadata;
|
p.metadata = media.metadata;
|
||||||
p.readyThumbnails = photo.readyThumbnails;
|
p.readyThumbnails = media.readyThumbnails;
|
||||||
|
|
||||||
// save changes
|
// save changes
|
||||||
localStorage.setItem(directoryName, JSON.stringify(directory));
|
localStorage.setItem(directoryName, JSON.stringify(directory));
|
||||||
|
@ -55,7 +55,7 @@ a:hover .photo-container {
|
|||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* transforming photo, based on exif orientation*/
|
/* transforming media, based on exif orientation*/
|
||||||
.photo-orientation-1 {
|
.photo-orientation-1 {
|
||||||
}
|
}
|
||||||
.photo-orientation-2 {
|
.photo-orientation-2 {
|
||||||
|
@ -3,11 +3,12 @@ import {DomSanitizer} from '@angular/platform-browser';
|
|||||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
import {Utils} from '../../../../common/Utils';
|
import {Utils} from '../../../../common/Utils';
|
||||||
import {Photo} from '../Photo';
|
import {Media} from '../Media';
|
||||||
import {Thumbnail, ThumbnailManagerService} from '../thumnailManager.service';
|
import {Thumbnail, ThumbnailManagerService} from '../thumnailManager.service';
|
||||||
import {PageHelper} from '../../model/page.helper';
|
import {PageHelper} from '../../model/page.helper';
|
||||||
import {QueryService} from '../../model/query.service';
|
import {QueryService} from '../../model/query.service';
|
||||||
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||||
|
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-gallery-directory',
|
selector: 'app-gallery-directory',
|
||||||
@ -28,9 +29,9 @@ export class GalleryDirectoryComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
size: number = null;
|
size: number = null;
|
||||||
|
|
||||||
public get SamplePhoto(): PhotoDTO {
|
public get SamplePhoto(): MediaDTO {
|
||||||
if (this.directory.photos.length > 0) {
|
if (this.directory.media.length > 0) {
|
||||||
return this.directory.photos[0];
|
return this.directory.media[0];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -57,8 +58,8 @@ export class GalleryDirectoryComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.directory.photos.length > 0) {
|
if (this.directory.media.length > 0) {
|
||||||
this.thumbnail = this.thumbnailService.getThumbnail(new Photo(this.SamplePhoto, this.calcSize(), this.calcSize()));
|
this.thumbnail = this.thumbnailService.getThumbnail(new Media(this.SamplePhoto, this.calcSize(), this.calcSize()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +34,8 @@
|
|||||||
[directory]="directory"></app-gallery-directory>
|
[directory]="directory"></app-gallery-directory>
|
||||||
</div>
|
</div>
|
||||||
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
|
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
|
||||||
[photos]="_galleryService.content.value.directory.photos"></app-gallery-map>
|
[photos]="_galleryService.content.value.directory.media"></app-gallery-map>
|
||||||
<app-gallery-grid [photos]="_galleryService.content.value.directory.photos"
|
<app-gallery-grid [photos]="_galleryService.content.value.directory.media"
|
||||||
[lightbox]="lightbox"></app-gallery-grid>
|
[lightbox]="lightbox"></app-gallery-grid>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -58,13 +58,13 @@
|
|||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<app-gallery-map *ngIf="isPhotoWithLocation && mapEnabled"
|
<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">
|
<div class="directories">
|
||||||
<app-gallery-directory *ngFor="let directory of directories"
|
<app-gallery-directory *ngFor="let directory of directories"
|
||||||
[directory]="directory"></app-gallery-directory>
|
[directory]="directory"></app-gallery-directory>
|
||||||
</div>
|
</div>
|
||||||
<app-gallery-grid [photos]="_galleryService.content.value.searchResult.photos"
|
<app-gallery-grid [photos]="_galleryService.content.value.searchResult.media"
|
||||||
[lightbox]="lightbox"></app-gallery-grid>
|
[lightbox]="lightbox"></app-gallery-grid>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -122,16 +122,16 @@ export class GalleryComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const tmp = <DirectoryDTO | SearchResultDTO>(content.searchResult || content.directory || {
|
const tmp = <DirectoryDTO | SearchResultDTO>(content.searchResult || content.directory || {
|
||||||
directories: [],
|
directories: [],
|
||||||
photos: []
|
media: []
|
||||||
});
|
});
|
||||||
this.directories = tmp.directories;
|
this.directories = tmp.directories;
|
||||||
this.sortDirectories();
|
this.sortDirectories();
|
||||||
this.isPhotoWithLocation = false;
|
this.isPhotoWithLocation = false;
|
||||||
for (let i = 0; i < tmp.photos.length; i++) {
|
for (let i = 0; i < tmp.media.length; i++) {
|
||||||
if (tmp.photos[i].metadata &&
|
if (tmp.media[i].metadata &&
|
||||||
tmp.photos[i].metadata.positionData &&
|
tmp.media[i].metadata.positionData &&
|
||||||
tmp.photos[i].metadata.positionData.GPSData &&
|
tmp.media[i].metadata.positionData.GPSData &&
|
||||||
tmp.photos[i].metadata.positionData.GPSData.longitude
|
tmp.media[i].metadata.positionData.GPSData.longitude
|
||||||
) {
|
) {
|
||||||
this.isPhotoWithLocation = true;
|
this.isPhotoWithLocation = true;
|
||||||
break;
|
break;
|
||||||
|
@ -147,7 +147,7 @@ export class GalleryService {
|
|||||||
this.content.next(cw);
|
this.content.next(cw);
|
||||||
|
|
||||||
// if instant search do not have a result, do not do a search
|
// 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) {
|
if (this.searchId != null) {
|
||||||
clearTimeout(this.searchId);
|
clearTimeout(this.searchId);
|
||||||
}
|
}
|
||||||
|
26
frontend/app/gallery/grid/GridMedia.ts
Normal file
26
frontend/app/gallery/grid/GridMedia.ts
Normal 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';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,10 +1,11 @@
|
|||||||
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||||
|
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||||
|
|
||||||
export class GridRowBuilder {
|
export class GridRowBuilder {
|
||||||
|
|
||||||
private photoRow: PhotoDTO[] = [];
|
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[],
|
constructor(private photos: PhotoDTO[],
|
||||||
@ -52,7 +53,7 @@ export class GridRowBuilder {
|
|||||||
while (this.calcRowHeight() < minHeight && this.removePhoto() === true) { // roo too small -> remove images
|
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) {
|
if (this.photoRow.length <= 0) {
|
||||||
this.addPhoto();
|
this.addPhoto();
|
||||||
}
|
}
|
||||||
@ -61,7 +62,7 @@ export class GridRowBuilder {
|
|||||||
public calcRowHeight(): number {
|
public calcRowHeight(): number {
|
||||||
let width = 0;
|
let width = 0;
|
||||||
for (let i = 0; i < this.photoRow.length; i++) {
|
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
|
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
|
const height = (this.containerWidth - this.photoRow.length * (this.photoMargin * 2) - 1) / width; // cant be equal -> width-1
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
*ngFor="let gridPhoto of photosToRender"
|
*ngFor="let gridPhoto of photosToRender"
|
||||||
|
|
||||||
[routerLink]="[]"
|
[routerLink]="[]"
|
||||||
[queryParams]="queryService.getParams(gridPhoto.photo)"
|
[queryParams]="queryService.getParams(gridPhoto.media)"
|
||||||
[gridPhoto]="gridPhoto"
|
[gridPhoto]="gridPhoto"
|
||||||
[style.width.px]="gridPhoto.renderWidth"
|
[style.width.px]="gridPhoto.renderWidth"
|
||||||
[style.height.px]="gridPhoto.renderHeight"
|
[style.height.px]="gridPhoto.renderHeight"
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||||
import {GridRowBuilder} from './GridRowBuilder';
|
import {GridRowBuilder} from './GridRowBuilder';
|
||||||
import {GalleryLightboxComponent} from '../lightbox/lightbox.gallery.component';
|
import {GalleryLightboxComponent} from '../lightbox/lightbox.gallery.component';
|
||||||
import {GridPhoto} from './GridPhoto';
|
import {GridMedia} from './GridMedia';
|
||||||
import {GalleryPhotoComponent} from './photo/photo.grid.gallery.component';
|
import {GalleryPhotoComponent} from './photo/photo.grid.gallery.component';
|
||||||
import {OverlayService} from '../overlay.service';
|
import {OverlayService} from '../overlay.service';
|
||||||
import {Config} from '../../../../common/config/public/Config';
|
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 {QueryService} from '../../model/query.service';
|
||||||
import {GalleryService} from '../gallery.service';
|
import {GalleryService} from '../gallery.service';
|
||||||
import {SortingMethods} from '../../../../common/entities/SortingMethods';
|
import {SortingMethods} from '../../../../common/entities/SortingMethods';
|
||||||
|
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-gallery-grid',
|
selector: 'app-gallery-grid',
|
||||||
@ -40,7 +41,7 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
@Input() photos: Array<PhotoDTO>;
|
@Input() photos: Array<PhotoDTO>;
|
||||||
@Input() lightbox: GalleryLightboxComponent;
|
@Input() lightbox: GalleryLightboxComponent;
|
||||||
|
|
||||||
photosToRender: Array<GridPhoto> = [];
|
photosToRender: Array<GridMedia> = [];
|
||||||
containerWidth = 0;
|
containerWidth = 0;
|
||||||
screenHeight = 0;
|
screenHeight = 0;
|
||||||
|
|
||||||
@ -189,8 +190,8 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
const imageHeight = rowHeight - (this.IMAGE_MARGIN * 2);
|
const imageHeight = rowHeight - (this.IMAGE_MARGIN * 2);
|
||||||
|
|
||||||
photoRowBuilder.getPhotoRow().forEach((photo) => {
|
photoRowBuilder.getPhotoRow().forEach((photo) => {
|
||||||
const imageWidth = imageHeight * PhotoDTO.calcRotatedAspectRatio(photo);
|
const imageWidth = imageHeight * MediaDTO.calcRotatedAspectRatio(photo);
|
||||||
this.photosToRender.push(new GridPhoto(photo, imageWidth, imageHeight, this.renderedPhotoIndex));
|
this.photosToRender.push(new GridMedia(photo, imageWidth, imageHeight, this.renderedPhotoIndex));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.renderedPhotoIndex += photoRowBuilder.getPhotoRow().length;
|
this.renderedPhotoIndex += photoRowBuilder.getPhotoRow().length;
|
||||||
@ -248,7 +249,7 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
let lastRowId = null;
|
let lastRowId = null;
|
||||||
for (let i = 0; i < this.photos.length && i < this.photosToRender.length; ++i) {
|
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) {
|
if (this.photosToRender[i].rowId !== lastRowId) {
|
||||||
lastSameIndex = i;
|
lastSameIndex = i;
|
||||||
lastRowId = this.photosToRender[i].rowId;
|
lastRowId = this.photosToRender[i].rowId;
|
||||||
@ -316,7 +317,7 @@ export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, O
|
|||||||
this.renderedPhotoIndex < numberOfPhotos)) {
|
this.renderedPhotoIndex < numberOfPhotos)) {
|
||||||
const ret = this.renderARow();
|
const ret = this.renderARow();
|
||||||
if (ret === null) {
|
if (ret === null) {
|
||||||
throw new Error('Grid photos rendering failed');
|
throw new Error('Grid media rendering failed');
|
||||||
}
|
}
|
||||||
renderedContentHeight += ret;
|
renderedContentHeight += ret;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div #photoContainer class="photo-container" (mouseover)="mouseOver()" (mouseout)="mouseOut()">
|
<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">
|
*ngIf="thumbnail.Available">
|
||||||
|
|
||||||
<app-gallery-grid-photo-loading
|
<app-gallery-grid-photo-loading
|
||||||
@ -14,7 +14,7 @@
|
|||||||
[style.margin-top.px]="infoBar.marginTop"
|
[style.margin-top.px]="infoBar.marginTop"
|
||||||
[style.background]="infoBar.background"
|
[style.background]="infoBar.background"
|
||||||
[style.width.px]="container.nativeElement.offsetWidth">
|
[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()">
|
<div class="photo-position" *ngIf="gridPhoto.hasPositionData()">
|
||||||
<span class="oi oi-map-marker"></span>
|
<span class="oi oi-map-marker"></span>
|
||||||
@ -27,8 +27,8 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="photo-keywords" *ngIf="gridPhoto.photo.metadata.keywords && gridPhoto.photo.metadata.keywords.length">
|
<div class="photo-keywords" *ngIf="gridPhoto.media.metadata.keywords && gridPhoto.media.metadata.keywords.length">
|
||||||
<ng-template ngFor let-keyword [ngForOf]="gridPhoto.photo.metadata.keywords" let-last="last">
|
<ng-template ngFor let-keyword [ngForOf]="gridPhoto.media.metadata.keywords" let-last="last">
|
||||||
<a *ngIf="searchEnabled"
|
<a *ngIf="searchEnabled"
|
||||||
[routerLink]="['/search', keyword, {type: SearchTypes[SearchTypes.keyword]}]">#{{keyword}}</a>
|
[routerLink]="['/search', keyword, {type: SearchTypes[SearchTypes.keyword]}]">#{{keyword}}</a>
|
||||||
<span *ngIf="!searchEnabled">#{{keyword}}</span>
|
<span *ngIf="!searchEnabled">#{{keyword}}</span>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {Component, ElementRef, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
import {Component, ElementRef, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||||
import {Dimension, IRenderable} from '../../../model/IRenderable';
|
import {Dimension, IRenderable} from '../../../model/IRenderable';
|
||||||
import {GridPhoto} from '../GridPhoto';
|
import {GridMedia} from '../GridMedia';
|
||||||
import {SearchTypes} from '../../../../../common/entities/AutoCompleteItem';
|
import {SearchTypes} from '../../../../../common/entities/AutoCompleteItem';
|
||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
import {Thumbnail, ThumbnailManagerService} from '../../thumnailManager.service';
|
import {Thumbnail, ThumbnailManagerService} from '../../thumnailManager.service';
|
||||||
@ -15,7 +15,7 @@ import {PageHelper} from '../../../model/page.helper';
|
|||||||
providers: [RouterLink]
|
providers: [RouterLink]
|
||||||
})
|
})
|
||||||
export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
|
export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
|
||||||
@Input() gridPhoto: GridPhoto;
|
@Input() gridPhoto: GridMedia;
|
||||||
@ViewChild('img') imageRef: ElementRef;
|
@ViewChild('img') imageRef: ElementRef;
|
||||||
@ViewChild('info') infoDiv: ElementRef;
|
@ViewChild('info') infoDiv: ElementRef;
|
||||||
@ViewChild('photoContainer') container: ElementRef;
|
@ViewChild('photoContainer') container: ElementRef;
|
||||||
@ -78,9 +78,9 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
|
|||||||
if (!this.gridPhoto) {
|
if (!this.gridPhoto) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return this.gridPhoto.photo.metadata.positionData.city ||
|
return this.gridPhoto.media.metadata.positionData.city ||
|
||||||
this.gridPhoto.photo.metadata.positionData.state ||
|
this.gridPhoto.media.metadata.positionData.state ||
|
||||||
this.gridPhoto.photo.metadata.positionData.country;
|
this.gridPhoto.media.metadata.positionData.country;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,12 +11,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-10">
|
<div class="col-10">
|
||||||
<div class="details-main">
|
<div class="details-main">
|
||||||
{{photo.name}}
|
{{media.name}}
|
||||||
</div>
|
</div>
|
||||||
<div class="details-sub row">
|
<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">{{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -27,32 +27,32 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-10">
|
<div class="col-10">
|
||||||
<div class="details-main">
|
<div class="details-main">
|
||||||
{{ photo.metadata.creationDate | date: (isThisYear() ? 'MMMM d': 'longDate')}}
|
{{ media.metadata.creationDate | date: (isThisYear() ? 'MMMM d': 'longDate')}}
|
||||||
</div>
|
</div>
|
||||||
<div class="details-sub row">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row" *ngIf="CameraData">
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<span class="details-icon oi oi-camera-slr"></span>
|
<span class="details-icon oi oi-camera-slr"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-10">
|
<div class="col-10">
|
||||||
<div class="details-main">
|
<div class="details-main">
|
||||||
{{photo.metadata.cameraData.model || photo.metadata.cameraData.make || "Camera"}}
|
{{CameraData.model || CameraData.make || "Camera"}}
|
||||||
</div>
|
</div>
|
||||||
<div class="details-sub row">
|
<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="CameraData.ISO">ISO{{CameraData.ISO}}</div>
|
||||||
<div class="col-3" *ngIf="photo.metadata.cameraData.fStop">f/{{photo.metadata.cameraData.fStop}}</div>
|
<div class="col-3" *ngIf="CameraData.fStop">f/{{CameraData.fStop}}</div>
|
||||||
<div class="col-3" *ngIf="photo.metadata.cameraData.exposure">
|
<div class="col-3" *ngIf="CameraData.exposure">
|
||||||
{{toFraction(photo.metadata.cameraData.exposure)}}s
|
{{toFraction(CameraData.exposure)}}s
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3" *ngIf="photo.metadata.cameraData.focalLength">
|
<div class="col-3" *ngIf="CameraData.focalLength">
|
||||||
{{photo.metadata.cameraData.focalLength}}mm
|
{{CameraData.focalLength}}mm
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -67,8 +67,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="details-sub row" *ngIf="hasGPS()">
|
<div class="details-sub row" *ngIf="hasGPS()">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
{{photo.metadata.positionData.GPSData.latitude.toFixed(3)}},
|
{{media.metadata.positionData.GPSData.latitude.toFixed(3)}},
|
||||||
{{photo.metadata.positionData.GPSData.longitude.toFixed(3)}}
|
{{media.metadata.positionData.GPSData.longitude.toFixed(3)}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -80,11 +80,11 @@
|
|||||||
[zoomControl]="false"
|
[zoomControl]="false"
|
||||||
[streetViewControl]="false"
|
[streetViewControl]="false"
|
||||||
[zoom]="10"
|
[zoom]="10"
|
||||||
[latitude]="photo.metadata.positionData.GPSData.latitude"
|
[latitude]="media.metadata.positionData.GPSData.latitude"
|
||||||
[longitude]="photo.metadata.positionData.GPSData.longitude">
|
[longitude]="media.metadata.positionData.GPSData.longitude">
|
||||||
<agm-marker
|
<agm-marker
|
||||||
[latitude]="photo.metadata.positionData.GPSData.latitude"
|
[latitude]="media.metadata.positionData.GPSData.latitude"
|
||||||
[longitude]="photo.metadata.positionData.GPSData.longitude">
|
[longitude]="media.metadata.positionData.GPSData.longitude">
|
||||||
</agm-marker>
|
</agm-marker>
|
||||||
</agm-map>
|
</agm-map>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {Component, ElementRef, EventEmitter, Input, Output} from '@angular/core';
|
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 {Config} from '../../../../../common/config/public/Config';
|
||||||
|
import {MediaDTO} from '../../../../../common/entities/MediaDTO';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-info-panel',
|
selector: 'app-info-panel',
|
||||||
@ -8,7 +9,7 @@ import {Config} from '../../../../../common/config/public/Config';
|
|||||||
templateUrl: './info-panel.lightbox.gallery.component.html',
|
templateUrl: './info-panel.lightbox.gallery.component.html',
|
||||||
})
|
})
|
||||||
export class InfoPanelLightboxComponent {
|
export class InfoPanelLightboxComponent {
|
||||||
@Input() photo: PhotoDTO;
|
@Input() media: MediaDTO;
|
||||||
@Output('onClose') onClose = new EventEmitter();
|
@Output('onClose') onClose = new EventEmitter();
|
||||||
public mapEnabled = true;
|
public mapEnabled = true;
|
||||||
|
|
||||||
@ -17,13 +18,13 @@ export class InfoPanelLightboxComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
calcMpx() {
|
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() {
|
calcFileSize() {
|
||||||
const postFixes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
const postFixes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let size = this.photo.metadata.fileSize;
|
let size = this.media.metadata.fileSize;
|
||||||
while (size > 1000 && index < postFixes.length - 1) {
|
while (size > 1000 && index < postFixes.length - 1) {
|
||||||
size /= 1000;
|
size /= 1000;
|
||||||
index++;
|
index++;
|
||||||
@ -33,12 +34,12 @@ export class InfoPanelLightboxComponent {
|
|||||||
|
|
||||||
isThisYear() {
|
isThisYear() {
|
||||||
return (new Date()).getFullYear() ===
|
return (new Date()).getFullYear() ===
|
||||||
(new Date(this.photo.metadata.creationDate)).getFullYear();
|
(new Date(this.media.metadata.creationDate)).getFullYear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getTime() {
|
getTime() {
|
||||||
const date = new Date(this.photo.metadata.creationDate);
|
const date = new Date(this.media.metadata.creationDate);
|
||||||
return date.toTimeString().split(' ')[0];
|
return date.toTimeString().split(' ')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,29 +52,33 @@ export class InfoPanelLightboxComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasPositionData(): boolean {
|
hasPositionData(): boolean {
|
||||||
return PhotoDTO.hasPositionData(this.photo);
|
return MediaDTO.hasPositionData(this.media);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasGPS() {
|
hasGPS() {
|
||||||
return this.photo.metadata.positionData && this.photo.metadata.positionData.GPSData &&
|
return this.media.metadata.positionData && this.media.metadata.positionData.GPSData &&
|
||||||
this.photo.metadata.positionData.GPSData.latitude && this.photo.metadata.positionData.GPSData.longitude;
|
this.media.metadata.positionData.GPSData.latitude && this.media.metadata.positionData.GPSData.longitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPositionText(): string {
|
getPositionText(): string {
|
||||||
if (!this.photo.metadata.positionData) {
|
if (!this.media.metadata.positionData) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
let str = this.photo.metadata.positionData.city ||
|
let str = this.media.metadata.positionData.city ||
|
||||||
this.photo.metadata.positionData.state || '';
|
this.media.metadata.positionData.state || '';
|
||||||
|
|
||||||
if (str.length !== 0) {
|
if (str.length !== 0) {
|
||||||
str += ', ';
|
str += ', ';
|
||||||
}
|
}
|
||||||
str += this.photo.metadata.positionData.country || '';
|
str += this.media.metadata.positionData.country || '';
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get CameraData(): CameraMetadata {
|
||||||
|
return (<PhotoDTO>this.media).metadata.cameraData;
|
||||||
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.onClose.emit();
|
this.onClose.emit();
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="lightbox" #lightbox>
|
<div class="lightbox" #lightbox>
|
||||||
<app-gallery-lightbox-photo [gridPhoto]="activePhoto ? activePhoto.gridPhoto : null"
|
<app-gallery-lightbox-photo [gridMedia]="activePhoto ? activePhoto.gridPhoto : null"
|
||||||
[loadImage]="!animating"
|
[loadMedia]="!animating"
|
||||||
[windowAspect]="getWindowAspectRatio()"
|
[windowAspect]="getWindowAspectRatio()"
|
||||||
#photo>
|
#photo>
|
||||||
</app-gallery-lightbox-photo>
|
</app-gallery-lightbox-photo>
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<a *ngIf="activePhoto"
|
<a *ngIf="activePhoto"
|
||||||
class="highlight control-button"
|
class="highlight control-button"
|
||||||
[href]="activePhoto.gridPhoto.getPhotoPath()"
|
[href]="activePhoto.gridPhoto.getPhotoPath()"
|
||||||
[download]="activePhoto.gridPhoto.photo.name">
|
[download]="activePhoto.gridPhoto.media.name">
|
||||||
<span class="oi oi-data-transfer-download"
|
<span class="oi oi-data-transfer-download"
|
||||||
title="download" i18n-title></span>
|
title="download" i18n-title></span>
|
||||||
</a>
|
</a>
|
||||||
@ -84,7 +84,7 @@
|
|||||||
<app-info-panel *ngIf="activePhoto && infoPanelVisible"
|
<app-info-panel *ngIf="activePhoto && infoPanelVisible"
|
||||||
id="info-panel"
|
id="info-panel"
|
||||||
[style.width.px]="infoPanelWidth"
|
[style.width.px]="infoPanelWidth"
|
||||||
[photo]="activePhoto.gridPhoto.photo"
|
[media]="activePhoto.gridPhoto.photo"
|
||||||
(onClose)="hideInfoPanel()">
|
(onClose)="hideInfoPanel()">
|
||||||
|
|
||||||
</app-info-panel>
|
</app-info-panel>
|
||||||
|
@ -22,6 +22,7 @@ import {filter} from 'rxjs/operators';
|
|||||||
import {ActivatedRoute, Params, Router} from '@angular/router';
|
import {ActivatedRoute, Params, Router} from '@angular/router';
|
||||||
import {PageHelper} from '../../model/page.helper';
|
import {PageHelper} from '../../model/page.helper';
|
||||||
import {QueryService} from '../../model/query.service';
|
import {QueryService} from '../../model/query.service';
|
||||||
|
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||||
|
|
||||||
export enum LightboxStates {
|
export enum LightboxStates {
|
||||||
Open = 1,
|
Open = 1,
|
||||||
@ -111,15 +112,15 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onNavigateTo(photoName: string) {
|
onNavigateTo(photoName: string) {
|
||||||
if (this.activePhoto && this.activePhoto.gridPhoto.photo.name === photoName) {
|
if (this.activePhoto && this.activePhoto.gridPhoto.media.name === photoName) {
|
||||||
return;
|
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) {
|
if (!photo) {
|
||||||
return this.delayedPhotoShow = photoName;
|
return this.delayedPhotoShow = photoName;
|
||||||
}
|
}
|
||||||
if (this.status === LightboxStates.Closed) {
|
if (this.status === LightboxStates.Closed) {
|
||||||
this.showLigthbox(photo.gridPhoto.photo);
|
this.showLigthbox(photo.gridPhoto.media);
|
||||||
} else {
|
} else {
|
||||||
this.showPhoto(this.gridPhotoQL.toArray().indexOf(photo));
|
this.showPhoto(this.gridPhotoQL.toArray().indexOf(photo));
|
||||||
}
|
}
|
||||||
@ -175,7 +176,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
private navigateToPhoto(photoIndex: number) {
|
private navigateToPhoto(photoIndex: number) {
|
||||||
this.router.navigate([],
|
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.activePhoto = null;
|
||||||
this.changeDetector.detectChanges();
|
this.changeDetector.detectChanges();
|
||||||
@ -188,7 +189,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
this.updateActivePhoto(photoIndex, resize);
|
this.updateActivePhoto(photoIndex, resize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public showLigthbox(photo: PhotoDTO) {
|
public showLigthbox(photo: MediaDTO) {
|
||||||
this.controllersVisible = true;
|
this.controllersVisible = true;
|
||||||
this.showControls();
|
this.showControls();
|
||||||
this.status = LightboxStates.Open;
|
this.status = LightboxStates.Open;
|
||||||
@ -200,7 +201,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
const lightboxDimension = selectedPhoto.getDimension();
|
const lightboxDimension = selectedPhoto.getDimension();
|
||||||
lightboxDimension.top -= PageHelper.ScrollY;
|
lightboxDimension.top -= PageHelper.ScrollY;
|
||||||
this.animating = true;
|
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.animating = false;
|
||||||
});
|
});
|
||||||
this.animateLightbox(
|
this.animateLightbox(
|
||||||
@ -261,7 +262,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
lightboxDimension.top -= PageHelper.ScrollY;
|
lightboxDimension.top -= PageHelper.ScrollY;
|
||||||
this.blackCanvasOpacity = 0;
|
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>{
|
this.animateLightbox(<Dimension>{
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@ -325,9 +326,9 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
this.infoPanelVisible = false;
|
this.infoPanelVisible = false;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
const starPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
|
const starPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.media);
|
||||||
this.infoPanelWidth = 0;
|
this.infoPanelWidth = 0;
|
||||||
const endPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
|
const endPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.media);
|
||||||
if (_animate) {
|
if (_animate) {
|
||||||
this.animatePhoto(starPhotoPos, endPhotoPos);
|
this.animatePhoto(starPhotoPos, endPhotoPos);
|
||||||
}
|
}
|
||||||
@ -365,9 +366,9 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
showInfoPanel() {
|
showInfoPanel() {
|
||||||
this.infoPanelVisible = true;
|
this.infoPanelVisible = true;
|
||||||
|
|
||||||
const starPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
|
const starPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.media);
|
||||||
this.infoPanelWidth = 400;
|
this.infoPanelWidth = 400;
|
||||||
const endPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
|
const endPhotoPos = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.media);
|
||||||
this.animatePhoto(starPhotoPos, endPhotoPos);
|
this.animatePhoto(starPhotoPos, endPhotoPos);
|
||||||
this.animateLightbox(<Dimension>{
|
this.animateLightbox(<Dimension>{
|
||||||
top: 0,
|
top: 0,
|
||||||
@ -419,13 +420,13 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
|
|
||||||
if (photoIndex < 0 || photoIndex > this.gridPhotoQL.length) {
|
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.activePhotoId = photoIndex;
|
||||||
this.activePhoto = pcList[photoIndex];
|
this.activePhoto = pcList[photoIndex];
|
||||||
|
|
||||||
if (resize) {
|
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.hasPrev = photoIndex > 0;
|
||||||
this.navigation.hasNext = photoIndex + 1 < pcList.length;
|
this.navigation.hasNext = photoIndex + 1 < pcList.length;
|
||||||
@ -466,17 +467,17 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
|
|||||||
this.playBackState = 0;
|
this.playBackState = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private findPhotoComponent(photo: any): GalleryPhotoComponent {
|
private findPhotoComponent(media: MediaDTO): GalleryPhotoComponent {
|
||||||
const galleryPhotoComponents = this.gridPhotoQL.toArray();
|
const galleryPhotoComponents = this.gridPhotoQL.toArray();
|
||||||
for (let i = 0; i < galleryPhotoComponents.length; i++) {
|
for (let i = 0; i < galleryPhotoComponents.length; i++) {
|
||||||
if (galleryPhotoComponents[i].gridPhoto.photo === photo) {
|
if (galleryPhotoComponents[i].gridPhoto.media === media) {
|
||||||
return galleryPhotoComponents[i];
|
return galleryPhotoComponents[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private calcLightBoxPhotoDimension(photo: PhotoDTO): Dimension {
|
private calcLightBoxPhotoDimension(photo: MediaDTO): Dimension {
|
||||||
let width = 0;
|
let width = 0;
|
||||||
let height = 0;
|
let height = 0;
|
||||||
const photoAspect = photo.metadata.size.width / photo.metadata.size.height;
|
const photoAspect = photo.metadata.size.width / photo.metadata.size.height;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
.imgContainer img {
|
.imgContainer img,
|
||||||
|
.imgContainer video {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
@ -4,13 +4,26 @@
|
|||||||
[style.height.%]="imageSize.height"
|
[style.height.%]="imageSize.height"
|
||||||
[src]="thumbnailSrc"/>
|
[src]="thumbnailSrc"/>
|
||||||
|
|
||||||
<img *ngIf="gridPhoto !== null && loadImage && photoSrc"
|
<img *ngIf="gridMedia !== null && gridMedia.isPhoto() && loadMedia && photoSrc"
|
||||||
[style.width.%]="imageSize.width"
|
[style.width.%]="imageSize.width"
|
||||||
[style.height.%]="imageSize.height"
|
[style.height.%]="imageSize.height"
|
||||||
[src]="photoSrc"
|
[src]="photoSrc"
|
||||||
(load)="onImageLoad()"
|
(load)="onImageLoad()"
|
||||||
(error)="onImageError()"/>
|
(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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import {Component, ElementRef, Input, OnChanges} from '@angular/core';
|
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 {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
|
||||||
import {FixOrientationPipe} from '../../FixOrientationPipe';
|
import {FixOrientationPipe} from '../../FixOrientationPipe';
|
||||||
|
import {MediaDTO} from '../../../../../common/entities/MediaDTO';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-gallery-lightbox-photo',
|
selector: 'app-gallery-lightbox-photo',
|
||||||
@ -10,8 +11,8 @@ import {FixOrientationPipe} from '../../FixOrientationPipe';
|
|||||||
})
|
})
|
||||||
export class GalleryLightboxPhotoComponent implements OnChanges {
|
export class GalleryLightboxPhotoComponent implements OnChanges {
|
||||||
|
|
||||||
@Input() gridPhoto: GridPhoto;
|
@Input() gridMedia: GridMedia;
|
||||||
@Input() loadImage = false;
|
@Input() loadMedia = false;
|
||||||
@Input() windowAspect = 1;
|
@Input() windowAspect = 1;
|
||||||
prevGirdPhoto = null;
|
prevGirdPhoto = null;
|
||||||
|
|
||||||
@ -30,28 +31,33 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
|
|||||||
this.imageLoaded = false;
|
this.imageLoaded = false;
|
||||||
this.imageLoadFinished = false;
|
this.imageLoadFinished = false;
|
||||||
this.setImageSize();
|
this.setImageSize();
|
||||||
if (this.prevGirdPhoto !== this.gridPhoto) {
|
if (this.prevGirdPhoto !== this.gridMedia) {
|
||||||
this.prevGirdPhoto = this.gridPhoto;
|
this.prevGirdPhoto = this.gridMedia;
|
||||||
this.thumbnailSrc = null;
|
this.thumbnailSrc = null;
|
||||||
this.photoSrc = null;
|
this.photoSrc = null;
|
||||||
}
|
}
|
||||||
if (this.thumbnailSrc == null && this.gridPhoto && this.ThumbnailUrl !== null) {
|
if (this.thumbnailSrc == null && this.gridMedia && this.ThumbnailUrl !== null) {
|
||||||
FixOrientationPipe.transform(this.ThumbnailUrl, this.gridPhoto.photo.metadata.orientation)
|
FixOrientationPipe.transform(this.ThumbnailUrl, this.gridMedia.Orientation)
|
||||||
.then((src) => this.thumbnailSrc = src);
|
.then((src) => this.thumbnailSrc = src);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.photoSrc == null && this.gridPhoto && this.loadImage) {
|
if (this.photoSrc == null && this.gridMedia && this.loadMedia) {
|
||||||
FixOrientationPipe.transform(this.gridPhoto.getPhotoPath(), this.gridPhoto.photo.metadata.orientation)
|
FixOrientationPipe.transform(this.gridMedia.getPhotoPath(), this.gridMedia.Orientation)
|
||||||
.then((src) => this.thumbnailSrc = src);
|
.then((src) => this.photoSrc = src);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onImageError() {
|
onImageError() {
|
||||||
// TODO:handle error
|
// TODO:handle error
|
||||||
this.imageLoadFinished = true;
|
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() {
|
onImageLoad() {
|
||||||
this.imageLoadFinished = true;
|
this.imageLoadFinished = true;
|
||||||
@ -59,34 +65,34 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private get ThumbnailUrl(): string {
|
private get ThumbnailUrl(): string {
|
||||||
if (this.gridPhoto.isThumbnailAvailable() === true) {
|
if (this.gridMedia.isThumbnailAvailable() === true) {
|
||||||
return this.gridPhoto.getThumbnailPath();
|
return this.gridMedia.getThumbnailPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.gridPhoto.isReplacementThumbnailAvailable() === true) {
|
if (this.gridMedia.isReplacementThumbnailAvailable() === true) {
|
||||||
return this.gridPhoto.getReplacementThumbnailPath();
|
return this.gridMedia.getReplacementThumbnailPath();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get PhotoSrc(): string {
|
public get PhotoSrc(): string {
|
||||||
return this.gridPhoto.getPhotoPath();
|
return this.gridMedia.getPhotoPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
public showThumbnail(): boolean {
|
public showThumbnail(): boolean {
|
||||||
return this.gridPhoto &&
|
return this.gridMedia &&
|
||||||
!this.imageLoaded &&
|
!this.imageLoaded &&
|
||||||
this.thumbnailSrc !== null &&
|
this.thumbnailSrc !== null &&
|
||||||
(this.gridPhoto.isThumbnailAvailable() || this.gridPhoto.isReplacementThumbnailAvailable());
|
(this.gridMedia.isThumbnailAvailable() || this.gridMedia.isReplacementThumbnailAvailable());
|
||||||
}
|
}
|
||||||
|
|
||||||
private setImageSize() {
|
private setImageSize() {
|
||||||
if (!this.gridPhoto) {
|
if (!this.gridMedia) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const photoAspect = PhotoDTO.calcRotatedAspectRatio(this.gridPhoto.photo);
|
const photoAspect = MediaDTO.calcRotatedAspectRatio(this.gridMedia.media);
|
||||||
|
|
||||||
if (photoAspect < this.windowAspect) {
|
if (photoAspect < this.windowAspect) {
|
||||||
this.imageSize.height = '100';
|
this.imageSize.height = '100';
|
||||||
|
@ -4,10 +4,11 @@ import {Dimension} from '../../../model/IRenderable';
|
|||||||
import {FullScreenService} from '../../fullscreen.service';
|
import {FullScreenService} from '../../fullscreen.service';
|
||||||
import {AgmMap, LatLngBounds, MapsAPILoader} from '@agm/core';
|
import {AgmMap, LatLngBounds, MapsAPILoader} from '@agm/core';
|
||||||
import {IconThumbnail, Thumbnail, ThumbnailManagerService} from '../../thumnailManager.service';
|
import {IconThumbnail, Thumbnail, ThumbnailManagerService} from '../../thumnailManager.service';
|
||||||
import {IconPhoto} from '../../IconPhoto';
|
import {MediaIcon} from '../../MediaIcon';
|
||||||
import {Photo} from '../../Photo';
|
import {Media} from '../../Media';
|
||||||
import {PageHelper} from '../../../model/page.helper';
|
import {PageHelper} from '../../../model/page.helper';
|
||||||
import {OrientationTypes} from 'ts-exif-parser';
|
import {OrientationTypes} from 'ts-exif-parser';
|
||||||
|
import {MediaDTO} from '../../../../../common/entities/MediaDTO';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -108,13 +109,13 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
|
|||||||
}).map(p => {
|
}).map(p => {
|
||||||
let width = 500;
|
let width = 500;
|
||||||
let height = 500;
|
let height = 500;
|
||||||
const rotatedSize = PhotoDTO.getRotatedSize(p);
|
const rotatedSize = MediaDTO.getRotatedSize(p);
|
||||||
if (rotatedSize.width > rotatedSize.height) {
|
if (rotatedSize.width > rotatedSize.height) {
|
||||||
height = width * (rotatedSize.height / rotatedSize.width);
|
height = width * (rotatedSize.height / rotatedSize.width);
|
||||||
} else {
|
} else {
|
||||||
width = height * (rotatedSize.width / rotatedSize.height);
|
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;
|
iconTh.Visible = true;
|
||||||
const obj: MapPhoto = {
|
const obj: MapPhoto = {
|
||||||
latitude: p.metadata.positionData.GPSData.latitude,
|
latitude: p.metadata.positionData.GPSData.latitude,
|
||||||
@ -124,7 +125,7 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
|
|||||||
preview: {
|
preview: {
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
thumbnail: this.thumbnailService.getLazyThumbnail(new Photo(p, width, height))
|
thumbnail: this.thumbnailService.getLazyThumbnail(new Media(p, width, height))
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<div class="right-side">
|
<div class="right-side">
|
||||||
<div class="photos-count" *ngIf="directory.photos.length > 0 && config.Client.Other.NavBar.showItemCount">
|
<div class="photos-count" *ngIf="directory.media.length > 0 && config.Client.Other.NavBar.showItemCount">
|
||||||
{{directory.photos.length}} <span i18n>items</span>
|
{{directory.media.length}} <span i18n>items</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider" *ngIf="directory.photos.length > 0 && config.Client.Other.NavBar.showItemCount"> </div>
|
<div class="divider" *ngIf="directory.media.length > 0 && config.Client.Other.NavBar.showItemCount"> </div>
|
||||||
<div class="btn-group" dropdown placement="bottom right">
|
<div class="btn-group" dropdown placement="bottom right">
|
||||||
<button id="button-alignment" dropdownToggle type="button"
|
<button id="button-alignment" dropdownToggle type="button"
|
||||||
class="btn btn-default dropdown-toggle" aria-controls="dropdown-alignment"
|
class="btn btn-default dropdown-toggle" aria-controls="dropdown-alignment"
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {GalleryCacheService} from './cache.gallery.service';
|
import {GalleryCacheService} from './cache.gallery.service';
|
||||||
import {Photo} from './Photo';
|
import {Media} from './Media';
|
||||||
import {IconPhoto} from './IconPhoto';
|
import {MediaIcon} from './MediaIcon';
|
||||||
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||||
import {Config} from '../../../common/config/public/Config';
|
import {Config} from '../../../common/config/public/Config';
|
||||||
|
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
||||||
|
|
||||||
export enum ThumbnailLoadingPriority {
|
export enum ThumbnailLoadingPriority {
|
||||||
extraHigh = 4, high = 3, medium = 2, low = 1
|
extraHigh = 4, high = 3, medium = 2, low = 1
|
||||||
@ -35,7 +36,7 @@ export class ThumbnailLoaderService {
|
|||||||
const curImg = new Image();
|
const curImg = new Image();
|
||||||
curImg.onload = () => {
|
curImg.onload = () => {
|
||||||
task.onLoaded();
|
task.onLoaded();
|
||||||
this.galleryCacheService.photoUpdated(task.photo);
|
this.galleryCacheService.mediaUpdated(task.media);
|
||||||
task.taskEntities.forEach((te: ThumbnailTaskEntity) => te.listener.onLoad());
|
task.taskEntities.forEach((te: ThumbnailTaskEntity) => te.listener.onLoad());
|
||||||
|
|
||||||
this.taskReady(task);
|
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;
|
let thTask: ThumbnailTask = null;
|
||||||
// is image already qued?
|
// is image already qued?
|
||||||
for (let i = 0; i < this.que.length; i++) {
|
for (let i = 0; i < this.que.length; i++) {
|
||||||
@ -84,7 +85,7 @@ export class ThumbnailLoaderService {
|
|||||||
}
|
}
|
||||||
if (thTask == null) {
|
if (thTask == null) {
|
||||||
thTask = {
|
thTask = {
|
||||||
photo: photo.photo,
|
media: photo.media,
|
||||||
inProgress: false,
|
inProgress: false,
|
||||||
taskEntities: [],
|
taskEntities: [],
|
||||||
onLoaded: () => {
|
onLoaded: () => {
|
||||||
@ -106,25 +107,25 @@ export class ThumbnailLoaderService {
|
|||||||
return thumbnailTaskEntity;
|
return thumbnailTaskEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadImage(photo: Photo, priority: ThumbnailLoadingPriority, listener: ThumbnailLoadingListener): ThumbnailTaskEntity {
|
loadImage(media: Media, priority: ThumbnailLoadingPriority, listener: ThumbnailLoadingListener): ThumbnailTaskEntity {
|
||||||
|
|
||||||
let thTask: ThumbnailTask = null;
|
let thTask: ThumbnailTask = null;
|
||||||
// is image already qued?
|
// is image already qued?
|
||||||
for (let i = 0; i < this.que.length; i++) {
|
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];
|
thTask = this.que[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (thTask == null) {
|
if (thTask == null) {
|
||||||
thTask = {
|
thTask = {
|
||||||
photo: photo.photo,
|
media: media.media,
|
||||||
inProgress: false,
|
inProgress: false,
|
||||||
taskEntities: [],
|
taskEntities: [],
|
||||||
onLoaded: () => {
|
onLoaded: () => {
|
||||||
photo.thumbnailLoaded();
|
media.thumbnailLoaded();
|
||||||
},
|
},
|
||||||
path: photo.getThumbnailPath()
|
path: media.getThumbnailPath()
|
||||||
};
|
};
|
||||||
this.que.push(thTask);
|
this.que.push(thTask);
|
||||||
}
|
}
|
||||||
@ -193,7 +194,7 @@ export interface ThumbnailTaskEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ThumbnailTask {
|
interface ThumbnailTask {
|
||||||
photo: PhotoDTO;
|
media: MediaDTO;
|
||||||
inProgress: boolean;
|
inProgress: boolean;
|
||||||
taskEntities: Array<ThumbnailTaskEntity>;
|
taskEntities: Array<ThumbnailTaskEntity>;
|
||||||
path: string;
|
path: string;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {ThumbnailLoaderService, ThumbnailLoadingListener, ThumbnailLoadingPriority, ThumbnailTaskEntity} from './thumnailLoader.service';
|
import {ThumbnailLoaderService, ThumbnailLoadingListener, ThumbnailLoadingPriority, ThumbnailTaskEntity} from './thumnailLoader.service';
|
||||||
import {Photo} from './Photo';
|
import {Media} from './Media';
|
||||||
import {IconPhoto} from './IconPhoto';
|
import {MediaIcon} from './MediaIcon';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -11,16 +11,16 @@ export class ThumbnailManagerService {
|
|||||||
constructor(private thumbnailLoader: ThumbnailLoaderService) {
|
constructor(private thumbnailLoader: ThumbnailLoaderService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getThumbnail(photo: Photo): Thumbnail {
|
public getThumbnail(photo: Media): Thumbnail {
|
||||||
return new Thumbnail(photo, this.thumbnailLoader);
|
return new Thumbnail(photo, this.thumbnailLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLazyThumbnail(photo: Photo): Thumbnail {
|
public getLazyThumbnail(photo: Media): Thumbnail {
|
||||||
return new Thumbnail(photo, this.thumbnailLoader, false);
|
return new Thumbnail(photo, this.thumbnailLoader, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public getIcon(photo: IconPhoto) {
|
public getIcon(photo: MediaIcon) {
|
||||||
return new IconThumbnail(photo, this.thumbnailLoader);
|
return new IconThumbnail(photo, this.thumbnailLoader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,19 +73,19 @@ export abstract class ThumbnailBase {
|
|||||||
|
|
||||||
export class IconThumbnail extends ThumbnailBase {
|
export class IconThumbnail extends ThumbnailBase {
|
||||||
|
|
||||||
constructor(private photo: IconPhoto, thumbnailService: ThumbnailLoaderService) {
|
constructor(private media: MediaIcon, thumbnailService: ThumbnailLoaderService) {
|
||||||
super(thumbnailService);
|
super(thumbnailService);
|
||||||
this.src = '';
|
this.src = '';
|
||||||
this.error = false;
|
this.error = false;
|
||||||
if (this.photo.isIconAvailable()) {
|
if (this.media.isIconAvailable()) {
|
||||||
this.src = this.photo.getIconPath();
|
this.src = this.media.getIconPath();
|
||||||
this.available = true;
|
this.available = true;
|
||||||
if (this.onLoad) {
|
if (this.onLoad) {
|
||||||
this.onLoad();
|
this.onLoad();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.photo.isIconAvailable()) {
|
if (!this.media.isIconAvailable()) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
||||||
const listener: ThumbnailLoadingListener = {
|
const listener: ThumbnailLoadingListener = {
|
||||||
@ -93,7 +93,7 @@ export class IconThumbnail extends ThumbnailBase {
|
|||||||
this.loading = true;
|
this.loading = true;
|
||||||
},
|
},
|
||||||
onLoad: () => {// onLoaded
|
onLoad: () => {// onLoaded
|
||||||
this.src = this.photo.getIconPath();
|
this.src = this.media.getIconPath();
|
||||||
if (this.onLoad) {
|
if (this.onLoad) {
|
||||||
this.onLoad();
|
this.onLoad();
|
||||||
}
|
}
|
||||||
@ -107,7 +107,7 @@ export class IconThumbnail extends ThumbnailBase {
|
|||||||
this.error = true;
|
this.error = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.thumbnailTask = this.thumbnailService.loadIcon(this.photo, ThumbnailLoadingPriority.high, listener);
|
this.thumbnailTask = this.thumbnailService.loadIcon(this.media, ThumbnailLoadingPriority.high, listener);
|
||||||
|
|
||||||
|
|
||||||
}, 0);
|
}, 0);
|
||||||
@ -132,16 +132,16 @@ export class IconThumbnail extends ThumbnailBase {
|
|||||||
export class Thumbnail 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);
|
super(thumbnailService);
|
||||||
if (this.photo.isThumbnailAvailable()) {
|
if (this.media.isThumbnailAvailable()) {
|
||||||
this.src = this.photo.getThumbnailPath();
|
this.src = this.media.getThumbnailPath();
|
||||||
this.available = true;
|
this.available = true;
|
||||||
if (this.onLoad) {
|
if (this.onLoad) {
|
||||||
this.onLoad();
|
this.onLoad();
|
||||||
}
|
}
|
||||||
} else if (this.photo.isReplacementThumbnailAvailable()) {
|
} else if (this.media.isReplacementThumbnailAvailable()) {
|
||||||
this.src = this.photo.getReplacementThumbnailPath();
|
this.src = this.media.getReplacementThumbnailPath();
|
||||||
this.available = true;
|
this.available = true;
|
||||||
}
|
}
|
||||||
if (autoLoad) {
|
if (autoLoad) {
|
||||||
@ -154,13 +154,13 @@ export class Thumbnail extends ThumbnailBase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (value === true) {
|
if (value === true) {
|
||||||
if (this.photo.isReplacementThumbnailAvailable()) {
|
if (this.media.isReplacementThumbnailAvailable()) {
|
||||||
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
|
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
|
||||||
} else {
|
} else {
|
||||||
this.thumbnailTask.priority = ThumbnailLoadingPriority.extraHigh;
|
this.thumbnailTask.priority = ThumbnailLoadingPriority.extraHigh;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.photo.isReplacementThumbnailAvailable()) {
|
if (this.media.isReplacementThumbnailAvailable()) {
|
||||||
this.thumbnailTask.priority = ThumbnailLoadingPriority.low;
|
this.thumbnailTask.priority = ThumbnailLoadingPriority.low;
|
||||||
} else {
|
} else {
|
||||||
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
|
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
|
||||||
@ -173,13 +173,13 @@ export class Thumbnail extends ThumbnailBase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (visible === true) {
|
if (visible === true) {
|
||||||
if (this.photo.isReplacementThumbnailAvailable()) {
|
if (this.media.isReplacementThumbnailAvailable()) {
|
||||||
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
|
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
|
||||||
} else {
|
} else {
|
||||||
this.thumbnailTask.priority = ThumbnailLoadingPriority.high;
|
this.thumbnailTask.priority = ThumbnailLoadingPriority.high;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.photo.isReplacementThumbnailAvailable()) {
|
if (this.media.isReplacementThumbnailAvailable()) {
|
||||||
this.thumbnailTask.priority = ThumbnailLoadingPriority.low;
|
this.thumbnailTask.priority = ThumbnailLoadingPriority.low;
|
||||||
} else {
|
} else {
|
||||||
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
|
this.thumbnailTask.priority = ThumbnailLoadingPriority.medium;
|
||||||
@ -188,14 +188,14 @@ export class Thumbnail extends ThumbnailBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public load() {
|
public load() {
|
||||||
if (!this.photo.isThumbnailAvailable() && this.thumbnailTask == null) {
|
if (!this.media.isThumbnailAvailable() && this.thumbnailTask == null) {
|
||||||
// setTimeout(() => {
|
// setTimeout(() => {
|
||||||
const listener: ThumbnailLoadingListener = {
|
const listener: ThumbnailLoadingListener = {
|
||||||
onStartedLoading: () => { // onLoadStarted
|
onStartedLoading: () => { // onLoadStarted
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
},
|
},
|
||||||
onLoad: () => {// onLoaded
|
onLoad: () => {// onLoaded
|
||||||
this.src = this.photo.getThumbnailPath();
|
this.src = this.media.getThumbnailPath();
|
||||||
if (this.onLoad) {
|
if (this.onLoad) {
|
||||||
this.onLoad();
|
this.onLoad();
|
||||||
}
|
}
|
||||||
@ -209,10 +209,10 @@ export class Thumbnail extends ThumbnailBase {
|
|||||||
this.error = true;
|
this.error = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (this.photo.isReplacementThumbnailAvailable()) {
|
if (this.media.isReplacementThumbnailAvailable()) {
|
||||||
this.thumbnailTask = this.thumbnailService.loadImage(this.photo, ThumbnailLoadingPriority.medium, listener);
|
this.thumbnailTask = this.thumbnailService.loadImage(this.media, ThumbnailLoadingPriority.medium, listener);
|
||||||
} else {
|
} else {
|
||||||
this.thumbnailTask = this.thumbnailService.loadImage(this.photo, ThumbnailLoadingPriority.high, listener);
|
this.thumbnailTask = this.thumbnailService.loadImage(this.media, ThumbnailLoadingPriority.high, listener);
|
||||||
}
|
}
|
||||||
// }, 0);
|
// }, 0);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {ShareService} from '../gallery/share.service';
|
import {ShareService} from '../gallery/share.service';
|
||||||
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||||
|
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueryService {
|
export class QueryService {
|
||||||
@ -10,10 +11,10 @@ export class QueryService {
|
|||||||
constructor(private shareService: ShareService) {
|
constructor(private shareService: ShareService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
getParams(photo?: PhotoDTO): { [key: string]: string } {
|
getParams(media?: MediaDTO): { [key: string]: string } {
|
||||||
const query = {};
|
const query = {};
|
||||||
if (photo) {
|
if (media) {
|
||||||
query[QueryService.PHOTO_PARAM] = photo.name;
|
query[QueryService.PHOTO_PARAM] = media.name;
|
||||||
}
|
}
|
||||||
if (this.shareService.isSharing()) {
|
if (this.shareService.isSharing()) {
|
||||||
query['sk'] = this.shareService.getSharingKey();
|
query['sk'] = this.shareService.getSharingKey();
|
||||||
|
@ -21,7 +21,7 @@ export class RandomPhotoSettingsComponent extends SettingsComponent<ClientConfig
|
|||||||
_settingsService: RandomPhotoSettingsService,
|
_settingsService: RandomPhotoSettingsService,
|
||||||
notification: NotificationService,
|
notification: NotificationService,
|
||||||
i18n: I18n) {
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"cookie-session": "2.0.0-beta.3",
|
"cookie-session": "2.0.0-beta.3",
|
||||||
"ejs": "2.6.1",
|
"ejs": "2.6.1",
|
||||||
"express": "4.16.4",
|
"express": "4.16.4",
|
||||||
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"jimp": "0.5.6",
|
"jimp": "0.5.6",
|
||||||
"locale": "0.1.0",
|
"locale": "0.1.0",
|
||||||
"reflect-metadata": "0.1.12",
|
"reflect-metadata": "0.1.12",
|
||||||
@ -65,6 +66,7 @@
|
|||||||
"@types/chai": "4.1.7",
|
"@types/chai": "4.1.7",
|
||||||
"@types/cookie-session": "2.0.36",
|
"@types/cookie-session": "2.0.36",
|
||||||
"@types/express": "4.16.0",
|
"@types/express": "4.16.0",
|
||||||
|
"@types/fluent-ffmpeg": "^2.1.8",
|
||||||
"@types/gm": "1.18.1",
|
"@types/gm": "1.18.1",
|
||||||
"@types/jasmine": "2.8.9",
|
"@types/jasmine": "2.8.9",
|
||||||
"@types/node": "10.12.2",
|
"@types/node": "10.12.2",
|
||||||
|
28
sandbox.ts
Normal file
28
sandbox.ts
Normal 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();
|
@ -61,7 +61,7 @@ describe('Typeorm integration', () => {
|
|||||||
d.lastModified = Date.now();
|
d.lastModified = Date.now();
|
||||||
d.lastScanned = null;
|
d.lastScanned = null;
|
||||||
d.parent = null;
|
d.parent = null;
|
||||||
d.photos = [];
|
d.media = [];
|
||||||
d.directories = [];
|
d.directories = [];
|
||||||
return d;
|
return d;
|
||||||
};
|
};
|
||||||
@ -98,7 +98,7 @@ describe('Typeorm integration', () => {
|
|||||||
|
|
||||||
|
|
||||||
const d = new PhotoEntity();
|
const d = new PhotoEntity();
|
||||||
d.name = 'test photo.jpg';
|
d.name = 'test media.jpg';
|
||||||
d.directory = null;
|
d.directory = null;
|
||||||
d.metadata = m;
|
d.metadata = m;
|
||||||
return d;
|
return d;
|
||||||
@ -142,7 +142,7 @@ describe('Typeorm integration', () => {
|
|||||||
expect((await dr.find()).length).to.equal(1);
|
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 conn = await SQLConnection.getConnection();
|
||||||
const pr = conn.getRepository(PhotoEntity);
|
const pr = conn.getRepository(PhotoEntity);
|
||||||
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
|
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
|
||||||
@ -152,7 +152,7 @@ describe('Typeorm integration', () => {
|
|||||||
expect((await pr.find()).length).to.equal(1);
|
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 conn = await SQLConnection.getConnection();
|
||||||
const pr = conn.getRepository(PhotoEntity);
|
const pr = conn.getRepository(PhotoEntity);
|
||||||
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
|
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
|
||||||
@ -161,10 +161,10 @@ describe('Typeorm integration', () => {
|
|||||||
await pr.save(photo);
|
await pr.save(photo);
|
||||||
|
|
||||||
let photos = await pr
|
let photos = await pr
|
||||||
.createQueryBuilder('photo')
|
.createQueryBuilder('media')
|
||||||
.orderBy('photo.metadata.creationDate', 'ASC')
|
.orderBy('media.metadata.creationDate', 'ASC')
|
||||||
.where('photo.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + photo.metadata.positionData.city + '%'})
|
.where('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + photo.metadata.positionData.city + '%'})
|
||||||
.innerJoinAndSelect('photo.directory', 'directory')
|
.innerJoinAndSelect('media.directory', 'directory')
|
||||||
.limit(10)
|
.limit(10)
|
||||||
.getMany();
|
.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 conn = await SQLConnection.getConnection();
|
||||||
const pr = conn.getRepository(PhotoEntity);
|
const pr = conn.getRepository(PhotoEntity);
|
||||||
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
|
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
|
||||||
@ -183,17 +183,17 @@ describe('Typeorm integration', () => {
|
|||||||
photo.metadata.positionData = null;
|
photo.metadata.positionData = null;
|
||||||
await pr.save(photo);
|
await pr.save(photo);
|
||||||
let photos = await pr
|
let photos = await pr
|
||||||
.createQueryBuilder('photo')
|
.createQueryBuilder('media')
|
||||||
.orderBy('photo.metadata.creationDate', 'ASC')
|
.orderBy('media.metadata.creationDate', 'ASC')
|
||||||
.where('photo.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + city + '%'})
|
.where('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + city + '%'})
|
||||||
.innerJoinAndSelect('photo.directory', 'directory')
|
.innerJoinAndSelect('media.directory', 'directory')
|
||||||
.limit(10)
|
.limit(10)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
expect(photos.length).to.equal(0);
|
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();
|
let conn = await SQLConnection.getConnection();
|
||||||
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
|
const dir = await conn.getRepository(DirectoryEntity).save(getDir());
|
||||||
let dir2 = getDir();
|
let dir2 = getDir();
|
||||||
|
@ -60,7 +60,7 @@ describe('SearchManager', () => {
|
|||||||
|
|
||||||
|
|
||||||
const d = new PhotoEntity();
|
const d = new PhotoEntity();
|
||||||
d.name = 'test photo.jpg';
|
d.name = 'test media.jpg';
|
||||||
d.directory = dir;
|
d.directory = dir;
|
||||||
d.metadata = m;
|
d.metadata = m;
|
||||||
return d;
|
return d;
|
||||||
@ -159,7 +159,7 @@ describe('SearchManager', () => {
|
|||||||
searchText: 'sw',
|
searchText: 'sw',
|
||||||
searchType: null,
|
searchType: null,
|
||||||
directories: [],
|
directories: [],
|
||||||
photos: [p, p2],
|
media: [p, p2],
|
||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -167,7 +167,7 @@ describe('SearchManager', () => {
|
|||||||
searchText: 'Tatooine',
|
searchText: 'Tatooine',
|
||||||
searchType: SearchTypes.position,
|
searchType: SearchTypes.position,
|
||||||
directories: [],
|
directories: [],
|
||||||
photos: [p],
|
media: [p],
|
||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -175,7 +175,7 @@ describe('SearchManager', () => {
|
|||||||
searchText: 'ortm',
|
searchText: 'ortm',
|
||||||
searchType: SearchTypes.keyword,
|
searchType: SearchTypes.keyword,
|
||||||
directories: [],
|
directories: [],
|
||||||
photos: [p2],
|
media: [p2],
|
||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -183,7 +183,7 @@ describe('SearchManager', () => {
|
|||||||
searchText: 'ortm',
|
searchText: 'ortm',
|
||||||
searchType: SearchTypes.keyword,
|
searchType: SearchTypes.keyword,
|
||||||
directories: [],
|
directories: [],
|
||||||
photos: [p2],
|
media: [p2],
|
||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -191,7 +191,7 @@ describe('SearchManager', () => {
|
|||||||
searchText: 'wa',
|
searchText: 'wa',
|
||||||
searchType: SearchTypes.keyword,
|
searchType: SearchTypes.keyword,
|
||||||
directories: [dir],
|
directories: [dir],
|
||||||
photos: [p, p2],
|
media: [p, p2],
|
||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@ import {DiskMangerWorker} from '../../../../../backend/model/threading/DiskMange
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {Config} from '../../../../../common/config/private/Config';
|
import {Config} from '../../../../../common/config/private/Config';
|
||||||
import {ProjectPath} from '../../../../../backend/ProjectPath';
|
import {ProjectPath} from '../../../../../backend/ProjectPath';
|
||||||
|
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
|
||||||
|
|
||||||
describe('DiskMangerWorker', () => {
|
describe('DiskMangerWorker', () => {
|
||||||
|
|
||||||
@ -10,12 +11,12 @@ describe('DiskMangerWorker', () => {
|
|||||||
Config.Server.imagesFolder = path.join(__dirname, '/../../assets');
|
Config.Server.imagesFolder = path.join(__dirname, '/../../assets');
|
||||||
ProjectPath.ImageFolder = path.join(__dirname, '/../../assets');
|
ProjectPath.ImageFolder = path.join(__dirname, '/../../assets');
|
||||||
const dir = await DiskMangerWorker.scanDirectory('/');
|
const dir = await DiskMangerWorker.scanDirectory('/');
|
||||||
expect(dir.photos.length).to.be.equals(1);
|
expect(dir.media.length).to.be.equals(1);
|
||||||
expect(dir.photos[0].name).to.be.equals('test image öüóőúéáű-.,.jpg');
|
expect(dir.media[0].name).to.be.equals('test image öüóőúéáű-.,.jpg');
|
||||||
expect(dir.photos[0].metadata.keywords).to.deep.equals(['Berkley', 'USA', 'űáéúőóüö ŰÁÉÚŐÓÜÖ']);
|
expect(dir.media[0].metadata.keywords).to.deep.equals(['Berkley', 'USA', 'űáéúőóüö ŰÁÉÚŐÓÜÖ']);
|
||||||
expect(dir.photos[0].metadata.fileSize).to.deep.equals(62392);
|
expect(dir.media[0].metadata.fileSize).to.deep.equals(62392);
|
||||||
expect(dir.photos[0].metadata.size).to.deep.equals({width: 140, height: 93});
|
expect(dir.media[0].metadata.size).to.deep.equals({width: 140, height: 93});
|
||||||
expect(dir.photos[0].metadata.cameraData).to.deep.equals({
|
expect((<PhotoDTO>dir.media[0]).metadata.cameraData).to.deep.equals({
|
||||||
ISO: 3200,
|
ISO: 3200,
|
||||||
model: 'óüöúőűáé ÓÜÖÚŐŰÁÉ',
|
model: 'óüöúőűáé ÓÜÖÚŐŰÁÉ',
|
||||||
make: 'Canon',
|
make: 'Canon',
|
||||||
@ -25,7 +26,7 @@ describe('DiskMangerWorker', () => {
|
|||||||
lens: 'EF-S15-85mm f/3.5-5.6 IS USM'
|
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: {
|
GPSData: {
|
||||||
latitude: 37.871093333333334,
|
latitude: 37.871093333333334,
|
||||||
longitude: -122.25678,
|
longitude: -122.25678,
|
||||||
@ -36,7 +37,7 @@ describe('DiskMangerWorker', () => {
|
|||||||
city: 'óüöúőűáé ÓÜÖÚŐŰÁ'
|
city: 'óüöúőűáé ÓÜÖÚŐŰÁ'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(dir.photos[0].metadata.creationDate).to.be.equals(1434018566000);
|
expect(dir.media[0].metadata.creationDate).to.be.equals(1434018566000);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user