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, 'ffmpeg'); $config->set_default_string(MediaConfig::CONVERT_PATH, 'convert'); } public function onPageRequest(PageRequestEvent $event): void { global $page, $user; if ($event->page_matches("media_rescan/{image_id}", method: "POST", permission: Permissions::RESCAN_MEDIA)) { $image = Image::by_id_ex($event->get_iarg('image_id')); send_event(new MediaCheckPropertiesEvent($image)); $image->save_to_db(); $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("post/view/$image->id")); } } public function onSetupBuilding(SetupBuildingEvent $event): void { $sb = $event->panel->create_new_block("Media Engine Commands"); // 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->start_table(); $sb->add_text_option(MediaConfig::CONVERT_PATH, "convert", true); // } $sb->add_text_option(MediaConfig::FFMPEG_PATH, "ffmpeg", true); $sb->add_text_option(MediaConfig::FFPROBE_PATH, "ffprobe", true); $sb->add_shorthand_int_option(MediaConfig::MEM_LIMIT, "Mem limit", true); $sb->end_table(); } public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event): void { global $user; if ($user->can(Permissions::DELETE_IMAGE)) { $event->add_button("Scan Media Properties", "media_rescan/{$event->image->id}"); } } public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event): void { global $user; if ($user->can(Permissions::RESCAN_MEDIA)) { $event->add_action("bulk_media_rescan", "Scan Media Properties"); } } public function onBulkAction(BulkActionEvent $event): void { global $page, $user; switch ($event->action) { case "bulk_media_rescan": if ($user->can(Permissions::RESCAN_MEDIA)) { $total = 0; $failed = 0; foreach ($event->items as $image) { try { log_debug("media", "Rescanning media for {$image->hash} ({$image->id})"); send_event(new MediaCheckPropertiesEvent($image)); $image->save_to_db(); $total++; } catch (MediaException $e) { $failed++; } } $page->flash("Scanned media properties for $total items, failed for $failed"); } break; } } public function onCliGen(CliGenEvent $event): void { $event->app->register('media-rescan') ->addArgument('id_or_hash', InputArgument::REQUIRED) ->setDescription('Refresh metadata for a given post') ->setCode(function (InputInterface $input, OutputInterface $output): int { $uid = $input->getArgument('id_or_hash'); $image = Image::by_id_or_hash($uid); if ($image) { send_event(new MediaCheckPropertiesEvent($image)); $image->save_to_db(); } else { $output->writeln("No post with ID '$uid'"); } return Command::SUCCESS; }); } /** * @param MediaResizeEvent $event */ public function onMediaResize(MediaResizeEvent $event): void { if (!in_array( $event->resize_type, MediaEngine::RESIZE_TYPE_SUPPORT[$event->engine] )) { throw new MediaException("Resize type $event->resize_type not supported by selected media engine $event->engine"); } switch ($event->engine) { case MediaEngine::GD: $info = getimagesize($event->input_path); if ($info === false) { throw new MediaException("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_mime, $event->alpha_color, $event->resize_type, $event->target_quality, $event->allow_upscale ); break; case MediaEngine::IMAGICK: // if (self::imagick_available()) { // } else { self::image_resize_convert( $event->input_path, $event->input_mime, $event->target_width, $event->target_height, $event->output_path, $event->target_mime, $event->alpha_color, $event->resize_type, $event->target_quality, $event->minimize, $event->allow_upscale ); //} break; case MediaEngine::STATIC: copy($event->input_path, $event->output_path); break; default: throw new MediaException("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); // } } public function onSearchTermParse(SearchTermParseEvent $event): void { if (is_null($event->term)) { return; } $matches = []; if (preg_match("/^content[=|:]((video)|(audio)|(image)|(unknown))$/i", $event->term, $matches)) { $field = $matches[1]; if ($field === "unknown") { $event->add_querylet(new Querylet("video IS NULL OR audio IS NULL OR image IS NULL")); } else { $event->add_querylet(new Querylet("$field = :true", ["true" => true])); } } elseif (preg_match("/^ratio([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+):(\d+)$/i", $event->term, $matches)) { $cmp = preg_replace_ex('/^:/', '=', $matches[1]); $args = ["width{$event->id}" => int_escape($matches[2]), "height{$event->id}" => int_escape($matches[3])]; $event->add_querylet(new Querylet("width / :width{$event->id} $cmp height / :height{$event->id}", $args)); } elseif (preg_match("/^size([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)x(\d+)$/i", $event->term, $matches)) { $cmp = ltrim($matches[1], ":") ?: "="; $args = ["width{$event->id}" => int_escape($matches[2]), "height{$event->id}" => int_escape($matches[3])]; $event->add_querylet(new Querylet("width $cmp :width{$event->id} AND height $cmp :height{$event->id}", $args)); } elseif (preg_match("/^width([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) { $cmp = ltrim($matches[1], ":") ?: "="; $event->add_querylet(new Querylet("width $cmp :width{$event->id}", ["width{$event->id}" => int_escape($matches[2])])); } elseif (preg_match("/^height([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) { $cmp = ltrim($matches[1], ":") ?: "="; $event->add_querylet(new Querylet("height $cmp :height{$event->id}", ["height{$event->id}" => int_escape($matches[2])])); } elseif (preg_match("/^length([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(.+)$/i", $event->term, $matches)) { $value = parse_to_milliseconds($matches[2]); $cmp = ltrim($matches[1], ":") ?: "="; $event->add_querylet(new Querylet("length $cmp :length{$event->id}", ["length{$event->id}" => $value])); } } public function onHelpPageBuilding(HelpPageBuildingEvent $event): void { if ($event->key === HelpPages::SEARCH) { $block = new Block(); $block->header = "Media"; $block->body = $this->theme->get_help_html(); $event->add_block($block); } } public function onParseLinkTemplate(ParseLinkTemplateEvent $event): void { if ($event->image->width && $event->image->height && $event->image->length) { $s = ((int)($event->image->length / 100)) / 10; $event->replace('$size', "{$event->image->width}x{$event->image->height}, {$s}s"); } elseif ($event->image->width && $event->image->height) { $event->replace('$size', "{$event->image->width}x{$event->image->height}"); } elseif ($event->image->length) { $s = ((int)($event->image->length / 100)) / 10; $event->replace('$size', "{$s}s"); } else { $event->replace('$size', "unknown 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. * https://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize * * @param array{0:int,1:int,2:int,bits?:int,channels?:int} $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. */ 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; } 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 MediaException */ public static function create_thumbnail_ffmpeg(Image $image): bool { global $config; $ffmpeg = $config->get_string(MediaConfig::FFMPEG_PATH); if (empty($ffmpeg)) { throw new MediaException("ffmpeg command not configured"); } $ok = false; $inname = $image->get_image_filename(); $tmpname = shm_tempnam("ffmpeg_thumb"); try { $outname = $image->get_thumb_filename(); $orig_size = self::video_size($inname); $scaled_size = get_thumbnail_size($orig_size[0], $orig_size[1], true); $args = [ escapeshellarg($ffmpeg), "-y", "-i", escapeshellarg($inname), "-vf", "scale=$scaled_size[0]:$scaled_size[1],thumbnail", "-f", "image2", "-vframes", "1", "-c:v", "png", escapeshellarg($tmpname), ]; $cmd = escapeshellcmd(implode(" ", $args)); log_debug('media', "Generating thumbnail with command `$cmd`..."); exec($cmd, $output, $ret); if ((int)$ret === (int)0) { log_debug('media', "Generating thumbnail with command `$cmd`, returns $ret"); create_scaled_image($tmpname, $outname, $scaled_size, MimeType::PNG); $ok = true; } else { log_error('media', "Generating thumbnail with command `$cmd`, returns $ret"); } } finally { @unlink($tmpname); } return $ok; } /** * @return array */ public static function get_ffprobe_data(string $filename): array { global $config; $ffprobe = $config->get_string(MediaConfig::FFPROBE_PATH); if (empty($ffprobe)) { throw new MediaException("ffprobe command not 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); return json_decode($output, true); } else { log_error('media', "Getting media data `$cmd`, returns $ret"); return []; } } public static function determine_ext(string $mime): string { $ext = FileExtension::get_for_mime($mime); if (empty($ext)) { throw new ServerError("Could not determine extension for $mime"); } return $ext; } // private static function image_save_imagick(Imagick $image, string $path, string $format, int $output_quality = 80, bool $minimize): void // { // switch ($format) { // case FileExtension::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 is_lossless(string $filename, string $mime): bool { if (in_array($mime, self::LOSSLESS_FORMATS)) { return true; } switch ($mime) { case MimeType::WEBP: return MimeType::is_lossless_webp($filename); } return false; } public static function image_resize_convert( string $input_path, string $input_mime, int $new_width, int $new_height, string $output_filename, ?string $output_mime = null, string $alpha_color = Media::DEFAULT_ALPHA_CONVERSION_COLOR, string $resize_type = self::RESIZE_TYPE_FIT, int $output_quality = 80, bool $minimize = false, bool $allow_upscale = true ): void { global $config; $convert = $config->get_string(MediaConfig::CONVERT_PATH); if (empty($convert)) { throw new MediaException("convert command not configured"); } if (empty($output_mime)) { $output_mime = $input_mime; } if ($output_mime == MimeType::WEBP && self::is_lossless($input_path, $input_mime)) { $output_mime = MimeType::WEBP_LOSSLESS; } $bg = "\"$alpha_color\""; if (self::supports_alpha($output_mime)) { $bg = "none"; } $resize_suffix = ""; if (!$allow_upscale) { $resize_suffix .= "\>"; } if ($resize_type == Media::RESIZE_TYPE_STRETCH) { $resize_suffix .= "\!"; } $args = " -auto-orient "; $resize_arg = "-resize"; if ($minimize) { $args .= "-strip "; $resize_arg = "-thumbnail"; } $input_ext = self::determine_ext($input_mime); $file_arg = "{$input_ext}:\"{$input_path}[0]\""; if ($resize_type === Media::RESIZE_TYPE_FIT_BLUR_PORTRAIT) { if ($new_height > $new_width) { $resize_type = Media::RESIZE_TYPE_FIT_BLUR; } else { $resize_type = Media::RESIZE_TYPE_FILL; } } switch ($resize_type) { case Media::RESIZE_TYPE_FIT: case Media::RESIZE_TYPE_STRETCH: $args .= "{$file_arg} {$resize_arg} {$new_width}x{$new_height}{$resize_suffix} -background {$bg} -flatten "; break; case Media::RESIZE_TYPE_FILL: $args .= "{$file_arg} {$resize_arg} {$new_width}x{$new_height}\^ -background {$bg} -flatten -gravity center -extent {$new_width}x{$new_height} "; break; case Media::RESIZE_TYPE_FIT_BLUR: $blur_size = max(ceil(max($new_width, $new_height) / 25), 5); $args .= "{$file_arg} ". "\( -clone 0 -auto-orient -resize {$new_width}x{$new_height}\^ -background {$bg} -flatten -gravity center -fill black -colorize 50% -extent {$new_width}x{$new_height} -blur 0x{$blur_size} \) ". "\( -clone 0 -auto-orient -resize {$new_width}x{$new_height} \) ". "-delete 0 -gravity center -compose over -composite"; break; } switch ($output_mime) { case MimeType::WEBP_LOSSLESS: $args .= ' -define webp:lossless=true'; break; case MimeType::PNG: $args .= ' -define png:compression-level=9'; break; } $args .= " -quality {$output_quality} "; $output_ext = self::determine_ext($output_mime); $format = '"%s" %s %s:"%s" 2>&1'; $cmd = sprintf($format, $convert, $args, $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); if ($ret != 0) { throw new MediaException("Resizing image with command `$cmd`, returns $ret, outputting " . implode("\r\n", $output)); } else { log_debug('media', "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{0:int,1:int,2:int} $info The output of getimagesize() for the source file. * @param int $new_width * @param int $new_height * @param string $output_filename * @param ?string $output_mime 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. */ public static function image_resize_gd( string $image_filename, array $info, int $new_width, int $new_height, string $output_filename, ?string $output_mime = null, string $alpha_color = Media::DEFAULT_ALPHA_CONVERSION_COLOR, string $resize_type = self::RESIZE_TYPE_FIT, int $output_quality = 80, bool $allow_upscale = true ): void { $width = $info[0]; $height = $info[1]; if ($output_mime === null) { /* If not specified, output to the same format as the original image */ switch ($info[2]) { case IMAGETYPE_GIF: $output_mime = MimeType::GIF; break; case IMAGETYPE_JPEG: $output_mime = MimeType::JPEG; break; case IMAGETYPE_PNG: $output_mime = MimeType::PNG; break; case IMAGETYPE_WEBP: $output_mime = MimeType::WEBP; break; case IMAGETYPE_BMP: $output_mime = MimeType::BMP; break; default: throw new MediaException("Failed to save the new image - Unsupported MIME 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 ($resize_type == Media::RESIZE_TYPE_FIT) { 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(\Safe\file_get_contents($image_filename)); if ($image === false) { throw new MediaException("Could not load image: " . $image_filename); } $image_resized = imagecreatetruecolor($new_width, $new_height); if ($image_resized === false) { throw new MediaException("Could not create output image with dimensions $new_width x $new_height "); } try { // 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 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 MediaException("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: https://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php // if (imagealphablending($image_resized, false) === false) { throw new MediaException("Unable to disable image alpha blending"); } if (imagesavealpha($image_resized, true) === false) { throw new MediaException("Unable to enable image save alpha"); } $transparent_color = imagecolorallocatealpha($image_resized, 255, 255, 255, 127); if ($transparent_color === false) { throw new MediaException("Unable to allocate transparent color"); } if (imagefilledrectangle($image_resized, 0, 0, $new_width, $new_height, $transparent_color) === false) { throw new MediaException("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 MediaException("Unable to copy resized image data to new image"); } switch ($output_mime) { case MimeType::BMP: case MimeType::JPEG: // In case of alpha channels $width = imagesx($image_resized); $height = imagesy($image_resized); $new_image = imagecreatetruecolor($width, $height); if ($new_image === false) { throw new ImageTranscodeException("Could not create image with dimensions $width x $height"); } $background_color = Media::hex_color_allocate($new_image, $alpha_color); if (imagefilledrectangle($new_image, 0, 0, $width, $height, $background_color) === false) { throw new ImageTranscodeException("Could not fill background color"); } if (imagecopy($new_image, $image_resized, 0, 0, 0, 0, $width, $height) === false) { throw new ImageTranscodeException("Could not copy source image to new image"); } imagedestroy($image_resized); $image_resized = $new_image; break; } switch ($output_mime) { case MimeType::BMP: $result = imagebmp($image_resized, $output_filename, true); break; case MimeType::WEBP: $result = imagewebp($image_resized, $output_filename, $output_quality); break; case MimeType::JPEG: $result = imagejpeg($image_resized, $output_filename, $output_quality); break; case MimeType::PNG: $result = imagepng($image_resized, $output_filename, 9); break; case MimeType::GIF: $result = imagegif($image_resized, $output_filename); break; default: throw new MediaException("Failed to save the new image - Unsupported image type: $output_mime"); } if ($result === false) { throw new MediaException("Failed to save the new image, function returned false when saving type: $output_mime"); } } finally { @imagedestroy($image); @imagedestroy($image_resized); } } public static function supports_alpha(string $mime): bool { return MimeType::matches_array($mime, self::ALPHA_FORMATS, true); } /** * Determines the dimensions of a video file using ffmpeg. * * @param string $filename * @return array{0: int, 1: int} */ public static function video_size(string $filename): array { global $config; $ffmpeg = $config->get_string(MediaConfig::FFMPEG_PATH); $cmd = escapeshellcmd(implode(" ", [ escapeshellarg($ffmpeg), "-y", "-i", escapeshellarg($filename), "-vstats" ])); // \Safe\shell_exec is a little broken // https://github.com/thecodingmachine/safe/issues/281 $output = shell_exec($cmd . " 2>&1"); if (is_null($output) || $output === false) { throw new MediaException("Failed to execute command: $cmd"); } // 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 = [(int)$regs[2], (int)$regs[1]]; } else { $size = [(int)$regs[1], (int)$regs[2]]; } } else { $size = [1, 1]; } log_debug('media', "Getting video size with `$cmd`, returns $output -- $size[0], $size[1]"); return $size; } public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void { global $config, $database; if ($this->get_version(MediaConfig::VERSION) < 1) { $current_value = $config->get_string("thumb_ffmpeg_path"); if (!empty($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(MediaConfig::FFMPEG_PATH, 'ffmpeg'); } } 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); } 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(MediaConfig::CONVERT_PATH, 'convert'); } } $current_value = $config->get_int("thumb_mem_limit"); if (!empty($current_value)) { $config->set_int(MediaConfig::MEM_LIMIT, $current_value); } $this->set_version(MediaConfig::VERSION, 1); } if ($this->get_version(MediaConfig::VERSION) < 2) { $database->execute("ALTER TABLE images ADD COLUMN image BOOLEAN NULL"); switch ($database->get_driver_id()) { case DatabaseDriverID::PGSQL: case DatabaseDriverID::SQLITE: $database->execute('CREATE INDEX images_image_idx ON images(image) WHERE image IS NOT NULL'); break; default: $database->execute('CREATE INDEX images_image_idx ON images(image)'); break; } $this->set_version(MediaConfig::VERSION, 2); } if ($this->get_version(MediaConfig::VERSION) < 3) { $database->execute("ALTER TABLE images ADD COLUMN video_codec varchar(512) NULL"); $this->set_version(MediaConfig::VERSION, 3); } if ($this->get_version(MediaConfig::VERSION) < 4) { $database->standardise_boolean("images", "image"); $this->set_version(MediaConfig::VERSION, 4); } if ($this->get_version(MediaConfig::VERSION) < 5) { $database->execute("UPDATE images SET image = :f WHERE ext IN ('swf','mp3','ani','flv','mp4','m4v','ogv','webm')", ["f" => false]); $database->execute("UPDATE images SET image = :t WHERE ext IN ('jpg','jpeg','ico','cur','png')", ["t" => true]); $this->set_version(MediaConfig::VERSION, 5); } } public static function hex_color_allocate(mixed $im, string $hex): int { $hex = ltrim($hex, '#'); $a = (int)hexdec(substr($hex, 0, 2)); $b = (int)hexdec(substr($hex, 2, 2)); $c = (int)hexdec(substr($hex, 4, 2)); $col = imagecolorallocate($im, $a, $b, $c); assert($col !== false); return $col; } }