Generating an image from uploaded video from panel

Hello everyone, I’m using FFMpeg to create an image file from a uploaded video from panel.
It is generating the image and renaming to the same name of the uploaded video but with extension .jpg but the .txt file of that generated image is not being renamed as the .jpg file.
I need both to be renamed and it would be greate if the generated image became a property of the uploaded video so I can use this on template to be the video poster like this:

<?php elseif ($cover->type() == 'video') : ?>
                        <div class="main_gallery_item <?= $studyProgram ?>">
                            <div class="main_gallery_item_video">
                                <a href="<?= $project->url() ?>">
                                    <video 
                                        class="lazyload" 
                                        poster="<?= $project->video()->poster()->toFile()->url() ?>" 
                                        playsinline 
                                        muted 
                                        autoplay 
                                        loop 
                                        data-src="<?= $cover->url() ?>"
                                    >
                                        <source data-src="<?= $cover->url() ?>" type="video/mp4">
                                        <noscript>
                                            <video poster="<?= $cover->url() ?>" playsinline muted autoplay loop>
                                                <source src="<?= $cover->url() ?>" type="video/mp4">
                                            </video>
                                        </noscript>
                                    </video>
                                </a>
                            </div>
                            <div class="bubble main_gallery_item_title">
                                <?= $project->headline()->kirbytext() ?>
                            </div>
                            <span style="display:none;"><?= $project->tags() ?></span>
                        </div>
                    <?php endif ?>

Can someone help me please ?

How is this done? I mean, why is it generated first and then renamed but not the meta data file? Sounds as if you were doing this on some low level, i.e. not using Kirby’s $file->rename()?

So I have this panel here:

When the user uploads a file it must be renamed to a certain pattern like “bauhaus_art_and_design_cloud_001, 002 003…”

But when the file is a video, it must autogenerate and image to be used on HTML as a poster image while the video loads.

I make some code on site/config/config.php to solve my this issue but it still needs the autogenerated image to be binded to the video file, then when the video file gets renamed the image binded should be renamed as well.

I’ll show you the code from site/config/config.php

<?php
@include_once __DIR__ . '/vendor/autoload.php';

use Intervention\Image\ImageManager;
use FFMpeg\FFMpeg;
use FFMpeg\Coordinate\TimeCode;



return [
    'hooks' => [
        'file.create:before' => function (Kirby\Cms\File $file, Kirby\Filesystem\File $upload) {
            // Check if file is image type            
            if ($file->type() === 'image') {
                // Full path of the file
                $filePath = $upload->realpath();
                try {
                    // Loads image using Intervention Image
                    $imageManager = new ImageManager();
                    $image = $imageManager->make($filePath);

                    // Save the image
                    $image->save($filePath, 10);
                } catch (Exception $e) {
                    throw new Exception('There was an error compressing the image: ' . $e->getMessage());
                }
            }
        },
        'file.create:after' => function (Kirby\Cms\File $file) {
            $page = $file->page();
            error_log('studyprogram:> ' . $page->studyprogram()->text());
            $lastNumber = 0;
            // increment the last number by one and pad it with zeros
            if ($file->type() == 'image') {
                $lastNumber = $page->images()->count();
            } else if ($file->type() == 'video') {
                $lastNumber = $page->videos()->count();
            }

            $number = str_pad($lastNumber, 3, '0', STR_PAD_LEFT);
            $newName = 'bauhaus_art_design_cloud_' . $number;
            // rename the file and the txt
            $file->changeName($newName);
            if ($file->type() == 'video') {
                $ffmpeg = FFMpeg::create([
                    'ffmpeg.binaries' => 'C:/ffmpeg/bin/ffmpeg.exe',
                    'ffprobe.binaries' => 'C:/ffmpeg/bin/ffprobe.exe',
                ]);

                // Get the directory path
                $directory = dirname($file->root());                

                // Change the file name
                $newFileName = $newName;

                // Create the new file path
                $newFilePath = $directory . '/' . $newFileName;

                $video = $ffmpeg->open($newFilePath . '.' . $file->extension());
                $frame = $video->frame(TimeCode::fromSeconds(1));                

                $posterPath = $newFilePath . '.jpg';  // Path to save the poster image
               
                error_log('POSTERPATH:> ' . $posterPath);
                $frame->save($posterPath);

                // Create a new file for the poster image
                $poster = $page->files()->create([
                    'parent' => $page,
                    'source' => $posterPath,
                    'filename' => $newFileName . '.jpg',
                    'template' => 'poster',
                ]);

                // Attach the poster file to the video file
                $new = $file->update([
                    'image' => $poster,
                ]);
            }
        },
        'file.changeName:before' => function (Kirby\Cms\File $file, string $name) {
            try {
                // Generate a new filename based on your desired format
                $newName = $name;
                error_log('FILE->image():> ' . $file->image());
                $file->changeName($newName);
            } catch (Exception $e) {
                throw new Exception('There was an error renaming the file: ' . $e->getMessage());
            }
        }
    ]
];

