استخراج صورة مصغرة من فيديو | Cloud Functions

استخراج صورة مصغرة عند رفع فيديو الى Firebase Storage

AbdulAlim Rajjoubمنذ سنة

شرحنا في الدرس السابق عن كيفية توليد الصور المصغرة من صورة ما عند رفعها الى Firebase Storage ,(أنصحك بمتابعته فالطريقة التي نستخدمها هي نفسها ولكن ببعض التغييرات)

واليوم سنقوم باستخراج صورة مصغرة من فيديو , وهذه الطريقة تساعد المستخدم على معرفة ماهو محتوى هذا الفيديو عبر رؤية الصورة فقط.

 

سيناريو العمل سيكون بهذا الشكل,نفس السيناريو الذي كان في الدرس السابق ولكن ببعض التغييرات البسيطة

 

 

نبدأ بتجهيز المشروع ونضيف بعض المكتبات, سنضيف نفس المكتبات التي استعملناها سابقاً بالإضافة الى مكتبة ffmpeg المختصة بمعالجة الفيديو والتي سنقوم باستخراج الصورة عبرها.

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

 نقوم بتعريف المكتبات وبعض الثوابت ولا ننسَ تحميل ملف key.json (راجع الدرس السابق)

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

const mkdirp = require('mkdirp-promise');
// Include a Service Account Key to use a Signed URL
const gcs = require('@google-cloud/storage')({ keyFilename: 'key.json' });

const ffmpeg = require('@ffmpeg-installer/ffmpeg').path;


const spawn = require('child-process-promise').spawn;
const path = require('path');
const os = require('os');
const fs = require('fs');



// Thumbnail prefix added to file names.
const THUMB_PREFIX = 'thumb_';

ثم نقوم بتعريف حجم Scale للصورة,اذا كانت 500 = Scale فسيكون العرض 500 والطول 281,واذا كانت Scale =200 فسيكون العرض 200 والطول 143 وهكذا..

//scale size ,it will be scaled to 500px X 281px
const scale = 500;

نبدأ الآن بتعريف function وإضافة مسارات الملفات

