chore: add color analysis scripts

- analyze-image-colors.ts: Test color extraction on local images
- check-photo-colors.ts: Verify color data in database
- find-image-colors.ts: Extract colors from Cloudinary URLs
- reanalyze-colors.ts: Bulk reprocess colors for all media

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Justin Edmund 2025-06-19 01:59:36 +01:00
parent aa0677090b
commit a8978373e0
4 changed files with 454 additions and 0 deletions

View file

@ -0,0 +1,135 @@
#!/usr/bin/env tsx
import { PrismaClient } from '@prisma/client'
import { selectBestDominantColor, isGreyColor, analyzeColor } from '../src/lib/server/color-utils'
const prisma = new PrismaClient()
async function analyzeImage(filename: string) {
try {
// Find the image by filename
const media = await prisma.media.findFirst({
where: {
filename: {
contains: filename
}
},
select: {
id: true,
filename: true,
url: true,
dominantColor: true,
colors: true,
width: true,
height: true
}
})
if (!media) {
console.log(`Media not found with filename: ${filename}`)
return
}
console.log('\n=== Image Analysis ===')
console.log(`Filename: ${media.filename}`)
console.log(`URL: ${media.url}`)
console.log(`Current dominant color: ${media.dominantColor}`)
console.log(`Dimensions: ${media.width}x${media.height}`)
if (media.colors && Array.isArray(media.colors)) {
const colors = media.colors as Array<[string, number]>
console.log('\n=== Color Distribution ===')
console.log('Top 15 colors:')
colors.slice(0, 15).forEach(([hex, percentage], index) => {
const isGrey = isGreyColor(hex)
console.log(`${index + 1}. ${hex} - ${percentage.toFixed(2)}%${isGrey ? ' (grey)' : ''}`)
})
console.log('\n=== Color Analysis Strategies ===')
// Try different strategies
const strategies = {
'Default (min 2%, prefer vibrant & bright)': selectBestDominantColor(colors, {
minPercentage: 2,
preferVibrant: true,
excludeGreys: false,
preferBrighter: true
}),
'Exclude greys, prefer bright': selectBestDominantColor(colors, {
minPercentage: 1,
preferVibrant: true,
excludeGreys: true,
preferBrighter: true
}),
'Very low threshold (0.5%), bright': selectBestDominantColor(colors, {
minPercentage: 0.5,
preferVibrant: true,
excludeGreys: false,
preferBrighter: true
}),
'Allow dark colors': selectBestDominantColor(colors, {
minPercentage: 1,
preferVibrant: true,
excludeGreys: false,
preferBrighter: false
}),
'Focus on prominence (5%)': selectBestDominantColor(colors, {
minPercentage: 5,
preferVibrant: false,
excludeGreys: false,
preferBrighter: true
})
}
Object.entries(strategies).forEach(([strategy, color]) => {
const analysis = analyzeColor(color)
console.log(`${strategy}: ${color} | V:${analysis.vibrance.toFixed(2)} B:${analysis.brightness.toFixed(2)}${analysis.isGrey ? ' (grey)' : ''}${analysis.isDark ? ' (dark)' : ''}`)
})
// Show non-grey colors
console.log('\n=== Non-Grey Colors ===')
const nonGreyColors = colors.filter(([hex]) => !isGreyColor(hex))
console.log(`Found ${nonGreyColors.length} non-grey colors out of ${colors.length} total`)
if (nonGreyColors.length > 0) {
console.log('\nTop 10 non-grey colors:')
nonGreyColors.slice(0, 10).forEach(([hex, percentage], index) => {
const analysis = analyzeColor(hex)
console.log(`${index + 1}. ${hex} - ${percentage.toFixed(2)}% | B:${analysis.brightness.toFixed(2)}`)
})
// Look for more vibrant colors deeper in the list
console.log('\n=== All Colors with >0.5% ===')
const significantColors = colors.filter(([_, pct]) => pct > 0.5)
significantColors.forEach(([hex, percentage]) => {
const isGrey = isGreyColor(hex)
// Convert hex to RGB to analyze
const r = parseInt(hex.slice(1, 3), 16)
const g = parseInt(hex.slice(3, 5), 16)
const b = parseInt(hex.slice(5, 7), 16)
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
const saturation = max === 0 ? 0 : (max - min) / max * 100
console.log(`${hex} - ${percentage.toFixed(2)}% | Sat: ${saturation.toFixed(0)}%${isGrey ? ' (grey)' : ''}`)
})
}
} else {
console.log('\nNo color data available for this image')
}
} catch (error) {
console.error('Error:', error)
} finally {
await prisma.$disconnect()
}
}
// Get filename from command line argument
const filename = process.argv[2] || 'B0000295.jpg'
analyzeImage(filename)

