Files
wticreatorstudio/Modules/StudioPlus/Services/UploadService.php
T
Leonard Biano f06b560150 StudioPlus Module Update
Fix download filename upon reprocess and video bugs
2024-06-12 22:44:02 +08:00

608 lines
24 KiB
PHP

<?php
namespace Modules\StudioPlus\Services;
use \FFMpeg\FFMpeg;
use \FFMpeg\FFProbe;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Modules\StudioPlus\Entities\StudioPlusVideo;
use Modules\StudioPlus\Jobs\ProcessVideo;
use Modules\StudioPlus\Jobs\StoreVideo;
use Symfony\Component\Process\Process;
use Modules\StudioPlus\Entities\StudioPlusThumbnail;
class UploadService
{
protected $watermark_service;
protected $video_resolutions_service;
public function __construct()
{
$this->watermark_service = new WatermarkService();
$this->video_resolutions_service = new VideoResolutionsService();
}
public function handleUpload(Request $request)
{
$file = $request->file('video');
$filename = $file->getClientOriginalName();
$token = $request->token;
if ($request->has('mode') && $request->mode == 'create') {
$mode = 'create';
$id = auth()->id();
} else {
$mode = 'update';
$id = $request->id;
}
if ($id) {
if ($request->has('tempPath')) {
$temp_path = $request->tempPath;
} else {
$temp_path = $this->getTempLocalStoragePath($id, $token, $mode);
}
if ($temp_path) {
Storage::deleteDirectory($temp_path);
Storage::put("$temp_path/filename.dat", $filename);
Storage::put("$temp_path/path.dat", $temp_path);
$temp_filename = 'video.mp4';
$video_local_url = Storage::putFileAs($temp_path, $file, $temp_filename);
$resolution = $this->getVideoResolution(storage_path("app/$video_local_url"));
if ($this->isResolutionAllowed($resolution)) {
VideoService::copyAndRescale(storage_path("app/$temp_path/$temp_filename"), storage_path("app/$temp_path/video.mp4"));
$ffmpeg = FFMpeg::create();
$video = $ffmpeg->open(storage_path("app/{$video_local_url}"));
$ffprobe = FFProbe::create(get_ffprobe_config());
$code_ac = $ffprobe->format(storage_path("app/{$video_local_url}"))->get('duration');
// $thumbnails_storage_path = "$temp_path/thumbnails";
// if (! Storage::exists($thumbnails_storage_path)) {
// Storage::makeDirectory($thumbnails_storage_path);
// }
// $thumbnails = [];
// $timeFrameInSeconds = 2;
// for ($i = 0; $i < 3; $i++) {
// if ($timeFrameInSeconds < round($code_ac, PHP_ROUND_HALF_DOWN)) {
// $thumbnail_filename = Str::random(10).'.jpg';
// $thumbnail_path = asset("storage/app/$temp_path/thumbnails/$thumbnail_filename");
// $video->frame(\FFMpeg\Coordinate\TimeCode::fromSeconds($timeFrameInSeconds))
// ->save(storage_path("app/$temp_path/thumbnails/$thumbnail_filename"));
// $thumbnails[] = [
// 'id' => $thumbnail_filename,
// 'thumbnail_url' => $thumbnail_path,
// 'filename' => $thumbnail_filename,
// 'is_active' => $i === 0,
// ];
// $timeFrameInSeconds += 2;
// }
// }
// if ($mode == 'update') {
// $studio_plus_video = StudioPlusVideo::find($id);
// $current_thumbnails = $studio_plus_video->thumbnails->where('is_uploaded',1)->toArray();
// $thumbnails = array_merge($thumbnails, $current_thumbnails);
// }
return [
'temp_path' => $temp_path,
'url' => asset('storage/app/'.$video_local_url),
//'thumbnails' => $thumbnails,
];
} else {
return 'invalid-resolution';
}
}
}
return false;
}
public function handleUploadThumbnail(Request $request)
{
$file = $request->file('thumbnail');
$filename = $file->getClientOriginalName();
$exploded_filename = explode('.', $filename);
$ext = $exploded_filename[count($exploded_filename) - 1];
$id = auth()->id();
$mode = 'create';
if ($id) {
if ($request->has('tempPath')) {
$temp_path = $request->tempPath;
} else {
$temp_path = $this->getTempLocalStoragePath($id, $mode);
}
if ($temp_path) {
$ext = 'png';
if($request->has('type') && $request->type == 'short'){
$temp_filename = "000-is-uploaded-short.$ext";
} else {
$temp_filename = "000-is-uploaded.$ext";
}
Storage::delete("$temp_path/thumbnails/$temp_filename");
$thumbnail_local_url = Storage::putFileAs("$temp_path/thumbnails", $file, $temp_filename);
$thumbnails = Storage::allFiles("$temp_path/thumbnails");
$ret = [];
$i = 0;
foreach ($thumbnails as $thumbnail) {
$thumbnail_filename = basename($thumbnail);
$thumbnail_path = asset("storage/app/$temp_path/thumbnails/$thumbnail_filename");
$ret[] = [
'key' => Str::random(),
'id' => $thumbnail_filename,
'thumbnail_url' => $thumbnail_path,
'filename' => $thumbnail_filename,
'is_short' => str_contains($thumbnail_filename, 'is-uploaded-short') ? true : false,
'is_uploaded' => str_contains($thumbnail_filename, 'is-uploaded') ? true : false,
];
$i++;
}
return $ret;
}
}
return null;
}
public function processVideo($id, $mode = 'update')
{
ProcessVideo::dispatch($id, $mode);
}
public function asyncStoreVideo($id)
{
StoreVideo::dispatch($id);
}
public function updateVideo($id, $mode = 'update')
{
$video = StudioPlusVideo::find($id);
if ($video) {
$video->upload_status = StudioPlusVideo::UPLOAD_STATUS_PROCESSING;
$video->update();
$temp_path = $this->getTempUploadStoragePath($id);
//$result_path = storage_path("app/$temp_path/result.mp4");
Storage::delete("$temp_path/result.mp4");
//$is_short = Storage::exists("$temp_path/short.dat");
Storage::copy("$temp_path/video.mp4", "$temp_path/result.mp4");
if (Storage::exists("$temp_path/result.mp4")) {
Storage::put("$temp_path/processed.dat", 'Video processed.');
$this->storeVideo($id, $mode);
}
}
}
public function updateVideox($id, $mode = 'update')
{
$studio_plus_video = StudioPlusVideo::find($id);
$temp_path = $this->getTempUploadStoragePath($id);
if ($studio_plus_video) {
//$studio_plus_video->upload_status = StudioPlusVideo::UPLOAD_STATUS_PROCESSING;
//$studio_plus_video->update();
Log::info('Processing video '.$studio_plus_video->id);
}
$globalSetting = $studio_plus_video->globalSetting;
/*if ($globalSetting) {
$introLocalPath = storage_path('app/public/' . $globalSetting->details['intro']);
$outroLocalPath = storage_path('app/public/' . $globalSetting->details['outro']);
$outroOutputPath = storage_path("app/$temp_path/outro.mp4");
Storage::delete("$temp_path/outro.mp4");
VideoService::copyAndRescale($outroLocalPath, $outroOutputPath);
Log::info('Copied outro');
Log::info('intro outro paths');
Log::info($introLocalPath);
Log::info($outroLocalPath);
}
else {
$introLocalPath = module_path('StudioPlus') . "/Storage/default.ts";
Log::info('no global setting');
}
$introOutputPath = storage_path("app/$temp_path/intro.mp4");
Storage::delete("$temp_path/intro.mp4");
VideoService::copyAndRescale($introLocalPath, $introOutputPath);
Log::info('Copied intro');*/
// Copy watermark
$watermark = storage_path('app/CreatorStudio/watermark-transparent.png');
$video = storage_path("app/$temp_path/video.mp4");
//$outroOutputPath = isset($outroOutputPath) ? $outroOutputPath : $introOutputPath;
$resultPath = storage_path("app/$temp_path/result.mp4");
$result_path = $resultPath;
Storage::delete("$temp_path/result.mp4");
$is_short = Storage::exists("$temp_path/short.dat");
try {
/*Storage::copy("$temp_path/video.mp4", "$temp_path/result.mp4");
Log::info('Copied video as result for short');
if ($is_short) {
Storage::copy("$temp_path/video.mp4", "$temp_path/result.mp4");
Log::info('Copied video as result for short');
}
else {
Log::info('Merging videos');
$this->concat(
$introOutputPath,
$video,
$resultPath,
$outroOutputPath
);
Log::info('Merged videos');
}*/
if ($is_short) {
Storage::copy("$temp_path/video.mp4", "$temp_path/result.mp4");
} else {
$this->watermark_service->addWatermark($studio_plus_video, $result_path);
}
} catch(\Exception $ex) {
Log::error('Exception processing video');
Log::error($ex);
}
if (Storage::exists("$temp_path/result.mp4")) {
Storage::put("$temp_path/processed.dat", 'Added intro and outro videos.');
StoreVideo::dispatch($id, $mode);
}
}
public function storeVideo($id, $mode = 'update')
{
$video = StudioPlusVideo::find($id);
if ($video) {
$gcp = new GoogleCloudPlatform();
$temp_path = $this->getTempUploadStoragePath($id);
$is_processed = Storage::exists("$temp_path/processed.dat");
$is_stored = Storage::exists("$temp_path/stored.dat");
if ($is_processed && ! $is_stored) {
$original_video_path = storage_path("app/$temp_path/video.mp4");
$ffprobe = FFProbe::create(get_ffprobe_config());
$code_ac = $ffprobe->format($original_video_path)->get('duration');
$original_video_filename = 'O'.Carbon::now()->format('YmdHis').Str::random().'.mp4';
$original_video_object_name = $gcp->getGoogleStoragePath($video)."/$original_video_filename";
$gcp->uploadFileToGCP($original_video_path, $original_video_object_name, $video->videoType->vertical_video ? $video->title : $video->asin);
$result_path = storage_path("app/$temp_path/result.mp4");
$filename = 'P'.Carbon::now()->format('YmdHis').Str::random().'.mp4';
$object_name = $gcp->getGoogleStoragePath($video)."/$filename";
$gcp->uploadFileToGCP($result_path, $object_name, $video->videoType->vertical_video ? $video->title : $video->asin);
$gcp_storage_path = $gcp->getGoogleStoragePath($video);
// $thumbnails = Storage::allFiles("$temp_path/thumbnails");
// $has_active_thumbnail = false;
// if ($mode == 'update') {
// $video->thumbnails()->where('is_uploaded',0)->delete();
// $has_active_thumbnail = $video->thumbnails()->where('is_active',1)->first();
// }
// $i = 0;
// foreach ($thumbnails as $thumbnail) {
// $thumbnail_filename = basename($thumbnail);
// $thumbnail_nfilename = Carbon::now()->format('YmdHis').Str::random().$thumbnail_filename;
// $thumbnail_path = "$gcp_storage_path/thumbnails/$thumbnail_nfilename";
// $gcp->uploadFileToGCP(storage_path("app/$temp_path/thumbnails/$thumbnail_filename"), $thumbnail_path);
// if ($video->thumbnail_id) {
// $is_active = $video->thumbnail_id == $thumbnail_filename;
// } else {
// $is_active = $i === 0;
// }
// $video->thumbnails()->create([
// 'thumbnail_url' => "$thumbnail_path",
// 'filename' => $thumbnail_nfilename,
// 'is_active' => $has_active_thumbnail ? 0 : $is_active,
// 'is_uploaded' => str_contains($thumbnail_filename, 'is-uploaded') ? true : false,
// ]);
// $i++;
// }
/*$total_reviews = 0;
$product_details = (new AmazonProductService())->getProductDetails($video->asin);
if ($product_details) {
$total_reviews = $product_details['amazon_total_reviews'];
}*/
$video->update([
'original_video_url' => $original_video_object_name,
'video_url' => $object_name,
'video_duration' => round($code_ac, PHP_ROUND_HALF_UP),
'upload_status' => StudioPlusVideo::UPLOAD_STATUS_PENDING,
]);
Storage::put("$temp_path/stored.dat", 'Video and thumbnails has been uploaded.');
Storage::deleteDirectory(Storage::get("$temp_path/temp_path.dat"));
Storage::deleteDirectory($temp_path);
return true;
} else {
sleep(5);
StoreVideo::dispatch($id);
}
}
return false;
}
public function createVideo($request, $studio_plus_channel)
{
$gcp = new GoogleCloudPlatform();
$id = auth()->id();
$token = $request->token;
//$temp_path = $this->getTempLocalStoragePath($id, $token, 'create');
$temp_path = $request->tempPath;
if ($temp_path) {
if ($request->has('is_short') && $request->is_short) {
Storage::put("$temp_path/short.dat", 'Short Video');
}
Storage::put("$temp_path/thumbnail.dat", $request->thumbnail_id);
$filename = Storage::get("$temp_path/filename.dat");
$filename = str_replace(' ', '', $filename);
$exploded_filename = explode('.', $filename);
$title = collect(Arr::except($exploded_filename, array_key_last($exploded_filename)))->join(' ');
$video_local_url = "storage/app/$temp_path/video.mp4";
$params = $request->except('_token');
$params['original_video_url'] = $video_local_url;
$params['video_url'] = $video_local_url;
$video = $gcp->storeNewVideo(
$studio_plus_channel,
$title,
$filename,
$params
);
// $thumbnails = Storage::allFiles("$temp_path/thumbnails");
// $i = 0;
// foreach ($thumbnails as $thumbnail) {
// $thumbnail_filename = basename($thumbnail);
// $thumbnail_path = "app/$temp_path/thumbnails/$thumbnail_filename";
// if ($request->has('thumbnail_id')) {
// $is_active = $request->thumbnail_id == $thumbnail_filename;
// } else {
// $is_active = $i === 0;
// }
// $video->thumbnails()->create([
// 'thumbnail_url' => "$thumbnail_path",
// 'filename' => $thumbnail_filename,
// 'is_active' => $is_active,
// 'is_short' => str_contains($thumbnail_filename, 'is-uploaded-short') ? true : false,
// 'is_uploaded' => str_contains($thumbnail_filename, 'is-uploaded') ? true : false,
// ]);
// $i++;
// }
Storage::put("$temp_path/temp_path.dat", $temp_path);
$video_upload_path = $this->getTempUploadStoragePath($video->id);
File::copyDirectory(storage_path("app/$temp_path"), storage_path("app/$video_upload_path"));
if ($video) {
$video_upload_temp_path = "storage/app/$video_upload_path/video.mp4";
$video->original_video_url = $video_upload_temp_path;
$video->video_url = $video_upload_temp_path;
$video->studio_plus_asin_id = !$video->videoType->vertical_video ? $request->studio_plus_asin_id : null;
$video->save();
}
return $video;
}
return false;
}
public function handleUpdateThumbnail($id, $file)
{
$video = StudioPlusVideo::find($id);
if ($video) {
$gcp = new GoogleCloudPlatform();
$filename = $file->getClientOriginalName();
$exploded_filename = explode('.', $filename);
$ext = $exploded_filename[count($exploded_filename) - 1];
$nfilename = Carbon::now()->format('YmdHis').Str::random().$filename;
$full_path = '/uploads/users/'.auth()->id()."/videos/{$video->id}/thumbnails/$nfilename";
$gcp->uploadFileToGCP($file, $full_path);
$video->thumbnails()->update([
'is_active' => false,
'is_uploaded' => false,
]);
$thumbnail = $video->thumbnails()->create([
'filename' => $filename,
'thumbnail_url' => $full_path,
'is_active' => true,
'is_short' => str_contains($filename, 'is-uploaded-short') ? true : false,
'is_uploaded' => true,
]);
return $thumbnail;
}
return null;
}
public function getTempLocalStoragePath($id, $token = 'default', $mode = 'update')
{
if ($mode == 'create') {
$stamp = Carbon::now()->format('YmdHis');
$uid = $stamp.Str::random();
$path = "CreatorStudio/temp/user/$id/$token/$uid";
return $path;
} else {
$video = StudioPlusVideo::find($id);
if ($video) {
return "CreatorStudio/temp/videos/$id";
}
}
return null;
}
public function getTempUploadStoragePath($id)
{
$video = StudioPlusVideo::find($id);
if ($video) {
return "CreatorStudio/temp/upload/$id";
}
return null;
}
public function concat(string $intro, string $pathToVideo, string $pathToOutput, string $outro = ''): void
{
$newOutro = $outro ?: $intro;
`ffmpeg -i "$intro" -i "$pathToVideo" -i "$newOutro" -filter_complex "[0:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:-1:-1,setsar=1,fps=30,format=yuv420p[v0];[1:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:-1:-1,setsar=1,fps=30,format=yuv420p[v1];[2:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:-1:-1,setsar=1,fps=30,format=yuv420p[v2];[v0][0:a][v1][1:a][v2][2:a]concat=n=3:v=1:a=1[v][a]" -map "[v]" -map "[a]" -c:v libx264 -c:a aac -movflags +faststart "$pathToOutput"`;
}
public function getVideoResolution($video_path)
{
Log::info('Getting Video Resolution:');
Log::info($video_path);
$process = new Process(['ffprobe', '-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=width,height', '-of', 'json', $video_path]);
$process->run();
if ($process->isSuccessful()) {
$output = json_decode($process->getOutput(), true);
Log::info('GetVideoResolution Success.');
Log::info($output);
return $output['streams'][0];
} else {
Log::info($process->getOutput());
Log::info('GetVideoResolution Failed.');
}
return false;
}
public function watermarked($video_path, $watermark_path, $output_video_path)
{
`ffmpeg -i "$video_path" -i "$watermark_path" -filter_complex "overlay=10:10:enable='between(t\\,0\\,3)'" "$output_video_path"`;
}
public function getVideoDuration($video)
{
$ffprobe = FFProbe::create(get_ffprobe_config());
$duration = $ffprobe
->streams($video)
->videos()
->first()
->get('duration');
return $duration;
}
public function addWatermark($video, $watermark, $output)
{
$output_stage_1 = str_replace('result', 'result-1', $output);
$output_stage_2 = str_replace('result', 'result-2', $output);
$output_stage_3 = str_replace('result', 'result-3', $output);
$this->addStartingWatermark($video, $watermark, $output_stage_1);
$this->addEndingWatermark($output_stage_1, $watermark, $output_stage_2);
$this->addTextWatermark($output_stage_2, 'WTI Creator Studio', $output_stage_3);
}
public function addStartingWatermark($video, $watermark, $output)
{
`ffmpeg -i "$video" -i "$watermark" -filter_complex "overlay=10:10:enable='between(t\\,0\\,3)'" "$output"`;
}
public function addEndingWatermark($video, $watermark, $output)
{
$duration = $this->getVideoDuration($video);
if ($duration) {
$duration = ceil($duration);
$start = $duration - 3;
$end = $duration;
`ffmpeg -i "$video" -i "$watermark" -filter_complex "overlay=10:10:enable='between(t\\,$start\\,$end)'" "$output"`;
}
}
public function addTextWatermark($video, $watermark, $output)
{
$font = storage_path('app/CreatorStudio/sample.ttf');
$font = str_replace('\\', '/', $font);
Log::info($font);
$font = 'D\:/Works/wticreatorstudio/storage/app/CreatorStudio/sample.ttf';
`ffmpeg -i "$video" -vf "drawtext=fontfile='$font':text='$watermark':fontcolor=white:fontsize=48:box=1:[email protected]:boxborderw=20:x=(w-text_w)/2:y=(h-text_h)/2:enable='between(t\\,5\\,10)'" "$output"`;
}
protected function isResolutionAllowed($resolution)
{
$allowed_resolutions = $this->video_resolutions_service->getActive();
foreach ($allowed_resolutions as $item) {
if ($item->width == $resolution['width'] && $item->height == $resolution['height']) {
return true;
}
}
return false;
}
}