mirror of
https://github.com/xuthus83/pigallery2.git
synced 2024-11-03 21:04:03 +08:00
Merge branch 'bpatrik:master' into master
Some checks failed
pigallery2 / pigallery2-Gitea-Actions (push) Failing after 5m28s
Some checks failed
pigallery2 / pigallery2-Gitea-Actions (push) Failing after 5m28s
This commit is contained in:
commit
35f76a4d36
20
.github/workflows/build.yml
vendored
20
.github/workflows/build.yml
vendored
@ -17,21 +17,13 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [18.x]
|
node-version: [18.x]
|
||||||
|
|
||||||
services:
|
|
||||||
mariadb:
|
|
||||||
image: mariadb:lts
|
|
||||||
ports:
|
|
||||||
- 3306
|
|
||||||
env:
|
|
||||||
MYSQL_USER: user
|
|
||||||
MYSQL_PASSWORD: password
|
|
||||||
MYSQL_DATABASE: pigallery_test
|
|
||||||
MYSQL_ROOT_PASSWORD: password
|
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
|
||||||
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: getong/mariadb-action@v1.11
|
||||||
|
with:
|
||||||
|
mysql database: 'pigallery_test'
|
||||||
|
mysql root password: 'password'
|
||||||
|
mysql user: 'user'
|
||||||
|
mysql password: 'password'
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
@ -35,7 +35,7 @@ VOLUME ["/app/data/config", "/app/data/db", "/app/data/images", "/app/data/tmp"]
|
|||||||
RUN ["node", "./src/backend/index", "--expose-gc", "--run-diagnostics", "--config-path=/app/diagnostics-config.json"]
|
RUN ["node", "./src/backend/index", "--expose-gc", "--run-diagnostics", "--config-path=/app/diagnostics-config.json"]
|
||||||
HEALTHCHECK --interval=40s --timeout=30s --retries=3 --start-period=60s \
|
HEALTHCHECK --interval=40s --timeout=30s --retries=3 --start-period=60s \
|
||||||
CMD wget --quiet --tries=1 --no-check-certificate --spider \
|
CMD wget --quiet --tries=1 --no-check-certificate --spider \
|
||||||
http://localhost:80/heartbeat || exit 1
|
http://127.0.0.1:80/heartbeat || exit 1
|
||||||
|
|
||||||
# after a extensive job (like video converting), pigallery calls gc, to clean up everthing as fast as possible
|
# after a extensive job (like video converting), pigallery calls gc, to clean up everthing as fast as possible
|
||||||
# Exec form entrypoint is need otherwise (using shell form) ENV variables are not properly passed down to the app
|
# Exec form entrypoint is need otherwise (using shell form) ENV variables are not properly passed down to the app
|
||||||
|
@ -36,7 +36,7 @@ VOLUME ["/app/data/config", "/app/data/db", "/app/data/images", "/app/data/tmp"]
|
|||||||
RUN ["node", "./src/backend/index", "--expose-gc", "--run-diagnostics", "--config-path=/app/diagnostics-config.json"]
|
RUN ["node", "./src/backend/index", "--expose-gc", "--run-diagnostics", "--config-path=/app/diagnostics-config.json"]
|
||||||
HEALTHCHECK --interval=40s --timeout=30s --retries=3 --start-period=60s \
|
HEALTHCHECK --interval=40s --timeout=30s --retries=3 --start-period=60s \
|
||||||
CMD wget --quiet --tries=1 --no-check-certificate --spider \
|
CMD wget --quiet --tries=1 --no-check-certificate --spider \
|
||||||
http://localhost:80/heartbeat || exit 1
|
http://127.0.0.1:80/heartbeat || exit 1
|
||||||
|
|
||||||
# after a extensive job (like video converting), pigallery calls gc, to clean up everthing as fast as possible
|
# after a extensive job (like video converting), pigallery calls gc, to clean up everthing as fast as possible
|
||||||
# Exec form entrypoint is need otherwise (using shell form) ENV variables are not properly passed down to the app
|
# Exec form entrypoint is need otherwise (using shell form) ENV variables are not properly passed down to the app
|
||||||
|
@ -36,7 +36,7 @@ VOLUME ["/app/data/config", "/app/data/db", "/app/data/images", "/app/data/tmp"]
|
|||||||
RUN ["node", "./src/backend/index", "--expose-gc", "--run-diagnostics", "--config-path=/app/diagnostics-config.json"]
|
RUN ["node", "./src/backend/index", "--expose-gc", "--run-diagnostics", "--config-path=/app/diagnostics-config.json"]
|
||||||
HEALTHCHECK --interval=40s --timeout=30s --retries=3 --start-period=60s \
|
HEALTHCHECK --interval=40s --timeout=30s --retries=3 --start-period=60s \
|
||||||
CMD wget --quiet --tries=1 --no-check-certificate --spider \
|
CMD wget --quiet --tries=1 --no-check-certificate --spider \
|
||||||
http://localhost:80/heartbeat || exit 1
|
http://127.0.0.1:80/heartbeat || exit 1
|
||||||
|
|
||||||
# after a extensive job (like video converting), pigallery calls gc, to clean up everthing as fast as possible
|
# after a extensive job (like video converting), pigallery calls gc, to clean up everthing as fast as possible
|
||||||
# Exec form entrypoint is need otherwise (using shell form) ENV variables are not properly passed down to the app
|
# Exec form entrypoint is need otherwise (using shell form) ENV variables are not properly passed down to the app
|
||||||
|
@ -36,7 +36,7 @@ VOLUME ["/app/data/config", "/app/data/db", "/app/data/images", "/app/data/tmp"]
|
|||||||
RUN ["node", "./src/backend/index", "--expose-gc", "--run-diagnostics", "--config-path=/app/diagnostics-config.json"]
|
RUN ["node", "./src/backend/index", "--expose-gc", "--run-diagnostics", "--config-path=/app/diagnostics-config.json"]
|
||||||
HEALTHCHECK --interval=40s --timeout=30s --retries=3 --start-period=60s \
|
HEALTHCHECK --interval=40s --timeout=30s --retries=3 --start-period=60s \
|
||||||
CMD wget --quiet --tries=1 --no-check-certificate --spider \
|
CMD wget --quiet --tries=1 --no-check-certificate --spider \
|
||||||
http://localhost:80/heartbeat || exit 1
|
http://127.0.0.1:80/heartbeat || exit 1
|
||||||
|
|
||||||
# after a extensive job (like video converting), pigallery calls gc, to clean up everthing as fast as possible
|
# after a extensive job (like video converting), pigallery calls gc, to clean up everthing as fast as possible
|
||||||
# Exec form entrypoint is need otherwise (using shell form) ENV variables are not properly passed down to the app
|
# Exec form entrypoint is need otherwise (using shell form) ENV variables are not properly passed down to the app
|
||||||
|
@ -39,7 +39,7 @@ VOLUME ["/app/data/config", "/app/data/db", "/app/data/images", "/app/data/tmp"]
|
|||||||
RUN ["node", "./src/backend/index", "--expose-gc", "--run-diagnostics", "--config-path=/app/diagnostics-config.json"]
|
RUN ["node", "./src/backend/index", "--expose-gc", "--run-diagnostics", "--config-path=/app/diagnostics-config.json"]
|
||||||
HEALTHCHECK --interval=40s --timeout=30s --retries=3 --start-period=60s \
|
HEALTHCHECK --interval=40s --timeout=30s --retries=3 --start-period=60s \
|
||||||
CMD wget --quiet --tries=1 --no-check-certificate --spider \
|
CMD wget --quiet --tries=1 --no-check-certificate --spider \
|
||||||
http://localhost:80/heartbeat || exit 1
|
http://127.0.0.1:80/heartbeat || exit 1
|
||||||
|
|
||||||
# after a extensive job (like video converting), pigallery calls gc, to clean up everthing as fast as possible
|
# after a extensive job (like video converting), pigallery calls gc, to clean up everthing as fast as possible
|
||||||
# Exec form entrypoint is need otherwise (using shell form) ENV variables are not properly passed down to the app
|
# Exec form entrypoint is need otherwise (using shell form) ENV variables are not properly passed down to the app
|
||||||
|
@ -365,7 +365,7 @@ export class SearchManager {
|
|||||||
switch (sort.method) {
|
switch (sort.method) {
|
||||||
case SortByTypes.Date:
|
case SortByTypes.Date:
|
||||||
if (Config.Gallery.ignoreTimestampOffset === true) {
|
if (Config.Gallery.ignoreTimestampOffset === true) {
|
||||||
query.addOrderBy('media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)', sort.ascending ? 'ASC' : 'DESC');
|
query.addOrderBy('media.metadata.creationDate + (coalesce(media.metadata.creationDateOffset,0) * 60000)', sort.ascending ? 'ASC' : 'DESC');
|
||||||
} else {
|
} else {
|
||||||
query.addOrderBy('media.metadata.creationDate', sort.ascending ? 'ASC' : 'DESC');
|
query.addOrderBy('media.metadata.creationDate', sort.ascending ? 'ASC' : 'DESC');
|
||||||
}
|
}
|
||||||
@ -568,7 +568,7 @@ export class SearchManager {
|
|||||||
textParam['from' + queryId] = (query as FromDateSearch).value;
|
textParam['from' + queryId] = (query as FromDateSearch).value;
|
||||||
if (Config.Gallery.ignoreTimestampOffset === true) {
|
if (Config.Gallery.ignoreTimestampOffset === true) {
|
||||||
q.where(
|
q.where(
|
||||||
`(media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)) ${relation} :from${queryId}`,
|
`(media.metadata.creationDate + (coalesce(media.metadata.creationDateOffset,0) * 60000)) ${relation} :from${queryId}`,
|
||||||
textParam
|
textParam
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -597,7 +597,7 @@ export class SearchManager {
|
|||||||
textParam['to' + queryId] = (query as ToDateSearch).value;
|
textParam['to' + queryId] = (query as ToDateSearch).value;
|
||||||
if (Config.Gallery.ignoreTimestampOffset === true) {
|
if (Config.Gallery.ignoreTimestampOffset === true) {
|
||||||
q.where(
|
q.where(
|
||||||
`(media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)) ${relation} :to${queryId}`,
|
`(media.metadata.creationDate + (coalesce(media.metadata.creationDateOffset,0) * 60000)) ${relation} :to${queryId}`,
|
||||||
textParam
|
textParam
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -809,9 +809,9 @@ export class SearchManager {
|
|||||||
if (tq.negate) {
|
if (tq.negate) {
|
||||||
if (Config.Gallery.ignoreTimestampOffset === true) {
|
if (Config.Gallery.ignoreTimestampOffset === true) {
|
||||||
q.where(
|
q.where(
|
||||||
`(media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)) >= :to${queryId}`,
|
`(media.metadata.creationDate + (coalesce(media.metadata.creationDateOffset,0) * 60000)) >= :to${queryId}`,
|
||||||
textParam
|
textParam
|
||||||
).orWhere(`(media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)) < :from${queryId}`,
|
).orWhere(`(media.metadata.creationDate + (coalesce(media.metadata.creationDateOffset,0) * 60000)) < :from${queryId}`,
|
||||||
textParam);
|
textParam);
|
||||||
} else {
|
} else {
|
||||||
q.where(
|
q.where(
|
||||||
@ -824,9 +824,9 @@ export class SearchManager {
|
|||||||
} else {
|
} else {
|
||||||
if (Config.Gallery.ignoreTimestampOffset === true) {
|
if (Config.Gallery.ignoreTimestampOffset === true) {
|
||||||
q.where(
|
q.where(
|
||||||
`(media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)) < :to${queryId}`,
|
`(media.metadata.creationDate + (coalesce(media.metadata.creationDateOffset,0) * 60000)) < :to${queryId}`,
|
||||||
textParam
|
textParam
|
||||||
).andWhere(`(media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)) >= :from${queryId}`,
|
).andWhere(`(media.metadata.creationDate + (coalesce(media.metadata.creationDateOffset,0) * 60000)) >= :from${queryId}`,
|
||||||
textParam);
|
textParam);
|
||||||
} else {
|
} else {
|
||||||
q.where(
|
q.where(
|
||||||
|
@ -10,7 +10,10 @@ export class PersonEntry implements PersonDTO {
|
|||||||
@PrimaryGeneratedColumn({unsigned: true})
|
@PrimaryGeneratedColumn({unsigned: true})
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@Column(columnCharsetCS)
|
@Column({
|
||||||
|
charset: columnCharsetCS.charset,
|
||||||
|
collation: columnCharsetCS.collation,
|
||||||
|
})
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@Column('int', {unsigned: true, default: 0})
|
@Column('int', {unsigned: true, default: 0})
|
||||||
|
@ -3,12 +3,14 @@ import {Logger} from '../../Logger';
|
|||||||
import {NotificationManager} from '../NotifocationManager';
|
import {NotificationManager} from '../NotifocationManager';
|
||||||
import {SQLConnection} from '../database/SQLConnection';
|
import {SQLConnection} from '../database/SQLConnection';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
import {FFmpegFactory} from '../FFmpegFactory';
|
import {FFmpegFactory} from '../FFmpegFactory';
|
||||||
import {
|
import {
|
||||||
ClientAlbumConfig,
|
ClientAlbumConfig,
|
||||||
ClientFacesConfig,
|
ClientFacesConfig,
|
||||||
ClientMapConfig,
|
ClientMapConfig,
|
||||||
ClientMetaFileConfig,
|
ClientMetaFileConfig,
|
||||||
|
ClientPhotoConfig,
|
||||||
ClientRandomPhotoConfig,
|
ClientRandomPhotoConfig,
|
||||||
ClientSearchConfig,
|
ClientSearchConfig,
|
||||||
ClientSharingConfig,
|
ClientSharingConfig,
|
||||||
@ -28,7 +30,9 @@ import {SearchQueryTypes, TextSearch,} from '../../../common/entities/SearchQuer
|
|||||||
import {Utils} from '../../../common/Utils';
|
import {Utils} from '../../../common/Utils';
|
||||||
import {JobRepository} from '../jobs/JobRepository';
|
import {JobRepository} from '../jobs/JobRepository';
|
||||||
import {ConfigClassBuilder} from '../../../../node_modules/typeconfig/node';
|
import {ConfigClassBuilder} from '../../../../node_modules/typeconfig/node';
|
||||||
import { Config } from '../../../common/config/private/Config';
|
import {Config} from '../../../common/config/private/Config';
|
||||||
|
import {SupportedFormats} from '../../../common/SupportedFormats';
|
||||||
|
import {MediaRendererInput, PhotoWorker, ThumbnailSourceType} from '../fileaccess/PhotoWorker';
|
||||||
|
|
||||||
const LOG_TAG = '[ConfigDiagnostics]';
|
const LOG_TAG = '[ConfigDiagnostics]';
|
||||||
|
|
||||||
@ -78,9 +82,9 @@ export class ConfigDiagnostics {
|
|||||||
jobsConfig: ServerJobConfig
|
jobsConfig: ServerJobConfig
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
Logger.debug(LOG_TAG, 'Testing jobs config');
|
Logger.debug(LOG_TAG, 'Testing jobs config');
|
||||||
for(let i = 0; i< jobsConfig.scheduled.length; ++i){
|
for (let i = 0; i < jobsConfig.scheduled.length; ++i) {
|
||||||
const j = jobsConfig.scheduled[i];
|
const j = jobsConfig.scheduled[i];
|
||||||
if(!JobRepository.Instance.exists(j.name)){
|
if (!JobRepository.Instance.exists(j.name)) {
|
||||||
throw new Error('Unknown Job :' + j.name);
|
throw new Error('Unknown Job :' + j.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -292,6 +296,46 @@ export class ConfigDiagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes unsupported image formats.
|
||||||
|
* It is possible that some OS support one or the other image formats (like Mac os does with HEIC)
|
||||||
|
* , but others not.
|
||||||
|
* Those formats are added to the config, but dynamically removed.
|
||||||
|
* @param config
|
||||||
|
*/
|
||||||
|
static async removeUnsupportedPhotoExtensions(config: ClientPhotoConfig): Promise<void> {
|
||||||
|
Logger.verbose(LOG_TAG, 'Checking for supported image formats');
|
||||||
|
let removedSome = false;
|
||||||
|
let i = config.supportedFormats.length;
|
||||||
|
while (i--) {
|
||||||
|
const ext = config.supportedFormats[i].toLowerCase();
|
||||||
|
const testImage = path.join(__dirname, 'image_formats', 'test.' + ext);
|
||||||
|
// Check if a test available for this image format.
|
||||||
|
// if not probably because it is trivial
|
||||||
|
if (!fs.existsSync(testImage)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await PhotoWorker.renderFromImage({
|
||||||
|
type: ThumbnailSourceType.Photo,
|
||||||
|
mediaPath: testImage,
|
||||||
|
size: 10,
|
||||||
|
useLanczos3: Config.Media.Photo.useLanczos3,
|
||||||
|
quality: Config.Media.Photo.quality,
|
||||||
|
smartSubsample: Config.Media.Photo.smartSubsample,
|
||||||
|
} as MediaRendererInput, true
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.verbose(LOG_TAG, 'The current OS does not support the following photo format:' + ext + ', removing it form config.');
|
||||||
|
config.supportedFormats.splice(i, 1);
|
||||||
|
removedSome = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removedSome) {
|
||||||
|
SupportedFormats.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async testConfig(config: PrivateConfigClass): Promise<void> {
|
static async testConfig(config: PrivateConfigClass): Promise<void> {
|
||||||
|
|
||||||
await ConfigDiagnostics.testDatabase(config.Database);
|
await ConfigDiagnostics.testDatabase(config.Database);
|
||||||
@ -310,7 +354,6 @@ export class ConfigDiagnostics {
|
|||||||
await ConfigDiagnostics.testRandomPhotoConfig(config.Sharing, config);
|
await ConfigDiagnostics.testRandomPhotoConfig(config.Sharing, config);
|
||||||
await ConfigDiagnostics.testMapConfig(config.Map);
|
await ConfigDiagnostics.testMapConfig(config.Map);
|
||||||
await ConfigDiagnostics.testJobsConfig(config.Jobs);
|
await ConfigDiagnostics.testJobsConfig(config.Jobs);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -562,7 +605,7 @@ export class ConfigDiagnostics {
|
|||||||
const err: Error = ex;
|
const err: Error = ex;
|
||||||
NotificationManager.warning(
|
NotificationManager.warning(
|
||||||
'Jobs error. Resetting to default for now to let the app start up. ' +
|
'Jobs error. Resetting to default for now to let the app start up. ' +
|
||||||
'Please adjust the config properly.',
|
'Please adjust the config properly.',
|
||||||
err.toString()
|
err.toString()
|
||||||
);
|
);
|
||||||
Logger.warn(
|
Logger.warn(
|
||||||
@ -575,6 +618,8 @@ export class ConfigDiagnostics {
|
|||||||
Config.Jobs.scheduled = pc.Jobs.scheduled;
|
Config.Jobs.scheduled = pc.Jobs.scheduled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.removeUnsupportedPhotoExtensions(Config.Media.Photo);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
BIN
src/backend/model/diagnostics/image_formats/test.avif
Normal file
BIN
src/backend/model/diagnostics/image_formats/test.avif
Normal file
Binary file not shown.
BIN
src/backend/model/diagnostics/image_formats/test.heic
Normal file
BIN
src/backend/model/diagnostics/image_formats/test.heic
Normal file
Binary file not shown.
BIN
src/backend/model/diagnostics/image_formats/test.jpg
Normal file
BIN
src/backend/model/diagnostics/image_formats/test.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 631 B |
@ -23,8 +23,8 @@ export class PhotoWorker {
|
|||||||
throw new Error('Unsupported media type to render thumbnail:' + input.type);
|
throw new Error('Unsupported media type to render thumbnail:' + input.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static renderFromImage(input: SvgRendererInput | MediaRendererInput): Promise<void> {
|
public static renderFromImage(input: SvgRendererInput | MediaRendererInput, dryRun = false): Promise<void> {
|
||||||
return ImageRendererFactory.render(input);
|
return ImageRendererFactory.render(input, dryRun);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static renderFromVideo(input: MediaRendererInput): Promise<void> {
|
public static renderFromVideo(input: MediaRendererInput): Promise<void> {
|
||||||
@ -43,8 +43,8 @@ export enum ThumbnailSourceType {
|
|||||||
interface RendererInput {
|
interface RendererInput {
|
||||||
type: ThumbnailSourceType;
|
type: ThumbnailSourceType;
|
||||||
size: number;
|
size: number;
|
||||||
makeSquare: boolean;
|
makeSquare?: boolean;
|
||||||
outPath: string;
|
outPath?: string;
|
||||||
quality: number;
|
quality: number;
|
||||||
useLanczos3: boolean;
|
useLanczos3: boolean;
|
||||||
animate: boolean; // animates the output. Used for Gifs
|
animate: boolean; // animates the output. Used for Gifs
|
||||||
@ -80,14 +80,14 @@ export class VideoRendererFactory {
|
|||||||
let width = null;
|
let width = null;
|
||||||
let height = null;
|
let height = null;
|
||||||
for (const stream of data.streams) {
|
for (const stream of data.streams) {
|
||||||
if (stream.width) {
|
if (stream.width && stream.height && !isNaN(stream.width) && !isNaN(stream.height)) {
|
||||||
width = stream.width;
|
width = stream.width;
|
||||||
height = stream.height;
|
height = stream.height;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!width || !height) {
|
if (!width || !height || isNaN(width) || isNaN(height)) {
|
||||||
return reject('[FFmpeg] Can not read video dimension');
|
return reject(`[FFmpeg] Can not read video dimension. Found: ${{width}}x${{height}}`);
|
||||||
}
|
}
|
||||||
const command: FfmpegCommand = ffmpeg(input.mediaPath);
|
const command: FfmpegCommand = ffmpeg(input.mediaPath);
|
||||||
const fileName = path.basename(input.outPath);
|
const fileName = path.basename(input.outPath);
|
||||||
@ -132,7 +132,7 @@ export class VideoRendererFactory {
|
|||||||
export class ImageRendererFactory {
|
export class ImageRendererFactory {
|
||||||
|
|
||||||
@ExtensionDecorator(e => e.gallery.ImageRenderer.render)
|
@ExtensionDecorator(e => e.gallery.ImageRenderer.render)
|
||||||
public static async render(input: MediaRendererInput | SvgRendererInput): Promise<void> {
|
public static async render(input: MediaRendererInput | SvgRendererInput, dryRun = false): Promise<void> {
|
||||||
|
|
||||||
let image: Sharp;
|
let image: Sharp;
|
||||||
if ((input as MediaRendererInput).mediaPath) {
|
if ((input as MediaRendererInput).mediaPath) {
|
||||||
@ -174,17 +174,24 @@ export class ImageRendererFactory {
|
|||||||
fit: 'cover',
|
fit: 'cover',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let processedImg: sharp.Sharp;
|
||||||
if ((input as MediaRendererInput).mediaPath) {
|
if ((input as MediaRendererInput).mediaPath) {
|
||||||
await image.webp({
|
processedImg = image.webp({
|
||||||
effort: 6,
|
effort: 6,
|
||||||
quality: input.quality,
|
quality: input.quality,
|
||||||
smartSubsample: (input as MediaRendererInput).smartSubsample
|
smartSubsample: (input as MediaRendererInput).smartSubsample
|
||||||
}).toFile(input.outPath);
|
});
|
||||||
} else {
|
} else {
|
||||||
if ((input as SvgRendererInput).svgString) {
|
if ((input as SvgRendererInput).svgString) {
|
||||||
await image.png({effort: 6, quality: input.quality}).toFile(input.outPath);
|
processedImg = image.png({effort: 6, quality: input.quality});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// do not save to file
|
||||||
|
if (dryRun) {
|
||||||
|
await processedImg.toBuffer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await processedImg.toFile(input.outPath);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,44 +10,45 @@ if (typeof window !== 'undefined') {
|
|||||||
Config = require('./config/private/Config').Config;
|
Config = require('./config/private/Config').Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SupportedFormats = {
|
export class SupportedFormats {
|
||||||
Photos: Config.Media.Photo.supportedFormats,
|
static Photos = Config.Media.Photo.supportedFormats;
|
||||||
// Browser supported video formats
|
// Browser supported video formats
|
||||||
// Read more: https://www.w3schools.com/html/html5_video.asp
|
// Read more: https://www.w3schools.com/html/html5_video.asp
|
||||||
Videos: Config.Media.Video.supportedFormats,
|
static Videos = Config.Media.Video.supportedFormats;
|
||||||
MetaFiles: Config.MetaFile.supportedFormats,
|
static MetaFiles = Config.MetaFile.supportedFormats;
|
||||||
// These formats need to be transcoded (with the build-in ffmpeg support)
|
// These formats need to be transcoded (with the build-in ffmpeg support)
|
||||||
TranscodeNeed: {
|
static TranscodeNeed = {
|
||||||
// based on libvips, all supported formats for sharp: https://github.com/libvips/libvips
|
// based on libvips, all supported formats for sharp: https://github.com/libvips/libvips
|
||||||
Photos: [] as string[],
|
Photos: [] as string[],
|
||||||
Videos: Config.Media.Video.supportedFormatsWithTranscoding,
|
Videos: Config.Media.Video.supportedFormatsWithTranscoding,
|
||||||
},
|
};
|
||||||
// --------------------------------------------
|
// --------------------------------------------
|
||||||
// Below this, it is autogenerated, DO NOT EDIT
|
// Below this, it is autogenerated, DO NOT EDIT
|
||||||
WithDots: {
|
static WithDots = {
|
||||||
Photos: [] as string[],
|
Photos: [] as string[],
|
||||||
Videos: [] as string[],
|
Videos: [] as string[],
|
||||||
MetaFiles: [] as string[],
|
MetaFiles: [] as string[],
|
||||||
TranscodeNeed: {
|
TranscodeNeed: {
|
||||||
Photos: [] as string[],
|
Photos: [] as string[],
|
||||||
Videos: [] as string[],
|
Videos: [] as string[],
|
||||||
},
|
}
|
||||||
},
|
};
|
||||||
};
|
static init(){
|
||||||
|
SupportedFormats.Photos = SupportedFormats.Photos.concat(
|
||||||
SupportedFormats.Photos = SupportedFormats.Photos.concat(
|
SupportedFormats.TranscodeNeed.Photos
|
||||||
SupportedFormats.TranscodeNeed.Photos
|
);
|
||||||
);
|
SupportedFormats.Videos = SupportedFormats.Videos.concat(
|
||||||
SupportedFormats.Videos = SupportedFormats.Videos.concat(
|
SupportedFormats.TranscodeNeed.Videos
|
||||||
SupportedFormats.TranscodeNeed.Videos
|
);
|
||||||
);
|
SupportedFormats.WithDots.Photos = SupportedFormats.Photos.map((f) => '.' + f);
|
||||||
SupportedFormats.WithDots.Photos = SupportedFormats.Photos.map((f) => '.' + f);
|
SupportedFormats.WithDots.Videos = SupportedFormats.Videos.map((f) => '.' + f);
|
||||||
SupportedFormats.WithDots.Videos = SupportedFormats.Videos.map((f) => '.' + f);
|
SupportedFormats.WithDots.MetaFiles = SupportedFormats.MetaFiles.map(
|
||||||
SupportedFormats.WithDots.MetaFiles = SupportedFormats.MetaFiles.map(
|
(f) => '.' + f
|
||||||
(f) => '.' + f
|
);
|
||||||
);
|
SupportedFormats.WithDots.TranscodeNeed.Photos =
|
||||||
SupportedFormats.WithDots.TranscodeNeed.Photos =
|
SupportedFormats.TranscodeNeed.Photos.map((f) => '.' + f);
|
||||||
SupportedFormats.TranscodeNeed.Photos.map((f) => '.' + f);
|
SupportedFormats.WithDots.TranscodeNeed.Videos =
|
||||||
SupportedFormats.WithDots.TranscodeNeed.Videos =
|
SupportedFormats.TranscodeNeed.Videos.map((f) => '.' + f);
|
||||||
SupportedFormats.TranscodeNeed.Videos.map((f) => '.' + f);
|
}
|
||||||
|
}
|
||||||
|
SupportedFormats.init();
|
||||||
|
@ -1084,7 +1084,7 @@ export class ClientGalleryConfig {
|
|||||||
|
|
||||||
@ConfigProperty({
|
@ConfigProperty({
|
||||||
tags: {
|
tags: {
|
||||||
name: $localize`Ignore timestamp offsets`,
|
name: $localize`Ignore timestamp offsets (use local time)`,
|
||||||
priority: ConfigPriority.advanced,
|
priority: ConfigPriority.advanced,
|
||||||
},
|
},
|
||||||
description: $localize`If enabled, timestamp offsets are ignored, meaning that the local times of pictures are used for searching, sorting and grouping. If disabled, global time is used and pictures with no timestamp are assumed to be in UTC (offset +00:00).`
|
description: $localize`If enabled, timestamp offsets are ignored, meaning that the local times of pictures are used for searching, sorting and grouping. If disabled, global time is used and pictures with no timestamp are assumed to be in UTC (offset +00:00).`
|
||||||
@ -1266,7 +1266,7 @@ export class ClientPhotoConfig {
|
|||||||
},
|
},
|
||||||
description: $localize`Photo formats that are supported. Browser needs to support these formats natively. Also sharp (libvips) package should be able to convert these formats.`,
|
description: $localize`Photo formats that are supported. Browser needs to support these formats natively. Also sharp (libvips) package should be able to convert these formats.`,
|
||||||
})
|
})
|
||||||
supportedFormats: string[] = ['gif', 'jpeg', 'jpg', 'jpe', 'png', 'webp', 'svg'];
|
supportedFormats: string[] = ['gif', 'jpeg', 'jpg', 'jpe', 'png', 'webp', 'svg', 'avif', 'heic'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
@SubConfigClass({tags: {client: true}, softReadonly: true})
|
||||||
|
@ -199,9 +199,6 @@ export class TestHelper {
|
|||||||
} as FaceRegion, {
|
} as FaceRegion, {
|
||||||
box: {height: 10, width: 10, left: 104, top: 104},
|
box: {height: 10, width: 10, left: 104, top: 104},
|
||||||
name: 'Unkle Ben'
|
name: 'Unkle Ben'
|
||||||
} as FaceRegion, {
|
|
||||||
box: {height: 10, width: 10, left: 105, top: 105},
|
|
||||||
name: 'Arvíztűrő Tükörfúrógép'
|
|
||||||
} as FaceRegion, {
|
} as FaceRegion, {
|
||||||
box: {height: 10, width: 10, left: 201, top: 201},
|
box: {height: 10, width: 10, left: 201, top: 201},
|
||||||
name: 'R2-D2'
|
name: 'R2-D2'
|
||||||
|
@ -1387,7 +1387,7 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
|
|||||||
resultOverflow: false
|
resultOverflow: false
|
||||||
} as SearchResultDTO));
|
} as SearchResultDTO));
|
||||||
|
|
||||||
query = ({value: 6, type: SearchQueryTypes.min_person_count} as MinPersonCountSearch);
|
query = ({value: 5, type: SearchQueryTypes.min_person_count} as MinPersonCountSearch);
|
||||||
expect(Utils.clone(await sm.search(query)))
|
expect(Utils.clone(await sm.search(query)))
|
||||||
.to.deep.equalInAnyOrder(removeDir({
|
.to.deep.equalInAnyOrder(removeDir({
|
||||||
searchQuery: query,
|
searchQuery: query,
|
||||||
|
@ -24,7 +24,8 @@ describe('DiskMangerWorker', () => {
|
|||||||
ProjectPath.ImageFolder = path.join(__dirname, '/../../../assets');
|
ProjectPath.ImageFolder = path.join(__dirname, '/../../../assets');
|
||||||
const dir = await DiskManager.scanDirectory('/');
|
const dir = await DiskManager.scanDirectory('/');
|
||||||
// should match the number of media (photo/video) files in the assets folder
|
// should match the number of media (photo/video) files in the assets folder
|
||||||
expect(dir.media.length).to.be.equals(16);
|
// TODO: make this test less flaky. Every time a new image is added to the folder, it fails.
|
||||||
|
expect(dir.media.length).to.be.equals(17);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const expected = require(path.join(__dirname, '/../../../assets/test image öüóőúéáű-.,.json'));
|
const expected = require(path.join(__dirname, '/../../../assets/test image öüóőúéáű-.,.json'));
|
||||||
const i = dir.media.findIndex(m => m.name === 'test image öüóőúéáű-.,.jpg');
|
const i = dir.media.findIndex(m => m.name === 'test image öüóőúéáű-.,.jpg');
|
||||||
|
Loading…
Reference in New Issue
Block a user