View file

@ -0,0 +1,63 @@
#!/usr/bin/env tsx
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function checkPhotoColors() {
try {
// Count total photography media
const totalPhotos = await prisma.media.count({
where: { isPhotography: true }
})
// Count photos with dominant color
const photosWithColor = await prisma.media.count({
where: {
isPhotography: true,
dominantColor: { not: null }
}
})
// Count photos without dominant color
const photosWithoutColor = await prisma.media.count({
where: {
isPhotography: true,
dominantColor: null
}
})
// Get some examples
const examples = await prisma.media.findMany({
where: {
isPhotography: true,
dominantColor: { not: null }
},
select: {
filename: true,
dominantColor: true,
thumbnailUrl: true
},
take: 5
})
console.log('=== Photography Color Analysis ===')
console.log(`Total photography items: ${totalPhotos}`)
console.log(`With dominant color: ${photosWithColor} (${((photosWithColor/totalPhotos)*100).toFixed(1)}%)`)
console.log(`Without dominant color: ${photosWithoutColor} (${((photosWithoutColor/totalPhotos)*100).toFixed(1)}%)`)
if (examples.length > 0) {
console.log('\n=== Examples with dominant colors ===')
examples.forEach(media => {
console.log(`${media.filename}: ${media.dominantColor}`)
})
}
} catch (error) {
console.error('Error:', error)
} finally {
await prisma.$disconnect()
}
}
checkPhotoColors()

View file

@ -0,0 +1,89 @@
import { prisma } from '../src/lib/server/database'
async function findImageColors() {
try {
console.log('Searching for image with filename: B0000295.jpg\n')
// Search in Photo table
console.log('Checking Photo table...')
const photo = await prisma.photo.findFirst({
where: {
filename: 'B0000295.jpg'
},
select: {
id: true,
filename: true,
dominantColor: true,
colors: true,
url: true,
thumbnailUrl: true,
width: true,
height: true,
aspectRatio: true
}
})
if (photo) {
console.log('Found in Photo table:')
console.log('ID:', photo.id)
console.log('Filename:', photo.filename)
console.log('URL:', photo.url)
console.log('Dominant Color:', photo.dominantColor || 'Not set')
console.log('Colors:', photo.colors ? JSON.stringify(photo.colors, null, 2) : 'Not set')
console.log('Dimensions:', photo.width ? `${photo.width}x${photo.height}` : 'Not set')
console.log('Aspect Ratio:', photo.aspectRatio || 'Not set')
} else {
console.log('Not found in Photo table.')
}
// Search in Media table
console.log('\nChecking Media table...')
const media = await prisma.media.findFirst({
where: {
filename: 'B0000295.jpg'
},
select: {
id: true,
filename: true,
originalName: true,
dominantColor: true,
colors: true,
url: true,
thumbnailUrl: true,
width: true,
height: true,
aspectRatio: true,
mimeType: true,
size: true
}
})
if (media) {
console.log('Found in Media table:')
console.log('ID:', media.id)
console.log('Filename:', media.filename)
console.log('Original Name:', media.originalName || 'Not set')
console.log('URL:', media.url)
console.log('Dominant Color:', media.dominantColor || 'Not set')
console.log('Colors:', media.colors ? JSON.stringify(media.colors, null, 2) : 'Not set')
console.log('Dimensions:', media.width ? `${media.width}x${media.height}` : 'Not set')
console.log('Aspect Ratio:', media.aspectRatio || 'Not set')
console.log('MIME Type:', media.mimeType)
console.log('Size:', media.size, 'bytes')
} else {
console.log('Not found in Media table.')
}
if (!photo && !media) {
console.log('\nImage B0000295.jpg not found in either Photo or Media tables.')
}
} catch (error) {
console.error('Error searching for image:', error)
} finally {
await prisma.$disconnect()
}
}
// Run the script
findImageColors()

