Files
easystream-main/f_jobs/ThumbnailGenerationJob.php
SamiAhmed7777 0b7e2d0a5b feat: Add comprehensive documentation suite and reorganize project structure
- Created complete documentation in docs/ directory
- Added PROJECT_OVERVIEW.md with feature highlights and getting started guide
- Added ARCHITECTURE.md with system design and technical details
- Added SECURITY.md with comprehensive security implementation guide
- Added DEVELOPMENT.md with development workflows and best practices
- Added DEPLOYMENT.md with production deployment instructions
- Added API.md with complete REST API documentation
- Added CONTRIBUTING.md with contribution guidelines
- Added CHANGELOG.md with version history and migration notes
- Reorganized all documentation files into docs/ directory for better organization
- Updated README.md with proper documentation links and quick navigation
- Enhanced project structure with professional documentation standards
2025-10-21 00:39:45 -07:00

293 lines
10 KiB
PHP

<?php
/*******************************************************************************************************************
| Thumbnail Generation Job
| Handles thumbnail generation for videos and images
|*******************************************************************************************************************/
class ThumbnailGenerationJob extends BaseJob
{
/**
* Handle thumbnail generation
* @param array $data Thumbnail generation data
* @return array Generation result
*/
public function handle($data)
{
$this->validateData($data, ['file_key', 'file_path', 'file_type']);
$fileKey = $data['file_key'];
$filePath = $data['file_path'];
$fileType = $data['file_type'];
$sizes = $data['sizes'] ?? ['small' => [160, 120], 'medium' => [320, 240], 'large' => [640, 480]];
$this->logProgress('Starting thumbnail generation', [
'file_key' => $fileKey,
'file_path' => $filePath,
'file_type' => $fileType,
'sizes' => array_keys($sizes)
]);
try {
if (!file_exists($filePath)) {
throw new Exception("Source file not found: {$filePath}");
}
$thumbnailDir = _FPATH . 'f_data/thumbnails/' . substr($fileKey, 0, 2) . '/' . $fileKey . '/';
if (!is_dir($thumbnailDir)) {
mkdir($thumbnailDir, 0755, true);
}
$results = [];
if ($fileType === 'video') {
$results = $this->generateVideoThumbnails($filePath, $thumbnailDir, $fileKey, $sizes);
} elseif (in_array($fileType, ['image', 'photo'])) {
$results = $this->generateImageThumbnails($filePath, $thumbnailDir, $fileKey, $sizes);
} else {
throw new Exception("Unsupported file type for thumbnail generation: {$fileType}");
}
// Update database with thumbnail information
$this->updateThumbnailRecord($fileKey, $results);
$this->logProgress('Thumbnail generation completed', [
'file_key' => $fileKey,
'thumbnails_generated' => count($results)
]);
return [
'file_key' => $fileKey,
'thumbnails' => $results,
'thumbnail_dir' => $thumbnailDir
];
} catch (Exception $e) {
$this->logError('Thumbnail generation failed', [
'file_key' => $fileKey,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* Generate video thumbnails using FFmpeg
* @param string $videoPath Video file path
* @param string $outputDir Output directory
* @param string $fileKey File key
* @param array $sizes Thumbnail sizes
* @return array Generation results
*/
private function generateVideoThumbnails($videoPath, $outputDir, $fileKey, $sizes)
{
$ffmpegPath = $this->findFFmpegPath();
$results = [];
foreach ($sizes as $sizeName => $dimensions) {
$width = $dimensions[0];
$height = $dimensions[1];
$outputFile = $outputDir . $fileKey . "_{$sizeName}.jpg";
// Generate thumbnail at 10% of video duration
$command = sprintf(
'%s -i %s -ss 00:00:10 -vframes 1 -vf "scale=%d:%d:force_original_aspect_ratio=decrease,pad=%d:%d:(ow-iw)/2:(oh-ih)/2:color=black" -q:v 2 %s 2>&1',
escapeshellarg($ffmpegPath),
escapeshellarg($videoPath),
$width,
$height,
$width,
$height,
escapeshellarg($outputFile)
);
$output = [];
$returnCode = 0;
exec($command, $output, $returnCode);
if ($returnCode === 0 && file_exists($outputFile)) {
$results[$sizeName] = [
'success' => true,
'file' => $outputFile,
'size' => filesize($outputFile),
'width' => $width,
'height' => $height
];
} else {
$results[$sizeName] = [
'success' => false,
'error' => 'FFmpeg thumbnail generation failed',
'output' => implode("\n", $output)
];
}
}
return $results;
}
/**
* Generate image thumbnails using GD
* @param string $imagePath Image file path
* @param string $outputDir Output directory
* @param string $fileKey File key
* @param array $sizes Thumbnail sizes
* @return array Generation results
*/
private function generateImageThumbnails($imagePath, $outputDir, $fileKey, $sizes)
{
$results = [];
// Get image info
$imageInfo = getimagesize($imagePath);
if (!$imageInfo) {
throw new Exception("Unable to read image information");
}
$sourceWidth = $imageInfo[0];
$sourceHeight = $imageInfo[1];
$mimeType = $imageInfo['mime'];
// Create source image resource
switch ($mimeType) {
case 'image/jpeg':
$sourceImage = imagecreatefromjpeg($imagePath);
break;
case 'image/png':
$sourceImage = imagecreatefrompng($imagePath);
break;
case 'image/gif':
$sourceImage = imagecreatefromgif($imagePath);
break;
case 'image/webp':
$sourceImage = imagecreatefromwebp($imagePath);
break;
default:
throw new Exception("Unsupported image format: {$mimeType}");
}
if (!$sourceImage) {
throw new Exception("Failed to create image resource");
}
foreach ($sizes as $sizeName => $dimensions) {
$targetWidth = $dimensions[0];
$targetHeight = $dimensions[1];
// Calculate dimensions maintaining aspect ratio
$ratio = min($targetWidth / $sourceWidth, $targetHeight / $sourceHeight);
$newWidth = (int)($sourceWidth * $ratio);
$newHeight = (int)($sourceHeight * $ratio);
// Create thumbnail
$thumbnail = imagecreatetruecolor($targetWidth, $targetHeight);
// Handle transparency for PNG and GIF
if ($mimeType === 'image/png' || $mimeType === 'image/gif') {
imagealphablending($thumbnail, false);
imagesavealpha($thumbnail, true);
$transparent = imagecolorallocatealpha($thumbnail, 255, 255, 255, 127);
imagefill($thumbnail, 0, 0, $transparent);
} else {
// Fill with white background for JPEG
$white = imagecolorallocate($thumbnail, 255, 255, 255);
imagefill($thumbnail, 0, 0, $white);
}
// Calculate centering offsets
$offsetX = (int)(($targetWidth - $newWidth) / 2);
$offsetY = (int)(($targetHeight - $newHeight) / 2);
// Resize and copy
imagecopyresampled(
$thumbnail, $sourceImage,
$offsetX, $offsetY, 0, 0,
$newWidth, $newHeight, $sourceWidth, $sourceHeight
);
// Save thumbnail
$outputFile = $outputDir . $fileKey . "_{$sizeName}.jpg";
if (imagejpeg($thumbnail, $outputFile, 85)) {
$results[$sizeName] = [
'success' => true,
'file' => $outputFile,
'size' => filesize($outputFile),
'width' => $targetWidth,
'height' => $targetHeight
];
} else {
$results[$sizeName] = [
'success' => false,
'error' => 'Failed to save thumbnail'
];
}
imagedestroy($thumbnail);
}
imagedestroy($sourceImage);
return $results;
}
/**
* Update thumbnail record in database
* @param string $fileKey File key
* @param array $results Thumbnail generation results
*/
private function updateThumbnailRecord($fileKey, $results)
{
try {
$db = $this->getDatabase();
$thumbnailData = [];
foreach ($results as $sizeName => $result) {
if ($result['success']) {
$thumbnailData[$sizeName] = [
'file' => basename($result['file']),
'width' => $result['width'],
'height' => $result['height'],
'size' => $result['size']
];
}
}
$updateData = [
'thumbnails_generated' => 1,
'thumbnail_data' => json_encode($thumbnailData),
'thumbnail_updated' => date('Y-m-d H:i:s')
];
// Update appropriate table based on file type
$db->doUpdate('db_videofiles', 'file_key', $updateData, $fileKey);
$this->logProgress('Thumbnail record updated', [
'file_key' => $fileKey,
'thumbnails' => array_keys($thumbnailData)
]);
} catch (Exception $e) {
$this->logError('Failed to update thumbnail record', [
'file_key' => $fileKey,
'error' => $e->getMessage()
]);
}
}
/**
* Find FFmpeg path
* @return string FFmpeg path
*/
private function findFFmpegPath()
{
$paths = ['/usr/bin/ffmpeg', '/usr/local/bin/ffmpeg', 'ffmpeg'];
foreach ($paths as $path) {
if (shell_exec("which {$path}") || file_exists($path)) {
return $path;
}
}
return 'ffmpeg'; // Fallback to PATH
}
}