teknolis / app /Services /ReplicateEnhanceService.php
Codex
Deploy latest assistant, DeepSeek defaults, and automation updates
74acf19
Raw
History Blame Contribute Delete
5.42 kB
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use RuntimeException;
/**
* Image enhancement via Replicate free-tier models.
*
* Supported models (Try-for-Free):
* - topazlabs/image-upscale β€” professional upscaling (2x/4x/6x)
* - sczhou/codeformer β€” face restoration for old/AI photos
*
* Polls synchronously (upscaling typically takes 10-40s).
*/
class ReplicateEnhanceService
{
private string $apiKey;
private string $baseUrl;
public function __construct()
{
$this->apiKey = (string) config('services.replicate.api_key', '');
$this->baseUrl = (string) config('services.replicate.base_url', 'https://api.replicate.com/v1');
}
public function isAvailable(): bool
{
return $this->apiKey !== '';
}
/**
* Upscale image using topazlabs/image-upscale.
* @param string $imageUrl Absolute https:// URL
* @param int $factor 2, 4, or 6
* @return string Output image URL from Replicate CDN
*/
public function upscaleImage(string $imageUrl, int $factor = 2): string
{
$normalizedFactor = match ($factor) {
4 => '4x',
6 => '6x',
default => '2x',
};
return $this->runModel('topazlabs/image-upscale', [
'image' => $imageUrl,
'upscale_factor' => $normalizedFactor,
'output_format' => 'png',
]);
}
/**
* Restore faces using sczhou/codeformer.
* @param string $imageUrl Absolute https:// URL
* @param float $fidelity 0.0 (restore more) – 1.0 (preserve original)
* @return string Output image URL from Replicate CDN
*/
public function restoreFace(string $imageUrl, float $fidelity = 0.7): string
{
return $this->runModel('sczhou/codeformer', [
'image' => $imageUrl,
'codeformer_fidelity' => round($fidelity, 2),
'background_enhance' => true,
'face_upsample' => true,
'upscale' => 2,
]);
}
public function portraitEnhance(string $imageUrl, int $factor = 2, float $fidelity = 0.7): string
{
$upscaledUrl = $this->upscaleImage($imageUrl, $factor);
return $this->restoreFace($upscaledUrl, $fidelity);
}
// ─── Internal ──────────────────────────────────────────────────────────
private function runModel(string $model, array $input): string
{
if (! $this->isAvailable()) {
throw new RuntimeException('REPLICATE_API_KEY belum diset.');
}
// Ensure image URL is absolute
if (! str_starts_with($input['image'] ?? '', 'http')) {
$input['image'] = rtrim((string) config('app.url', ''), '/') . '/' . ltrim($input['image'], '/');
}
$endpoint = rtrim($this->baseUrl, '/') . '/models/' . $model . '/predictions';
$payload = ['input' => $input];
$response = Http::withToken($this->apiKey)->timeout(30)->post($endpoint, $payload);
if ($response->status() === 429) {
sleep(3);
$response = Http::withToken($this->apiKey)->timeout(30)->post($endpoint, $payload);
}
if ($response->failed()) {
$status = $response->status();
$detail = $response->json('detail') ?? $response->body();
if ($status === 429) {
throw new RuntimeException('Replicate rate limit. Tunggu beberapa detik lalu coba lagi.');
}
throw new RuntimeException('Replicate enhance gagal: ' . Str::limit((string) $detail, 200));
}
$id = $response->json('id');
if (blank($id)) {
throw new RuntimeException('Replicate: prediction ID tidak ada di response.');
}
return $this->pollForUrl($id, $model);
}
private function pollForUrl(string $id, string $model): string
{
$maxWait = 120;
$interval = 3;
$elapsed = 0;
$endpoint = rtrim($this->baseUrl, '/') . '/predictions/' . $id;
while ($elapsed < $maxWait) {
sleep($interval);
$elapsed += $interval;
$r = Http::withToken($this->apiKey)->timeout(15)->get($endpoint);
if ($r->failed()) {
continue;
}
$status = $r->json('status');
if ($status === 'succeeded') {
$output = $r->json('output');
$url = is_array($output) ? ($output[0] ?? '') : (string) ($output ?? '');
if (filled($url)) {
return $url;
}
throw new RuntimeException('Replicate enhance: output URL kosong.');
}
if (in_array($status, ['failed', 'canceled'], true)) {
$err = $r->json('error') ?? 'Unknown error';
Log::warning('ReplicateEnhanceService: failed', ['id' => $id, 'model' => $model, 'error' => $err]);
throw new RuntimeException('Replicate enhance gagal: ' . Str::limit($err, 150));
}
}
throw new RuntimeException('Replicate enhance timeout (>2 menit). Coba lagi.');
}
}