1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace App\Libraries;
7
8use App\Exceptions\ImageProcessorServiceException;
9use App\Models\Beatmapset;
10use GuzzleHttp\Client;
11use GuzzleHttp\Exception\GuzzleException;
12
13class ImageProcessorService
14{
15 private string $endpoint;
16
17 public function __construct(?string $endpoint = null)
18 {
19 $this->endpoint = $endpoint ?? $GLOBALS['cfg']['osu']['beatmap_processor']['thumbnailer'];
20 }
21
22 private static function isValidFormat($size)
23 {
24 return in_array($size, Beatmapset::coverSizes(), true);
25 }
26
27 public function optimize($src)
28 {
29 return $this->process('optim', $src);
30 }
31
32 public function resize($src, $format)
33 {
34 if (!self::isValidFormat($format)) {
35 throw new ImageProcessorServiceException('Invalid format requested.');
36 }
37
38 return $this->process("thumb/$format", $src);
39 }
40
41 // returns a handle instead of a filename to keep tmpfile alive
42 public function process($method, $src)
43 {
44 $src = preg_replace('/https?:\/\//', '', $src);
45 try {
46 $tmpFile = tmpfile();
47 $bytesWritten = fwrite(
48 $tmpFile,
49 (new Client())->request('GET', "{$this->endpoint}/{$method}/{$src}")->getBody()->getContents(),
50 );
51 } catch (GuzzleException $e) {
52 if (str_contains($e->getMessage(), 'VipsJpeg: Premature end of input file')) {
53 throw new ImageProcessorServiceException(
54 'Invalid image file',
55 ImageProcessorServiceException::INVALID_IMAGE,
56 $e,
57 );
58 }
59 throw new ImageProcessorServiceException('HTTP request failed!', 0, $e);
60 }
61
62 if ($bytesWritten === false || $bytesWritten < 100) {
63 throw new ImageProcessorServiceException("Error retrieving processed image: $method.");
64 }
65
66 return $tmpFile;
67 }
68}