2021-12-14 18:32:47 +00:00
< ? php
declare ( strict_types = 1 );
2019-06-18 18:45:59 +00:00
2023-01-10 22:44:09 +00:00
namespace Shimmie2 ;
2024-01-11 00:55:05 +00:00
use Symfony\Component\Console\Command\Command ;
use Symfony\Component\Console\Input\ { InputInterface , InputArgument };
use Symfony\Component\Console\Output\OutputInterface ;
2019-08-16 14:40:42 +00:00
require_once " config.php " ;
require_once " events.php " ;
require_once " media_engine.php " ;
2020-08-28 14:11:14 +00:00
require_once " video_codecs.php " ;
2019-08-16 14:40:42 +00:00
2019-06-18 18:45:59 +00:00
/*
2019-06-24 15:05:16 +00:00
* This is used by the media code when there is an error
2019-06-18 18:45:59 +00:00
*/
2019-06-24 15:05:16 +00:00
class MediaException extends SCoreException
2019-06-18 18:45:59 +00:00
{
}
2024-02-11 15:47:40 +00:00
class InsufficientMemoryException extends ServerError
{
}
2019-06-24 15:05:16 +00:00
class Media extends Extension
2019-06-18 18:45:59 +00:00
{
2020-02-04 00:46:36 +00:00
/** @var MediaTheme */
2023-06-27 14:56:49 +00:00
protected Themelet $theme ;
2020-02-04 00:46:36 +00:00
2020-06-14 16:36:52 +00:00
private const LOSSLESS_FORMATS = [
2020-06-14 16:05:55 +00:00
MimeType :: WEBP_LOSSLESS ,
MimeType :: PNG ,
MimeType :: PSD ,
MimeType :: BMP ,
MimeType :: ICO ,
MimeType :: ANI ,
MimeType :: GIF
2019-06-18 18:45:59 +00:00
];
2020-06-14 16:36:52 +00:00
private const ALPHA_FORMATS = [
2020-06-14 16:05:55 +00:00
MimeType :: WEBP_LOSSLESS ,
MimeType :: WEBP ,
MimeType :: PNG ,
2019-06-18 18:45:59 +00:00
];
2020-06-14 16:36:52 +00:00
public const RESIZE_TYPE_FIT = " Fit " ;
public const RESIZE_TYPE_FIT_BLUR = " Fit Blur " ;
2020-08-28 14:15:45 +00:00
public const RESIZE_TYPE_FIT_BLUR_PORTRAIT = " Fit Blur Tall, Fill Wide " ;
2020-06-14 16:36:52 +00:00
public const RESIZE_TYPE_FILL = " Fill " ;
public const RESIZE_TYPE_STRETCH = " Stretch " ;
public const DEFAULT_ALPHA_CONVERSION_COLOR = " #00000000 " ;
2019-06-18 18:45:59 +00:00
2019-09-29 13:30:55 +00:00
public static function imagick_available () : bool
2019-06-18 18:45:59 +00:00
{
return extension_loaded ( " imagick " );
}
/**
* High priority just so that it can be early in the settings
*/
public function get_priority () : int
{
return 30 ;
}
2024-01-15 11:52:35 +00:00
public function onInitExt ( InitExtEvent $event ) : void
2019-06-18 18:45:59 +00:00
{
global $config ;
2019-06-24 15:05:16 +00:00
$config -> set_default_string ( MediaConfig :: FFPROBE_PATH , 'ffprobe' );
$config -> set_default_int ( MediaConfig :: MEM_LIMIT , parse_shorthand_int ( '8MB' ));
2019-06-25 20:17:13 +00:00
$config -> set_default_string ( MediaConfig :: FFMPEG_PATH , 'ffmpeg' );
2019-06-24 15:05:16 +00:00
$config -> set_default_string ( MediaConfig :: CONVERT_PATH , 'convert' );
2019-06-18 18:45:59 +00:00
}
2024-01-15 11:52:35 +00:00
public function onPageRequest ( PageRequestEvent $event ) : void
2019-06-25 20:17:13 +00:00
{
2019-10-02 10:23:57 +00:00
global $page , $user ;
2019-06-25 20:17:13 +00:00
2024-02-11 11:34:09 +00:00
if ( $event -> page_matches ( " media_rescan/ { image_id} " , method : " POST " , permission : Permissions :: RESCAN_MEDIA )) {
2024-02-20 00:22:25 +00:00
$image = Image :: by_id_ex ( $event -> get_iarg ( 'image_id' ));
2019-06-25 20:17:13 +00:00
2020-01-29 20:22:50 +00:00
send_event ( new MediaCheckPropertiesEvent ( $image ));
$image -> save_to_db ();
2019-06-25 20:17:13 +00:00
$page -> set_mode ( PageMode :: REDIRECT );
$page -> set_redirect ( make_link ( " post/view/ $image->id " ));
}
}
2024-01-15 11:52:35 +00:00
public function onSetupBuilding ( SetupBuildingEvent $event ) : void
2019-06-18 18:45:59 +00:00
{
2023-03-20 16:47:59 +00:00
$sb = $event -> panel -> create_new_block ( " Media Engine Commands " );
2019-06-18 18:45:59 +00:00
2023-05-25 11:57:21 +00:00
// 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("<b style='color:red'>ImageMagick not detected</b>");
// }
// } else {
2019-06-27 04:00:49 +00:00
$sb -> start_table ();
$sb -> add_text_option ( MediaConfig :: CONVERT_PATH , " convert " , true );
2023-05-25 11:57:21 +00:00
// }
2019-06-18 18:45:59 +00:00
2019-11-29 02:20:48 +00:00
$sb -> add_text_option ( MediaConfig :: FFMPEG_PATH , " ffmpeg " , true );
$sb -> add_text_option ( MediaConfig :: FFPROBE_PATH , " ffprobe " , true );
2019-06-18 18:45:59 +00:00
2020-06-22 00:09:04 +00:00
$sb -> add_shorthand_int_option ( MediaConfig :: MEM_LIMIT , " Mem limit " , true );
2019-06-27 04:00:49 +00:00
$sb -> end_table ();
2019-06-18 18:45:59 +00:00
}
2024-01-15 11:52:35 +00:00
public function onImageAdminBlockBuilding ( ImageAdminBlockBuildingEvent $event ) : void
2019-06-25 20:17:13 +00:00
{
global $user ;
2019-07-09 14:10:21 +00:00
if ( $user -> can ( Permissions :: DELETE_IMAGE )) {
2024-02-10 18:35:55 +00:00
$event -> add_button ( " Scan Media Properties " , " media_rescan/ { $event -> image -> id } " );
2019-06-25 20:17:13 +00:00
}
}
2024-01-15 11:52:35 +00:00
public function onBulkActionBlockBuilding ( BulkActionBlockBuildingEvent $event ) : void
2019-06-25 20:17:13 +00:00
{
global $user ;
2019-09-29 18:00:51 +00:00
if ( $user -> can ( Permissions :: RESCAN_MEDIA )) {
2019-06-25 20:17:13 +00:00
$event -> add_action ( " bulk_media_rescan " , " Scan Media Properties " );
}
}
2024-01-15 11:52:35 +00:00
public function onBulkAction ( BulkActionEvent $event ) : void
2019-06-25 20:17:13 +00:00
{
2019-12-15 19:47:18 +00:00
global $page , $user ;
2019-06-25 20:17:13 +00:00
switch ( $event -> action ) {
case " bulk_media_rescan " :
2019-09-29 18:00:51 +00:00
if ( $user -> can ( Permissions :: RESCAN_MEDIA )) {
2019-06-25 20:17:13 +00:00
$total = 0 ;
2019-10-02 09:10:47 +00:00
$failed = 0 ;
2019-07-05 15:36:07 +00:00
foreach ( $event -> items as $image ) {
2019-06-25 20:17:13 +00:00
try {
2020-02-02 17:01:17 +00:00
log_debug ( " media " , " Rescanning media for { $image -> hash } ( { $image -> id } ) " );
2020-01-29 20:22:50 +00:00
send_event ( new MediaCheckPropertiesEvent ( $image ));
2020-02-02 17:01:17 +00:00
$image -> save_to_db ();
2019-06-25 20:17:13 +00:00
$total ++ ;
} catch ( MediaException $e ) {
2019-10-02 09:10:47 +00:00
$failed ++ ;
2019-06-25 20:17:13 +00:00
}
}
2019-12-15 19:47:18 +00:00
$page -> flash ( " Scanned media properties for $total items, failed for $failed " );
2019-06-25 20:17:13 +00:00
}
break ;
}
}
2024-01-15 11:52:35 +00:00
public function onCliGen ( CliGenEvent $event ) : void
2019-10-04 19:50:36 +00:00
{
2024-01-11 00:55:05 +00:00
$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 ;
});
2019-10-04 19:50:36 +00:00
}
2019-06-18 18:45:59 +00:00
/**
2019-06-24 15:05:16 +00:00
* @ param MediaResizeEvent $event
2019-06-18 18:45:59 +00:00
*/
2024-01-15 11:52:35 +00:00
public function onMediaResize ( MediaResizeEvent $event ) : void
2019-06-18 18:45:59 +00:00
{
2020-06-14 16:05:55 +00:00
if ( ! in_array (
$event -> resize_type ,
MediaEngine :: RESIZE_TYPE_SUPPORT [ $event -> engine ]
)) {
2020-06-11 21:58:19 +00:00
throw new MediaException ( " Resize type $event->resize_type not supported by selected media engine $event->engine " );
}
2019-06-18 18:45:59 +00:00
switch ( $event -> engine ) {
2019-06-24 15:05:16 +00:00
case MediaEngine :: GD :
2019-06-18 18:45:59 +00:00
$info = getimagesize ( $event -> input_path );
if ( $info === false ) {
2019-06-24 15:05:16 +00:00
throw new MediaException ( " getimagesize failed for " . $event -> input_path );
2019-06-18 18:45:59 +00:00
}
self :: image_resize_gd (
$event -> input_path ,
$info ,
$event -> target_width ,
$event -> target_height ,
$event -> output_path ,
2020-06-14 16:05:55 +00:00
$event -> target_mime ,
2020-06-14 16:36:52 +00:00
$event -> alpha_color ,
2020-06-11 21:58:19 +00:00
$event -> resize_type ,
2019-06-18 18:45:59 +00:00
$event -> target_quality ,
2019-09-29 13:30:55 +00:00
$event -> allow_upscale
);
2019-06-18 18:45:59 +00:00
break ;
2019-06-24 15:05:16 +00:00
case MediaEngine :: IMAGICK :
2023-05-25 11:57:21 +00:00
// if (self::imagick_available()) {
// } else {
2019-06-18 18:55:18 +00:00
self :: image_resize_convert (
$event -> input_path ,
2020-06-14 16:05:55 +00:00
$event -> input_mime ,
2019-06-18 18:55:18 +00:00
$event -> target_width ,
$event -> target_height ,
$event -> output_path ,
2020-06-14 16:05:55 +00:00
$event -> target_mime ,
2020-06-14 16:36:52 +00:00
$event -> alpha_color ,
2020-06-11 21:58:19 +00:00
$event -> resize_type ,
2019-06-18 18:55:18 +00:00
$event -> target_quality ,
$event -> minimize ,
2019-09-29 13:30:55 +00:00
$event -> allow_upscale
);
2019-06-18 18:45:59 +00:00
//}
break ;
2020-01-28 22:23:03 +00:00
case MediaEngine :: STATIC :
copy ( $event -> input_path , $event -> output_path );
break ;
2019-06-18 18:45:59 +00:00
default :
2019-06-24 15:05:16 +00:00
throw new MediaException ( " Engine not supported for resize: " . $event -> engine );
2019-06-18 18:45:59 +00:00
}
// TODO: Get output optimization tools working better
2023-05-25 11:57:21 +00:00
// if ($config->get_bool("thumb_optim", false)) {
// exec("jpegoptim $outname", $output, $ret);
// }
2019-06-18 18:45:59 +00:00
}
2024-01-15 11:52:35 +00:00
public function onSearchTermParse ( SearchTermParseEvent $event ) : void
2019-06-24 15:05:16 +00:00
{
2020-01-26 16:38:26 +00:00
if ( is_null ( $event -> term )) {
return ;
}
2020-01-26 13:19:35 +00:00
2019-06-25 20:17:13 +00:00
$matches = [];
2024-02-21 21:31:25 +00:00
if ( preg_match ( " /^content[=|:]((video)|(audio)|(image)|(unknown)) $ /i " , $event -> term , $matches )) {
2019-08-16 14:40:42 +00:00
$field = $matches [ 1 ];
2023-11-11 21:49:12 +00:00
if ( $field === " unknown " ) {
2020-02-01 22:44:50 +00:00
$event -> add_querylet ( new Querylet ( " video IS NULL OR audio IS NULL OR image IS NULL " ));
2019-08-16 14:40:42 +00:00
} else {
2023-11-11 21:49:12 +00:00
$event -> add_querylet ( new Querylet ( " $field = :true " , [ " true " => true ]));
2019-08-16 14:40:42 +00:00
}
2024-02-21 21:31:25 +00:00
} elseif ( preg_match ( " /^ratio([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])( \ d+):( \ d+) $ /i " , $event -> term , $matches )) {
2024-08-31 18:53:07 +00:00
$cmp = preg_replace_ex ( '/^:/' , '=' , $matches [ 1 ]);
2024-02-21 21:31:25 +00:00
$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 ]));
2019-06-25 20:17:13 +00:00
}
}
2024-01-15 11:52:35 +00:00
public function onHelpPageBuilding ( HelpPageBuildingEvent $event ) : void
2019-08-02 20:05:49 +00:00
{
2023-11-11 21:49:12 +00:00
if ( $event -> key === HelpPages :: SEARCH ) {
2019-08-02 20:05:49 +00:00
$block = new Block ();
$block -> header = " Media " ;
$block -> body = $this -> theme -> get_help_html ();
$event -> add_block ( $block );
}
}
2024-01-15 11:52:35 +00:00
public function onParseLinkTemplate ( ParseLinkTemplateEvent $event ) : void
2020-02-09 19:22:25 +00:00
{
if ( $event -> image -> width && $event -> image -> height && $event -> image -> length ) {
2023-11-11 21:49:12 +00:00
$s = (( int )( $event -> image -> length / 100 )) / 10 ;
2023-02-19 11:24:33 +00:00
$event -> replace ( '$size' , " { $event -> image -> width } x { $event -> image -> height } , { $s } s " );
2020-02-09 19:22:25 +00:00
} elseif ( $event -> image -> width && $event -> image -> height ) {
$event -> replace ( '$size' , " { $event -> image -> width } x { $event -> image -> height } " );
} elseif ( $event -> image -> length ) {
2023-11-11 21:49:12 +00:00
$s = (( int )( $event -> image -> length / 100 )) / 10 ;
2023-02-19 11:24:33 +00:00
$event -> replace ( '$size' , " { $s } s " );
2023-12-30 13:02:31 +00:00
} else {
$event -> replace ( '$size' , " unknown size " );
2020-02-09 19:22:25 +00:00
}
}
2019-06-18 18:45:59 +00:00
/**
* 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 .
2020-03-25 11:47:00 +00:00
* https :// stackoverflow . com / questions / 527532 / reasonable - php - memory - limit - for - image - resize
2019-06-18 18:45:59 +00:00
*
2024-01-20 14:10:59 +00:00
* @ param array { 0 : int , 1 : int , 2 : int , bits ? : int , channels ? : int } $info The output of getimagesize () for the source file in question .
2019-06-18 18:45:59 +00:00
* @ return int The number of bytes an image resize operation is estimated to use .
*/
2019-06-21 20:23:59 +00:00
public static function calc_memory_use ( array $info ) : int
2019-06-18 18:45:59 +00:00
{
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 .
2019-06-24 15:05:16 +00:00
* @ throws MediaException
2019-06-18 18:45:59 +00:00
*/
2024-01-09 04:49:19 +00:00
public static function create_thumbnail_ffmpeg ( Image $image ) : bool
2019-06-18 18:45:59 +00:00
{
global $config ;
2019-06-24 15:05:16 +00:00
$ffmpeg = $config -> get_string ( MediaConfig :: FFMPEG_PATH );
2020-10-29 01:28:46 +00:00
if ( empty ( $ffmpeg )) {
throw new MediaException ( " ffmpeg command not configured " );
2019-06-18 18:45:59 +00:00
}
2020-10-29 01:28:46 +00:00
$ok = false ;
2024-01-09 04:49:19 +00:00
$inname = $image -> get_image_filename ();
2024-01-20 20:48:47 +00:00
$tmpname = shm_tempnam ( " ffmpeg_thumb " );
2020-08-28 14:12:02 +00:00
try {
2024-01-09 04:49:19 +00:00
$outname = $image -> get_thumb_filename ();
2019-06-18 18:45:59 +00:00
2020-08-28 14:12:02 +00:00
$orig_size = self :: video_size ( $inname );
$scaled_size = get_thumbnail_size ( $orig_size [ 0 ], $orig_size [ 1 ], true );
2019-06-18 18:45:59 +00:00
2020-08-28 14:12:02 +00:00
$args = [
escapeshellarg ( $ffmpeg ),
" -y " , " -i " , escapeshellarg ( $inname ),
2022-03-27 01:18:11 +00:00
" -vf " , " scale= $scaled_size[0] : $scaled_size[1] ,thumbnail " ,
2020-08-28 14:12:02 +00:00
" -f " , " image2 " ,
" -vframes " , " 1 " ,
" -c:v " , " png " ,
escapeshellarg ( $tmpname ),
];
2019-06-18 18:45:59 +00:00
2020-08-28 14:12:02 +00:00
$cmd = escapeshellcmd ( implode ( " " , $args ));
2019-06-18 18:45:59 +00:00
2022-03-27 01:18:11 +00:00
log_debug ( 'media' , " Generating thumbnail with command ` $cmd `... " );
2020-08-28 14:12:02 +00:00
exec ( $cmd , $output , $ret );
2019-06-18 18:45:59 +00:00
2020-08-28 14:12:02 +00:00
if (( int ) $ret === ( int ) 0 ) {
log_debug ( 'media' , " Generating thumbnail with command ` $cmd `, returns $ret " );
create_scaled_image ( $tmpname , $outname , $scaled_size , MimeType :: PNG );
2020-10-29 01:28:46 +00:00
$ok = true ;
2020-08-28 14:12:02 +00:00
} else {
log_error ( 'media' , " Generating thumbnail with command ` $cmd `, returns $ret " );
}
} finally {
@ unlink ( $tmpname );
2019-06-18 18:45:59 +00:00
}
2020-10-29 01:28:46 +00:00
return $ok ;
2019-06-18 18:45:59 +00:00
}
2024-01-20 14:10:59 +00:00
/**
* @ return array < string , mixed >
*/
public static function get_ffprobe_data ( string $filename ) : array
2019-06-25 20:17:13 +00:00
{
global $config ;
$ffprobe = $config -> get_string ( MediaConfig :: FFPROBE_PATH );
2023-06-27 16:45:35 +00:00
if ( empty ( $ffprobe )) {
throw new MediaException ( " ffprobe command not configured " );
2019-06-25 20:17:13 +00:00
}
$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 ) {
2020-02-02 17:01:17 +00:00
log_debug ( 'media' , " Getting media data ` $cmd `, returns $ret " );
2019-06-25 20:17:13 +00:00
$output = implode ( $output );
2020-01-26 13:19:35 +00:00
return json_decode ( $output , true );
2019-06-25 20:17:13 +00:00
} else {
2020-02-02 17:01:17 +00:00
log_error ( 'media' , " Getting media data ` $cmd `, returns $ret " );
2019-06-25 20:17:13 +00:00
return [];
}
}
2020-06-14 16:05:55 +00:00
public static function determine_ext ( string $mime ) : string
2019-06-18 18:45:59 +00:00
{
2020-06-14 16:05:55 +00:00
$ext = FileExtension :: get_for_mime ( $mime );
if ( empty ( $ext )) {
2024-02-11 15:47:40 +00:00
throw new ServerError ( " Could not determine extension for $mime " );
2019-06-18 18:45:59 +00:00
}
2020-06-14 16:05:55 +00:00
return $ext ;
2019-06-18 18:45:59 +00:00
}
2024-01-20 14:10:59 +00:00
// private static function image_save_imagick(Imagick $image, string $path, string $format, int $output_quality = 80, bool $minimize): void
2023-11-11 21:49:12 +00:00
// {
// 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);
// }
// }
2019-06-18 18:45:59 +00:00
2021-03-14 23:43:50 +00:00
public static function is_lossless ( string $filename , string $mime ) : bool
2019-09-29 13:30:55 +00:00
{
2020-06-14 16:05:55 +00:00
if ( in_array ( $mime , self :: LOSSLESS_FORMATS )) {
2019-06-25 23:43:57 +00:00
return true ;
}
2020-06-14 16:05:55 +00:00
switch ( $mime ) {
case MimeType :: WEBP :
return MimeType :: is_lossless_webp ( $filename );
2019-06-25 23:43:57 +00:00
}
return false ;
}
2019-06-18 18:45:59 +00:00
public static function image_resize_convert (
2020-01-26 23:12:48 +00:00
string $input_path ,
2020-06-14 16:05:55 +00:00
string $input_mime ,
2019-06-18 18:45:59 +00:00
int $new_width ,
int $new_height ,
string $output_filename ,
2024-08-31 23:04:34 +00:00
? string $output_mime = null ,
2020-06-14 16:36:52 +00:00
string $alpha_color = Media :: DEFAULT_ALPHA_CONVERSION_COLOR ,
2020-06-11 21:58:19 +00:00
string $resize_type = self :: RESIZE_TYPE_FIT ,
2019-06-18 18:45:59 +00:00
int $output_quality = 80 ,
bool $minimize = false ,
bool $allow_upscale = true
2019-09-29 13:30:55 +00:00
) : void {
2019-06-18 18:45:59 +00:00
global $config ;
2019-06-24 15:05:16 +00:00
$convert = $config -> get_string ( MediaConfig :: CONVERT_PATH );
2019-06-18 18:45:59 +00:00
2019-06-21 20:23:59 +00:00
if ( empty ( $convert )) {
2019-06-24 15:05:16 +00:00
throw new MediaException ( " convert command not configured " );
2019-06-18 18:45:59 +00:00
}
2020-06-14 16:05:55 +00:00
if ( empty ( $output_mime )) {
$output_mime = $input_mime ;
2019-06-18 18:45:59 +00:00
}
2023-11-11 21:49:12 +00:00
if ( $output_mime == MimeType :: WEBP && self :: is_lossless ( $input_path , $input_mime )) {
2020-06-14 16:05:55 +00:00
$output_mime = MimeType :: WEBP_LOSSLESS ;
2019-06-25 23:43:57 +00:00
}
2020-06-14 16:36:52 +00:00
$bg = " \" $alpha_color\ " " ;
2020-06-14 16:05:55 +00:00
if ( self :: supports_alpha ( $output_mime )) {
2019-06-18 18:45:59 +00:00
$bg = " none " ;
}
2019-06-25 23:43:57 +00:00
2020-06-11 21:58:19 +00:00
$resize_suffix = " " ;
2019-06-18 18:45:59 +00:00
if ( ! $allow_upscale ) {
2020-06-11 21:58:19 +00:00
$resize_suffix .= " \ > " ;
2019-06-18 18:45:59 +00:00
}
2023-11-11 21:49:12 +00:00
if ( $resize_type == Media :: RESIZE_TYPE_STRETCH ) {
2020-06-11 21:58:19 +00:00
$resize_suffix .= " \ ! " ;
2019-06-18 18:45:59 +00:00
}
2020-10-08 22:08:22 +00:00
$args = " -auto-orient " ;
2020-06-11 21:58:19 +00:00
$resize_arg = " -resize " ;
if ( $minimize ) {
$args .= " -strip " ;
$resize_arg = " -thumbnail " ;
}
2020-06-14 16:05:55 +00:00
$input_ext = self :: determine_ext ( $input_mime );
2023-02-19 11:24:33 +00:00
$file_arg = " { $input_ext } : \" { $input_path } [0] \" " ;
2020-06-11 21:58:19 +00:00
2023-11-11 21:49:12 +00:00
if ( $resize_type === Media :: RESIZE_TYPE_FIT_BLUR_PORTRAIT ) {
if ( $new_height > $new_width ) {
2020-08-28 14:15:45 +00:00
$resize_type = Media :: RESIZE_TYPE_FIT_BLUR ;
} else {
$resize_type = Media :: RESIZE_TYPE_FILL ;
}
}
2020-06-11 21:58:19 +00:00
switch ( $resize_type ) {
case Media :: RESIZE_TYPE_FIT :
case Media :: RESIZE_TYPE_STRETCH :
2023-02-19 11:24:33 +00:00
$args .= " { $file_arg } { $resize_arg } { $new_width } x { $new_height } { $resize_suffix } -background { $bg } -flatten " ;
2020-06-11 21:58:19 +00:00
break ;
case Media :: RESIZE_TYPE_FILL :
2023-02-19 11:24:33 +00:00
$args .= " { $file_arg } { $resize_arg } { $new_width } x { $new_height } \ ^ -background { $bg } -flatten -gravity center -extent { $new_width } x { $new_height } " ;
2020-06-11 21:58:19 +00:00
break ;
case Media :: RESIZE_TYPE_FIT_BLUR :
$blur_size = max ( ceil ( max ( $new_width , $new_height ) / 25 ), 5 );
2023-02-19 11:24:33 +00:00
$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 } \ ) " .
2020-06-11 21:58:19 +00:00
" -delete 0 -gravity center -compose over -composite " ;
break ;
}
2020-06-14 16:05:55 +00:00
switch ( $output_mime ) {
case MimeType :: WEBP_LOSSLESS :
$args .= ' -define webp:lossless=true' ;
2019-06-25 23:43:57 +00:00
break ;
2020-06-14 16:05:55 +00:00
case MimeType :: PNG :
$args .= ' -define png:compression-level=9' ;
2019-06-25 23:43:57 +00:00
break ;
}
2020-06-11 21:58:19 +00:00
2023-02-19 11:24:33 +00:00
$args .= " -quality { $output_quality } " ;
2019-07-05 15:36:07 +00:00
2020-06-14 16:05:55 +00:00
$output_ext = self :: determine_ext ( $output_mime );
2019-06-25 23:43:57 +00:00
2020-06-11 21:58:19 +00:00
$format = '"%s" %s %s:"%s" 2>&1' ;
$cmd = sprintf ( $format , $convert , $args , $output_ext , $output_filename );
2019-06-18 18:45:59 +00:00
$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 ) {
2019-06-24 15:05:16 +00:00
throw new MediaException ( " Resizing image with command ` $cmd `, returns $ret , outputting " . implode ( " \r \n " , $output ));
2019-06-18 18:45:59 +00:00
} else {
2020-02-02 17:01:17 +00:00
log_debug ( 'media' , " Generating thumbnail with command ` $cmd `, returns $ret " );
2019-06-18 18:45:59 +00:00
}
}
/**
* Performs a resize operation on an image file using GD .
*
2023-08-17 17:12:36 +00:00
* @ param string $image_filename The source file to be resized .
2024-01-20 14:10:59 +00:00
* @ param array { 0 : int , 1 : int , 2 : int } $info The output of getimagesize () for the source file .
2019-06-18 18:45:59 +00:00
* @ param int $new_width
* @ param int $new_height
* @ param string $output_filename
2021-09-25 12:40:41 +00:00
* @ 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 .
2019-06-18 18:45:59 +00:00
* @ param int $output_quality Defaults to 80.
*/
public static function image_resize_gd (
2020-01-26 23:12:48 +00:00
string $image_filename ,
2019-06-18 18:45:59 +00:00
array $info ,
int $new_width ,
int $new_height ,
string $output_filename ,
2021-09-25 12:40:41 +00:00
? string $output_mime = null ,
2020-06-14 16:36:52 +00:00
string $alpha_color = Media :: DEFAULT_ALPHA_CONVERSION_COLOR ,
2020-06-11 21:58:19 +00:00
string $resize_type = self :: RESIZE_TYPE_FIT ,
2019-06-18 18:45:59 +00:00
int $output_quality = 80 ,
bool $allow_upscale = true
2024-01-20 14:10:59 +00:00
) : void {
2019-06-18 18:45:59 +00:00
$width = $info [ 0 ];
$height = $info [ 1 ];
2023-06-27 16:45:35 +00:00
if ( $output_mime === null ) {
2019-06-18 18:45:59 +00:00
/* If not specified, output to the same format as the original image */
switch ( $info [ 2 ]) {
case IMAGETYPE_GIF :
2020-06-14 16:05:55 +00:00
$output_mime = MimeType :: GIF ;
2019-06-18 18:45:59 +00:00
break ;
case IMAGETYPE_JPEG :
2020-06-14 16:05:55 +00:00
$output_mime = MimeType :: JPEG ;
2019-06-18 18:45:59 +00:00
break ;
case IMAGETYPE_PNG :
2020-06-14 16:05:55 +00:00
$output_mime = MimeType :: PNG ;
2019-06-18 18:45:59 +00:00
break ;
case IMAGETYPE_WEBP :
2020-06-14 16:05:55 +00:00
$output_mime = MimeType :: WEBP ;
2019-06-18 18:45:59 +00:00
break ;
case IMAGETYPE_BMP :
2020-06-14 16:05:55 +00:00
$output_mime = MimeType :: BMP ;
2019-06-18 18:45:59 +00:00
break ;
default :
2020-06-14 16:05:55 +00:00
throw new MediaException ( " Failed to save the new image - Unsupported MIME type. " );
2019-06-18 18:45:59 +00:00
}
}
$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 ) " );
}
2023-11-11 21:49:12 +00:00
if ( $resize_type == Media :: RESIZE_TYPE_FIT ) {
2019-06-18 18:45:59 +00:00
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 ;
}
2024-02-20 00:22:25 +00:00
$image = imagecreatefromstring ( \Safe\file_get_contents ( $image_filename ));
2024-01-20 20:48:47 +00:00
if ( $image === false ) {
throw new MediaException ( " Could not load image: " . $image_filename );
}
2019-06-18 18:45:59 +00:00
$image_resized = imagecreatetruecolor ( $new_width , $new_height );
2024-01-20 20:48:47 +00:00
if ( $image_resized === false ) {
throw new MediaException ( " Could not create output image with dimensions $new_width x $new_height " );
}
2019-06-18 18:45:59 +00:00
2024-01-20 20:48:47 +00:00
try {
2019-06-18 18:45:59 +00:00
// 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 ) {
2019-06-24 15:05:16 +00:00
throw new MediaException ( " Unable to allocate transparent color " );
2019-06-18 18:45:59 +00:00
}
// Completely fill the background of the new image with allocated color.
if ( imagefill ( $image_resized , 0 , 0 , $transparency ) === false ) {
2019-06-24 15:05:16 +00:00
throw new MediaException ( " Unable to fill new image with transparent color " );
2019-06-18 18:45:59 +00:00
}
// Set the background color for new image to transparent
imagecolortransparent ( $image_resized , $transparency );
}
break ;
case IMAGETYPE_PNG :
case IMAGETYPE_WEBP :
//
2020-03-25 11:47:00 +00:00
// More info here: https://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php
2019-06-18 18:45:59 +00:00
//
if ( imagealphablending ( $image_resized , false ) === false ) {
2019-06-24 15:05:16 +00:00
throw new MediaException ( " Unable to disable image alpha blending " );
2019-06-18 18:45:59 +00:00
}
if ( imagesavealpha ( $image_resized , true ) === false ) {
2019-06-24 15:05:16 +00:00
throw new MediaException ( " Unable to enable image save alpha " );
2019-06-18 18:45:59 +00:00
}
$transparent_color = imagecolorallocatealpha ( $image_resized , 255 , 255 , 255 , 127 );
if ( $transparent_color === false ) {
2019-06-24 15:05:16 +00:00
throw new MediaException ( " Unable to allocate transparent color " );
2019-06-18 18:45:59 +00:00
}
if ( imagefilledrectangle ( $image_resized , 0 , 0 , $new_width , $new_height , $transparent_color ) === false ) {
2019-06-24 15:05:16 +00:00
throw new MediaException ( " Unable to fill new image with transparent color " );
2019-06-18 18:45:59 +00:00
}
break ;
}
// Actually resize the image.
if ( imagecopyresampled (
2019-09-29 13:30:55 +00:00
$image_resized ,
$image ,
0 ,
0 ,
0 ,
0 ,
$new_width ,
$new_height ,
$width ,
$height
2019-11-02 19:57:34 +00:00
) === false ) {
2019-06-24 15:05:16 +00:00
throw new MediaException ( " Unable to copy resized image data to new image " );
2019-06-18 18:45:59 +00:00
}
2020-06-14 16:36:52 +00:00
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 );
2023-11-11 21:49:12 +00:00
if ( $new_image === false ) {
2020-06-14 16:36:52 +00:00
throw new ImageTranscodeException ( " Could not create image with dimensions $width x $height " );
}
$background_color = Media :: hex_color_allocate ( $new_image , $alpha_color );
2023-11-11 21:49:12 +00:00
if ( imagefilledrectangle ( $new_image , 0 , 0 , $width , $height , $background_color ) === false ) {
2020-06-14 16:36:52 +00:00
throw new ImageTranscodeException ( " Could not fill background color " );
}
2023-11-11 21:49:12 +00:00
if ( imagecopy ( $new_image , $image_resized , 0 , 0 , 0 , 0 , $width , $height ) === false ) {
2020-06-14 16:36:52 +00:00
throw new ImageTranscodeException ( " Could not copy source image to new image " );
}
imagedestroy ( $image_resized );
$image_resized = $new_image ;
2022-10-27 16:21:46 +00:00
break ;
2020-06-14 16:36:52 +00:00
}
2020-06-14 16:05:55 +00:00
switch ( $output_mime ) {
case MimeType :: BMP :
2019-06-18 18:45:59 +00:00
$result = imagebmp ( $image_resized , $output_filename , true );
break ;
2020-06-14 16:05:55 +00:00
case MimeType :: WEBP :
2019-06-18 18:45:59 +00:00
$result = imagewebp ( $image_resized , $output_filename , $output_quality );
break ;
2020-06-14 16:05:55 +00:00
case MimeType :: JPEG :
2019-06-18 18:45:59 +00:00
$result = imagejpeg ( $image_resized , $output_filename , $output_quality );
break ;
2020-06-14 16:05:55 +00:00
case MimeType :: PNG :
2019-06-18 18:45:59 +00:00
$result = imagepng ( $image_resized , $output_filename , 9 );
break ;
2020-06-14 16:05:55 +00:00
case MimeType :: GIF :
2019-06-18 18:45:59 +00:00
$result = imagegif ( $image_resized , $output_filename );
break ;
default :
2020-06-14 16:05:55 +00:00
throw new MediaException ( " Failed to save the new image - Unsupported image type: $output_mime " );
2019-06-18 18:45:59 +00:00
}
if ( $result === false ) {
2020-06-14 16:05:55 +00:00
throw new MediaException ( " Failed to save the new image, function returned false when saving type: $output_mime " );
2019-06-18 18:45:59 +00:00
}
} finally {
@ imagedestroy ( $image );
@ imagedestroy ( $image_resized );
}
}
2019-06-24 15:05:16 +00:00
2020-06-14 16:05:55 +00:00
public static function supports_alpha ( string $mime ) : bool
2019-06-24 15:05:16 +00:00
{
2020-06-14 16:05:55 +00:00
return MimeType :: matches_array ( $mime , self :: ALPHA_FORMATS , true );
2019-06-18 18:45:59 +00:00
}
/**
* Determines the dimensions of a video file using ffmpeg .
*
* @ param string $filename
2024-01-20 14:10:59 +00:00
* @ return array { 0 : int , 1 : int }
2019-06-18 18:45:59 +00:00
*/
2019-09-29 13:30:55 +00:00
public static function video_size ( string $filename ) : array
2019-06-18 18:45:59 +00:00
{
global $config ;
2019-06-24 15:05:16 +00:00
$ffmpeg = $config -> get_string ( MediaConfig :: FFMPEG_PATH );
2019-06-18 18:45:59 +00:00
$cmd = escapeshellcmd ( implode ( " " , [
escapeshellarg ( $ffmpeg ),
" -y " , " -i " , escapeshellarg ( $filename ),
" -vstats "
]));
2024-02-20 00:22:25 +00:00
// \Safe\shell_exec is a little broken
// https://github.com/thecodingmachine/safe/issues/281
$output = shell_exec ( $cmd . " 2>&1 " );
2024-08-31 16:05:18 +00:00
if ( is_null ( $output ) || $output === false ) {
2024-02-20 00:22:25 +00:00
throw new MediaException ( " Failed to execute command: $cmd " );
}
2019-06-18 18:45:59 +00:00
// 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 )) {
2020-01-26 21:14:50 +00:00
$size = [( int ) $regs [ 2 ], ( int ) $regs [ 1 ]];
2019-06-18 18:45:59 +00:00
} else {
2020-01-26 21:14:50 +00:00
$size = [( int ) $regs [ 1 ], ( int ) $regs [ 2 ]];
2019-06-18 18:45:59 +00:00
}
} else {
$size = [ 1 , 1 ];
}
2020-02-02 17:01:17 +00:00
log_debug ( 'media' , " Getting video size with ` $cmd `, returns $output -- $size[0] , $size[1] " );
2019-06-18 18:45:59 +00:00
return $size ;
}
2019-08-16 14:40:42 +00:00
2024-01-15 11:52:35 +00:00
public function onDatabaseUpgrade ( DatabaseUpgradeEvent $event ) : void
2019-08-16 14:40:42 +00:00
{
global $config , $database ;
2019-11-03 19:04:57 +00:00
if ( $this -> get_version ( MediaConfig :: VERSION ) < 1 ) {
2019-08-16 14:40:42 +00:00
$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 );
}
2019-11-03 19:04:57 +00:00
$this -> set_version ( MediaConfig :: VERSION , 1 );
2019-08-16 14:40:42 +00:00
}
2019-11-03 19:04:57 +00:00
if ( $this -> get_version ( MediaConfig :: VERSION ) < 2 ) {
2020-10-27 00:34:28 +00:00
$database -> execute ( " ALTER TABLE images ADD COLUMN image BOOLEAN NULL " );
2019-08-16 14:40:42 +00:00
2022-10-28 00:45:35 +00:00
switch ( $database -> get_driver_id ()) {
case DatabaseDriverID :: PGSQL :
case DatabaseDriverID :: SQLITE :
2019-08-16 14:40:42 +00:00
$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 ;
}
2019-11-03 19:04:57 +00:00
$this -> set_version ( MediaConfig :: VERSION , 2 );
2019-08-16 14:40:42 +00:00
}
2020-08-17 22:05:51 +00:00
if ( $this -> get_version ( MediaConfig :: VERSION ) < 3 ) {
2020-10-27 00:11:49 +00:00
$database -> execute ( " ALTER TABLE images ADD COLUMN video_codec varchar(512) NULL " );
2020-08-17 22:05:51 +00:00
$this -> set_version ( MediaConfig :: VERSION , 3 );
}
2020-10-27 00:34:28 +00:00
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 ) {
2023-11-11 21:49:12 +00:00
$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 ]);
2020-10-27 00:34:28 +00:00
$this -> set_version ( MediaConfig :: VERSION , 5 );
}
2019-08-16 14:40:42 +00:00
}
2020-06-14 16:36:52 +00:00
2024-01-20 14:10:59 +00:00
public static function hex_color_allocate ( mixed $im , string $hex ) : int
2020-06-14 16:36:52 +00:00
{
$hex = ltrim ( $hex , '#' );
2024-01-20 20:48:47 +00:00
$a = ( int ) hexdec ( substr ( $hex , 0 , 2 ));
$b = ( int ) hexdec ( substr ( $hex , 2 , 2 ));
$c = ( int ) hexdec ( substr ( $hex , 4 , 2 ));
2024-01-20 14:10:59 +00:00
$col = imagecolorallocate ( $im , $a , $b , $c );
assert ( $col !== false );
return $col ;
2020-06-14 16:36:52 +00:00
}
2019-06-18 18:45:59 +00:00
}