CmdMagickMediaHandler.php: add WIP partial implementation

This commit is contained in:
Zankaria 2025-03-31 14:29:42 +02:00
parent 262728cea9
commit 7d55f2ad5c

View file

@ -0,0 +1,143 @@
<?php
namespace Vichan\Service\Media;
use Vichan\Functions\{Fs, Metadata};
use Vichan\Data\{Exif, MediaInstallResult, ThumbGenerationResult};
class CmdMagickMediaHandler implements MediaHandler {
use MediaHandlerTrait;
public const CMD_IMAGE_MAGICK = 'magick';
public const CMD_GRAPHICS_MAGICK = 'gm';
public const THUMB_KEEP_FRAMES_NO = 0;
public const THUMB_KEEP_FRAMES_ALL = -1;
private string $prefix;
private bool $strip_metadata;
private int $max_image_width;
private int $max_image_height;
private static function parseOrientation(string $str): ?int {
switch ($str) {
case 'Undefined':
return Exif::EXIF_ORIENTATION_UNDEFINED;
case 'TopLeft':
return Exif::EXIF_ORIENTATION_0_UPRIGHT;
case 'TopRight':
return Exif::EXIF_ORIENTATION_0_FLIPPED;
case 'BottomRight':
return Exif::EXIF_ORIENTATION_180_UPRIGHT;
case 'BottomLeft':
return Exif::EXIF_ORIENTATION_180_FLIPPED;
case 'LeftTop':
return Exif::EXIF_ORIENTATION_90_UPRIGHT;
case 'RightTop':
return Exif::EXIF_ORIENTATION_90_FLIPPED;
case 'RightBottom':
return Exif::EXIF_ORIENTATION_270_UPRIGHT;
case 'LeftBottom':
return Exif::EXIF_ORIENTATION_270_FLIPPED;
case 'Unrecognized':
default:
return null;
}
}
public function getMetadata(string $file_path, bool $only_first_frame): array {
$arg = \escapeshellarg($only_first_frame ? "{$file_path}[0]" : $file_path);
$ret_exec = shell_exec_error("{$this->prefix} identify -format '%w %h %m %[orientation] %n' $arg");
if (!\is_string($ret_exec)) {
throw new MediaException('Error while executing identify', MediaException::ERR_COMPUTE_ERR);
}
$ret_match = \preg_match('/^(\d+) (\d+) ([\w\d]+) ([\w]+) (\d+)$/', $ret_exec, $m);
if ($ret_match !== 1) {
throw new MediaException('Could not parse identify output', MediaException::ERR_COMPUTE_ERR);
}
$width = \intval($m[1]);
$height = \intval($m[2]);
$ext = $m[3];
$orientation_str = $m[4];
$frame_count = \intval($m[5]);
$mime = Metadata\ext_to_mime($ext) ?? 'application/octet-stream';
$exif_orientation = self::parseOrientation($orientation_str);
return [ $width, $height, $mime, $exif_orientation, $frame_count ];
}
private function __construct(string $cmd, bool $strip_metadata, int $max_image_width, int $max_image_height) {
$this->prefix = $cmd;
$this->strip_metadata = $strip_metadata;
$this->max_image_width = $max_image_width;
$this->max_image_height = $max_image_height;
}
public function supportsMime(string $mime): bool {
// Could not find a good way to query the commands. Also it would be really slow.
return \in_array($mime, Metadata\SUPPORTED_IMAGE_MIME_TYPES)
|| \in_array($mime, Metadata\SUPPORTED_THUMB_MIME_TYPES);
}
public function openHandle(string $file_path, string $file_mime, int $file_kind): mixed {
$is_pdf = $file_mime === 'application/pdf';
list($width, $height, $mime, $exif_orientation) = self::getMetadata($file_path, $is_pdf);
if ($mime !== $file_mime) {
throw new MediaException("Mime type mismatch, expected $file_mime, got $mime", MediaException::ERR_COMPUTE_ERR);
}
if ($width > $this->max_image_width || $height > $this->max_image_height) {
throw new MediaException("Image too big", MediaException::ERR_IMAGE_TOO_LARGE);
}
return [ $file_path, $file_mime, $file_kind, $width, $height, $exif_orientation ];
}
public function closeHandle(mixed $handle) {
// No-op.
}
public function installMediaAndGenerateThumb(
mixed $handle,
string $media_preferred_out_file_basepath,
string $thumb_preferred_out_file_basepath,
int $thumb_max_width,
int $thumb_max_height
): MediaInstallResult {
list($source_file_path, $source_file_mime, $source_file_kind, $width, $height, $exif_orientation) = $handle;
if (!$this->strip_metadata) {
$media_out_path = $media_preferred_out_file_basepath . Metadata\mime_to_ext($source_file_mime);
$this->move_or_link_or_copy($source_file_kind, $source_file_path, $media_out_path);
if ($width <= $thumb_max_width && $height <= $thumb_max_height) {
$thumb_out_path = $thumb_preferred_out_file_basepath . Metadata\mime_to_ext($source_file_mime);
if (!Fs\link_or_copy($media_out_path, $thumb_out_path)) {
throw new MediaException("Could not link or copy '$media_out_path' to '$thumb_out_path'", MediaException::ERR_IO_ERR);
}
}
}
}
public function installMedia(mixed $handle, string $preferred_out_file_basepath): string {
list($source_file_path, $source_file_mime, $source_file_kind, $width, $height, $exif_orientation) = $handle;
if (!$this->strip_metadata) {
$out_path = $preferred_out_file_basepath . Metadata\mime_to_ext($source_file_mime);
$this->move_or_link_or_copy($source_file_kind, $source_file_path, $out_path);
return $out_path;
}
}
public function generateThumb(mixed $handle, string $preferred_out_file_basepath, int $max_width, int $max_height): ThumbGenerationResult {
list($source_file_path, $source_file_mime, $source_file_kind, $width, $height, $exif_orientation) = $handle;
if (!$this->strip_metadata && $width <= $max_width && $height <= $max_height) {
$out_path = $preferred_out_file_basepath . Metadata\mime_to_ext($source_file_mime);
$this->move_or_link_or_copy($source_file_kind, $source_file_path, $out_path);
return new ThumbGenerationResult($out_path, $source_file_mime, $width, $height);
} else {
}
}
}