I also checked this post here and tried to create a plugin, but I don’t know how it works, I’m new on Kirby.

Do you think you can help me?

This here should be

$file->update([
	'image' => [$poster->uuid()->toString()],
]);

Since you store the generated poster file in the video metadata, why do you have to rename those files when the video is renamed?

But if the video is renamed and you must rename the attached poster as well, then you need to do this in a file.changeName:after hook, not in a before hook.

Thanks for yout time helping me!

So What do you mean with “Since you store the generated poster file in the video metadata”.
What is video metadata ?

I have to rename all files to a certain kind of prefix like “bauhaus_art_design_cloud_” and then if the user rename the video file, the image needs to be renamed too. So when the page visitor rigth click the poster image when the video is not loaded yet and try to download the poster image with the option “save image as” the image will have that name with that pattern.

Hum, and also when the user (the page editor) deletes the video file it must delete the poster image together.

I don’t know if I was clear on the explain, let me know if you couldn’t understand.

I’ve made some changes in the code but didn’t understand what you mean about video metadata but now the .jpg image is generated when uploading a video, the name of the video is changed but the name of the .jpg doen’t change, it gives an error instead and when I delete the video file in the panel, the poster is not deleted together.

here is the code:

<?php
@include_once __DIR__ . '/vendor/autoload.php';

use Intervention\Image\ImageManager;
use FFMpeg\FFMpeg;
use FFMpeg\Coordinate\TimeCode;
use Kirby\Cms\File;

return [
    'hooks' => [
        'file.create:before' => function (Kirby\Cms\File $file, Kirby\Filesystem\File $upload) {
            // Check if file is image type            
            if ($file->type() === 'image') {
                // Full path of the file
                $filePath = $upload->realpath();
                try {
                    // Loads image using Intervention Image
                    $imageManager = new ImageManager();
                    $image = $imageManager->make($filePath);

                    // Save the image
                    $image->save($filePath, 10);
                } catch (Exception $e) {
                    throw new Exception('There was an error compressing the image: ' . $e->getMessage());
                }
            }
        },
        'file.create:after' => function (Kirby\Cms\File $file) {
            $page = $file->page();
            $lastNumber = 0;
            // increment the last number by one and pad it with zeros
            if ($file->type() == 'image') {
                $lastNumber = $page->images()->count();
            } else if ($file->type() == 'video') {
                $lastNumber = $page->videos()->count();
            }

            $number = str_pad($lastNumber, 3, '0', STR_PAD_LEFT);

            $newName = 'bauhaus_art_design_cloud_' . $number;


            if ($file->type() == 'video') {
                $ffmpeg = FFMpeg::create([
                    'ffmpeg.binaries' => 'C:/ffmpeg/bin/ffmpeg.exe',
                    'ffprobe.binaries' => 'C:/ffmpeg/bin/ffprobe.exe',
                ]);

                $video = $ffmpeg->open($file->root());
                $frame = $video->frame(TimeCode::fromSeconds(1));

                //$posterPath = $newFilePath . '.jpg';  // Path to save the poster image
                $posterPath = $file->root() . '.jpg';

                $filenameWithExtension = basename($posterPath);                
                
                try {
                    $frame->save($posterPath);
                } catch (Exception $e) {
                    throw new Exception('There was an error saving frame: ' . $e->getMessage());
                }

                // Create a new file for the poster image 
                // And calls the file.create:before and after hooks I guess
                $poster = new File([
                    'parent' => $page,
                    'source' => $filenameWithExtension,                   
                    'filename' => $filenameWithExtension,
                    'template' => 'upload-image',
                ]);
                error_log('poster:> ' . $poster);
                $file->update([
                    'image' => [$poster->uuid()->toString()],
                ]);
                
            }
            
            //calls the changeName hook
            /*it changes the name for the .mp4 file
            but when the in the line 65 it calls the file.create hook again
            the panel alerts an error telling that the first paramenter must be a kirby file
            and null was given
            */
            $file->changeName($newName);
        },
        'file.changeName:after' => function (Kirby\Cms\File $file, string $name) {
            try {
                // Generate a new filename based on your desired format
                $newName = $name;
                error_log('FILE->image():> ' . $file->image());
                $file->changeName($newName);
            } catch (Exception $e) {
                throw new Exception('There was an error renaming the file: ' . $e->getMessage());
            }
        }
    ]
];

