mirror of
https://github.com/openclassify/openclassify.git
synced 2026-04-14 11:12:09 -05:00
150 lines
5.0 KiB
PHP
150 lines
5.0 KiB
PHP
<?php
|
|
|
|
namespace Modules\Video\Support;
|
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use Modules\S3\Support\MediaStorage;
|
|
use Modules\Video\Models\Video;
|
|
use RuntimeException;
|
|
use Symfony\Component\Process\Process;
|
|
|
|
class VideoTranscoder
|
|
{
|
|
public function transcode(Video $video): array
|
|
{
|
|
$disk = (string) config('video.disk', MediaStorage::activeDisk());
|
|
$inputDisk = Storage::disk((string) ($video->upload_disk ?: $disk));
|
|
$outputDisk = Storage::disk($disk);
|
|
$workspace = storage_path('app/private/video-processing/'.Str::uuid());
|
|
$inputExtension = pathinfo((string) $video->upload_path, PATHINFO_EXTENSION) ?: 'mp4';
|
|
$inputPath = $workspace.'/input.'.$inputExtension;
|
|
$outputPath = $workspace.'/output.mp4';
|
|
$outputRelativePath = $video->mobileOutputPath();
|
|
$inputStream = null;
|
|
$outputStream = null;
|
|
|
|
if (! is_dir($workspace) && ! mkdir($workspace, 0775, true) && ! is_dir($workspace)) {
|
|
throw new RuntimeException('Video processing workspace could not be created.');
|
|
}
|
|
|
|
try {
|
|
$inputStream = $inputDisk->readStream((string) $video->upload_path);
|
|
|
|
if (! is_resource($inputStream)) {
|
|
throw new RuntimeException('Source video could not be read.');
|
|
}
|
|
|
|
$localInputStream = fopen($inputPath, 'wb');
|
|
|
|
if (! is_resource($localInputStream)) {
|
|
throw new RuntimeException('Temporary input video could not be created.');
|
|
}
|
|
|
|
stream_copy_to_stream($inputStream, $localInputStream);
|
|
fclose($localInputStream);
|
|
|
|
$process = new Process([
|
|
(string) config('video.ffmpeg', 'ffmpeg'),
|
|
'-y',
|
|
'-i',
|
|
$inputPath,
|
|
'-map',
|
|
'0:v:0',
|
|
'-map',
|
|
'0:a:0?',
|
|
'-vf',
|
|
'scale=min('.(int) config('video.mobile_width', 854).'\\,iw):-2',
|
|
'-c:v',
|
|
'libx264',
|
|
'-preset',
|
|
'veryfast',
|
|
'-crf',
|
|
(string) config('video.mobile_crf', 31),
|
|
'-maxrate',
|
|
(string) config('video.mobile_video_bitrate', '900k'),
|
|
'-bufsize',
|
|
'1800k',
|
|
'-movflags',
|
|
'+faststart',
|
|
'-pix_fmt',
|
|
'yuv420p',
|
|
'-c:a',
|
|
'aac',
|
|
'-b:a',
|
|
(string) config('video.mobile_audio_bitrate', '96k'),
|
|
'-ac',
|
|
'2',
|
|
$outputPath,
|
|
]);
|
|
|
|
$process->setTimeout((int) config('video.timeout', 1800));
|
|
$process->run();
|
|
|
|
if (! $process->isSuccessful()) {
|
|
throw new RuntimeException(trim($process->getErrorOutput()) ?: 'Video transcoding failed.');
|
|
}
|
|
|
|
$probe = new Process([
|
|
(string) config('video.ffprobe', 'ffprobe'),
|
|
'-v',
|
|
'error',
|
|
'-select_streams',
|
|
'v:0',
|
|
'-show_entries',
|
|
'stream=width,height:format=duration',
|
|
'-of',
|
|
'json',
|
|
$outputPath,
|
|
]);
|
|
|
|
$probe->setTimeout(30);
|
|
$probe->run();
|
|
|
|
$outputStream = fopen($outputPath, 'rb');
|
|
|
|
if (! is_resource($outputStream)) {
|
|
throw new RuntimeException('Processed video could not be opened.');
|
|
}
|
|
|
|
if (! $outputDisk->put($outputRelativePath, $outputStream, ['visibility' => 'public'])) {
|
|
throw new RuntimeException('Processed video could not be stored.');
|
|
}
|
|
|
|
$metadata = json_decode($probe->getOutput(), true);
|
|
$stream = $metadata['streams'][0] ?? [];
|
|
$format = $metadata['format'] ?? [];
|
|
|
|
return [
|
|
'disk' => $disk,
|
|
'path' => $outputRelativePath,
|
|
'mime_type' => $outputDisk->mimeType($outputRelativePath) ?: 'video/mp4',
|
|
'size' => $outputDisk->size($outputRelativePath),
|
|
'width' => isset($stream['width']) ? (int) $stream['width'] : null,
|
|
'height' => isset($stream['height']) ? (int) $stream['height'] : null,
|
|
'duration_seconds' => isset($format['duration']) ? (int) round((float) $format['duration']) : null,
|
|
];
|
|
} finally {
|
|
if (is_resource($inputStream)) {
|
|
fclose($inputStream);
|
|
}
|
|
|
|
if (is_resource($outputStream)) {
|
|
fclose($outputStream);
|
|
}
|
|
|
|
if (is_file($inputPath)) {
|
|
unlink($inputPath);
|
|
}
|
|
|
|
if (is_file($outputPath)) {
|
|
unlink($outputPath);
|
|
}
|
|
|
|
if (is_dir($workspace)) {
|
|
rmdir($workspace);
|
|
}
|
|
}
|
|
}
|
|
}
|