378 lines
12 KiB
PHP
378 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Helpers\FirstParty\Render\RenderConstants;
|
|
use App\Models\User;
|
|
use App\Models\Video;
|
|
use App\Models\VideoCaption;
|
|
use App\Models\VideoElement;
|
|
use App\Models\VideoRender;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use stdClass;
|
|
use Str;
|
|
|
|
class RenderController extends Controller
|
|
{
|
|
public function getVideoElements(Request $request, string $uuid)
|
|
{
|
|
if (! Str::isUuid($uuid)) {
|
|
return response()->json([
|
|
'error' => [
|
|
'message' => 'Invalid UUID format.',
|
|
],
|
|
], 400);
|
|
}
|
|
|
|
$video = Video::with('video_elements')->where('uuid', $uuid)->first();
|
|
|
|
if (! $video) {
|
|
return response()->json([
|
|
'error' => [
|
|
'message' => 'Video not found.',
|
|
],
|
|
]);
|
|
}
|
|
|
|
return response()->json((object) [
|
|
'success' => [
|
|
'data' => [
|
|
'video_elements' => $video->video_elements,
|
|
],
|
|
],
|
|
]);
|
|
|
|
if (! $video_render) {
|
|
return response()->json([
|
|
'error' => [
|
|
'message' => 'Video render not found.',
|
|
],
|
|
]);
|
|
}
|
|
|
|
$video = Video::where('id', $video_render->video_id)->first();
|
|
|
|
if (! $video) {
|
|
return response()->json([
|
|
'error' => [
|
|
'message' => 'Video not found.',
|
|
],
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function startRender(Request $request)
|
|
{
|
|
|
|
$video_render_request = array_to_object_2025($request->all());
|
|
|
|
$video_render_action = $this->saveUserVideoRenderRequest(Auth::user(), $video_render_request);
|
|
|
|
if (! $video_render_action->success) {
|
|
$error_message = $video_render_action?->message ? $video_render_action->message : 'Unable to render, possibly because the video is already being rendered. Check external ID.';
|
|
|
|
return response()->json([
|
|
'error' => [
|
|
'message' => $error_message,
|
|
|
|
],
|
|
]);
|
|
}
|
|
|
|
// Create a video
|
|
return response()->json((object) [
|
|
'success' => [
|
|
'data' => [
|
|
'uuid' => $video_render_action->model->uuid,
|
|
'status' => $video_render_action->model->status,
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function renderStatus(Request $request, string $uuid)
|
|
{
|
|
if (! Str::isUuid($uuid)) {
|
|
return response()->json([
|
|
'error' => [
|
|
'message' => 'Invalid UUID.',
|
|
],
|
|
]);
|
|
}
|
|
$video_render = VideoRender::where('uuid', $uuid)->first();
|
|
|
|
if (! $video_render) {
|
|
return response()->json([
|
|
'error' => [
|
|
'message' => 'Video render not found.',
|
|
],
|
|
]);
|
|
}
|
|
|
|
return response()->json((object) [
|
|
'success' => [
|
|
'data' => [
|
|
'uuid' => $video_render->uuid,
|
|
'status' => $video_render->status,
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function allRenders(Request $request)
|
|
{
|
|
$user = Auth::user();
|
|
|
|
$video_renders = VideoRender::where('user_id', $user->id)
|
|
->orderBy('id', 'desc')->get();
|
|
|
|
return response()->json((object) [
|
|
'success' => [
|
|
'data' => [
|
|
'video_renders' => $video_renders,
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
private function saveUserVideoRenderRequest(User $user, stdClass $video_render_request)
|
|
{
|
|
// check if there is an existing video render request with the same external id
|
|
|
|
$video_render_is_busy = VideoRender::where('user_id', $user->id)
|
|
->where('external_id', $video_render_request->external_id)
|
|
->whereIn('status', [
|
|
RenderConstants::STATUS_PLANNED,
|
|
RenderConstants::STATUS_WAITING,
|
|
RenderConstants::STATUS_TRANSCRIBING,
|
|
RenderConstants::STATUS_RENDERING,
|
|
])->first();
|
|
|
|
if ($video_render_is_busy) {
|
|
return (object) [
|
|
'success' => false,
|
|
'message' => 'Video is already in queue or rendering. Status: '.$video_render_is_busy->status,
|
|
];
|
|
}
|
|
// dd($video_render_request);
|
|
|
|
$video = $this->getUserVideoByExternalId($user, $video_render_request->external_id);
|
|
|
|
$video = $this->updateVideoWithRenderRequest($video, $video_render_request);
|
|
|
|
$video = $this->saveUserVideo($video);
|
|
|
|
$this->saveVideoCaptions($video);
|
|
|
|
try {
|
|
$this->saveVideoElements($video);
|
|
} catch (\Exception $e) {
|
|
return (object) [
|
|
'success' => false,
|
|
'message' => $e->getMessage(),
|
|
];
|
|
}
|
|
|
|
$new_video_render = VideoRender::create([
|
|
'user_id' => $user->id,
|
|
'video_id' => $video->id,
|
|
'external_id' => $video_render_request->external_id,
|
|
'payload' => $video_render_request,
|
|
'status' => RenderConstants::STATUS_PLANNED,
|
|
]);
|
|
|
|
return (object) [
|
|
'success' => true,
|
|
'model' => $new_video_render,
|
|
];
|
|
}
|
|
|
|
private function saveVideoCaptions(Video $video)
|
|
{
|
|
VideoCaption::where('video_id', $video->id)->delete();
|
|
|
|
if (isset($video->payload->captions)) {
|
|
foreach ($video->payload->captions as $caption) {
|
|
$video_caption = new VideoCaption;
|
|
$video_caption->video_id = $video->id;
|
|
$video_caption->time = $caption->time;
|
|
$video_caption->duration = $caption->duration;
|
|
$video_caption->text = $caption->text;
|
|
|
|
if (isset($caption->parameters)) {
|
|
$video_caption->parameters = $caption->parameters;
|
|
}
|
|
|
|
$video_caption->words = $caption->words;
|
|
$video_caption->save();
|
|
}
|
|
}
|
|
}
|
|
|
|
private function saveVideoElements(Video $video)
|
|
{
|
|
if (isset($video->payload->elements)) {
|
|
$existing_video_elements = VideoElement::where('video_id', $video->id)->get();
|
|
|
|
// Create a lookup array of existing elements by asset hash, but keep ALL matching elements
|
|
$existing_elements_by_hash = [];
|
|
foreach ($existing_video_elements as $existing_element) {
|
|
if (! isset($existing_elements_by_hash[$existing_element->asset_hash])) {
|
|
$existing_elements_by_hash[$existing_element->asset_hash] = [];
|
|
}
|
|
$existing_elements_by_hash[$existing_element->asset_hash][] = $existing_element;
|
|
}
|
|
|
|
// Track which elements we're keeping
|
|
$kept_element_ids = [];
|
|
// Track which hashes we've already processed to handle duplicates
|
|
$processed_hashes = [];
|
|
|
|
// Validate element URL if exist
|
|
foreach ($video->payload->elements as $element) {
|
|
if (isset($element->url)) {
|
|
if (! $this->validateElementUrl($element->url)) {
|
|
throw new \Exception('Invalid URL: '.$element->url);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save
|
|
foreach ($video->payload->elements as $element) {
|
|
$asset_hash = $this->getAssetHash($video, $element->url);
|
|
|
|
if (isset($existing_elements_by_hash[$asset_hash]) && count($existing_elements_by_hash[$asset_hash]) > 0) {
|
|
// Get the next unused element with this hash
|
|
$unused_elements = array_filter($existing_elements_by_hash[$asset_hash], function ($elem) use ($kept_element_ids) {
|
|
return ! in_array($elem->id, $kept_element_ids);
|
|
});
|
|
|
|
if (count($unused_elements) > 0) {
|
|
// Use the first unused element
|
|
$video_element = reset($unused_elements);
|
|
$kept_element_ids[] = $video_element->id;
|
|
} else {
|
|
// All elements with this hash are already used, create a new one
|
|
$video_element = new VideoElement;
|
|
$video_element->video_id = $video->id;
|
|
$video_element->asset_hash = $asset_hash;
|
|
$video_element->original_asset_url = $element->url;
|
|
}
|
|
} else {
|
|
// No elements with this hash, create a new one
|
|
$video_element = new VideoElement;
|
|
$video_element->video_id = $video->id;
|
|
$video_element->asset_hash = $asset_hash;
|
|
$video_element->original_asset_url = $element->url;
|
|
}
|
|
|
|
if (isset($element->external_reference) && ! is_empty($element->external_reference)) {
|
|
$video_element->external_reference = $element->external_reference;
|
|
}
|
|
$video_element->type = $element->type;
|
|
$video_element->time = $element->time;
|
|
$video_element->track = $element->track;
|
|
$video_element->duration = $element->duration;
|
|
|
|
if (isset($element->parameters)) {
|
|
$video_element->parameters = $element->parameters;
|
|
}
|
|
|
|
$video_element->save();
|
|
|
|
// Add newly created ID to kept list if needed
|
|
if (! in_array($video_element->id, $kept_element_ids)) {
|
|
$kept_element_ids[] = $video_element->id;
|
|
}
|
|
}
|
|
|
|
// Delete elements that weren't in the payload
|
|
if (count($existing_video_elements) > 0) {
|
|
VideoElement::where('video_id', $video->id)
|
|
->whereNotIn('id', $kept_element_ids)
|
|
->delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
private function getAssetHash(Video $video, $url)
|
|
{
|
|
return hash('sha256', $video->id.'-'.$url);
|
|
}
|
|
|
|
private function validateElementUrl(string $url)
|
|
{
|
|
// First check if it's a valid URL format
|
|
$validator = Validator::make(['url' => $url], [
|
|
'url' => 'required|url',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return false;
|
|
}
|
|
|
|
// validate url by making a http head request, return true | false boolean
|
|
try {
|
|
// Using Laravel's HTTP client to make a HEAD request
|
|
$response = Http::withOptions([
|
|
'timeout' => 10,
|
|
'connect_timeout' => 5,
|
|
'verify' => true,
|
|
'http_errors' => false, // Don't throw exceptions for 4xx/5xx responses
|
|
])->head($url);
|
|
|
|
// Check if the response is successful (2xx status code) or a redirect (3xx)
|
|
return $response->successful();
|
|
} catch (\Exception $e) {
|
|
// Catch any exceptions (connection issues, invalid URLs, etc.)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function saveUserVideo(Video $video)
|
|
{
|
|
if ($video->isDirty()) {
|
|
$video->save();
|
|
}
|
|
|
|
return $video;
|
|
}
|
|
|
|
private function updateVideoWithRenderRequest(Video $video, stdClass $video_render_request)
|
|
{
|
|
// dd($video_render_request);
|
|
|
|
$video->content_type = $video_render_request->content_type;
|
|
$video->width = $video_render_request->width;
|
|
$video->height = $video_render_request->height;
|
|
$video->aspect_ratio = $video_render_request->aspect_ratio;
|
|
$video->payload = $video_render_request;
|
|
$video->render_settings = (object) [
|
|
'video_bitrate' => $video_render_request->video_bitrate,
|
|
'audio_bitrate' => $video_render_request->audio_bitrate,
|
|
'fps' => $video_render_request->fps,
|
|
];
|
|
|
|
return $video;
|
|
}
|
|
|
|
private function getUserVideoByExternalId(User $user, string $external_id)
|
|
{
|
|
$video = Video::where('user_id', $user->id)
|
|
->where('external_id', $external_id)
|
|
->first();
|
|
|
|
if (! $video) {
|
|
|
|
$video = new Video;
|
|
$video->user_id = $user->id;
|
|
$video->external_id = $external_id;
|
|
}
|
|
|
|
return $video;
|
|
}
|
|
}
|