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