# Converting uploaded video and saving preview images

I'm coding a basic video upload system for a client. The uploaded videos will live on the page using the HTML5 video element.

Essentially what I've done is call the following PHP script asynchronously from the upload page when the video is uploaded (the upload page, meanwhile, displays a message that their video is converting and will be viewable shortly). Using FFmpeg (a static build), it saves the uploaded video twice, once as a .mp4 and again as a .webm, to cover as many browsers as possible. It also saves a scaled and cropped .png as a preview image of the video.

Once everything is properly saved, it inserts the video into a database. I haven't added this bit yet, but it will also email the user when their video is uploaded with a link to its individual page.

The whole thing is running on an Ubuntu server.

I haven't done anything like this before, so I'm just sort of running on adrenaline and hope right now. I'm probably doing some things fairly inefficiently, or else just stupidly. I'm working in a digital design agency right out of college (from a course that was WAY more focused on front-end than back-end) with no senior developer or similar person to oversee my work, so I really have very little frame of reference for knowing if I'm putting this together in a reasonably decent way.

<?php
// variables are passed in this order: tempPath, newPath, email, language, basePath
$tempPath =$argv[1];
$newPath =$argv[2];
$basePath =$argv[5];

/*****************************************************************
CONVERT & SAVE VIDEOS
*****************************************************************/

// create a short 5-second version of the video in order to get the height and width
// we do it this way because FFmpeg will automatically rotate outputted video to conform to rotation exifdata
// so we're getting the height and width of the rotated video, not the un-rotated video
shell_exec($basePath . '/libraries/ffmpeg/ffmpeg -i ' .$tempPath . ' -c:v libx264 -t 5 ' . $newPath . 'orient.mp4'); // use FFprobe to get information about the video returned back to us in JSON format$output = shell_exec($basePath . '/libraries/ffmpeg/ffprobe -i ' .$newPath . 'orient.mp4 -show_streams -print_format json');
// decode the contents into an associative array
$encoded = json_decode($output, true);

// define the width and height of the video in order to determine if the video is portrait or landscape
// if height is larger than width, we'll size height down to 1280, and vice versa
$width =$encoded['streams'][0]['width'];
$height =$encoded['streams'][0]['height'];

if(!(isset($width) && isset($height))){
die('Could not get dimensions');
} else {
if($width >$height){
// landscape
$scale = '1280:-2'; } else{ // portrait or square$scale = '-2:1280';
}
}

// just in case, set permissions to make sure we have permission to delete the orientation file
chmod($basePath . '/spinnies/' . basename($newPath . 'orient.mp4'), 0777);
// delete the orientation file
unlink($basePath . '/spinnies/' . basename($newPath . 'orient.mp4'));

// use FFmpeg to save the video as a scaled mp4
shell_exec($basePath . '/libraries/ffmpeg/ffmpeg -i ' .$tempPath . ' -c:v libx264 -vf scale=' . $scale . ' -t 15 ' .$newPath . 'mp4');
// use FFmpeg to save the video as a scaled webm
shell_exec($basePath . '/libraries/ffmpeg/ffmpeg -i ' .$tempPath . ' -c:v libvpx -vf scale=' . $scale . ' -t 15 ' .$newPath . 'webm');

// just in case, set permissions to make sure we have permission to delete the temp file
chmod($basePath . '/temp_videos/' . basename($tempPath), 0777);
// delete the temp file
unlink($basePath . '/temp_videos/' . basename($tempPath));

/*****************************************************************
SCALE PNG
*****************************************************************/

// use FFprobe to get information about the new video returned back to us in JSON format
$output = shell_exec($basePath . '/libraries/ffmpeg/ffprobe -i ' . $newPath . 'mp4 -show_streams -print_format json'); // decode the contents into an associative array$encoded = json_decode($output, true); // define the width and height of the video // we'll use these along with some math to determine our values for cropping the preview image$width = $encoded['streams'][0]['width'];$height = $encoded['streams'][0]['height']; if(intval($height) < 480){
// too short
$scale = '-2:480'; } else if(intval($width) < 640){
// too narrow, or no problem
$scale = '640:-2'; } // use FFmpeg to save a frame from the video as a scaled png shell_exec($basePath . '/libraries/ffmpeg/ffmpeg -ss 00:00:03 -i ' . $newPath . 'mp4 -frames:v 1 -vf scale=' .$scale . ' ' . $newPath . 'img.png'); /***************************************************************** CROP PNG *****************************************************************/ // use FFprobe to get information about the new video returned back to us in JSON format$output = shell_exec($basePath . '/libraries/ffmpeg/ffprobe -i ' .$newPath . 'img.png -show_streams -print_format json');
// decode the contents into an associative array
$encoded = json_decode($output, true);

// define the width and height of the video
// we'll use these along with some math to determine our values for cropping the preview image
$width =$encoded['streams'][0]['width'];
$height =$encoded['streams'][0]['height'];

// if the image is landscape, we want to crop it from the sides inward
// if the image is portrait, we want to crop it from the top and bottom inward
if(intval($height) < intval($width)){
// landscape
$offsetY = 0; // calculate the cropping offset$widthDiff = intval($width) - 480;$offsetX = $widthDiff / 2; } else { // portrait or square$offsetX = 0;

// calculate the cropping offset
$heightDiff = intval($height) - 480;
$offsetY =$heightDiff / 2;
}

// use FFmpeg to save the scaled png as a cropped png
shell_exec($basePath . '/libraries/ffmpeg/ffmpeg -i ' .$newPath . 'img.png -vf crop=640:480:' . $offsetX . ':' .$offsetY . ' ' . $newPath . 'png'); // just in case, set permissions to make sure we have permission to delete the temp file chmod($basePath . '/temp_videos/' . basename($tempPath), 0777); // delete the temporary image unlink($basePath . '/spinnies/' . $newPath . 'img.png'; /***************************************************************** SAVE TO DATABASE *****************************************************************/ // get the file extension of the spinnie name in order to remove it$file_ext = explode('.', $tempPath) ;$file_ext = end($file_ext); // define our variables to input to the database$spinnie_path = rtrim(basename($tempPath,$file_ext), '.');
$spinnie_date = date('Y-m-d H:i:s');$spinnie_email = $argv[3];$spinnie_votes = 0;
$spinnie_language =$argv[4];

// include the database connection
include('connection.php');

// insert spinnie information into the database
$stmt =$mysqli->prepare("INSERT INTO spinnies (spinnie_path, spinnie_date, spinnie_email, spinnie_votes, spinnie_language) VALUES (?, ?, ?, ?, ?)");
$stmt->bind_param("sssis",$spinnie_path, $spinnie_date,$spinnie_email, $spinnie_votes,$spinnie_language);

$stmt->execute();$stmt->close();
?>


• if(!(isset($width) && isset($height))){ could simply contain !isset($width,$height)
• I would refrain from using die. It's not user friendly and can easily screw up the entire process (the script has started a file upload but the die prevents it from finishing successfully or unsuccessfully).
• I'm not sure where \$argv is coming from, but just in case you aren't already: protect your system calls and either validate or sanitize this data. Preferably validate it, because sanitizing can alter user info which doesn't necessarily pose a threat.