Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Feature: Low Quality Image Placeholder #2558

Open
wants to merge 40 commits into
base: v6
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3d4c390
Add placeholder size variant
aSouchereau Aug 15, 2024
4befd9a
Add localization for placeholder variant
aSouchereau Aug 15, 2024
c7f762e
Add placeholder to size variants array
aSouchereau Aug 15, 2024
7128a04
Include placeholder in generate thumbs command
aSouchereau Aug 15, 2024
062bfc0
Add config option for enabling placeholders
aSouchereau Aug 15, 2024
f5e6912
Update tests for placeholder size variant
aSouchereau Aug 15, 2024
922e0df
Update variant naming strategy so all placeholders use webp extension
aSouchereau Aug 16, 2024
4c62c08
Temporary: add compression quality override
aSouchereau Aug 16, 2024
1229c8d
Add method to convert image files using GD
aSouchereau Aug 18, 2024
21439dd
Add workaround to call GdHandler convert function from imagickHandler…
aSouchereau Aug 18, 2024
088bc3d
Remove new, nonfunctional imagick convert method
aSouchereau Aug 20, 2024
2d10c00
Add placeholder encoding pipe
aSouchereau Aug 20, 2024
e71cf7c
Update url attribute to handle placeholders
aSouchereau Aug 20, 2024
5f398a7
Update thumb dto to include placeholder
aSouchereau Aug 20, 2024
2a3ef50
Fix attempting to get symLinkURL for nonexistent placeholder file
aSouchereau Aug 21, 2024
e4e2b16
Fix attempting to get file when placeholder is null
aSouchereau Aug 21, 2024
de0cfa3
Use correct BaseConfigMigration class
aSouchereau Aug 21, 2024
51f9c28
Remove call to undefined method
aSouchereau Aug 21, 2024
8e81f4f
Add pipe to other set of standalone pipes
aSouchereau Aug 22, 2024
c4f2c90
Add placeholder encoding to generate thumbs command
aSouchereau Aug 24, 2024
fdea538
Refactor encoding logic to separate class
aSouchereau Aug 24, 2024
9b4e6d6
Fix call on possibly null object
aSouchereau Aug 24, 2024
53ae8f8
Add test to check if placeholder is encoded from upload
aSouchereau Aug 26, 2024
ee6acd5
Separate compression step into its own function
aSouchereau Aug 29, 2024
a818b38
Undo add convertAndSave GdHandler function
aSouchereau Aug 29, 2024
c2b5a68
Add placeholder diagnostics check
aSouchereau Sep 1, 2024
ade2680
Remove old placeholder quality definition
aSouchereau Sep 2, 2024
4b1824d
Remove accidental whitespace
aSouchereau Sep 2, 2024
42572cb
Ignore phpstan on lines using dynamic property names
aSouchereau Sep 2, 2024
51063d0
Add gif upload failure workaround
aSouchereau Sep 14, 2024
3826a15
Add command to encode placeholders
aSouchereau Sep 14, 2024
7215905
Refactor placeholder encoder
aSouchereau Oct 7, 2024
be6f2d9
Remove encoding from generate thumbs
aSouchereau Oct 7, 2024
80151e6
Update encoding tests
aSouchereau Oct 8, 2024
c1ef199
Add placeholder to list of ignored private property errors
aSouchereau Oct 8, 2024
d6ddab9
Fix command returning wrong exit code
aSouchereau Oct 8, 2024
618a099
Add encoding step to legacy code path
aSouchereau Oct 8, 2024
fae77b8
fix gif issue, remove workaround
aSouchereau Oct 8, 2024
de5eaab
formatting
ildyria Oct 19, 2024
52fd83e
fix missing data in legacy resource
ildyria Oct 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/Actions/Diagnostics/Errors.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use App\Actions\Diagnostics\Pipes\Checks\IniSettingsCheck;
use App\Actions\Diagnostics\Pipes\Checks\MigrationCheck;
use App\Actions\Diagnostics\Pipes\Checks\PHPVersionCheck;
use App\Actions\Diagnostics\Pipes\Checks\PlaceholderExistsCheck;
use App\Actions\Diagnostics\Pipes\Checks\SmallMediumExistsCheck;
use App\Actions\Diagnostics\Pipes\Checks\SupporterCheck;
use App\Actions\Diagnostics\Pipes\Checks\TimezoneCheck;
Expand Down Expand Up @@ -44,6 +45,7 @@ class Errors
ForeignKeyListInfo::class,
DBIntegrityCheck::class,
SmallMediumExistsCheck::class,
PlaceholderExistsCheck::class,
CountSizeVariantsCheck::class,
SupporterCheck::class,
];
Expand Down
76 changes: 76 additions & 0 deletions app/Actions/Diagnostics/Pipes/Checks/PlaceholderExistsCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

