From 3859e27839dc77fa3ba86e58ce45f04821cabd95 Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Tue, 18 Jun 2019 13:45:59 -0500 Subject: [PATCH 01/19] New Graphics extension Added constants to several extensions --- core/basethemelet.php | 2 +- core/imageboard/image.php | 8 +- core/imageboard/misc.php | 389 ++---------------- core/sys_config.php | 2 +- core/util.php | 2 +- ext/et/main.php | 19 +- ext/et/theme.php | 16 +- ext/graphics/main.php | 808 +++++++++++++++++++++++++++++++++++++ ext/graphics/theme.php | 6 + ext/handle_flash/main.php | 2 +- ext/handle_ico/main.php | 8 +- ext/handle_pixel/main.php | 66 +-- ext/handle_pixel/theme.php | 2 +- ext/handle_svg/main.php | 8 +- ext/handle_video/main.php | 37 +- ext/image/main.php | 110 ++--- ext/ouroboros_api/main.php | 12 +- ext/report_image/theme.php | 2 +- ext/resize/main.php | 50 ++- ext/resize/theme.php | 4 +- ext/rotate/main.php | 2 +- ext/rule34/main.php | 2 +- ext/setup/test.php | 2 +- ext/transcode/main.php | 138 ++----- 24 files changed, 1069 insertions(+), 628 deletions(-) create mode 100644 ext/graphics/main.php create mode 100644 ext/graphics/theme.php 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..d6b63371 100644 --- a/core/imageboard/image.php +++ b/core/imageboard/image.php @@ -463,7 +463,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 +480,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 +512,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..2a349c80 100644 --- a/core/imageboard/misc.php +++ b/core/imageboard/misc.php @@ -125,24 +125,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 +161,37 @@ 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 .= "\>"; - } - - $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 GraphicResizeEvent( + $engine, + $inname, + $type, + $outname, + $tsize[0], + $tsize[1], + false, + $config->get_string(ImageConfig::THUMB_TYPE), + $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 -{ - global $config; - $ffmpeg = $config->get_string("thumb_ffmpeg_path"); - if ($ffmpeg==null||$ffmpeg=="") { - return false; - } - - $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; -} diff --git a/core/sys_config.php b/core/sys_config.php index d3ed2761..a489a96f 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,graphics"); // 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..24bda493 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(GraphicsConfig::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..50a639e1 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[GraphicsConfig::FFMPEG_PATH] = $config->get_string(GraphicsConfig::FFMPEG_PATH); + $info[GraphicsConfig::CONVERT_PATH] = $config->get_string(GraphicsConfig::CONVERT_PATH); + $info[GraphicsConfig::MEM_LIMIT] = $config->get_int(GraphicsConfig::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..a0778dff 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']} +Graphics System: +Memory Limit: {$info[GraphicsConfig::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/graphics/main.php b/ext/graphics/main.php new file mode 100644 index 00000000..e46d470d --- /dev/null +++ b/ext/graphics/main.php @@ -0,0 +1,808 @@ + + * Description: Provides common functions and settings used for graphic operations. + * License: MIT + * Version: 1.0 + */ + +/* +* This is used by the graphics code when there is an error +*/ + +use FFMpeg\FFMpeg; + +abstract class GraphicsConfig +{ + const FFMPEG_PATH = "graphics_ffmpeg_path"; + const FFPROBE_PATH = "graphics_ffprobe_path"; + const CONVERT_PATH = "graphics_convert_path"; + const VERSION = "ext_graphics_version"; + const MEM_LIMIT = 'graphics_mem_limit'; + +} + +class GraphicsException extends SCoreException +{ +} + +class GraphicResizeEvent extends Event +{ + public $engine; + public $input_path; + public $input_type; + public $output_path; + public $target_format; + public $target_width; + public $target_height; + public $target_quality; + public $minimize; + public $ignore_aspect_ratio; + public $allow_upscale; + + public function __construct(String $engine, string $input_path, string $input_type, string $output_path, + int $target_width, int $target_height, + bool $ignore_aspect_ratio = false, + string $target_format = null, + int $target_quality = 80, + bool $minimize = false, + bool $allow_upscale = true) + { + assert(in_array($engine, Graphics::GRAPHICS_ENGINES)); + $this->engine = $engine; + $this->input_path = $input_path; + $this->input_type = $input_type; + $this->output_path = $output_path; + $this->target_height = $target_height; + $this->target_width = $target_width; + $this->target_format = $target_format; + $this->target_quality = $target_quality; + $this->minimize = $minimize; + $this->ignore_aspect_ratio = $ignore_aspect_ratio; + $this->allow_upscale = $allow_upscale; + } +} + +class Graphics extends Extension +{ + const WEBP_LOSSY = "webp-lossy"; + const WEBP_LOSSLESS = "webp-lossless"; + + const FFMPEG_ENGINE = "ffmpeg"; + const GD_ENGINE = "gd"; + const IMAGICK_ENGINE = "convert"; + + const GRAPHICS_ENGINES = [ + self::GD_ENGINE, + self::FFMPEG_ENGINE, + self::IMAGICK_ENGINE + ]; + + const IMAGE_GRAPHICS_ENGINES = [ + "GD" => self::GD_ENGINE, + "ImageMagick" => self::IMAGICK_ENGINE, + ]; + + const ENGINE_INPUT_SUPPORT = [ + self::GD_ENGINE => [ + "bmp", + "gif", + "jpg", + "png", + "webp", + ], + self::IMAGICK_ENGINE => [ + "bmp", + "gif", + "jpg", + "png", + "psd", + "tiff", + "webp", + "ico", + ], + self::FFMPEG_ENGINE => [ + "avi", + "mkv", + "webm", + "mp4", + "mov", + "flv" + ] + ]; + + const ENGINE_OUTPUT_SUPPORT = [ + self::GD_ENGINE => [ + "gif", + "jpg", + "png", + "webp", + self::WEBP_LOSSY, + ], + self::IMAGICK_ENGINE => [ + "gif", + "jpg", + "png", + "webp", + self::WEBP_LOSSY, + self::WEBP_LOSSLESS, + ], + self::FFMPEG_ENGINE => [ + + ] + ]; + + const LOSSLESS_FORMATS = [ + self::WEBP_LOSSLESS, + "png", + ]; + + const ALPHA_FORMATS = [ + self::WEBP_LOSSLESS, + self::WEBP_LOSSY, + "png", + ]; + + const FORMAT_ALIASES = [ + "tif" => "tiff", + "jpeg" => "jpg", + ]; + + + static function imagick_available(): bool + { + return extension_loaded("imagick"); + } + + /** + * High priority just so that it can be early in the settings + */ + public function get_priority(): int + { + return 30; + } + + public function onInitExt(InitExtEvent $event) + { + global $config; + $config->set_default_string(GraphicsConfig::FFPROBE_PATH, 'ffprobe'); + + + if ($config->get_int(GraphicsConfig::VERSION) < 1) { + $current_value = $config->get_string("thumb_ffmpeg_path"); + if(!empty($current_value)) { + $config->set_string(GraphicsConfig::FFMPEG_PATH, $current_value); + } elseif ($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(GraphicsConfig::FFMPEG_PATH, 'ffmpeg'); + } + } else { + $config->set_default_string(GraphicsConfig::FFMPEG_PATH, ''); + } + + $current_value = $config->get_string("thumb_convert_path"); + if(!empty($current_value)) { + $config->set_string(GraphicsConfig::CONVERT_PATH, $current_value); + } elseif ($convert = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' convert')) { + //ffmpeg exists in PATH, check if it's executable, and if so, default to it instead of static + if (is_executable(strtok($convert, PHP_EOL))) { + $config->set_default_string(GraphicsConfig::CONVERT_PATH, 'convert'); + } + } else { + $config->set_default_string(GraphicsConfig::CONVERT_PATH, ''); + } + + $current_value = $config->get_int("thumb_mem_limit"); + if(!empty($current_value)) { + $config->set_int(GraphicsConfig::MEM_LIMIT, $current_value); + } + + + + + $config->set_int(GraphicsConfig::VERSION, 1); + log_info("graphics", "extension installed"); + } + } + + public function onSetupBuilding(SetupBuildingEvent $event) + { + $sb = new SetupBlock("Graphics"); + +// if (self::imagick_available()) { +// try { +// $image = new Imagick(realpath('tests/favicon.png')); +// $image->clear(); +// $sb->add_label("ImageMagick detected"); +// } catch (ImagickException $e) { +// $sb->add_label("ImageMagick not detected"); +// } +// } else { + $sb->add_text_option(GraphicsConfig::CONVERT_PATH, "convert command: "); +// } + + $sb->add_text_option(GraphicsConfig::FFMPEG_PATH, "
ffmpeg command: "); + + $sb->add_shorthand_int_option(GraphicsConfig::MEM_LIMIT, "
Max memory use: "); + + $event->panel->add_block($sb); + + } + + /** + * @param GraphicResizeEvent $event + * @throws GraphicsException + * @throws InsufficientMemoryException + */ + public function onGraphicResize(GraphicResizeEvent $event) + { + switch ($event->engine) { + case self::GD_ENGINE: + $info = getimagesize($event->input_path); + if ($info === false) { + throw new GraphicsException("getimagesize failed for " . $event->input_path); + } + + self::image_resize_gd( + $event->input_path, + $info, + $event->target_width, + $event->target_height, + $event->output_path, + $event->target_format, + $event->ignore_aspect_ratio, + $event->target_quality, + $event->allow_upscale); + + break; + case self::IMAGICK_ENGINE: +// if (self::imagick_available()) { +// } else { + self::image_resize_convert( + $event->input_path, + $event->input_type, + $event->target_width, + $event->target_height, + $event->output_path, + $event->target_format, + $event->ignore_aspect_ratio, + $event->target_quality, + $event->minimize, + $event->allow_upscale); + //} + break; + default: + throw new GraphicsException("Engine not supported for resize: " . $event->engine); + } + + // TODO: Get output optimization tools working better +// if ($config->get_bool("thumb_optim", false)) { +// exec("jpegoptim $outname", $output, $ret); +// } + } + + /** + * 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. + */ + static 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; + } + + + /** + * Creates a thumbnail using ffmpeg. + * + * @param $hash + * @return bool true if successful, false if not. + * @throws GraphicsException + */ + static function create_thumbnail_ffmpeg($hash): bool + { + global $config; + + $ffmpeg = $config->get_string(GraphicsConfig::FFMPEG_PATH); + if ($ffmpeg == null || $ffmpeg == "") { + throw new GraphicsException("ffmpeg command configured"); + } + + $inname = warehouse_path(Image::IMAGE_DIR, $hash); + $outname = warehouse_path(Image::THUMBNAIL_DIR, $hash); + + $orig_size = self::video_size($inname); + $scaled_size = get_thumbnail_size($orig_size[0], $orig_size[1], true); + + $codec = "mjpeg"; + $quality = $config->get_int(ImageConfig::THUMB_QUALITY); + if ($config->get_string(ImageConfig::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('graphics', "Generating thumbnail with command `$cmd`, returns $ret"); + return true; + } else { + log_error('graphics', "Generating thumbnail with command `$cmd`, returns $ret"); + return false; + } + } + + public static function determine_ext(String $format): String + { + $format = self::normalize_format($format); + switch ($format) { + case self::WEBP_LOSSLESS: + case self::WEBP_LOSSY: + return "webp"; + default: + return $format; + } + } + +// private static function image_save_imagick(Imagick $image, string $path, string $format, int $output_quality = 80, bool $minimize) +// { +// switch ($format) { +// case "png": +// $result = $image->setOption('png:compression-level', 9); +// if ($result !== true) { +// throw new GraphicsException("Could not set png compression option"); +// } +// break; +// case Graphics::WEBP_LOSSLESS: +// $result = $image->setOption('webp:lossless', true); +// if ($result !== true) { +// throw new GraphicsException("Could not set lossless webp option"); +// } +// break; +// default: +// $result = $image->setImageCompressionQuality($output_quality); +// if ($result !== true) { +// throw new GraphicsException("Could not set compression quality for $path to $output_quality"); +// } +// break; +// } +// +// if (self::supports_alpha($format)) { +// $result = $image->setImageBackgroundColor(new \ImagickPixel('transparent')); +// } else { +// $result = $image->setImageBackgroundColor(new \ImagickPixel('black')); +// } +// if ($result !== true) { +// throw new GraphicsException("Could not set background color"); +// } +// +// +// if ($minimize) { +// $profiles = $image->getImageProfiles("icc", true); +// $result = $image->stripImage(); +// if ($result !== true) { +// throw new GraphicsException("Could not strip information from image"); +// } +// if (!empty($profiles)) { +// $image->profileImage("icc", $profiles['icc']); +// } +// } +// +// $ext = self::determine_ext($format); +// +// $result = $image->writeImage($ext . ":" . $path); +// if ($result !== true) { +// throw new GraphicsException("Could not write image to $path"); +// } +// } + +// public static function image_resize_imagick( +// String $input_path, +// String $input_type, +// int $new_width, +// int $new_height, +// string $output_filename, +// string $output_type = null, +// bool $ignore_aspect_ratio = false, +// int $output_quality = 80, +// bool $minimize = false, +// bool $allow_upscale = true +// ): void +// { +// global $config; +// +// if (!empty($input_type)) { +// $input_type = self::determine_ext($input_type); +// } +// +// try { +// $image = new Imagick($input_type . ":" . $input_path); +// try { +// $result = $image->flattenImages(); +// if ($result !== true) { +// throw new GraphicsException("Could not flatten image $input_path"); +// } +// +// $height = $image->getImageHeight(); +// $width = $image->getImageWidth(); +// if (!$allow_upscale && +// ($new_width > $width || $new_height > $height)) { +// $new_height = $height; +// $new_width = $width; +// } +// +// $result = $image->resizeImage($new_width, $new_width, Imagick::FILTER_LANCZOS, 0, !$ignore_aspect_ratio); +// if ($result !== true) { +// throw new GraphicsException("Could not perform image resize on $input_path"); +// } +// +// +// if (empty($output_type)) { +// $output_type = $input_type; +// } +// +// self::image_save_imagick($image, $output_filename, $output_type, $output_quality); +// +// } finally { +// $image->destroy(); +// } +// } catch (ImagickException $e) { +// throw new GraphicsException("Error while resizing with Imagick: " . $e->getMessage(), $e->getCode(), $e); +// } +// } + + + public static function image_resize_convert( + String $input_path, + String $input_type, + int $new_width, + int $new_height, + string $output_filename, + string $output_type = null, + bool $ignore_aspect_ratio = false, + int $output_quality = 80, + bool $minimize = false, + bool $allow_upscale = true + ): void + { + global $config; + + $convert = $config->get_string(GraphicsConfig::CONVERT_PATH); + + if ($convert == null || $convert == "") { + throw new GraphicsException("convert command not configured"); + } + + if (empty($output_type)) { + $output_type = $input_type; + } + + $bg = "black"; + if (self::supports_alpha($output_type)) { + $bg = "none"; + } + if (!empty($input_type)) { + $input_type = $input_type . ":"; + } + $args = ""; + if ($minimize) { + $args = " -strip -thumbnail"; + } + + $resize_args = ""; + if (!$allow_upscale) { + $resize_args .= "\>"; + } + if ($ignore_aspect_ratio) { + $resize_args .= "\!"; + } + + $format = '"%s" -flatten %s %ux%u%s -quality %u -background %s %s"%s[0]" %s:"%s" 2>&1'; + $cmd = sprintf($format, $convert, $args, $new_width, $new_height, $resize_args, $output_quality, $bg, $input_type, $input_path, $output_type, $output_filename); + $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) { + throw new GraphicsException("Resizing image with command `$cmd`, returns $ret, outputting " . implode("\r\n", $output)); + } else { + log_debug('graphics', "Generating thumbnail with command `$cmd`, returns $ret"); + } + } + + /** + * 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 GraphicsException + * @throws InsufficientMemoryException if the estimated memory usage exceeds the memory limit. + */ + public static function image_resize_gd( + String $image_filename, + array $info, + int $new_width, + int $new_height, + string $output_filename, + string $output_type = null, + bool $ignore_aspect_ratio = false, + int $output_quality = 80, + bool $allow_upscale = true + ) + { + $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 GraphicsException("Failed to save the new image - Unsupported image type."); + } + } + + $memory_use = self::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)"); + } + + if (!$ignore_aspect_ratio) { + list($new_width, $new_height) = get_scaled_by_aspect_ratio($width, $height, $new_width, $new_height); + } + if (!$allow_upscale && + ($new_width > $width || $new_height > $height)) { + $new_height = $height; + $new_width = $width; + } + + $image = imagecreatefromstring(file_get_contents($image_filename)); + $image_resized = imagecreatetruecolor($new_width, $new_height); + try { + if ($image === false) { + throw new GraphicsException("Could not load image: " . $image_filename); + } + if ($image_resized === false) { + throw new GraphicsException("Could not create output image with dimensions $new_width c $new_height "); + } + + // Handle transparent images + switch ($info[2]) { + case IMAGETYPE_GIF: + $transparency = imagecolortransparent($image); + $pallet_size = imagecolorstotal($image); + + // If we have a specific transparent color + if ($transparency >= 0 && $transparency < $pallet_size) { + // 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 GraphicsException("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 GraphicsException("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 GraphicsException("Unable to disable image alpha blending"); + } + if (imagesavealpha($image_resized, true) === false) { + throw new GraphicsException("Unable to enable image save alpha"); + } + $transparent_color = imagecolorallocatealpha($image_resized, 255, 255, 255, 127); + if ($transparent_color === false) { + throw new GraphicsException("Unable to allocate transparent color"); + } + if (imagefilledrectangle($image_resized, 0, 0, $new_width, $new_height, $transparent_color) === false) { + throw new GraphicsException("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 GraphicsException("Unable to copy resized image data to new image"); + } + + switch ($output_type) { + case "bmp": + $result = imagebmp($image_resized, $output_filename, true); + break; + case "webp": + case Graphics::WEBP_LOSSY: + $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 GraphicsException("Failed to save the new image - Unsupported image type: $output_type"); + } + if ($result === false) { + throw new GraphicsException("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. + */ + public static 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); + } + + public static function supports_alpha(string $format) + { + return in_array(self::normalize_format($format), self::ALPHA_FORMATS); + } + + public static function is_input_supported($engine, $format): bool + { + $format = self::normalize_format($format); + if (!in_array($format, Graphics::ENGINE_INPUT_SUPPORT[$engine])) { + return false; + } + return true; + } + + public static function is_output_supported($engine, $format): bool + { + $format = self::normalize_format($format); + if (!in_array($format, Graphics::ENGINE_OUTPUT_SUPPORT[$engine])) { + return false; + } + return true; + } + + /** + * Checks if a format (normally a file extension) is a variant name of another format (ie, jpg and jpeg). + * If one is found, then the maine name that the Graphics extension will recognize is returned, + * otherwise the incoming format is returned. + * + * @param $format + * @return string|null The format name that the graphics extension will recognize. + */ + static public function normalize_format($format): ?string + { + if (array_key_exists($format, Graphics::FORMAT_ALIASES)) { + return self::FORMAT_ALIASES[$format]; + } + return $format; + } + + + /** + * Determines the dimensions of a video file using ffmpeg. + * + * @param string $filename + * @return array [width, height] + */ + static public function video_size(string $filename): array + { + global $config; + $ffmpeg = $config->get_string(GraphicsConfig::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('graphics', "Getting video size with `$cmd`, returns $output -- $size[0], $size[1]"); + return $size; + } + +} diff --git a/ext/graphics/theme.php b/ext/graphics/theme.php new file mode 100644 index 00000000..6210644e --- /dev/null +++ b/ext/graphics/theme.php @@ -0,0 +1,6 @@ +getMessage()); + return false; + } } } diff --git a/ext/handle_pixel/main.php b/ext/handle_pixel/main.php index ac72bcc1..f5c9b3f1 100644 --- a/ext/handle_pixel/main.php +++ b/ext/handle_pixel/main.php @@ -56,24 +56,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 +88,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..da4c9ff4 100644 --- a/ext/handle_svg/main.php +++ b/ext/handle_svg/main.php @@ -35,10 +35,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, Graphics::IMAGICK_ENGINE); + return true; + } catch (GraphicsException $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) diff --git a/ext/handle_video/main.php b/ext/handle_video/main.php index f4f50320..15f192cc 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,9 +42,6 @@ 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: "); @@ -55,20 +53,19 @@ class VideoFileHandler extends DataHandlerExtension */ protected function create_thumb(string $hash, string $type): bool { - return create_thumbnail_ffmpeg($hash); + return Graphics::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); + $size = Graphics::video_size($filename); $image->width = $size[0]; $image->height = $size[1]; @@ -103,13 +100,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/image/main.php b/ext/image/main.php index c460660c..dc53ee7c 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -9,30 +9,64 @@ */ +abstract class ImageConfig { + const THUMB_ENGINE = 'thumb_engine'; + const THUMB_WIDTH = 'thumb_width'; + const THUMB_HEIGHT = 'thumb_height'; + const THUMB_SCALING = 'thumb_scaling'; + const THUMB_QUALITY = 'thumb_quality'; + const THUMB_TYPE = 'thumb_type'; + + const SHOW_META = 'image_show_meta'; + const ILINK = 'image_ilink'; + const TLINK = 'image_tlink'; + const TIP = 'image_tip'; + const EXPIRES = 'image_expires'; + const UPLOAD_COLLISION_HANDLER = 'upload_collision_handler'; + + const COLLISION_MERGE = 'merge'; + const COLLISION_ERROR = 'error'; + +} + /** * A class to handle adding / getting / removing image files from the disk. */ class ImageIO extends Extension { + + const COLLISION_OPTIONS = ['Error'=>ImageConfig::COLLISION_ERROR, 'Merge'=>ImageConfig::COLLISION_MERGE]; + + const EXIF_READ_FUNCTION = "exif_read_data"; + + + const THUMBNAIL_ENGINES = [ + 'Built-in GD' => Graphics::GD_ENGINE, + 'ImageMagick' => Graphics::IMAGICK_ENGINE + ]; + + const THUMBNAIL_TYPES = [ + 'JPEG' => "jpg", + 'WEBP (Not IE/Safari compatible)' => "webp" + ]; + public function onInitExt(InitExtEvent $event) { global $config; - $config->set_default_int('thumb_width', 192); - $config->set_default_int('thumb_height', 192); - $config->set_default_int('thumb_scaling', 100); - $config->set_default_int('thumb_quality', 75); - $config->set_default_string('thumb_type', 'jpg'); - $config->set_default_int('thumb_mem_limit', parse_shorthand_int('8MB')); - $config->set_default_string('thumb_convert_path', 'convert'); + $config->set_default_int(ImageConfig::THUMB_WIDTH, 192); + $config->set_default_int(ImageConfig::THUMB_HEIGHT, 192); + $config->set_default_int(ImageConfig::THUMB_SCALING, 100); + $config->set_default_int(ImageConfig::THUMB_QUALITY, 75); + $config->set_default_string(ImageConfig::THUMB_TYPE, 'jpg'); - if (function_exists("exif_read_data")) { - $config->set_default_bool('image_show_meta', false); + if (function_exists(self::EXIF_READ_FUNCTION)) { + $config->set_default_bool(ImageConfig::SHOW_META, false); } - $config->set_default_string('image_ilink', ''); - $config->set_default_string('image_tlink', ''); - $config->set_default_string('image_tip', '$tags // $size // $filesize'); - $config->set_default_string('upload_collision_handler', 'error'); - $config->set_default_int('image_expires', (60*60*24*31)); // defaults to one month + $config->set_default_string(ImageConfig::ILINK, ''); + $config->set_default_string(ImageConfig::TLINK, ''); + $config->set_default_string(ImageConfig::TIP, '$tags // $size // $filesize'); + $config->set_default_string(ImageConfig::UPLOAD_COLLISION_HANDLER, ImageConfig::COLLISION_ERROR); + $config->set_default_int(ImageConfig::EXPIRES, (60*60*24*31)); // defaults to one month } public function onPageRequest(PageRequestEvent $event) @@ -125,50 +159,36 @@ class ImageIO extends Extension $sb = new SetupBlock("Image Options"); $sb->position = 30; // advanced only - //$sb->add_text_option("image_ilink", "Image link: "); - //$sb->add_text_option("image_tlink", "
Thumbnail link: "); - $sb->add_text_option("image_tip", "Image tooltip: "); - $sb->add_choice_option("upload_collision_handler", ['Error'=>'error', 'Merge'=>'merge'], "
Upload collision handler: "); - if (function_exists("exif_read_data")) { - $sb->add_bool_option("image_show_meta", "
Show metadata: "); + //$sb->add_text_option(ImageConfig::ILINK, "Image link: "); + //$sb->add_text_option(ImageConfig::TLINK, "
Thumbnail link: "); + $sb->add_text_option(ImageConfig::TIP, "Image tooltip: "); + $sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "
Upload collision handler: "); + if (function_exists(self::EXIF_READ_FUNCTION)) { + $sb->add_bool_option(ImageConfig::SHOW_META, "
Show metadata: "); } $event->panel->add_block($sb); - $thumbers = []; - $thumbers['Built-in GD'] = "gd"; - $thumbers['ImageMagick'] = "convert"; - $thumb_types = []; - $thumb_types['JPEG'] = "jpg"; - $thumb_types['WEBP (Not IE/Safari compatible)'] = "webp"; $sb = new SetupBlock("Thumbnailing"); - $sb->add_choice_option("thumb_engine", $thumbers, "Engine: "); + $sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine: "); $sb->add_label("
"); - $sb->add_choice_option("thumb_type", $thumb_types, "Filetype: "); + $sb->add_choice_option(ImageConfig::THUMB_TYPE, self::THUMBNAIL_TYPES, "Filetype: "); $sb->add_label("
Size "); - $sb->add_int_option("thumb_width"); + $sb->add_int_option(ImageConfig::THUMB_WIDTH); $sb->add_label(" x "); - $sb->add_int_option("thumb_height"); + $sb->add_int_option(ImageConfig::THUMB_HEIGHT); $sb->add_label(" px at "); - $sb->add_int_option("thumb_quality"); + $sb->add_int_option(ImageConfig::THUMB_QUALITY); $sb->add_label(" % quality "); $sb->add_label("
High-DPI scaling "); - $sb->add_int_option("thumb_scaling"); + $sb->add_int_option(ImageConfig::THUMB_SCALING); $sb->add_label("%"); - if ($config->get_string("thumb_engine") == "convert") { - $sb->add_label("
ImageMagick Binary: "); - $sb->add_text_option("thumb_convert_path"); - } - - if ($config->get_string("thumb_engine") == "gd") { - $sb->add_shorthand_int_option("thumb_mem_limit", "
Max memory use: "); - } $event->panel->add_block($sb); } @@ -193,8 +213,8 @@ class ImageIO extends Extension */ $existing = Image::by_hash($image->hash); if (!is_null($existing)) { - $handler = $config->get_string("upload_collision_handler"); - if ($handler == "merge" || isset($_GET['update'])) { + $handler = $config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER); + if ($handler == ImageConfig::COLLISION_MERGE || isset($_GET['update'])) { $merged = array_merge($image->get_tag_array(), $existing->get_tag_array()); send_event(new TagSetEvent($existing, $merged)); if (isset($_GET['rating']) && isset($_GET['update']) && ext_is_live("Ratings")) { @@ -256,7 +276,7 @@ class ImageIO extends Extension global $page; if (!is_null($image)) { if ($type == "thumb") { - $ext = $config->get_string("thumb_type"); + $ext = $config->get_string(ImageConfig::THUMB_TYPE); if (array_key_exists($ext, MIME_TYPE_MAP)) { $page->set_type(MIME_TYPE_MAP[$ext]); } else { @@ -289,8 +309,8 @@ class ImageIO extends Extension $page->set_file($file); - if ($config->get_int("image_expires")) { - $expires = date(DATE_RFC1123, time() + $config->get_int("image_expires")); + if ($config->get_int(ImageConfig::EXPIRES)) { + $expires = date(DATE_RFC1123, time() + $config->get_int(ImageConfig::EXPIRES)); } else { $expires = 'Fri, 2 Sep 2101 12:42:42 GMT'; // War was beginning } diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php index 91fd85ef..d701fbea 100644 --- a/ext/ouroboros_api/main.php +++ b/ext/ouroboros_api/main.php @@ -231,8 +231,8 @@ class _SafeOuroborosImage $this->has_notes = false; // thumb - $this->preview_height = $config->get_int('thumb_height'); - $this->preview_width = $config->get_int('thumb_width'); + $this->preview_height = $config->get_int(ImageConfig::THUMB_HEIGHT); + $this->preview_width = $config->get_int(ImageConfig::THUMB_WIDTH); $this->preview_url = make_http($img->get_thumb_link()); // sample (use the full image here) @@ -481,8 +481,8 @@ class OuroborosAPI extends Extension protected function postCreate(OuroborosPost $post, string $md5 = '') { global $config; - $handler = $config->get_string("upload_collision_handler"); - if (!empty($md5) && !($handler == 'merge')) { + $handler = $config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER); + if (!empty($md5) && !($handler == ImageConfig::COLLISION_MERGE)) { $img = Image::by_hash($md5); if (!is_null($img)) { $this->sendResponse(420, self::ERROR_POST_CREATE_DUPE); @@ -524,8 +524,8 @@ class OuroborosAPI extends Extension if (!empty($meta['hash'])) { $img = Image::by_hash($meta['hash']); if (!is_null($img)) { - $handler = $config->get_string("upload_collision_handler"); - if ($handler == "merge") { + $handler = $config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER); + if ($handler == ImageConfig::COLLISION_MERGE) { $postTags = is_array($post->tags) ? $post->tags : Tag::explode($post->tags); $merged = array_merge($postTags, $img->get_tag_array()); send_event(new TagSetEvent($img, $merged)); diff --git a/ext/report_image/theme.php b/ext/report_image/theme.php index a2a80f91..f4de5934 100644 --- a/ext/report_image/theme.php +++ b/ext/report_image/theme.php @@ -46,7 +46,7 @@ class ReportImageTheme extends Themelet "; } - $thumb_width = $config->get_int("thumb_width"); + $thumb_width = $config->get_int(ImageConfig::THUMB_WIDTH); $html = " diff --git a/ext/resize/main.php b/ext/resize/main.php index 08036260..8cbb7dd6 100644 --- a/ext/resize/main.php +++ b/ext/resize/main.php @@ -26,8 +26,6 @@ abstract class ResizeConfig */ class ResizeImage extends Extension { - const SUPPORTED_EXT = ["jpg","jpeg","png","gif","webp"]; - /** * Needs to be after the data processing extensions */ @@ -40,17 +38,18 @@ class ResizeImage extends Extension public function onInitExt(InitExtEvent $event) { global $config; - $config->set_default_bool('resize_enabled', true); - $config->set_default_bool('resize_upload', false); - $config->set_default_int('resize_default_width', 0); - $config->set_default_int('resize_default_height', 0); + $config->set_default_bool(ResizeConfig::ENABLED, true); + $config->set_default_bool(ResizeConfig::UPLOAD, false); + $config->set_default_string(ResizeConfig::ENGINE, Graphics::GD_ENGINE); + $config->set_default_int(ResizeConfig::DEFAULT_WIDTH, 0); + $config->set_default_int(ResizeConfig::DEFAULT_HEIGHT, 0); } public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) { global $user, $config; - if ($user->is_admin() && $config->get_bool("resize_enabled") - && in_array($event->image->ext, self::SUPPORTED_EXT)) { + if ($user->is_admin() && $config->get_bool(ResizeConfig::ENABLED) + && $this->can_resize_format($event->image->ext)) { /* Add a link to resize the image */ $event->add_part($this->theme->get_resize_html($event->image)); } @@ -60,6 +59,7 @@ class ResizeImage extends Extension { $sb = new SetupBlock("Image Resize"); $sb->start_table(); + $sb->add_choice_option(ResizeConfig::ENGINE, Media::IMAGE_MEDIA_ENGINES, "Engine: ", true); $sb->add_bool_option(ResizeConfig::ENABLED, "Allow resizing images: ", true); $sb->add_bool_option(ResizeConfig::UPLOAD, "Resize on upload: ", true); $sb->end_table(); @@ -82,15 +82,15 @@ class ResizeImage extends Extension $image_obj = Image::by_id($event->image_id); - if ($config->get_bool("resize_upload") == true - && in_array($event->type, self::SUPPORTED_EXT)) { + if ($config->get_bool(ResizeConfig::UPLOAD) == true + && $this->can_resize_format($event->type)) { $width = $height = 0; - if ($config->get_int("resize_default_width") !== 0) { - $height = $config->get_int("resize_default_width"); + if ($config->get_int(ResizeConfig::DEFAULT_WIDTH) !== 0) { + $height = $config->get_int(ResizeConfig::DEFAULT_WIDTH); } - if ($config->get_int("resize_default_height") !== 0) { - $height = $config->get_int("resize_default_height"); + if ($config->get_int(ResizeConfig::DEFAULT_HEIGHT) !== 0) { + $height = $config->get_int(ResizeConfig::DEFAULT_HEIGHT); } $isanigif = 0; if ($image_obj->ext == "gif") { @@ -169,8 +169,16 @@ class ResizeImage extends Extension } } } - - + + private function can_resize_format($format): bool + { + global $config; + $engine = $config->get_string(ResizeConfig::ENGINE); + return Graphics::is_input_supported($engine, $format) + && Graphics::is_output_supported($engine, $format); + } + + // Private functions /* ----------------------------- */ private function resize_image(Image $image_obj, int $width, int $height) @@ -197,7 +205,15 @@ class ResizeImage extends Extension throw new ImageResizeException("Unable to save temporary image file."); } - image_resize_gd($image_filename, $info, $new_width, $new_height, $tmp_filename); + send_event(new GraphicResizeEvent( + Graphics::GD_ENGINE, + $image_filename, + $image_obj->ext, + $tmp_filename, + $new_width, + $new_height, + true + )); $new_image = new Image(); $new_image->hash = md5_file($tmp_filename); diff --git a/ext/resize/theme.php b/ext/resize/theme.php index 0cc86777..302af40f 100644 --- a/ext/resize/theme.php +++ b/ext/resize/theme.php @@ -9,8 +9,8 @@ class ResizeImageTheme extends Themelet { global $config; - $default_width = $config->get_int('resize_default_width'); - $default_height = $config->get_int('resize_default_height'); + $default_width = $config->get_int(ResizeConfig::DEFAULT_WIDTH); + $default_height = $config->get_int(ResizeConfig::DEFAULT_HEIGHT); if (!$default_width) { $default_width = $image->width; diff --git a/ext/rotate/main.php b/ext/rotate/main.php index 65194bbc..bf539ba8 100644 --- a/ext/rotate/main.php +++ b/ext/rotate/main.php @@ -130,7 +130,7 @@ class RotateImage extends Extension $info = getimagesize($image_filename); - $memory_use =calc_memory_use($info); + $memory_use = Graphics::calc_memory_use($info); $memory_limit = get_memory_limit(); if ($memory_use > $memory_limit) { diff --git a/ext/rule34/main.php b/ext/rule34/main.php index e1e6f5a3..b26fb166 100644 --- a/ext/rule34/main.php +++ b/ext/rule34/main.php @@ -30,7 +30,7 @@ class Rule34 extends Extension public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event) { global $config; - $image_link = $config->get_string('image_ilink'); + $image_link = $config->get_string(ImageConfig::ILINK); $url0 = $event->image->parse_link_template($image_link, "url_escape", 0); $url1 = $event->image->parse_link_template($image_link, "url_escape", 1); $html = ""; diff --git a/ext/setup/test.php b/ext/setup/test.php index c2bb2874..9f55a090 100644 --- a/ext/setup/test.php +++ b/ext/setup/test.php @@ -39,6 +39,6 @@ class SetupTest extends ShimmiePHPUnitTestCase $this->log_in_as_admin(); $this->get_page('setup/advanced'); $this->assert_title("Shimmie Setup"); - $this->assert_text("thumb_quality"); + $this->assert_text(ImageConfig::THUMB_QUALITY); } } diff --git a/ext/transcode/main.php b/ext/transcode/main.php index 9c8d3185..d5562e8c 100644 --- a/ext/transcode/main.php +++ b/ext/transcode/main.php @@ -33,50 +33,6 @@ class TranscodeImage extends Extension { const ACTION_BULK_TRANSCODE = "bulk_transcode"; - const CONVERSION_ENGINES = [ - "GD" => "gd", - "ImageMagick" => "convert", - ]; - - const ENGINE_INPUT_SUPPORT = [ - "gd" => [ - "bmp", - "gif", - "jpg", - "png", - "webp", - ], - "convert" => [ - "bmp", - "gif", - "jpg", - "png", - "psd", - "tiff", - "webp", - "ico", - ] - ]; - - const ENGINE_OUTPUT_SUPPORT = [ - "gd" => [ - "jpg", - "png", - "webp-lossy", - ], - "convert" => [ - "jpg", - "png", - "webp-lossy", - "webp-lossless", - ] - ]; - - const LOSSLESS_FORMATS = [ - "webp-lossless", - "png", - ]; - const INPUT_FORMATS = [ "BMP" => "bmp", "GIF" => "gif", @@ -88,17 +44,12 @@ class TranscodeImage extends Extension "WEBP" => "webp", ]; - const FORMAT_ALIASES = [ - "tif" => "tiff", - "jpeg" => "jpg", - ]; - const OUTPUT_FORMATS = [ "" => "", "JPEG (lossy)" => "jpg", "PNG (lossless)" => "png", - "WEBP (lossy)" => "webp-lossy", - "WEBP (lossless)" => "webp-lossless", + "WEBP (lossy)" => Graphics::WEBP_LOSSY, + "WEBP (lossless)" => Graphics::WEBP_LOSSLESS, ]; /** @@ -113,13 +64,13 @@ class TranscodeImage extends Extension public function onInitExt(InitExtEvent $event) { global $config; - $config->set_default_bool('transcode_enabled', true); - $config->set_default_bool('transcode_upload', false); - $config->set_default_string('transcode_engine', "gd"); - $config->set_default_int('transcode_quality', 80); + $config->set_default_bool(TranscodeConfig::ENABLED, true); + $config->set_default_bool(TranscodeConfig::UPLOAD, false); + $config->set_default_string(TranscodeConfig::ENGINE, Graphics::GD_ENGINE); + $config->set_default_int(TranscodeConfig::QUALITY, 80); foreach (array_values(self::INPUT_FORMATS) as $format) { - $config->set_default_string('transcode_upload_'.$format, ""); + $config->set_default_string(TranscodeConfig::UPLOAD_PREFIX.$format, ""); } } @@ -127,8 +78,8 @@ class TranscodeImage extends Extension { global $user, $config; - if ($user->is_admin() && $config->get_bool("resize_enabled")) { - $engine = $config->get_string("transcode_engine"); + if ($user->is_admin()) { + $engine = $config->get_string(TranscodeConfig::ENGINE); if ($this->can_convert_format($engine, $event->image->ext)) { $options = $this->get_supported_output_formats($engine, $event->image->ext); $event->add_part($this->theme->get_transcode_html($event->image, $options)); @@ -140,16 +91,16 @@ class TranscodeImage extends Extension { global $config; - $engine = $config->get_string("transcode_engine"); + $engine = $config->get_string(TranscodeConfig::ENGINE); $sb = new SetupBlock("Image Transcode"); $sb->start_table(); $sb->add_bool_option(TranscodeConfig::ENABLED, "Allow transcoding images: ", true); $sb->add_bool_option(TranscodeConfig::UPLOAD, "Transcode on upload: ", true); - $sb->add_choice_option(TranscodeConfig::ENGINE, self::CONVERSION_ENGINES, "Engine", true); + $sb->add_choice_option(TranscodeConfig::ENGINE, Graphics::IMAGE_GRAPHICS_ENGINES, "Engine", true); foreach (self::INPUT_FORMATS as $display=>$format) { - if (in_array($format, self::ENGINE_INPUT_SUPPORT[$engine])) { + if (in_array($format, Graphics::ENGINE_INPUT_SUPPORT[$engine])) { $outputs = $this->get_supported_output_formats($engine, $format); $sb->add_choice_option(TranscodeConfig::UPLOAD_PREFIX.$format, $outputs, "$display", true); } @@ -163,23 +114,23 @@ class TranscodeImage extends Extension { global $config, $page; - if ($config->get_bool("transcode_upload") == true) { + if ($config->get_bool(TranscodeConfig::UPLOAD) == true) { $ext = strtolower($event->type); - $ext = $this->clean_format($ext); + $ext = Graphics::normalize_format($ext); - if ($event->type=="gif"&&is_animated_gif($event->tmpname)) { + if ($event->type=="gif"&&Graphics::is_animated_gif($event->tmpname)) { return; } if (in_array($ext, array_values(self::INPUT_FORMATS))) { - $target_format = $config->get_string("transcode_upload_".$ext); + $target_format = $config->get_string(TranscodeConfig::UPLOAD_PREFIX.$ext); if (empty($target_format)) { return; } try { $new_image = $this->transcode_image($event->tmpname, $ext, $target_format); - $event->set_type($this->determine_ext($target_format)); + $event->set_type(Graphics::determine_ext($target_format)); $event->set_tmpname($new_image); } catch (Exception $e) { log_error("transcode", "Error while performing upload transcode: ".$e->getMessage()); @@ -227,7 +178,7 @@ class TranscodeImage extends Extension { global $user, $config; - $engine = $config->get_string("transcode_engine"); + $engine = $config->get_string(TranscodeConfig::ENGINE); if ($user->is_admin()) { $event->add_action(self::ACTION_BULK_TRANSCODE, "Transcode", null,"", $this->theme->get_transcode_picker_html($this->get_supported_output_formats($engine))); @@ -239,7 +190,7 @@ class TranscodeImage extends Extension global $user, $database; switch ($event->action) { - case "bulk_transcode": + case self::ACTION_BULK_TRANSCODE: if (!isset($_POST['transcode_format'])) { return; } @@ -251,8 +202,9 @@ class TranscodeImage extends Extension $database->beginTransaction(); $this->transcode_and_replace_image($image, $format); - // If a subsequent transcode fails, the database need to have everything about the previous transcodes recorded already, - // otherwise the image entries will be stuck pointing to missing image files + // If a subsequent transcode fails, the database needs to have everything about the previous + // transcodes recorded already, otherwise the image entries will be stuck pointing to + // missing image files $database->commit(); $total++; } catch (Exception $e) { @@ -269,54 +221,34 @@ class TranscodeImage extends Extension } } - private function clean_format($format): ?string - { - if (array_key_exists($format, self::FORMAT_ALIASES)) { - return self::FORMAT_ALIASES[$format]; - } - return $format; - } private function can_convert_format($engine, $format): bool { - $format = $this->clean_format($format); - if (!in_array($format, self::ENGINE_INPUT_SUPPORT[$engine])) { - return false; - } - return true; + return Graphics::is_input_supported($engine, $format); } + private function get_supported_output_formats($engine, ?String $omit_format = null): array { - $omit_format = $this->clean_format($omit_format); + $omit_format = Graphics::normalize_format($omit_format); $output = []; foreach (self::OUTPUT_FORMATS as $key=>$value) { if ($value=="") { $output[$key] = $value; continue; } - if (in_array($value, self::ENGINE_OUTPUT_SUPPORT[$engine]) - &&(empty($omit_format)||$omit_format!=$this->determine_ext($value))) { + if(Graphics::is_output_supported($engine, $value) + &&(empty($omit_format)||$omit_format!=Graphics::determine_ext($value))) { $output[$key] = $value; } } return $output; } - private function determine_ext(String $format): String - { - switch ($format) { - case "webp-lossless": - case "webp-lossy": - return "webp"; - default: - return $format; - } - } + private function transcode_and_replace_image(Image $image_obj, String $target_format) { - $target_format = $this->clean_format($target_format); $original_file = warehouse_path(Image::IMAGE_DIR, $image_obj->hash); $tmp_filename = $this->transcode_image($original_file, $image_obj->ext, $target_format); @@ -327,7 +259,7 @@ class TranscodeImage extends Extension $new_image->filename = $image_obj->filename; $new_image->width = $image_obj->width; $new_image->height = $image_obj->height; - $new_image->ext = $this->determine_ext($target_format); + $new_image->ext = Graphics::determine_ext($target_format); /* Move the new image into the main storage location */ $target = warehouse_path(Image::IMAGE_DIR, $new_image->hash); @@ -346,7 +278,7 @@ class TranscodeImage extends Extension { global $config; - if ($source_format==$this->determine_ext($target_format)) { + if ($source_format==Graphics::determine_ext($target_format)) { throw new ImageTranscodeException("Source and target formats are the same: ".$source_format); } @@ -357,7 +289,7 @@ class TranscodeImage extends Extension if (!$this->can_convert_format($engine, $source_format)) { throw new ImageTranscodeException("Engine $engine does not support input format $source_format"); } - if (!in_array($target_format, self::ENGINE_OUTPUT_SUPPORT[$engine])) { + if (!in_array($target_format, Graphics::ENGINE_OUTPUT_SUPPORT[$engine])) { throw new ImageTranscodeException("Engine $engine does not support output format $target_format"); } @@ -426,20 +358,20 @@ class TranscodeImage extends Extension global $config; $q = $config->get_int("transcode_quality"); - $convert = $config->get_string("thumb_convert_path"); + $convert = $config->get_string(GraphicsConfig::CONVERT_PATH); if ($convert==null||$convert=="") { throw new ImageTranscodeException("ImageMagick path not configured"); } - $ext = $this->determine_ext($target_format); + $ext = Graphics::determine_ext($target_format); $args = " -flatten "; $bg = "none"; switch ($target_format) { - case "webp-lossless": + case Graphics::WEBP_LOSSLESS: $args .= '-define webp:lossless=true'; break; - case "webp-lossy": + case Graphics::WEBP_LOSSY: $args .= ''; break; case "png": From e98e63f836d578c4ea16535864ca5e65f6e1c96a Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Tue, 18 Jun 2019 13:55:18 -0500 Subject: [PATCH 02/19] Adjusted graphic extension settings --- ext/graphics/main.php | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/ext/graphics/main.php b/ext/graphics/main.php index e46d470d..130782c7 100644 --- a/ext/graphics/main.php +++ b/ext/graphics/main.php @@ -19,7 +19,7 @@ abstract class GraphicsConfig const FFPROBE_PATH = "graphics_ffprobe_path"; const CONVERT_PATH = "graphics_convert_path"; const VERSION = "ext_graphics_version"; - const MEM_LIMIT = 'graphics_mem_limit'; + const MEM_LIMIT = 'graphics_mem_limit'; } @@ -167,41 +167,37 @@ class Graphics extends Extension { global $config; $config->set_default_string(GraphicsConfig::FFPROBE_PATH, 'ffprobe'); + $config->set_default_int(GraphicsConfig::MEM_LIMIT, parse_shorthand_int('8MB')); + $config->set_default_string(GraphicsConfig::FFMPEG_PATH, ''); + $config->set_default_string(GraphicsConfig::CONVERT_PATH, ''); if ($config->get_int(GraphicsConfig::VERSION) < 1) { $current_value = $config->get_string("thumb_ffmpeg_path"); - if(!empty($current_value)) { + if (!empty($current_value)) { $config->set_string(GraphicsConfig::FFMPEG_PATH, $current_value); } elseif ($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(GraphicsConfig::FFMPEG_PATH, 'ffmpeg'); } - } else { - $config->set_default_string(GraphicsConfig::FFMPEG_PATH, ''); } $current_value = $config->get_string("thumb_convert_path"); - if(!empty($current_value)) { + if (!empty($current_value)) { $config->set_string(GraphicsConfig::CONVERT_PATH, $current_value); } elseif ($convert = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' convert')) { //ffmpeg exists in PATH, check if it's executable, and if so, default to it instead of static if (is_executable(strtok($convert, PHP_EOL))) { $config->set_default_string(GraphicsConfig::CONVERT_PATH, 'convert'); } - } else { - $config->set_default_string(GraphicsConfig::CONVERT_PATH, ''); } $current_value = $config->get_int("thumb_mem_limit"); - if(!empty($current_value)) { + if (!empty($current_value)) { $config->set_int(GraphicsConfig::MEM_LIMIT, $current_value); } - - - $config->set_int(GraphicsConfig::VERSION, 1); log_info("graphics", "extension installed"); } @@ -220,7 +216,7 @@ class Graphics extends Extension // $sb->add_label("ImageMagick not detected"); // } // } else { - $sb->add_text_option(GraphicsConfig::CONVERT_PATH, "convert command: "); + $sb->add_text_option(GraphicsConfig::CONVERT_PATH, "convert command: "); // } $sb->add_text_option(GraphicsConfig::FFMPEG_PATH, "
ffmpeg command: "); @@ -260,17 +256,17 @@ class Graphics extends Extension case self::IMAGICK_ENGINE: // if (self::imagick_available()) { // } else { - self::image_resize_convert( - $event->input_path, - $event->input_type, - $event->target_width, - $event->target_height, - $event->output_path, - $event->target_format, - $event->ignore_aspect_ratio, - $event->target_quality, - $event->minimize, - $event->allow_upscale); + self::image_resize_convert( + $event->input_path, + $event->input_type, + $event->target_width, + $event->target_height, + $event->output_path, + $event->target_format, + $event->ignore_aspect_ratio, + $event->target_quality, + $event->minimize, + $event->allow_upscale); //} break; default: From 18656db7c88ff8011465158dc0860f4c7ed38f5d Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Fri, 21 Jun 2019 14:57:04 -0500 Subject: [PATCH 03/19] Update main.php Testing a CI issue --- ext/handle_pixel/main.php | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/handle_pixel/main.php b/ext/handle_pixel/main.php index f5c9b3f1..7c83d83b 100644 --- a/ext/handle_pixel/main.php +++ b/ext/handle_pixel/main.php @@ -69,6 +69,7 @@ class PixelFileHandler extends DataHandlerExtension imagestring($thumb, 5, 10, 24, "Image Too Large :(", $black); return true; } catch (Exception $e) { + throw $e; log_error("handle_pixel", "Error while creating thumbnail: ".$e->getMessage()); return false; } From b0e12f6a6c38333bed012f6df7cd50174833690b Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Fri, 21 Jun 2019 15:01:25 -0500 Subject: [PATCH 04/19] Update main.php --- ext/handle_pixel/main.php | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/handle_pixel/main.php b/ext/handle_pixel/main.php index 7c83d83b..f5c9b3f1 100644 --- a/ext/handle_pixel/main.php +++ b/ext/handle_pixel/main.php @@ -69,7 +69,6 @@ class PixelFileHandler extends DataHandlerExtension imagestring($thumb, 5, 10, 24, "Image Too Large :(", $black); return true; } catch (Exception $e) { - throw $e; log_error("handle_pixel", "Error while creating thumbnail: ".$e->getMessage()); return false; } From 3753a1b6d69b9509f2ae03866343ccb5801de871 Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Fri, 21 Jun 2019 15:02:54 -0500 Subject: [PATCH 05/19] Update main.php --- ext/graphics/main.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/graphics/main.php b/ext/graphics/main.php index 130782c7..ab9be965 100644 --- a/ext/graphics/main.php +++ b/ext/graphics/main.php @@ -11,8 +11,6 @@ * This is used by the graphics code when there is an error */ -use FFMpeg\FFMpeg; - abstract class GraphicsConfig { const FFMPEG_PATH = "graphics_ffmpeg_path"; @@ -169,7 +167,7 @@ class Graphics extends Extension $config->set_default_string(GraphicsConfig::FFPROBE_PATH, 'ffprobe'); $config->set_default_int(GraphicsConfig::MEM_LIMIT, parse_shorthand_int('8MB')); $config->set_default_string(GraphicsConfig::FFMPEG_PATH, ''); - $config->set_default_string(GraphicsConfig::CONVERT_PATH, ''); + $config->set_default_string(GraphicsConfig::CONVERT_PATH, 'convert'); if ($config->get_int(GraphicsConfig::VERSION) < 1) { From 7cc725fbc1acba223377b4679bf908bd0e589367 Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Fri, 21 Jun 2019 15:23:59 -0500 Subject: [PATCH 06/19] Moved graphics engine constants to their own class --- ext/graphics/main.php | 140 ++++++++++++++++++++-------------------- ext/handle_ico/main.php | 2 +- ext/handle_svg/main.php | 2 +- ext/image/main.php | 4 +- ext/resize/main.php | 4 +- ext/transcode/main.php | 6 +- 6 files changed, 79 insertions(+), 79 deletions(-) diff --git a/ext/graphics/main.php b/ext/graphics/main.php index ab9be965..4792a498 100644 --- a/ext/graphics/main.php +++ b/ext/graphics/main.php @@ -21,6 +21,65 @@ abstract class GraphicsConfig } +abstract class GraphicsEngine { + public const GD = "gd"; + public const IMAGICK = "convert"; + public const FFMPEG = "ffmpeg"; + + public const ALL = [ + GraphicsEngine::GD, + GraphicsEngine::FFMPEG, + GraphicsEngine::IMAGICK + ]; + public const OUTPUT_SUPPORT = [ + GraphicsEngine::GD => [ + "gif", + "jpg", + "png", + "webp", + self::WEBP_LOSSY, + ], + GraphicsEngine::IMAGICK => [ + "gif", + "jpg", + "png", + "webp", + self::WEBP_LOSSY, + self::WEBP_LOSSLESS, + ], + GraphicsEngine::FFMPEG => [ + + ] + ]; + public const INPUT_SUPPORT = [ + GraphicsEngine::GD => [ + "bmp", + "gif", + "jpg", + "png", + "webp", + ], + GraphicsEngine::IMAGICK => [ + "bmp", + "gif", + "jpg", + "png", + "psd", + "tiff", + "webp", + "ico", + ], + GraphicsEngine::FFMPEG => [ + "avi", + "mkv", + "webm", + "mp4", + "mov", + "flv" + ] + ]; +} + class GraphicsException extends SCoreException { } @@ -47,7 +106,7 @@ class GraphicResizeEvent extends Event bool $minimize = false, bool $allow_upscale = true) { - assert(in_array($engine, Graphics::GRAPHICS_ENGINES)); + assert(in_array($engine, GraphicsEngine::ALL)); $this->engine = $engine; $this->input_path = $input_path; $this->input_type = $input_type; @@ -67,68 +126,9 @@ class Graphics extends Extension const WEBP_LOSSY = "webp-lossy"; const WEBP_LOSSLESS = "webp-lossless"; - const FFMPEG_ENGINE = "ffmpeg"; - const GD_ENGINE = "gd"; - const IMAGICK_ENGINE = "convert"; - - const GRAPHICS_ENGINES = [ - self::GD_ENGINE, - self::FFMPEG_ENGINE, - self::IMAGICK_ENGINE - ]; - const IMAGE_GRAPHICS_ENGINES = [ - "GD" => self::GD_ENGINE, - "ImageMagick" => self::IMAGICK_ENGINE, - ]; - - const ENGINE_INPUT_SUPPORT = [ - self::GD_ENGINE => [ - "bmp", - "gif", - "jpg", - "png", - "webp", - ], - self::IMAGICK_ENGINE => [ - "bmp", - "gif", - "jpg", - "png", - "psd", - "tiff", - "webp", - "ico", - ], - self::FFMPEG_ENGINE => [ - "avi", - "mkv", - "webm", - "mp4", - "mov", - "flv" - ] - ]; - - const ENGINE_OUTPUT_SUPPORT = [ - self::GD_ENGINE => [ - "gif", - "jpg", - "png", - "webp", - self::WEBP_LOSSY, - ], - self::IMAGICK_ENGINE => [ - "gif", - "jpg", - "png", - "webp", - self::WEBP_LOSSY, - self::WEBP_LOSSLESS, - ], - self::FFMPEG_ENGINE => [ - - ] + "GD" => GraphicsEngine::GD, + "ImageMagick" => GraphicsEngine::IMAGICK, ]; const LOSSLESS_FORMATS = [ @@ -203,7 +203,7 @@ class Graphics extends Extension public function onSetupBuilding(SetupBuildingEvent $event) { - $sb = new SetupBlock("Graphics"); + $sb = new SetupBlock("Graphic Engines"); // if (self::imagick_available()) { // try { @@ -233,7 +233,7 @@ class Graphics extends Extension public function onGraphicResize(GraphicResizeEvent $event) { switch ($event->engine) { - case self::GD_ENGINE: + case GraphicsEngine::GD: $info = getimagesize($event->input_path); if ($info === false) { throw new GraphicsException("getimagesize failed for " . $event->input_path); @@ -251,7 +251,7 @@ class Graphics extends Extension $event->allow_upscale); break; - case self::IMAGICK_ENGINE: + case GraphicsEngine::IMAGICK: // if (self::imagick_available()) { // } else { self::image_resize_convert( @@ -292,7 +292,7 @@ class Graphics extends Extension * @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. */ - static function calc_memory_use(array $info): int + public static 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; @@ -312,7 +312,7 @@ class Graphics extends Extension * @return bool true if successful, false if not. * @throws GraphicsException */ - static function create_thumbnail_ffmpeg($hash): bool + public static function create_thumbnail_ffmpeg($hash): bool { global $config; @@ -500,7 +500,7 @@ class Graphics extends Extension $convert = $config->get_string(GraphicsConfig::CONVERT_PATH); - if ($convert == null || $convert == "") { + if (empty($convert)) { throw new GraphicsException("convert command not configured"); } @@ -735,7 +735,7 @@ class Graphics extends Extension public static function is_input_supported($engine, $format): bool { $format = self::normalize_format($format); - if (!in_array($format, Graphics::ENGINE_INPUT_SUPPORT[$engine])) { + if (!in_array($format, GraphicsEngine::INPUT_SUPPORT[$engine])) { return false; } return true; @@ -744,7 +744,7 @@ class Graphics extends Extension public static function is_output_supported($engine, $format): bool { $format = self::normalize_format($format); - if (!in_array($format, Graphics::ENGINE_OUTPUT_SUPPORT[$engine])) { + if (!in_array($format, GraphicsEngine::OUTPUT_SUPPORT[$engine])) { return false; } return true; diff --git a/ext/handle_ico/main.php b/ext/handle_ico/main.php index 1e1f7305..5f6d2745 100644 --- a/ext/handle_ico/main.php +++ b/ext/handle_ico/main.php @@ -57,7 +57,7 @@ class IcoFileHandler extends DataHandlerExtension protected function create_thumb(string $hash, string $type): bool { try { - create_image_thumb($hash, $type, Graphics::IMAGICK_ENGINE); + create_image_thumb($hash, $type, GraphicsEngine::IMAGICK); return true; } catch (GraphicsException $e) { log_warning("handle_ico", "Could not generate thumbnail. " . $e->getMessage()); diff --git a/ext/handle_svg/main.php b/ext/handle_svg/main.php index da4c9ff4..b73f07de 100644 --- a/ext/handle_svg/main.php +++ b/ext/handle_svg/main.php @@ -36,7 +36,7 @@ class SVGFileHandler extends DataHandlerExtension protected function create_thumb(string $hash, string $type): bool { try { - create_image_thumb($hash, $type, Graphics::IMAGICK_ENGINE); + create_image_thumb($hash, $type, GraphicsEngine::IMAGICK); return true; } catch (GraphicsException $e) { log_warning("handle_svg", "Could not generate thumbnail. " . $e->getMessage()); diff --git a/ext/image/main.php b/ext/image/main.php index dc53ee7c..fbcac36f 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -41,8 +41,8 @@ class ImageIO extends Extension const THUMBNAIL_ENGINES = [ - 'Built-in GD' => Graphics::GD_ENGINE, - 'ImageMagick' => Graphics::IMAGICK_ENGINE + 'Built-in GD' => GraphicsEngine::GD, + 'ImageMagick' => GraphicsEngine::IMAGICK ]; const THUMBNAIL_TYPES = [ diff --git a/ext/resize/main.php b/ext/resize/main.php index 8cbb7dd6..b41f11e5 100644 --- a/ext/resize/main.php +++ b/ext/resize/main.php @@ -40,7 +40,7 @@ class ResizeImage extends Extension global $config; $config->set_default_bool(ResizeConfig::ENABLED, true); $config->set_default_bool(ResizeConfig::UPLOAD, false); - $config->set_default_string(ResizeConfig::ENGINE, Graphics::GD_ENGINE); + $config->set_default_string(ResizeConfig::ENGINE, GraphicsEngine::GD); $config->set_default_int(ResizeConfig::DEFAULT_WIDTH, 0); $config->set_default_int(ResizeConfig::DEFAULT_HEIGHT, 0); } @@ -206,7 +206,7 @@ class ResizeImage extends Extension } send_event(new GraphicResizeEvent( - Graphics::GD_ENGINE, + GraphicsEngine::GD, $image_filename, $image_obj->ext, $tmp_filename, diff --git a/ext/transcode/main.php b/ext/transcode/main.php index d5562e8c..650af1bb 100644 --- a/ext/transcode/main.php +++ b/ext/transcode/main.php @@ -66,7 +66,7 @@ class TranscodeImage extends Extension global $config; $config->set_default_bool(TranscodeConfig::ENABLED, true); $config->set_default_bool(TranscodeConfig::UPLOAD, false); - $config->set_default_string(TranscodeConfig::ENGINE, Graphics::GD_ENGINE); + $config->set_default_string(TranscodeConfig::ENGINE, GraphicsEngine::GD); $config->set_default_int(TranscodeConfig::QUALITY, 80); foreach (array_values(self::INPUT_FORMATS) as $format) { @@ -100,7 +100,7 @@ class TranscodeImage extends Extension $sb->add_bool_option(TranscodeConfig::UPLOAD, "Transcode on upload: ", true); $sb->add_choice_option(TranscodeConfig::ENGINE, Graphics::IMAGE_GRAPHICS_ENGINES, "Engine", true); foreach (self::INPUT_FORMATS as $display=>$format) { - if (in_array($format, Graphics::ENGINE_INPUT_SUPPORT[$engine])) { + if (in_array($format, GraphicsEngine::INPUT_SUPPORT[$engine])) { $outputs = $this->get_supported_output_formats($engine, $format); $sb->add_choice_option(TranscodeConfig::UPLOAD_PREFIX.$format, $outputs, "$display", true); } @@ -289,7 +289,7 @@ class TranscodeImage extends Extension if (!$this->can_convert_format($engine, $source_format)) { throw new ImageTranscodeException("Engine $engine does not support input format $source_format"); } - if (!in_array($target_format, Graphics::ENGINE_OUTPUT_SUPPORT[$engine])) { + if (!in_array($target_format, GraphicsEngine::OUTPUT_SUPPORT[$engine])) { throw new ImageTranscodeException("Engine $engine does not support output format $target_format"); } From 0c16d3e78c65aa7126c6f74e541fd361c9744589 Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Fri, 21 Jun 2019 15:32:23 -0500 Subject: [PATCH 07/19] Fixed some extension references --- ext/graphics/main.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/graphics/main.php b/ext/graphics/main.php index 4792a498..98cfd42e 100644 --- a/ext/graphics/main.php +++ b/ext/graphics/main.php @@ -37,15 +37,15 @@ abstract class GraphicsEngine { "jpg", "png", "webp", - self::WEBP_LOSSY, + Graphics::WEBP_LOSSY, ], GraphicsEngine::IMAGICK => [ "gif", "jpg", "png", "webp", - self::WEBP_LOSSY, - self::WEBP_LOSSLESS, + Graphics::WEBP_LOSSY, + Graphics::WEBP_LOSSLESS, ], GraphicsEngine::FFMPEG => [ From a41e99d1af182c2e2d3a97b6ad71b9dd7a0d9d00 Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Mon, 24 Jun 2019 10:05:16 -0500 Subject: [PATCH 08/19] Renamed graphics extension to media extension --- core/imageboard/misc.php | 3 +- core/sys_config.php | 2 +- core/util.php | 2 +- ext/et/main.php | 6 +- ext/et/theme.php | 4 +- ext/graphics/theme.php | 6 - ext/handle_flash/main.php | 2 +- ext/handle_ico/main.php | 4 +- ext/handle_svg/main.php | 4 +- ext/handle_video/main.php | 4 +- ext/image/main.php | 4 +- ext/{graphics => media}/main.php | 265 +++++++++++++++++++++---------- ext/media/theme.php | 6 + ext/resize/main.php | 10 +- ext/rotate/main.php | 2 +- ext/transcode/main.php | 40 ++--- themes/lite/view.theme.php | 2 + 17 files changed, 229 insertions(+), 137 deletions(-) delete mode 100644 ext/graphics/theme.php rename ext/{graphics => media}/main.php (74%) create mode 100644 ext/media/theme.php diff --git a/core/imageboard/misc.php b/core/imageboard/misc.php index 2a349c80..5af65d6f 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. @@ -179,7 +178,7 @@ function create_image_thumb(string $hash, string $type, string $engine = null) { $engine = $config->get_string(ImageConfig::THUMB_ENGINE); } - send_event(new GraphicResizeEvent( + send_event(new MediaResizeEvent( $engine, $inname, $type, diff --git a/core/sys_config.php b/core/sys_config.php index a489a96f..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,graphics"); // 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 24bda493..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(GraphicsConfig::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 50a639e1..e3e9b9c7 100644 --- a/ext/et/main.php +++ b/ext/et/main.php @@ -53,9 +53,9 @@ class ET extends Extension to_shorthand_int(disk_total_space("./")); $info['sys_server'] = isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown'; - $info[GraphicsConfig::FFMPEG_PATH] = $config->get_string(GraphicsConfig::FFMPEG_PATH); - $info[GraphicsConfig::CONVERT_PATH] = $config->get_string(GraphicsConfig::CONVERT_PATH); - $info[GraphicsConfig::MEM_LIMIT] = $config->get_int(GraphicsConfig::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); diff --git a/ext/et/theme.php b/ext/et/theme.php index a0778dff..0582fddf 100644 --- a/ext/et/theme.php +++ b/ext/et/theme.php @@ -35,8 +35,8 @@ Database: {$info['sys_db']} Server: {$info['sys_server']} Disk use: {$info['sys_disk']} -Graphics System: -Memory Limit: {$info[GraphicsConfig::MEM_LIMIT]} +Media System: +Memory Limit: {$info[MediaConfig::MEM_LIMIT]} Thumbnail Generation: Engine: {$info[ImageConfig::THUMB_ENGINE]} diff --git a/ext/graphics/theme.php b/ext/graphics/theme.php deleted file mode 100644 index 6210644e..00000000 --- a/ext/graphics/theme.php +++ /dev/null @@ -1,6 +0,0 @@ -getMessage()); return false; } diff --git a/ext/handle_svg/main.php b/ext/handle_svg/main.php index b73f07de..4df1154b 100644 --- a/ext/handle_svg/main.php +++ b/ext/handle_svg/main.php @@ -36,9 +36,9 @@ class SVGFileHandler extends DataHandlerExtension protected function create_thumb(string $hash, string $type): bool { try { - create_image_thumb($hash, $type, GraphicsEngine::IMAGICK); + create_image_thumb($hash, $type, MediaEngine::IMAGICK); return true; - } catch (GraphicsException $e) { + } 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; diff --git a/ext/handle_video/main.php b/ext/handle_video/main.php index 15f192cc..666def30 100644 --- a/ext/handle_video/main.php +++ b/ext/handle_video/main.php @@ -53,7 +53,7 @@ class VideoFileHandler extends DataHandlerExtension */ protected function create_thumb(string $hash, string $type): bool { - return Graphics::create_thumbnail_ffmpeg($hash); + return Media::create_thumbnail_ffmpeg($hash); } protected function supported_ext(string $ext): bool @@ -65,7 +65,7 @@ class VideoFileHandler extends DataHandlerExtension { $image = new Image(); - $size = Graphics::video_size($filename); + $size = Media::video_size($filename); $image->width = $size[0]; $image->height = $size[1]; diff --git a/ext/image/main.php b/ext/image/main.php index fbcac36f..c851a90d 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -41,8 +41,8 @@ class ImageIO extends Extension const THUMBNAIL_ENGINES = [ - 'Built-in GD' => GraphicsEngine::GD, - 'ImageMagick' => GraphicsEngine::IMAGICK + 'Built-in GD' => MediaEngine::GD, + 'ImageMagick' => MediaEngine::IMAGICK ]; const THUMBNAIL_TYPES = [ diff --git a/ext/graphics/main.php b/ext/media/main.php similarity index 74% rename from ext/graphics/main.php rename to ext/media/main.php index 98cfd42e..610959a9 100644 --- a/ext/graphics/main.php +++ b/ext/media/main.php @@ -1,65 +1,66 @@ - * Description: Provides common functions and settings used for graphic operations. + * Description: Provides common functions and settings used for media operations. * License: MIT * Version: 1.0 */ /* -* This is used by the graphics code when there is an error +* This is used by the media code when there is an error */ -abstract class GraphicsConfig +abstract class MediaConfig { - const FFMPEG_PATH = "graphics_ffmpeg_path"; - const FFPROBE_PATH = "graphics_ffprobe_path"; - const CONVERT_PATH = "graphics_convert_path"; - const VERSION = "ext_graphics_version"; - const MEM_LIMIT = 'graphics_mem_limit'; + const FFMPEG_PATH = "media_ffmpeg_path"; + const FFPROBE_PATH = "media_ffprobe_path"; + const CONVERT_PATH = "media_convert_path"; + const VERSION = "ext_media_version"; + const MEM_LIMIT = 'media_mem_limit'; } -abstract class GraphicsEngine { +abstract class MediaEngine +{ public const GD = "gd"; public const IMAGICK = "convert"; public const FFMPEG = "ffmpeg"; public const ALL = [ - GraphicsEngine::GD, - GraphicsEngine::FFMPEG, - GraphicsEngine::IMAGICK + MediaEngine::GD, + MediaEngine::FFMPEG, + MediaEngine::IMAGICK ]; public const OUTPUT_SUPPORT = [ - GraphicsEngine::GD => [ + MediaEngine::GD => [ "gif", "jpg", "png", "webp", - Graphics::WEBP_LOSSY, + Media::WEBP_LOSSY, ], - GraphicsEngine::IMAGICK => [ + MediaEngine::IMAGICK => [ "gif", "jpg", "png", "webp", - Graphics::WEBP_LOSSY, - Graphics::WEBP_LOSSLESS, + Media::WEBP_LOSSY, + Media::WEBP_LOSSLESS, ], - GraphicsEngine::FFMPEG => [ + MediaEngine::FFMPEG => [ ] ]; public const INPUT_SUPPORT = [ - GraphicsEngine::GD => [ + MediaEngine::GD => [ "bmp", "gif", "jpg", "png", "webp", ], - GraphicsEngine::IMAGICK => [ + MediaEngine::IMAGICK => [ "bmp", "gif", "jpg", @@ -69,7 +70,7 @@ abstract class GraphicsEngine { "webp", "ico", ], - GraphicsEngine::FFMPEG => [ + MediaEngine::FFMPEG => [ "avi", "mkv", "webm", @@ -80,11 +81,11 @@ abstract class GraphicsEngine { ]; } -class GraphicsException extends SCoreException +class MediaException extends SCoreException { } -class GraphicResizeEvent extends Event +class MediaResizeEvent extends Event { public $engine; public $input_path; @@ -106,7 +107,7 @@ class GraphicResizeEvent extends Event bool $minimize = false, bool $allow_upscale = true) { - assert(in_array($engine, GraphicsEngine::ALL)); + assert(in_array($engine, MediaEngine::ALL)); $this->engine = $engine; $this->input_path = $input_path; $this->input_type = $input_type; @@ -121,14 +122,28 @@ class GraphicResizeEvent extends Event } } -class Graphics extends Extension +class MediaCheckLosslessEvent extends Event +{ + public $file_name; + public $ext; + public $result = false; + + public function __construct(string $file_name, string $ext) + { + $this->file_name = $file_name; + $this->ext = $ext; + } + +} + +class Media extends Extension { const WEBP_LOSSY = "webp-lossy"; const WEBP_LOSSLESS = "webp-lossless"; - const IMAGE_GRAPHICS_ENGINES = [ - "GD" => GraphicsEngine::GD, - "ImageMagick" => GraphicsEngine::IMAGICK, + const IMAGE_MEDIA_ENGINES = [ + "GD" => MediaEngine::GD, + "ImageMagick" => MediaEngine::IMAGICK, ]; const LOSSLESS_FORMATS = [ @@ -148,6 +163,16 @@ class Graphics extends Extension ]; + //RIFF####WEBPVP8?..............ANIM + private const WEBP_ANIMATION_HEADER = + [0x52, 0x49, 0x46, 0x46, null, null, null, null, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0x41, 0x4E, 0x49, 0x4D]; + + //RIFF####WEBPVP8L + private const WEBP_LOSSLESS_HEADER = + [0x52, 0x49, 0x46, 0x46, null, null, null, null, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, 0x4C]; + + static function imagick_available(): bool { return extension_loaded("imagick"); @@ -164,46 +189,46 @@ class Graphics extends Extension public function onInitExt(InitExtEvent $event) { global $config; - $config->set_default_string(GraphicsConfig::FFPROBE_PATH, 'ffprobe'); - $config->set_default_int(GraphicsConfig::MEM_LIMIT, parse_shorthand_int('8MB')); - $config->set_default_string(GraphicsConfig::FFMPEG_PATH, ''); - $config->set_default_string(GraphicsConfig::CONVERT_PATH, 'convert'); + $config->set_default_string(MediaConfig::FFPROBE_PATH, 'ffprobe'); + $config->set_default_int(MediaConfig::MEM_LIMIT, parse_shorthand_int('8MB')); + $config->set_default_string(MediaConfig::FFMPEG_PATH, ''); + $config->set_default_string(MediaConfig::CONVERT_PATH, 'convert'); - if ($config->get_int(GraphicsConfig::VERSION) < 1) { + if ($config->get_int(MediaConfig::VERSION) < 1) { $current_value = $config->get_string("thumb_ffmpeg_path"); if (!empty($current_value)) { - $config->set_string(GraphicsConfig::FFMPEG_PATH, $current_value); + $config->set_string(MediaConfig::FFMPEG_PATH, $current_value); } elseif ($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(GraphicsConfig::FFMPEG_PATH, 'ffmpeg'); + $config->set_default_string(MediaConfig::FFMPEG_PATH, 'ffmpeg'); } } $current_value = $config->get_string("thumb_convert_path"); if (!empty($current_value)) { - $config->set_string(GraphicsConfig::CONVERT_PATH, $current_value); + $config->set_string(MediaConfig::CONVERT_PATH, $current_value); } elseif ($convert = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' convert')) { //ffmpeg exists in PATH, check if it's executable, and if so, default to it instead of static if (is_executable(strtok($convert, PHP_EOL))) { - $config->set_default_string(GraphicsConfig::CONVERT_PATH, 'convert'); + $config->set_default_string(MediaConfig::CONVERT_PATH, 'convert'); } } $current_value = $config->get_int("thumb_mem_limit"); if (!empty($current_value)) { - $config->set_int(GraphicsConfig::MEM_LIMIT, $current_value); + $config->set_int(MediaConfig::MEM_LIMIT, $current_value); } - $config->set_int(GraphicsConfig::VERSION, 1); - log_info("graphics", "extension installed"); + $config->set_int(MediaConfig::VERSION, 1); + log_info("media", "extension installed"); } } public function onSetupBuilding(SetupBuildingEvent $event) { - $sb = new SetupBlock("Graphic Engines"); + $sb = new SetupBlock("Media Engines"); // if (self::imagick_available()) { // try { @@ -214,29 +239,29 @@ class Graphics extends Extension // $sb->add_label("ImageMagick not detected"); // } // } else { - $sb->add_text_option(GraphicsConfig::CONVERT_PATH, "convert command: "); + $sb->add_text_option(MediaConfig::CONVERT_PATH, "convert command: "); // } - $sb->add_text_option(GraphicsConfig::FFMPEG_PATH, "
ffmpeg command: "); + $sb->add_text_option(MediaConfig::FFMPEG_PATH, "
ffmpeg command: "); - $sb->add_shorthand_int_option(GraphicsConfig::MEM_LIMIT, "
Max memory use: "); + $sb->add_shorthand_int_option(MediaConfig::MEM_LIMIT, "
Max memory use: "); $event->panel->add_block($sb); } /** - * @param GraphicResizeEvent $event - * @throws GraphicsException + * @param MediaResizeEvent $event + * @throws MediaException * @throws InsufficientMemoryException */ - public function onGraphicResize(GraphicResizeEvent $event) + public function onMediaResize(MediaResizeEvent $event) { switch ($event->engine) { - case GraphicsEngine::GD: + case MediaEngine::GD: $info = getimagesize($event->input_path); if ($info === false) { - throw new GraphicsException("getimagesize failed for " . $event->input_path); + throw new MediaException("getimagesize failed for " . $event->input_path); } self::image_resize_gd( @@ -251,7 +276,7 @@ class Graphics extends Extension $event->allow_upscale); break; - case GraphicsEngine::IMAGICK: + case MediaEngine::IMAGICK: // if (self::imagick_available()) { // } else { self::image_resize_convert( @@ -268,7 +293,7 @@ class Graphics extends Extension //} break; default: - throw new GraphicsException("Engine not supported for resize: " . $event->engine); + throw new MediaException("Engine not supported for resize: " . $event->engine); } // TODO: Get output optimization tools working better @@ -277,6 +302,22 @@ class Graphics extends Extension // } } + public function onMediaCheckLossless(MediaCheckLosslessEvent $event) + { + switch ($event->ext) { + case "png": + case "psd": + case "bmp": + case "gif": + case "ico": + $event->result = true; + break; + case "webp": + $event->result = Media::is_lossless_webp($event->file_name); + break; + } + } + /** * Check Memory usage limits * @@ -310,15 +351,15 @@ class Graphics extends Extension * * @param $hash * @return bool true if successful, false if not. - * @throws GraphicsException + * @throws MediaException */ public static function create_thumbnail_ffmpeg($hash): bool { global $config; - $ffmpeg = $config->get_string(GraphicsConfig::FFMPEG_PATH); + $ffmpeg = $config->get_string(MediaConfig::FFMPEG_PATH); if ($ffmpeg == null || $ffmpeg == "") { - throw new GraphicsException("ffmpeg command configured"); + throw new MediaException("ffmpeg command configured"); } $inname = warehouse_path(Image::IMAGE_DIR, $hash); @@ -355,10 +396,10 @@ class Graphics extends Extension exec($cmd, $output, $ret); if ((int)$ret == (int)0) { - log_debug('graphics', "Generating thumbnail with command `$cmd`, returns $ret"); + log_debug('Media', "Generating thumbnail with command `$cmd`, returns $ret"); return true; } else { - log_error('graphics', "Generating thumbnail with command `$cmd`, returns $ret"); + log_error('Media', "Generating thumbnail with command `$cmd`, returns $ret"); return false; } } @@ -498,10 +539,10 @@ class Graphics extends Extension { global $config; - $convert = $config->get_string(GraphicsConfig::CONVERT_PATH); + $convert = $config->get_string(MediaConfig::CONVERT_PATH); if (empty($convert)) { - throw new GraphicsException("convert command not configured"); + throw new MediaException("convert command not configured"); } if (empty($output_type)) { @@ -533,9 +574,9 @@ class Graphics extends Extension $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) { - throw new GraphicsException("Resizing image with command `$cmd`, returns $ret, outputting " . implode("\r\n", $output)); + throw new MediaException("Resizing image with command `$cmd`, returns $ret, outputting " . implode("\r\n", $output)); } else { - log_debug('graphics', "Generating thumbnail with command `$cmd`, returns $ret"); + log_debug('Media', "Generating thumbnail with command `$cmd`, returns $ret"); } } @@ -549,7 +590,7 @@ class Graphics extends Extension * @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 GraphicsException + * @throws MediaException * @throws InsufficientMemoryException if the estimated memory usage exceeds the memory limit. */ public static function image_resize_gd( @@ -586,7 +627,7 @@ class Graphics extends Extension $output_type = "bmp"; break; default: - throw new GraphicsException("Failed to save the new image - Unsupported image type."); + throw new MediaException("Failed to save the new image - Unsupported image type."); } } @@ -609,10 +650,10 @@ class Graphics extends Extension $image_resized = imagecreatetruecolor($new_width, $new_height); try { if ($image === false) { - throw new GraphicsException("Could not load image: " . $image_filename); + throw new MediaException("Could not load image: " . $image_filename); } if ($image_resized === false) { - throw new GraphicsException("Could not create output image with dimensions $new_width c $new_height "); + throw new MediaException("Could not create output image with dimensions $new_width c $new_height "); } // Handle transparent images @@ -629,12 +670,12 @@ class Graphics extends Extension // 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 GraphicsException("Unable to allocate transparent color"); + throw new MediaException("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 GraphicsException("Unable to fill new image with transparent color"); + throw new MediaException("Unable to fill new image with transparent color"); } // Set the background color for new image to transparent @@ -647,17 +688,17 @@ class Graphics extends Extension // 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 GraphicsException("Unable to disable image alpha blending"); + throw new MediaException("Unable to disable image alpha blending"); } if (imagesavealpha($image_resized, true) === false) { - throw new GraphicsException("Unable to enable image save alpha"); + throw new MediaException("Unable to enable image save alpha"); } $transparent_color = imagecolorallocatealpha($image_resized, 255, 255, 255, 127); if ($transparent_color === false) { - throw new GraphicsException("Unable to allocate transparent color"); + throw new MediaException("Unable to allocate transparent color"); } if (imagefilledrectangle($image_resized, 0, 0, $new_width, $new_height, $transparent_color) === false) { - throw new GraphicsException("Unable to fill new image with transparent color"); + throw new MediaException("Unable to fill new image with transparent color"); } break; } @@ -675,7 +716,7 @@ class Graphics extends Extension $width, $height ) === false) { - throw new GraphicsException("Unable to copy resized image data to new image"); + throw new MediaException("Unable to copy resized image data to new image"); } switch ($output_type) { @@ -683,7 +724,7 @@ class Graphics extends Extension $result = imagebmp($image_resized, $output_filename, true); break; case "webp": - case Graphics::WEBP_LOSSY: + case Media::WEBP_LOSSY: $result = imagewebp($image_resized, $output_filename, $output_quality); break; case "jpg": @@ -697,10 +738,10 @@ class Graphics extends Extension $result = imagegif($image_resized, $output_filename); break; default: - throw new GraphicsException("Failed to save the new image - Unsupported image type: $output_type"); + throw new MediaException("Failed to save the new image - Unsupported image type: $output_type"); } if ($result === false) { - throw new GraphicsException("Failed to save the new image, function returned false when saving type: $output_type"); + throw new MediaException("Failed to save the new image, function returned false when saving type: $output_type"); } } finally { @imagedestroy($image); @@ -714,19 +755,69 @@ class Graphics extends Extension * @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. */ - public static function is_animated_gif(String $image_filename) + public static function is_animated_gif(String $image_filename): bool { $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); + try { + //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); + } + } finally { + @fclose($fh); } } return ($is_anim_gif == 0); } + + + + private static function compare_file_bytes(String $file_name, array $comparison): bool + { + $size= filesize($file_name); + if ($size < count($comparison)) { + // Can't match because it's too small + return false; + } + + if (($fh = @fopen($file_name, 'rb'))) { + try { + $chunk = unpack("C*",fread($fh, count($comparison))); + + for ($i = 0; $i < count($comparison); $i++) { + $byte = $comparison[$i]; + if($byte==null) { + continue; + } else { + $fileByte = $chunk[$i+1]; + if($fileByte!=$byte) { + return false; + } + } + } + return true; + } finally { + @fclose($fh); + } + } else { + throw new MediaException("Unable to open file for byte check: $file_name"); + } + + } + + public static function is_animated_webp(String $image_filename): bool + { + return self::compare_file_bytes($image_filename, self::WEBP_ANIMATION_HEADER); + } + + public static function is_lossless_webp(String $image_filename): bool + { + return self::compare_file_bytes($image_filename, self::WEBP_LOSSLESS_HEADER); + } + public static function supports_alpha(string $format) { return in_array(self::normalize_format($format), self::ALPHA_FORMATS); @@ -735,7 +826,7 @@ class Graphics extends Extension public static function is_input_supported($engine, $format): bool { $format = self::normalize_format($format); - if (!in_array($format, GraphicsEngine::INPUT_SUPPORT[$engine])) { + if (!in_array($format, MediaEngine::INPUT_SUPPORT[$engine])) { return false; } return true; @@ -744,7 +835,7 @@ class Graphics extends Extension public static function is_output_supported($engine, $format): bool { $format = self::normalize_format($format); - if (!in_array($format, GraphicsEngine::OUTPUT_SUPPORT[$engine])) { + if (!in_array($format, MediaEngine::OUTPUT_SUPPORT[$engine])) { return false; } return true; @@ -752,15 +843,15 @@ class Graphics extends Extension /** * Checks if a format (normally a file extension) is a variant name of another format (ie, jpg and jpeg). - * If one is found, then the maine name that the Graphics extension will recognize is returned, + * If one is found, then the maine name that the Media extension will recognize is returned, * otherwise the incoming format is returned. * * @param $format - * @return string|null The format name that the graphics extension will recognize. + * @return string|null The format name that the media extension will recognize. */ static public function normalize_format($format): ?string { - if (array_key_exists($format, Graphics::FORMAT_ALIASES)) { + if (array_key_exists($format, Media::FORMAT_ALIASES)) { return self::FORMAT_ALIASES[$format]; } return $format; @@ -776,7 +867,7 @@ class Graphics extends Extension static public function video_size(string $filename): array { global $config; - $ffmpeg = $config->get_string(GraphicsConfig::FFMPEG_PATH); + $ffmpeg = $config->get_string(MediaConfig::FFMPEG_PATH); $cmd = escapeshellcmd(implode(" ", [ escapeshellarg($ffmpeg), "-y", "-i", escapeshellarg($filename), @@ -795,7 +886,7 @@ class Graphics extends Extension } else { $size = [1, 1]; } - log_debug('graphics', "Getting video size with `$cmd`, returns $output -- $size[0], $size[1]"); + log_debug('Media', "Getting video size with `$cmd`, returns $output -- $size[0], $size[1]"); return $size; } diff --git a/ext/media/theme.php b/ext/media/theme.php new file mode 100644 index 00000000..e213a0e9 --- /dev/null +++ b/ext/media/theme.php @@ -0,0 +1,6 @@ +set_default_bool(ResizeConfig::ENABLED, true); $config->set_default_bool(ResizeConfig::UPLOAD, false); - $config->set_default_string(ResizeConfig::ENGINE, GraphicsEngine::GD); + $config->set_default_string(ResizeConfig::ENGINE, MediaEngine::GD); $config->set_default_int(ResizeConfig::DEFAULT_WIDTH, 0); $config->set_default_int(ResizeConfig::DEFAULT_HEIGHT, 0); } @@ -174,8 +174,8 @@ class ResizeImage extends Extension { global $config; $engine = $config->get_string(ResizeConfig::ENGINE); - return Graphics::is_input_supported($engine, $format) - && Graphics::is_output_supported($engine, $format); + return Media::is_input_supported($engine, $format) + && Media::is_output_supported($engine, $format); } @@ -205,8 +205,8 @@ class ResizeImage extends Extension throw new ImageResizeException("Unable to save temporary image file."); } - send_event(new GraphicResizeEvent( - GraphicsEngine::GD, + send_event(new MediaResizeEvent( + MediaEngine::GD, $image_filename, $image_obj->ext, $tmp_filename, diff --git a/ext/rotate/main.php b/ext/rotate/main.php index bf539ba8..4936ca59 100644 --- a/ext/rotate/main.php +++ b/ext/rotate/main.php @@ -130,7 +130,7 @@ class RotateImage extends Extension $info = getimagesize($image_filename); - $memory_use = Graphics::calc_memory_use($info); + $memory_use = Media::calc_memory_use($info); $memory_limit = get_memory_limit(); if ($memory_use > $memory_limit) { diff --git a/ext/transcode/main.php b/ext/transcode/main.php index 650af1bb..41dfa07e 100644 --- a/ext/transcode/main.php +++ b/ext/transcode/main.php @@ -48,8 +48,8 @@ class TranscodeImage extends Extension "" => "", "JPEG (lossy)" => "jpg", "PNG (lossless)" => "png", - "WEBP (lossy)" => Graphics::WEBP_LOSSY, - "WEBP (lossless)" => Graphics::WEBP_LOSSLESS, + "WEBP (lossy)" => Media::WEBP_LOSSY, + "WEBP (lossless)" => Media::WEBP_LOSSLESS, ]; /** @@ -66,7 +66,7 @@ class TranscodeImage extends Extension global $config; $config->set_default_bool(TranscodeConfig::ENABLED, true); $config->set_default_bool(TranscodeConfig::UPLOAD, false); - $config->set_default_string(TranscodeConfig::ENGINE, GraphicsEngine::GD); + $config->set_default_string(TranscodeConfig::ENGINE, MediaEngine::GD); $config->set_default_int(TranscodeConfig::QUALITY, 80); foreach (array_values(self::INPUT_FORMATS) as $format) { @@ -98,9 +98,9 @@ class TranscodeImage extends Extension $sb->start_table(); $sb->add_bool_option(TranscodeConfig::ENABLED, "Allow transcoding images: ", true); $sb->add_bool_option(TranscodeConfig::UPLOAD, "Transcode on upload: ", true); - $sb->add_choice_option(TranscodeConfig::ENGINE, Graphics::IMAGE_GRAPHICS_ENGINES, "Engine", true); + $sb->add_choice_option(TranscodeConfig::ENGINE, Media::IMAGE__MEDIA_ENGINES, "Engine", true); foreach (self::INPUT_FORMATS as $display=>$format) { - if (in_array($format, GraphicsEngine::INPUT_SUPPORT[$engine])) { + if (in_array($format, MediaEngine::INPUT_SUPPORT[$engine])) { $outputs = $this->get_supported_output_formats($engine, $format); $sb->add_choice_option(TranscodeConfig::UPLOAD_PREFIX.$format, $outputs, "$display", true); } @@ -117,9 +117,9 @@ class TranscodeImage extends Extension if ($config->get_bool(TranscodeConfig::UPLOAD) == true) { $ext = strtolower($event->type); - $ext = Graphics::normalize_format($ext); + $ext = Media::normalize_format($ext); - if ($event->type=="gif"&&Graphics::is_animated_gif($event->tmpname)) { + if ($event->type=="gif"&&Media::is_animated_gif($event->tmpname)) { return; } @@ -130,7 +130,7 @@ class TranscodeImage extends Extension } try { $new_image = $this->transcode_image($event->tmpname, $ext, $target_format); - $event->set_type(Graphics::determine_ext($target_format)); + $event->set_type(Media::determine_ext($target_format)); $event->set_tmpname($new_image); } catch (Exception $e) { log_error("transcode", "Error while performing upload transcode: ".$e->getMessage()); @@ -224,21 +224,21 @@ class TranscodeImage extends Extension private function can_convert_format($engine, $format): bool { - return Graphics::is_input_supported($engine, $format); + return Media::is_input_supported($engine, $format); } private function get_supported_output_formats($engine, ?String $omit_format = null): array { - $omit_format = Graphics::normalize_format($omit_format); + $omit_format = Media::normalize_format($omit_format); $output = []; foreach (self::OUTPUT_FORMATS as $key=>$value) { if ($value=="") { $output[$key] = $value; continue; } - if(Graphics::is_output_supported($engine, $value) - &&(empty($omit_format)||$omit_format!=Graphics::determine_ext($value))) { + if(Media::is_output_supported($engine, $value) + &&(empty($omit_format)||$omit_format!=Media::determine_ext($value))) { $output[$key] = $value; } } @@ -259,7 +259,7 @@ class TranscodeImage extends Extension $new_image->filename = $image_obj->filename; $new_image->width = $image_obj->width; $new_image->height = $image_obj->height; - $new_image->ext = Graphics::determine_ext($target_format); + $new_image->ext = Media::determine_ext($target_format); /* Move the new image into the main storage location */ $target = warehouse_path(Image::IMAGE_DIR, $new_image->hash); @@ -278,7 +278,7 @@ class TranscodeImage extends Extension { global $config; - if ($source_format==Graphics::determine_ext($target_format)) { + if ($source_format==Media::determine_ext($target_format)) { throw new ImageTranscodeException("Source and target formats are the same: ".$source_format); } @@ -289,7 +289,7 @@ class TranscodeImage extends Extension if (!$this->can_convert_format($engine, $source_format)) { throw new ImageTranscodeException("Engine $engine does not support input format $source_format"); } - if (!in_array($target_format, GraphicsEngine::OUTPUT_SUPPORT[$engine])) { + if (!in_array($target_format, MediaEngine::OUTPUT_SUPPORT[$engine])) { throw new ImageTranscodeException("Engine $engine does not support output format $target_format"); } @@ -313,7 +313,7 @@ class TranscodeImage extends Extension try { $result = false; switch ($target_format) { - case "webp-lossy": + case Media::WEBP_LOSSY: $result = imagewebp($image, $tmp_name, $q); break; case "png": @@ -358,20 +358,20 @@ class TranscodeImage extends Extension global $config; $q = $config->get_int("transcode_quality"); - $convert = $config->get_string(GraphicsConfig::CONVERT_PATH); + $convert = $config->get_string(MediaConfig::CONVERT_PATH); if ($convert==null||$convert=="") { throw new ImageTranscodeException("ImageMagick path not configured"); } - $ext = Graphics::determine_ext($target_format); + $ext = Media::determine_ext($target_format); $args = " -flatten "; $bg = "none"; switch ($target_format) { - case Graphics::WEBP_LOSSLESS: + case Media::WEBP_LOSSLESS: $args .= '-define webp:lossless=true'; break; - case Graphics::WEBP_LOSSY: + case Media::WEBP_LOSSY: $args .= ''; break; case "png": diff --git a/themes/lite/view.theme.php b/themes/lite/view.theme.php index b3ae0046..4c1beac9 100644 --- a/themes/lite/view.theme.php +++ b/themes/lite/view.theme.php @@ -18,6 +18,7 @@ class CustomViewImageTheme extends ViewImageTheme $h_owner = html_escape($image->get_owner()->name); $h_ownerlink = "$h_owner"; $h_ip = html_escape($image->owner_ip); + $h_type = html_escape($image->get_mime_type()); $h_date = autodate($image->posted); $h_filesize = to_shorthand_int($image->filesize); @@ -31,6 +32,7 @@ class CustomViewImageTheme extends ViewImageTheme
Posted: $h_date by $h_ownerlink
Size: {$image->width}x{$image->height}
Filesize: $h_filesize +
Type: ".$h_type." "; if (!is_null($image->source)) { From b1db833d51fe6a95430effd434ab2eb9bc5df761 Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Tue, 25 Jun 2019 15:17:13 -0500 Subject: [PATCH 09/19] Added additional media properties to the images table, video, audio, length, and lossless. Added new event to handle fetching media properties like height, width, and the newly added fields, and admin controls to manually scan files for their properties. Added a search terms content:video and content:audio to search for images that do (or do not) have those flags. --- core/imageboard/image.php | 13 ++ core/imageboard/misc.php | 18 +++ ext/handle_flash/main.php | 26 +++- ext/handle_ico/main.php | 37 +++-- ext/handle_mp3/main.php | 14 ++ ext/handle_pixel/main.php | 46 +++++- ext/handle_svg/main.php | 22 ++- ext/handle_video/main.php | 58 ++++++- ext/image/main.php | 63 +++++--- ext/media/main.php | 261 ++++++++++++++++++++++++++++---- ext/media/theme.php | 24 +++ ext/resize/main.php | 6 +- ext/transcode/main.php | 21 ++- ext/transcode/script.js | 11 ++ ext/transcode/theme.php | 4 +- ext/upgrade/main.php | 57 +++++++ themes/danbooru/view.theme.php | 8 +- themes/danbooru2/view.theme.php | 8 + themes/lite/view.theme.php | 5 + 19 files changed, 607 insertions(+), 95 deletions(-) create mode 100644 ext/transcode/script.js diff --git a/core/imageboard/image.php b/core/imageboard/image.php index d6b63371..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. diff --git a/core/imageboard/misc.php b/core/imageboard/misc.php index 5af65d6f..cec051d1 100644 --- a/core/imageboard/misc.php +++ b/core/imageboard/misc.php @@ -194,3 +194,21 @@ function create_image_thumb(string $hash, string $type, string $engine = null) { } +const TIME_UNITS = ["s"=>60,"m"=>60,"h"=>24,"d"=>365,"y"=>PHP_INT_MAX]; +function format_milliseconds(int $input): string +{ + $output = ""; + + $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; + } + + return trim($output); +} \ No newline at end of file diff --git a/ext/handle_flash/main.php b/ext/handle_flash/main.php index 3277d2b9..3c53622d 100644 --- a/ext/handle_flash/main.php +++ b/ext/handle_flash/main.php @@ -8,6 +8,26 @@ 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; @@ -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 840767c5..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']; 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 f5c9b3f1..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']; diff --git a/ext/handle_svg/main.php b/ext/handle_svg/main.php index 4df1154b..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)) { @@ -82,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 666def30..5c090faf 100644 --- a/ext/handle_video/main.php +++ b/ext/handle_video/main.php @@ -48,6 +48,60 @@ class VideoFileHandler extends DataHandlerExtension $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. */ @@ -65,10 +119,6 @@ class VideoFileHandler extends DataHandlerExtension { $image = new Image(); - $size = Media::video_size($filename); - $image->width = $size[0]; - $image->height = $size[1]; - switch (getMimeType($filename)) { case "video/webm": $image->ext = "webm"; diff --git a/ext/image/main.php b/ext/image/main.php index c851a90d..ff883c77 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -241,12 +241,12 @@ class ImageIO extends Extension ) VALUES ( :owner_id, :owner_ip, :filename, :filesize, - :hash, :ext, :width, :height, now(), :source + :hash, :ext, 0, 0, now(), :source )", [ "owner_id" => $user->id, "owner_ip" => $_SERVER['REMOTE_ADDR'], "filename" => substr($image->filename, 0, 255), "filesize" => $image->filesize, - "hash"=>$image->hash, "ext"=>strtolower($image->ext), "width"=>$image->width, "height"=>$image->height, "source"=>$image->source - ] + "hash" => $image->hash, "ext" => strtolower($image->ext), "source" => $image->source + ] ); $image->id = $database->get_last_insert_id('images_id_seq'); @@ -264,6 +264,13 @@ class ImageIO extends Extension if ($image->source !== null) { log_info("core-image", "Source for Image #{$image->id} set to: {$image->source}"); } + + try { + Media::update_image_media_properties($image->hash, strtolower($image->ext)); + } catch(MediaException $e) { + log_warning("add_image","Error while running update_image_media_properties: ".$e->getMessage()); + } + } // }}} end add @@ -340,33 +347,49 @@ class ImageIO extends Extension throw new ImageReplaceException("Image to replace does not exist!"); } + $duplicate = Image::by_hash($image->hash); + if(!is_null($duplicate)) { + $error = "Image {$duplicate->id} " . + "already has hash {$image->hash}:

" . $this->theme->build_thumb_html($duplicate); + throw new ImageReplaceException($error); + } + if (strlen(trim($image->source)) == 0) { $image->source = $existing->get_source(); } - + + // Update the data in the database. + $database->Execute( + "UPDATE images SET + filename = :filename, filesize = :filesize, hash = :hash, + ext = :ext, width = 0, height = 0, source = :source + WHERE + id = :id + ", + [ + "filename" => substr($image->filename, 0, 255), + "filesize" => $image->filesize, + "hash" => $image->hash, + "ext" => strtolower($image->ext), + "source" => $image->source, + "id" => $id, + ] + ); + /* This step could be optional, ie: perhaps move the image somewhere and have it stored in a 'replaced images' list that could be inspected later by an admin? */ - log_debug("image", "Removing image with hash ".$existing->hash); + log_debug("image", "Removing image with hash " . $existing->hash); $existing->remove_image_only(); // Actually delete the old image file from disk - - // Update the data in the database. - $database->Execute( - "UPDATE images SET - filename = :filename, filesize = :filesize, hash = :hash, - ext = :ext, width = :width, height = :height, source = :source - WHERE - id = :id - ", - [ - "filename" => substr($image->filename, 0, 255), "filesize"=>$image->filesize, "hash"=>$image->hash, - "ext"=>strtolower($image->ext), "width"=>$image->width, "height"=>$image->height, "source"=>$image->source, - "id"=>$id - ] - ); + + try { + Media::update_image_media_properties($image->hash, $image->ext); + } catch(MediaException $e) { + log_warning("image_replace","Error while running update_image_media_properties: ".$e->getMessage()); + } /* Generate new thumbnail */ send_event(new ThumbnailGenerationEvent($image->hash, strtolower($image->ext))); diff --git a/ext/media/main.php b/ext/media/main.php index 610959a9..6b87c60a 100644 --- a/ext/media/main.php +++ b/ext/media/main.php @@ -49,7 +49,9 @@ abstract class MediaEngine Media::WEBP_LOSSLESS, ], MediaEngine::FFMPEG => [ - + "jpg", + "webp", + "png" ] ]; public const INPUT_SUPPORT = [ @@ -59,6 +61,8 @@ abstract class MediaEngine "jpg", "png", "webp", + Media::WEBP_LOSSY, + Media::WEBP_LOSSLESS ], MediaEngine::IMAGICK => [ "bmp", @@ -68,6 +72,8 @@ abstract class MediaEngine "psd", "tiff", "webp", + Media::WEBP_LOSSY, + Media::WEBP_LOSSLESS, "ico", ], MediaEngine::FFMPEG => [ @@ -122,11 +128,16 @@ class MediaResizeEvent extends Event } } -class MediaCheckLosslessEvent extends Event +class MediaCheckPropertiesEvent extends Event { public $file_name; public $ext; - public $result = false; + public $lossless = null; + public $audio = null; + public $video = null; + public $length = null; + public $height = null; + public $width = null; public function __construct(string $file_name, string $ext) { @@ -136,6 +147,7 @@ class MediaCheckLosslessEvent extends Event } + class Media extends Extension { const WEBP_LOSSY = "webp-lossy"; @@ -149,11 +161,19 @@ class Media extends Extension const LOSSLESS_FORMATS = [ self::WEBP_LOSSLESS, "png", + "psd", + "bmp", + "ico", + "cur", + "ani", + "gif" + ]; const ALPHA_FORMATS = [ self::WEBP_LOSSLESS, self::WEBP_LOSSY, + "webp", "png", ]; @@ -191,7 +211,7 @@ class Media extends Extension global $config; $config->set_default_string(MediaConfig::FFPROBE_PATH, 'ffprobe'); $config->set_default_int(MediaConfig::MEM_LIMIT, parse_shorthand_int('8MB')); - $config->set_default_string(MediaConfig::FFMPEG_PATH, ''); + $config->set_default_string(MediaConfig::FFMPEG_PATH, 'ffmpeg'); $config->set_default_string(MediaConfig::CONVERT_PATH, 'convert'); @@ -206,6 +226,13 @@ class Media extends Extension } } + if ($ffprobe = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' ffprobe')) { + //ffprobe exists in PATH, check if it's executable, and if so, default to it instead of static + if (is_executable(strtok($ffprobe, PHP_EOL))) { + $config->set_default_string(MediaConfig::FFPROBE_PATH, 'ffprobe'); + } + } + $current_value = $config->get_string("thumb_convert_path"); if (!empty($current_value)) { $config->set_string(MediaConfig::CONVERT_PATH, $current_value); @@ -226,6 +253,20 @@ class Media extends Extension } } + public function onPageRequest(PageRequestEvent $event) + { + global $database, $page, $user; + + if ($event->page_matches("media_rescan/") && $user->is_admin() && isset($_POST['image_id'])) { + $image = Image::by_id(int_escape($_POST['image_id'])); + + $this->update_image_media_properties($image->hash, $image->ext); + + $page->set_mode(PageMode::REDIRECT); + $page->set_redirect(make_link("post/view/$image->id")); + } + } + public function onSetupBuilding(SetupBuildingEvent $event) { $sb = new SetupBlock("Media Engines"); @@ -243,6 +284,7 @@ class Media extends Extension // } $sb->add_text_option(MediaConfig::FFMPEG_PATH, "
ffmpeg command: "); + $sb->add_text_option(MediaConfig::FFPROBE_PATH, "
ffprobe command: "); $sb->add_shorthand_int_option(MediaConfig::MEM_LIMIT, "
Max memory use: "); @@ -250,6 +292,66 @@ class Media extends Extension } + public function onAdminBuilding(AdminBuildingEvent $event) + { + global $database; + $types = $database->get_all("SELECT ext, count(*) count FROM images group by ext"); + + $this->theme->display_form($types); + } + + public function onAdminAction(AdminActionEvent $event) + { + $action = $event->action; + if (method_exists($this, $action)) { + $event->redirect = $this->$action(); + } + } + + + public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) + { + global $user; + if ($user->can("delete_image")) { + $event->add_part($this->theme->get_buttons_html($event->image->id)); + } + } + + + public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event) + { + global $user; + + if ($user->is_admin()) { + $event->add_action("bulk_media_rescan", "Scan Media Properties"); + } + } + + public function onBulkAction(BulkActionEvent $event) + { + global $user; + + switch ($event->action) { + case "bulk_media_rescan": + if ($user->is_admin()) { + $total = 0; + foreach ($event->items as $id) { + $image = Image::by_id($id); + if ($image == null) { + continue; + } + try { + $this->update_image_media_properties($image->hash, $image->ext); + $total++; + } catch (MediaException $e) { + } + } + flash_message("Scanned media properties for $total items"); + } + break; + } + } + /** * @param MediaResizeEvent $event * @throws MediaException @@ -302,22 +404,88 @@ class Media extends Extension // } } - public function onMediaCheckLossless(MediaCheckLosslessEvent $event) + + const CONTENT_SEARCH_TERM_REGEX = "/^(-)?content[=|:]((video)|(audio))$/i"; + + + public function onSearchTermParse(SearchTermParseEvent $event) { - switch ($event->ext) { - case "png": - case "psd": - case "bmp": - case "gif": - case "ico": - $event->result = true; - break; - case "webp": - $event->result = Media::is_lossless_webp($event->file_name); - break; + global $database; + + $matches = []; + if (preg_match(self::CONTENT_SEARCH_TERM_REGEX, $event->term, $matches)) { + $positive = $matches[1]; + $field = $matches[2]; + $event->add_querylet(new Querylet("$field = " . $database->scoreql_to_sql($positive != "-" ? "SCORE_BOOL_Y" : "SCORE_BOOL_N"))); } } + public function onTagTermParse(TagTermParseEvent $event) + { + $matches = []; + + if (preg_match(self::SEARCH_TERM_REGEX, strtolower($event->term), $matches) && $event->parse) { + // Nothing to save, just helping filter out reserved tags + } + + if (!empty($matches)) { + $event->metatag = true; + } + } + + private function media_rescan(): bool + { + $ext = ""; + if (array_key_exists("media_rescan_type", $_POST)) { + $ext = $_POST["media_rescan_type"]; + } + + $results = $this->get_images($ext); + + foreach ($results as $result) { + $this->update_image_media_properties($result["hash"], $result["ext"]); + } + return true; + } + + public static function update_image_media_properties(string $hash, string $ext) + { + global $database; + + $path = warehouse_path(Image::IMAGE_DIR, $hash); + $mcpe = new MediaCheckPropertiesEvent($path, $ext); + send_event($mcpe); + + + $database->execute( + "UPDATE images SET + lossless = :lossless, video = :video, audio = :audio, + height = :height, width = :width, + length = :length WHERE hash = :hash", + [ + "hash" => $hash, + "width" => $mcpe->width ?? 0, + "height" => $mcpe->height ?? 0, + "lossless" => $database->scoresql_value_prepare($mcpe->lossless), + "video" => $database->scoresql_value_prepare($mcpe->video), + "audio" => $database->scoresql_value_prepare($mcpe->audio), + "length" => $mcpe->length + ]); + } + + public function get_images(String $ext = null) + { + global $database; + + $query = "SELECT id, hash, ext FROM images "; + $args = []; + if (!empty($ext)) { + $query .= " WHERE ext = :ext"; + $args["ext"] = $ext; + } + return $database->get_all($query, $args); + } + /** * Check Memory usage limits * @@ -404,6 +572,41 @@ class Media extends Extension } } + + public static function get_ffprobe_data($filename): array + { + global $config; + + $ffprobe = $config->get_string(MediaConfig::FFPROBE_PATH); + if ($ffprobe == null || $ffprobe == "") { + throw new MediaException("ffprobe command configured"); + } + + $args = [ + escapeshellarg($ffprobe), + "-print_format", "json", + "-v", "quiet", + "-show_format", + "-show_streams", + escapeshellarg($filename), + ]; + + $cmd = escapeshellcmd(implode(" ", $args)); + + exec($cmd, $output, $ret); + + if ((int)$ret == (int)0) { + log_debug('Media', "Getting media data `$cmd`, returns $ret"); + $output = implode($output); + $data = json_decode($output, true); + + return $data; + } else { + log_error('Media', "Getting media data `$cmd`, returns $ret"); + return []; + } + } + public static function determine_ext(String $format): String { $format = self::normalize_format($format); @@ -773,11 +976,9 @@ class Media extends Extension } - - private static function compare_file_bytes(String $file_name, array $comparison): bool { - $size= filesize($file_name); + $size = filesize($file_name); if ($size < count($comparison)) { // Can't match because it's too small return false; @@ -785,15 +986,15 @@ class Media extends Extension if (($fh = @fopen($file_name, 'rb'))) { try { - $chunk = unpack("C*",fread($fh, count($comparison))); + $chunk = unpack("C*", fread($fh, count($comparison))); for ($i = 0; $i < count($comparison); $i++) { $byte = $comparison[$i]; - if($byte==null) { + if ($byte == null) { continue; } else { - $fileByte = $chunk[$i+1]; - if($fileByte!=$byte) { + $fileByte = $chunk[$i + 1]; + if ($fileByte != $byte) { return false; } } @@ -823,9 +1024,9 @@ class Media extends Extension return in_array(self::normalize_format($format), self::ALPHA_FORMATS); } - public static function is_input_supported($engine, $format): bool + public static function is_input_supported($engine, $format, ?bool $lossless = null): bool { - $format = self::normalize_format($format); + $format = self::normalize_format($format, $lossless); if (!in_array($format, MediaEngine::INPUT_SUPPORT[$engine])) { return false; } @@ -849,8 +1050,16 @@ class Media extends Extension * @param $format * @return string|null The format name that the media extension will recognize. */ - static public function normalize_format($format): ?string + static public function normalize_format(string $format, ?bool $lossless = null): ?string { + if ($format == "webp") { + if ($lossless === true) { + $format = Media::WEBP_LOSSLESS; + } else { + $format = Media::WEBP_LOSSY; + } + } + if (array_key_exists($format, Media::FORMAT_ALIASES)) { return self::FORMAT_ALIASES[$format]; } diff --git a/ext/media/theme.php b/ext/media/theme.php index e213a0e9..d2c7755c 100644 --- a/ext/media/theme.php +++ b/ext/media/theme.php @@ -2,5 +2,29 @@ class MediaTheme extends Themelet { + public function display_form(array $types) + { + global $page, $database; + $html = "Use this to force scanning for media properties."; + $html .= make_form(make_link("admin/media_rescan")); + $html .= "
"; + $html .= ""; + $html .= "\n"; + $page->add_block(new Block("Media Tools", $html)); + } + + public function get_buttons_html(int $image_id): string + { + return " + ".make_form(make_link("media_rescan/"))." + + + + "; + } } diff --git a/ext/resize/main.php b/ext/resize/main.php index 166b93f1..9290f08b 100644 --- a/ext/resize/main.php +++ b/ext/resize/main.php @@ -49,7 +49,7 @@ class ResizeImage extends Extension { global $user, $config; if ($user->is_admin() && $config->get_bool(ResizeConfig::ENABLED) - && $this->can_resize_format($event->image->ext)) { + && $this->can_resize_format($event->image->ext, $event->image->lossless)) { /* Add a link to resize the image */ $event->add_part($this->theme->get_resize_html($event->image)); } @@ -83,7 +83,7 @@ class ResizeImage extends Extension $image_obj = Image::by_id($event->image_id); if ($config->get_bool(ResizeConfig::UPLOAD) == true - && $this->can_resize_format($event->type)) { + && $this->can_resize_format($event->type, $image_obj->lossless)) { $width = $height = 0; if ($config->get_int(ResizeConfig::DEFAULT_WIDTH) !== 0) { @@ -170,7 +170,7 @@ class ResizeImage extends Extension } } - private function can_resize_format($format): bool + private function can_resize_format($format, ?bool $lossless): bool { global $config; $engine = $config->get_string(ResizeConfig::ENGINE); diff --git a/ext/transcode/main.php b/ext/transcode/main.php index 41dfa07e..72119c82 100644 --- a/ext/transcode/main.php +++ b/ext/transcode/main.php @@ -80,8 +80,8 @@ class TranscodeImage extends Extension if ($user->is_admin()) { $engine = $config->get_string(TranscodeConfig::ENGINE); - if ($this->can_convert_format($engine, $event->image->ext)) { - $options = $this->get_supported_output_formats($engine, $event->image->ext); + if ($this->can_convert_format($engine, $event->image->ext, $event->image->lossless)) { + $options = $this->get_supported_output_formats($engine, $event->image->ext, $event->image->lossless??false); $event->add_part($this->theme->get_transcode_html($event->image, $options)); } } @@ -222,23 +222,27 @@ class TranscodeImage extends Extension } - private function can_convert_format($engine, $format): bool + private function can_convert_format($engine, $format, ?bool $lossless = null): bool { - return Media::is_input_supported($engine, $format); + return Media::is_input_supported($engine, $format, $lossless); } - private function get_supported_output_formats($engine, ?String $omit_format = null): array + private function get_supported_output_formats($engine, ?String $omit_format = null, ?bool $lossless = null): array { - $omit_format = Media::normalize_format($omit_format); + if($omit_format!=null) { + $omit_format = Media::normalize_format($omit_format, $lossless); + } $output = []; + + foreach (self::OUTPUT_FORMATS as $key=>$value) { if ($value=="") { $output[$key] = $value; continue; } if(Media::is_output_supported($engine, $value) - &&(empty($omit_format)||$omit_format!=Media::determine_ext($value))) { + &&(empty($omit_format)||$omit_format!=$value)) { $output[$key] = $value; } } @@ -278,7 +282,7 @@ class TranscodeImage extends Extension { global $config; - if ($source_format==Media::determine_ext($target_format)) { + if ($source_format==$target_format) { throw new ImageTranscodeException("Source and target formats are the same: ".$source_format); } @@ -313,6 +317,7 @@ class TranscodeImage extends Extension try { $result = false; switch ($target_format) { + case "webp": case Media::WEBP_LOSSY: $result = imagewebp($image, $tmp_name, $q); break; diff --git a/ext/transcode/script.js b/ext/transcode/script.js new file mode 100644 index 00000000..6f78ac33 --- /dev/null +++ b/ext/transcode/script.js @@ -0,0 +1,11 @@ +function transcodeSubmit(e) { + var format = document.getElementById('transcode_format').value; + if(format!="webp-lossless" && format != "png") { + var lossless = document.getElementById('image_lossless'); + if(lossless!=null && lossless.value=='1') { + return confirm('You are about to transcode from a lossless format to a lossy format. Lossless formats compress with no quality loss, but converting to a lossy format always results in quality loss, and it will lose more quality every time it is done again on the same image. Are you sure you want to perform this transcode?'); + } else { + return confirm('Converting to a lossy format always results in quality loss, and it will lose more quality every time it is done again on the same image. Are you sure you want to perform this transcode?'); + } + } +} \ No newline at end of file diff --git a/ext/transcode/theme.php b/ext/transcode/theme.php index 85e948f0..bee29cc8 100644 --- a/ext/transcode/theme.php +++ b/ext/transcode/theme.php @@ -10,8 +10,10 @@ class TranscodeImageTheme extends Themelet global $config; $html = " - ".make_form(make_link("transcode/{$image->id}"), 'POST')." + ".make_form(make_link("transcode/{$image->id}"), 'POST', false, "", + "return transcodeSubmit()")." + ".$this->get_transcode_picker_html($options)."
diff --git a/ext/upgrade/main.php b/ext/upgrade/main.php index a96c2e78..68f09fe1 100644 --- a/ext/upgrade/main.php +++ b/ext/upgrade/main.php @@ -166,6 +166,63 @@ class Upgrade extends Extension log_info("upgrade", "Database at version 16"); $config->set_bool("in_upgrade", false); } + + if ($config->get_int("db_version") < 17) { + $config->set_bool("in_upgrade", true); + $config->set_int("db_version", 17); + + log_info("upgrade", "Adding media information columns to images table"); + $database->execute($database->scoreql_to_sql( + "ALTER TABLE images ADD COLUMN lossless SCORE_BOOL NULL" + )); + $database->execute($database->scoreql_to_sql( + "ALTER TABLE images ADD COLUMN video SCORE_BOOL NULL" + )); + $database->execute($database->scoreql_to_sql( + "ALTER TABLE images ADD COLUMN audio SCORE_BOOL NULL" + )); + $database->execute("ALTER TABLE images ADD COLUMN length INTEGER NULL "); + + log_info("upgrade", "Setting indexes for media columns"); + switch($database->get_driver_name()) { + case DatabaseDriver::PGSQL: + case DatabaseDriver::SQLITE: + $database->execute('CREATE INDEX images_video_idx ON images(video) WHERE video IS NOT NULL'); + $database->execute('CREATE INDEX images_audio_idx ON images(audio) WHERE audio IS NOT NULL'); + $database->execute('CREATE INDEX images_length_idx ON images(length) WHERE length IS NOT NULL'); + break; + default: + $database->execute('CREATE INDEX images_video_idx ON images(video)'); + $database->execute('CREATE INDEX images_audio_idx ON images(audio)'); + $database->execute('CREATE INDEX images_length_idx ON images(length)'); + break; + } + + if ($database->get_driver_name()==DatabaseDriver::PGSQL) { // These updates can take a little bit + $database->execute("SET statement_timeout TO 300000;"); + } + + log_info("upgrade", "Setting index for ext column"); + $database->execute('CREATE INDEX images_ext_idx ON images(ext)'); + + + $database->commit(); // Each of these commands could hit a lot of data, combining them into one big transaction would not be a good idea. + log_info("upgrade", "Setting predictable media values for known file types"); + $database->execute("UPDATE images SET lossless = true, video = true WHERE ext IN ('swf')"); + $database->execute("UPDATE images SET lossless = false, video = false, audio = true WHERE ext IN ('mp3')"); + $database->execute("UPDATE images SET lossless = false, video = false, audio = false WHERE ext IN ('jpg','jpeg')"); + $database->execute("UPDATE images SET lossless = true, video = false, audio = false WHERE ext IN ('ico','ani','cur','png','svg')"); + $database->execute("UPDATE images SET lossless = true, audio = false WHERE ext IN ('gif')"); + $database->execute("UPDATE images SET audio = false WHERE ext IN ('webp')"); + $database->execute("UPDATE images SET lossless = false, video = true WHERE ext IN ('flv','mp4','m4v','ogv','webm')"); + + + log_info("upgrade", "Database at version 17"); + $config->set_bool("in_upgrade", false); + } + + + } public function get_priority(): int diff --git a/themes/danbooru/view.theme.php b/themes/danbooru/view.theme.php index 62f67c3b..f113541a 100644 --- a/themes/danbooru/view.theme.php +++ b/themes/danbooru/view.theme.php @@ -18,6 +18,7 @@ class CustomViewImageTheme extends ViewImageTheme $h_owner = html_escape($image->get_owner()->name); $h_ownerlink = "$h_owner"; $h_ip = html_escape($image->owner_ip); + $h_type = html_escape($image->get_mime_type()); $h_date = autodate($image->posted); $h_filesize = to_shorthand_int($image->filesize); @@ -31,7 +32,12 @@ class CustomViewImageTheme extends ViewImageTheme
Posted: $h_date by $h_ownerlink
Size: {$image->width}x{$image->height}
Filesize: $h_filesize - "; +
Type: $h_type"; + + if($image->length!=null) { + $h_length = format_milliseconds($image->length); + $html .= "
Length: $h_length"; + } if (!is_null($image->source)) { $h_source = html_escape($image->source); diff --git a/themes/danbooru2/view.theme.php b/themes/danbooru2/view.theme.php index 589d054c..237f1a8c 100644 --- a/themes/danbooru2/view.theme.php +++ b/themes/danbooru2/view.theme.php @@ -17,6 +17,7 @@ class CustomViewImageTheme extends ViewImageTheme $h_owner = html_escape($image->get_owner()->name); $h_ownerlink = "$h_owner"; $h_ip = html_escape($image->owner_ip); + $h_type = html_escape($image->get_mime_type()); $h_date = autodate($image->posted); $h_filesize = to_shorthand_int($image->filesize); @@ -30,8 +31,15 @@ class CustomViewImageTheme extends ViewImageTheme
Uploader: $h_ownerlink
Date: $h_date
Size: $h_filesize ({$image->width}x{$image->height}) +
Type: $h_type "; + if($image->length!=null) { + $h_length = format_milliseconds($image->length); + $html .= "
Length: $h_length"; + } + + if (!is_null($image->source)) { $h_source = html_escape($image->source); if (substr($image->source, 0, 7) != "http://" && substr($image->source, 0, 8) != "https://") { diff --git a/themes/lite/view.theme.php b/themes/lite/view.theme.php index 4c1beac9..a4613467 100644 --- a/themes/lite/view.theme.php +++ b/themes/lite/view.theme.php @@ -34,6 +34,11 @@ class CustomViewImageTheme extends ViewImageTheme
Filesize: $h_filesize
Type: ".$h_type." "; + if($image->length!=null) { + $h_length = format_milliseconds($image->length); + $html .= "
Length: $h_length"; + } + if (!is_null($image->source)) { $h_source = html_escape($image->source); From ae6126d388f6c6621e337c6ad4a3c73ac94ed9f7 Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Tue, 25 Jun 2019 16:09:31 -0500 Subject: [PATCH 10/19] Changed upgrade code to use SCORE stuff --- ext/upgrade/main.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ext/upgrade/main.php b/ext/upgrade/main.php index 68f09fe1..3bad2d52 100644 --- a/ext/upgrade/main.php +++ b/ext/upgrade/main.php @@ -13,6 +13,7 @@ class Upgrade extends Extension { global $config, $database; + if ($config->get_bool("in_upgrade")) { return; } @@ -163,6 +164,8 @@ class Upgrade extends Extension } // SQLite doesn't support altering existing columns? This seems like a problem? + + log_info("upgrade", "Database at version 16"); $config->set_bool("in_upgrade", false); } @@ -208,13 +211,13 @@ class Upgrade extends Extension $database->commit(); // Each of these commands could hit a lot of data, combining them into one big transaction would not be a good idea. log_info("upgrade", "Setting predictable media values for known file types"); - $database->execute("UPDATE images SET lossless = true, video = true WHERE ext IN ('swf')"); - $database->execute("UPDATE images SET lossless = false, video = false, audio = true WHERE ext IN ('mp3')"); - $database->execute("UPDATE images SET lossless = false, video = false, audio = false WHERE ext IN ('jpg','jpeg')"); - $database->execute("UPDATE images SET lossless = true, video = false, audio = false WHERE ext IN ('ico','ani','cur','png','svg')"); - $database->execute("UPDATE images SET lossless = true, audio = false WHERE ext IN ('gif')"); - $database->execute("UPDATE images SET audio = false WHERE ext IN ('webp')"); - $database->execute("UPDATE images SET lossless = false, video = true WHERE ext IN ('flv','mp4','m4v','ogv','webm')"); + $database->execute($database->scoreql_to_sql("UPDATE images SET lossless = SCORE_BOOL_Y, video = SCORE_BOOL_Y WHERE ext IN ('swf')")); + $database->execute($database->scoreql_to_sql("UPDATE images SET lossless = SCORE_BOOL_N, video = SCORE_BOOL_N, audio = SCORE_BOOL_Y WHERE ext IN ('mp3')")); + $database->execute($database->scoreql_to_sql("UPDATE images SET lossless = SCORE_BOOL_N, video = SCORE_BOOL_N, audio = SCORE_BOOL_N WHERE ext IN ('jpg','jpeg')")); + $database->execute($database->scoreql_to_sql("UPDATE images SET lossless = SCORE_BOOL_Y, video = SCORE_BOOL_N, audio = SCORE_BOOL_N WHERE ext IN ('ico','ani','cur','png','svg')")); + $database->execute($database->scoreql_to_sql("UPDATE images SET lossless = SCORE_BOOL_Y, audio = SCORE_BOOL_N WHERE ext IN ('gif')")); + $database->execute($database->scoreql_to_sql("UPDATE images SET audio = SCORE_BOOL_N WHERE ext IN ('webp')")); + $database->execute($database->scoreql_to_sql("UPDATE images SET lossless = SCORE_BOOL_N, video = SCORE_BOOL_Y WHERE ext IN ('flv','mp4','m4v','ogv','webm')")); log_info("upgrade", "Database at version 17"); From a0c0b6e3d186923174b8b098a0ca5141f144e920 Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Tue, 25 Jun 2019 18:43:57 -0500 Subject: [PATCH 11/19] Various fixes --- core/imageboard/misc.php | 7 ++++++- ext/handle_video/theme.php | 12 +++++++++++- ext/media/main.php | 39 ++++++++++++++++++++++++++++++++------ ext/resize/main.php | 19 +++++++++++++------ 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/core/imageboard/misc.php b/core/imageboard/misc.php index cec051d1..c82d9f98 100644 --- a/core/imageboard/misc.php +++ b/core/imageboard/misc.php @@ -178,6 +178,11 @@ function create_image_thumb(string $hash, string $type, string $engine = null) { $engine = $config->get_string(ImageConfig::THUMB_ENGINE); } + $output_format = $config->get_string(ImageConfig::THUMB_TYPE); + if($output_format=="webp") { + $output_format = Media::WEBP_LOSSY; + } + send_event(new MediaResizeEvent( $engine, $inname, @@ -186,7 +191,7 @@ function create_image_thumb(string $hash, string $type, string $engine = null) { $tsize[0], $tsize[1], false, - $config->get_string(ImageConfig::THUMB_TYPE), + $output_format, $config->get_int(ImageConfig::THUMB_QUALITY), true, $config->get_bool('thumb_upscale', false) 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 .= " -

ImageReasonAction
LinksImage Only (Backup Server)
"; + $html .= ""; + $html .= ""; + $html .= "
Image Type
"; - $html .= ""; - $html .= "\n"; + $html .= "
\n"; $page->add_block(new Block("Media Tools", $html)); } From 58948a90fb6638fbe5ca1346415d0aab8b594e77 Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Wed, 26 Jun 2019 23:14:12 -0500 Subject: [PATCH 15/19] Fixed a constant reference --- ext/media/main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/media/main.php b/ext/media/main.php index 6c977fa7..d4f37cc4 100644 --- a/ext/media/main.php +++ b/ext/media/main.php @@ -428,7 +428,7 @@ class Media extends Extension { $matches = []; - if (preg_match(self::SEARCH_TERM_REGEX, strtolower($event->term), $matches) && $event->parse) { + if (preg_match(self::CONTENT_SEARCH_TERM_REGEX, strtolower($event->term), $matches) && $event->parse) { // Nothing to save, just helping filter out reserved tags } From 9ce5a058409148dd225a4433b768e5e9f77799ec Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Thu, 27 Jun 2019 19:08:23 -0500 Subject: [PATCH 16/19] Update main.php --- ext/handle_pixel/main.php | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/handle_pixel/main.php b/ext/handle_pixel/main.php index 3b73bdfb..d46ca7ca 100644 --- a/ext/handle_pixel/main.php +++ b/ext/handle_pixel/main.php @@ -99,6 +99,7 @@ class PixelFileHandler extends DataHandlerExtension imagestring($thumb, 5, 10, 24, "Image Too Large :(", $black); return true; } catch (Exception $e) { + throw $e; log_error("handle_pixel", "Error while creating thumbnail: ".$e->getMessage()); return false; } From f2496b99f6496027a121c526ac9e20a140263157 Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Thu, 27 Jun 2019 19:20:22 -0500 Subject: [PATCH 17/19] fix for resize arg isssue --- ext/media/main.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/media/main.php b/ext/media/main.php index d4f37cc4..d801827a 100644 --- a/ext/media/main.php +++ b/ext/media/main.php @@ -781,7 +781,9 @@ class Media extends Extension $args = " -flatten "; if ($minimize) { - $args = " -strip -thumbnail"; + $args .= " -strip -thumbnail"; + } else { + $args .= " -resize"; } $resize_args = ""; @@ -803,7 +805,7 @@ class Media extends Extension $output_ext = self::determine_ext($output_type); - $format = '"%s" %s -resize %ux%u%s -quality %u -background %s %s"%s[0]" %s:"%s" 2>&1'; + $format = '"%s" %s %ux%u%s -quality %u -background %s %s"%s[0]" %s:"%s" 2>&1'; $cmd = sprintf($format, $convert, $args, $new_width, $new_height, $resize_args, $output_quality, $bg, $input_type, $input_path, $output_ext, $output_filename); $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); From 7991e981ca8e9388cdf36f16430fa2841e38d46d Mon Sep 17 00:00:00 2001 From: matthew Date: Thu, 27 Jun 2019 19:25:10 -0500 Subject: [PATCH 18/19] Revert "Update main.php" This reverts commit 0eeede7977f69c1e3a60d778de771f08eed4fc38. --- ext/handle_pixel/main.php | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/handle_pixel/main.php b/ext/handle_pixel/main.php index d46ca7ca..3b73bdfb 100644 --- a/ext/handle_pixel/main.php +++ b/ext/handle_pixel/main.php @@ -99,7 +99,6 @@ class PixelFileHandler extends DataHandlerExtension imagestring($thumb, 5, 10, 24, "Image Too Large :(", $black); return true; } catch (Exception $e) { - throw $e; log_error("handle_pixel", "Error while creating thumbnail: ".$e->getMessage()); return false; } From 38cc05cf37426f6def80f740fb8b7d1fdfe36a89 Mon Sep 17 00:00:00 2001 From: matthew Date: Thu, 4 Jul 2019 12:58:15 -0500 Subject: [PATCH 19/19] Fixed issue with merge's duplicate hash check --- ext/image/main.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/image/main.php b/ext/image/main.php index ff883c77..099bdd65 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -348,7 +348,8 @@ class ImageIO extends Extension } $duplicate = Image::by_hash($image->hash); - if(!is_null($duplicate)) { + + if(!is_null($duplicate) && $duplicate->id!=$id) { $error = "Image {$duplicate->id} " . "already has hash {$image->hash}:

" . $this->theme->build_thumb_html($duplicate); throw new ImageReplaceException($error);