diff --git a/ext/image/main.php b/ext/image/main.php
index c460660c..099bdd65 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' => MediaEngine::GD,
+ 'ImageMagick' => MediaEngine::IMAGICK
+ ];
+
+ 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")) {
@@ -221,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');
@@ -244,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
@@ -256,7 +283,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 +316,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
}
@@ -320,33 +347,50 @@ class ImageIO extends Extension
throw new ImageReplaceException("Image to replace does not exist!");
}
+ $duplicate = Image::by_hash($image->hash);
+
+ 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);
+ }
+
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
new file mode 100644
index 00000000..d801827a
--- /dev/null
+++ b/ext/media/main.php
@@ -0,0 +1,1135 @@
+
+ * Description: Provides common functions and settings used for media operations.
+ * License: MIT
+ * Version: 1.0
+ */
+
+/*
+* This is used by the media code when there is an error
+*/
+
+abstract class MediaConfig
+{
+ 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 MediaEngine
+{
+ public const GD = "gd";
+ public const IMAGICK = "convert";
+ public const FFMPEG = "ffmpeg";
+
+ public const ALL = [
+ MediaEngine::GD,
+ MediaEngine::FFMPEG,
+ MediaEngine::IMAGICK
+ ];
+ public const OUTPUT_SUPPORT = [
+ MediaEngine::GD => [
+ "gif",
+ "jpg",
+ "png",
+ "webp",
+ Media::WEBP_LOSSY,
+ ],
+ MediaEngine::IMAGICK => [
+ "gif",
+ "jpg",
+ "png",
+ "webp",
+ Media::WEBP_LOSSY,
+ Media::WEBP_LOSSLESS,
+ ],
+ MediaEngine::FFMPEG => [
+ "jpg",
+ "webp",
+ "png"
+ ]
+ ];
+ public const INPUT_SUPPORT = [
+ MediaEngine::GD => [
+ "bmp",
+ "gif",
+ "jpg",
+ "png",
+ "webp",
+ Media::WEBP_LOSSY,
+ Media::WEBP_LOSSLESS
+ ],
+ MediaEngine::IMAGICK => [
+ "bmp",
+ "gif",
+ "jpg",
+ "png",
+ "psd",
+ "tiff",
+ "webp",
+ Media::WEBP_LOSSY,
+ Media::WEBP_LOSSLESS,
+ "ico",
+ ],
+ MediaEngine::FFMPEG => [
+ "avi",
+ "mkv",
+ "webm",
+ "mp4",
+ "mov",
+ "flv"
+ ]
+ ];
+}
+
+class MediaException extends SCoreException
+{
+}
+
+class MediaResizeEvent 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, MediaEngine::ALL));
+ $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 MediaCheckPropertiesEvent extends Event
+{
+ public $file_name;
+ public $ext;
+ 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)
+ {
+ $this->file_name = $file_name;
+ $this->ext = $ext;
+ }
+
+}
+
+
+class Media extends Extension
+{
+ const WEBP_LOSSY = "webp-lossy";
+ const WEBP_LOSSLESS = "webp-lossless";
+
+ const IMAGE_MEDIA_ENGINES = [
+ "GD" => MediaEngine::GD,
+ "ImageMagick" => MediaEngine::IMAGICK,
+ ];
+
+ const LOSSLESS_FORMATS = [
+ self::WEBP_LOSSLESS,
+ "png",
+ "psd",
+ "bmp",
+ "ico",
+ "cur",
+ "ani",
+ "gif"
+
+ ];
+
+ const ALPHA_FORMATS = [
+ self::WEBP_LOSSLESS,
+ self::WEBP_LOSSY,
+ "webp",
+ "png",
+ ];
+
+ const FORMAT_ALIASES = [
+ "tif" => "tiff",
+ "jpeg" => "jpg",
+ ];
+
+
+ //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");
+ }
+
+ /**
+ * 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(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');
+
+
+ if ($config->get_int(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);
+ }
+
+ $config->set_int(MediaConfig::VERSION, 1);
+ log_info("media", "extension installed");
+ }
+ }
+
+ 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");
+
+// 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_table_header("Commands");
+
+ $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();
+
+ $event->panel->add_block($sb);
+
+ }
+
+ 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
+ * @throws InsufficientMemoryException
+ */
+ public function onMediaResize(MediaResizeEvent $event)
+ {
+ 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_format,
+ $event->ignore_aspect_ratio,
+ $event->target_quality,
+ $event->allow_upscale);
+
+ break;
+ case MediaEngine::IMAGICK:
+// 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 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);
+// }
+ }
+
+
+ const CONTENT_SEARCH_TERM_REGEX = "/^(-)?content[=|:]((video)|(audio))$/i";
+
+
+ public function onSearchTermParse(SearchTermParseEvent $event)
+ {
+ 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::CONTENT_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
+ *
+ * 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.
+ */
+ 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($hash): bool
+ {
+ global $config;
+
+ $ffmpeg = $config->get_string(MediaConfig::FFMPEG_PATH);
+ if ($ffmpeg == null || $ffmpeg == "") {
+ throw new MediaException("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('Media', "Generating thumbnail with command `$cmd`, returns $ret");
+ return true;
+ } else {
+ log_error('Media', "Generating thumbnail with command `$cmd`, returns $ret");
+ return false;
+ }
+ }
+
+
+ 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);
+ 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 is_lossless(string $filename, string $format) {
+ if(in_array($format, self::LOSSLESS_FORMATS)) {
+ return true;
+ }
+ switch($format) {
+ case "webp":
+ return self::is_lossless_webp($filename);
+ break;
+ }
+ return false;
+ }
+
+ 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(MediaConfig::CONVERT_PATH);
+
+ if (empty($convert)) {
+ throw new MediaException("convert command not configured");
+ }
+
+ if (empty($output_type)) {
+ $output_type = $input_type;
+ }
+
+ if($output_type=="webp" && self::is_lossless($input_path, $input_type)) {
+ $output_type = self::WEBP_LOSSLESS;
+ }
+
+ $bg = "black";
+ if (self::supports_alpha($output_type)) {
+ $bg = "none";
+ }
+ if (!empty($input_type)) {
+ $input_type = $input_type . ":";
+ }
+
+ $args = " -flatten ";
+ if ($minimize) {
+ $args .= " -strip -thumbnail";
+ } else {
+ $args .= " -resize";
+ }
+
+ $resize_args = "";
+ if (!$allow_upscale) {
+ $resize_args .= "\>";
+ }
+ if ($ignore_aspect_ratio) {
+ $resize_args .= "\!";
+ }
+
+ switch ($output_type) {
+ case Media::WEBP_LOSSLESS:
+ $args .= '-define webp:lossless=true';
+ break;
+ case "png":
+ $args .= '-define png:compression-level=9';
+ break;
+ }
+
+ $output_ext = self::determine_ext($output_type);
+
+ $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);
+ 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 $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 MediaException
+ * @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 MediaException("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 MediaException("Could not load image: " . $image_filename);
+ }
+ if ($image_resized === false) {
+ throw new MediaException("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 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: http://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_type) {
+ case "bmp":
+ $result = imagebmp($image_resized, $output_filename, true);
+ break;
+ case "webp":
+ case Media::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 MediaException("Failed to save the new image - Unsupported image type: $output_type");
+ }
+ if ($result === false) {
+ throw new MediaException("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): bool
+ {
+ $is_anim_gif = 0;
+ if (($fh = @fopen($image_filename, 'rb'))) {
+ 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);
+ }
+
+ public static function is_input_supported(string $engine, string $format, ?bool $lossless = null): bool
+ {
+ $format = self::normalize_format($format, $lossless);
+ if (!in_array($format, MediaEngine::INPUT_SUPPORT[$engine])) {
+ return false;
+ }
+ return true;
+ }
+
+ public static function is_output_supported(string $engine, string $format, ?bool $lossless = false): bool
+ {
+ $format = self::normalize_format($format, $lossless);
+ if (!in_array($format, MediaEngine::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 Media extension will recognize is returned,
+ * otherwise the incoming format is returned.
+ *
+ * @param $format
+ * @return string|null The format name that the media extension will recognize.
+ */
+ 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];
+ }
+ 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(MediaConfig::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('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..12d77f32
--- /dev/null
+++ b/ext/media/theme.php
@@ -0,0 +1,31 @@
+";
+ $html .= "
Image Type | |
";
+ $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/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 = "
Image | Reason | Action |
diff --git a/ext/resize/main.php b/ext/resize/main.php
index 08036260..212890fe 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, MediaEngine::GD);
+ $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, $event->image->lossless)) {
/* 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, $image_obj->lossless)) {
$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,18 +169,33 @@ class ResizeImage extends Extension
}
}
}
-
-
+
+ private function can_resize_format($format, ?bool $lossless = null): bool
+ {
+ global $config;
+ $engine = $config->get_string(ResizeConfig::ENGINE);
+ return Media::is_input_supported($engine, $format, $lossless)
+ && Media::is_output_supported($engine, $format, $lossless);
+ }
+
+
// Private functions
/* ----------------------------- */
private function resize_image(Image $image_obj, int $width, int $height)
{
- global $database;
+ global $database, $config;
if (($height <= 0) && ($width <= 0)) {
throw new ImageResizeException("Invalid options for height and width. ($width x $height)");
}
-
+
+ $engine = $config->get_string(ResizeConfig::ENGINE);
+
+
+ if(!$this->can_resize_format($image_obj->ext, $image_obj->lossless)) {
+ throw new ImageResizeException("Engine $engine cannot resize selected image");
+ }
+
$hash = $image_obj->hash;
$image_filename = warehouse_path(Image::IMAGE_DIR, $hash);
@@ -197,7 +212,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 MediaResizeEvent(
+ $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..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 =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/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 = "Links | Image Only (Backup Server) |
";
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..17fef4ad 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)" => Media::WEBP_LOSSY,
+ "WEBP (lossless)" => Media::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, MediaEngine::GD);
+ $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,10 +78,10 @@ class TranscodeImage extends Extension
{
global $user, $config;
- if ($user->is_admin() && $config->get_bool("resize_enabled")) {
- $engine = $config->get_string("transcode_engine");
- if ($this->can_convert_format($engine, $event->image->ext)) {
- $options = $this->get_supported_output_formats($engine, $event->image->ext);
+ if ($user->is_admin()) {
+ $engine = $config->get_string(TranscodeConfig::ENGINE);
+ 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));
}
}
@@ -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, Media::IMAGE_MEDIA_ENGINES, "Engine", true);
foreach (self::INPUT_FORMATS as $display=>$format) {
- if (in_array($format, self::ENGINE_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);
}
@@ -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 = Media::normalize_format($ext);
- if ($event->type=="gif"&&is_animated_gif($event->tmpname)) {
+ if ($event->type=="gif"&&Media::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(Media::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,38 @@ class TranscodeImage extends Extension
}
}
- private function clean_format($format): ?string
+
+ private function can_convert_format($engine, $format, ?bool $lossless = null): bool
{
- if (array_key_exists($format, self::FORMAT_ALIASES)) {
- return self::FORMAT_ALIASES[$format];
- }
- return $format;
+ return Media::is_input_supported($engine, $format, $lossless);
}
- 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;
- }
- 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 = $this->clean_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 (in_array($value, self::ENGINE_OUTPUT_SUPPORT[$engine])
- &&(empty($omit_format)||$omit_format!=$this->determine_ext($value))) {
+ if(Media::is_output_supported($engine, $value)
+ &&(empty($omit_format)||$omit_format!=$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 +263,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 = Media::determine_ext($target_format);
/* Move the new image into the main storage location */
$target = warehouse_path(Image::IMAGE_DIR, $new_image->hash);
@@ -346,7 +282,7 @@ class TranscodeImage extends Extension
{
global $config;
- if ($source_format==$this->determine_ext($target_format)) {
+ if ($source_format==$target_format) {
throw new ImageTranscodeException("Source and target formats are the same: ".$source_format);
}
@@ -357,7 +293,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, MediaEngine::OUTPUT_SUPPORT[$engine])) {
throw new ImageTranscodeException("Engine $engine does not support output format $target_format");
}
@@ -381,7 +317,8 @@ class TranscodeImage extends Extension
try {
$result = false;
switch ($target_format) {
- case "webp-lossy":
+ case "webp":
+ case Media::WEBP_LOSSY:
$result = imagewebp($image, $tmp_name, $q);
break;
case "png":
@@ -426,20 +363,20 @@ class TranscodeImage extends Extension
global $config;
$q = $config->get_int("transcode_quality");
- $convert = $config->get_string("thumb_convert_path");
+ $convert = $config->get_string(MediaConfig::CONVERT_PATH);
if ($convert==null||$convert=="") {
throw new ImageTranscodeException("ImageMagick path not configured");
}
- $ext = $this->determine_ext($target_format);
+ $ext = Media::determine_ext($target_format);
$args = " -flatten ";
$bg = "none";
switch ($target_format) {
- case "webp-lossless":
+ case Media::WEBP_LOSSLESS:
$args .= '-define webp:lossless=true';
break;
- case "webp-lossy":
+ case Media::WEBP_LOSSY:
$args .= '';
break;
case "png":
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..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,9 +164,68 @@ 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);
}
+
+ 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($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");
+ $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 b3ae0046..a4613467 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,7 +32,13 @@ 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);