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

Add face margin to be part of converted faces

This commit is contained in:
Patrik J. Braun 2023-09-12 23:44:45 +02:00
parent 7d3343a8ff
commit 4bbd8fb6b4

View File

@ -24,11 +24,11 @@ export class PhotoProcessing {
if (Config.Server.Threading.enabled === true) { if (Config.Server.Threading.enabled === true) {
if (Config.Server.Threading.thumbnailThreads > 0) { if (Config.Server.Threading.thumbnailThreads > 0) {
Config.Media.Thumbnail.concurrentThumbnailGenerations = Config.Media.Thumbnail.concurrentThumbnailGenerations =
Config.Server.Threading.thumbnailThreads; Config.Server.Threading.thumbnailThreads;
} else { } else {
Config.Media.Thumbnail.concurrentThumbnailGenerations = Math.max( Config.Media.Thumbnail.concurrentThumbnailGenerations = Math.max(
1, 1,
os.cpus().length - 1 os.cpus().length - 1
); );
} }
} else { } else {
@ -36,31 +36,31 @@ export class PhotoProcessing {
} }
this.taskQue = new TaskExecuter( this.taskQue = new TaskExecuter(
Config.Media.Thumbnail.concurrentThumbnailGenerations, Config.Media.Thumbnail.concurrentThumbnailGenerations,
(input): Promise<void> => PhotoWorker.render(input) (input): Promise<void> => PhotoWorker.render(input)
); );
this.initDone = true; this.initDone = true;
} }
public static async generatePersonThumbnail( public static async generatePersonThumbnail(
person: PersonEntry person: PersonEntry
): Promise<string> { ): Promise<string> {
// load parameters // load parameters
const photo: PhotoDTO = person.sampleRegion.media; const photo: PhotoDTO = person.sampleRegion.media;
const mediaPath = path.join( const mediaPath = path.join(
ProjectPath.ImageFolder, ProjectPath.ImageFolder,
photo.directory.path, photo.directory.path,
photo.directory.name, photo.directory.name,
photo.name photo.name
); );
const size: number = Config.Media.Thumbnail.personThumbnailSize; const size: number = Config.Media.Thumbnail.personThumbnailSize;
const faceRegion = person.sampleRegion.media.metadata.faces.find(f => f.name === person.name); const faceRegion = person.sampleRegion.media.metadata.faces.find(f => f.name === person.name);
// generate thumbnail path // generate thumbnail path
const thPath = PhotoProcessing.generatePersonThumbnailPath( const thPath = PhotoProcessing.generatePersonThumbnailPath(
mediaPath, mediaPath,
faceRegion, faceRegion,
size size
); );
// check if thumbnail already exist // check if thumbnail already exist
@ -73,12 +73,12 @@ export class PhotoProcessing {
const margin = { const margin = {
x: Math.round( x: Math.round(
faceRegion.box.width * faceRegion.box.width *
Config.Media.Thumbnail.personFaceMargin Config.Media.Thumbnail.personFaceMargin
), ),
y: Math.round( y: Math.round(
faceRegion.box.height * faceRegion.box.height *
Config.Media.Thumbnail.personFaceMargin Config.Media.Thumbnail.personFaceMargin
), ),
}; };
@ -91,10 +91,10 @@ export class PhotoProcessing {
makeSquare: false, makeSquare: false,
cut: { cut: {
left: Math.round( left: Math.round(
Math.max(0, faceRegion.box.left - margin.x / 2) Math.max(0, faceRegion.box.left - margin.x / 2)
), ),
top: Math.round( top: Math.round(
Math.max(0, faceRegion.box.top - margin.y / 2) Math.max(0, faceRegion.box.top - margin.y / 2)
), ),
width: faceRegion.box.width + margin.x, width: faceRegion.box.width + margin.x,
height: faceRegion.box.height + margin.y, height: faceRegion.box.height + margin.y,
@ -104,12 +104,12 @@ export class PhotoProcessing {
smartSubsample: Config.Media.Thumbnail.smartSubsample, smartSubsample: Config.Media.Thumbnail.smartSubsample,
} as MediaRendererInput; } as MediaRendererInput;
input.cut.width = Math.min( input.cut.width = Math.min(
input.cut.width, input.cut.width,
photo.metadata.size.width - input.cut.left photo.metadata.size.width - input.cut.left
); );
input.cut.height = Math.min( input.cut.height = Math.min(
input.cut.height, input.cut.height,
photo.metadata.size.height - input.cut.top photo.metadata.size.height - input.cut.top
); );
await fsp.mkdir(ProjectPath.FacesFolder, {recursive: true}); await fsp.mkdir(ProjectPath.FacesFolder, {recursive: true});
@ -120,34 +120,35 @@ export class PhotoProcessing {
public static generateConvertedPath(mediaPath: string, size: number): string { public static generateConvertedPath(mediaPath: string, size: number): string {
const file = path.basename(mediaPath); const file = path.basename(mediaPath);
return path.join( return path.join(
ProjectPath.TranscodedFolder, ProjectPath.TranscodedFolder,
ProjectPath.getRelativePathToImages(path.dirname(mediaPath)), ProjectPath.getRelativePathToImages(path.dirname(mediaPath)),
file + '_' + size + 'q' + Config.Media.Thumbnail.quality + (Config.Media.Thumbnail.smartSubsample ? 'cs' : '') + PhotoProcessing.CONVERTED_EXTENSION file + '_' + size + 'q' + Config.Media.Thumbnail.quality + (Config.Media.Thumbnail.smartSubsample ? 'cs' : '') + PhotoProcessing.CONVERTED_EXTENSION
); );
} }
public static generatePersonThumbnailPath( public static generatePersonThumbnailPath(
mediaPath: string, mediaPath: string,
faceRegion: FaceRegion, faceRegion: FaceRegion,
size: number size: number
): string { ): string {
return path.join( return path.join(
ProjectPath.FacesFolder, ProjectPath.FacesFolder,
crypto crypto
.createHash('md5') .createHash('md5')
.update( .update(
mediaPath + mediaPath +
'_' + '_' +
faceRegion.name + faceRegion.name +
'_' + '_' +
faceRegion.box.left + faceRegion.box.left +
'_' + '_' +
faceRegion.box.top faceRegion.box.top
) )
.digest('hex') + .digest('hex') +
'_' + '_' +
size + size +
PhotoProcessing.CONVERTED_EXTENSION '_' + Config.Media.Thumbnail.personFaceMargin +
PhotoProcessing.CONVERTED_EXTENSION
); );
} }
@ -156,14 +157,14 @@ export class PhotoProcessing {
* @param convertedPath * @param convertedPath
*/ */
public static async isValidConvertedPath( public static async isValidConvertedPath(
convertedPath: string convertedPath: string
): Promise<boolean> { ): Promise<boolean> {
const origFilePath = path.join( const origFilePath = path.join(
ProjectPath.ImageFolder, ProjectPath.ImageFolder,
path.relative( path.relative(
ProjectPath.TranscodedFolder, ProjectPath.TranscodedFolder,
convertedPath.substring(0, convertedPath.lastIndexOf('_')) convertedPath.substring(0, convertedPath.lastIndexOf('_'))
) )
); );
if (path.extname(convertedPath) !== PhotoProcessing.CONVERTED_EXTENSION) { if (path.extname(convertedPath) !== PhotoProcessing.CONVERTED_EXTENSION) {
@ -171,24 +172,24 @@ export class PhotoProcessing {
} }
const sizeStr = convertedPath.substring( const sizeStr = convertedPath.substring(
convertedPath.lastIndexOf('_') + 1, convertedPath.lastIndexOf('_') + 1,
convertedPath.lastIndexOf('q') convertedPath.lastIndexOf('q')
); );
const size = parseInt(sizeStr, 10); const size = parseInt(sizeStr, 10);
if ( if (
(size + '').length !== sizeStr.length || (size + '').length !== sizeStr.length ||
(Config.Media.Thumbnail.thumbnailSizes.indexOf(size) === -1 && (Config.Media.Thumbnail.thumbnailSizes.indexOf(size) === -1 &&
Config.Media.Photo.Converting.resolution !== size) Config.Media.Photo.Converting.resolution !== size)
) { ) {
return false; return false;
} }
let qualityStr = convertedPath.substring( let qualityStr = convertedPath.substring(
convertedPath.lastIndexOf('q') + 1, convertedPath.lastIndexOf('q') + 1,
convertedPath.length - path.extname(convertedPath).length convertedPath.length - path.extname(convertedPath).length
); );
@ -202,7 +203,7 @@ export class PhotoProcessing {
const quality = parseInt(qualityStr, 10); const quality = parseInt(qualityStr, 10);
if ((quality + '').length !== qualityStr.length || if ((quality + '').length !== qualityStr.length ||
quality !== Config.Media.Thumbnail.quality) { quality !== Config.Media.Thumbnail.quality) {
return false; return false;
} }
@ -217,16 +218,16 @@ export class PhotoProcessing {
public static async convertPhoto(mediaPath: string): Promise<string> { public static async convertPhoto(mediaPath: string): Promise<string> {
return this.generateThumbnail( return this.generateThumbnail(
mediaPath, mediaPath,
Config.Media.Photo.Converting.resolution, Config.Media.Photo.Converting.resolution,
ThumbnailSourceType.Photo, ThumbnailSourceType.Photo,
false false
); );
} }
static async convertedPhotoExist( static async convertedPhotoExist(
mediaPath: string, mediaPath: string,
size: number size: number
): Promise<boolean> { ): Promise<boolean> {
// generate thumbnail path // generate thumbnail path
const outPath = PhotoProcessing.generateConvertedPath(mediaPath, size); const outPath = PhotoProcessing.generateConvertedPath(mediaPath, size);
@ -243,10 +244,10 @@ export class PhotoProcessing {
public static async generateThumbnail( public static async generateThumbnail(
mediaPath: string, mediaPath: string,
size: number, size: number,
sourceType: ThumbnailSourceType, sourceType: ThumbnailSourceType,
makeSquare: boolean makeSquare: boolean
): Promise<string> { ): Promise<string> {
// generate thumbnail path // generate thumbnail path
const outPath = PhotoProcessing.generateConvertedPath(mediaPath, size); const outPath = PhotoProcessing.generateConvertedPath(mediaPath, size);
@ -284,9 +285,9 @@ export class PhotoProcessing {
} }
public static async renderSVG( public static async renderSVG(
svgString: SVGIconConfig, svgIcon: SVGIconConfig,
outPath: string, outPath: string,
color = '#000' color = '#000'
): Promise<string> { ): Promise<string> {
// check if file already exist // check if file already exist
@ -302,7 +303,7 @@ export class PhotoProcessing {
const input = { const input = {
type: ThumbnailSourceType.Photo, type: ThumbnailSourceType.Photo,
svgString: `<svg fill="${color}" width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg" svgString: `<svg fill="${color}" width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg"
viewBox="${Config.Server.svgIcon.viewBox || '0 0 512 512'}">d="${Config.Server.svgIcon.items}</svg>`, viewBox="${svgIcon.viewBox || '0 0 512 512'}">d="${svgIcon.items}</svg>`,
size: size, size: size,
outPath, outPath,
makeSquare: false, makeSquare: false,