167
scripts/reanalyze-colors.ts Executable file
View file

@ -0,0 +1,167 @@
#!/usr/bin/env tsx
/**
* Script to reanalyze colors for specific images or all images
* Usage: tsx scripts/reanalyze-colors.ts [options]
*
* Options:
* --id <mediaId> Reanalyze specific media ID
* --grey-only Only reanalyze images with grey dominant colors
* --all Reanalyze all images with color data
* --dry-run Show what would be changed without updating
*/
import { PrismaClient } from '@prisma/client'
import { selectBestDominantColor, isGreyColor } from '../src/lib/server/color-utils'
const prisma = new PrismaClient()
interface Options {
id?: number
greyOnly: boolean
all: boolean
dryRun: boolean
}
function parseArgs(): Options {
const args = process.argv.slice(2)
const options: Options = {
greyOnly: false,
all: false,
dryRun: false
}
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--id':
options.id = parseInt(args[++i])
break
case '--grey-only':
options.greyOnly = true
break
case '--all':
options.all = true
break
case '--dry-run':
options.dryRun = true
break
}
}
return options
}
async function reanalyzeColors(options: Options) {
try {
// Build query
const where: any = {
colors: { not: null }
}
if (options.id) {
where.id = options.id
} else if (options.greyOnly) {
// We'll filter in code since Prisma doesn't support function calls in where
}
// Get media items
const mediaItems = await prisma.media.findMany({
where,
select: {
id: true,
filename: true,
dominantColor: true,
colors: true
}
})
console.log(`Found ${mediaItems.length} media items with color data`)
let updated = 0
let skipped = 0
for (const media of mediaItems) {
if (!media.colors || !Array.isArray(media.colors)) {
skipped++
continue
}
const currentColor = media.dominantColor
const colors = media.colors as Array<[string, number]>
// Skip if grey-only filter and current color isn't grey
if (options.greyOnly && currentColor && !isGreyColor(currentColor)) {
skipped++
continue
}
// Calculate new dominant color
const newColor = selectBestDominantColor(colors, {
minPercentage: 2,
preferVibrant: true,
excludeGreys: false
})
if (newColor !== currentColor) {
console.log(`\n${media.filename}:`)
console.log(` Current: ${currentColor || 'none'}`)
console.log(` New: ${newColor}`)
// Show color breakdown
const topColors = colors.slice(0, 5)
console.log(' Top colors:')
topColors.forEach(([hex, percentage]) => {
const isGrey = isGreyColor(hex)
console.log(` ${hex} - ${percentage.toFixed(1)}%${isGrey ? ' (grey)' : ''}`)
})
if (!options.dryRun) {
// Update media
await prisma.media.update({
where: { id: media.id },
data: { dominantColor: newColor }
})
// Update related photos
await prisma.photo.updateMany({
where: { mediaId: media.id },
data: { dominantColor: newColor }
})
updated++
}
} else {
skipped++
}
}
console.log(`\n✓ Complete!`)
console.log(` Updated: ${updated}`)
console.log(` Skipped: ${skipped}`)
if (options.dryRun) {
console.log(` (Dry run - no changes made)`)
}
} catch (error) {
console.error('Error:', error)
process.exit(1)
} finally {
await prisma.$disconnect()
}
}
// Run the script
const options = parseArgs()
if (!options.id && !options.all && !options.greyOnly) {
console.log('Usage: tsx scripts/reanalyze-colors.ts [options]')
console.log('')
console.log('Options:')
console.log(' --id <mediaId> Reanalyze specific media ID')
console.log(' --grey-only Only reanalyze images with grey dominant colors')
console.log(' --all Reanalyze all images with color data')
console.log(' --dry-run Show what would be changed without updating')
process.exit(1)
}
reanalyzeColors(options)