exports.generateThumbnailFromVideo = functions.storage.object().onChange(event => {
  //RAW folder/Video.mp4
  const originalFilePath = event.data.name;

  
  console.log("filePath IS ",originalFilePath);
  

  //FILE DIR "."
  const originalFileDir = path.dirname(originalFilePath);

  console.log("FILE DIR IS ",originalFileDir);

  //Video.mp4
  const originalFileName = path.basename(originalFilePath);

  console.log("FILE NAME IS ",originalFileName);
  

  // /tmp/folder/Video.mp4
  const tempFilePath = path.join(os.tmpdir(), originalFilePath);
  console.log("tempLocalFile IS ",tempFilePath);
  
  // /tmp/folder
  const tempFileDir = path.dirname(tempFilePath);
  console.log("tempLocalDir IS ",tempFileDir);
  

ثم نقوم بتعريف مسار thumbnail ونقوم باستبدال اللاحقة(mp4 مثلاً) ب jpg 

//RAW folder/thumb_video.jpg

  const thumbFilePath = path.normalize(path.join(originalFileDir, `${THUMB_PREFIX}${originalFileName.replace(path.extname(originalFileName),".jpg")}`))
  
  console.log("thumbFilePath IS ",thumbFilePath);
  

  //tmp/thumb_image.jpg  
  const tempLocalThumbFile = path.join(os.tmpdir(), thumbFilePath);
  console.log("tempLocalThumbFile IS ",tempLocalThumbFile);

نضيف بعض التحققات عند رفع الملف ,هل الملف فيديو؟,هل الملف يبدأ ب thumb_ ؟, هل تم حذف الملف؟. اذا كانت اي من هذه الحالات فسنقوم بعمل return null ولا ننفذ أي شيئ.


  if (!event.data.contentType.startsWith('video/')) {
    console.log('This is not a video.');
    return null;
  }


  // Exit if the image is already a thumbnail.
  if (originalFileName.startsWith(THUMB_PREFIX)) {
    console.log('Already a Thumbnail.');
    return null;
  }

  // Exit if this is a move or deletion event.
  if (event.data.resourceState === 'not_exists') {
    console.log('This is a deletion event.');
    return null;
  }

نقوم بتعريف ملفات GCS ونقوم بإنشاء المجلد المؤقت tmp ونقوم بتحميل الفيديو اليه

// Cloud Storage files.
  const bucket = gcs.bucket(event.data.bucket);
  const file = bucket.file(originalFilePath);
  const thumbFile = bucket.file(thumbFilePath);
  
  console.log("thumbFile is ",thumbFile);

  // Create the temp directory where the storage file will be downloaded.
  return mkdirp(tempFileDir).then(() => {
    // Download file from bucket.
    return file.download({ destination: tempFilePath });
  }).then(() => {

بعد تحميل الملف الى tmp نقوم باستخراج الصورة باستخدام ffmpeg عبر الأمر ,وأعطيناه مسار الفيديو tempFilePath و حجم الصورة scale  و مسار الصورة الذي سيتم الحفظ اليها tempLocalThumbFile

return spawn(ffmpeg, ['-ss', '0', '-i', tempFilePath, '-f', 'image2', '-vframes', '1', '-vf', `scale=${scale}:-1`, tempLocalThumbFile]);

ثم نرفع الصورة المصغرة الى Firebase Storage 

 }).then(() => {

    console.log('Thumbnail created at', tempLocalThumbFile);
    // Uploading the Thumbnail.
    return bucket.upload(tempLocalThumbFile, { destination: thumbFilePath });

ثم نحذف ملف الفيديو المؤقت وملف الصورة المؤقت 

}).then(() => {
    console.log('Thumbnail uploaded to Storage at', thumbFilePath);
    // Once the image has been uploaded delete the local files to free up disk space.
    fs.unlinkSync(tempFilePath);
    fs.unlinkSync(tempLocalThumbFile);

الآن سنقوم بتوليد الروابط للفيديو وللصورة ونجعل هذا الرابط تنتهي صلاحيته بعد فترة طويلة ,سنة 2500 مثلا :D

    const config = {
      action: 'read',
      expires: '03-01-2500',
    };
    return Promise.all([
      thumbFile.getSignedUrl(config),
      file.getSignedUrl(config),
    ]);

أخيراً نستخرج الروابط من Promise Array ونقوم بحفظ الروابط في الداتابيز

 }).then((results) => {
    console.log('Got Signed URLs.');
    const thumbResult = results[0];
    const originalResult = results[1];
    const thumbFileUrl = thumbResult[0];
    const fileUrl = originalResult[0];
    // Add the URLs to the Database
    return admin.database().ref('videos').push({ videoUrl: fileUrl, thumbnailUrl: thumbFileUrl });
  }).then(() => console.log('Thumbnail URLs saved to database.'));

 

نقوم بعمل deploy ونجرب نرفع فيديو ما الى Firebase Storage 

سنجد أنه تم حفظ الصورة في Firebase Storage

ونذهب الى الداتابيز وسنرى رابط الصورة ورابط الفيديو

 

الكود كاملاً كالتالي

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

const mkdirp = require('mkdirp-promise');
// Include a Service Account Key to use a Signed URL
const gcs = require('@google-cloud/storage')({ keyFilename: 'key.json' });

const ffmpeg = require('@ffmpeg-installer/ffmpeg').path;


const spawn = require('child-process-promise').spawn;
const path = require('path');
const os = require('os');
const fs = require('fs');



// Thumbnail prefix added to file names.
const THUMB_PREFIX = 'thumb_';

//scale size ,it will be scaled to 500px X 281px
const scale = 500;

exports.generateThumbnailFromVideo = functions.storage.object().onChange(event => {
  //RAW folder/Video.mp4
  const originalFilePath = event.data.name;

  
  console.log("filePath IS ",originalFilePath);
  

  //FILE DIR "."
  const originalFileDir = path.dirname(originalFilePath);

  console.log("FILE DIR IS ",originalFileDir);

  //Video.mp4
  const originalFileName = path.basename(originalFilePath);

  console.log("FILE NAME IS ",originalFileName);
  

  // /tmp/folder/Video.mp4
  const tempFilePath = path.join(os.tmpdir(), originalFilePath);
  console.log("tempLocalFile IS ",tempFilePath);
  
  // /tmp/folder
  const tempFileDir = path.dirname(tempFilePath);
  console.log("tempLocalDir IS ",tempFileDir);
  
//RAW folder/thumb_video.jpg

  const thumbFilePath = path.normalize(path.join(originalFileDir, `${THUMB_PREFIX}${originalFileName.replace(path.extname(originalFileName),".jpg")}`))
  
  console.log("thumbFilePath IS ",thumbFilePath);
  

  //tmp/thumb_image.jpg  
  const tempLocalThumbFile = path.join(os.tmpdir(), thumbFilePath);
  console.log("tempLocalThumbFile IS ",tempLocalThumbFile);
  

  if (!event.data.contentType.startsWith('video/')) {
    console.log('This is not a video.');
    return null;
  }


  // Exit if the image is already a thumbnail.
  if (originalFileName.startsWith(THUMB_PREFIX)) {
    console.log('Already a Thumbnail.');
    return null;
  }

  // Exit if this is a move or deletion event.
  if (event.data.resourceState === 'not_exists') {
    console.log('This is a deletion event.');
    return null;
  }

  // Cloud Storage files.
  const bucket = gcs.bucket(event.data.bucket);
  const file = bucket.file(originalFilePath);
  const thumbFile = bucket.file(thumbFilePath);
  
  console.log("thumbFile is ",thumbFile);

  // Create the temp directory where the storage file will be downloaded.
  return mkdirp(tempFileDir).then(() => {
    // Download file from bucket.
    return file.download({ destination: tempFilePath });
  }).then(() => {


    console.log('The file has been downloaded to', tempFilePath);

    return spawn(ffmpeg, ['-ss', '0', '-i', tempFilePath, '-f', 'image2', '-vframes', '1', '-vf', `scale=${scale}:-1`, tempLocalThumbFile]);
    
  }).then(() => {



    console.log('Thumbnail created at', tempLocalThumbFile);
    // Uploading the Thumbnail.
    return bucket.upload(tempLocalThumbFile, { destination: thumbFilePath });

  }).then(() => {
    console.log('Thumbnail uploaded to Storage at', thumbFilePath);
    // Once the image has been uploaded delete the local files to free up disk space.
    fs.unlinkSync(tempFilePath);
    fs.unlinkSync(tempLocalThumbFile);
    const config = {
      action: 'read',
      expires: '03-01-2500',
    };
    return Promise.all([
      thumbFile.getSignedUrl(config),
      file.getSignedUrl(config),
    ]);
  }).then((results) => {
    console.log('Got Signed URLs.');
    const thumbResult = results[0];
    const originalResult = results[1];
    const thumbFileUrl = thumbResult[0];
    const fileUrl = originalResult[0];
    // Add the URLs to the Database
    return admin.database().ref('videos').push({ videoUrl: fileUrl, thumbnailUrl: thumbFileUrl });
  }).then(() => console.log('Thumbnail URLs saved to database.'));
});

 

المشروع على Github

 

كلمات دليلية: firebase functions storage
1
إعجاب
783
مشاهدات
0
مشاركة
2
متابع
متميز
محتوى رهيب

التعليقات (0)

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !