selectFaxListNotExistsThumb(); $fileCnt = 0; $receiver = 'uds_tiff'; if (empty($res)) { CLI::write('No target fax images.', 'yellow'); return; } foreach ($res as $row) { $fileCnt++; CLI::write("\n\n[{$fileCnt}] Processing...", 'green'); $mid = $row['mid'] ?? null; $file_name = $row['file_name'] ?? ''; $save_path = $row['save_path'] ?? ''; $caller_no = $row['caller_no'] ?? ''; $callee_no = $row['callee_no'] ?? ''; $tiff_file_name = $row['file_name'] ?? ''; $tiff_file_size = $row['file_size'] ?? ''; $recv_time = $row['recv_time'] ?? ''; $save_time = $row['save_time'] ?? ''; if (empty($save_path) || empty($file_name)) { CLI::write(' - skip: save_path or file_name empty', 'yellow'); continue; } // ✅ pathinfo로 안전하게 처리 $ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION)); $base = pathinfo($file_name, PATHINFO_FILENAME); if (!in_array($ext, ['tif', 'tiff'], true)) { CLI::write(" - skip: not tif/tiff ({$ext})", 'yellow'); continue; } if (substr($save_path, -1) !== '/') { $save_path .= '/'; } $tiffPath = $save_path . $file_name; if (!is_file($tiffPath)) { CLI::write(" - skip: file not found ({$tiffPath})", 'yellow'); continue; } CLI::write(" - TIFF: {$tiffPath}"); try { // ✅ TIFF 다중 페이지 안전 처리 $im = new \Imagick(); $im->readImage($tiffPath); // coalesce: 멀티프레임 안정화(특히 tiff/gif 계열) $frames = $im->coalesceImages(); $index = 0; foreach ($frames as $frame) { try { // 페이지별 정보 $image_width = (int) $frame->getImageWidth(); $image_height = (int) $frame->getImageHeight(); $image_size = (int) $frame->getImageLength(); // 출력 경로 $jpgName = "{$base}_{$index}.jpg"; $jpgPath = $save_path . $jpgName; $thumbName = "{$base}_{$index}_thumb.jpg"; $thumbPath = $save_path . $thumbName; // ✅ JPG로 변환(페이지별로 frame clone 사용 권장) $page = clone $frame; // 이미지 포맷/품질(필요시 조정) $page->setImageFormat('jpeg'); $page->setImageCompression(\Imagick::COMPRESSION_JPEG); $page->setImageCompressionQuality(85); // ✅ 리사이즈 (가로 800 제한) if ($image_width > 800) { $resize_width = 800; $ratio = $resize_width / max(1, $image_width); $resize_height = (int) round($image_height * $ratio); $page->resizeImage($resize_width, $resize_height, \Imagick::FILTER_LANCZOS, 1, true); } if (!$page->writeImage($jpgPath)) { CLI::write(" - fail: write jpg ({$jpgPath})", 'red'); $page->clear(); $page->destroy(); $index++; continue; } // ✅ QR scan (실패해도 계속) $qrcode_data = ''; try { $qrcode_data = (string) $qrCode->scan($jpgPath); } catch (\Throwable $e) { log_message('error', 'QR scan failed: ' . $e->getMessage()); } // ✅ 썸네일은 clone으로 (원본/페이지 훼손 방지) $thumb = clone $page; $thumb->thumbnailImage(105, 80, true); if (!$thumb->writeImage($thumbPath)) { CLI::write(" - fail: write thumb ({$thumbPath})", 'red'); // 그래도 JPG는 만들어졌으니 다음 진행은 정책에 따라 선택 } CLI::write(" - JPG: {$jpgPath}"); CLI::write(" - THUMB: {$thumbPath}"); // ✅ DB 저장 (기존 시그니처 유지) $faxModel->insertFaxImgs( $mid, $jpgName, $save_path, $thumbName, $image_width, $image_height, $image_size, $qrcode_data, $caller_no, $callee_no, $tiff_file_name, $tiffPath, $tiff_file_size, $recv_time, $save_time, $receiver ); // ✅ 권한 처리 (에러 숨기지 말고 로그) if (!@chmod($jpgPath, 0666)) { log_message('error', "chmod failed: {$jpgPath}"); } if (!@chmod($thumbPath, 0666)) { log_message('error', "chmod failed: {$thumbPath}"); } log_message('debug', $jpgPath); log_message('debug', $thumbPath); // ✅ 메모리 정리(프레임 단위) $thumb->clear(); $thumb->destroy(); $page->clear(); $page->destroy(); } catch (\Throwable $e) { log_message('error', 'Frame 처리 실패: ' . $e->getMessage()); } // 다음 페이지 $index++; } // ✅ 전체 메모리 정리 $frames->clear(); $frames->destroy(); $im->clear(); $im->destroy(); gc_collect_cycles(); CLI::write(" - done.\n", 'green'); } catch (\Throwable $e) { // 기존 writeLog 사용 중이면 그걸로 교체 가능 log_message('error', 'TIFF 처리 실패: ' . $e->getMessage()); CLI::write(" - error: " . $e->getMessage(), 'red'); } } } }