Created
February 2, 2026 16:17
-
-
Save elbruno/48e94f07c7026b82f3796fc718b349f3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env dotnet run | |
| #:package OpenCvSharp4@4.* | |
| #:package OpenCvSharp4.runtime.win@4.* | |
| /* | |
| Video Extension Tool (.NET 10) | |
| Extends a video by adding extra time using the last frame. | |
| Usage: dotnet run extendvideo.cs | |
| Demo for El Bruno's YouTube channel: https://www.youtube.com/elbruno | |
| */ | |
| using OpenCvSharp; | |
| // Supported video extensions | |
| string[] videoExtensions = [".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv"]; | |
| Console.WriteLine(new string('=', 50)); | |
| Console.WriteLine(" 🎥 Video Extension Tool (.NET)"); | |
| Console.WriteLine(new string('=', 50)); | |
| // Get videos from current folder | |
| var videos = GetVideosFromFolder(); | |
| if (videos.Count == 0) | |
| { | |
| Console.WriteLine("\n❌ No video files found in the current folder."); | |
| Console.WriteLine(" Supported formats: mp4, avi, mov, mkv, wmv, flv"); | |
| return; | |
| } | |
| // Select video | |
| var selectedVideo = SelectVideo(videos); | |
| Console.WriteLine($"\n✅ Selected: {Path.GetFileName(selectedVideo)}"); | |
| // Select extra time | |
| var extraTime = SelectExtraTime(); | |
| Console.WriteLine($"✅ Extra time: {extraTime} seconds"); | |
| // Extend the video | |
| try | |
| { | |
| var (outputPath, originalDuration, newDuration) = ExtendVideo(selectedVideo, extraTime); | |
| // Show results | |
| Console.WriteLine("\n" + new string('=', 50)); | |
| Console.WriteLine(" 📊 Results"); | |
| Console.WriteLine(new string('=', 50)); | |
| Console.WriteLine($" Original duration: {FormatDuration(originalDuration)}"); | |
| Console.WriteLine($" New duration: {FormatDuration(newDuration)}"); | |
| Console.WriteLine($" New video: {Path.GetFileName(outputPath)}"); | |
| Console.WriteLine(new string('=', 50)); | |
| Console.WriteLine("\n✅ Video extended successfully!"); | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"\n❌ Error processing video: {ex.Message}"); | |
| } | |
| // --- Functions --- | |
| List<string> GetVideosFromFolder(string folder = ".") | |
| { | |
| var videos = new List<string>(); | |
| foreach (var ext in videoExtensions) | |
| { | |
| videos.AddRange(Directory.GetFiles(folder, $"*{ext}", SearchOption.TopDirectoryOnly)); | |
| } | |
| return videos.Take(5).ToList(); | |
| } | |
| string SelectVideo(List<string> videoList) | |
| { | |
| Console.WriteLine("\n📹 Available videos:"); | |
| for (int i = 0; i < videoList.Count; i++) | |
| { | |
| var defaultLabel = i == 0 ? " (default)" : ""; | |
| Console.WriteLine($" {i + 1}. {Path.GetFileName(videoList[i])}{defaultLabel}"); | |
| } | |
| Console.Write("\nSelect a video [1]: "); | |
| var choice = Console.ReadLine()?.Trim() ?? ""; | |
| if (string.IsNullOrEmpty(choice) || !int.TryParse(choice, out int idx)) | |
| return videoList[0]; | |
| idx--; | |
| return (idx >= 0 && idx < videoList.Count) ? videoList[idx] : videoList[0]; | |
| } | |
| int SelectExtraTime() | |
| { | |
| int[] options = [5, 10, 15]; | |
| Console.WriteLine("\n⏱️ Select extra time (seconds):"); | |
| for (int i = 0; i < options.Length; i++) | |
| { | |
| var defaultLabel = options[i] == 5 ? " (default)" : ""; | |
| Console.WriteLine($" {i + 1}. {options[i]} seconds{defaultLabel}"); | |
| } | |
| Console.Write("\nSelect extra time [1]: "); | |
| var choice = Console.ReadLine()?.Trim() ?? ""; | |
| if (string.IsNullOrEmpty(choice) || !int.TryParse(choice, out int idx)) | |
| return 5; | |
| idx--; | |
| return (idx >= 0 && idx < options.Length) ? options[idx] : 5; | |
| } | |
| (string outputPath, double originalDuration, double newDuration) ExtendVideo(string videoPath, int extraSeconds) | |
| { | |
| Console.WriteLine($"\n🎬 Processing: {Path.GetFileName(videoPath)}"); | |
| // Open the video | |
| using var cap = new VideoCapture(videoPath); | |
| if (!cap.IsOpened()) | |
| throw new Exception($"Cannot open video: {videoPath}"); | |
| // Get video properties | |
| var fps = cap.Fps; | |
| var width = (int)cap.Get(VideoCaptureProperties.FrameWidth); | |
| var height = (int)cap.Get(VideoCaptureProperties.FrameHeight); | |
| var totalFrames = (int)cap.Get(VideoCaptureProperties.FrameCount); | |
| var originalDuration = totalFrames / fps; | |
| // Generate output filename | |
| var directory = Path.GetDirectoryName(videoPath) ?? "."; | |
| var baseName = Path.GetFileNameWithoutExtension(videoPath); | |
| var ext = Path.GetExtension(videoPath); | |
| var outputPath = Path.Combine(directory, $"{baseName}_extended{ext}"); | |
| // Set up video writer | |
| var fourcc = FourCC.MP4V; | |
| using var writer = new VideoWriter(outputPath, fourcc, fps, new Size(width, height)); | |
| Console.WriteLine("📝 Writing extended video..."); | |
| using var lastFrame = new Mat(); | |
| var frameCount = 0; | |
| // Copy all original frames | |
| using var frame = new Mat(); | |
| while (true) | |
| { | |
| if (!cap.Read(frame) || frame.Empty()) | |
| break; | |
| frame.CopyTo(lastFrame); | |
| writer.Write(frame); | |
| frameCount++; | |
| if (frameCount % 100 == 0) | |
| Console.Write($"\r Processed {frameCount}/{totalFrames} frames..."); | |
| } | |
| Console.WriteLine($"\r Processed {frameCount}/{totalFrames} frames... "); | |
| // Add extra frames using the last frame | |
| var extraFrames = (int)(extraSeconds * fps); | |
| Console.WriteLine($" Adding {extraFrames} extra frames ({extraSeconds}s)..."); | |
| for (int i = 0; i < extraFrames; i++) | |
| { | |
| writer.Write(lastFrame); | |
| if ((i + 1) % 100 == 0) | |
| Console.Write($"\r Added {i + 1}/{extraFrames} extra frames..."); | |
| } | |
| Console.WriteLine($"\r Added {extraFrames}/{extraFrames} extra frames... "); | |
| var newDuration = originalDuration + extraSeconds; | |
| return (outputPath, originalDuration, newDuration); | |
| } | |
| string FormatDuration(double seconds) | |
| { | |
| var minutes = (int)(seconds / 60); | |
| var secs = (int)(seconds % 60); | |
| return $"{minutes:D2}:{secs:D2}"; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment