使用 Cloud Functions for Firebase 从视频中获取缩略图 [英] Getting a thumbnail from a video using Cloud Functions for Firebase

查看:14
本文介绍了使用 Cloud Functions for Firebase 从视频中获取缩略图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前拥有的代码:

exports.generateThumbnail = functions.storage.object().onChange(event => {

...

  .then(() => {
    console.log('File downloaded locally to', tempFilePath);
    // Generate a thumbnail using ImageMagick.
    if (contentType.startsWith('video/')) {
      return spawn('convert', [tempFilePath + '[0]', '-quiet', `${tempFilePath}.jpg`]);
    } else if (contentType.startsWith('image/')){
        return spawn('convert', [tempFilePath, '-thumbnail', '200x200', tempFilePath]);

我在控制台中遇到的错误:

The error I get in the console:

Failed AGAIN! { Error: spawn ffmpeg ENOENT
at exports._errnoException (util.js:1026:11)
at Process.ChildProcess._handle.onexit (internal/child_process.js:193:32)
at onErrorNT (internal/child_process.js:359:16)
at _combinedTickCallback (internal/process/next_tick.js:74:11)
at process._tickDomainCallback (internal/process/next_tick.js:122:9)
code: 'ENOENT',
errno: 'ENOENT',
syscall: 'spawn ffmpeg',
path: 'ffmpeg',
spawnargs: [ '-t', '1', '-i', '/tmp/myVideo.m4v', 'theThumbs.jpg' ] }

我也试过 Imagemagick:

I also tried Imagemagick:

return spawn('convert', [tempFilePath + '[0]', '-quiet',`${tempFilePath}.jpg`]);

同样没有成功.

谁能给我指出正确的方向吗?

Can anyone point me to the right direction here?

推荐答案

@andrew-robinson 的帖子是一个好的开始.以下将为图像和视频生成缩略图.

@andrew-robinson post was a good start. The following will generate a thumbnail for both images and videos.

将以下内容添加到您的 npm 包中:

Add the following to your npm packages:

@ffmpeg-installer/ffmpeg
@google-cloud/storage
child-process-promise
mkdirp
mkdirp-promise

使用以下方法从较大的图像生成缩略图:

Use the following to generate a thumbnail from a larger image:

function generateFromImage(file, tempLocalThumbFile, fileName) {
    const tempLocalFile = path.join(os.tmpdir(), fileName);

    // Download file from bucket.
    return file.download({destination: tempLocalFile}).then(() => {
        console.info('The file has been downloaded to', tempLocalFile);
        // Generate a thumbnail using ImageMagick with constant width and variable height (maintains ratio)
        return spawn('convert', [tempLocalFile, '-thumbnail', THUMB_MAX_WIDTH, tempLocalThumbFile], {capture: ['stdout', 'stderr']});
    }).then(() => {
        fs.unlinkSync(tempLocalFile);
        return Promise.resolve();
    })
}

使用以下方法从视频中生成缩略图:

Use the following to generate a thumbnail from a video:

function generateFromVideo(file, tempLocalThumbFile) {
    return file.getSignedUrl({action: 'read', expires: '05-24-2999'}).then((signedUrl) => {
        const fileUrl = signedUrl[0];
        const promise = spawn(ffmpegPath, ['-ss', '0', '-i', fileUrl, '-f', 'image2', '-vframes', '1', '-vf', `scale=${THUMB_MAX_WIDTH}:-1`, tempLocalThumbFile]);
        // promise.childProcess.stdout.on('data', (data) => console.info('[spawn] stdout: ', data.toString()));
        // promise.childProcess.stderr.on('data', (data) => console.info('[spawn] stderr: ', data.toString()));
        return promise;
    })
}

当视频或图像上传到存储时,将执行以下操作.它确定文件类型,生成临时文件的缩略图,将缩略图上传到存储,然后调用updateDatabase()",这应该是更新数据库的承诺(如果需要):

The following will execute when a video or image is uploaded to storage. It determines the file type, generates the thumbnail to a temp file, uploads the thumbnail to storage, then call 'updateDatabase()' which should be a promise that updates your database (if necessary):

const functions = require('firebase-functions');

const mkdirp = require('mkdirp-promise');
const gcs = require('@google-cloud/storage');
const admin = require('firebase-admin');
const spawn = require('child-process-promise').spawn;
const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;
const path = require('path');
const os = require('os');
const fs = require('fs');

const db = admin.firestore();

// Max height and width of the thumbnail in pixels.
const THUMB_MAX_WIDTH = 384;

const SERVICE_ACCOUNT = '<your firebase credentials file>.json';

const adminConfig = JSON.parse(process.env.FIREBASE_CONFIG);

module.exports = functions.storage.bucket(adminConfig.storageBucket).object().onFinalize(object => {
    const fileBucket = object.bucket; // The Storage bucket that contains the file.
    const filePathInBucket = object.name;
    const resourceState = object.resourceState; // The resourceState is 'exists' or 'not_exists' (for file/folder deletions).
    const metageneration = object.metageneration; // Number of times metadata has been generated. New objects have a value of 1.
    const contentType = object.contentType; // This is the image MIME type
    const isImage = contentType.startsWith('image/');
    const isVideo = contentType.startsWith('video/');

    // Exit if this is a move or deletion event.
    if (resourceState === 'not_exists') {
        return Promise.resolve();
    }
    // Exit if file exists but is not new and is only being triggered
    // because of a metadata change.
    else if (resourceState === 'exists' && metageneration > 1) {
        return Promise.resolve();
    }
    // Exit if the image is already a thumbnail.
    else if (filePathInBucket.indexOf('.thumbnail.') !== -1) {
        return Promise.resolve();
    }
    // Exit if this is triggered on a file that is not an image or video.
    else if (!(isImage || isVideo)) {
        return Promise.resolve();
    }


    const fileDir            = path.dirname(filePathInBucket);
    const fileName           = path.basename(filePathInBucket);
    const fileInfo           = parseName(fileName);
    const thumbFileExt       = isVideo ? 'jpg' : fileInfo.ext;
    let   thumbFilePath      = path.normalize(path.join(fileDir, `${fileInfo.name}_${fileInfo.timestamp}.thumbnail.${thumbFileExt}`));
    const tempLocalThumbFile = path.join(os.tmpdir(), thumbFilePath);
    const tempLocalDir       = path.join(os.tmpdir(), fileDir);
    const generateOperation  = isVideo ? generateFromVideo : generateFromImage;


    // Cloud Storage files.
    const bucket = gcs({keyFilename: SERVICE_ACCOUNT}).bucket(fileBucket);
    const file = bucket.file(filePathInBucket);

    const metadata = {
        contentType: isVideo ? 'image/jpeg' : contentType,
        // To enable Client-side caching you can set the Cache-Control headers here. Uncomment below.
        // 'Cache-Control': 'public,max-age=3600',
    };


    // Create the temp directory where the storage file will be downloaded.
    return mkdirp(tempLocalDir).then(() => {
        return generateOperation(file, tempLocalThumbFile, fileName);
    }).then(() => {
        console.info('Thumbnail created at', tempLocalThumbFile);
        // Get the thumbnail dimensions
        return spawn('identify', ['-ping', '-format', '%wx%h', tempLocalThumbFile], {capture: ['stdout', 'stderr']});
    }).then((result) => {
        const dim = result.stdout.toString();
        const idx = thumbFilePath.indexOf('.');

        thumbFilePath = `${thumbFilePath.substring(0,idx)}_${dim}${thumbFilePath.substring(idx)}`;
        console.info('Thumbnail dimensions:', dim);
        // Uploading the Thumbnail.
        return bucket.upload(tempLocalThumbFile, {destination: thumbFilePath, metadata: metadata});
    }).then(() => {
        console.info('Thumbnail uploaded to Storage at', thumbFilePath);

        const thumbFilename = path.basename(thumbFilePath);

        return updateDatabase(fileDir, fileName, thumbFilename);
    }).then(() => {
        console.info('Thumbnail generated.');

        fs.unlinkSync(tempLocalThumbFile);

        return Promise.resolve();
    })
});

parseName() 应该解析你的文件名格式.至少它应该返回文件的基本名称和扩展名.

parseName() should parse your filename format. At the very least it should return the file's basename and extension.

updateDatabase() 应该返回一个用新生成的缩略图更新数据库的承诺(如果需要).

updateDatabase() should return a promise that updates your database with the newly generated thumbnail (if necessary).

请注意,@ffmpeg-installer/ffmpeg 无需在云函数中直接包含 ffmpeg 二进制文件.

Note that @ffmpeg-installer/ffmpeg removes the need of directly including a ffmpeg binary in your cloud function.

这篇关于使用 Cloud Functions for Firebase 从视频中获取缩略图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