diff --git a/core/basethemelet.php b/core/basethemelet.php index 897a1f33..b3a2baeb 100644 --- a/core/basethemelet.php +++ b/core/basethemelet.php @@ -58,7 +58,7 @@ class BaseThemelet $tsize = get_thumbnail_size($image->width, $image->height); } else { //Use max thumbnail size if using thumbless filetype - $tsize = get_thumbnail_size($config->get_int('thumb_width'), $config->get_int('thumb_height')); + $tsize = get_thumbnail_size($config->get_int(ImageConfig::THUMB_WIDTH), $config->get_int(ImageConfig::THUMB_WIDTH)); } $custom_classes = ""; diff --git a/core/imageboard/image.php b/core/imageboard/image.php index 10590854..90ab9e6a 100644 --- a/core/imageboard/image.php +++ b/core/imageboard/image.php @@ -54,6 +54,19 @@ class Image /** @var boolean */ public $locked = false; + /** @var boolean */ + public $lossless = null; + + /** @var boolean */ + public $video = null; + + /** @var boolean */ + public $audio = null; + + /** @var int */ + public $length = null; + + /** * One will very rarely construct an image directly, more common * would be to use Image::by_id, Image::by_hash, etc. @@ -463,7 +476,7 @@ class Image */ public function get_image_link(): string { - return $this->get_link('image_ilink', '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.$ext'); + return $this->get_link(ImageConfig::ILINK, '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.$ext'); } /** @@ -480,8 +493,8 @@ class Image public function get_thumb_link(): string { global $config; - $ext = $config->get_string("thumb_type"); - return $this->get_link('image_tlink', '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext); + $ext = $config->get_string(ImageConfig::THUMB_TYPE); + return $this->get_link(ImageConfig::TLINK, '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext); } /** @@ -512,7 +525,7 @@ class Image public function get_tooltip(): string { global $config; - $tt = $this->parse_link_template($config->get_string('image_tip'), "no_escape"); + $tt = $this->parse_link_template($config->get_string(ImageConfig::TIP), "no_escape"); // Removes the size tag if the file is an mp3 if ($this->ext === 'mp3') { diff --git a/core/imageboard/misc.php b/core/imageboard/misc.php index 1614ca70..c82d9f98 100644 --- a/core/imageboard/misc.php +++ b/core/imageboard/misc.php @@ -95,7 +95,6 @@ function get_extension_from_mime(String $file_path): String throw new UploadException("Could not determine file mime type: ".$file_path); } - /** * Given a full size pair of dimensions, return a pair scaled down to fit * into the configured thumbnail square, with ratio intact. @@ -125,24 +124,31 @@ function get_thumbnail_size(int $orig_width, int $orig_height, bool $use_dpi_sca } - if ($use_dpi_scaling) { - $max_size = get_thumbnail_max_size_scaled(); - $max_width = $max_size[0]; - $max_height = $max_size[1]; + if($use_dpi_scaling) { + list($max_width, $max_height) = get_thumbnail_max_size_scaled(); } else { - $max_width = $config->get_int('thumb_width'); - $max_height = $config->get_int('thumb_height'); + $max_width = $config->get_int(ImageConfig::THUMB_WIDTH); + $max_height = $config->get_int(ImageConfig::THUMB_HEIGHT); } - $xscale = ($max_height / $orig_height); - $yscale = ($max_width / $orig_width); - $scale = ($xscale < $yscale) ? $xscale : $yscale; + $output = get_scaled_by_aspect_ratio($orig_width, $orig_height, $max_width, $max_height); - if ($scale > 1 && $config->get_bool('thumb_upscale')) { + if ($output[2] > 1 && $config->get_bool('thumb_upscale')) { return [(int)$orig_width, (int)$orig_height]; } else { - return [(int)($orig_width*$scale), (int)($orig_height*$scale)]; + return $output; } + +} + +function get_scaled_by_aspect_ratio(int $original_width, int $original_height, int $max_width, int $max_height) : array +{ + $xscale = ($max_width/ $original_width); + $yscale = ($max_height/ $original_height); + + $scale = ($yscale < $xscale) ? $yscale : $xscale ; + + return [(int)($original_width*$scale), (int)($original_height*$scale), $scale]; } /** @@ -154,355 +160,60 @@ function get_thumbnail_max_size_scaled(): array { global $config; - $scaling = $config->get_int("thumb_scaling"); - $max_width = $config->get_int('thumb_width') * ($scaling/100); - $max_height = $config->get_int('thumb_height') * ($scaling/100); + $scaling = $config->get_int(ImageConfig::THUMB_SCALING); + $max_width = $config->get_int(ImageConfig::THUMB_WIDTH) * ($scaling/100); + $max_height = $config->get_int(ImageConfig::THUMB_HEIGHT) * ($scaling/100); return [$max_width, $max_height]; } -/** - * Creates a thumbnail file using ImageMagick's convert command. - * - * @param $hash - * @param string $input_type Optional, allows specifying the input format. Usually not necessary. - * @return bool true is successful, false if not. - */ -function create_thumbnail_convert($hash, $input_type = ""): bool -{ + +function create_image_thumb(string $hash, string $type, string $engine = null) { global $config; $inname = warehouse_path(Image::IMAGE_DIR, $hash); $outname = warehouse_path(Image::THUMBNAIL_DIR, $hash); + $tsize = get_thumbnail_max_size_scaled(); - $q = $config->get_int("thumb_quality"); - $convert = $config->get_string("thumb_convert_path"); - - if ($convert==null||$convert=="") { - return false; + if(empty($engine)) { + $engine = $config->get_string(ImageConfig::THUMB_ENGINE); } - // ffff imagemagick fails sometimes, not sure why - //$format = "'%s' '%s[0]' -format '%%[fx:w] %%[fx:h]' info:"; - //$cmd = sprintf($format, $convert, $inname); - //$size = shell_exec($cmd); - //$size = explode(" ", trim($size)); - list($w, $h) = get_thumbnail_max_size_scaled(); - - - // running the call with cmd.exe requires quoting for our paths - $type = $config->get_string('thumb_type'); - - $options = ""; - if (!$config->get_bool('thumb_upscale')) { - $options .= "\>"; + $output_format = $config->get_string(ImageConfig::THUMB_TYPE); + if($output_format=="webp") { + $output_format = Media::WEBP_LOSSY; } - $bg = "black"; - if ($type=="webp") { - $bg = "none"; - } - if (!empty($input_type)) { - $input_type = $input_type.":"; - } - $format = '"%s" -flatten -strip -thumbnail %ux%u%s -quality %u -background %s %s"%s[0]" %s:"%s" 2>&1'; - $cmd = sprintf($format, $convert, $w, $h, $options, $q, $bg, $input_type, $inname, $type, $outname); - $cmd = str_replace("\"convert\"", "convert", $cmd); // quotes are only needed if the path to convert contains a space; some other times, quotes break things, see github bug #27 - exec($cmd, $output, $ret); - if ($ret!=0) { - log_warning('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret, outputting ".implode("\r\n", $output)); - } else { - log_debug('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret"); - } - - if ($config->get_bool("thumb_optim", false)) { - exec("jpegoptim $outname", $output, $ret); - } - - return true; + send_event(new MediaResizeEvent( + $engine, + $inname, + $type, + $outname, + $tsize[0], + $tsize[1], + false, + $output_format, + $config->get_int(ImageConfig::THUMB_QUALITY), + true, + $config->get_bool('thumb_upscale', false) + )); } -/** - * Creates a thumbnail using ffmpeg. - * - * @param $hash - * @return bool true if successful, false if not. - */ -function create_thumbnail_ffmpeg($hash): bool + +const TIME_UNITS = ["s"=>60,"m"=>60,"h"=>24,"d"=>365,"y"=>PHP_INT_MAX]; +function format_milliseconds(int $input): string { - global $config; + $output = ""; - $ffmpeg = $config->get_string("thumb_ffmpeg_path"); - if ($ffmpeg==null||$ffmpeg=="") { - return false; + $remainder = floor($input / 1000); + + foreach (TIME_UNITS AS $unit=>$conversion) { + $count = $remainder % $conversion; + $remainder = floor($remainder / $conversion); + if($count==0&&$remainder<1) { + break; + } + $output = "$count".$unit." ".$output; } - $inname = warehouse_path(Image::IMAGE_DIR, $hash); - $outname = warehouse_path(Image::THUMBNAIL_DIR, $hash); - - $orig_size = video_size($inname); - $scaled_size = get_thumbnail_size($orig_size[0], $orig_size[1], true); - - $codec = "mjpeg"; - $quality = $config->get_int("thumb_quality"); - if ($config->get_string("thumb_type")=="webp") { - $codec = "libwebp"; - } else { - // mjpeg quality ranges from 2-31, with 2 being the best quality. - $quality = floor(31 - (31 * ($quality/100))); - if ($quality<2) { - $quality = 2; - } - } - - $args = [ - escapeshellarg($ffmpeg), - "-y", "-i", escapeshellarg($inname), - "-vf", "thumbnail,scale={$scaled_size[0]}:{$scaled_size[1]}", - "-f", "image2", - "-vframes", "1", - "-c:v", $codec, - "-q:v", $quality, - escapeshellarg($outname), - ]; - - $cmd = escapeshellcmd(implode(" ", $args)); - - exec($cmd, $output, $ret); - - if ((int)$ret == (int)0) { - log_debug('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret"); - return true; - } else { - log_error('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret"); - return false; - } -} - -/** - * Determines the dimensions of a video file using ffmpeg. - * - * @param string $filename - * @return array [width, height] - */ -function video_size(string $filename): array -{ - global $config; - $ffmpeg = $config->get_string("thumb_ffmpeg_path"); - $cmd = escapeshellcmd(implode(" ", [ - escapeshellarg($ffmpeg), - "-y", "-i", escapeshellarg($filename), - "-vstats" - ])); - $output = shell_exec($cmd . " 2>&1"); - // error_log("Getting size with `$cmd`"); - - $regex_sizes = "/Video: .* ([0-9]{1,4})x([0-9]{1,4})/"; - if (preg_match($regex_sizes, $output, $regs)) { - if (preg_match("/displaymatrix: rotation of (90|270).00 degrees/", $output)) { - $size = [$regs[2], $regs[1]]; - } else { - $size = [$regs[1], $regs[2]]; - } - } else { - $size = [1, 1]; - } - log_debug('imageboard/misc', "Getting video size with `$cmd`, returns $output -- $size[0], $size[1]"); - return $size; -} - -/** - * Check Memory usage limits - * - * Old check: $memory_use = (filesize($image_filename)*2) + ($width*$height*4) + (4*1024*1024); - * New check: $memory_use = $width * $height * ($bits_per_channel) * channels * 2.5 - * - * It didn't make sense to compute the memory usage based on the NEW size for the image. ($width*$height*4) - * We need to consider the size that we are GOING TO instead. - * - * The factor of 2.5 is simply a rough guideline. - * http://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize - * - * @param array $info The output of getimagesize() for the source file in question. - * @return int The number of bytes an image resize operation is estimated to use. - */ -function calc_memory_use(array $info): int -{ - if (isset($info['bits']) && isset($info['channels'])) { - $memory_use = ($info[0] * $info[1] * ($info['bits'] / 8) * $info['channels'] * 2.5) / 1024; - } else { - // If we don't have bits and channel info from the image then assume default values - // of 8 bits per color and 4 channels (R,G,B,A) -- ie: regular 24-bit color - $memory_use = ($info[0] * $info[1] * 1 * 4 * 2.5) / 1024; - } - return (int)$memory_use; -} - -/** - * Performs a resize operation on an image file using GD. - * - * @param String $image_filename The source file to be resized. - * @param array $info The output of getimagesize() for the source file. - * @param int $new_width - * @param int $new_height - * @param string $output_filename - * @param string|null $output_type If set to null, the output file type will be automatically determined via the $info parameter. Otherwise an exception will be thrown. - * @param int $output_quality Defaults to 80. - * @throws ImageResizeException - * @throws InsufficientMemoryException if the estimated memory usage exceeds the memory limit. - */ -function image_resize_gd( - String $image_filename, - array $info, - int $new_width, - int $new_height, - string $output_filename, - string $output_type=null, - int $output_quality = 80 -) { - $width = $info[0]; - $height = $info[1]; - - if ($output_type==null) { - /* If not specified, output to the same format as the original image */ - switch ($info[2]) { - case IMAGETYPE_GIF: $output_type = "gif"; break; - case IMAGETYPE_JPEG: $output_type = "jpeg"; break; - case IMAGETYPE_PNG: $output_type = "png"; break; - case IMAGETYPE_WEBP: $output_type = "webp"; break; - case IMAGETYPE_BMP: $output_type = "bmp"; break; - default: throw new ImageResizeException("Failed to save the new image - Unsupported image type."); - } - } - - $memory_use = calc_memory_use($info); - $memory_limit = get_memory_limit(); - if ($memory_use > $memory_limit) { - throw new InsufficientMemoryException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)"); - } - - $image = imagecreatefromstring(file_get_contents($image_filename)); - $image_resized = imagecreatetruecolor($new_width, $new_height); - try { - if ($image===false) { - throw new ImageResizeException("Could not load image: ".$image_filename); - } - if ($image_resized===false) { - throw new ImageResizeException("Could not create output image with dimensions $new_width c $new_height "); - } - - // Handle transparent images - switch ($info[2]) { - case IMAGETYPE_GIF: - $transparency = imagecolortransparent($image); - $palletsize = imagecolorstotal($image); - - // If we have a specific transparent color - if ($transparency >= 0 && $transparency < $palletsize) { - // Get the original image's transparent color's RGB values - $transparent_color = imagecolorsforindex($image, $transparency); - - // Allocate the same color in the new image resource - $transparency = imagecolorallocate($image_resized, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']); - if ($transparency===false) { - throw new ImageResizeException("Unable to allocate transparent color"); - } - - // Completely fill the background of the new image with allocated color. - if (imagefill($image_resized, 0, 0, $transparency)===false) { - throw new ImageResizeException("Unable to fill new image with transparent color"); - } - - // Set the background color for new image to transparent - imagecolortransparent($image_resized, $transparency); - } - break; - case IMAGETYPE_PNG: - case IMAGETYPE_WEBP: - // - // More info here: http://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php - // - if (imagealphablending($image_resized, false)===false) { - throw new ImageResizeException("Unable to disable image alpha blending"); - } - if (imagesavealpha($image_resized, true)===false) { - throw new ImageResizeException("Unable to enable image save alpha"); - } - $transparent_color = imagecolorallocatealpha($image_resized, 255, 255, 255, 127); - if ($transparent_color===false) { - throw new ImageResizeException("Unable to allocate transparent color"); - } - if (imagefilledrectangle($image_resized, 0, 0, $new_width, $new_height, $transparent_color)===false) { - throw new ImageResizeException("Unable to fill new image with transparent color"); - } - break; - } - - // Actually resize the image. - if (imagecopyresampled( - $image_resized, - $image, - 0, - 0, - 0, - 0, - $new_width, - $new_height, - $width, - $height - )===false) { - throw new ImageResizeException("Unable to copy resized image data to new image"); - } - - - switch ($output_type) { - case "bmp": - $result = imagebmp($image_resized, $output_filename, true); - break; - case "webp": - $result = imagewebp($image_resized, $output_filename, $output_quality); - break; - case "jpg": - case "jpeg": - $result = imagejpeg($image_resized, $output_filename, $output_quality); - break; - case "png": - $result = imagepng($image_resized, $output_filename, 9); - break; - case "gif": - $result = imagegif($image_resized, $output_filename); - break; - default: - throw new ImageResizeException("Failed to save the new image - Unsupported image type: $output_type"); - } - if ($result==false) { - throw new ImageResizeException("Failed to save the new image, function returned false when saving type: $output_type"); - } - } finally { - imagedestroy($image); - imagedestroy($image_resized); - } -} - -/** - * Determines if a file is an animated gif. - * - * @param String $image_filename The path of the file to check. - * @return bool true if the file is an animated gif, false if it is not. - */ -function is_animated_gif(String $image_filename) -{ - $is_anim_gif = 0; - if (($fh = @fopen($image_filename, 'rb'))) { - //check if gif is animated (via http://www.php.net/manual/en/function.imagecreatefromgif.php#104473) - while (!feof($fh) && $is_anim_gif < 2) { - $chunk = fread($fh, 1024 * 100); - $is_anim_gif += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches); - } - } - return ($is_anim_gif == 0); -} - -function image_to_id(Image $image): int -{ - return $image->id; -} + return trim($output); +} \ No newline at end of file diff --git a/core/sys_config.php b/core/sys_config.php index d3ed2761..9ce0d8d0 100644 --- a/core/sys_config.php +++ b/core/sys_config.php @@ -40,7 +40,7 @@ _d("SEARCH_ACCEL", false); // boolean use search accelerator _d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse _d("VERSION", '2.7-beta'); // string shimmie version _d("TIMEZONE", null); // string timezone -_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,handle_static,comment,tag_list,index,tag_edit,alias_editor"); // extensions to always enable +_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,handle_static,comment,tag_list,index,tag_edit,alias_editor,media"); // extensions to always enable _d("EXTRA_EXTS", ""); // string optional extra extensions _d("BASE_URL", null); // string force a specific base URL (default is auto-detect) _d("MIN_PHP_VERSION", '7.1');// string minimum supported PHP version diff --git a/core/util.php b/core/util.php index 007822d5..91e467ff 100644 --- a/core/util.php +++ b/core/util.php @@ -79,7 +79,7 @@ function get_memory_limit(): int // thumbnail generation requires lots of memory $default_limit = 8*1024*1024; // 8 MB of memory is PHP's default. - $shimmie_limit = parse_shorthand_int($config->get_int("thumb_mem_limit")); + $shimmie_limit = parse_shorthand_int($config->get_int(MediaConfig::MEM_LIMIT)); if ($shimmie_limit < 3*1024*1024) { // we aren't going to fit, override diff --git a/ext/et/main.php b/ext/et/main.php index 702c9c9c..e3e9b9c7 100644 --- a/ext/et/main.php +++ b/ext/et/main.php @@ -52,14 +52,17 @@ class ET extends Extension $info['sys_disk'] = to_shorthand_int(disk_total_space("./") - disk_free_space("./")) . " / " . to_shorthand_int(disk_total_space("./")); $info['sys_server'] = isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown'; - - $info['thumb_engine'] = $config->get_string("thumb_engine"); - $info['thumb_quality'] = $config->get_int('thumb_quality'); - $info['thumb_width'] = $config->get_int('thumb_width'); - $info['thumb_height'] = $config->get_int('thumb_height'); - $info['thumb_scaling'] = $config->get_int('thumb_scaling'); - $info['thumb_type'] = $config->get_string('thumb_type'); - $info['thumb_mem'] = $config->get_int("thumb_mem_limit"); + + $info[MediaConfig::FFMPEG_PATH] = $config->get_string(MediaConfig::FFMPEG_PATH); + $info[MediaConfig::CONVERT_PATH] = $config->get_string(MediaConfig::CONVERT_PATH); + $info[MediaConfig::MEM_LIMIT] = $config->get_int(MediaConfig::MEM_LIMIT); + + $info[ImageConfig::THUMB_ENGINE] = $config->get_string(ImageConfig::THUMB_ENGINE); + $info[ImageConfig::THUMB_QUALITY] = $config->get_int(ImageConfig::THUMB_QUALITY); + $info[ImageConfig::THUMB_WIDTH] = $config->get_int(ImageConfig::THUMB_WIDTH); + $info[ImageConfig::THUMB_HEIGHT] = $config->get_int(ImageConfig::THUMB_HEIGHT); + $info[ImageConfig::THUMB_SCALING] = $config->get_int(ImageConfig::THUMB_SCALING); + $info[ImageConfig::THUMB_TYPE] = $config->get_string(ImageConfig::THUMB_TYPE); $info['stat_images'] = $database->get_one("SELECT COUNT(*) FROM images"); $info['stat_comments'] = $database->get_one("SELECT COUNT(*) FROM comments"); diff --git a/ext/et/theme.php b/ext/et/theme.php index cb55ffb9..0582fddf 100644 --- a/ext/et/theme.php +++ b/ext/et/theme.php @@ -35,14 +35,16 @@ Database: {$info['sys_db']} Server: {$info['sys_server']} Disk use: {$info['sys_disk']} +Media System: +Memory Limit: {$info[MediaConfig::MEM_LIMIT]} + Thumbnail Generation: -Engine: {$info['thumb_engine']} -Type: {$info['thumb_type']} -Memory: {$info['thumb_mem']} -Quality: {$info['thumb_quality']} -Width: {$info['thumb_width']} -Height: {$info['thumb_height']} -Scaling: {$info['thumb_scaling']} +Engine: {$info[ImageConfig::THUMB_ENGINE]} +Type: {$info[ImageConfig::THUMB_TYPE]} +Quality: {$info[ImageConfig::THUMB_QUALITY]} +Width: {$info[ImageConfig::THUMB_WIDTH]} +Height: {$info[ImageConfig::THUMB_HEIGHT]} +Scaling: {$info[ImageConfig::THUMB_SCALING]} Shimmie stats: Images: {$info['stat_images']} diff --git a/ext/handle_flash/main.php b/ext/handle_flash/main.php index e47b193b..3c53622d 100644 --- a/ext/handle_flash/main.php +++ b/ext/handle_flash/main.php @@ -8,11 +8,31 @@ class FlashFileHandler extends DataHandlerExtension { + + public function onMediaCheckProperties(MediaCheckPropertiesEvent $event) + { + switch ($event->ext) { + case "swf": + $event->lossless = true; + $event->video = true; + + $info = getimagesize($event->file_name); + if (!$info) { + return null; + } + + $event->width = $info[0]; + $event->height = $info[1]; + + break; + } + } + protected function create_thumb(string $hash, string $type): bool { global $config; - if (!create_thumbnail_ffmpeg($hash)) { + if (!Media::create_thumbnail_ffmpeg($hash)) { copy("ext/handle_flash/thumb.jpg", warehouse_path(Image::THUMBNAIL_DIR, $hash)); } return true; @@ -35,13 +55,7 @@ class FlashFileHandler extends DataHandlerExtension $image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']); $image->source = $metadata['source']; - $info = getimagesize($filename); - if (!$info) { - return null; - } - $image->width = $info[0]; - $image->height = $info[1]; return $image; } diff --git a/ext/handle_ico/main.php b/ext/handle_ico/main.php index ab005c96..f67937b6 100644 --- a/ext/handle_ico/main.php +++ b/ext/handle_ico/main.php @@ -10,6 +10,29 @@ class IcoFileHandler extends DataHandlerExtension const SUPPORTED_EXTENSIONS = ["ico", "ani", "cur"]; + public function onMediaCheckProperties(MediaCheckPropertiesEvent $event) + { + if(in_array($event->ext, self::SUPPORTED_EXTENSIONS)) { + $event->lossless = true; + $event->video = false; + $event->audio = false; + + $fp = fopen($event->file_name, "r"); + try { + unpack("Snull/Stype/Scount", fread($fp, 6)); + $subheader = unpack("Cwidth/Cheight/Ccolours/Cnull/Splanes/Sbpp/Lsize/loffset", fread($fp, 16)); + } finally { + fclose($fp); + } + + $width = $subheader['width']; + $height = $subheader['height']; + $event->width = $width == 0 ? 256 : $width; + $event->height = $height == 0 ? 256 : $height; + } + } + + protected function supported_ext(string $ext): bool { return in_array(strtolower($ext), self::SUPPORTED_EXTENSIONS); @@ -19,20 +42,6 @@ class IcoFileHandler extends DataHandlerExtension { $image = new Image(); - - $fp = fopen($filename, "r"); - try { - unpack("Snull/Stype/Scount", fread($fp, 6)); - $subheader = unpack("Cwidth/Cheight/Ccolours/Cnull/Splanes/Sbpp/Lsize/loffset", fread($fp, 16)); - } finally { - fclose($fp); - } - - $width = $subheader['width']; - $height = $subheader['height']; - $image->width = $width == 0 ? 256 : $width; - $image->height = $height == 0 ? 256 : $height; - $image->filesize = $metadata['size']; $image->hash = $metadata['hash']; $image->filename = $metadata['filename']; @@ -56,6 +65,12 @@ class IcoFileHandler extends DataHandlerExtension protected function create_thumb(string $hash, string $type): bool { - return create_thumbnail_convert($hash, $type); + try { + create_image_thumb($hash, $type, MediaEngine::IMAGICK); + return true; + } catch (MediaException $e) { + log_warning("handle_ico", "Could not generate thumbnail. " . $e->getMessage()); + return false; + } } } diff --git a/ext/handle_mp3/main.php b/ext/handle_mp3/main.php index e0fcd5a7..81fbee49 100644 --- a/ext/handle_mp3/main.php +++ b/ext/handle_mp3/main.php @@ -7,6 +7,19 @@ class MP3FileHandler extends DataHandlerExtension { + public function onMediaCheckProperties(MediaCheckPropertiesEvent $event) + { + switch ($event->ext) { + case "mp3": + $event->audio = true; + $event->video = false; + $event->lossless = false; + break; + } + // TODO: Buff out audio format support, length scanning + + } + protected function create_thumb(string $hash, string $type): bool { copy("ext/handle_mp3/thumb.jpg", warehouse_path(Image::THUMBNAIL_DIR, $hash)); @@ -37,6 +50,7 @@ class MP3FileHandler extends DataHandlerExtension $image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']); $image->source = $metadata['source']; + return $image; } diff --git a/ext/handle_pixel/main.php b/ext/handle_pixel/main.php index ac72bcc1..3b73bdfb 100644 --- a/ext/handle_pixel/main.php +++ b/ext/handle_pixel/main.php @@ -10,6 +10,44 @@ class PixelFileHandler extends DataHandlerExtension { const SUPPORTED_EXTENSIONS = ["jpg", "jpeg", "gif", "png", "webp"]; + + public function onMediaCheckProperties(MediaCheckPropertiesEvent $event) + { + if(in_array($event->ext, Media::LOSSLESS_FORMATS)) { + $event->lossless = true; + } elseif($event->ext=="webp") { + $event->lossless = Media::is_lossless_webp($event->file_name); + } + + if(in_array($event->ext,self::SUPPORTED_EXTENSIONS)) { + if($event->lossless==null) { + $event->lossless = false; + } + $event->audio = false; + switch ($event->ext) { + case "gif": + $event->video = Media::is_animated_gif($event->file_name); + break; + case "webp": + $event->video = Media::is_animated_webp($event->file_name); + break; + default: + $event->video = false; + break; + } + + $info = getimagesize($event->file_name); + if (!$info) { + return null; + } + + $event->width = $info[0]; + $event->height = $info[1]; + } + } + + + protected function supported_ext(string $ext): bool { $ext = (($pos = strpos($ext, '?')) !== false) ? substr($ext, 0, $pos) : $ext; @@ -20,14 +58,6 @@ class PixelFileHandler extends DataHandlerExtension { $image = new Image(); - $info = getimagesize($filename); - if (!$info) { - return null; - } - - $image->width = $info[0]; - $image->height = $info[1]; - $image->filesize = $metadata['size']; $image->hash = $metadata['hash']; $image->filename = (($pos = strpos($metadata['filename'], '?')) !== false) ? substr($metadata['filename'], 0, $pos) : $metadata['filename']; @@ -56,24 +86,22 @@ class PixelFileHandler extends DataHandlerExtension protected function create_thumb(string $hash, string $type): bool { - global $config; - - $inname = warehouse_path(Image::IMAGE_DIR, $hash); - $outname = warehouse_path(Image::THUMBNAIL_DIR, $hash); - - $ok = false; - - switch ($config->get_string("thumb_engine")) { - default: - case 'gd': - $ok = $this->make_thumb_gd($inname, $outname); - break; - case 'convert': - $ok = create_thumbnail_convert($hash); - break; + try { + create_image_thumb($hash, $type); + return true; + } catch (InsufficientMemoryException $e) { + $tsize = get_thumbnail_max_size_scaled(); + $thumb = imagecreatetruecolor($tsize[0], min($tsize[1], 64)); + $white = imagecolorallocate($thumb, 255, 255, 255); + $black = imagecolorallocate($thumb, 0, 0, 0); + imagefill($thumb, 0, 0, $white); + log_warning("handle_pixel", "Insufficient memory while creating thumbnail: ".$e->getMessage()); + imagestring($thumb, 5, 10, 24, "Image Too Large :(", $black); + return true; + } catch (Exception $e) { + log_error("handle_pixel", "Error while creating thumbnail: ".$e->getMessage()); + return false; } - - return $ok; } public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) @@ -90,38 +118,4 @@ class PixelFileHandler extends DataHandlerExtension ", 20); } - // GD thumber {{{ - private function make_thumb_gd(string $inname, string $outname): bool - { - global $config; - - try { - $info = getimagesize($inname); - $tsize = get_thumbnail_size($info[0], $info[1], true); - $image = image_resize_gd( - $inname, - $info, - $tsize[0], - $tsize[1], - $outname, - $config->get_string('thumb_type'), - $config->get_int('thumb_quality') - ); - } catch (InsufficientMemoryException $e) { - $tsize = get_thumbnail_max_size_scaled(); - $thumb = imagecreatetruecolor($tsize[0], min($tsize[1], 64)); - $white = imagecolorallocate($thumb, 255, 255, 255); - $black = imagecolorallocate($thumb, 0, 0, 0); - imagefill($thumb, 0, 0, $white); - log_warning("handle_pixel", "Insufficient memory while creating thumbnail: ".$e->getMessage()); - imagestring($thumb, 5, 10, 24, "Image Too Large :(", $black); - return true; - } catch (Exception $e) { - log_error("handle_pixel", "Error while creating thumbnail: ".$e->getMessage()); - return false; - } - - return true; - } - // }}} } diff --git a/ext/handle_pixel/theme.php b/ext/handle_pixel/theme.php index b8ebd36d..278ed0d7 100644 --- a/ext/handle_pixel/theme.php +++ b/ext/handle_pixel/theme.php @@ -7,7 +7,7 @@ class PixelFileHandlerTheme extends Themelet global $config; $u_ilink = $image->get_image_link(); - if ($config->get_bool("image_show_meta") && function_exists("exif_read_data")) { + if ($config->get_bool(ImageConfig::SHOW_META) && function_exists(ImageIO::EXIF_READ_FUNCTION)) { # FIXME: only read from jpegs? $exif = @exif_read_data($image->get_image_filename(), 0, true); if ($exif) { diff --git a/ext/handle_svg/main.php b/ext/handle_svg/main.php index 9998a245..79fd6613 100644 --- a/ext/handle_svg/main.php +++ b/ext/handle_svg/main.php @@ -10,6 +10,24 @@ use enshrined\svgSanitize\Sanitizer; class SVGFileHandler extends DataHandlerExtension { + + public function onMediaCheckProperties(MediaCheckPropertiesEvent $event) + { + switch ($event->ext) { + case "svg": + $event->lossless = true; + $event->video = false; + $event->audio = false; + + $msp = new MiniSVGParser($event->file_name); + $event->width = $msp->width; + $event->height = $msp->height; + + break; + } + } + + public function onDataUpload(DataUploadEvent $event) { if ($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) { @@ -35,10 +53,14 @@ class SVGFileHandler extends DataHandlerExtension protected function create_thumb(string $hash, string $type): bool { - if (!create_thumbnail_convert($hash)) { + try { + create_image_thumb($hash, $type, MediaEngine::IMAGICK); + return true; + } catch (MediaException $e) { + log_warning("handle_svg", "Could not generate thumbnail. " . $e->getMessage()); copy("ext/handle_svg/thumb.jpg", warehouse_path(Image::THUMBNAIL_DIR, $hash)); + return false; } - return true; } public function onDisplayingImage(DisplayingImageEvent $event) @@ -78,10 +100,6 @@ class SVGFileHandler extends DataHandlerExtension { $image = new Image(); - $msp = new MiniSVGParser($filename); - $image->width = $msp->width; - $image->height = $msp->height; - $image->filesize = $metadata['size']; $image->hash = $metadata['hash']; $image->filename = $metadata['filename']; diff --git a/ext/handle_video/main.php b/ext/handle_video/main.php index f4f50320..5c090faf 100644 --- a/ext/handle_video/main.php +++ b/ext/handle_video/main.php @@ -16,20 +16,21 @@ class VideoFileHandler extends DataHandlerExtension { + const SUPPORTED_MIME = [ + 'video/webm', + 'video/mp4', + 'video/ogg', + 'video/flv', + 'video/x-flv' + ]; + const SUPPORTED_EXT = ["flv", "mp4", "m4v", "ogv", "webm"]; + public function onInitExt(InitExtEvent $event) { global $config; if ($config->get_int("ext_handle_video_version") < 1) { - if ($ffmpeg = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' ffmpeg')) { - //ffmpeg exists in PATH, check if it's executable, and if so, default to it instead of static - if (is_executable(strtok($ffmpeg, PHP_EOL))) { - $config->set_default_string('thumb_ffmpeg_path', 'ffmpeg'); - } - } else { - $config->set_default_string('thumb_ffmpeg_path', ''); - } - + // This used to set the ffmpeg path. It does not do this anymore, that is now in the base graphic extension. $config->set_int("ext_handle_video_version", 1); log_info("handle_video", "extension installed"); } @@ -41,37 +42,83 @@ class VideoFileHandler extends DataHandlerExtension public function onSetupBuilding(SetupBuildingEvent $event) { $sb = new SetupBlock("Video Options"); - $sb->add_label("
Path to ffmpeg: "); - $sb->add_text_option("thumb_ffmpeg_path"); - $sb->add_label("
"); $sb->add_bool_option("video_playback_autoplay", "Autoplay: "); $sb->add_label("
"); $sb->add_bool_option("video_playback_loop", "Loop: "); $event->panel->add_block($sb); } + public function onMediaCheckProperties(MediaCheckPropertiesEvent $event) + { + if(in_array($event->ext, self::SUPPORTED_EXT)) { + $event->video = true; + try { + $data = Media::get_ffprobe_data($event->file_name); + + if(is_array($data)) { + if(array_key_exists("streams", $data)) { + $video = false; + $audio = true; + $streams = $data["streams"]; + if (is_array($streams)) { + foreach ($streams as $stream) { + if(is_array($stream)) { + if (array_key_exists("codec_type", $stream)) { + $type = $stream["codec_type"]; + switch ($type) { + case "audio": + $audio = true; + break; + case "video": + $video = true; + break; + } + } + if (array_key_exists("width", $stream) && !empty($stream["width"]) + && is_numeric($stream["width"]) && intval($stream["width"]) > ($event->width) ?? 0) { + $event->width = intval($stream["width"]); + } + if (array_key_exists("height", $stream) && !empty($stream["height"]) + && is_numeric($stream["height"]) && intval($stream["height"]) > ($event->height) ?? 0) { + $event->height = intval($stream["height"]); + } + + } + } + $event->video = $video; + $event->audio = $audio; + } + } + if(array_key_exists("format", $data)&& is_array($data["format"])) { + $format = $data["format"]; + if(array_key_exists("duration", $format) && is_numeric($format["duration"])) { + $event->length = floor(floatval($format["duration"]) * 1000); + } + } + } + } catch(MediaException $e) { + + } + } + } + /** * Generate the Thumbnail image for particular file. */ protected function create_thumb(string $hash, string $type): bool { - return create_thumbnail_ffmpeg($hash); + return Media::create_thumbnail_ffmpeg($hash); } protected function supported_ext(string $ext): bool { - $exts = ["flv", "mp4", "m4v", "ogv", "webm"]; - return in_array(strtolower($ext), $exts); + return in_array(strtolower($ext), self::SUPPORTED_EXT); } protected function create_image_from_data(string $filename, array $metadata): Image { $image = new Image(); - $size = video_size($filename); - $image->width = $size[0]; - $image->height = $size[1]; - switch (getMimeType($filename)) { case "video/webm": $image->ext = "webm"; @@ -103,13 +150,7 @@ class VideoFileHandler extends DataHandlerExtension { return ( file_exists($tmpname) && - in_array(getMimeType($tmpname), [ - 'video/webm', - 'video/mp4', - 'video/ogg', - 'video/flv', - 'video/x-flv' - ]) + in_array(getMimeType($tmpname), self::SUPPORTED_MIME) ); } } diff --git a/ext/handle_video/theme.php b/ext/handle_video/theme.php index 64251769..c304eff7 100644 --- a/ext/handle_video/theme.php +++ b/ext/handle_video/theme.php @@ -13,6 +13,15 @@ class VideoFileHandlerTheme extends Themelet $loop = $config->get_bool("video_playback_loop"); $player = make_link('vendor/bower-asset/mediaelement/build/flashmediaelement.swf'); + $width="auto"; + if($image->width>1) { + $width = $image->width."px"; + } + $height="auto"; + if($image->height>1) { + $height = $image->height."px"; + } + $html = "Video not playing? Click here to download the file.
"; //Browser media format support: https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats @@ -48,7 +57,8 @@ class VideoFileHandlerTheme extends Themelet $loop = ($loop ? ' loop' : ''); $html .= " -