Adding a watermark to a video via ffmpeg

The php web script that I’m using allows the admin panel to add a url, pointing to an image, that will appear as a watermark on the video when it plays on the site, but when the video is downloaded, it has no watermark (it appears). So, my question is, can the ffmpeg code be modified so that a watermark appears with the downloaded video?

Googled for “ffmpeg overlay”, first hit: https://gist.github.com/bennylope/d5d6029fb63648582fed2367ae23cfd6

Thank you for your reply.
I’ve looked over the (below) file (…/ajax/upload-video-ffmpeg.php), from the web script that I’m using (but didn’t write), but am not sure if it is the right file to add a modification, to add a watermark upon every video upload. Any comments will be welcomed.

<?php 

$max_user_upload = $pt->config->user_max_upload;

if (IS_LOGGED == false || $pt->config->upload_system != 'on') {
    $data = array('status' => 400, 'error' => 'Not logged in');
    echo json_encode($data);
    exit();
}

else if ($pt->user->is_pro != 1 && $pt->user->uploads >= $max_user_upload && $pt->config->go_pro == 1){
    $data = array('status' => 401);
    echo json_encode($data);
    exit();
}
else if($pt->config->ffmpeg_system != 'on'){
    $data = array('status' => 402);
    echo json_encode($data);
    exit();
}

else{
    if (!empty($_FILES['video']['tmp_name'])) {
        if ($_FILES['video']['size'] > $pt->config->max_upload) {
            $max  = pt_size_format($pt->config->max_upload);
            $data = array('status' => 402,'message' => ($lang->file_is_too_big .": $max"));
            echo json_encode($data);
            exit();
        }

        $file_info    = array(
            'file'    => $_FILES['video']['tmp_name'],
            'size'    => $_FILES['video']['size'],
            'name'    => $_FILES['video']['name'],
            'type'    => $_FILES['video']['type'],
            'allowed' => 'mp4,mov,webm,mpeg,3gp,avi,flv,ogg,mkv,mk3d,mks,wmv'
        );

        $default_amazon = $pt->config->s3_upload;

        $pt->config->s3_upload = 'off';
        $pt->config->ftp_upload = 'off';
        $file_upload   = PT_ShareFile($file_info);
        $getID3        = new getID3;
        $images        = array();
        $video         = false;

        if (!empty($file_upload['filename'])) {
            $analyze   = $getID3->analyze($file_upload['filename']);
            if (empty($analyze['error']) && !empty($analyze['fileformat'])) {
                $video = $analyze;
            }

            $filename  = $file_upload['filename'];
            $_SESSION['uploads']['videos'][] = $filename;
        }

        if (!empty($video)) {

            $pt->config->s3_upload = $default_amazon;
            $ffmpeg_b      = $pt->config->ffmpeg_binary_file;
            $total_seconds = ffmpeg_duration($filename);
            $thumb_1_duration = (int) ($total_seconds > 10) ? 11 : 1;
            $thumb_2_duration = (int) ($total_seconds > 24) ? 25 : 15;
            $thumb_3_duration = (int) $total_seconds - 1;
            $thumb_4_duration = (int) $total_seconds / 2;
            $thumb_5_duration = (int) ($total_seconds / 2) * 1.3;
            $thumb_6_duration = (int) $total_seconds / 3;
            $uniq_id = rand(1111,9999);
            $img_pos       = array($thumb_1_duration, $thumb_2_duration, $thumb_3_duration, $thumb_4_duration, $thumb_5_duration, $thumb_6_duration);
            if (!file_exists('upload/photos/' . date('Y'))) {
                @mkdir('upload/photos/' . date('Y'), 0777, true);
            }

            if (!file_exists('upload/photos/' . date('Y') . '/' . date('m'))) {
                @mkdir('upload/photos/' . date('Y') . '/' . date('m'), 0777, true);
            }
            $dir      = "upload/photos/" . date('Y') . '/' . date('m');
            foreach ($img_pos as $i) { 
                $hash     = sha1(time() + time() - rand(9999,9999)) . PT_GenerateKey();

                $thumb    = "$dir/$hash.video_thumb_$uniq_id" . "_$i.jpeg";
                $full_dir = str_replace('ajax', '/', __DIR__);

                $input_path = $full_dir . $file_upload['filename'];
                $output_path = $full_dir . $thumb;

                $output_thumb = shell_exec("$ffmpeg_b -ss \"$i\" -i $input_path -vframes 1 -f mjpeg $output_path 2<&1");

                if (file_exists($thumb) && !empty(getimagesize($thumb))) {
                    PT_Resize_Crop_Image(1076, 604, $thumb, $thumb, 80);
                    $images[] = $thumb;
                    $_SESSION['uploads']['images'][] = $thumb;
                } else {
                    @unlink($thumb);
                }
            }

            $data['status']    = 200; 
            $data['file_path'] = $filename; 
            $data['file_name'] = $file_upload['name'];
            $data['images']    = $images;

            $update = array(
                'uploads' => ($pt->user->uploads += $file_info['size'])
            );

            $db->where('id',$pt->user->id)->update(T_USERS,$update);
        } 

        else if (!empty($file_upload['error'])) {
            $data = array('status' => 400, 'error' => $file_upload['error']);
        }
    }
}