and here is the error (If I change the changeName:after to changeName:before there is no error but the image is not renamed too):

I’ll show you the project.yml file to that is the blueprint for this page maybe there is some error too that may be affecting somehow.

title: Project

columns:
  - width: 3/3
    sections: 
      page-header:    
        type: fields
        fields:
          headline:
            label: Headline
            type: textarea 
            buttons:
              - italic 
  - width: 1/3
    sections: 
      page-cover: 
        type: fields
        fields:
          cover:
            label: Cover
            type: files
            max: 1
            uploads: false
            empty: Select a image or video
            help: Image or video that will be the cover for this project page.
  - width: 1/3
    sections: 
      page-alt-cover: 
        type: fields
        fields:
          videocover:
            label: Alternative Cover
            type: files
            max: 1
            uploads: false
            query: page.images
            empty: Select an image
            help: An alternative image that will be the cover for the cover above in case its a video. 
  - width: 1/3
    sections: 
      page-study-program: 
        type: fields
        fields:
          studyprogram:
            label: Study Program
            type: checkboxes
            options:
              fine_art: Fine Art               
              fine_art_education: Fine Art Education
              media_art_and_design: Media Art and Design
              product_design: Product Design
              scientific_disciplines: Scientific Disciplines
              visual_communication: Visual Communication
  - width: 2/4
    sections: 
      page-gallery: 
        type: fields
        fields:  
          gallery:
            empty: No items attatched to gallery yet.
            type: structure
            fields:                    
              link:                
                type: url
                help: If you put a url here you must remove image from field Image                              
              label:
                type: text
              image:
                type: files
                max: 1
                uploads: false
  
  - width: 2/4 
    sections:                  
      images:
        type: files
        headline: Files         
        layout: list
        template: upload-image
  - width: 1/3
    sections: 
      information:
        type: fields
        fields: 
          information:
            label: Project Information
            type: textarea
            buttons:
              - link
              - '|'
              - italic
  - width: 2/3
    sections: 
      description:
        type: fields
        fields: 
          description:
            label: Project Description
            type: textarea
            buttons:
              - link
              - '|'
              - italic
  - width: 3/3
    sections:
      tags:
        type: fields
        fields:
          tags:
            label: Tags
            type: tags



      
    
    
      

There is the logs from error_log() that i put in the code while trying to debug what is happening.

[Sun Jul  2 20:31:42 2023] poster:> <img alt="" src="http://localhost:8000/media/pages/tixies/82136a0612-1688340702/gradial1.mp4.jpg">
[Sun Jul  2 20:31:42 2023] FILE->image():> 

Thanks again and sorry for bothering you.

All files have a meta data file: https://getkirby.com/docs/guide/files/files#adding-meta-data-to-your-files

And you do this in this part of your code:

$file->update([
	'image' => [$poster->uuid()->toString()], // This stored the uuid of the poster in a meta data field call image (I'd call it poster, but hey)
]);

Ok, you can do this in a file.delete:after hook.

Understand, so a file.changeName:after hook to do that, as I already mentioned above.

What’s the purpose of the first file.create:before hook, compressing images? Does this work as expected?

Thank you one more time!

So, when I do the:

$file->update([
	'image' => [$poster->uuid()->toString()], // This stored the uuid of the poster in a meta data field call image (I'd call it poster, but hey)
]);

I should see something like that:

Label: 

----

Sort: 1

----

Uuid: 68fy6izdjzusx5NT

----

Image: 68fsdaizdjzusx5TN (uuiD of that image)

----
Template: upload-image

If is that so its not working, because none of metadada contains that Image: metadata.

And yes I was thinking I would probally have to do something on file.delete hook.

And yes, the purpose of the first file.create:before hook is to compressing images and this is working as expected!

Almost, it will look like this:

Image: - file://68fsdaizdjzusx5TN

Does this part work to create the image file object?

But wait, this seems to be the meta data file of the created image. But you are rightly updating the video meta data file in the code ($file)! So you should go looking there…

yes it is creating the metadata but without Image: uuid on it

@Ewerton_Azevedo have you been able to solve this? I’m searching for a way to auto generate poster images as well :slight_smile: