Skip to content

Commit

Permalink
[stacked 7] Add standalone flow. (LycheeOrg#2371)
Browse files Browse the repository at this point in the history
  • Loading branch information
ildyria authored Apr 11, 2024
1 parent 3c4cc5f commit 069e735
Show file tree
Hide file tree
Showing 19 changed files with 514 additions and 9 deletions.
45 changes: 45 additions & 0 deletions app/Actions/Photo/Create.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
use App\Actions\Photo\Pipes\Duplicate;
use App\Actions\Photo\Pipes\Init;
use App\Actions\Photo\Pipes\Shared;
use App\Actions\Photo\Pipes\Standalone;
use App\Assets\Features;
use App\Contracts\Exceptions\LycheeException;
use App\Contracts\Models\AbstractAlbum;
use App\DTO\ImportMode;
use App\DTO\ImportParam;
use App\DTO\PhotoCreate\DuplicateDTO;
use App\DTO\PhotoCreate\InitDTO;
use App\DTO\PhotoCreate\StandaloneDTO;
use App\Exceptions\PhotoResyncedException;
use App\Exceptions\PhotoSkippedException;
use App\Image\Files\NativeLocalFile;
Expand Down Expand Up @@ -80,6 +82,10 @@ public function add(NativeLocalFile $sourceFile, ?AbstractAlbum $album, ?int $fi
return $this->handleDuplicate($initDTO);
}

if ($initDTO->livePartner === null) {
return $this->handleStandalone($initDTO);
}

$oldCodePath = new LegacyPhotoCreate($this->strategyParameters->importMode, $this->strategyParameters->intendedOwnerId);

return $oldCodePath->add($sourceFile, $album, $fileLastModifiedTime);
Expand Down Expand Up @@ -122,4 +128,43 @@ private function handleDuplicate(InitDTO $initDTO): Photo
throw $e;
}
}

private function handleStandalone(InitDTO $initDTO): Photo
{
$dto = new StandaloneDTO($initDTO);

$pipes = [
Standalone\FixTimeStamps::class,
Standalone\InitNamingStrategy::class,
Shared\HydrateMetadata::class,
Shared\SetStarred::class,
Shared\SetParentAndOwnership::class,
Standalone\SetOriginalChecksum::class,
Standalone\FetchSourceImage::class,
Standalone\ExtractGoogleMotionPictures::class,
Standalone\PlacePhoto::class,
Standalone\PlaceGoogleMotionVideo::class,
Standalone\SetChecksum::class,
Shared\Save::class,
Standalone\CreateOriginalSizeVariant::class,
Standalone\CreateSizeVariants::class,
];

try {
return app(Pipeline::class)
->send($dto)
->through($pipes)
->thenReturn()
->photo;
} catch (LycheeException $e) {
// If source file could not be put into final destination, remove
// freshly created photo from DB to avoid having "zombie" entries.
try {
$dto->photo->delete();
} catch (\Throwable) {
// Sic! If anything goes wrong here, we still throw the original exception
}
throw $e;
}
}
}
3 changes: 2 additions & 1 deletion app/Actions/Photo/Pipes/Shared/HydrateMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Contracts\PhotoCreate\SharedPipe;
use App\DTO\PhotoCreate\DuplicateDTO;
use App\DTO\PhotoCreate\StandaloneDTO;

