Support timeouts for ffmpeg and magick commands

https://rhymix.org/qna/1935749
This commit is contained in:
Kijin Sung 2026-03-31 19:53:08 +09:00
parent ba49fe7b70
commit 81b32378ca
5 changed files with 62 additions and 6 deletions

View file

@ -122,6 +122,10 @@ class FileAdminController extends File
$config->magick_command = escape(utf8_trim(Context::get('magick_command'))) ?: '';
}
// Timeouts
$config->ffmpeg_timeout = max(0, intval(Context::get('ffmpeg_timeout'))) ?: null;
$config->magick_timeout = max(0, intval(Context::get('magick_timeout'))) ?: null;
// Check maximum file size (probably not necessary anymore)
if (PHP_INT_SIZE < 8)
{
@ -441,6 +445,10 @@ class FileAdminController extends File
'-limit memory 64MB -limit map 128MB -limit disk 1GB',
escapeshellarg($temp_filename),
]);
if (!\RX_WINDOWS && isset($config->magick_timeout) && $config->magick_timeout > 0)
{
$command = 'timeout -k1 ' . intval($config->magick_timeout) . ' ' . $command;
}
@exec($command, $output, $return_var);
$result = $return_var === 0 ? true : false;
}

View file

@ -1166,6 +1166,10 @@ class FileController extends File
{
$command = \RX_WINDOWS ? escapeshellarg($config->magick_command) : $config->magick_command;
$command .= ' identify ' . escapeshellarg($file_info['tmp_name']);
if (!\RX_WINDOWS && isset($config->magick_timeout) && $config->magick_timeout > 0)
{
$command = 'timeout -k1 ' . intval($config->magick_timeout) . ' ' . $command;
}
@exec($command, $output, $return_var);
if ($return_var === 0 && preg_match('/([A-Z]+) ([0-9]+)x([0-9]+)/', substr(array_last($output), strlen($file_info['tmp_name'])), $matches))
{
@ -1335,6 +1339,10 @@ class FileController extends File
$command .= ' -movflags +faststart -pix_fmt yuv420p -c:v libx264 -crf 23';
$command .= sprintf(' -vf "scale=%d:%d"', $adjusted['width'], $adjusted['height']);
$command .= ' ' . escapeshellarg($output_name);
if (!\RX_WINDOWS && isset($config->ffmpeg_timeout) && $config->ffmpeg_timeout > 0)
{
$command = 'timeout -k1 ' . intval($config->ffmpeg_timeout) . ' ' . $command;
}
@exec($command, $output, $return_var);
$result = $return_var === 0 ? true : false;
@ -1365,6 +1373,10 @@ class FileController extends File
'-limit memory 64MB -limit map 128MB -limit disk 1GB',
escapeshellarg($output_name),
]);
if (!\RX_WINDOWS && isset($config->magick_timeout) && $config->magick_timeout > 0)
{
$command = 'timeout -k1 ' . intval($config->magick_timeout) . ' ' . $command;
}
@exec($command, $output, $return_var);
$result = $return_var === 0 ? true : false;
}
@ -1386,6 +1398,10 @@ class FileController extends File
'-limit memory 64MB -limit map 128MB -limit disk 1GB',
escapeshellarg($output_name),
]);
if (!\RX_WINDOWS && isset($config->magick_timeout) && $config->magick_timeout > 0)
{
$command = 'timeout -k1 ' . intval($config->magick_timeout) . ' ' . $command;
}
@exec($command, $output, $return_var);
$result = $return_var === 0 ? true : false;
}
@ -1572,6 +1588,10 @@ class FileController extends File
$command .= empty($stream_info['audio']) ? ' -an' : ' -acodec aac';
$command .= sprintf(' -vf "scale=%d:%d"', $adjusted['width'], $adjusted['height']);
$command .= ' ' . escapeshellarg($output_name);
if (!\RX_WINDOWS && isset($config->ffmpeg_timeout) && $config->ffmpeg_timeout > 0)
{
$command = 'timeout -k1 ' . intval($config->ffmpeg_timeout) . ' ' . $command;
}
@exec($command, $output, $return_var);
$result = $return_var === 0 ? true : false;
@ -1604,6 +1624,10 @@ class FileController extends File
$command = \RX_WINDOWS ? escapeshellarg($config->ffmpeg_command) : $config->ffmpeg_command;
$command .= sprintf(' -ss 00:00:00.%d -i %s -vframes 1', mt_rand(0, 99), escapeshellarg($file_info['tmp_name']));
$command .= ' -nostdin ' . escapeshellarg($thumbnail_name);
if (!\RX_WINDOWS && isset($config->ffmpeg_timeout) && $config->ffmpeg_timeout > 0)
{
$command = 'timeout -k1 ' . intval($config->ffmpeg_timeout) . ' ' . $command;
}
@exec($command, $output, $return_var);
if ($return_var === 0)
{

View file

@ -104,15 +104,15 @@ $lang->max_image_size_same_format_to_jpg = 'Convert to JPG';
$lang->max_image_size_same_format_to_webp = 'Convert to WebP';
$lang->max_image_size_admin = 'Also apply to administrator';
$lang->image_quality_adjustment = 'Image Quality';
$lang->about_image_quality_adjustment = 'adjust the quality of images that will is converted by other settings.<br />If set to more than 75% (Standard), the file size may be larger than the original.';
$lang->about_image_quality_adjustment = 'Adjust the quality of images that will be converted by other settings.<br />If set to more than 75% (Standard), the file size may be larger than the original.';
$lang->image_autorotate = 'Fix Image Rotation';
$lang->about_image_autorotate = 'correct images that are rotated by mobile devices.';
$lang->about_image_autorotate = 'Correct images that are rotated by mobile devices.';
$lang->image_remove_exif_data = 'Remove EXIF';
$lang->about_image_remove_exif_data = 'remove EXIF data including camera, GPS information, and more in image file for privacy.<br />Even if this option is not used, EXIF data may be removed when the image is converted by other settings.';
$lang->about_image_remove_exif_data = 'Remove EXIF data including camera, GPS information, and more in image file for privacy.<br />Even if this option is not used, EXIF data may be removed when the image is converted by other settings.';
$lang->image_always_reencode = 'Always Reencode';
$lang->about_image_always_reencode = 'Reencode images to a constant quality even if they do not meet one of the conditions above. This may help save disk space and traffic.';
$lang->image_autoconv_gif2mp4 = 'Convert GIF to MP4';
$lang->about_image_autoconv_gif2mp4 = 'convert animated GIF images into MP4 videos to save storage and bandwidth.<br />This requires ffmpeg settings below. Videos may not play properly in older browsers.';
$lang->about_image_autoconv_gif2mp4 = 'Convert animated GIF images into MP4 videos to save storage and bandwidth.<br />This requires ffmpeg settings below. Videos may not play properly in older browsers.';
$lang->max_video_size = 'Limit Video Size';
$lang->about_max_video_size = 'Limit the dimensions of uploaded videos. Note that this is only indirectly related to file size.';
$lang->max_video_duration = 'Limit Video Duration';
@ -122,15 +122,19 @@ $lang->about_video_autoconv_any2mp4 = 'Convert all other types of videos to MP4
$lang->video_always_reencode = 'Always Reencode';
$lang->about_video_always_reencode = 'Reencode videos to a constant quality even if they do not meet one of the conditions above. This may help save disk space and traffic.';
$lang->video_thumbnail = 'Video Thumbnail';
$lang->about_video_thumbnail = 'extract a thumbnail image from uploaded video.';
$lang->about_video_thumbnail = 'Extract a thumbnail image from uploaded video.';
$lang->video_mp4_gif_time = 'Play Like GIF';
$lang->about_video_mp4_gif_time = 'treat silent MP4 videos with duration less than the set time as GIF images, and play with auto and loop.';
$lang->about_video_mp4_gif_time = 'Treat silent MP4 videos with duration less than the set time as GIF images, and play with auto and loop.';
$lang->external_program_paths = 'Paths to External Programs';
$lang->ffmpeg_path = 'Absolute Path to ffmpeg';
$lang->ffprobe_path = 'Absolute Path to ffprobe';
$lang->ffmpeg_timeout = 'ffmpeg Timeout';
$lang->magick_path = 'Absolute Path to magick';
$lang->magick_timeout = 'magick Timeout';
$lang->about_ffmpeg_path = 'Rhymix uses ffmpeg to convert video files.';
$lang->about_ffmpeg_timeout = 'If the video conversion task is not completed within a certain time, it will be terminated.<br />Proper timeout settings can help manage server load.<br />However, if set longer than the PHP execution time limit (%d seconds), the conversion result will not be saved.';
$lang->about_magick_path = 'Rhymix uses magick to convert newer image formats such as AVIF and HEIC.<br />Note that the \'convert\' command from previous versions of ImageMagick doesn\'t support these formats.<br />The latest version can be downloaded from their <a href="https://imagemagick.org/script/download.php" target="_blank">official site</a>.';
$lang->about_magick_timeout = 'If the image conversion task is not completed within a certain time, it will be terminated.<br />Proper timeout settings can help manage server load.<br />However, if set longer than the PHP execution time limit (%d seconds), the conversion result will not be saved.';
$lang->msg_cannot_use_exec = 'The exec() function is disabled on this server.';
$lang->msg_cannot_use_ffmpeg = 'In order to use this feature, PHP must be able to execute \'ffmpeg\' and \'ffprobe\' commands.';
$lang->msg_cannot_use_exif = 'In order to use this feature, PHP must be installed with the \'exif\' extension.';

View file

@ -128,9 +128,13 @@ $lang->about_video_mp4_gif_time = '설정된 시간 이하의 길이를 가진
$lang->external_program_paths = '외부 프로그램 경로';
$lang->ffmpeg_path = 'ffmpeg 절대경로';
$lang->ffprobe_path = 'ffprobe 절대경로';
$lang->ffmpeg_timeout = 'ffmpeg 타임아웃';
$lang->magick_path = 'magick 절대경로';
$lang->magick_timeout = 'magick 타임아웃';
$lang->about_ffmpeg_path = '동영상 변환에 사용합니다.';
$lang->about_ffmpeg_timeout = '동영상 변환 작업이 일정 시간 안에 완료되지 않으면 강제로 종료합니다.<br />적절한 타임아웃 설정은 서버 부하 관리에 도움이 됩니다.<br />단, PHP 실행 제한 시간(%d초)보다 길게 설정할 경우 변환 결과가 저장되지 않습니다.';
$lang->about_magick_path = 'AVIF, HEIC 등 일부 이미지 변환에 사용합니다.<br />구 버전 ImageMagick의 convert 명령은 이러한 포맷을 지원하지 않습니다.<br />새 버전은 <a href="https://imagemagick.org/script/download.php" target="_blank">공식 사이트</a>에서 다운받을 수 있습니다.';
$lang->about_magick_timeout = '이미지 변환 작업이 일정 시간 안에 완료되지 않으면 강제로 종료합니다.<br />적절한 타임아웃 설정은 서버 부하 관리에 도움이 됩니다.<br />단, PHP 실행 제한 시간(%d초)보다 길게 설정할 경우 변환 결과가 저장되지 않습니다.';
$lang->msg_cannot_use_exec = '이 서버에서 exec() 함수를 사용할 수 없습니다.';
$lang->msg_cannot_use_ffmpeg = '이 기능을 사용하려면 PHP에서 ffmpeg 및 ffprobe 명령을 실행할 수 있어야 합니다.';
$lang->msg_cannot_use_exif = '이 기능을 사용하려면 PHP exif 확장모듈이 필요합니다.';

View file

@ -283,6 +283,14 @@
<p class="x_text-info" cond="!$is_exec_available">{$lang->msg_cannot_use_exec}</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label">{$lang->ffmpeg_timeout}</label>
<div class="x_controls">
<input type="number" min="0" name="ffmpeg_timeout" value="{$config->ffmpeg_timeout}" style="min-width:80px" /> {$lang->unit_sec}
<p class="x_help-block">{sprintf($lang->about_ffmpeg_timeout, ini_get('max_execution_time'))}</p>
<p class="x_text-info" cond="!$is_exec_available">{$lang->msg_cannot_use_exec}</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label">{$lang->magick_path}</label>
<div class="x_controls">
@ -291,6 +299,14 @@
<p class="x_text-info" cond="!$is_exec_available">{$lang->msg_cannot_use_exec}</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label">{$lang->magick_timeout}</label>
<div class="x_controls">
<input type="number" min="0" name="magick_timeout" value="{$config->magick_timeout}" style="min-width:80px" /> {$lang->unit_sec}
<p class="x_help-block">{sprintf($lang->about_magick_timeout, ini_get('max_execution_time'))}</p>
<p class="x_text-info" cond="!$is_exec_available">{$lang->msg_cannot_use_exec}</p>
</div>
</div>
</section>
<div class="x_clearfix btnArea">