4
\$\begingroup\$

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();
?>
\$\endgroup\$
1
\$\begingroup\$

I'm not familiar with video uploading, just as you are. However, I'll offer some advice on the way things look.

  • First off, I highly suggest you break up your code into separate files. You currently are breaking things apart with a comment, but to insure less confusing code in the future, I suggest you make distinct files and call them only when needed. There are ways to make this OOP, but it'd be smarter to use a well known framework to do it that way.

  • 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.

Hopefully someone with more file management experience can comment on the actual intent of your script.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.