Video Upload
This commit is contained in:
+163
-30
@@ -11,8 +11,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
ini_set('display_errors', '1');
|
||||
ini_set('display_startup_errors', '1');
|
||||
ini_set('display_errors', '0'); // API: keine HTML-Fehler ausgeben (bricht JSON)
|
||||
ini_set('display_startup_errors', '0');
|
||||
ini_set('log_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// --- Load settings -----------------------------------------------------------
|
||||
@@ -506,6 +507,56 @@ function igel_images_upload(PDO $pdo, int $uid, int $igId): void
|
||||
}
|
||||
|
||||
$out = [];
|
||||
|
||||
// Hilfsfunktion: Extension/MIME erkennen (inkl. Videos)
|
||||
$detectMedia = function (string $origName, string $fileType): array {
|
||||
$lower = strtolower($origName);
|
||||
$ext = '.bin';
|
||||
$mime = 'application/octet-stream';
|
||||
$isImage = false;
|
||||
|
||||
$extFromLower = function (array $map) use ($lower): ?array {
|
||||
foreach ($map as $pattern => $info) {
|
||||
if (preg_match($pattern, $lower)) {
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// bevorzugt Endung, fallback auf gelieferten Content-Type
|
||||
$info = $extFromLower([
|
||||
'/\.(jpg|jpeg)$/' => ['.jpg', 'image/jpeg', true],
|
||||
'/\.png$/' => ['.png', 'image/png', true],
|
||||
'/\.webp$/' => ['.webp', 'image/webp', true],
|
||||
'/\.gif$/' => ['.gif', 'image/gif', true],
|
||||
'/\.heic$/' => ['.heic', 'image/heic', true],
|
||||
'/\.heif$/' => ['.heif', 'image/heif', true],
|
||||
'/\.(mp4|m4v)$/' => ['.mp4', 'video/mp4', false],
|
||||
'/\.mov$/' => ['.mov', 'video/quicktime', false],
|
||||
'/\.avi$/' => ['.avi', 'video/x-msvideo', false],
|
||||
'/\.mkv$/' => ['.mkv', 'video/x-matroska', false],
|
||||
'/\.webm$/' => ['.webm', 'video/webm', false],
|
||||
'/\.(3gp|3gpp)$/' => ['.3gp', 'video/3gpp', false],
|
||||
]);
|
||||
|
||||
if ($info) {
|
||||
[$ext, $mime, $isImage] = $info;
|
||||
} elseif ($fileType) {
|
||||
$t = strtolower($fileType);
|
||||
if (str_starts_with($t, 'image/')) {
|
||||
$mime = $t;
|
||||
$ext = '.jpg';
|
||||
$isImage = true;
|
||||
} elseif (str_starts_with($t, 'video/')) {
|
||||
$mime = $t;
|
||||
$ext = '.mp4';
|
||||
$isImage = false;
|
||||
}
|
||||
}
|
||||
|
||||
return [$ext, $mime, $isImage];
|
||||
};
|
||||
$count = is_array($files['name']) ? count($files['name']) : 0;
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
if ((int) $files['error'][$i] !== UPLOAD_ERR_OK)
|
||||
@@ -514,26 +565,14 @@ function igel_images_upload(PDO $pdo, int $uid, int $igId): void
|
||||
$orig = (string) $files['name'][$i];
|
||||
$size = (int) $files['size'][$i];
|
||||
|
||||
if ($size <= 0 || $size > MAX_IMAGE_SIZE)
|
||||
$maxSize = defined('MAX_MEDIA_SIZE') ? MAX_MEDIA_SIZE : MAX_IMAGE_SIZE;
|
||||
|
||||
if ($size <= 0 || $size > $maxSize)
|
||||
continue;
|
||||
|
||||
// MIME grob anhand Endung
|
||||
$lower = strtolower($orig);
|
||||
$ext = '.bin';
|
||||
$mime = 'application/octet-stream';
|
||||
if (preg_match('/\.(jpg|jpeg)$/', $lower)) {
|
||||
$ext = '.jpg';
|
||||
$mime = 'image/jpeg';
|
||||
} elseif (preg_match('/\.png$/', $lower)) {
|
||||
$ext = '.png';
|
||||
$mime = 'image/png';
|
||||
} elseif (preg_match('/\.webp$/', $lower)) {
|
||||
$ext = '.webp';
|
||||
$mime = 'image/webp';
|
||||
} elseif (preg_match('/\.gif$/', $lower)) {
|
||||
$ext = '.gif';
|
||||
$mime = 'image/gif';
|
||||
}
|
||||
// MIME/Extension erkennen
|
||||
$fileType = is_array($files['type']) && isset($files['type'][$i]) ? (string) $files['type'][$i] : '';
|
||||
[$ext, $mime, $isImage] = $detectMedia($orig, $fileType);
|
||||
|
||||
// sichere Dateinamen
|
||||
$base = bin2hex(random_bytes(8));
|
||||
@@ -547,18 +586,37 @@ function igel_images_upload(PDO $pdo, int $uid, int $igId): void
|
||||
if (!move_uploaded_file($tmp, $dest))
|
||||
continue;
|
||||
|
||||
// Thumb
|
||||
// Thumb (für Bilder echtes Thumbnail, für Videos Frame/Placeholder)
|
||||
$thumbUrl = null;
|
||||
try {
|
||||
$thumbDir = rtrim(UPLOAD_THUMB_DIR, '/');
|
||||
if (!is_dir($thumbDir)) {
|
||||
@mkdir($thumbDir, 0755, true);
|
||||
$thumbBase = $base . ($isImage ? $ext : '.png');
|
||||
if ($isImage) {
|
||||
try {
|
||||
$thumbDir = rtrim(UPLOAD_THUMB_DIR, '/');
|
||||
if (!is_dir($thumbDir)) {
|
||||
@mkdir($thumbDir, 0755, true);
|
||||
}
|
||||
$thumbPath = $thumbDir . '/' . $thumbBase;
|
||||
create_thumbnail($dest, $thumbPath, 512, 512); // Quadrat-Box
|
||||
$thumbUrl = rtrim(UPLOAD_BASE_URL, '/') . '/thumbs/' . $thumbBase;
|
||||
} catch (Throwable $e) {
|
||||
$thumbUrl = null; // ok
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$thumbDir = rtrim(UPLOAD_THUMB_DIR, '/');
|
||||
if (!is_dir($thumbDir)) {
|
||||
@mkdir($thumbDir, 0755, true);
|
||||
}
|
||||
$thumbPath = $thumbDir . '/' . $thumbBase;
|
||||
try {
|
||||
create_video_thumbnail($dest, $thumbPath, 512, 512);
|
||||
} catch (Throwable $e) {
|
||||
create_video_placeholder($thumbPath, 512, 288);
|
||||
}
|
||||
$thumbUrl = rtrim(UPLOAD_BASE_URL, '/') . '/thumbs/' . $thumbBase;
|
||||
} catch (Throwable $e) {
|
||||
$thumbUrl = null;
|
||||
}
|
||||
$thumbPath = $thumbDir . '/' . $fn;
|
||||
create_thumbnail($dest, $thumbPath, 512, 512); // Quadrat-Box
|
||||
$thumbUrl = rtrim(UPLOAD_BASE_URL, '/') . '/thumbs/' . $fn;
|
||||
} catch (Throwable $e) {
|
||||
$thumbUrl = null; // ok
|
||||
}
|
||||
|
||||
$url = rtrim(UPLOAD_BASE_URL, '/') . '/' . $fn;
|
||||
@@ -701,6 +759,81 @@ function create_thumbnail(string $src, string $dest, int $maxW, int $maxH): void
|
||||
imagedestroy($thumb);
|
||||
}
|
||||
|
||||
// Placeholder für Video-Thumbnails (ohne ffmpeg)
|
||||
function create_video_placeholder(string $dest, int $w = 512, int $h = 288): void
|
||||
{
|
||||
if (!extension_loaded('gd'))
|
||||
throw new Exception('GD not loaded');
|
||||
|
||||
$im = imagecreatetruecolor($w, $h);
|
||||
$bg = imagecolorallocate($im, 34, 34, 34);
|
||||
imagefilledrectangle($im, 0, 0, $w, $h, $bg);
|
||||
|
||||
$accent = imagecolorallocate($im, 240, 240, 240);
|
||||
$txt = imagecolorallocate($im, 180, 180, 180);
|
||||
|
||||
// Play-Icon (Dreieck)
|
||||
$size = (int) min($w, $h) * 0.3;
|
||||
$cx = (int) ($w / 2);
|
||||
$cy = (int) ($h / 2);
|
||||
$half = (int) ($size / 2);
|
||||
$points = [
|
||||
$cx - (int) ($half * 0.7), $cy - $half,
|
||||
$cx - (int) ($half * 0.7), $cy + $half,
|
||||
$cx + $half, $cy,
|
||||
];
|
||||
imagefilledpolygon($im, $points, 3, $accent);
|
||||
|
||||
// Rand
|
||||
$border = imagecolorallocatealpha($im, 255, 255, 255, 60);
|
||||
imagerectangle($im, 0, 0, $w - 1, $h - 1, $border);
|
||||
|
||||
// Text "VIDEO"
|
||||
$label = 'VIDEO';
|
||||
$fontSize = 5; // built-in font
|
||||
$tw = imagefontwidth($fontSize) * strlen($label);
|
||||
$th = imagefontheight($fontSize);
|
||||
imagestring($im, $fontSize, (int) (($w - $tw) / 2), $h - $th - 6, $label, $txt);
|
||||
|
||||
$ext = strtolower(pathinfo($dest, PATHINFO_EXTENSION));
|
||||
if ($ext === 'jpg' || $ext === 'jpeg') {
|
||||
imagejpeg($im, $dest, 85);
|
||||
} else {
|
||||
imagepng($im, $dest, 6);
|
||||
}
|
||||
|
||||
imagedestroy($im);
|
||||
}
|
||||
|
||||
// Video-Thumbnail �ber ffmpeg (erstes Frame)
|
||||
function create_video_thumbnail(string $src, string $dest, int $maxW, int $maxH): void
|
||||
{
|
||||
$ffmpeg = trim((string) @shell_exec('command -v ffmpeg'));
|
||||
if ($ffmpeg === '') {
|
||||
throw new Exception('ffmpeg not available');
|
||||
}
|
||||
|
||||
$srcEsc = escapeshellarg($src);
|
||||
$destEsc = escapeshellarg($dest);
|
||||
// Skaliert proportional, dann schwarze R�nder auf Quadrat
|
||||
$scale = sprintf(
|
||||
'scale=%d:%d:force_original_aspect_ratio=decrease,pad=%d:%d:(%d-iw)/2:(%d-ih)/2',
|
||||
$maxW,
|
||||
$maxH,
|
||||
$maxW,
|
||||
$maxH,
|
||||
$maxW,
|
||||
$maxH
|
||||
);
|
||||
$cmd = "$ffmpeg -y -v error -i $srcEsc -frames:v 1 -vf \"$scale\" $destEsc";
|
||||
$out = [];
|
||||
$ret = 0;
|
||||
@exec($cmd, $out, $ret);
|
||||
if ($ret !== 0 || !is_file($dest)) {
|
||||
throw new Exception('ffmpeg failed');
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MESSWERTE
|
||||
// =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user