?>

Looks like ffmpeg is only used here to generate thumbnails, not to transcode videos. Maybe that happens elsewhere?

Thanks for your reply. Maybe this file?

<?php
if (IS_LOGGED == false || $pt->config->upload_system != 'on') {
    $data = array(
        'status' => 400,
        'error' => 'Not logged in'
    );
    echo json_encode($data);
    exit();
} else if ($pt->config->ffmpeg_system != 'on') {
    $data = array(
        'status' => 402
    );
    echo json_encode($data);
    exit();
} else {
    $getID3    = new getID3;
    $featured  = ($user->is_pro == 1) ? 1 : 0;
    $filesize  = 0;
    $error     = false;
    $request   = array();
    $request[] = (empty($_POST['title']) || empty($_POST['description']));
    $request[] = (empty($_POST['tags']) || empty($_POST['video-thumnail']));
    if (in_array(true, $request)) {
        $error = $lang->please_check_details;
    } else if (empty($_POST['video-location'])) {
        $error = $lang->video_not_found_please_try_again;
    } else {
        $request   = array();
        $request[] = (!in_array($_POST['video-location'], $_SESSION['uploads']['videos']));
        $request[] = (!in_array($_POST['video-thumnail'], $_SESSION['uploads']['images']));
        $request[] = (!file_exists($_POST['video-location']));
        if (in_array(true, $request)) {
            $error = $lang->error_msg;
        }
    }
    if (empty($error)) {
        $file     = $getID3->analyze($_POST['video-location']);
        $duration = '00:00';
        if (!empty($file['playtime_string'])) {
            $duration = PT_Secure($file['playtime_string']);
        }
        if (!empty($file['filesize'])) {
            $filesize = $file['filesize'];
        }
        $video_res = (!empty($file['video']['resolution_x'])) ? $file['video']['resolution_x'] : 0;
        $video_id        = PT_GenerateKey(15, 15);
        $check_for_video = $db->where('video_id', $video_id)->getValue(T_VIDEOS, 'count(*)');
        if ($check_for_video > 0) {
            $video_id = PT_GenerateKey(15, 15);
        }
        $thumbnail = PT_Secure($_POST['video-thumnail'], 0);
        if (file_exists($thumbnail)) {
            $upload = PT_UploadToS3($thumbnail);
        }
        $category_id = 0;
        $convert     = true;
        $thumbnail   = substr($thumbnail, strpos($thumbnail, "upload"), 120);
        if (!empty($_POST['category_id'])) {
            if (in_array($_POST['category_id'], array_keys($categories))) {
                $category_id = PT_Secure($_POST['category_id']);
            }
        }
        $link_regex = '/(http\:\/\/|https\:\/\/|www\.)([^\ ]+)/i';
        $i          = 0;
        preg_match_all($link_regex, PT_Secure($_POST['description']), $matches);
        foreach ($matches[0] as $match) {
            $match_url            = strip_tags($match);
            $syntax               = '[a]' . urlencode($match_url) . '[/a]';
            $_POST['description'] = str_replace($match, $syntax, $_POST['description']);
        }
        $video_privacy = 0;
        if (!empty($_POST['privacy'])) {
            if (in_array($_POST['privacy'], array(0, 1, 2))) {
                $video_privacy = PT_Secure($_POST['privacy']);
            }
        }
        $age_restriction = 1;
        if (!empty($_POST['age_restriction'])) {
            if (in_array($_POST['age_restriction'], array(1, 2))) {
                $age_restriction = PT_Secure($_POST['age_restriction']);
            }
        }
        $data_insert = array(
            'video_id' => $video_id,
            'user_id' => $user->id,
            'title' => PT_Secure($_POST['title']),
            'description' => PT_Secure($_POST['description']),
            'tags' => PT_Secure($_POST['tags']),
            'duration' => $duration,
            'video_location' => '',
            'category_id' => $category_id,
            'thumbnail' => $thumbnail,
            'time' => time(),
            'registered' => date('Y') . '/' . intval(date('m')),
            'featured' => $featured,
            'converted' => '2',
            'size' => $filesize,
            'privacy' => $video_privacy,
            'age_restriction' => $age_restriction
        );
        if ($pt->config->approve_videos == 'on' && !PT_IsAdmin()) {
            $data_insert['approved'] = 0;
        }
        $insert      = $db->insert(T_VIDEOS, $data_insert);
        if ($insert) {
            $data = array(
                'status' => 200,
                'video_id' => $video_id,
                'link' => PT_Link("watch/$video_id")
            );
            ob_end_clean();
            header("Content-Encoding: none");
            header("Connection: close");
            ignore_user_abort();
            ob_start();
            header('Content-Type: application/json');
            echo json_encode($data);
            $size = ob_get_length();
            header("Content-Length: $size");
            ob_end_flush();
            flush();
            session_write_close();
            if (is_callable('fastcgi_finish_request')) {
                fastcgi_finish_request();
            }
            $ffmpeg_b                   = $pt->config->ffmpeg_binary_file;
            $filepath                   = explode('.', $_POST['video-location'])[0];
            $time                       = time();
            $full_dir                   = str_replace('ajax', '/', __DIR__);

            $video_output_full_path_240 = $full_dir . $filepath . "_240p_converted.mp4";
            $video_output_full_path_360 = $full_dir . $filepath . "_360p_converted.mp4";
            $video_output_full_path_480 = $full_dir . $filepath . "_480p_converted.mp4";
            $video_output_full_path_720 = $full_dir . $filepath . "_720p_converted.mp4";
            $video_output_full_path_1080 = $full_dir . $filepath . "_1080p_converted.mp4";
            $video_output_full_path_2048 = $full_dir . $filepath . "_2048p_converted.mp4";
            $video_output_full_path_4096 = $full_dir . $filepath . "_4096p_converted.mp4";

            $video_file_full_path       = $full_dir . $_POST['video-location'];

            $shell     = shell_exec("$ffmpeg_b -y -i $video_file_full_path -vcodec libx264 -preset {$pt->config->convert_speed} -filter:v scale=426:-2 -crf 26 $video_output_full_path_240 2>&1");
            $upload_s3 = PT_UploadToS3($filepath . "_240p_converted.mp4");
            $db->where('id', $insert);
            $db->update(T_VIDEOS, array(
                'converted' => 1,
                '240p' => 1,
                'video_location' => $filepath . "_240p_converted.mp4"
            ));

            if ($video_res >= 640 || $video_res == 0) {
                $shell                      = shell_exec("$ffmpeg_b -y -i $video_file_full_path -vcodec libx264 -preset {$pt->config->convert_speed} -filter:v scale=640:-2 -crf 26 $video_output_full_path_360 2>&1");
                $upload_s3                  = PT_UploadToS3($filepath . "_360p_converted.mp4");
                $db->where('id', $insert);
                $db->update(T_VIDEOS, array( 
                    '360p' => 1,
                ));
            }

            if ($video_res >= 854 || $video_res == 0) {
                $shell     = shell_exec("$ffmpeg_b -y -i $video_file_full_path -vcodec libx264 -preset {$pt->config->convert_speed} -filter:v scale=854:-2 -crf 26 $video_output_full_path_480 2>&1");
                $upload_s3 = PT_UploadToS3($filepath . "_480p_converted.mp4");
                $db->where('id', $insert);
                $db->update(T_VIDEOS, array(
                    '480p' => 1
                ));
            }

            if ($video_res >= 1280 || $video_res == 0) {
                $shell     = shell_exec("$ffmpeg_b -y -i $video_file_full_path -vcodec libx264 -preset {$pt->config->convert_speed} -filter:v scale=1280:-2 -crf 26 $video_output_full_path_720 2>&1");
                $upload_s3 = PT_UploadToS3($filepath . "_720p_converted.mp4");
                $db->where('id', $insert);
                $db->update(T_VIDEOS, array(
                    '720p' => 1
                ));
            }

            if ($video_res >= 1920 || $video_res == 0) {
                $shell     = shell_exec("$ffmpeg_b -y -i $video_file_full_path -vcodec libx264 -preset {$pt->config->convert_speed} -filter:v scale=1920:-2 -crf 26 $video_output_full_path_1080 2>&1");
                $upload_s3 = PT_UploadToS3($filepath . "_1080p_converted.mp4");
                $db->where('id', $insert);
                $db->update(T_VIDEOS, array(
                    '1080p' => 1
                ));
            }

            if ($video_res >= 2048) {
                $shell     = shell_exec("$ffmpeg_b -y -i $video_file_full_path -vcodec libx264 -preset {$pt->config->convert_speed} -filter:v scale=2048:-2 -crf 26 $video_output_full_path_2048 2>&1");
                $upload_s3 = PT_UploadToS3($filepath . "_2048p_converted.mp4");
                $db->where('id', $insert);
                $db->update(T_VIDEOS, array(
                    '2048p' => 1
                ));
            }

            if ($video_res >= 3840) {
                $shell     = shell_exec("$ffmpeg_b -y -i $video_file_full_path -vcodec libx264 -preset {$pt->config->convert_speed} -filter:v scale=3840:-2 -crf 26 $video_output_full_path_4096 2>&1");
                $upload_s3 = PT_UploadToS3($filepath . "_4096p_converted.mp4");
                $db->where('id', $insert);
                $db->update(T_VIDEOS, array(
                    '4096p' => 1
                ));
            }


            if (file_exists($_POST['video-location'])) {
                unlink($_POST['video-location']);
            }

            if (!empty($_SESSION['uploads']['images'])) {
                if (is_array($_SESSION['uploads']['images'])) {
                    foreach ($_SESSION['uploads']['images'] as $key => $file) {
                        if ($thumbnail == $file) {
                            unset($_SESSION['uploads']['images'][$key]);
                        } else {
                            //@unlink($file);
                        }
                    }
                    $_SESSION['uploads']['images'] = array();
                }
            }

            pt_push_channel_notifiations($video_id);
            $_SESSION['uploads'] = array();
            exit();
        }
    } else {
        $data = array(
            'status' => 400,
            'message' => $error_icon . $error
        );
    }
}

