forked from LycheeOrg/Lychee
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[stacked 9] Support video partners. (LycheeOrg#2373)
- Loading branch information
Showing
6 changed files
with
239 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?php | ||
|
||
namespace App\Actions\Photo\Pipes\VideoPartner; | ||
|
||
use App\Contracts\PhotoCreate\VideoPartnerPipe; | ||
use App\DTO\PhotoCreate\VideoPartnerDTO; | ||
|
||
class GetVideoPath implements VideoPartnerPipe | ||
{ | ||
public function handle(VideoPartnerDTO $state, \Closure $next): VideoPartnerDTO | ||
{ | ||
$photoFile = $state->photo->size_variants->getOriginal()->getFile(); | ||
$photoPath = $photoFile->getRelativePath(); | ||
$photoExt = $photoFile->getOriginalExtension(); | ||
$videoExt = $state->videoFile->getOriginalExtension(); | ||
$state->videoPath = substr($photoPath, 0, -strlen($photoExt)) . $videoExt; | ||
|
||
return $next($state); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
<?php | ||
|
||
namespace App\Actions\Photo\Pipes\VideoPartner; | ||
|
||
use App\Actions\Diagnostics\Pipes\Checks\BasicPermissionCheck; | ||
use App\Contracts\Models\AbstractSizeVariantNamingStrategy; | ||
use App\Contracts\PhotoCreate\VideoPartnerPipe; | ||
use App\DTO\PhotoCreate\VideoPartnerDTO; | ||
use App\Exceptions\ConfigurationException; | ||
use App\Exceptions\Handler; | ||
use App\Exceptions\Internal\LycheeAssertionError; | ||
use App\Exceptions\MediaFileOperationException; | ||
use App\Image\Files\FlysystemFile; | ||
use App\Image\Files\NativeLocalFile; | ||
use App\Image\StreamStat; | ||
|
||
/** | ||
* Puts the video source file into the final position at video target file. | ||
* | ||
* We need to distinguish two cases: | ||
* | ||
* A) The video source file is a native, local file. | ||
* | ||
* In this case, the video file has just been uploaded (and the photo | ||
* partner is already on the target disk). | ||
* The video file must be put onto the target disk, the same way as it | ||
* would for a stand-alone upload. | ||
* | ||
* B) The video source file is a FlysystemFile, too. | ||
* | ||
* In this case, the video file is already on the final disk, but in the | ||
* wrong position. | ||
* In that case we can and must rename it. | ||
* Note, that we must take a little extra care, if the final disk is also | ||
* local and the video file has been imported via a symbolic link. | ||
* We want to rename the symbolic link, not the target of the symbolic | ||
* link. | ||
* | ||
* @param FlysystemFile $videoTargetFile | ||
* | ||
* @return StreamStat|null statistics about the uploaded video file; `null` if no file has been uploaded, but renamed in-place | ||
* | ||
* @throws MediaFileOperationException | ||
* @throws ConfigurationException | ||
*/ | ||
class PlaceVideo implements VideoPartnerPipe | ||
{ | ||
public function handle(VideoPartnerDTO $state, \Closure $next): VideoPartnerDTO | ||
{ | ||
$videoTargetFile = new FlysystemFile(AbstractSizeVariantNamingStrategy::getImageDisk(), $state->videoPath); | ||
|
||
try { | ||
if ($state->videoFile instanceof NativeLocalFile) { | ||
// This is case A (see above) | ||
// The code is very similar to | ||
// AddStandaloneStrategy::putSourceIntoFinalDestination() | ||
// except that we can skip the part about normalization of | ||
// orientation, because we don't support that for videos. | ||
if ($state->shallImportViaSymlink) { | ||
if (!$videoTargetFile->isLocalFile()) { | ||
throw new ConfigurationException('Symlinking is only supported on local filesystems'); | ||
} | ||
$targetPath = $videoTargetFile->toLocalFile()->getPath(); | ||
$sourcePath = $state->videoFile->getRealPath(); | ||
// For symlinks we must manually create a non-existing | ||
// parent directory. | ||
// This mimics the behaviour of Flysystem for regular files. | ||
$targetDirectory = pathinfo($targetPath, PATHINFO_DIRNAME); | ||
if (!is_dir($targetDirectory)) { | ||
$umask = \umask(0); | ||
\Safe\mkdir($targetDirectory, BasicPermissionCheck::getConfiguredDirectoryPerm(), true); | ||
\umask($umask); | ||
} | ||
\Safe\symlink($sourcePath, $targetPath); | ||
$streamStat = StreamStat::createFromLocalFile($state->videoFile); | ||
} else { | ||
$streamStat = $videoTargetFile->write($state->videoFile->read(), true); | ||
$state->videoFile->close(); | ||
$videoTargetFile->close(); | ||
if ($state->shallDeleteImported) { | ||
// This may throw an exception, if the original has been | ||
// readable, but is not writable | ||
// In this case, the media file will have been copied, but | ||
// cannot be "moved". | ||
try { | ||
$state->videoFile->delete(); | ||
} catch (MediaFileOperationException $e) { | ||
// If deletion failed, we do not cancel the whole | ||
// import, but fall back to copy-semantics and | ||
// log the exception | ||
Handler::reportSafely($e); | ||
} | ||
} | ||
} | ||
} elseif ($state->videoFile instanceof FlysystemFile) { | ||
// It seems as if Flysystem calls a primitive \rename under the | ||
// hood, if the storage adapter is the `Local` adapter. | ||
// This also works for symbolic links, so we are good here. | ||
$state->videoFile->move($videoTargetFile->getRelativePath()); | ||
$streamStat = null; | ||
} else { | ||
throw new LycheeAssertionError('Unexpected type of $videoFile: ' . get_class($state->videoFile)); | ||
} | ||
|
||
$state->streamStat = $streamStat; | ||
} catch (\ErrorException $e) { | ||
throw new MediaFileOperationException('Could move/copy/symlink source file to final destination', $e); | ||
} | ||
|
||
return $next($state); | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
app/Actions/Photo/Pipes/VideoPartner/UpdateLivePartner.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php | ||
|
||
namespace App\Actions\Photo\Pipes\VideoPartner; | ||
|
||
use App\Contracts\PhotoCreate\VideoPartnerPipe; | ||
use App\DTO\PhotoCreate\VideoPartnerDTO; | ||
|
||
class UpdateLivePartner implements VideoPartnerPipe | ||
{ | ||
public function handle(VideoPartnerDTO $state, \Closure $next): VideoPartnerDTO | ||
{ | ||
$state->photo->live_photo_short_path = $state->videoPath; | ||
$state->photo->live_photo_checksum = $state->streamStat?->checksum; | ||
|
||
return $next($state); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?php | ||
|
||
namespace App\Contracts\PhotoCreate; | ||
|
||
use App\DTO\PhotoCreate\VideoPartnerDTO; | ||
|
||
/** | ||
* Basic definition of a Video Partner pipe. | ||
*/ | ||
interface VideoPartnerPipe | ||
{ | ||
/** | ||
* @param VideoPartnerDTO $state | ||
* @param \Closure(VideoPartnerDTO $state): VideoPartnerDTO $next | ||
* | ||
* @return VideoPartnerDTO | ||
*/ | ||
public function handle(VideoPartnerDTO $state, \Closure $next): VideoPartnerDTO; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
|
||
namespace App\DTO\PhotoCreate; | ||
|
||
use App\Contracts\Image\StreamStats; | ||
use App\Contracts\PhotoCreate\PhotoDTO; | ||
use App\Image\Files\BaseMediaFile; | ||
use App\Models\Photo; | ||
|
||
class VideoPartnerDTO implements PhotoDTO | ||
{ | ||
public readonly bool $shallImportViaSymlink; | ||
public readonly bool $shallDeleteImported; | ||
|
||
// The resulting photo | ||
public readonly Photo $photo; | ||
|
||
public StreamStats|null $streamStat; | ||
|
||
public string $videoPath; | ||
public readonly BaseMediaFile $videoFile; | ||
|
||
public function __construct(InitDTO $initDTO) | ||
{ | ||
$this->videoFile = $initDTO->sourceFile; | ||
$this->photo = $initDTO->livePartner; | ||
$this->shallImportViaSymlink = $initDTO->importMode->shallImportViaSymlink; | ||
$this->shallDeleteImported = $initDTO->importMode->shallDeleteImported; | ||
} | ||
|
||
public function getPhoto(): Photo | ||
{ | ||
return $this->photo; | ||
} | ||
} |