Last active
August 20, 2024 05:24
-
-
Save tsemachh/f95532dfd9f88f0fa62a355fc620e165 to your computer and use it in GitHub Desktop.
PayloadCms 3.00 Thumbnail for video asset
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import path from 'path' | |
import { fileURLToPath } from 'url' | |
import { type CollectionConfig } from 'payload' | |
import { videoCoverImage } from './hooks/videoCoverImage' | |
/* eslint-enable */ | |
const __filename = fileURLToPath(import.meta.url) | |
const __dirname = path.dirname(__filename) | |
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
// @ts-expect-error | |
const generateURL = ({ collectionSlug, config, filename }: GenerateURLArgs) => { | |
if (filename) { | |
return `${config.serverURL || '/'}${config?.routes?.api || 'api'}/${collectionSlug}/file/${filename}` | |
} | |
return undefined | |
} | |
/* eslint-disable @typescript-eslint/no-explicit-any */ | |
const isVideo = (data: any) => { | |
return data?.mimeType?.toLowerCase().includes('video'); | |
} | |
export const Media: CollectionConfig = { | |
slug: 'media', | |
upload: { | |
staticDir: path.resolve(__dirname, '../../../media'), | |
imageSizes: [ | |
{ | |
name: 'webp', | |
formatOptions: { format: 'webp' }, | |
}, | |
{ | |
name: 'thumbnail', | |
width: 250, | |
formatOptions: { format: 'webp' }, | |
}, | |
{ | |
name: 'medium', | |
width: 800, | |
formatOptions: { format: 'webp', options: { quality: 90 } }, | |
}, | |
{ | |
name: 'large', | |
width: 1200, | |
formatOptions: { format: 'webp' }, | |
}, | |
], | |
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
// @ts-expect-error | |
adminThumbnail: ({ doc: originalDoc }) => { | |
const config = {} | |
if (isVideo(originalDoc)) { | |
return originalDoc.thumbnailURL; | |
} | |
else | |
if ( | |
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
// @ts-expect-error | |
typeof adminThumbnail === 'string' && | |
'sizes' in originalDoc && | |
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
// @ts-expect-error | |
originalDoc.sizes?.[adminThumbnail]?.filename | |
) { | |
return generateURL({ | |
collectionSlug: "media", | |
config, | |
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
// @ts-expect-error | |
filename: originalDoc.sizes?.[adminThumbnail].filename as string, | |
}) | |
} | |
}, | |
}, | |
fields: [ | |
{ | |
name: 'coverImage', | |
type: 'upload', | |
relationTo: 'media', | |
filterOptions: { | |
mimeType: { contains: 'image' }, | |
}, | |
admin: { | |
hidden: true, | |
}, | |
}, | |
], | |
hooks: { | |
beforeChange: [videoCoverImage], | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { withPayload } from '@payloadcms/next/withPayload' | |
/** @type {import('next').NextConfig} */ | |
const nextConfig = { | |
experimental: { | |
serverSourceMaps: true, | |
}, | |
productionBrowserSourceMaps: true, | |
output: 'standalone', | |
serverExternalPackages: ["ffmpeg-static"], | |
} | |
export default withPayload(nextConfig) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"ffmpeg-static": "^5.2.0", | |
"fluent-ffmpeg": "^2.1.3", |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
// @ts-expect-error | |
import ffmpeg from 'fluent-ffmpeg' | |
import ffmpegStatic from 'ffmpeg-static' | |
import fs from 'fs' | |
import { CollectionBeforeChangeHook } from 'payload' | |
ffmpeg.setFfmpegPath(ffmpegStatic) | |
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
// @ts-expect-error | |
async function extractFrame(tempFilePath, outputPath) { | |
return new Promise<void>((resolve, reject) => { | |
ffmpeg() | |
.input(tempFilePath) | |
.videoFilters('select=eq(n\\,0)') // Select first frame | |
.output(outputPath) | |
.on('end', () => { | |
resolve(); | |
}) | |
/* eslint-disable @typescript-eslint/no-explicit-any */ | |
.on('error', (err: any) => { | |
reject(err); | |
}) | |
.run(); | |
}); | |
} | |
export const videoCoverImage: CollectionBeforeChangeHook = async ({ data, req, collection }) => { | |
'use server' | |
if (req.context?.from || !data?.mimeType?.startsWith('video') || data.thumbnailURL) { | |
return data | |
} | |
console.log('<<<<<<<<<< Starting videoCoverImage hook') | |
try { | |
if (req.file) { | |
const tempVideoFilePath = `${collection.upload.staticDir}/tmp_${req.file.name}` | |
const outpuCoverImagetPath = `${collection.upload.staticDir}/coverImage_${req.file.name}.webp` | |
fs.writeFileSync(tempVideoFilePath, req.file.data) | |
// Extract the first frame | |
await extractFrame(tempVideoFilePath, outpuCoverImagetPath) | |
console.log('Finished processing') | |
const coverImageDoc = await req.payload.create({ | |
collection: 'media', | |
data: { title: 'Auto generated cover image for ' + req.file.name }, | |
filePath: outpuCoverImagetPath, | |
context: { from: 'hook' }, | |
}) | |
data.coverImage = coverImageDoc.id | |
data.thumbnailURL = coverImageDoc.sizes.thumbnail.url | |
data.sizes.thumbnail = {"url":data.thumbnailURL} | |
console.log('Cover image created and assigned to media:', coverImageDoc.id) | |
//Remove the temporary files after processing | |
fs.unlinkSync(tempVideoFilePath) | |
fs.unlinkSync(outpuCoverImagetPath) | |
} | |
console.log(data) | |
return data | |
} catch (error) { | |
console.error('<<<<<<<<<< Error processing video cover image:', (error as Error).message) | |
throw new Error('Video cover image processing failed.' + (error as Error).message) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment