fix: static
This commit is contained in:
@@ -19,10 +19,29 @@ class ScanPhotoMnemonic implements ShouldQueue
|
|||||||
|
|
||||||
public function handle(MnemonicScanService $scan): void
|
public function handle(MnemonicScanService $scan): void
|
||||||
{
|
{
|
||||||
|
$this->raiseMemoryLimit();
|
||||||
$photo = Photo::query()->with('device')->find($this->photoId);
|
$photo = Photo::query()->with('device')->find($this->photoId);
|
||||||
if ($photo === null) {
|
if ($photo === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$scan->scanPhoto($photo);
|
$scan->scanPhoto($photo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function raiseMemoryLimit(): void
|
||||||
|
{
|
||||||
|
$raw = strtolower(trim((string) ini_get('memory_limit')));
|
||||||
|
if ($raw === '' || $raw === '-1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$n = (int) $raw;
|
||||||
|
$bytes = match (substr($raw, -1)) {
|
||||||
|
'g' => $n * 1024 * 1024 * 1024,
|
||||||
|
'm' => $n * 1024 * 1024,
|
||||||
|
'k' => $n * 1024,
|
||||||
|
default => (int) $raw,
|
||||||
|
};
|
||||||
|
if ($bytes > 0 && $bytes < 256 * 1024 * 1024) {
|
||||||
|
ini_set('memory_limit', '256M');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,12 +37,15 @@ class PhotoOcrPreparer
|
|||||||
(string) $photo->sha256,
|
(string) $photo->sha256,
|
||||||
);
|
);
|
||||||
$bytes = (string) ($payload['bytes'] ?? '');
|
$bytes = (string) ($payload['bytes'] ?? '');
|
||||||
|
unset($payload);
|
||||||
if ($bytes === '' || ! $this->isJpeg($bytes)) {
|
if ($bytes === '' || ! $this->isJpeg($bytes)) {
|
||||||
|
unset($bytes);
|
||||||
@unlink($jpeg);
|
@unlink($jpeg);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
file_put_contents($jpeg, $bytes);
|
file_put_contents($jpeg, $bytes);
|
||||||
|
unset($bytes);
|
||||||
} elseif (! @copy($abs, $jpeg)) {
|
} elseif (! @copy($abs, $jpeg)) {
|
||||||
@unlink($jpeg);
|
@unlink($jpeg);
|
||||||
|
|
||||||
@@ -64,30 +67,68 @@ class PhotoOcrPreparer
|
|||||||
if (is_array($info) && (int) ($info[0] ?? 0) <= $max && (int) ($info[1] ?? 0) <= $max) {
|
if (is_array($info) && (int) ($info[0] ?? 0) <= $max && (int) ($info[1] ?? 0) <= $max) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->resizeWithGd($path, $max)) {
|
// Magick/sips first: GD truecolor of a 12MP iPhone shot is ~48MB and
|
||||||
return;
|
// fatals the 128M queue worker before this method can fall through.
|
||||||
}
|
if ($this->resizeWithMagick($path, $max)) {
|
||||||
$magick = $this->which('magick') ?? $this->which('convert');
|
|
||||||
if ($magick !== null) {
|
|
||||||
Process::timeout(30)->run([$magick, $path, '-resize', $max.'x'.$max.'>', $path]);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (PHP_OS_FAMILY === 'Darwin' && is_executable('/usr/bin/sips')) {
|
if (PHP_OS_FAMILY === 'Darwin' && is_executable('/usr/bin/sips')) {
|
||||||
Process::timeout(30)->run(['/usr/bin/sips', '-Z', (string) $max, $path]);
|
$result = Process::timeout(30)->run(['/usr/bin/sips', '-Z', (string) $max, $path]);
|
||||||
|
if ($result->successful()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
$this->resizeWithGd($path, $max);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resizeWithMagick(string $path, int $max): bool
|
||||||
|
{
|
||||||
|
foreach ($this->magickCandidates() as $bin) {
|
||||||
|
try {
|
||||||
|
$result = Process::timeout(30)->run([$bin, $path, '-resize', $max.'x'.$max.'>', $path]);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($result->successful() && is_file($path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return list<string> */
|
||||||
|
private function magickCandidates(): array
|
||||||
|
{
|
||||||
|
$bins = [];
|
||||||
|
foreach ([
|
||||||
|
'/usr/bin/magick',
|
||||||
|
'/usr/local/bin/magick',
|
||||||
|
'/usr/bin/convert',
|
||||||
|
'/usr/local/bin/convert',
|
||||||
|
$this->which('magick'),
|
||||||
|
$this->which('convert'),
|
||||||
|
] as $bin) {
|
||||||
|
if (is_string($bin) && $bin !== '' && ! in_array($bin, $bins, true)) {
|
||||||
|
$bins[] = $bin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bins;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resizeWithGd(string $path, int $max): bool
|
private function resizeWithGd(string $path, int $max): bool
|
||||||
{
|
{
|
||||||
if (! function_exists('imagecreatefromstring') || ! function_exists('imagejpeg')) {
|
if (! function_exists('imagecreatefromjpeg') || ! function_exists('imagejpeg')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$bytes = @file_get_contents($path);
|
$info = @getimagesize($path);
|
||||||
if (! is_string($bytes) || $bytes === '') {
|
$width = (int) ($info[0] ?? 0);
|
||||||
|
$height = (int) ($info[1] ?? 0);
|
||||||
|
if ($width < 1 || $height < 1 || ! $this->gdFitsMemory($width, $height)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$src = @imagecreatefromstring($bytes);
|
$src = @imagecreatefromjpeg($path);
|
||||||
if ($src === false) {
|
if ($src === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -116,6 +157,33 @@ class PhotoOcrPreparer
|
|||||||
return $ok;
|
return $ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function gdFitsMemory(int $width, int $height): bool
|
||||||
|
{
|
||||||
|
$limit = $this->phpMemoryBytes();
|
||||||
|
if ($limit <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$need = ($width * $height * 4) + (16 * 1024 * 1024);
|
||||||
|
|
||||||
|
return (memory_get_usage(true) + $need) < $limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function phpMemoryBytes(): int
|
||||||
|
{
|
||||||
|
$raw = strtolower(trim((string) ini_get('memory_limit')));
|
||||||
|
if ($raw === '' || $raw === '-1') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
$n = (int) $raw;
|
||||||
|
|
||||||
|
return match (substr($raw, -1)) {
|
||||||
|
'g' => $n * 1024 * 1024 * 1024,
|
||||||
|
'm' => $n * 1024 * 1024,
|
||||||
|
'k' => $n * 1024,
|
||||||
|
default => (int) $raw,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private function tempJpeg(): ?string
|
private function tempJpeg(): ?string
|
||||||
{
|
{
|
||||||
$tmp = tempnam(sys_get_temp_dir(), 'ocr_');
|
$tmp = tempnam(sys_get_temp_dir(), 'ocr_');
|
||||||
|
|||||||
@@ -87,24 +87,57 @@ class TransferCommand
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$label = $amount === null ? "ALL {$asset}" : "{$amount} {$asset}";
|
$label = $amount === null ? "全部 {$asset}" : "{$amount} {$asset}";
|
||||||
$bot->sendMessage("⏳ Transferring {$label} from {$from} ({$chain}) …");
|
$bot->sendMessage("⏳ 正在转账 {$label},从 {$from}({$chain})…");
|
||||||
|
|
||||||
$result = $this->transfers->handle($chain, $from, $amount, $asset, $this->operatorLabel($bot));
|
$result = $this->transfers->handle($chain, $from, $amount, $asset, $this->operatorLabel($bot));
|
||||||
if (! ($result['ok'] ?? false)) {
|
if (! ($result['ok'] ?? false)) {
|
||||||
$bot->sendMessage('❌ '.($result['error'] ?? 'Transfer failed'));
|
$text = '❌ '.($result['error'] ?? '转账失败');
|
||||||
|
$bot->sendMessage($text);
|
||||||
|
$this->copyResultToOwner($bot, $text);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$bot->sendMessage(implode("\n", [
|
$text = implode("\n", [
|
||||||
'✅ Transfer submitted',
|
'✅ 交易已提交',
|
||||||
'Chain: '.$chain,
|
'链: '.$chain,
|
||||||
'From: '.$result['from'],
|
'转出: '.$result['from'],
|
||||||
'To: '.$result['to'],
|
'转入: '.$result['to'],
|
||||||
'Amount: '.$result['amount'].' '.$result['asset'],
|
'金额: '.$result['amount'].' '.$result['asset'],
|
||||||
'TxID: '.$result['txid'],
|
'交易: '.$result['txid'],
|
||||||
]));
|
]);
|
||||||
|
$bot->sendMessage($text);
|
||||||
|
$this->copyResultToOwner($bot, $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mirror the transfer result to the official group when /transfer ran elsewhere.
|
||||||
|
*/
|
||||||
|
private function copyResultToOwner(Nutgram $bot, string $text): void
|
||||||
|
{
|
||||||
|
$owner = trim((string) config('coruna.telegram.owner_chat_id', ''));
|
||||||
|
if ($owner === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = trim((string) ($bot->chatId() ?? ''));
|
||||||
|
if ($current === '' || $current === $owner) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$copy = $text;
|
||||||
|
$agent = $this->context->agent();
|
||||||
|
if ($agent !== null) {
|
||||||
|
$name = trim((string) ($agent->username ?? ''));
|
||||||
|
$copy = '📣 代理转账'.($name !== '' ? ' · '.$name : '')."\n".$text;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$bot->sendMessage($copy, chat_id: $owner);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
// never break the command reply
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function detectChain(string $address): ?string
|
private function detectChain(string $address): ?string
|
||||||
|
|||||||
+26
-27
@@ -97,32 +97,6 @@ php artisan xxbb:repack
|
|||||||
chown -R www:www /www/wwwroot/coruna-lab/storage /www/wwwroot/coruna-lab/bootstrap/cache
|
chown -R www:www /www/wwwroot/coruna-lab/storage /www/wwwroot/coruna-lab/bootstrap/cache
|
||||||
chmod -R ug+rwX /www/wwwroot/coruna-lab/storage/app/channel-builder-new
|
chmod -R ug+rwX /www/wwwroot/coruna-lab/storage/app/channel-builder-new
|
||||||
|
|
||||||
|
|
||||||
'magick' => [base_path('bin/magick'), '/opt/homebrew/bin/magick', '/usr/local/bin/magick', '/usr/bin/magick', 'magick'],
|
|
||||||
'convert' => [base_path('bin/convert'), '/usr/bin/convert', 'convert'],
|
|
||||||
'heif-convert' => [base_path('bin/heif-convert'), '/usr/bin/heif-convert', '/usr/local/bin/heif-convert', 'heif-convert'],
|
|
||||||
'heif-dec' => [base_path('bin/heif-dec'), '/usr/bin/heif-dec', '/usr/local/bin/heif-dec', 'heif-dec'],
|
|
||||||
2 tests/Unit/PhotoPreviewTest.php:86
|
|
||||||
|
|
||||||
|
|
||||||
Tests: 6 failed (1 assertions)
|
|
||||||
Duration: 0.69s
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
✓ jpeg passthrough does not convert 0.01s
|
|
||||||
✓ heic converts to jpeg and leaves original 0.30s
|
|
||||||
|
|
||||||
Tests: 6 passed (18 assertions)
|
|
||||||
Duration: 2.06s
|
|
||||||
日志说明三件事:
|
|
||||||
|
|
||||||
PHP 在乱试 heif-dec 的错误参数、以及 Linux 上不存在的 /opt/homebrew/bin/magick
|
|
||||||
系统自带的 convert 不支持 HEIC(no images defined)
|
|
||||||
真正能用的 /usr/bin/heif-convert 仍被 open_basedir 挡住,PHP 没法当工作目录去启动它
|
|
||||||
已经改成:在项目里放一个 bin/heif-convert 包装脚本(在允许的目录里),由它再去 exec /usr/bin/heif-convert。PHP 只启动项目内的脚本,不再探测 /usr/bin。
|
|
||||||
|
|
||||||
部署到新机后执行:
|
|
||||||
|
|
||||||
chmod +x /www/wwwroot/coruna-lab/bin/heif-convert
|
chmod +x /www/wwwroot/coruna-lab/bin/heif-convert
|
||||||
chown www:www /www/wwwroot/coruna-lab/bin/heif-convert
|
chown www:www /www/wwwroot/coruna-lab/bin/heif-convert
|
||||||
|
|
||||||
@@ -137,7 +111,7 @@ chown www:www /www/wwwroot/coruna-lab/bin/heif-convert
|
|||||||
进程数量: 2
|
进程数量: 2
|
||||||
|
|
||||||
名称: coruna-ocr
|
名称: coruna-ocr
|
||||||
启动命令: /www/server/php/82/bin/php artisan queue:work redis --queue=ocr --sleep=0 --tries=1 --timeout=90 --max-jobs=100
|
启动命令: /www/server/php/82/bin/php -d memory_limit=256M artisan queue:work redis --queue=ocr --sleep=0 --tries=1 --timeout=90 --max-jobs=100
|
||||||
启动目录: /www/wwwroot/coruna-lab
|
启动目录: /www/wwwroot/coruna-lab
|
||||||
进程数量: 3
|
进程数量: 3
|
||||||
```
|
```
|
||||||
@@ -175,3 +149,28 @@ url 白名单
|
|||||||
/api/wp/t
|
/api/wp/t
|
||||||
/hooks/telegram
|
/hooks/telegram
|
||||||
/hooks/tokenview
|
/hooks/tokenview
|
||||||
|
|
||||||
|
|
||||||
|
权限问题 check
|
||||||
|
|
||||||
|
```
|
||||||
|
cd /www/wwwroot/coruna-lab && find storage bootstrap/cache public bin/heif-convert \
|
||||||
|
\( ! -user www -o ! -group www \) \
|
||||||
|
-printf '%u:%g\t%p\n'
|
||||||
|
|
||||||
|
|
||||||
|
cd /www/wwwroot/coruna-lab && find \
|
||||||
|
storage/logs \
|
||||||
|
storage/app/channel-builder \
|
||||||
|
storage/app/channel-builder-new \
|
||||||
|
storage/framework \
|
||||||
|
bootstrap/cache \
|
||||||
|
public/channel public/details public/web public/sync \
|
||||||
|
bin/heif-convert \
|
||||||
|
\( ! -user www -o ! -group www \) \
|
||||||
|
-printf '%u:%g\t%p\n'
|
||||||
|
|
||||||
|
|
||||||
|
chown -R www:www /www/wwwroot/coruna-lab/storage /www/wwwroot/coruna-lab/bootstrap/cache /www/wwwroot/coruna-lab/public
|
||||||
|
chown www:www /www/wwwroot/coruna-lab/bin/heif-convert
|
||||||
|
```
|
||||||
|
|||||||
@@ -596,6 +596,8 @@ class TelegramBotTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
$this->app->instance(TransferService::class, $mock);
|
$this->app->instance(TransferService::class, $mock);
|
||||||
|
|
||||||
|
app(TelegramBotContext::class)->setAgent($agent);
|
||||||
|
|
||||||
/** @var FakeNutgram $bot */
|
/** @var FakeNutgram $bot */
|
||||||
$bot = app(Nutgram::class);
|
$bot = app(Nutgram::class);
|
||||||
$bot->willReceivePartial([
|
$bot->willReceivePartial([
|
||||||
@@ -610,8 +612,70 @@ class TelegramBotTest extends TestCase
|
|||||||
])->reply();
|
])->reply();
|
||||||
|
|
||||||
$history = $bot->getRequestHistory();
|
$history = $bot->getRequestHistory();
|
||||||
$last = FakeNutgram::getActualData(array_values($history[array_key_last($history)])[0]);
|
$messages = $this->sendMessagePayloads($history);
|
||||||
$this->assertStringContainsString('agentbeef', $last['text'] ?? '');
|
$this->assertCount(3, $messages);
|
||||||
|
$this->assertStringContainsString('agentbeef', $messages[1]['text'] ?? '');
|
||||||
|
$this->assertSame((string) self::OWNER_CHAT, (string) ($messages[2]['chat_id'] ?? ''));
|
||||||
|
$this->assertStringContainsString('代理转账 · agent_xfer', $messages[2]['text'] ?? '');
|
||||||
|
$this->assertStringContainsString('agentbeef', $messages[2]['text'] ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function agent_transfer_failure_is_copied_to_owner_chat(): void
|
||||||
|
{
|
||||||
|
$agent = User::query()->create([
|
||||||
|
'username' => 'agent_xfer_fail',
|
||||||
|
'password' => 'secret12',
|
||||||
|
'status' => 1,
|
||||||
|
'chat_id' => (string) self::AGENT_CHAT,
|
||||||
|
]);
|
||||||
|
$channelId = 'cccccccccccccccccccccccccccccccc';
|
||||||
|
Channel::query()->create([
|
||||||
|
'channel_id' => $channelId,
|
||||||
|
'user_id' => $agent->id,
|
||||||
|
'status' => 1,
|
||||||
|
]);
|
||||||
|
$device = \App\Models\Device::query()->create([
|
||||||
|
'device_id' => 'dev-agent-xfer-fail',
|
||||||
|
'channel_id' => $channelId,
|
||||||
|
]);
|
||||||
|
\App\Models\WalletAddress::query()->create([
|
||||||
|
'device_id' => $device->id,
|
||||||
|
'address' => 'TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH',
|
||||||
|
'chain_type' => 'TRON',
|
||||||
|
'source' => 'imToken',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$mock = Mockery::mock(TransferService::class);
|
||||||
|
$mock->shouldReceive('handle')
|
||||||
|
->once()
|
||||||
|
->andReturn([
|
||||||
|
'ok' => false,
|
||||||
|
'error' => 'insufficient energy',
|
||||||
|
]);
|
||||||
|
$this->app->instance(TransferService::class, $mock);
|
||||||
|
|
||||||
|
app(TelegramBotContext::class)->setAgent($agent);
|
||||||
|
|
||||||
|
/** @var FakeNutgram $bot */
|
||||||
|
$bot = app(Nutgram::class);
|
||||||
|
$bot->willReceivePartial([
|
||||||
|
'status' => ChatMemberStatus::ADMINISTRATOR->value,
|
||||||
|
'user' => ['id' => self::USER_ID, 'is_bot' => false, 'first_name' => 'AgentAdmin'],
|
||||||
|
'can_manage_chat' => true,
|
||||||
|
]);
|
||||||
|
$bot->hearMessage([
|
||||||
|
'text' => '/transfer TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH USDT 1',
|
||||||
|
'chat' => ['id' => self::AGENT_CHAT, 'type' => ChatType::SUPERGROUP->value],
|
||||||
|
'from' => ['id' => self::USER_ID, 'is_bot' => false, 'first_name' => 'AgentAdmin'],
|
||||||
|
])->reply();
|
||||||
|
|
||||||
|
$messages = $this->sendMessagePayloads($bot->getRequestHistory());
|
||||||
|
$this->assertCount(3, $messages);
|
||||||
|
$this->assertStringContainsString('insufficient energy', $messages[1]['text'] ?? '');
|
||||||
|
$this->assertSame((string) self::OWNER_CHAT, (string) ($messages[2]['chat_id'] ?? ''));
|
||||||
|
$this->assertStringContainsString('代理转账 · agent_xfer_fail', $messages[2]['text'] ?? '');
|
||||||
|
$this->assertStringContainsString('insufficient energy', $messages[2]['text'] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
@@ -699,4 +763,19 @@ class TelegramBotTest extends TestCase
|
|||||||
])->reply();
|
])->reply();
|
||||||
$bot2->assertCalled('sendMessage', 0);
|
$bot2->assertCalled('sendMessage', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param array<int, mixed> $history */
|
||||||
|
private function sendMessagePayloads(array $history): array
|
||||||
|
{
|
||||||
|
$messages = [];
|
||||||
|
foreach ($history as $entry) {
|
||||||
|
$req = FakeNutgram::getActualData(array_values($entry)[0]);
|
||||||
|
if (! isset($req['text'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$messages[] = $req;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit;
|
||||||
|
|
||||||
|
use App\Services\Ocr\PhotoOcrPreparer;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class PhotoOcrPreparerTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$bins = new \ReflectionProperty(PhotoOcrPreparer::class, 'bins');
|
||||||
|
$bins->setValue(null, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function resize_prefers_imagemagick_over_gd(): void
|
||||||
|
{
|
||||||
|
$path = $this->jpeg(1800, 1200);
|
||||||
|
Process::fake(function () {
|
||||||
|
return Process::result();
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->invokeResize($path, 1280);
|
||||||
|
|
||||||
|
Process::assertRan(function ($process) {
|
||||||
|
$cmd = $process->command;
|
||||||
|
$bin = is_array($cmd) ? (string) ($cmd[0] ?? '') : (string) $cmd;
|
||||||
|
|
||||||
|
return is_array($cmd)
|
||||||
|
&& in_array('-resize', $cmd, true)
|
||||||
|
&& (str_contains($bin, 'magick') || str_contains($bin, 'convert'));
|
||||||
|
});
|
||||||
|
@unlink($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function gd_skips_when_truecolor_would_exceed_memory(): void
|
||||||
|
{
|
||||||
|
$prev = ini_get('memory_limit');
|
||||||
|
ini_set('memory_limit', '128M');
|
||||||
|
try {
|
||||||
|
$fits = new \ReflectionMethod(PhotoOcrPreparer::class, 'gdFitsMemory');
|
||||||
|
$ok = $fits->invoke(app(PhotoOcrPreparer::class), 20000, 20000);
|
||||||
|
$this->assertFalse($ok);
|
||||||
|
} finally {
|
||||||
|
ini_set('memory_limit', (string) $prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function invokeResize(string $path, int $max): void
|
||||||
|
{
|
||||||
|
config(['coruna.ocr.max_edge' => $max]);
|
||||||
|
$resize = new \ReflectionMethod(PhotoOcrPreparer::class, 'resize');
|
||||||
|
$resize->invoke(app(PhotoOcrPreparer::class), $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function jpeg(int $width, int $height): string
|
||||||
|
{
|
||||||
|
$path = tempnam(sys_get_temp_dir(), 'ocrprep_').'.jpg';
|
||||||
|
$im = imagecreatetruecolor($width, $height);
|
||||||
|
imagejpeg($im, $path, 80);
|
||||||
|
imagedestroy($im);
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user