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:
parent
aa0677090b
commit
a8978373e0
4 changed files with 454 additions and 0 deletions
135
scripts/analyze-image-colors.ts
Normal file
135
scripts/analyze-image-colors.ts
Normal 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)
|
||||
63
scripts/check-photo-colors.ts
Normal file
63
scripts/check-photo-colors.ts
Normal 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()
|
||||
89
scripts/find-image-colors.ts
Normal file
89
scripts/find-image-colors.ts
Normal 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
167
scripts/reanalyze-colors.ts
Executable 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)
|
||||
Loading…
Reference in a new issue