/**
* Hydrates meta-info of the media file from the
Expand All @@ -22,7 +23,7 @@
*/
class HydrateMetadata implements SharedPipe
{
public function handle(DuplicateDTO $state, \Closure $next): DuplicateDTO
public function handle(DuplicateDTO|StandaloneDTO $state, \Closure $next): DuplicateDTO|StandaloneDTO
{
if ($state->photo->title === null) {
$state->photo->title = $state->exifInfo->title;
Expand Down
3 changes: 2 additions & 1 deletion app/Actions/Photo/Pipes/Shared/NotifyAlbums.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
use App\Actions\User\Notify;
use App\Contracts\PhotoCreate\SharedPipe;
use App\DTO\PhotoCreate\DuplicateDTO;
use App\DTO\PhotoCreate\StandaloneDTO;

/**
* Notify by email if a picture has been added.
*/
class NotifyAlbums implements SharedPipe
{
public function handle(DuplicateDTO $state, \Closure $next): DuplicateDTO
public function handle(DuplicateDTO|StandaloneDTO $state, \Closure $next): DuplicateDTO|StandaloneDTO
{
if ($state->photo->album_id !== null) {
$notify = new Notify();
Expand Down
3 changes: 2 additions & 1 deletion app/Actions/Photo/Pipes/Shared/Save.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

use App\Contracts\PhotoCreate\SharedPipe;
use App\DTO\PhotoCreate\DuplicateDTO;
use App\DTO\PhotoCreate\StandaloneDTO;

class Save implements SharedPipe
{
public function handle(DuplicateDTO $state, \Closure $next): DuplicateDTO
public function handle(DuplicateDTO|StandaloneDTO $state, \Closure $next): DuplicateDTO|StandaloneDTO
{
$state->photo->save();

Expand Down
3 changes: 2 additions & 1 deletion app/Actions/Photo/Pipes/Shared/SetParentAndOwnership.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

use App\Contracts\PhotoCreate\SharedPipe;
use App\DTO\PhotoCreate\DuplicateDTO;
use App\DTO\PhotoCreate\StandaloneDTO;
use App\Models\Album;

class SetParentAndOwnership implements SharedPipe
{
public function handle(DuplicateDTO $state, \Closure $next): DuplicateDTO
public function handle(DuplicateDTO|StandaloneDTO $state, \Closure $next): DuplicateDTO|StandaloneDTO
{
if ($state->album instanceof Album) {
$state->photo->album_id = $state->album->id;
Expand Down
3 changes: 2 additions & 1 deletion app/Actions/Photo/Pipes/Shared/SetStarred.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

use App\Contracts\PhotoCreate\SharedPipe;
use App\DTO\PhotoCreate\DuplicateDTO;
use App\DTO\PhotoCreate\StandaloneDTO;

class SetStarred implements SharedPipe
{
public function handle(DuplicateDTO $state, \Closure $next): DuplicateDTO
public function handle(DuplicateDTO|StandaloneDTO $state, \Closure $next): DuplicateDTO|StandaloneDTO
{
// Adopt settings of duplicated photo acc. to target album
$state->photo->is_starred = $state->is_starred;
Expand Down
30 changes: 30 additions & 0 deletions app/Actions/Photo/Pipes/Standalone/CreateOriginalSizeVariant.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace App\Actions\Photo\Pipes\Standalone;

use App\Contracts\PhotoCreate\StandalonePipe;
use App\DTO\ImageDimension;
use App\DTO\PhotoCreate\StandaloneDTO;
use App\Enum\SizeVariantType;

class CreateOriginalSizeVariant implements StandalonePipe
{
public function handle(StandaloneDTO $state, \Closure $next): StandaloneDTO
{
// Create original size variant of photo
// If the image has been loaded (and potentially auto-rotated)
// take the dimension from the image.
// As a fallback for media files from which no image could be extracted (e.g. unsupported file formats) we use the EXIF data.
$imageDim = $state->sourceImage?->isLoaded() ?
$state->sourceImage->getDimensions() :
new ImageDimension($state->exifInfo->width, $state->exifInfo->height);
$state->photo->size_variants->create(
SizeVariantType::ORIGINAL,
$state->targetFile->getRelativePath(),
$imageDim,
$state->streamStat->bytes
);

return $next($state);
}
}
33 changes: 33 additions & 0 deletions app/Actions/Photo/Pipes/Standalone/CreateSizeVariants.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace App\Actions\Photo\Pipes\Standalone;

use App\Contracts\Models\SizeVariantFactory;
use App\Contracts\PhotoCreate\StandalonePipe;
use App\DTO\PhotoCreate\StandaloneDTO;
use App\Exceptions\Handler;

class CreateSizeVariants implements StandalonePipe
{
public function handle(StandaloneDTO $state, \Closure $next): StandaloneDTO
{
// Create remaining size variants if we were able to successfully
// extract a reference image
if ($state->sourceImage?->isLoaded()) {
try {
/** @var SizeVariantFactory $sizeVariantFactory */
$sizeVariantFactory = resolve(SizeVariantFactory::class);
$sizeVariantFactory->init($state->photo, $state->sourceImage, $state->namingStrategy);
$sizeVariantFactory->createSizeVariants();
} catch (\Throwable $t) {
// Don't re-throw the exception, because we do not want the
// import to fail completely only due to missing size variants.
// There are just too many options why the creation of size
// variants may fail.
Handler::reportSafely($t);
}
}

return $next($state);
}
}
35 changes: 35 additions & 0 deletions app/Actions/Photo/Pipes/Standalone/ExtractGoogleMotionPictures.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\Actions\Photo\Pipes\Standalone;

use App\Contracts\PhotoCreate\StandalonePipe;
use App\DTO\PhotoCreate\StandaloneDTO;
use App\Exceptions\Handler;
use App\Image\Files\TemporaryLocalFile;
use App\Image\Handlers\GoogleMotionPictureHandler;

class ExtractGoogleMotionPictures implements StandalonePipe
{
public function handle(StandaloneDTO $state, \Closure $next): StandaloneDTO
{
if ($state->exifInfo->microVideoOffset === 0) {
return $next($state);
}

// Handle Google Motion Pictures
// We must extract the video stream from the original (local)
// file and stash it away, before the original file is moved into
// its (potentially remote) final position
try {
$state->tmpVideoFile = new TemporaryLocalFile(GoogleMotionPictureHandler::FINAL_VIDEO_FILE_EXTENSION, $state->sourceFile->getBasename());
$gmpHandler = new GoogleMotionPictureHandler();
$gmpHandler->load($state->sourceFile, $state->exifInfo->microVideoOffset);
$gmpHandler->saveVideoStream($state->tmpVideoFile);
} catch (\Throwable $e) {
Handler::reportSafely($e);
$state->tmpVideoFile = null;
}

return $next($state);
}
}
40 changes: 40 additions & 0 deletions app/Actions/Photo/Pipes/Standalone/FetchSourceImage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace App\Actions\Photo\Pipes\Standalone;

use App\Contracts\PhotoCreate\StandalonePipe;
use App\DTO\PhotoCreate\StandaloneDTO;
use App\Exceptions\Handler;
use App\Image\Handlers\ImageHandler;
use App\Image\Handlers\VideoHandler;

class FetchSourceImage implements StandalonePipe
{
public function handle(StandaloneDTO $state, \Closure $next): StandaloneDTO
{
try {
if ($state->photo->isVideo()) {
$videoHandler = new VideoHandler();
$videoHandler->load($state->sourceFile);
$position = is_numeric($state->photo->aperture) ? floatval($state->photo->aperture) / 2 : 0.0;
$state->sourceImage = $videoHandler->extractFrame($position);
} else {
// Load source image if it is a supported photo format
$state->sourceImage = new ImageHandler();
$state->sourceImage->load($state->sourceFile);
}
} catch (\Throwable $t) {
// This may happen for videos if FFmpeg is not available to
// extract a still frame, or for raw files (Imagick may be
// able to convert them to jpeg, but there are no
// guarantees, and Imagick may not be available).
$state->sourceImage = null;

// Log an error without failing.
Handler::reportSafely($t);
}

return $next($state);
}
}

19 changes: 19 additions & 0 deletions app/Actions/Photo/Pipes/Standalone/FixTimeStamps.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace App\Actions\Photo\Pipes\Standalone;

use App\Contracts\PhotoCreate\StandalonePipe;
use App\DTO\PhotoCreate\StandaloneDTO;

/**
* Set the timestamps of the creation and updated_at time.
*/
class FixTimeStamps implements StandalonePipe
{
public function handle(StandaloneDTO $state, \Closure $next): StandaloneDTO
{
$state->photo->updateTimestamps();

return $next($state);
}
}
21 changes: 21 additions & 0 deletions app/Actions/Photo/Pipes/Standalone/InitNamingStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Actions\Photo\Pipes\Standalone;

use App\Contracts\Models\AbstractSizeVariantNamingStrategy;
use App\Contracts\PhotoCreate\StandalonePipe;
use App\DTO\PhotoCreate\StandaloneDTO;

class InitNamingStrategy implements StandalonePipe
{
public function handle(StandaloneDTO $state, \Closure $next): StandaloneDTO
{
$state->namingStrategy = resolve(AbstractSizeVariantNamingStrategy::class);
$state->namingStrategy->setPhoto($state->photo);
$state->namingStrategy->setExtension(
$state->sourceFile->getOriginalExtension()
);

return $next($state);
}
}
32 changes: 32 additions & 0 deletions app/Actions/Photo/Pipes/Standalone/PlaceGoogleMotionVideo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Actions\Photo\Pipes\Standalone;

use App\Contracts\PhotoCreate\StandalonePipe;
use App\DTO\PhotoCreate\StandaloneDTO;
use App\Image\Files\FlysystemFile;

class PlaceGoogleMotionVideo implements StandalonePipe
{
public function handle(StandaloneDTO $state, \Closure $next): StandaloneDTO
{
// If we have a temporary video file from a Google Motion Picture,
// we must move the preliminary extracted video file next to the
// final target file
if ($state->tmpVideoFile !== null) {
$videoTargetPath =
pathinfo($state->targetFile->getRelativePath(), PATHINFO_DIRNAME) .
'/' .
pathinfo($state->targetFile->getRelativePath(), PATHINFO_FILENAME) .
$state->tmpVideoFile->getExtension();
$videoTargetFile = new FlysystemFile($state->targetFile->getDisk(), $videoTargetPath);
$videoTargetFile->write($state->tmpVideoFile->read());
$state->photo->live_photo_short_path = $videoTargetFile->getRelativePath();
$state->tmpVideoFile->close();
$state->tmpVideoFile->delete();
$state->tmpVideoFile = null;
}

return $next($state);
}
}
Loading

0 comments on commit 069e735

Please sign in to comment.