This page lists files in the current directory. You can view content, get download/execute commands for Wget, Curl, or PowerShell, or filter the list using wildcards (e.g., `*.sh`).
wget 'https://sme10.lists2.roe3.org/kodbox/plugins/fileThumb/app.php'
<?php
class fileThumbPlugin extends PluginBase{
private $imgExts;
private $webExts;
private $ioFileInfo;
function __construct(){
parent::__construct();
$this->imgExts = array('gif','png','bmp','jpe','jpeg','jpg','webp','heic','heif','avif');
$this->webExts = array('png','jpg','jpeg','bmp','webp','gif','avif');
}
public function regist(){
$this->hookRegist(array(
'user.commonJs.insert' => 'fileThumbPlugin.echoJs',
'explorer.list.path.parse' => 'fileThumbPlugin.listParse',
'explorer.list.itemParse' => 'fileThumbPlugin.itemParse',
));
}
public function echoJs(){
$this->echoFile('static/main.js');
}
public function onUpdate(){
AutoTask::restart();sleep(3);
AutoTask::start();
}
/**
* 优化版,列表统一集中处理; 拉取列表时加入队列处理(同时加入预览大图转换); 1
* 总大概性能: 1000图片/s; checkCoverExists==>1000条约300ms; 缩略图处理==>1000条约200ms;Cache::get(); 10000次/s;
*/
public function listParse($data){
$current = is_array($data['current']) ? $data['current']:array();
if(!$data || !is_array($data['fileList']) || !count($data['fileList'])){return $data;}
if(!$this->getConvert() || !$this->getFFmpeg()){return $data;}
if($current && $current['targetType'] == 'system' && strstr($current['pathDisplay'],'plugin/fileThumb/')){return $data;}
$config = $this->getConfig('fileThumb');
$supportWeb = $this->webExts;
$supportThumb = explode(',',$config['fileThumb'].','.implode(',',$supportWeb));
$supportView = explode(',',$config['fileExt'].','.implode(',',$supportWeb));
$cachePath = false;$timeStart = timeFloat();
// 遍历文件列表,筛选出需要加入缩略图及显示图的内容(支持预览的,加入fileShowView)
$coverList = array();
foreach($data['fileList'] as &$file){
if(!$file['ext'] || $file['size'] <= 100){continue;}
if(!in_array($file['ext'],$supportThumb)){continue;}
if(!kodIO::allowCover($file)){continue;}
if(timeFloat() - $timeStart >= 10){break;} // 内容太多,超出时间则不再处理;
// if(in_array($file['ext'],$supportWeb) && $file['size'] <= 1024*50){continue;}
if(!$cachePath){$cachePath = $this->pluginCachePath();}
$fileHash = KodIO::hashPath($file);$path = $file['path'];
$coverList[$path] = array('fileThumb'=>array('cover'=>"cover_".$fileHash."_250.png",'width'=>250));
if(in_array($file['ext'],$supportView)){
$coverList[$path]['fileShowView'] = array('cover'=>"cover_".$fileHash."_1200.png",'width'=>1200);
}
};unset($file);
if(!$cachePath || !$coverList){return $data;};//return $data;
// 处理需要加入缩略图的文件; 查询数据库已存在的缓存图片;查询redis缓存(已处理,队列中,出错了:不处理)-->未在队列--加入队列;
$needMake = 0;$makeIndex = 0; // 待生成缩略图数量,不包含大图;
$coverList = $this->checkCoverExists($cachePath,$coverList,$needMake);
$makeCoverNow = $needMake <= 3 ? true:false; // 待生成列表小于5,则缩略图调用立即生成;
$obj = Action('user.index');
foreach($data['fileList'] as &$file){
if(!isset($coverList[$file['path']])){continue;}
if(timeFloat() - $timeStart >= 10){break;} // 内容太多,超出时间则不再处理;
$path = $file['path'];
foreach($coverList[$path] as $thumbKey=>$item){
$coverName = $item['cover'];// 少量没有缩略图的内容,立即生成处理;
$param = array('path'=>$path,'etag'=>$file['modifyTime'],'width'=>$item['width']);
$imageSrc = $obj->apiSignMake('plugin/fileThumb/cover',$param);
if(isset($item['sourceID']) && $item['sourceID']){$file[$thumbKey] = $imageSrc;continue;}
if($thumbKey == 'fileShowView'){$file[$thumbKey] = $imageSrc;}// 预览大图,不检测是否有缓存
if($thumbKey == 'fileThumb' && $makeCoverNow){$file[$thumbKey] = $imageSrc;continue;}
// 多张图片待生成缩略图时,第一张处理为触发调用后台任务队列;
if($thumbKey == 'fileThumb'){$makeIndex++;}
if($thumbKey == 'fileThumb' && $makeIndex <= 2){
$file[$thumbKey] = APP_HOST.'index.php?user/view/call&_t='.rand_string(5);
}
$cacheType = Cache::get($coverName);
if($cacheType == 'no' || $cacheType == 'queue'){continue;}
Cache::set($coverName,'queue',1200);
$args = array($cachePath,$path,$coverName,$item['width']);
$desc = '[fileThumb.coverMake]:size='.$file['size'].';cover='.$coverName.';name='.$file['name'].';path='.$file['path'];
TaskQueue::add('fileThumbPlugin.coverMake',$args,$desc,$coverName);
}
// jpg等图片,有该字段时,前端不自动获取图片缩略图,统一通过此插件转换;
if(in_array($file['ext'],$supportView) && !isset($file['fileThumb'])){$file['fileThumbDisable'] = 1;}
};unset($file);
// trace_log([$needMake,$data,$coverList]);
return $data;
}
// 批量查询缓存图片是否存在;
private function checkCoverExists($cachePath,$coverList,&$needMake){
if(!$cachePath || !count($coverList)){return $coverList;}
$sourceID = kodIO::sourceID($cachePath);
$coverArr = array();
foreach($coverList as $items){
foreach($items as $item){$coverArr[] = $item['cover'];}
}
$where = array('parentID'=>$sourceID,'name'=>array('in',$coverArr));
$lists = Model("Source")->field('sourceID,name')->where($where)->select();
$lists = array_to_keyvalue($lists,'name','sourceID');
foreach($coverList as $i=>$items){
foreach($items as $k=>$item){
if(isset($lists[$item['cover']])){$coverList[$i][$k]['sourceID'] = $lists[$item['cover']];}
}
if(!isset($coverList[$i]['fileThumb']['sourceID'])){$needMake++;}
}
return $coverList;
}
// 单个文件属性处理;
public function itemParse($file){
if(strtolower(ACT) != 'pathinfo' || $file['type'] == 'folder'){return $file;}
$data = $this->listParse(array('folderList'=>array(),'fileList'=>array($file)));
return $data['fileList'][0];
}
/**
* 缩略图预览
* @return void
*/
public function cover(){
$path = $this->filePath($this->in['path'],false);
$width = intval($this->in['width']);
$width = ($width && $width > 1000) ? 1200:250;
$file = IO::info($path);
$fileHash = KodIO::hashPath($file);
$coverName = "cover_".$fileHash."_{$width}.png";
$result = $this->coverMake($this->cachePath,$file['path'],"cover_".$fileHash."_{$width}.png",$width);
if($width == 1200){$this->coverMake($this->cachePath,$file['path'],"cover_".$fileHash."_250.png",250);}
$sourceID = IO::fileNameExist($this->cachePath,$coverName);
// pr(IO::Info(kodIO::make($sourceID)));exit;
if($sourceID){IO::fileOut(kodIO::make($sourceID));exit;}
// 1200预览输出原图; 生成失败,浏览器支持预览的直接输出;
if(in_array($file['ext'], $this->webExts)) {
IO::fileOut($path);exit;
}
echo $result;
}
/**
* 检查服务
* linux 注意修改获取bin文件的权限问题;
* @return void
*/
public function check(){
$this->checkImgnry(); // imaginary服务检查
Cache::remove('fileThumb.getFFmpeg');
Cache::remove('fileThumb.getConvert');
if(isset($_GET['action']) && $_GET['action'] == 'stopAll'){
// 清除所有任务;
@include_once($this->pluginPath.'lib/VideoResize.class.php');
@include_once($this->pluginPath.'lib/TaskConvert.class.php');
$video = new videoResize();
$video->stopAll();
$video->log("Success !");
return;
}
if(isset($_GET['check'])){
$convert = $this->getConvert();
$ffmpeg = $this->getFFmpegFind();
$ffmpegSupport = $ffmpeg ? $this->ffmpegSupportCheck($ffmpeg) : false;
$result = $convert && $ffmpeg && $ffmpegSupport;
if($ffmpeg && !$this->ffmpegSupportCheck($ffmpeg)){$result = false;}
$message = $result ? 'ok;': LNG('fileThumb.check.faild').'<br/>';
if(!$result){
$error = '';
$check = array('shell_exec','proc_open','proc_close','exec');
foreach ($check as $method) {
if(!function_exists($method)){$error .= $method .' ';}
}
if($error){$message = $message.'['.trim($error).'] is disabled(please allow it)<br/>';}
}
if(!$convert){
$message .= "$convert convert ".LNG('fileThumb.check.error').";<br/>";
}
if(!$ffmpeg){
$message .= "$ffmpeg ffmpeg".LNG('fileThumb.check.error').";<br/>";
}
if($ffmpeg && !$ffmpegSupport){
$message .= 'ffmpeg not support muxer:image2 or libx264; please install again!';
}
show_json($message,$result);
}
include($this->pluginPath.'static/check.html');
}
// 标清视频;
public function videoSmall(){
if(!$this->getFFmpeg()) return;
@include_once($this->pluginPath.'lib/VideoResize.class.php');
@include_once($this->pluginPath.'lib/TaskConvert.class.php');
$video = new videoResize();
$video->start($this);
}
// 视频预览图
public function videoPreview(){
if(!$this->getFFmpeg()) return;
@include_once($this->pluginPath.'lib/VideoResize.class.php');
$video = new videoResize();
$video->videoPreview($this);
}
// 本地文件:local,io-local, {{kod-local}}全量生成;
// 远端:(ftp,oss等对象存储)
public function coverMake($cachePath,$path,$coverName,$size){
$cckey = md5('fileThumb.conver.'.$path.$coverName.$size);
if (Cache::get($cckey)) return;
Cache::set($cckey, 1, 60); // 延迟1分钟,避免重复执行(对未生成的图片预览大图时,会执行3次250x250)
if(IO::fileNameExist($cachePath,$coverName)){return 'exists;';}
if(!is_dir(TEMP_FILES)){mk_dir(TEMP_FILES);}
// 清除pathInfo缓存,避免队列任务中历史版本影响
$parse = KodIO::parse($path);
if ($parse['type'] == KodIO::KOD_SOURCE && $parse['id']) {
Model('Source')->sourceCacheClear($parse['id']);
}
$info = IO::info($path);$ext = $info['ext'];
if ($ext == 'gif' && $size != 250) return; // gif大图不转换,预览输出原图
$thumbFile = TEMP_FILES . $coverName;
$localFile = $this->localFile($path);
// TODO 可能不应先下载到本地,而应该先判断是否需要生成
$movie = '3gp,avi,mp4,m4v,mov,mpg,mpeg,mpe,mts,m2ts,wmv,ogv,webm,vob,flv,f4v,mkv,rmvb,rm';
$isVideo = in_array($ext,explode(',',$movie));
// 过短的视频封面图,不指定时间;
$videoThumbTime = true;
if( $isVideo && is_array($info['fileInfoMore']) &&
isset($info['fileInfoMore']['playtime']) &&
floatval($info['fileInfoMore']['playtime']) <= 3 ){
$videoThumbTime = false;
}
if($isVideo){
// 不是本地文件; 切片后获取:mp4,mov,mpg,webm,f4v,ogv,avi,mkv,wmv;(部分失败)
if(!$localFile){
$localTemp = $thumbFile.'.'.$ext;
$localFile = $localTemp;
file_put_contents($localTemp,IO::fileSubstr($path,0,1024*600)); // TODO 太小时不一定能生成
}
$this->thumbVideo($localFile,$thumbFile,$videoThumbTime);
} else {
// 检查文件大小
if (!$this->thumbSizeLimit($localFile,'size',$info['size'])) {
return 'covert error! file size > limit;';
}
// 下载文件到本地
if(!$localFile){
// 支持imaginary的s3系文件使用url生成缩略图,省略中转下载
$localFile = $this->localFile2Url($path,$ext);
if (!$localFile) {
$localFile = $localTemp = $this->pluginLocalFile($path);
}
}
if($ext == 'ttf'){
$this->thumbFont($localFile,$thumbFile,$size);
}else{
$this->thumbImage($localFile,$thumbFile,$size,$ext);
}
}
if($localTemp){@unlink($localTemp);}
if(@file_exists($thumbFile) && @is_file($thumbFile) && filesize($thumbFile) > 0){
Cache::remove($coverName);
$destFile = IO::move($thumbFile,$cachePath);
$pathInfo = KodIO::parse($destFile);
if($pathInfo['type'] == KodIO::KOD_SOURCE){
Model('Source')->setDesc($pathInfo['id'],$path);
//write_log(get_caller_info(),'test');
}
return $destFile;
}
Cache::set($coverName,'no',600);
del_file($thumbFile);
$this->log('cover=makeError:'.$localFile.';temp='.$thumbFile);
return 'convert error! localFile='.get_path_this($localFile);
}
// 获取文件 hash
public function localFile($path){
$io = IO::init($path);
$pathParse = KodIO::parse($path);
if(!$pathParse['type']) return $path;
if(is_array($io->pathParse) && isset($io->pathParse['truePath'])){ //协作分享处理;
if(file_exists($io->pathParse['truePath'])) return $io->pathParse['truePath'];
return false;
}
$fileInfo = IO::info($path);
if($fileInfo['fileID']){
$tempInfo = Model('File')->fileInfo($fileInfo['fileID']);
$fileInfo = IO::info($tempInfo['path']);
$pathParse = KodIO::parse($tempInfo['path']);
}
$parent = array('path'=>'{userFav}/');
$fileInfo = Action('explorer.listDriver')->parsePathIO($fileInfo,$parent);
$this->ioFileInfo = array(
'path' => $path,
'ioDriver' => strtolower($fileInfo['ioDriver'])
);
if($fileInfo['ioDriver'] == 'Local' && $fileInfo['ioBasePath']){
$base = rtrim($fileInfo['ioBasePath'],'/');
if(substr($base,0,2) == './') {
$base = substr_replace($base, BASIC_PATH, 0, 2);
}
return $base . '/' . ltrim($pathParse['param'], '/');
}
return false;
}
// s3系对象存储,通过imaginary用文件url生成缩略图
private function localFile2Url($path,$ext){
if (request_url_safe($path)) return false;
if (!in_array($this->ioFileInfo['ioDriver'], array('s3','eds','eos','minio','oos'))) return false;
// 是否支持imaginary
$api = $this->getImgnryApi($ext);
if (!$api) return false;
return Action('explorer.share')->link($path);
}
// 缩略图:字体
private function thumbFont($fontFile,$cacheFile,$maxSize){
$textMore = '
可道云在线云盘
汉体书写信息技术标准相容
档案下载使用界面简单
支援服务升级资讯专业制作
创意空间快速无线上网
AaBbCc 0123456789AaBbCc
㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩';
$textMore = str_replace("\t",' ',$textMore);
$text = "字体ABC";$size = 200;
if($maxSize >= 500){
$text = $textMore;
$size = 800;
}
$im = imagecreatetruecolor($size,$size);
imagefill($im,0,0,imagecolorallocate($im,255,255,255));
$color = imagecolorallocate($im,10,10,10);
imagefttext($im,32,0,10,100,$color,$fontFile,$text);
imagejpeg($im,$cacheFile);//生成图片
imagedestroy($im);
}
// 缩略图:视频
private function thumbVideo($file,$cacheFile,$videoThumbTime){
$api = $this->getImgmgkApi('ffmpeg');
if ($api) {
$res = $api->createThumbVideo($file,$cacheFile,$videoThumbTime);
if ($res) return true;
}
// 生成失败,尝试调用OSS直链生成
$this->thumbVideoByLink($cacheFile);
}
// 对象存储通过链接获取缩略图——当前仅支持OSS
// https://help.aliyun.com/zh/oss/user-guide/video-snapshots?
private function thumbVideoByLink($cacheFile) {
$driver = _get($this->ioFileInfo, 'ioDriver', '');
if ($driver != 'oss') return false;
$path = $this->ioFileInfo['path'];
switch ($driver) {
case 'oss':
// 费用:截帧数*(0.1/1k)/1000
$options = array('x-oss-process' => 'video/snapshot,t_10000,f_jpg,w_250,m_fast');
$link = IO::link($path, $options);
break;
}
if (!$link) return;
// 写入文件——视频缩略图收费,写入文件,避免反复调用
$content = curl_get_contents($link);
if (!$content) return;
file_put_contents($cacheFile, $content);
return @file_exists($cacheFile) ? true : false;
}
// 缩略图:图片
private function thumbImage($file,$cacheFile,$maxSize,$ext){
// 1.使用imaginary接口
$api = $this->getImgnryApi($ext);
if ($api) return $api->createThumb($file,$cacheFile,$maxSize,$ext);
// 检查图片大小,提前拦截——imagick执行过程中不受PHP内存限制,也拦截
$isImg = false;
if (in_array($ext, $this->imgExts)) {
$isImg = true;
if (!$this->thumbSizeLimit($file)) return;
}
// 2.使用imagick扩展
$api = $this->getImgickApi($ext);
if ($api) return $api->createThumb($file,$cacheFile,$maxSize,$ext);
// 3.使用ImageMagick
$api = $this->getImgmgkApi();
if ($api) $api->createThumb($file,$cacheFile,$maxSize,$ext);
if ($isImg) return;
// 生成的封面图,再用gd生成缩略图
ImageThumb::createThumb($cacheFile,$cacheFile,$maxSize,$maxSize);
}
// 获取convert/ffmpeg命令
public function getCommand($type = 'convert') {
if ($type == 'convert') {
$command = $this->getConvert();
} else {
$command = $this->getFFmpeg();
}
if ($command) return $command;
echo ucfirst($type).' '.LNG("fileThumb.check.notFound");
return false;
}
public function getFFmpeg(){
return $this->getCall('fileThumb.getFFmpeg',600,array($this,'getFFmpegNow'));
}
public function getConvert(){
return $this->getCall('fileThumb.getConvert',600,array($this,'getConvertNow'));
}
// Cache::getCall
private function getCall($key,$timeout,$call,$args = array()){
$result = Cache::get($key);
if($result || $result === '') return $result;
$result = call_user_func_array($call,$args);
$result = $result ? $result : '';
Cache::set($key,$result,$timeout);
return $result;
}
public function ffmpegSupportCheck($ffmpeg){
$out = shell_exec($ffmpeg.' -v 2>&1');
if(strstr($out,'--disable-muxer=image2')){
$this->log('ffmpeg support error. '.$out);
return false;
}
return true;
}
public function getFFmpegNow(){
$ffmpeg = $this->getFFmpegFind();
if(!$ffmpeg) return false;
return $this->ffmpegSupportCheck($ffmpeg) ? $ffmpeg:false;
}
public function getFFmpegFind(){
$check = 'options';
$config = $this->getConfig();
if( $this->checkBin($config['ffmpegBin'],$check) ){
return $config['ffmpegBin'];
}
$result = $this->guessBinPath('ffmpeg',$check);
if($result){return $result;}
$findMore = array(
'/imagemagick/ffmpeg',
'/imagemagick/bin/ffmpeg',
'/ImageMagick/ffmpeg.exe',
'/ImageMagick-7.0.7-Q8/ffmpeg.exe',
);
foreach ($findMore as $value) {
$result = $this->guessBinPath($value,$check);
if($result){return $result;}
}
return false;
}
public function getConvertNow(){
$check = 'options';
$config = $this->getConfig();
if( $this->checkBin($config['imagickBin'],$check) ){
return $config['imagickBin'];
}
$result = $this->guessBinPath('convert',$check);
if($result){return $result;}
$findMore = array(
'/imagemagick/convert',
'/imagemagick/bin/convert',
'/ImageMagick/convert.exe',
'/ImageMagick-7.0.7-Q8/convert.exe',
);
foreach ($findMore as $value) {
$result = $this->guessBinPath($value,$check);
if($result){return $result;}
}
return false;
}
/**
* 查找可执行文件命令
* https://github.com/taligentx/ee204/blob/master/admin/SCRIPT_server_tools_pathguess.php
* @param [type] $bin 命令文件
* @param [type] $check 找到文件后执行,结果中匹配的字符串
* @return [type] 可执行命令路径
*/
private function guessBinPath($bin,$check){
$array = array(
"/bin/",
"/usr/local/bin/",
"/usr/bin/",
"/usr/sbin/",
"/usr/local/",
"/local/bin/",
"C:/Program Files/",
"C:/Program Files (x86)/",
"C:/",
);
$findArray = array();
foreach ($array as $value) {
if(file_exists($value.$bin)){
$file = $value.$bin;
if(strstr($file,' ')){
$file = '"'.$file.'"';
}
$findArray[] = $file;
}
}
if(!strstr($bin,'/')){
$findArray[] = $bin;
}
if(count($findArray) > 0){
foreach ($findArray as $file) {
if( $this->checkBin($file,$check) ){
//var_dump($file,$findArray,$check,shell_exec($file.' --help'));exit;
return $file;
}
}
}
return false;
}
private function checkBin($bin,$check){
if(!function_exists('shell_exec')) {
$this->log('shell_exec function is disabled.');
return false;
}
$result = shell_exec($bin.' --help');
if (stripos($result,$check) > 0) return true;
$out = shell_exec($bin.' --help 2>&1');
$this->log('imagick env error:'.$out.';cmd='.$bin.' --help 2>&1');
return false;
}
// 调试模式
public function log($log, $cmd = ''){
// $config = $this->getConfig();
//if(!$config['debug']) return;
write_log($log,'fileThumb');
}
// 根据文件大小检查是否支持生成缩略图
private function thumbSizeLimit($file, $type='pixel', $size=0) {
// 1.按文件大小限制——主要是Imagick/ImageMagick,imaginary可以在容器限制,暂时统一拦截
if ($type == 'size') {
$config = $this->getConfig();
$sizeLimit = intval(_get($config, 'thumbSizeLimit', 50));
if ($size < 1024*1024*$sizeLimit) {
return true;
}
$this->log('cover=makeError:'.$file.'; file size too large: '.$size);
return false;
}
// 2.按像素计算大小限制
$memNeed = $this->sysMemoryNeed($file);
if(!$memNeed){return true;}
$memFree = $this->sysMemoryFree();
if(!$memFree || ($memNeed < $memFree)){return true;}
$this->log('cover=makeError:'.$file.'; need memory too large: '.$size);
return false;
}
// 系统可用内存——不一定能获取到
public function sysMemoryFree() {
$server = new ServerInfo();
$memUsage = $server->memUsage();
return intval($memUsage['total'] - $memUsage['used']);
}
// (命令)内存限制参数
public function memLimitParam($type = 'ffmpeg', $param = '') {
$memBase = 128 * 1024 * 1024; // 128M,最小内存限制——实际可用内存可能更小,暂不处理
$memFree = $this->sysMemoryFree();
$memFree = max($memBase, intval($memFree * 0.5));
if ($type != 'ffmpeg') { // convert
$mapLimit = $memFree * 2;
$dskLimit = $memFree * 4;
$memLimit = $this->sizeFormat($memFree);
$mapLimit = $this->sizeFormat($mapLimit);
$dskLimit = $this->sizeFormat($dskLimit);
// 限制内存后可能失败——某些步骤可能需要连续内存块(如解码、色彩空间转换),内存过小无法完成初始化
// return " -define resource:limit=true -limit memory {$memLimit} -limit map {$mapLimit} -limit disk {$dskLimit} {$param}";
// return " -limit memory {$memLimit} -limit map {$mapLimit} -limit disk {$dskLimit} {$param}";
if(!is_dir(TEMP_FILES)){mk_dir(TEMP_FILES);}
$path = TEMP_FILES . 'imagemagick'; mk_dir($path);
$cmd = " -limit memory {$memLimit} -limit map {$mapLimit} -limit disk {$dskLimit} ";
$cmd .= "-define registry:temporary-path={$path} "; // 指定临时目录
return $cmd . $param;
}
// $memLimit = intval($memFree / 1024); // KB
// return "ulimit -v {$memLimit}; {$param} -threads 2 "; // $param=>ffmpeg
$memLimit = $memFree; // max_alloc 支持无后缀(bytes)和有后缀(m/g)
return "{$param} -max_alloc {$memLimit} -threads 2 "; // $param=>ffmpeg;
}
private function sizeFormat($size, $type = 'convert') {
// $temp = explode(' ',size_format($size));
// $size = floor($temp[0]) . $temp[1];
$temp = explode(' ',size_format($size, 1));
$size = $temp[0] . $temp[1]; // 用floor相差较大(比如1.5GB),用小数在某些系统下可能存在解析差异
if ($type == 'convert') return $size;
return str_replace('B', '', $size);
}
// ImageMagick生成缩略图所需内存(基本所需,实际要求更多)
public function sysMemoryNeed($image) {
// 获取图像信息
$imageInfo = $this->getImgSize($image);
if (!$imageInfo) {return false;}
// 获取基本参数
$width = $imageInfo[0];
$height = $imageInfo[1];
$channels = _get($imageInfo, 'channels', 4); // 默认4通道
$bits = _get($imageInfo, 'bits', 8); // 默认8位
// 基础内存需求(字节)- 原始图像内存
$memory = $width * $height * $channels * ($bits / 8);
// ImageMagick通常需要至少3倍的原始图像内存(原图+工作内存+输出)
// return $memory * 3;
return $memory; // convert添加了映射内存和磁盘,所以这里只返回原图所需,用于粗略过滤
}
// 获取图片信息
public function getImgSize($image) {
if (!file_exists($image)) return false;
// 使用内置函数获取
if ($imageInfo = getimagesize($image)) return $imageInfo;
// 通过外部服务获取
$ext = get_path_ext($image);
$apiMethods = array(
array('method' => 'getImgnryApi', 'param' => $ext),
array('method' => 'getImgickApi', 'param' => $ext),
array('method' => 'getImgmgkApi', 'param' => 'convert')
);
foreach ($apiMethods as $apiMethod) {
$func = $apiMethod['method'];
$api = $this->$func($apiMethod['param']);
if ($api && $imageInfo = $api->getImgSize($image)) {
return $imageInfo;
}
}
return false;
}
// ------------------------------------------- 外部服务 -------------------------------------------
// imagry环境检查
public function checkImgnry(){
$type = $this->in['type'];
if ($type != 'imgnry') return;
if(isset($_GET['check'])){
$rest = $this->getThumbApi('imgnry')->status();
$code = $rest ? 1 : 0;
$this->setConfig(array('imgnryStatus' => $code));
$msg = LNG('fileThumb.check.svc'.($code ? 'Ok' : 'Err'));
show_json($msg, boolval($code));
}
include($this->pluginPath.'static/check.html');
exit;
}
// 获取缩略图服务
public function getThumbApi($type='imgnry') {
$typeArr = array(
'imgnry' => 'Imaginary',
'imgick' => 'Imagick',
'imgmgk' => 'ImageMagick',
);
static $api = array();
if (!isset($api[$type])) {
$class = 'Kod'.$typeArr[$type];
@include_once($this->pluginPath."lib/{$class}.class.php");
if (!class_exists($class)) {return false;}
$api[$type] = new $class($this);
}
return $api[$type];
}
// 获取Imaginry对象
public function getImgnryApi($ext) {
// 检查服务是否启用
static $imgnryStatus = null;
if (is_null($imgnryStatus)) {
$config = $this->getConfig();
$open = intval($config['imgnryOpen']);
$stat = intval($config['imgnryStatus']);
$imgnryStatus = $open && $stat ? true : false;
}
if (!$imgnryStatus) return false;
// 判断是否支持
$api = $this->getThumbApi('imgnry');
return (!$api || !$api->isSupport($ext)) ? false : $api;
}
// 获取Imagick扩展对象
public function getImgickApi($ext) {
if (!class_exists('Imagick')) return false;
$api = $this->getThumbApi('imgick');
return (!$api || !$api->isSupport($ext)) ? false : $api;
}
// 获取ImageMagick对象
public function getImgmgkApi($type='convert') {
if (!$this->getCommand($type)) return false;
return $this->getThumbApi('imgmgk');
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/plugins/fileThumb/package.json'
{
"id":"fileThumb",
"name":"{{LNG['fileThumb.meta.name']}}",
"title":"{{LNG['fileThumb.meta.title']}}",
"version":"2.14",
"category":"file,media",
"source":{
"icon":"{{pluginHost}}static/icon.png"
},
"description":"{{LNG['fileThumb.meta.desc']}}",
"auther":{
"copyright":"kodcloud",
"homePage":"http://kodcloud.com"
},
"configItem":{
"formStyle":{
"loadFile":"{{pluginHost}}static/check.js",
"tabs":{
"{{LNG['admin.setting.base']}}":"pluginAuth,pluginSaveKeepOpen,coverType,imagickBin,ffmpegBin,imgnryDesc,imgnryHost,imgnryMore,imgnryApiKey,imgnryUrlKey,imgnryOpen,imgnryDesc2",
"{{LNG['fileThumb.video.title']}}":"videoConvertDesc,videoConvert,videoConvertLimit,videoConvertLimitTo,videoConvertTask,videoConvertType,sep003,videoPlayType",
"{{LNG['admin.setting.others']}}":",sep001,thumbSizeLimit,fileSort,fileExt,fileThumb,sep002,debug"
}
},
"pluginAuth":{
"type":"userSelect",
"value":{"all":1},
"display":"{{LNG['admin.plugin.auth']}}",
"desc":"{{LNG['admin.plugin.authDesc']}}",
"require":1
},
"pluginSaveKeepOpen":{"className":"hidden","type":"input","value":"0"},
"coverType":{
"type":"segment",
"value":"imgick",
"display":"{{LNG['fileThumb.config.svcType']}}",
"info":{
"imgick":"ImageMagick",
"imgnry":"Imaginary"
},
"switchItem":{
"imgick":"imagickBin,ffmpegBin",
"imgnry":"imgnryDesc,imgnryHost,imgnryMore,imgnryApiKey,imgnryUrlKey,imgnryOpen,imgnryDesc2"
}
},
"imagickBin":{
"type":"input",
"value":"convert",
"display":"imageMagick {{LNG['explorer.file.path']}}",
"require":1,
"desc":"psd,ttf,ps,gta {{LNG['fileThumb.config.use']}}"
},
"ffmpegBin":{
"type":"input",
"value":"ffmpeg",
"display":"ffmpegBin {{LNG['explorer.file.path']}}",
"require":1,
"desc":"ps,ai,pdf {{LNG['fileThumb.config.use']}}<br/><br/>
<button class='btn btn-success check-psd-server mr-10' style='border-radius:3px;'>{{LNG['fileThumb.config.test']}}</button>
<button class='btn btn-default convert-stop-all' style='border-radius:3px;'>Stop All</button>
<button class='btn btn-link check-psd-help' >{{LNG['fileThumb.config.help']}}</button>"
},
"imgnryOpen":{
"type":"switch",
"value":"0",
"display":"{{LNG['fileThumb.config.igryOpen']}}",
// "switchItem":{"1":"imgnryDesc,imgnryHost,imgnryMore,imgnryApiKey,imgnryUrlKey,imgnryDesc2"},
"switchItem":{"1":"imgnryDesc,imgnryHost,imgnryMore,imgnryDesc2"},
"desc":"{{LNG['fileThumb.config.igryOpenDesc']}}",
},
"imgnryDesc":{
"type":"html",
"value":"{{LNG['fileThumb.config.igryDesc']}}",
"display":"{{LNG['admin.setting.recDesc']}}",
},
"imgnryHost":{
"type":"input",
"value":"",
"display":"{{LNG['fileThumb.config.igryHost']}}",
},
"imgnryMore":{
"type":"button",
"info":{
"more":{
"display":"{{LNG['common.more']}} <b class='caret'></b>",
"className":"btn btn-default btn-sm",
}
},
"switchItem":{
"more":"imgnryApiKey,imgnryUrlKey"
},
},
"imgnryApiKey":{
"type":"input",
"value":"",
"display":"{{LNG['fileThumb.config.igryApiKey']}}",
"desc":"{{LNG['fileThumb.config.igryApiKeyDesc']}}",
"attr":{"placeholder":"{{LNG['fileThumb.config.igryNotMust']}}"}
},
"imgnryUrlKey":{
"type":"input",
"value":"",
"display":"{{LNG['fileThumb.config.igryUrlKey']}}",
"desc":"{{LNG['fileThumb.config.igryUrlKeyDesc']}}",
"attr":{"placeholder":"{{LNG['fileThumb.config.igryNotMust']}}"}
},
"imgnryDesc2":{
"type":"html",
"value":"<button class='btn btn-success check-imgnry-server' style='border-radius:3px;'>{{LNG['fileThumb.config.test']}}</button>
<button class='btn btn-link check-imgnry-help' >{{LNG['fileThumb.config.help']}}</button>",
"display":"",
},
// "sep001":"<h4>{{LNG['fileThumb.config.file']}}:</h4>",
"thumbSizeLimit":{
"type":"number",
"value":"50","titleRight":"MB",
"display":"{{LNG['fileThumb.config.imageSizeLimit']}}",
"desc":"{{LNG['fileThumb.config.imageSizeLimitDesc']}}"
},
"fileSort":{
"type":"number",
"display":"{{LNG['admin.plugin.fileSort']}}",
"desc":"{{LNG['admin.plugin.fileSortDesc']}}",
"value":100
},
"fileExt":{
"type":"tags",
"display":"{{LNG['admin.plugin.fileExt']}}",
"desc":"{{LNG['admin.plugin.fileExtDesc']}}",
//x3f,srw
"value":"psd,psb,ps,ps2,ps3,tif,tiff,tga,tst,plt,ai,jpe,dds,crw,3fr,fff,ppm,mef,mos,mdc,iiq,eps,heic,ttf,raw,rw2,dcm,erf,cr2,raf,kdc,dcr,dng,mrw,nrw,nef,orf,pef,x3f,srf,arw,sr2,avif,webp"
},
"fileThumb":{
"type":"tags",
"display":"{{LNG['fileThumb.Config.fileThumbExt']}}",
"value":"psd,psb,ps,ps2,ps3,tif,tiff,tga,tst,plt,ai,jpe,dds,crw,3fr,fff,ppm,mef,mos,mdc,iiq,eps,
,pdf,xps,heic,avif,webp,ttf,raw,rw2,dcm,erf,cr2,raf,kdc,dcr,dng,mrw,nrw,nef,orf,pef,x3f,srf,arw,sr2,
,3gp,avi,mp4,m4v,mov,mpg,mpeg,mpe,mts,m2ts,wmv,ogv,webm,vob,flv,f4v,mkv,rmvb,rm"
},
"sep002":"<hr/>",
"debug":{
"type":"switch",
"value":"0",
"className":"row-inline",
"display":"{{LNG['fileThumb.config.debug']}}",
"desc":"{{LNG['fileThumb.config.debugDesc']}}",
"className":"hidden",
},
"videoConvertDesc":"<h4>{{LNG['fileThumb.video.title']}}</h4>
<div class='info-alert info-alert-blue mt-10'>{{LNG['fileThumb.config.convertTips']}}</div>",
"videoConvert":{
"type":"switch",
"value":"0",
"display":"{{LNG['fileThumb.config.videoOpen']}}",
"desc":"{{LNG['fileThumb.config.videoOpenDesc']}}",
"switchItem":{"1":"videoConvertLimit,videoConvertLimitTo,videoConvertTask,videoConvertType,sep003,videoPlayType"}
},
"videoConvertLimit":{
"type":"number",
"value":"50","titleRight":"MB",
"display":"{{LNG['fileThumb.config.videoSizeLimit']}}",
"desc":"{{LNG['fileThumb.config.videoSizeLimitDesc']}}"
},
"videoConvertLimitTo":{
"type":"number",
"value":"10","titleRight":"GB",
"display":"{{LNG['fileThumb.config.videoSizeLimitTo']}}",
"desc":"{{LNG['fileThumb.config.videoSizeLimitToDesc']}}"
},
"videoConvertTask":{
"type":"number",
"value":"5",
"display":"{{LNG['fileThumb.config.videoTaskLimit']}}",
"desc":"{{LNG['fileThumb.config.videoTaskLimitDesc']}}"
},
"videoConvertType":{
"type":"tags",
"value":"3gp,avi,mp4,m4v,mov,mpg,mpeg,mpe,mts,m2ts,wmv,ogv,webm,vob,flv,f4v,mkv,rmvb,rm",
"display":"{{LNG['fileThumb.config.videoTypeLimit']}}",
"desc":"{{LNG['fileThumb.config.videoTypeLimitDesc']}}"
},
"sep003":"<hr/>",
"videoPlayType":{
"type":"segment",
"value":"normal",
"display":"{{LNG['fileThumb.config.playType']}}",
"info":{
"normal":"{{LNG['fileThumb.video.normal']}}",
"before":"{{LNG['fileThumb.video.before']}}",
},
"desc":"{{LNG['fileThumb.config.playTypeDesc']}}"
},
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/plugins/fileThumb/readme.md'
##### 2.10 更新内容
- bug修复:图片文件产生历史版本时可能因为缓存导致新文件缩略图异常。
##### 2.08 更新内容
- 功能调整
- 缩略图生成增加Imaginary服务支持;文件、内存限制优化。
##### 2.06 更新内容
- 功能调整
- ImageMagick生成缩略图时,增加内存限制,防止生成缩略图时内存溢出。