Add NAS file manager integration and all pending platform changes
- Installed p7h/nas-file-manager package via private VCS repo - Published config/nas-file-manager.php with super_admin middleware restriction - Added NAS env vars to .env.example - Created admin/nas-storage page with connection info panel and file browser widget - Added NAS Storage link to admin sidebar (super_admin only) - Added SuperAdminController@nasStorage method and admin.nas-storage route - Includes all accumulated branch changes: profile wall, 2FA, audit logs, settings panel, country/phone/timezone components, posts, slideshow, playlist shares, video downloads/shares, comment likes, notifications, social links, and more Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
1 parent
d44490dfe0
commit
0b2e95ea65
161 files changed
+27496
-7250
No files matched your search
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Setting;
|
||||
use App\Models\Video;
|
||||
use FFMpeg\FFMpeg;
|
||||
use FFMpeg\Format\Video\X264;
|
||||
@@ -42,17 +43,41 @@ class CompressVideoJob implements ShouldQueue
|
||||
$compressedPath = storage_path('app/public/videos/' . $compressedFilename);
|
||||
|
||||
try {
|
||||
$ffmpeg = FFMpeg::create();
|
||||
$ffmpegConfig = Config::get('ffmpeg');
|
||||
$ffmpeg = FFMpeg::create([
|
||||
'ffmpeg.binaries' => $ffmpegConfig['ffmpeg'] ?? '/usr/bin/ffmpeg',
|
||||
'ffprobe.binaries' => $ffmpegConfig['ffprobe'] ?? '/usr/bin/ffprobe',
|
||||
'timeout' => $ffmpegConfig['timeout'] ?? 3600,
|
||||
]);
|
||||
$ffmpegVideo = $ffmpeg->open($originalPath);
|
||||
|
||||
// Use CRF 18 for high quality (lower = better quality, 18-23 is good range)
|
||||
// Use 'slow' preset for better compression efficiency
|
||||
// GPU NVENC encoding via config
|
||||
$ffmpegConfig = Config::get('ffmpeg');
|
||||
$videoPasses = $ffmpegConfig['defaults']['video'] ?? [];
|
||||
$audioPasses = $ffmpegConfig['defaults']['audio'] ?? [];
|
||||
$gpuEnabled = Setting::gpuEnabled();
|
||||
$encoder = Setting::gpuEncoder();
|
||||
$preset = Setting::gpuPreset();
|
||||
$device = Setting::gpuDevice();
|
||||
|
||||
$format = new X264('aac', 'h264_nvenc');
|
||||
if ($gpuEnabled) {
|
||||
$videoPasses = [
|
||||
"-c:v {$encoder}",
|
||||
"-preset {$preset}",
|
||||
'-rc vbr',
|
||||
'-cq 23',
|
||||
'-profile:v high',
|
||||
'-pix_fmt yuv420p',
|
||||
"-gpu {$device}",
|
||||
];
|
||||
} else {
|
||||
$videoPasses = [
|
||||
'-c:v libx264',
|
||||
'-preset fast',
|
||||
'-crf 23',
|
||||
'-profile:v high',
|
||||
'-pix_fmt yuv420p',
|
||||
];
|
||||
}
|
||||
$audioPasses = ['-c:a aac', '-b:a 192k'];
|
||||
|
||||
$format = new X264('aac', $encoder);
|
||||
foreach ($videoPasses as $pass) {
|
||||
$format->addLegacyOption($pass);
|
||||
}
|
||||
@@ -79,12 +104,13 @@ class CompressVideoJob implements ShouldQueue
|
||||
'mime_type' => 'video/mp4',
|
||||
]);
|
||||
|
||||
Log::info('CompressVideoJob: Video compressed with GPU NVENC', [
|
||||
Log::info('CompressVideoJob: Video compressed', [
|
||||
'video_id' => $video->id,
|
||||
'original_size' => $originalSize,
|
||||
'compressed_size' => $compressedSize,
|
||||
'saved' => round(($originalSize - $compressedSize) / $originalSize * 100) . '%',
|
||||
'encoder' => 'h264_nvenc'
|
||||
'encoder' => $encoder,
|
||||
'gpu' => $gpuEnabled,
|
||||
]);
|
||||
} else {
|
||||
// Compressed file is larger, delete it
|
||||
|
||||
+94
-66
@@ -2,17 +2,15 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Setting;
|
||||
use App\Models\Video;
|
||||
use FFMpeg\FFMpeg;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class GenerateHlsJob implements ShouldQueue
|
||||
{
|
||||
@@ -41,83 +39,114 @@ class GenerateHlsJob implements ShouldQueue
|
||||
return;
|
||||
}
|
||||
|
||||
$hlsDir = 'public/hls/' . $video->id;
|
||||
$hlsDir = 'public/hls/' . $video->id;
|
||||
$hlsPath = storage_path('app/' . $hlsDir);
|
||||
|
||||
// Clean existing HLS
|
||||
if (is_dir($hlsPath)) {
|
||||
Storage::deleteDirectory($hlsDir);
|
||||
}
|
||||
|
||||
Storage::makeDirectory($hlsDir);
|
||||
|
||||
$variants = [
|
||||
['height' => 480, 'name' => '480p', 'bitrate' => '1000k'],
|
||||
['height' => 720, 'name' => '720p', 'bitrate' => '2500k'],
|
||||
['height' => 1080, 'name' => '1080p', 'bitrate' => '5000k'],
|
||||
];
|
||||
|
||||
foreach ($variants as $v) {
|
||||
@mkdir($hlsPath . '/' . $v['name'], 0755, true);
|
||||
}
|
||||
|
||||
try {
|
||||
$ffmpegConfig = Config::get('ffmpeg');
|
||||
$ffmpeg = FFMpeg::create([
|
||||
'ffmpeg.binaries' => $ffmpegConfig['ffmpeg'] ?? '/usr/bin/ffmpeg',
|
||||
'ffprobe.binaries' => $ffmpegConfig['ffprobe'] ?? '/usr/bin/ffprobe',
|
||||
'timeout' => $ffmpegConfig['timeout'] ?? 3600,
|
||||
$ffmpegBin = \App\Models\Setting::ffmpegBinary();
|
||||
$gpuEnabled = Setting::gpuEnabled();
|
||||
$encoder = Setting::gpuEncoder(); // h264_nvenc / hevc_nvenc / libx264
|
||||
$preset = Setting::gpuPreset(); // p1–p7 for NVENC, fast/medium/slow for x264
|
||||
$device = Setting::gpuDevice(); // GPU index (0, 1, …)
|
||||
$hwaccel = Setting::gpuHwaccel(); // cuda / none
|
||||
|
||||
$cmd = [escapeshellcmd($ffmpegBin)];
|
||||
|
||||
// Hardware-accelerated decode when GPU is in use
|
||||
if ($gpuEnabled && $hwaccel !== 'none') {
|
||||
$cmd[] = "-hwaccel {$hwaccel}";
|
||||
$cmd[] = "-hwaccel_device {$device}";
|
||||
}
|
||||
|
||||
$cmd[] = '-i ' . escapeshellarg($sourcePath);
|
||||
|
||||
// One video+audio stream per variant
|
||||
$n = count($variants);
|
||||
for ($i = 0; $i < $n; $i++) {
|
||||
$cmd[] = '-map 0:v:0';
|
||||
$cmd[] = '-map 0:a:0?';
|
||||
}
|
||||
|
||||
// Video codec
|
||||
$cmd[] = "-c:v {$encoder}";
|
||||
if ($gpuEnabled && str_contains($encoder, 'nvenc')) {
|
||||
$cmd[] = "-preset {$preset}";
|
||||
$cmd[] = '-rc vbr';
|
||||
$cmd[] = '-cq 23';
|
||||
$cmd[] = "-gpu {$device}";
|
||||
} else {
|
||||
$cmd[] = "-preset {$preset}";
|
||||
$cmd[] = '-crf 23';
|
||||
}
|
||||
$cmd[] = '-pix_fmt yuv420p';
|
||||
|
||||
// Audio codec
|
||||
$cmd[] = '-c:a aac';
|
||||
$cmd[] = '-b:a 128k';
|
||||
$cmd[] = '-ar 48000';
|
||||
|
||||
// Per-variant scale + bitrate
|
||||
for ($i = 0; $i < $n; $i++) {
|
||||
$cmd[] = "-filter:v:{$i} scale=-2:{$variants[$i]['height']}";
|
||||
$cmd[] = "-b:v:{$i} {$variants[$i]['bitrate']}";
|
||||
}
|
||||
|
||||
// HLS muxer options
|
||||
$cmd[] = '-g 48';
|
||||
$cmd[] = '-sc_threshold 0';
|
||||
$cmd[] = '-f hls';
|
||||
$cmd[] = '-hls_time 6';
|
||||
$cmd[] = '-hls_list_size 0';
|
||||
$cmd[] = '-hls_flags independent_segments';
|
||||
$cmd[] = '-hls_segment_filename ' . escapeshellarg($hlsPath . '/%v/%03d.ts');
|
||||
$cmd[] = '-master_pl_name playlist.m3u8';
|
||||
|
||||
$vsm = implode(' ', array_map(
|
||||
fn ($i, $v) => "v:{$i},a:{$i},name:{$v['name']}",
|
||||
array_keys($variants),
|
||||
$variants
|
||||
));
|
||||
$cmd[] = '-var_stream_map ' . escapeshellarg($vsm);
|
||||
|
||||
$cmd[] = escapeshellarg($hlsPath . '/%v/index.m3u8');
|
||||
|
||||
$fullCmd = implode(' ', $cmd) . ' 2>&1';
|
||||
|
||||
Log::info('GenerateHlsJob: Starting', [
|
||||
'video_id' => $video->id,
|
||||
'gpu' => $gpuEnabled,
|
||||
'encoder' => $encoder,
|
||||
'device' => $gpuEnabled ? $device : 'cpu',
|
||||
]);
|
||||
|
||||
$videoMedia = $ffmpeg->open($sourcePath);
|
||||
exec($fullCmd, $output, $exitCode);
|
||||
|
||||
// HLS variants: 480p, 720p, 1080p
|
||||
$variants = [
|
||||
[
|
||||
'height' => 480,
|
||||
'name' => '480p',
|
||||
'bitrate' => 1000,
|
||||
],
|
||||
[
|
||||
'height' => 720,
|
||||
'name' => '720p',
|
||||
'bitrate' => 2500,
|
||||
],
|
||||
[
|
||||
'height' => 1080,
|
||||
'name' => '1080p',
|
||||
'bitrate' => 5000,
|
||||
],
|
||||
];
|
||||
if ($exitCode !== 0) {
|
||||
$tail = implode("\n", array_slice($output, -30));
|
||||
throw new \RuntimeException("FFmpeg exited {$exitCode}:\n{$tail}");
|
||||
}
|
||||
|
||||
$hlsOptions = [
|
||||
'-c:v h264_nvenc',
|
||||
'-preset p4',
|
||||
'-g 48', // GOP size 2s @25fps
|
||||
'-sc_threshold 0',
|
||||
'-c:a aac',
|
||||
'-ar 48000',
|
||||
'-f hls',
|
||||
'-hls_time 6',
|
||||
'-hls_list_size 0',
|
||||
'-hls_segment_filename',
|
||||
'%v/%03d.ts',
|
||||
'-hls_flags',
|
||||
'delete_segments+append_list',
|
||||
'-master_pl_name',
|
||||
'playlist.m3u8',
|
||||
];
|
||||
$video->update(['has_hls' => true, 'hls_path' => $hlsDir]);
|
||||
|
||||
$videoMedia->save(new \FFMpeg\Format\Video\X264(), $hlsPath, function ($filters) use ($variants) {
|
||||
foreach ($variants as $variant) {
|
||||
$filters->custom('-map 0:v:0 -map 0:a:0?')
|
||||
->size("trunc(oh*a/oh/{$variant['height']})*{$variant['height']}") // Scale
|
||||
->resize(new \FFMpeg\Coordinate\Dimension($variant['height'] * 16 / 9, $variant['height']))
|
||||
->videoCodec($variant['bitrate'] . 'k')
|
||||
->addLegacyOption('-var_stream_map v:0,name:' . $variant['name'] . ' v:1,name:720p v:2,name:1080p');
|
||||
}
|
||||
});
|
||||
|
||||
// Mark HLS ready
|
||||
$video->update([
|
||||
'has_hls' => true,
|
||||
'hls_path' => $hlsDir,
|
||||
]);
|
||||
|
||||
Log::info('GenerateHlsJob: HLS generated successfully', [
|
||||
Log::info('GenerateHlsJob: HLS generated', [
|
||||
'video_id' => $video->id,
|
||||
'variants' => array_column($variants, 'name'),
|
||||
'hls_url' => asset('storage/' . $hlsDir . '/playlist.m3u8'),
|
||||
'encoder' => $encoder,
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
@@ -126,4 +155,3 @@ class GenerateHlsJob implements ShouldQueue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user