namespace App\Actions\Diagnostics\Pipes\Checks;

use App\Contracts\DiagnosticPipe;
use App\Enum\SizeVariantType;
use App\Image\SizeVariantDimensionHelpers;
use App\Models\SizeVariant;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

/**
* Check if there are placeholders that can be generated or encoded.
*/
class PlaceholderExistsCheck implements DiagnosticPipe
{
public const NUM_PLACEHOLDER = 'num_placeholder';
public const MAX_NUM_PLACEHOLDER = 'max_num_placeholder';
public const NUM_UNENCODED_PLACEHOLDER = 'num_unencoded_placeholder';
public const INFO_MSG_MISSING = 'Info: Found %d placeholders that could be generated.';
public const INFO_LINE_MISSING = ' You can use `php artisan lychee:generate_thumbs placeholder %d` to generate them.';
public const INFO_MSG_UNENCODED = 'Info: Found %d placeholder images that have not been encoded.';
public const INFO_LINE_UNENCODED = ' You can use `php artisan lychee:encode_placeholders %d` to encode them.';

/**
* {@inheritDoc}
*/
public function handle(array &$data, \Closure $next): array
{
if (!Schema::hasTable('configs') || !Schema::hasTable('size_variants')) {
// @codeCoverageIgnoreStart
return $next($data);
// @codeCoverageIgnoreEnd
}

$svHelpers = new SizeVariantDimensionHelpers();
if (!$svHelpers->isEnabledByConfiguration(SizeVariantType::PLACEHOLDER)) {
return $next($data);
}

$result = DB::query()
->selectSub(
SizeVariant::query()
->selectRaw('COUNT(*)')
->where('type', '=', SizeVariantType::PLACEHOLDER),
self::NUM_PLACEHOLDER
)
->selectSub(
SizeVariant::query()
->selectRaw('COUNT(*)')
->where('type', '=', SizeVariantType::ORIGINAL),
self::MAX_NUM_PLACEHOLDER
)
->selectSub(
SizeVariant::query()
->selectRaw('COUNT(*)')
->where('short_path', 'LIKE', '%placeholder/%'),
self::NUM_UNENCODED_PLACEHOLDER
)
->first();

$num = $result->{self::NUM_UNENCODED_PLACEHOLDER}; // @phpstan-ignore-line
if ($num > 0) {
$data[] = sprintf(self::INFO_MSG_UNENCODED, $num);
$data[] = sprintf(self::INFO_LINE_UNENCODED, $num);
}

$num = $result->{self::MAX_NUM_PLACEHOLDER} - $result->{self::NUM_PLACEHOLDER}; // @phpstan-ignore-line
if ($num > 0) {
$data[] = sprintf(self::INFO_MSG_MISSING, $num);
$data[] = sprintf(self::INFO_LINE_MISSING, $num);
}

return $next($data);
}
}
2 changes: 2 additions & 0 deletions app/Actions/Photo/Create.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ private function handleStandalone(InitDTO $initDTO): Photo
Shared\Save::class,
Standalone\CreateOriginalSizeVariant::class,
Standalone\CreateSizeVariants::class,
Standalone\EncodePlaceholder::class,
Standalone\ReplaceOriginalWithBackup::class,
Shared\UploadSizeVariantsToS3::class,
];
Expand Down Expand Up @@ -253,6 +254,7 @@ private function handlePhotoLivePartner(InitDTO $initDTO): Photo
Shared\Save::class,
Standalone\CreateOriginalSizeVariant::class,
Standalone\CreateSizeVariants::class,
Standalone\EncodePlaceholder::class,
Standalone\ReplaceOriginalWithBackup::class,
Shared\UploadSizeVariantsToS3::class,
];
Expand Down
26 changes: 26 additions & 0 deletions app/Actions/Photo/Pipes/Standalone/EncodePlaceholder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App\Actions\Photo\Pipes\Standalone;

use App\Contracts\PhotoCreate\StandalonePipe;
use App\DTO\PhotoCreate\StandaloneDTO;
use App\Exceptions\MediaFileOperationException;
use App\Image\PlaceholderEncoder;

class EncodePlaceholder implements StandalonePipe
{
public function handle(StandaloneDTO $state, \Closure $next): StandaloneDTO
{
try {
$placeholderEncoder = new PlaceholderEncoder();
$placeholder = $state->getPhoto()->size_variants->getPlaceholder();
if ($placeholder !== null) {
$placeholderEncoder->do($placeholder);
}

return $next($state);
} catch (\ErrorException $e) {
throw new MediaFileOperationException('Failed to encode placeholder to base64', $e);
}
}
}
9 changes: 9 additions & 0 deletions app/Assets/BaseSizeVariantNamingStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ abstract class BaseSizeVariantNamingStrategy extends AbstractSizeVariantNamingSt
*/
public const THUMB_EXTENSION = '.jpeg';

