Skip to content

Commit

Permalink
Merge pull request #932 from biigle/video-sprites-test
Browse files Browse the repository at this point in the history
Video sprites test
  • Loading branch information
mzur authored Sep 26, 2024
2 parents b59c095 + dc1ab5e commit 1bd698d
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 177 deletions.
102 changes: 41 additions & 61 deletions app/Jobs/ProcessNewVideo.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ public function handleFile($file, $path)
File::makeDirectory($tmpDir, 0755, true);
}

$this->createThumbnails($path, $disk, $fragment, $tmpDir);
// Extract images from video
$this->extractImagesfromVideo($path, $this->video->duration, $tmpDir);

// Generate thumbnails
$this->generateVideoThumbnails($disk, $fragment, $tmpDir);
} catch (Exception $e) {
// The video seems to be fine if it passed the previous checks. There may be
// errors in the actual video data but we can ignore that and skip generating
Expand All @@ -173,15 +177,6 @@ public function handleFile($file, $path)
}
}

protected function createThumbnails($path, $disk, $fragment, $tmpDir)
{
// Extract images from video
$this->extractImagesfromVideo($path, $this->video->duration, $tmpDir);

// Generate thumbnails
$this->generateVideoThumbnails($disk, $fragment, $tmpDir);
}

/**
* Get the codec of a video
*
Expand Down Expand Up @@ -242,47 +237,39 @@ protected function getVideoDimensions($url)
*
*/
protected function extractImagesfromVideo($path, $duration, $destinationPath)
{
$format = config('thumbnails.format');
$frameRate = $this->getThumbnailInfos($duration)['frameRate'];
$this->runFFMPEG($path, $frameRate, $destinationPath, $format);
}

protected function getThumbnailInfos($duration)
{
$maxThumbnails = config('videos.sprites_max_thumbnails');
$minThumbnails = config('videos.thumbnail_count');
$defaultThumbnailInterval = config('videos.sprites_thumbnail_interval');
$durationRounded = floor($duration * 10) / 10;
if ($durationRounded <= 0) {
return;
}

$estimatedThumbnails = $durationRounded / $defaultThumbnailInterval;
// Adjust the frame time based on the number of estimated thumbnails
$thumbnailInterval = ($estimatedThumbnails > $maxThumbnails) ? $durationRounded / $maxThumbnails
: (($estimatedThumbnails < $minThumbnails) ? $durationRounded / $minThumbnails : $defaultThumbnailInterval);
$frameRate = 1 / $thumbnailInterval;

return ['estimatedThumbnails' => $estimatedThumbnails, 'frameRate' => $frameRate];
}

protected function runFFMPEG($path, $frameRate, $destinationPath, $format)
{
// Leading zeros are important to prevent file sorting afterwards
Process::forever()
->run("ffmpeg -i '{$path}' -vf fps={$frameRate} {$destinationPath}/%04d.{$format}")
->throw();
$this->generateSnapshots($path, $frameRate, $destinationPath);
}

public function generateVideoThumbnails($disk, $fragment, $tmpDir)
{

// Config for normal thumbs
$format = config('thumbnails.format');
$thumbCount = config('videos.thumbnail_count');
$width = config('thumbnails.width');
$height = config('thumbnails.height');

// Config for sprite thumbs
$thumbnailsPerSprite = config('videos.sprites_thumbnails_per_sprite');
$thumbnailsPerRow = sqrt($thumbnailsPerSprite);
$spriteFormat = config('videos.sprites_format');

$files = $this->getFiles($tmpDir);
$files = File::glob($tmpDir . "/*.{$format}");
$nbrFiles = count($files);
$steps = $nbrFiles >= $thumbCount ? floor($nbrFiles / $thumbCount) : 1;

Expand All @@ -291,58 +278,51 @@ public function generateVideoThumbnails($disk, $fragment, $tmpDir)
$spriteCounter = 0;
foreach ($files as $i => $file) {
if ($i === intval($steps*$thumbCounter) && $thumbCounter < $thumbCount) {
$thumbnail = $this->createSingleThumbnail($file, $width, $height);
$this->save($disk, $thumbnail, true, $fragment, $thumbCounter, 85);
$thumbnail = $this->generateThumbnail($file, $width, $height);
$bufferedThumb = $thumbnail->writeToBuffer(".{$format}", [
'Q' => 85,
'strip' => true,
]);
$disk->put("{$fragment}/{$thumbCounter}.{$format}", $bufferedThumb);
$thumbCounter += 1;
}

if (count($thumbnails) < $thumbnailsPerSprite) {
$thumbnails[] = $this->createSingleThumbnail($file, $width, $height);
$thumbnails[] = $this->generateThumbnail($file, $width, $height);
}

if (count($thumbnails) === $thumbnailsPerSprite || $i === ($nbrFiles - 1)) {
$sprite = $this->createSingleSprite($thumbnails, $thumbnailsPerRow);
$this->save($disk, $sprite, false, $fragment, $spriteCounter, 75);
$sprite = VipsImage::arrayjoin($thumbnails, ['across' => $thumbnailsPerRow]);
$bufferedSprite = $sprite->writeToBuffer(".{$format}", [
'Q' => 75,
'strip' => true,
]);
$disk->put("{$fragment}/sprite_{$spriteCounter}.{$spriteFormat}", $bufferedSprite);
$thumbnails = [];
$spriteCounter += 1;
}
}
}

protected function getFiles($tmpDir)
/**
* Run the actual command to extract snapshots from the video. Separated into its own
* method for easier testing.
*/
protected function generateSnapshots(string $sourcePath, float $frameRate, string $targetDir): void
{
$format = config('thumbnails.format');
return File::glob($tmpDir . "/*.{$format}");
// Leading zeros are important to prevent file sorting afterwards
Process::forever()
->run("ffmpeg -i '{$sourcePath}' -vf fps={$frameRate} {$targetDir}/%04d.{$format}")
->throw();
}

protected function createSingleThumbnail($file, $width, $height)
/**
* Generate a thumbnail from a video snapshot. Separated into its own method for
* easier testing.
*/
protected function generateThumbnail(string $file, int $width, int $height): VipsImage
{
return VipsImage::thumbnail($file, $width, ['height' => $height]);
}

protected function createSingleSprite($thumbnails, $thumbnailsPerRow)
{
return VipsImage::arrayjoin($thumbnails, ['across' => $thumbnailsPerRow]);
}

protected function save($disk, $img, $isThumb, $fragment, $counter, $q)
{
$format = config('thumbnails.format');

if ($isThumb) {
$bufferedThumb = $img->writeToBuffer(".{$format}", [
'Q' => $q,
'strip' => true,
]);
$disk->put("{$fragment}/{$counter}.{$format}", $bufferedThumb);
} else {
$spriteFormat = config('videos.sprites_format');

$bufferedSprite = $img->writeToBuffer(".{$format}", [
'Q' => $q,
'strip' => true,
]);
$disk->put("{$fragment}/sprite_{$counter}.{$spriteFormat}", $bufferedSprite);
}
}
}
Loading

0 comments on commit 1bd698d

Please sign in to comment.