Hi there ChrisjChris,

I have to ask you this, what appears to me, obvious question…

“If you want to have a watermark on a video when it is downloaded
by a visitor, why don’t you put it on with video editing software?”

coothead

Thanks for your reply. I am looking for some tweak to the existing script to automatically add a watermark to each uploaded video.

You appear to have moved the goalposts. :eek:

In your original post you wrote…

Either way, I don’t see how javascript could permanently modify
a video file. :unhappy:

If you like, you could give me links to the video and the watermark
image and I will insert it for you. :winky:

coothead

Thanks for your reply, but it is not a single video that I need an image inserted.

Well, I can do more than one, you know - ( providing that I don’t die ). :biggrin:

coothead

1 Like

In my researching this I have found “Once you have ffmpeg installed, adding a watermark is as easy as passing your existing source through an overlay filter like so:
ffmpeg -i birds.mp4 -i watermark.png -filter_complex “overlay=10:10” birds1.mp4”
So, it sounds like a simple task, but I’m not sure where to add this. The script has a file that looks like it is ffmpeg.ffmpeg, that may be the file, but I don’t know how to open it. Any additional guidance will be appreciated

You have several lines in that latest code that performs a shell call to ffmpeg, couldn’t you change those to add whatever extra parameters are required to add the watermark?

$shell = shell_exec("$ffmpeg_b -y -i $video_file_full_path -vcodec libx264 -preset {$pt->config->convert_speed} -filter:v scale=426:-2 -crf 26 $video_output_full_path_240 2>&1");

and so on.

Thanks for your reply. Greatly appreciated. Yes, I think you’re right, but I don’t know how to replace this: $video_file_full_path

something like this?

$shell = shell_exec("$ffmpeg_b -y -i ../themes/default/img/watermark1.png -vcodec libx264 -preset {$pt->config->convert_speed} -filter:v scale=426:-2 -crf 26 $video_output_full_path_240 2>&1");

If you look at the sample you put in your post #11 above, it seems that you have to provide two files preceded by -i, which I presume (it’ll be in the doc or help files) marks them as input files. You obviously have to still specify the video filename, otherwise how would it know which video to apply it to?

I’m guessing you need to add the second filename as -i watermarkfilename.jpg and add the bit about overlaying it (the bit that starts -filter_complex). I’m guessing the final parameter is the output filename.

You could experiment with adding those two parameters with a hard-coded watermark filename and see what happens. If it works, then you can add in the code to get it from the database, and how to handle having the watermark of an appropriate size for the video.

1 Like

That is a Command Prompt. :winky:

So you need to set up your ffmpeg to use it…

How do I set up and use ffmpeg in Windows?

…should, hopefully, point you in the right direction. :biggrin:

coothead

Thanks for your reply, but I’m trying to add to php script on that runs on a linux server, this is not about windows

In your post #11 you drew our attention to this…

ffmpeg -i birds.mp4 -i watermark.png -filter_complex “overlay=10:10” birds1.mp4

…which is an input for the “Windows Command prompt”

Why did you post that when your actual requirements
were, unknown to me, for Linux ? :rolleyes:

Be that as it may, these links may possibly help you…

  1. how to install FFmpeg on various Linux distributions
  2. How to Use the Linux Command Line
  3. 20 FFmpeg Commands For Beginners

coothead

Thanks for all the replies.
This script file has these lines already:

                $input_path = $full_dir . $file_upload['filename'];
               
                $output_path = $full_dir . $thumb;

                $output_thumb = shell_exec("$ffmpeg_b -ss \"$i\" -i $input_path -vframes 1 -f mjpeg $output_path 2<&1");

And the script successfully uploads videos . So, I added this, without success:


                $shell = shell_exec("$ffmpeg_b -y -i $input_path -i ../themes/default/img/watermark1.png -vcodec libx264 -preset {$pt->config->convert_speed} -filter:v scale=426:-2 -crf 26 $video_output_full_path_240 2>&1");

Any additional guidance/suggestions will be appreciated.

The command line you showed in post #11 includes the option -filter_complex which is followed by what looks like co-ordinates, so I’d guess that is also required. A quick search for “ffmpeg add watermark” leads me to this page, which explains that you have to add two extra command parameters - the watermark graphic file name, and the filter to show where to overlay it:

http://ksloan.net/watermarking-videos-from-the-command-line-using-ffmpeg-filters/

You seem to have added one of them, but not the other, so the command doesn’t know what to do with the second input file.

Thanks for your reply.

I have added the line of code discussed in your link:

ffmpeg -i video.mp4 -i watermark1.png -filter_complex "overlay=main_w-overlay_w-10:main_h-overlay_h-10" bottom_left.mp4"

to this php file code(line 86):

<?php 

$max_user_upload = $pt->config->user_max_upload;

if (IS_LOGGED == false || $pt->config->upload_system != 'on') {
    $data = array('status' => 400, 'error' => 'Not logged in');
    echo json_encode($data);
    exit();
}

else if ($pt->user->is_pro != 1 && $pt->user->uploads >= $max_user_upload && $pt->config->go_pro == 1){
    $data = array('status' => 401);
    echo json_encode($data);
    exit();
}
else if($pt->config->ffmpeg_system != 'on'){
    $data = array('status' => 402);
    echo json_encode($data);
    exit();
}

