paginate(12); return view('videos.index', compact('videos')); } public function create() { return view('videos.create'); } public function store(Request $request) { $request->validate([ 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'video' => 'required|mimes:mp4,mov,avi,webm|max:2000000', 'thumbnail' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:5120', 'orientation' => 'nullable|in:portrait,landscape,square,ultrawide', ]); $file = $request->file('video'); $filename = Str::uuid() . '.' . $file->getClientOriginalExtension(); $path = $file->storeAs('videos', $filename, 'public'); $width = 0; $height = 0; $duration = 0; $orientation = $request->orientation ?? 'landscape'; $videoPath = storage_path('app/public/videos/' . $filename); if (!$request->orientation && file_exists($videoPath)) { $durationCmd = "ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 " . escapeshellarg($videoPath); $duration = (int) shell_exec($durationCmd); $dimCmd = "ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 " . escapeshellarg($videoPath); $output = shell_exec($dimCmd); if ($output) { $parts = explode('x', trim($output)); if (count($parts) == 2) { $width = (int) trim($parts[0]); $height = (int) trim($parts[1]); if ($height > $width * 1.2) { $orientation = 'portrait'; } elseif ($width > $height * 1.5) { $orientation = 'ultrawide'; } elseif (abs($width - $height) < 50) { $orientation = 'square'; } else { $orientation = 'landscape'; } } } } $thumbnailFilename = null; if ($request->hasFile('thumbnail')) { $thumbFile = $request->file('thumbnail'); $thumbnailFilename = Str::uuid() . '.' . $thumbFile->getClientOriginalExtension(); $thumbFile->storeAs('thumbnails', $thumbnailFilename, 'public'); } elseif (file_exists($videoPath)) { $thumbFilename = Str::uuid() . '.jpg'; $thumbPath = storage_path('app/public/thumbnails/' . $thumbFilename); $cmd = "ffmpeg -i " . escapeshellarg($videoPath) . " -ss 00:00:01 -vframes 1 -vf 'scale=320:-1' " . escapeshellarg($thumbPath) . " -y 2>/dev/null"; shell_exec($cmd); if (file_exists($thumbPath)) { $thumbnailFilename = $thumbFilename; } } $video = Video::create([ 'title' => $request->title, 'description' => $request->description, 'filename' => $filename, 'path' => 'videos', 'thumbnail' => $thumbnailFilename, 'duration' => $duration, 'width' => $width, 'height' => $height, 'orientation' => $orientation, 'size' => $file->getSize(), 'status' => 'ready', ]); return response()->json([ 'success' => true, 'message' => 'Video uploaded successfully!', 'video_id' => $video->id, 'redirect' => route('videos.show', $video->id) ]); } public function show(Video $video) { return view('videos.show', compact('video')); } public function edit(Video $video) { return view('videos.edit', compact('video')); } public function update(Request $request, Video $video) { $request->validate([ 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'thumbnail' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:5120', 'orientation' => 'nullable|in:portrait,landscape,square,ultrawide', ]); $video->title = $request->title; $video->description = $request->description; if ($request->orientation) { $video->orientation = $request->orientation; } if ($request->hasFile('thumbnail')) { if ($video->thumbnail) { $oldThumb = storage_path('app/public/thumbnails/' . $video->thumbnail); if (file_exists($oldThumb)) { unlink($oldThumb); } } $thumbFile = $request->file('thumbnail'); $thumbnailFilename = Str::uuid() . '.' . $thumbFile->getClientOriginalExtension(); $thumbFile->storeAs('thumbnails', $thumbnailFilename, 'public'); $video->thumbnail = $thumbnailFilename; } $video->save(); return redirect()->route('videos.show', $video->id) ->with('success', 'Video updated successfully!'); } public function stream(Video $video) { $filePath = storage_path('app/public/videos/' . $video->filename); if (!file_exists($filePath)) { abort(404, 'Video file not found'); } $fileSize = filesize($filePath); $range = request()->header('Range'); header('Content-Type: video/mp4'); header('Accept-Ranges: bytes'); if ($range) { $ranges = explode('-', str_replace('bytes=', '', $range)); $start = intval($ranges[0]); $end = isset($ranges[1]) && $ranges[1] ? intval($ranges[1]) : $fileSize - 1; $length = $end - $start + 1; header('HTTP/1.1 206 Partial Content'); header('Content-Length: ' . $length); header('Content-Range: bytes ' . $start . '-' . $end . '/' . $fileSize); $fp = fopen($filePath, 'rb'); fseek($fp, $start); $chunkSize = 8192; while (!feof($fp) && (ftell($fp) <= $end)) { $chunk = fread($fp, min($chunkSize, $end - ftell($fp) + 1)); echo $chunk; flush(); } fclose($fp); } else { header('Content-Length: ' . $fileSize); readfile($filePath); } exit; } public function destroy(Video $video) { $path = storage_path('app/public/videos/' . $video->filename); if (file_exists($path)) { unlink($path); } if ($video->thumbnail) { $thumbPath = storage_path('app/public/thumbnails/' . $video->thumbnail); if (file_exists($thumbPath)) { unlink($thumbPath); } } $video->delete(); return redirect()->route('videos.index') ->with('success', 'Video deleted successfully!'); } }