Skip to content

Commit 49f7109

Browse files
markus-moserclaude
andcommitted
[Bug] Add video thumbnail status endpoint for Studio polling
Studio needs a way to detect when an inprogress video thumbnail finishes converting so it can refresh the editor automatically. Today the only consumer of the thumbnail status is BinaryService, which uses it internally to decide whether to stream or throw ElementProcessingNotCompletedException - it isn't exposed to clients. Adds GET /pimcore-studio/api/assets/{id}/video/thumbnail/{thumbnailName}/status returning {status: finished | inprogress | error | not_started}. studio-ui-bundle polls this endpoint while a Video editable is in the in-progress state and reloads the editor when conversion finishes. Resolves pimcore/studio-ui-bundle#3407 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b3bc1f4 commit 49f7109

4 files changed

Lines changed: 188 additions & 0 deletions

File tree

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\StudioBackendBundle\Asset\Controller\Video;
15+
16+
use OpenApi\Attributes\Get;
17+
use OpenApi\Attributes\JsonContent;
18+
use Pimcore\Bundle\StudioBackendBundle\Asset\OpenApi\Attribute\Parameter\Path\ThumbnailNameParameter;
19+
use Pimcore\Bundle\StudioBackendBundle\Asset\Schema\Type\Video\VideoThumbnailStatus;
20+
use Pimcore\Bundle\StudioBackendBundle\Asset\Service\AssetServiceInterface;
21+
use Pimcore\Bundle\StudioBackendBundle\Asset\Service\VideoServiceInterface;
22+
use Pimcore\Bundle\StudioBackendBundle\Controller\AbstractApiController;
23+
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\ForbiddenException;
24+
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidElementTypeException;
25+
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidThumbnailException;
26+
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException;
27+
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\UserNotFoundException;
28+
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Parameter\Path\IdParameter;
29+
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\DefaultResponses;
30+
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\SuccessResponse;
31+
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Config\Tags;
32+
use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface;
33+
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\HttpResponseCodes;
34+
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\UserPermissions;
35+
use Symfony\Component\HttpFoundation\JsonResponse;
36+
use Symfony\Component\Routing\Attribute\Route;
37+
use Symfony\Component\Security\Http\Attribute\IsGranted;
38+
use Symfony\Component\Serializer\SerializerInterface;
39+
40+
/**
41+
* @internal
42+
*/
43+
final class ThumbnailStatusController extends AbstractApiController
44+
{
45+
public function __construct(
46+
SerializerInterface $serializer,
47+
private readonly AssetServiceInterface $assetService,
48+
private readonly SecurityServiceInterface $securityService,
49+
private readonly VideoServiceInterface $videoService,
50+
) {
51+
parent::__construct($serializer);
52+
}
53+
54+
/**
55+
* @throws ForbiddenException
56+
* @throws InvalidElementTypeException
57+
* @throws InvalidThumbnailException
58+
* @throws NotFoundException
59+
* @throws UserNotFoundException
60+
*/
61+
#[Route(
62+
'/assets/{id}/video/thumbnail/{thumbnailName}/status',
63+
name: 'pimcore_studio_api_video_thumbnail_status',
64+
methods: ['GET']
65+
)]
66+
#[IsGranted(UserPermissions::ASSETS->value)]
67+
#[Get(
68+
path: self::PREFIX . '/assets/{id}/video/thumbnail/{thumbnailName}/status',
69+
operationId: 'asset_video_thumbnail_status',
70+
description: 'asset_video_thumbnail_status_description',
71+
summary: 'asset_video_thumbnail_status_summary',
72+
tags: [Tags::Assets->name]
73+
)]
74+
#[IdParameter(type: 'video')]
75+
#[ThumbnailNameParameter]
76+
#[SuccessResponse(
77+
description: 'asset_video_thumbnail_status_success_response',
78+
content: new JsonContent(ref: VideoThumbnailStatus::class)
79+
)]
80+
#[DefaultResponses([
81+
HttpResponseCodes::BAD_REQUEST,
82+
HttpResponseCodes::FORBIDDEN,
83+
HttpResponseCodes::UNAUTHORIZED,
84+
HttpResponseCodes::NOT_FOUND,
85+
])]
86+
public function getVideoThumbnailStatus(int $id, string $thumbnailName): JsonResponse
87+
{
88+
$asset = $this->assetService->getAssetElement(
89+
$this->securityService->getCurrentUser(),
90+
$id
91+
);
92+
93+
return $this->jsonResponse(
94+
$this->videoService->getThumbnailStatus($asset, $thumbnailName)
95+
);
96+
}
97+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\StudioBackendBundle\Asset\Schema\Type\Video;
15+
16+
use OpenApi\Attributes\Property;
17+
use OpenApi\Attributes\Schema;
18+
use Pimcore\Bundle\StudioBackendBundle\Util\Schema\AdditionalAttributesInterface;
19+
use Pimcore\Bundle\StudioBackendBundle\Util\Trait\AdditionalAttributesTrait;
20+
21+
#[Schema(
22+
schema: 'VideoThumbnailStatus',
23+
title: 'Video Thumbnail Status',
24+
required: ['status'],
25+
type: 'object'
26+
)]
27+
final class VideoThumbnailStatus implements AdditionalAttributesInterface
28+
{
29+
use AdditionalAttributesTrait;
30+
31+
public const string STATUS_FINISHED = 'finished';
32+
33+
public const string STATUS_INPROGRESS = 'inprogress';
34+
35+
public const string STATUS_ERROR = 'error';
36+
37+
public const string STATUS_NOT_STARTED = 'not_started';
38+
39+
public function __construct(
40+
#[Property(
41+
description: 'Conversion status of the requested video thumbnail.',
42+
type: 'string',
43+
enum: [
44+
self::STATUS_FINISHED,
45+
self::STATUS_INPROGRESS,
46+
self::STATUS_ERROR,
47+
self::STATUS_NOT_STARTED,
48+
],
49+
example: self::STATUS_INPROGRESS,
50+
)]
51+
private readonly string $status,
52+
) {
53+
}
54+
55+
public function getStatus(): string
56+
{
57+
return $this->status;
58+
}
59+
}

