Extracting a thumbnail image when uploading a video to Firebase Storage
Today, we will extract a thumbnail image from a video, and this method helps the user to know what the content of this video is by only seeing the image.
The work scenario will be like this, the same scenario that was in the previous lesson, but with some simple changes
We will start preparing the project and add some libraries. We will add the same libraries that we used previously, in addition to the ffmpeg library specialized in video processing, through which we will extract the image.
npm install --save @google-cloud/storage
npm install --save mkdirp-promise
npm install --save child-process-promise
npm install --save @ffmpeg-installer/ffmpeg
We define libraries and some constants, and do not forget to upload the key.json file (refer to the previous lesson)
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_';
Then we define the Scale size of the image, if it is 500 = Scale then the width is 500 and the length is 281, and if Scale = 200 then the width is 200 and the length is 143 and so on ..
//scale size ,it will be scaled to 500px X 281px
const scale = 500;
We will now begin by defining a function and adding file paths
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);
Then we define the path for thumbnail and replace the suffix (mp4 for example) with 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);
We add some checks when uploading the file, is the file a video?, Does the file begin with thumb_ ?, Is the file deleted? If it is any of these cases, we will do return null and do nothing.
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;
}
We define the GCS files and create the tmp temporary folder and upload the video to it
// 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(() => {
After uploading the file to tmp we extract the image using ffmpeg via the command, and we gave it the video path tempFilePath , the scale image size and the path of the image to be saved to tempLocalThumbFile
return spawn(ffmpeg, ['-ss', '0', '-i', tempFilePath, '-f', 'image2', '-vframes', '1', '-vf', `scale=${scale}:-1`, tempLocalThumbFile]);
Then we upload the thumbnail to Firebase Storage
}).then(() => {
console.log('Thumbnail created at', tempLocalThumbFile);
// Uploading the Thumbnail.
return bucket.upload(tempLocalThumbFile, { destination: thumbFilePath });
Then we delete the temporary video file and the temporary photo file
}).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);
Now we will generate the links for the video and the image and make this link expire after a long period, for example 2500 year: D
const config = {
action: 'read',
expires: '03-01-2500',
};
return Promise.all([
thumbFile.getSignedUrl(config),
file.getSignedUrl(config),
]);
Finally, we extract the links from the Promise Array and save the links in databases
}).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.'));
We are deploying and trying to upload a video to Firebase Storage
We will find that the image has been saved in Firebase Storage
And we go to the databases and we will see the image link and the video link
The complete code is as follows
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.'));
});
The project is on Github
read also :
Comments
Post a Comment