This commit is contained in:
ct
2025-06-08 04:39:38 +08:00
parent 862a6bb19e
commit 8d6e86ebb3
40 changed files with 838 additions and 1601 deletions

View File

@@ -1,19 +0,0 @@
<?php
use App\Jobs\RunVideoRenderPipelineJob;
use App\Models\Video;
class JobTrigger
{
public static function RunVideoRenderPipelineJob()
{
$video = Video::latest()->first();
if ($video) {
$job = new RunVideoRenderPipelineJob($video->id);
$job->handle();
} else {
echo 'NO VIDEO';
}
}
}

View File

@@ -1,163 +0,0 @@
<?php
namespace App\Helpers\FirstParty\Render;
use App\Models\Video;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Symfony\Component\Process\Process;
/**
* Class FfmpegVideoRenderer
*
* Builds and optionally executes an ffmpeg command using VideoElement models and render settings.
*/
class FfmpegVideoRenderer
{
/**
* Render the given Video to a file via ffmpeg.
*
* @return object { string $name, string $path }
*
* @throws \InvalidArgumentException if required elements missing
* @throws \RuntimeException on ffmpeg failure
*/
public static function render(Video $video): object
{
$elements = self::get_video_elements($video);
$settings = self::get_render_settings($video);
// Gather elements by type and sort by time
$slide_images = $elements->where('external_reference', 'slideshow')->sortBy('time')->values();
$overlay_images = $elements->where('type', 'image')
->where('external_reference', '!=', 'slideshow')
->sortBy('time')
->values();
$video_element = $elements->firstWhere('type', 'video');
$audio_tracks = $elements->where('type', 'audio')->sortBy('time')->values();
if ($slide_images->isEmpty() || ! $video_element) {
throw new \InvalidArgumentException('At least one slideshow and one video element are required.');
}
// Build ffmpeg input arguments
$input_args = [];
foreach ($slide_images as $slide) {
$duration = number_format($slide->duration, 2);
$input_args[] = "-loop 1 -t {$duration} -i \"{$slide->asset_url}\"";
}
foreach ($overlay_images as $overlay) {
$duration = number_format($overlay->duration, 2);
$input_args[] = "-loop 1 -t {$duration} -i \"{$overlay->asset_url}\"";
}
$input_args[] = "-i \"{$video_element->asset_url}\"";
foreach ($audio_tracks as $audio) {
$input_args[] = "-i \"{$audio->asset_url}\"";
}
// Build filter_complex chains
$filters = [];
$slide_count = $slide_images->count();
$concat_input = '';
for ($i = 0; $i < $slide_count; $i++) {
$concat_input .= "[{$i}:v]";
}
$filters[] = "{$concat_input}concat=n={$slide_count}:v=1:a=0[slideshow]";
$current_label = 'slideshow';
foreach ($overlay_images as $idx => $overlay) {
$stream_idx = $slide_count + $idx;
$start_time = number_format($overlay->time, 2);
$end_time = number_format($overlay->time + $overlay->duration, 2);
$next_label = "overlay{$idx}";
$filters[] = "[{$current_label}][{$stream_idx}:v]overlay=enable=between(t\,{$start_time}\,{$end_time})[{$next_label}]";
$current_label = $next_label;
}
$video_idx = $slide_count + $overlay_images->count();
$v_start = number_format($video_element->time, 2);
$v_end = number_format($video_element->time + $video_element->duration, 2);
$filters[] = "[{$current_label}][{$video_idx}:v]overlay=enable=between(t\,{$v_start}\,{$v_end})[v_out]";
$audio_labels = [];
$first_audio = $video_idx + 1;
foreach ($audio_tracks as $idx => $audio) {
$stream_idx = $first_audio + $idx;
$dur = number_format($audio->duration, 2);
$start_time = number_format($audio->time, 2);
$chain = "[{$stream_idx}:a]atrim=duration={$dur},asetpts=PTS-STARTPTS";
if ($audio->time > 0) {
$delay_ms = (int) round($audio->time * 1000);
$chain .= ",adelay={$delay_ms}|{$delay_ms}";
}
$label = "a{$idx}";
$filters[] = "{$chain}[{$label}]";
$audio_labels[] = $label;
}
$mix_input = '';
foreach ($audio_labels as $lbl) {
$mix_input .= "[{$lbl}]";
}
$audio_count = count($audio_labels);
$filters[] = "{$mix_input}amix=inputs={$audio_count}:duration=longest[a_out]";
$filter_complex = implode(';', $filters);
// Prepare output
$filename = Str::random(12).'.mp4';
$output_path = sys_get_temp_dir().DIRECTORY_SEPARATOR.$filename;
// FFmpeg command, including render settings
$video_bitrate = $settings['video_bitrate'];
$audio_bitrate = $settings['audio_bitrate'];
$fps = $settings['fps'];
$command = 'ffmpeg '.implode(' ', $input_args)
." -r {$fps}"
." -filter_complex \"{$filter_complex}\""
.' -map "[v_out]" -map "[a_out]"'
." -c:v libx264 -b:v {$video_bitrate}"
." -c:a aac -b:a {$audio_bitrate}"
." -shortest \"{$output_path}\" -y";
$process = Process::fromShellCommandline($command, null, null, null, 300);
$process->run();
if (! $process->isSuccessful()) {
return (object) [
'success' => false,
'exception' => new \RuntimeException('FFmpeg failed: '.$process->getErrorOutput()),
];
throw new \RuntimeException('FFmpeg failed: '.$process->getErrorOutput());
}
return (object) [
'success' => true,
'name' => $filename,
'path' => $output_path,
];
}
/**
* Fetch video elements for rendering.
*/
private static function get_video_elements(Video $video): Collection
{
return $video->video_elements;
}
/**
* Extract render settings or defaults.
*/
private static function get_render_settings(Video $video): array
{
return [
'video_bitrate' => $video->render_settings?->video_bitrate ?? '3M',
'audio_bitrate' => $video->render_settings?->audio_bitrate ?? '128k',
'fps' => $video->render_settings?->fps ?? '30',
];
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace App\Helpers\FirstParty\Render;
class RenderConstants
{
const STATUS_PLANNED = 'STATUS_PLANNED'; // the render is queued for rendering
const STATUS_WAITING = 'STATUS_WAITING'; // the render is waiting for a third-party service (e.g., OpenAI or ElevenLabs) to finish
const STATUS_TRANSCRIBING = 'STATUS_TRANSCRIBING'; // an input file is being transcribed
const STATUS_RENDERING = 'STATUS_RENDERING'; // the render is being processed
const STATUS_SUCCEEDED = 'STATUS_SUCCEEDED'; // the render has been completed successfully
const STATUS_FAILED = 'STATUS_FAILED'; // the render failed due to the reason specified in the error_message field
const STATUSES = [
self::STATUS_PLANNED,
self::STATUS_WAITING,
self::STATUS_TRANSCRIBING,
self::STATUS_RENDERING,
self::STATUS_SUCCEEDED,
self::STATUS_FAILED,
];
}

View File

@@ -2,7 +2,6 @@
namespace App\Http\Controllers;
use App;
use Inertia\Inertia;
class FrontHomeController extends Controller
@@ -10,7 +9,6 @@ class FrontHomeController extends Controller
public function index()
{
return Inertia::render('home/home');
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use App\Models\MemeMedia;
use Illuminate\Http\Request;
class FrontMediaController extends Controller
{
public function memes(Request $request)
{
$memes = MemeMedia::where('type', 'video')->where('sub_type', 'overlay')->take('30')->inRandomOrder()->get();
return response()->json([
'success' => [
'data' => [
'memes' => $memes
]
]
]);
}
public function background(Request $request)
{
$backgrounds = MemeMedia::where('type', 'image')->where('sub_type', 'background')->take('30')->inRandomOrder()->get();
return response()->json([
'success' => [
'data' => [
'backgrounds' => $backgrounds
]
]
]);
}
}

View File

@@ -1,377 +0,0 @@
<?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;
}
}

View File

@@ -2,26 +2,10 @@
namespace App\Http\Controllers;
use App\Jobs\RunVideoRenderPipelineJob;
use App\Models\Video;
class TestController extends Controller
{
public function RunVideoRenderPipelineJob()
public function index()
{
$dispatch_job = true;
$video = Video::latest()->first();
if ($video) {
if ($dispatch_job) {
RunVideoRenderPipelineJob::dispatch($video->id)->onQueue('render');
} else {
$job = new RunVideoRenderPipelineJob($video->id);
$job->handle();
}
} else {
echo 'NO VIDEO';
}
//
}
}

View File

@@ -1,79 +0,0 @@
<?php
namespace App\Jobs;
use App\Helpers\FirstParty\MediaEngine\MediaEngine;
use App\Helpers\FirstParty\Render\FfmpegVideoRenderer;
use App\Helpers\FirstParty\Render\RenderConstants;
use App\Models\Video;
use Illuminate\Bus\Batchable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class RunVideoRenderPipelineJob implements ShouldQueue
{
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $video_id;
public $timeout = 300;
/**
* Create a new job instance.
*/
public function __construct(int $video_id)
{
$this->onQueue('general_video');
$this->video_id = $video_id;
}
/**
* Execute the job.
*/
public function handle(): void
{
if ($this->batch()?->cancelled()) {
return;
}
$video = Video::with('video_elements', 'latest_render')->find($this->video_id);
if (! $video) {
return;
}
$video_render = $video->latest_render;
$video_render->status = RenderConstants::STATUS_RENDERING;
$video_render->processing_started_at = now();
$video_render->save();
$output = FfmpegVideoRenderer::render($video);
if ($output->success) {
$video_render->status = RenderConstants::STATUS_SUCCEEDED;
$saved_media = MediaEngine::addMedia(
MediaEngine::getCollectionKeyByOwnerMediaType('user', 'video'),
'video',
MediaEngine::USER_RENDERED,
MediaEngine::USER,
$output->name,
file_get_contents($output->path),
);
$video_render->completed_video_uuid = $saved_media->uuid;
$video_render->completed_video_full_url = MediaEngine::getMediaCloudUrl($saved_media);
$video_render->processing_finished_at = now();
$video_render->save();
} else {
$video_render->processing_finished_at = now();
$video_render->status = RenderConstants::STATUS_FAILED;
$video_render->save();
throw $output->exception;
}
}
}

View File

@@ -1,78 +0,0 @@
<?php
namespace App\Jobs;
use App\Helpers\FirstParty\MediaEngine\MediaEngine;
use App\Models\Media;
use App\Models\Video;
use Illuminate\Bus\Batchable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SaveVideoElementsBatchJob implements ShouldQueue
{
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $video_id;
public $timeout = 300;
/**
* Create a new job instance.
*/
public function __construct(int $video_id)
{
$this->onQueue('general_video');
$this->video_id = $video_id;
}
/**
* Execute the job.
*/
public function handle(): void
{
if ($this->batch()?->cancelled()) {
return;
}
$video = Video::with('video_elements')->find($this->video_id);
if (! $video) {
return;
}
foreach ($video->video_elements as $video_element) {
if (! is_empty($video_element->asset_uuid)) {
continue;
}
// dump($video_element);
// Media Details: Filename, extension and mimetype
$media_details = MediaEngine::getFileDetailsbyUrl($video_element->original_asset_url);
// Media Content: Blob
$media_content = file_get_contents($video_element->original_asset_url);
// Media Filename: generate a new filename
$media_filename = $video_element->type.'_'.epoch_now_timestamp().'.'.$media_details->extension;
$saved_media = MediaEngine::addMedia(
MediaEngine::getCollectionKeyByOwnerMediaType('user', $video_element->type),
$video_element->type,
MediaEngine::USER_UPLOADED,
MediaEngine::USER,
$media_filename,
$media_content,
);
$video_element->asset_uuid = $saved_media->uuid;
$video_element->asset_url = MediaEngine::getMediaCloudUrl($saved_media);
$video_element->save();
}
}
}

59
app/Models/MemeMedia.php Normal file
View File

@@ -0,0 +1,59 @@
<?php
/**
* Created by Reliese Model.
*/
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Pgvector\Laravel\HasNeighbors;
use Pgvector\Laravel\Vector;
/**
* Class MemeMedia
*
* @property int $id
* @property string $type
* @property string $sub_type
* @property string $name
* @property string $description
* @property string $keywords
* @property uuid $media_1_uuid
* @property uuid|null $media_2_uuid
* @property string $media_1_mime_type
* @property string|null $media_2_mime_type
* @property USER-DEFINED|null $embedding
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property string|null $deleted_at
*
* @package App\Models
*/
class MemeMedia extends Model
{
use SoftDeletes, HasNeighbors;
protected $table = 'meme_medias';
protected $casts = [
'media_1_uuid' => 'uuid',
'media_2_uuid' => 'uuid',
'embedding' => Vector::class,
];
protected $fillable = [
'type',
'sub_type',
'name',
'description',
'keywords',
'media_1_uuid',
'media_2_uuid',
'media_1_mime_type',
'media_2_mime_type',
'embedding'
];
}

View File

@@ -1,80 +0,0 @@
<?php
/**
* Created by Reliese Model.
*/
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Str;
/**
* Class Video
*
* @property int $id
* @property string|null $external_id
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property Collection|VideoRender[] $video_renders
*/
class Video extends Model
{
use SoftDeletes;
protected $table = 'videos';
protected $casts = [
'width' => 'int',
'height' => 'int',
'payload' => 'object',
'render_settings' => 'object',
];
protected $fillable = [
'external_id',
'content_type',
'width',
'height',
'aspect_ratio',
'payload',
'render_settings',
];
protected $hidden = [
'id',
];
public function video_renders()
{
return $this->hasMany(VideoRender::class)->orderBy('id', 'DESC');
}
public function video_captions()
{
return $this->hasMany(VideoCaption::class)->orderBy('time', 'ASC');
}
public function video_elements()
{
return $this->hasMany(VideoElement::class)->orderBy('time', 'ASC');
}
public function latest_render()
{
return $this->hasOne(VideoRender::class)->latest();
}
/**
* The "booted" method of the model.
*/
protected static function booted(): void
{
static::creating(function ($model) {
$model->uuid = $model->uuid ?? (string) Str::uuid();
});
}
}

View File

@@ -1,56 +0,0 @@
<?php
/**
* Created by Reliese Model.
*/
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Class VideoCaption
*
* @property int $id
* @property int $video_id
* @property float $time
* @property float $duration
* @property string $text
* @property string $parameters
* @property string $payload
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property string|null $deleted_at
* @property Video $video
*/
class VideoCaption extends Model
{
use SoftDeletes;
protected $table = 'video_captions';
protected $casts = [
'video_id' => 'int',
'time' => 'float',
'duration' => 'float',
'parameters' => 'object',
'words' => 'object',
];
protected $fillable = [
'video_id',
'time',
'duration',
'text',
'parameters',
'words',
];
public function video()
{
return $this->belongsTo(Video::class);
}
}

View File

@@ -1,54 +0,0 @@
<?php
/**
* Created by Reliese Model.
*/
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Class VideoElement
*
* @property int $id
* @property string|null $original_asset_url
* @property uuid|null $asset_uuid
* @property string|null $asset_url
* @property string $type
* @property float $time
* @property int $track
* @property float $duration
* @property string $parameters
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
*/
class VideoElement extends Model
{
use SoftDeletes;
protected $table = 'video_elements';
protected $casts = [
'time' => 'float',
'track' => 'int',
'duration' => 'float',
'parameters' => 'object',
];
protected $fillable = [
'parent_element_id',
'external_reference',
'asset_hash',
'original_asset_url',
'asset_uuid',
'asset_url',
'type',
'time',
'track',
'duration',
'parameters',
];
}

View File

@@ -1,83 +0,0 @@
<?php
/**
* Created by Reliese Model.
*/
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Str;
/**
* Class VideoRender
*
* @property int $id
* @property uuid $uuid
* @property string|null $external_id
* @property int|null $video_id
* @property int|null $user_id
* @property string $payload
* @property string $status
* @property Carbon|null $processing_started_at
* @property Carbon|null $processing_finished_at
* @property uuid|null $completed_video_uuid
* @property string|null $completed_video_full_url
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property Video|null $video
* @property User|null $user
*/
class VideoRender extends Model
{
use SoftDeletes;
protected $table = 'video_renders';
protected $casts = [
'video_id' => 'int',
'user_id' => 'int',
'processing_started_at' => 'datetime',
'processing_finished_at' => 'datetime',
'payload' => 'object',
];
protected $fillable = [
'uuid',
'external_id',
'video_id',
'user_id',
'payload',
'status',
'processing_started_at',
'processing_finished_at',
'completed_video_uuid',
'completed_video_full_url',
];
protected $hidden = [
'id',
];
public function video()
{
return $this->belongsTo(Video::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
/**
* The "booted" method of the model.
*/
protected static function booted(): void
{
static::creating(function ($model) {
$model->uuid = $model->uuid ?? (string) Str::uuid();
});
}
}