src/Asset/Service/VideoService.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
namespace Pimcore\Bundle\StudioBackendBundle\Asset\Service;
1515

1616
use Pimcore\Bundle\StudioBackendBundle\Asset\Event\PreResponse\VideoTypeEvent;
17+
use Pimcore\Bundle\StudioBackendBundle\Asset\Schema\Type\Video\VideoThumbnailStatus;
1718
use Pimcore\Bundle\StudioBackendBundle\Asset\Schema\Type\Video\VideoType;
19+
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidElementTypeException;
20+
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\ElementTypes;
21+
use Pimcore\Model\Asset;
22+
use Pimcore\Model\Asset\Video as VideoAsset;
1823
use Pimcore\Model\DataObject\ClassDefinition\Data\Video;
1924
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
2025

@@ -25,6 +30,7 @@
2530
{
2631
public function __construct(
2732
private EventDispatcherInterface $eventDispatcher,
33+
private ThumbnailServiceInterface $thumbnailService,
2834
) {
2935
}
3036

@@ -40,4 +46,20 @@ public function getVideoTypes(): array
4046

4147
return $types;
4248
}
49+
50+
public function getThumbnailStatus(Asset $video, string $thumbnailName): VideoThumbnailStatus
51+
{
52+
if (!$video instanceof VideoAsset) {
53+
throw new InvalidElementTypeException($video->getType(), ElementTypes::TYPE_ASSET);
54+
}
55+
56+
$configuration = $this->thumbnailService->getVideoThumbnailConfig($thumbnailName);
57+
$thumbnail = $video->getThumbnail($configuration, ['mp4']);
58+
59+
$status = is_array($thumbnail) && isset($thumbnail['status'])
60+
? (string) $thumbnail['status']
61+
: VideoThumbnailStatus::STATUS_NOT_STARTED;
62+
63+
return new VideoThumbnailStatus($status);
64+
}
4365
}

src/Asset/Service/VideoServiceInterface.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313

1414
namespace Pimcore\Bundle\StudioBackendBundle\Asset\Service;
1515

16+
use Pimcore\Bundle\StudioBackendBundle\Asset\Schema\Type\Video\VideoThumbnailStatus;
1617
use Pimcore\Bundle\StudioBackendBundle\Asset\Schema\Type\Video\VideoType;
18+
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidElementTypeException;
19+
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidThumbnailException;
20+
use Pimcore\Model\Asset;
1721

1822
/**
1923
* @internal
@@ -24,4 +28,10 @@ interface VideoServiceInterface
2428
* @return VideoType[]
2529
*/
2630
public function getVideoTypes(): array;
31+
32+
/**
33+
* @throws InvalidElementTypeException
34+
* @throws InvalidThumbnailException
35+
*/
36+
public function getThumbnailStatus(Asset $video, string $thumbnailName): VideoThumbnailStatus;
2737
}

0 commit comments

Comments
 (0)