diff --git a/src/Asset/Controller/Video/ThumbnailStatusController.php b/src/Asset/Controller/Video/ThumbnailStatusController.php new file mode 100644 index 000000000..35a91fff5 --- /dev/null +++ b/src/Asset/Controller/Video/ThumbnailStatusController.php @@ -0,0 +1,97 @@ +value)] + #[Get( + path: self::PREFIX . '/assets/{id}/video/thumbnail/{thumbnailName}/status', + operationId: 'asset_video_thumbnail_status', + description: 'asset_video_thumbnail_status_description', + summary: 'asset_video_thumbnail_status_summary', + tags: [Tags::Assets->name] + )] + #[IdParameter(type: 'video')] + #[ThumbnailNameParameter] + #[SuccessResponse( + description: 'asset_video_thumbnail_status_success_response', + content: new JsonContent(ref: VideoThumbnailStatus::class) + )] + #[DefaultResponses([ + HttpResponseCodes::BAD_REQUEST, + HttpResponseCodes::FORBIDDEN, + HttpResponseCodes::UNAUTHORIZED, + HttpResponseCodes::NOT_FOUND, + ])] + public function getVideoThumbnailStatus(int $id, string $thumbnailName): JsonResponse + { + $asset = $this->assetService->getAssetElement( + $this->securityService->getCurrentUser(), + $id + ); + + return $this->jsonResponse( + $this->videoService->getThumbnailStatus($asset, $thumbnailName) + ); + } +} diff --git a/src/Asset/Schema/Type/Video/VideoThumbnailStatus.php b/src/Asset/Schema/Type/Video/VideoThumbnailStatus.php new file mode 100644 index 000000000..ee4235d99 --- /dev/null +++ b/src/Asset/Schema/Type/Video/VideoThumbnailStatus.php @@ -0,0 +1,59 @@ +status; + } +} diff --git a/src/Asset/Service/VideoService.php b/src/Asset/Service/VideoService.php index f0ee374ca..1c28c1028 100644 --- a/src/Asset/Service/VideoService.php +++ b/src/Asset/Service/VideoService.php @@ -14,7 +14,12 @@ namespace Pimcore\Bundle\StudioBackendBundle\Asset\Service; use Pimcore\Bundle\StudioBackendBundle\Asset\Event\PreResponse\VideoTypeEvent; +use Pimcore\Bundle\StudioBackendBundle\Asset\Schema\Type\Video\VideoThumbnailStatus; use Pimcore\Bundle\StudioBackendBundle\Asset\Schema\Type\Video\VideoType; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidElementTypeException; +use Pimcore\Bundle\StudioBackendBundle\Util\Constant\ElementTypes; +use Pimcore\Model\Asset; +use Pimcore\Model\Asset\Video as VideoAsset; use Pimcore\Model\DataObject\ClassDefinition\Data\Video; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -25,6 +30,7 @@ { public function __construct( private EventDispatcherInterface $eventDispatcher, + private ThumbnailServiceInterface $thumbnailService, ) { } @@ -40,4 +46,23 @@ public function getVideoTypes(): array return $types; } + + public function getThumbnailStatus(Asset $video, string $thumbnailName): VideoThumbnailStatus + { + if (!$video instanceof VideoAsset) { + throw new InvalidElementTypeException($video->getType(), ElementTypes::TYPE_ASSET); + } + + $configuration = $this->thumbnailService->getVideoThumbnailConfig($thumbnailName); + + // Read the status from the custom setting directly: Asset\Video::getThumbnail() would + // start a conversion as a side effect and returns null for errored conversions, + // which would make the error status unreachable for polling clients. + $customSetting = $video->getCustomSetting('thumbnails'); + $status = is_array($customSetting) + ? ($customSetting[$configuration->getName()]['status'] ?? VideoThumbnailStatus::STATUS_NOT_STARTED) + : VideoThumbnailStatus::STATUS_NOT_STARTED; + + return new VideoThumbnailStatus((string) $status); + } } diff --git a/src/Asset/Service/VideoServiceInterface.php b/src/Asset/Service/VideoServiceInterface.php index 33f78e9a3..8940c9986 100644 --- a/src/Asset/Service/VideoServiceInterface.php +++ b/src/Asset/Service/VideoServiceInterface.php @@ -13,7 +13,11 @@ namespace Pimcore\Bundle\StudioBackendBundle\Asset\Service; +use Pimcore\Bundle\StudioBackendBundle\Asset\Schema\Type\Video\VideoThumbnailStatus; use Pimcore\Bundle\StudioBackendBundle\Asset\Schema\Type\Video\VideoType; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidElementTypeException; +use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidThumbnailException; +use Pimcore\Model\Asset; /** * @internal @@ -24,4 +28,10 @@ interface VideoServiceInterface * @return VideoType[] */ public function getVideoTypes(): array; + + /** + * @throws InvalidElementTypeException + * @throws InvalidThumbnailException + */ + public function getThumbnailStatus(Asset $video, string $thumbnailName): VideoThumbnailStatus; } diff --git a/tests/Unit/Asset/Service/VideoServiceTest.php b/tests/Unit/Asset/Service/VideoServiceTest.php new file mode 100644 index 000000000..08914499d --- /dev/null +++ b/tests/Unit/Asset/Service/VideoServiceTest.php @@ -0,0 +1,104 @@ +expectException(InvalidElementTypeException::class); + + $this->getVideoService()->getThumbnailStatus(new Document(), self::THUMBNAIL_NAME); + } + + /** + * @throws Exception + */ + public function testGetThumbnailStatusReturnsStatusFromCustomSetting(): void + { + $video = $this->makeEmpty(Video::class, [ + 'getCustomSetting' => [ + self::THUMBNAIL_NAME => [ + 'status' => VideoThumbnailStatus::STATUS_ERROR, + ], + ], + ]); + + $status = $this->getVideoService()->getThumbnailStatus($video, self::THUMBNAIL_NAME); + + $this->assertSame(VideoThumbnailStatus::STATUS_ERROR, $status->getStatus()); + } + + /** + * @throws Exception + */ + public function testGetThumbnailStatusReturnsNotStartedWithoutCustomSetting(): void + { + $video = $this->makeEmpty(Video::class, [ + 'getCustomSetting' => null, + ]); + + $status = $this->getVideoService()->getThumbnailStatus($video, self::THUMBNAIL_NAME); + + $this->assertSame(VideoThumbnailStatus::STATUS_NOT_STARTED, $status->getStatus()); + } + + /** + * @throws Exception + */ + public function testGetThumbnailStatusReturnsNotStartedForUnknownThumbnail(): void + { + $video = $this->makeEmpty(Video::class, [ + 'getCustomSetting' => [ + 'other-thumbnail' => [ + 'status' => VideoThumbnailStatus::STATUS_FINISHED, + ], + ], + ]); + + $status = $this->getVideoService()->getThumbnailStatus($video, self::THUMBNAIL_NAME); + + $this->assertSame(VideoThumbnailStatus::STATUS_NOT_STARTED, $status->getStatus()); + } + + private function getVideoService(): VideoServiceInterface + { + $config = new Config(); + $config->setName(self::THUMBNAIL_NAME); + + return new VideoService( + $this->makeEmpty(EventDispatcherInterface::class), + $this->makeEmpty(ThumbnailServiceInterface::class, [ + 'getVideoThumbnailConfig' => $config, + ]), + ); + } +} diff --git a/translations/studio_api_docs.en.yaml b/translations/studio_api_docs.en.yaml index 43b5f4d0e..c1ccdefb9 100644 --- a/translations/studio_api_docs.en.yaml +++ b/translations/studio_api_docs.en.yaml @@ -212,6 +212,12 @@ asset_video_stream_by_thumbnail_description: | List of thumbnail names can be obtained via the thumbnail video collection endpoint asset_video_stream_by_thumbnail_success_response: Video stream based on thumbnail name asset_video_stream_by_thumbnail_summary: Stream video asset by ID and thumbnail name +asset_video_thumbnail_status_description: | + Get the conversion status of a video thumbnail based on the provided {id} and {thumbnailName}.
+ The {id} must be an ID of existing asset video
+ List of thumbnail names can be obtained via the thumbnail video collection endpoint +asset_video_thumbnail_status_success_response: Conversion status of the video thumbnail +asset_video_thumbnail_status_summary: Get video thumbnail conversion status by asset ID and thumbnail name asset_get_search_configuration: Get asset search configuration asset_get_search_configuration_description: Get asset configuration asset_get_search_configuration_summary: Get asset search configuration