Auto-label recognized recipes
This commit is contained in:
parent
b114bca43e
commit
7911cf1e2e
@ -15,7 +15,6 @@ import { GetPhotosOptions, areOptionsSensitive } from './db';
|
|||||||
import {
|
import {
|
||||||
FIELDS_TO_NOT_OVERWRITE_WITH_NULL_DATA_ON_SYNC,
|
FIELDS_TO_NOT_OVERWRITE_WITH_NULL_DATA_ON_SYNC,
|
||||||
PhotoFormData,
|
PhotoFormData,
|
||||||
convertFormDataToPhotoDbInsert,
|
|
||||||
convertPhotoToFormData,
|
convertPhotoToFormData,
|
||||||
} from './form';
|
} from './form';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
@ -34,7 +33,11 @@ import {
|
|||||||
PATH_ROOT,
|
PATH_ROOT,
|
||||||
pathForPhoto,
|
pathForPhoto,
|
||||||
} from '@/app/paths';
|
} from '@/app/paths';
|
||||||
import { blurImageFromUrl, extractImageDataFromBlobPath } from './server';
|
import {
|
||||||
|
blurImageFromUrl,
|
||||||
|
convertFormDataToPhotoDbInsertAndLookupRecipeTitle,
|
||||||
|
extractImageDataFromBlobPath,
|
||||||
|
} from './server';
|
||||||
import { TAG_FAVS, isTagFavs } from '@/tag';
|
import { TAG_FAVS, isTagFavs } from '@/tag';
|
||||||
import { convertPhotoToPhotoDbInsert, Photo } from '.';
|
import { convertPhotoToPhotoDbInsert, Photo } from '.';
|
||||||
import { runAuthenticatedAdminServerAction } from '@/auth';
|
import { runAuthenticatedAdminServerAction } from '@/auth';
|
||||||
@ -59,7 +62,8 @@ export const createPhotoAction = async (formData: FormData) =>
|
|||||||
const shouldStripGpsData = formData.get('shouldStripGpsData') === 'true';
|
const shouldStripGpsData = formData.get('shouldStripGpsData') === 'true';
|
||||||
formData.delete('shouldStripGpsData');
|
formData.delete('shouldStripGpsData');
|
||||||
|
|
||||||
const photo = convertFormDataToPhotoDbInsert(formData);
|
const photo =
|
||||||
|
await convertFormDataToPhotoDbInsertAndLookupRecipeTitle(formData);
|
||||||
|
|
||||||
const updatedUrl = await convertUploadToPhoto({
|
const updatedUrl = await convertUploadToPhoto({
|
||||||
urlOrigin: photo.url,
|
urlOrigin: photo.url,
|
||||||
@ -160,7 +164,8 @@ export const addAllUploadsAction = async ({
|
|||||||
if (updatedUrl) {
|
if (updatedUrl) {
|
||||||
const subheadFinal = 'Adding to database';
|
const subheadFinal = 'Adding to database';
|
||||||
streamUpdate(subheadFinal);
|
streamUpdate(subheadFinal);
|
||||||
const photo = convertFormDataToPhotoDbInsert(form);
|
const photo =
|
||||||
|
await convertFormDataToPhotoDbInsertAndLookupRecipeTitle(form);
|
||||||
photo.url = updatedUrl;
|
photo.url = updatedUrl;
|
||||||
await insertPhoto(photo);
|
await insertPhoto(photo);
|
||||||
addedUploadUrls.push(url);
|
addedUploadUrls.push(url);
|
||||||
@ -185,7 +190,8 @@ export const addAllUploadsAction = async ({
|
|||||||
|
|
||||||
export const updatePhotoAction = async (formData: FormData) =>
|
export const updatePhotoAction = async (formData: FormData) =>
|
||||||
runAuthenticatedAdminServerAction(async () => {
|
runAuthenticatedAdminServerAction(async () => {
|
||||||
const photo = convertFormDataToPhotoDbInsert(formData);
|
const photo =
|
||||||
|
await convertFormDataToPhotoDbInsertAndLookupRecipeTitle(formData);
|
||||||
|
|
||||||
let urlToDelete: string | undefined;
|
let urlToDelete: string | undefined;
|
||||||
if (photo.hidden && photo.url.includes(photo.id)) {
|
if (photo.hidden && photo.url.includes(photo.id)) {
|
||||||
@ -368,16 +374,17 @@ export const syncPhotoAction = async (photoId: string) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const photoFormDbInsert = convertFormDataToPhotoDbInsert({
|
const photoFormDbInsert =
|
||||||
...formDataFromPhoto,
|
await convertFormDataToPhotoDbInsertAndLookupRecipeTitle({
|
||||||
...formDataFromExif,
|
...formDataFromPhoto,
|
||||||
...!BLUR_ENABLED && { blurData: undefined },
|
...formDataFromExif,
|
||||||
...!photo.title && { title: atTitle },
|
...!BLUR_ENABLED && { blurData: undefined },
|
||||||
...!photo.caption && { caption: aiCaption },
|
...!photo.title && { title: atTitle },
|
||||||
...photo.tags.length === 0 && { tags: aiTags },
|
...!photo.caption && { caption: aiCaption },
|
||||||
...!photo.semanticDescription &&
|
...photo.tags.length === 0 && { tags: aiTags },
|
||||||
{ semanticDescription: aiSemanticDescription },
|
...!photo.semanticDescription &&
|
||||||
});
|
{ semanticDescription: aiSemanticDescription },
|
||||||
|
});
|
||||||
|
|
||||||
await updatePhoto(photoFormDbInsert)
|
await updatePhoto(photoFormDbInsert)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
|||||||
@ -187,7 +187,7 @@ export const insertPhoto = (photo: PhotoDbInsert) =>
|
|||||||
${photo.longitude},
|
${photo.longitude},
|
||||||
${photo.filmSimulation},
|
${photo.filmSimulation},
|
||||||
${photo.recipeTitle},
|
${photo.recipeTitle},
|
||||||
${JSON.stringify(photo.recipeData)},
|
${photo.recipeData},
|
||||||
${photo.priorityOrder},
|
${photo.priorityOrder},
|
||||||
${photo.hidden},
|
${photo.hidden},
|
||||||
${photo.takenAt},
|
${photo.takenAt},
|
||||||
@ -221,7 +221,7 @@ export const updatePhoto = (photo: PhotoDbInsert) =>
|
|||||||
longitude=${photo.longitude},
|
longitude=${photo.longitude},
|
||||||
film_simulation=${photo.filmSimulation},
|
film_simulation=${photo.filmSimulation},
|
||||||
recipe_title=${photo.recipeTitle},
|
recipe_title=${photo.recipeTitle},
|
||||||
recipe_data=${JSON.stringify(photo.recipeData)},
|
recipe_data=${photo.recipeData},
|
||||||
priority_order=${photo.priorityOrder || null},
|
priority_order=${photo.priorityOrder || null},
|
||||||
hidden=${photo.hidden},
|
hidden=${photo.hidden},
|
||||||
taken_at=${photo.takenAt},
|
taken_at=${photo.takenAt},
|
||||||
@ -357,6 +357,16 @@ export const getUniqueRecipes = async () =>
|
|||||||
})))
|
})))
|
||||||
, 'getUniqueRecipes');
|
, 'getUniqueRecipes');
|
||||||
|
|
||||||
|
export const getRecipeTitleForData = async (data: string | object) =>
|
||||||
|
safelyQueryPhotos(() => query(
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
'SELECT recipe_title FROM photos WHERE hidden IS NOT TRUE AND recipe_data = $1 LIMIT 1',
|
||||||
|
// Legacy check on escaped, string-based JSON
|
||||||
|
[typeof data === 'string' ? data : JSON.stringify(data)],
|
||||||
|
)
|
||||||
|
.then(({ rows }) => rows[0]?.recipe_title as string | undefined)
|
||||||
|
, 'getRecipeTitleForData');
|
||||||
|
|
||||||
export const getUniqueFocalLengths = async () =>
|
export const getUniqueFocalLengths = async () =>
|
||||||
safelyQueryPhotos(() => sql`
|
safelyQueryPhotos(() => sql`
|
||||||
SELECT DISTINCT focal_length, COUNT(*)
|
SELECT DISTINCT focal_length, COUNT(*)
|
||||||
|
|||||||
@ -144,7 +144,10 @@ export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => {
|
|||||||
exposureCompensationFormatted:
|
exposureCompensationFormatted:
|
||||||
formatExposureCompensation(photoDb.exposureCompensation),
|
formatExposureCompensation(photoDb.exposureCompensation),
|
||||||
recipeData: photoDb.recipeData
|
recipeData: photoDb.recipeData
|
||||||
? JSON.parse(photoDb.recipeData)
|
// Legacy check on escaped, string-based JSON
|
||||||
|
? typeof photoDb.recipeData === 'string'
|
||||||
|
? JSON.parse(photoDb.recipeData)
|
||||||
|
: photoDb.recipeData
|
||||||
: undefined,
|
: undefined,
|
||||||
takenAtNaiveFormatted:
|
takenAtNaiveFormatted:
|
||||||
formatDateFromPostgresString(photoDb.takenAtNaive),
|
formatDateFromPostgresString(photoDb.takenAtNaive),
|
||||||
|
|||||||
@ -2,7 +2,10 @@ import {
|
|||||||
getExtensionFromStorageUrl,
|
getExtensionFromStorageUrl,
|
||||||
getIdFromStorageUrl,
|
getIdFromStorageUrl,
|
||||||
} from '@/platforms/storage';
|
} from '@/platforms/storage';
|
||||||
import { convertExifToFormData } from '@/photo/form';
|
import {
|
||||||
|
convertExifToFormData,
|
||||||
|
convertFormDataToPhotoDbInsert,
|
||||||
|
} from '@/photo/form';
|
||||||
import {
|
import {
|
||||||
getFujifilmSimulationFromMakerNote,
|
getFujifilmSimulationFromMakerNote,
|
||||||
} from '@/platforms/fujifilm/simulation';
|
} from '@/platforms/fujifilm/simulation';
|
||||||
@ -19,6 +22,7 @@ import {
|
|||||||
FujifilmRecipe,
|
FujifilmRecipe,
|
||||||
getFujifilmRecipeFromMakerNote,
|
getFujifilmRecipeFromMakerNote,
|
||||||
} from '@/platforms/fujifilm/recipe';
|
} from '@/platforms/fujifilm/recipe';
|
||||||
|
import { getRecipeTitleForData } from './db/query';
|
||||||
const IMAGE_WIDTH_RESIZE = 200;
|
const IMAGE_WIDTH_RESIZE = 200;
|
||||||
const IMAGE_WIDTH_BLUR = 200;
|
const IMAGE_WIDTH_BLUR = 200;
|
||||||
|
|
||||||
@ -192,3 +196,18 @@ export const removeGpsData = async (image: ArrayBuffer) =>
|
|||||||
})
|
})
|
||||||
.toFormat('jpeg', { quality: PRESERVE_ORIGINAL_UPLOADS ? 95 : 80 })
|
.toFormat('jpeg', { quality: PRESERVE_ORIGINAL_UPLOADS ? 95 : 80 })
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
|
|
||||||
|
export const convertFormDataToPhotoDbInsertAndLookupRecipeTitle =
|
||||||
|
async (...args: Parameters<typeof convertFormDataToPhotoDbInsert>):
|
||||||
|
Promise<ReturnType<typeof convertFormDataToPhotoDbInsert>> => {
|
||||||
|
const photo = convertFormDataToPhotoDbInsert(...args);
|
||||||
|
|
||||||
|
if (photo.recipeData && !photo.recipeTitle) {
|
||||||
|
const recipeTitle = await getRecipeTitleForData(photo.recipeData);
|
||||||
|
if (recipeTitle) {
|
||||||
|
photo.recipeTitle = recipeTitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return photo;
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user