openstackid-resources/app/Services/Model/Imp/PresentationVideoMediaUploadProcessor.php
smarcet 4432a5f2b9 Import assets from MUX
Added new endpoints
POST api/v1/summits/{id}/presentations/all/import/mux

payload
* mux_token_id ( string|required)
* mux_token_secret ( string|required)
* email_to (optional|email)

required scopes

REALM_URL/summits/write
REALM_URL/summits/write-event
REALM_URL/summits/write-presentation

Change-Id: If3af7466f5c2fd1a38e1129fecd7e0b86312590e
Signed-off-by: smarcet <smarcet@gmail.com>
2021-06-04 16:28:20 -03:00

458 lines
20 KiB
PHP

<?php namespace App\Services\Model\Imp;
/**
* Copyright 2020 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use App\Jobs\CreateVideosFromMUXAssetsForSummitJob;
use App\Mail\MUXExportExcerptMail;
use App\Models\Foundation\Summit\Factories\PresentationVideoFactory;
use App\Models\Utils\IStorageTypesConstants;
use App\Services\Apis\MuxCredentials;
use App\Services\Filesystem\FileDownloadStrategyFactory;
use App\Services\Model\AbstractService;
use App\Services\Model\IPresentationVideoMediaUploadProcessor;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use libs\utils\ITransactionService;
use models\summit\ISummitEventRepository;
use models\summit\Presentation;
use MuxPhp\ApiException;
use MuxPhp\Configuration as MuxConfig;
use MuxPhp\Api\AssetsApi as MuxAssetApi;
use MuxPhp\Api\PlaybackIDApi as MuxPlaybackIDApi;
use GuzzleHttp\Client as GuzzleHttpClient;
use MuxPhp\Models\InputSettings as MuxInputSettings;
use MuxPhp\Models\CreateAssetRequest as MuxCreateAssetRequest;
use MuxPhp\Models\PlaybackPolicy as MuxPlaybackPolicy;
use MuxPhp\Models\UpdateAssetMP4SupportRequest as MuxUpdateAssetMP4SupportRequest;
/**
* Class PresentationVideoMediaUploadProcessor
* @package App\Services\Model\Imp
*/
final class PresentationVideoMediaUploadProcessor
extends AbstractService
implements IPresentationVideoMediaUploadProcessor
{
const MUX_STREAM_REGEX = '/https\:\/\/stream\.mux\.com\/(.*)\.m3u8/';
/**
* @var ISummitEventRepository
*/
private $event_repository;
/**
* @var MuxAssetApi
*/
private $assets_api;
/**
* @var MuxPlaybackIDApi
*/
private $playback_api;
public function __construct
(
ISummitEventRepository $event_repository,
ITransactionService $tx_service
)
{
parent::__construct($tx_service);
$this->event_repository = $event_repository;
$this->assets_api = null;
}
/**
* @param int $summit_id
* @param MuxCredentials $credentials
* @param string|null $mountingFolder
* @return int
* @throws \Exception
*/
public function processPublishedPresentationFor(int $summit_id, MuxCredentials $credentials, ?string $mountingFolder = null): int
{
$this->_configMux($credentials);
Log::debug(sprintf("PresentationVideoMediaUploadProcessor::processPublishedPresentationFor summit id %s mountingFolder %s", $summit_id, $mountingFolder));
$event_ids = $this->tx_service->transaction(function () use ($summit_id) {
return $this->event_repository->getPublishedEventsIdsBySummit($summit_id);
});
foreach ($event_ids as $event_id) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::processPublishedPresentationFor processing event %s", $event_id));
$this->processEvent(intval($event_id), $mountingFolder);
}
return count($event_ids);
}
/**
* @param int $event_id
* @param string|null $mountingFolder
* @param MuxCredentials|null $credentials
* @return bool
*/
public function processEvent(int $event_id, ?string $mountingFolder, ?MuxCredentials $credentials = null): bool
{
try {
$this->_configMux($credentials);
return $this->tx_service->transaction(function () use ($event_id, $mountingFolder) {
try {
$event = $this->event_repository->getByIdExclusiveLock($event_id);
if (is_null($event) || !$event instanceof Presentation) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::processEvent event %s not found", $event_id));
return false;
}
if (!$event->isPublished()) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::processEvent event %s not published", $event_id));
return false;
}
Log::debug(sprintf("PresentationVideoMediaUploadProcessor::processEvent processing event %s (%s)", $event->getTitle(), $event_id));
if (!empty($event->getMuxAssetId())) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::processEvent event %s already has assigned an asset id %s", $event_id, $event->getMuxAssetId()));
return false;
}
$has_video = false;
foreach ($event->getMediaUploads() as $mediaUpload) {
if ($mediaUpload->getMediaUploadType()->isVideo()) {
if ($has_video) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::processEvent event %s processing media upload %s (%s) already has a video processed!.", $event_id, $mediaUpload->getId(), $mediaUpload->getFilename()));
continue;
}
Log::debug(sprintf("PresentationVideoMediaUploadProcessor::processEvent event %s processing media upload %s", $event_id, $mediaUpload->getId()));
$has_video = true;
$strategy = FileDownloadStrategyFactory::build($mediaUpload->getMediaUploadType()->getPrivateStorageType());
if (!is_null($strategy)) {
$relativePath = $mediaUpload->getRelativePath(IStorageTypesConstants::PrivateType, $mountingFolder);
Log::debug(sprintf("PresentationVideoMediaUploadProcessor::processEvent event %s processing media upload %s relativePath %s", $event_id, $mediaUpload->getId(), $relativePath));
$assetUrl = $strategy->getUrl($relativePath);
if ($assetUrl == '#') {
Log::debug(sprintf("PresentationVideoMediaUploadProcessor::processEvent event %s processing media upload %s got asset url %s is not valid", $event_id, $mediaUpload->getId(), $assetUrl));
return false;
}
Log::debug(sprintf("PresentationVideoMediaUploadProcessor::processEvent event %s processing media upload %s got asset url %s", $event_id, $mediaUpload->getId(), $assetUrl));
// Create Asset Request
$input = new MuxInputSettings(["url" => $assetUrl]);
$createAssetRequest = new MuxCreateAssetRequest(["input" => $input, "playback_policy" => [MuxPlaybackPolicy::PUBLIC_PLAYBACK_POLICY]]);
// Ingest
$result = $this->assets_api->createAsset($createAssetRequest);
// Print URL
$playback_id = $result->getData()->getPlaybackIds()[0]->getId();
$streaming_url = sprintf("https://stream.mux.com/%s.m3u8", $playback_id);
$asset_id = $result->getData()->getId();
Log::debug(sprintf("PresentationVideoMediaUploadProcessor::processEvent event %s Playback URL: %s assset id %s", $event_id, $streaming_url, $asset_id));
$event->setStreamingUrl($streaming_url);
$event->setMuxAssetId($asset_id);
$event->setMuxPlaybackId($playback_id);
}
}
}
} catch (\Exception $ex) {
Log::warning($ex);
throw $ex;
}
return true;
});
} catch (\Exception $ex) {
Log::error($ex);
return false;
}
}
/**
* @param int $event_id
* @throws \Exception
*/
public function enableMP4Support(int $event_id, ?MuxCredentials $credentials = null): void
{
$this->_configMux($credentials);
$this->tx_service->transaction(function () use ($event_id) {
$event = $this->event_repository->getByIdExclusiveLock($event_id);
if (is_null($event) || !$event instanceof Presentation) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::enableMP4Support event %s not found", $event_id));
return;
}
if (!$event->isPublished()) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::enableMP4Support event %s not published", $event_id));
return;
}
Log::debug(sprintf("PresentationVideoMediaUploadProcessor::enableMP4Support processing event %s (%s)", $event->getTitle(), $event_id));
$assetId = $event->getMuxAssetId();
if (empty($assetId)) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::enableMP4Support event %s asset id is empty", $event_id));
return;
}
$this->_enableMP4Support($assetId);
});
}
/**
* @param string $assetId
* @return \MuxPhp\Models\AssetResponse
* @throws \MuxPhp\ApiException
*/
private function _enableMP4Support(string $assetId)
{
try {
$request = new MuxUpdateAssetMP4SupportRequest(['mp4_support' => MuxUpdateAssetMP4SupportRequest::MP4_SUPPORT_STANDARD]);
$result = $this->assets_api->updateAssetMp4Support($assetId, $request);
$tracks = $result->getData()->getTracks();
Log::debug
(
sprintf
(
"PresentationVideoMediaUploadProcessor::_enableMP4Support asset id %s enable mp4 support response %s",
$assetId,
json_encode($tracks)
)
);
return $tracks;
}
catch(ApiException $ex){
Log::warning($ex);
$response = json_decode($ex->getResponseBody());
if(count($response->error->messages) > 0){
if($response->error->messages[0] == "Download already exists"){
return null;
}
}
throw $ex;
}
}
private function _configMux(MuxCredentials $credentials)
{
if (!is_null($this->assets_api)) return;
// Authentication Setup
$config = MuxConfig::getDefaultConfiguration()
->setUsername($credentials->getTokenId())
->setPassword($credentials->getTokenSecret());
// API Client Initialization
$this->assets_api = new MuxAssetApi(
new GuzzleHttpClient,
$config
);
$this->playback_api = new MuxPlaybackIDApi(
new GuzzleHttpClient,
$config
);
}
/**
* @param int $summit_id
* @param MuxCredentials $credentials
* @param string|null $mail_to
* @return int
* @throws \Exception
*/
public function processSummitEventsStreamURLs
(
int $summit_id,
MuxCredentials $credentials,
?string $mail_to
): int
{
$event_ids = $this->tx_service->transaction(function () use ($summit_id) {
return $this->event_repository->getPublishedEventsIdsBySummit($summit_id);
});
$this->_configMux($credentials);
$excerpt = 'Starting MUX Assets Enabling MP4 Support Process.'.PHP_EOL;
foreach ($event_ids as $event_id) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::processMuxAssetsFromStreamUrl processing event %s", $event_id));
try {
$this->tx_service->transaction(function () use ($event_id, $credentials, &$excerpt) {
try {
$event = $this->event_repository->getByIdExclusiveLock($event_id);
if (is_null($event) || !$event instanceof Presentation) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::processMuxAssetsFromStreamUrl event %s not found", $event_id));
return false;
}
if (!$event->isPublished()) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::processMuxAssetsFromStreamUrl event %s not published", $event_id));
$excerpt .= sprintf("event %s not published", $event_id) . PHP_EOL;
return false;
}
if (!empty($event->getMuxAssetId())) {
$excerpt .= sprintf("event %s - mux asset id (%s) - already has enabled mp4 support.", $event_id, $event->getMuxAssetId()) . PHP_EOL;
return false;
}
$stream_url = $event->getStreamingUrl();
// test $stream_url
if (!preg_match(self::MUX_STREAM_REGEX, $stream_url, $matches)) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::processMuxAssetsFromStreamUrl event %s stream url does not match mux format (%s)", $event_id, $stream_url));
$excerpt .= sprintf("event %s stream url does not match mux format (%s)", $event_id, $stream_url) . PHP_EOL;
}
$playback_id = $matches[1];
$event->setMuxPlaybackId($playback_id);
$playbackResponse = $this->playback_api->getAssetOrLivestreamId($playback_id);
$asset_id = $playbackResponse->getData()->getObject()->getId();
$event->setMuxAssetId($asset_id);
$assetResponse = $this->assets_api->getAsset($asset_id);
$staticRenditions = $assetResponse->getData()->getStaticRenditions();
if(!is_null($staticRenditions)){
$excerpt .= sprintf("event %s - mux asset id (%s) - has already enabled mp4 support.", $event_id, $asset_id) . PHP_EOL;
return false;
}
$this->_enableMP4Support($asset_id);
$excerpt .= sprintf("event %s - mux asset id (%s) - has been enabled mp4 support.", $event_id, $asset_id) . PHP_EOL;
}
catch (\Exception $ex) {
$excerpt .= sprintf("event %s - error on enabling mp4 support.", $event_id) . PHP_EOL;
Log::warning($ex);
throw $ex;
}
});
} catch (\Exception $ex) {
Log::error($ex);
}
}
$excerpt .= sprintf("%s events processed.", count($event_ids)) . PHP_EOL;
if (!empty($mail_to))
Mail::queue(new MUXExportExcerptMail($mail_to, "MUX Assets MP4 Enabling Process", $excerpt));
// fire exporting
// @see https://docs.mux.com/guides/video/download-your-videos#download-videos
CreateVideosFromMUXAssetsForSummitJob::dispatch(
$summit_id,
$credentials->getTokenId(),
$credentials->getTokenSecret(),
$mail_to
)->delay(now()->addMinutes(30));
return count($event_ids);
}
/**
* @param int $summit_id
* @param MuxCredentials $credentials
* @param string|null $mail_to
* @return int
* @throws \Exception
*/
public function createVideosFromMUXAssets(int $summit_id,
MuxCredentials $credentials,
?string $mail_to): int
{
$event_ids = $this->tx_service->transaction(function () use ($summit_id) {
return $this->event_repository->getPublishedEventsIdsBySummit($summit_id);
});
$this->_configMux($credentials);
$excerpt = 'Starting Create Videos From MUX Assets Process.'.PHP_EOL;
foreach ($event_ids as $event_id) {
$this->tx_service->transaction(function () use ($event_id, $credentials, &$excerpt) {
try {
$event = $this->event_repository->getByIdExclusiveLock($event_id);
if (is_null($event) || !$event instanceof Presentation) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::createVideosFromMUXAssets event %s not found", $event_id));
return false;
}
if (!$event->isPublished()) {
Log::warning(sprintf("PresentationVideoMediaUploadProcessor::createVideosFromMUXAssets event %s not published", $event_id));
$excerpt .= sprintf("event %s not published.", $event_id) . PHP_EOL;
return false;
}
if ($event->getVideosWithExternalUrls()->count() > 0) {
$excerpt .= sprintf("event %s already processed.", $event_id) . PHP_EOL;
return false;
}
$assetId = $event->getMuxAssetId();
if(empty($assetId)){
$excerpt .= sprintf("event %s not processed.", $event_id) . PHP_EOL;
return false;
}
$result = $this->assets_api->getAsset($assetId);
$staticRenditions = $result->getData()->getStaticRenditions();
if(is_null($staticRenditions)){
$excerpt .= sprintf("event %s - mp4 not enabled.", $event_id) . PHP_EOL;
return false;
}
if ($staticRenditions->getStatus() != 'ready') {
$excerpt .= sprintf("event %s - transcoding not ready (%s)", $event_id, $staticRenditions->getStatus()) . PHP_EOL;
return false;
}
$bestFile = null;
$fileNames = [
'low.mp4',
'medium.mp4',
'high.mp4',
];
foreach ($staticRenditions->getFiles() as $file) {
if (is_null($bestFile)) $bestFile = $file;
if (array_search($bestFile->getName(), $fileNames) < array_search($file->getName(), $fileNames)) {
$bestFile = $file;
}
}
if (is_null($bestFile)) {
$excerpt .= sprintf("event %s - transcoding not ready (%s).", $event_id, $staticRenditions->getStatus()) . PHP_EOL;
return 0;
};
$newVideo = PresentationVideoFactory::build([
'name' => $event->getTitle(),
'external_url' => sprintf("https://stream.mux.com/%s/%s", $event->getMuxPlaybackId(), $bestFile->getName())
]);
$event->addVideo($newVideo);
} catch (\Exception $ex) {
$excerpt .= sprintf("event %s - error on exporting mux asset.", $event_id) . PHP_EOL;
Log::warning($ex);
throw $ex;
}
});
}
$excerpt .= sprintf("%s events processed.", count($event_ids)) . PHP_EOL;
if (!empty($mail_to))
Mail::queue(new MUXExportExcerptMail($mail_to, "Videos Creation Process", $excerpt));
return count($event_ids);
}
}