/**
* The file extension which is always used by placeholder variants.
*/
public const PLACEHOLDER_EXTENSION = '.webp';

/**
* Returns the file extension incl. the preceding dot.
*
Expand All @@ -32,6 +37,10 @@ protected function generateExtension(SizeVariantType $sizeVariant): string
return self::THUMB_EXTENSION;
}

if ($sizeVariant === SizeVariantType::PLACEHOLDER) {
return self::PLACEHOLDER_EXTENSION;
}

if ($this->extension === '') {
throw new MissingValueException('extension');
}
Expand Down
63 changes: 63 additions & 0 deletions app/Console/Commands/ImageProcessing/EncodePlaceholders.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace App\Console\Commands\ImageProcessing;

use App\Exceptions\UnexpectedException;
use App\Image\PlaceholderEncoder;
use App\Models\SizeVariant;
use Illuminate\Console\Command;
use Safe\Exceptions\InfoException;
use function Safe\set_time_limit;

class EncodePlaceholders extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'lychee:encode_placeholders {limit=5 : number of photos to encode placeholders for} {tm=600 : timeout time requirement}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Encode placeholders if size variant exists and image has not been encoded';

/**
* Execute the console command.
*/
public function handle(): int
{
try {
$limit = (int) $this->argument('limit');
$timeout = (int) $this->argument('tm');

try {
set_time_limit($timeout);
} catch (InfoException) {
// Silently do nothing, if `set_time_limit` is denied.
}

$placeholders = SizeVariant::query()
->where('short_path', 'LIKE', '%placeholder/%')
->limit($limit)
->get();
if (count($placeholders) === 0) {
$this->line('No placeholders require encoding.');

return 0;
}

$placeholderEncoder = new PlaceholderEncoder();
foreach ($placeholders as $placeholder) {
$placeholderEncoder->do($placeholder);
}

return 0;
} catch (\Throwable $e) {
throw new UnexpectedException($e);
}
}
}
1 change: 1 addition & 0 deletions app/Console/Commands/ImageProcessing/GenerateThumbs.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class GenerateThumbs extends Command
* @var array<string,SizeVariantType>
*/
public const SIZE_VARIANTS = [
'placeholder' => SizeVariantType::PLACEHOLDER,
'thumb' => SizeVariantType::THUMB,
'thumb2x' => SizeVariantType::THUMB2X,
'small' => SizeVariantType::SMALL,
Expand Down
3 changes: 3 additions & 0 deletions app/Enum/SizeVariantType.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum SizeVariantType: int
case SMALL = 4;
case THUMB2X = 5;
case THUMB = 6;
case PLACEHOLDER = 7;

/**
* Given a sizeVariantType return the associated name.
Expand All @@ -25,6 +26,7 @@ enum SizeVariantType: int
public function name(): string
{
return match ($this) {
self::PLACEHOLDER => 'placeholder',
self::THUMB => 'thumb',
self::THUMB2X => 'thumb2x',
self::SMALL => 'small',
Expand All @@ -43,6 +45,7 @@ public function name(): string
public function localization(): string
{
return match ($this) {
self::PLACEHOLDER => __('lychee.PHOTO_PLACEHOLDER'),
self::THUMB => __('lychee.PHOTO_THUMB'),
self::THUMB2X => __('lychee.PHOTO_THUMB_HIDPI'),
self::SMALL => __('lychee.PHOTO_SMALL'),
Expand Down
3 changes: 3 additions & 0 deletions app/Http/Resources/Models/SizeVariantsResouce.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class SizeVariantsResouce extends Data
public ?SizeVariantResource $small;
public ?SizeVariantResource $thumb2x;
public ?SizeVariantResource $thumb;
public ?SizeVariantResource $placeholder;

public function __construct(Photo $photo)
{
Expand All @@ -34,6 +35,7 @@ public function __construct(Photo $photo)
$small2x = $size_variants?->getSizeVariant(SizeVariantType::SMALL2X);
$thumb = $size_variants?->getSizeVariant(SizeVariantType::THUMB);
$thumb2x = $size_variants?->getSizeVariant(SizeVariantType::THUMB2X);
$placeholder = $size_variants?->getSizeVariant(SizeVariantType::PLACEHOLDER);

$this->medium = $medium?->toResource();
$this->medium2x = $medium2x?->toResource();
Expand All @@ -42,5 +44,6 @@ public function __construct(Photo $photo)
$this->small2x = $small2x?->toResource();
$this->thumb = $thumb?->toResource();
$this->thumb2x = $thumb2x?->toResource();
$this->placeholder = $placeholder?->toResource();
}
}
Loading
Loading