else{
    if (!empty($_FILES['video']['tmp_name'])) {
        if ($_FILES['video']['size'] > $pt->config->max_upload) {
            $max  = pt_size_format($pt->config->max_upload);
            $data = array('status' => 402,'message' => ($lang->file_is_too_big .": $max"));
            echo json_encode($data);
            exit();
        }

        $file_info    = array(
            'file'    => $_FILES['video']['tmp_name'],
            'size'    => $_FILES['video']['size'],
            'name'    => $_FILES['video']['name'],
            'type'    => $_FILES['video']['type'],
            'allowed' => 'mp4,mov,webm,mpeg,3gp,avi,flv,ogg,mkv,mk3d,mks,wmv'
        );

        $default_amazon = $pt->config->s3_upload;

        $pt->config->s3_upload = 'off';
        $pt->config->ftp_upload = 'off';
        $file_upload   = PT_ShareFile($file_info);
        $getID3        = new getID3;
        $images        = array();
        $video         = false;

        if (!empty($file_upload['filename'])) {
            $analyze   = $getID3->analyze($file_upload['filename']);
            if (empty($analyze['error']) && !empty($analyze['fileformat'])) {
                $video = $analyze;
            }

            $filename  = $file_upload['filename'];
            $_SESSION['uploads']['videos'][] = $filename;
        }

        if (!empty($video)) {

            $pt->config->s3_upload = $default_amazon;
            $ffmpeg_b      = $pt->config->ffmpeg_binary_file;
            $total_seconds = ffmpeg_duration($filename);
            $thumb_1_duration = (int) ($total_seconds > 10) ? 11 : 1;
            $thumb_2_duration = (int) ($total_seconds > 24) ? 25 : 15;
            $thumb_3_duration = (int) $total_seconds - 1;
            $thumb_4_duration = (int) $total_seconds / 2;
            $thumb_5_duration = (int) ($total_seconds / 2) * 1.3;
            $thumb_6_duration = (int) $total_seconds / 3;
            $uniq_id = rand(1111,9999);
            $img_pos       = array($thumb_1_duration, $thumb_2_duration, $thumb_3_duration, $thumb_4_duration, $thumb_5_duration, $thumb_6_duration);
            if (!file_exists('upload/photos/' . date('Y'))) {
                @mkdir('upload/photos/' . date('Y'), 0777, true);
            }

            if (!file_exists('upload/photos/' . date('Y') . '/' . date('m'))) {
                @mkdir('upload/photos/' . date('Y') . '/' . date('m'), 0777, true);
            }
            $dir      = "upload/photos/" . date('Y') . '/' . date('m');
            foreach ($img_pos as $i) { 
                $hash     = sha1(time() + time() - rand(9999,9999)) . PT_GenerateKey();

                $thumb    = "$dir/$hash.video_thumb_$uniq_id" . "_$i.jpeg";
                $full_dir = str_replace('ajax', '/', __DIR__);

                $input_path = $full_dir . $file_upload['filename'];
                ffmpeg -i video.mp4 -i watermark1.png -filter_complex "overlay=main_w-overlay_w-10:main_h-overlay_h-10" bottom_left.mp4"
                $output_path = $full_dir . $thumb;

                $output_thumb = shell_exec("$ffmpeg_b -ss \"$i\" -i $input_path -vframes 1 -f mjpeg $output_path 2<&1");

                if (file_exists($thumb) && !empty(getimagesize($thumb))) {
                    PT_Resize_Crop_Image(1076, 604, $thumb, $thumb, 80);
                    $images[] = $thumb;
                    $_SESSION['uploads']['images'][] = $thumb;
                } else {
                    @unlink($thumb);
                }
            }

            $data['status']    = 200; 
            $data['file_path'] = $filename; 
            $data['file_name'] = $file_upload['name'];
            $data['images']    = $images;

            $update = array(
                'uploads' => ($pt->user->uploads += $file_info['size'])
            );

            $db->where('id',$pt->user->id)->update(T_USERS,$update);
        } 

        else if (!empty($file_upload['error'])) {
            $data = array('status' => 400, 'error' => $file_upload['error']);
        }
    }
}

?>

Can you suggest how I could tie it into the existing code?