Skip to main content

Building a C# WinForms Video Project Archive Renamer With Gemini 2.5 Pro

I've got a collection of video projects amassed over years, each with its own unique (and often inconsistent) naming scheme. I eventually came up with a format that makes it easier, but there are probably hundreds of older projects in archive that are a mess. 

Tthis can become a real headache when trying to find old projects. Recently, I decided to tackle this by building a custom C# WinForms application to bring some order to my video project archive with the help of Gemini Pro 2.5 in their web based AI studio.

This post isn't just about the final code; it's about the journey – the initial idea, the back-and-forth of refining requirements with Gemini, and the iterative process of building a tool that meets the need. We'll be using .NET 8 and C# for this WinForms adventure.

The Initial Spark: The Problem and The Request

The project began with a clear problem: my old video project folders were a mess. Some had dates, some didn't, and there was no standard early on. I wanted a tool to:

  1. Identify folders that didn't already follow my new naming convention (YYYYMMDD-ProjectName).

  2. For those folders, find the oldest Magix Vegas Pro project file (.vf or .veg) inside.

  3. Use that file's creation date and its filename (without extension) to rename the parent folder.

  4. If no Vegas files were present, use the oldest file of any type to get the date.

  5. Provide a simple UI with a folder selector, start/cancel buttons, a status display, and a progress bar.

Here's the initial prompt I started working from:

"Here is the original image:

I put together the application interface myself and gave Gemini a photo.

I'd like to make a C# .net 8.0 winforms application.

I'll handle the interface aspect. Here are the three controls on the form for reference:
* A label called lblFolderSelectedDisplay.
* A button called btnSelectFolderAndStart for selecting the folder and then starting the process.
* A button called btnCancelRenameProcess that will be enabled/disabled based on if the rename process is running or not. This will stop the background worker if it's running but the user wants it to stop. Once cancel has been run the button will be disabled until the user selects another folder and starts another rename process.
* A TextBox called txtStatusDisplay that will be used to show the status of each folder renamed.
* A BackgroundWorker called bwFolderRenamingProcess form object that will hold the processing
* A ProgressBar called pbStatusBar control to visually display the current status of how many folders are remaining.

The folder renaming logic:
There is a folder with a large number of folders inside. My current naming scheme is like: 20201008-rc-Sigma-56-vs-Canon-50 where 20201008 is the date (October 8th 2020). Another example is 20240918-7Artisans-50mm-85mm where 20240918 is the date (September 18th 2024).

But I have a lot of older projects where I wasn't following that formula.
I want to rename those folders to follow the new naming formula.
Skip the folders that already have at least one number at the start of the folder name.

Inside each project folder should be files with the extension .vf or .veg Vegas video editor project files. I'd like to take the file creation date from the oldest .vf or .veg file inside each project. If there are no .vf or .veg files, use whatever video files or whatever else in the folder.

In addition to using the file creation date, I'd like to use the filename portion of the .vf or .veg file as the second part of the folder name, using - between the date and the rest of the filename.

Let me know if you have any code detail questions before creating it."

 

Laying the Groundwork: The First Version

With the requirements laid out, the first version of the Form1.cs code was drafted. Key components included:

  • UI Event Handlers: For btnSelectFolderAndStart_Click and btnCancelRenameProcess_Click.

  • BackgroundWorker (bwFolderRenamingProcess): Essential for keeping the UI responsive during potentially long-running file operations. It was set up with WorkerReportsProgress = true and WorkerSupportsCancellation = true.

    • DoWork: Contained the main folder iteration and renaming logic.

    • ProgressChanged: Updated the txtStatusDisplay and pbStatusBar.

    • RunWorkerCompleted: Handled the end of the process (success, error, or cancellation) and reset UI elements.

  • Helper Class ProgressReportUpdate: A small class to pass structured status messages and progress information from DoWork to ProgressChanged.

  • SanitizeFolderName Method: A static utility function to remove invalid characters from potential folder names.

The core logic in DoWork aimed to:

  1. Get all subdirectories.

  2. For each subdirectory:
    a. Check if it started with a number (the initial "skip" logic).
    b. Search for .vf/.veg files, find the oldest.
    c. If not found, search for any file and find the oldest.
    d. Construct the new name: YYYYMMDD-FilenamePart.
    e. Handle potential name collisions by appending _1, _2, etc.
    f. Perform the rename using Directory.Move().


The Iterative Process: Refining and Fixing

Software development is rarely a straight line. As soon as the first version was tested, a few quirks and necessary refinements emerged.

1. Oops! System Folders:

  • The Feedback: "The program seems to work, but it did try to access two things it shouldn't: System Volume Information... ERROR accessing files... and $RECYCLE.BIN... Skipped (no files found)... Keep in mind this was the base directory of a drive instead of a folder..."

  • The Problem: The initial scan didn't account for protected system or hidden folders, leading to access denied errors or incorrect processing.

  • The Fix: We needed to check the FileAttributes of each folder.

    // Inside the DoWork loop, for each directory:
    DirectoryInfo dirInfo = new DirectoryInfo(currentSubFolderPath);
    if ((dirInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden ||
        (dirInfo.Attributes & FileAttributes.System) == FileAttributes.System)
    {
        worker.ReportProgress(i + 1, new ProgressReportUpdate($"Skipped: '{currentSubFolderName}' (System or Hidden folder)."));
        continue; // Skip to the next folder
    }

    This addition ensured these special folders were skipped.

3. The Date Dilemma: Today's Date Anomaly

  • The Feedback: "This isn't working right. It's using today's date instead of the creation date of each .vf or .veg file."

  • The Problem: This was a critical bug! The oldestFileDate variable, intended to store the historical date from a file, was likely not being reset correctly for each folder being processed, or the file system was reporting current dates for CreationTimeUtc.

  • The Fix & Investigation:

    1. Crucial Bug Fix: The DateTime oldestFileDate = DateTime.MaxValue; initialization needed to be inside the main for loop that iterates through each subfolder. This ensures each folder's date calculation starts fresh.

    2. Enhanced Logging: To understand what dates were being read, extensive DEBUG messages were added to ReportProgress, showing fileInfo.CreationTimeUtc, fileInfo.LastWriteTimeUtc, and how oldestFileDate was being updated at each step.

      // Example debug log
      worker.ReportProgress(i, new ProgressReportUpdate($"DEBUG: File '{fileInfo.Name}'. Creation: {fileInfo.CreationTimeUtc:O}, Modified: {fileInfo.LastWriteTimeUtc:O}."));
          

4. Refining Date Logic: Creation vs. Modification Date

  • The Request: "Let's use the date modified if the date created is newer..."

  • The Problem: Sometimes, CreationTime might be newer than LastWriteTime (e.g., if a file was copied). We wanted the truly oldest relevant timestamp for a file.

  • The Fix:

    • For each file being considered, determine its "effective date":

      DateTime effectiveFileDateForThisFile = fileInfo.CreationTimeUtc < fileInfo.LastWriteTimeUtc ? fileInfo.CreationTimeUtc : fileInfo.LastWriteTimeUtc;
          
    • This effectiveFileDateForThisFile was then used to update the overall oldestFileDate (for the fallback scenario).

    • When a primary project file (.vf/.veg) was identified (itself chosen by the oldest CreationTimeUtc among project files), its effective date (older of its own created/modified) was used for the final oldestFileDate for renaming.

      // After finding oldestProjectFileByCreation
      DateTime dateForThisSpecificProjectFile = oldestProjectFileByCreation.CreationTimeUtc < oldestProjectFileByCreation.LastWriteTimeUtc ? oldestProjectFileByCreation.CreationTimeUtc : oldestProjectFileByCreation.LastWriteTimeUtc;
      oldestFileDate = dateForThisSpecificProjectFile;
          

5. Function Start-up Robustness (User Contribution):

  • The Suggestion: "Instead of checking for one number, let's still fix ones with a number first. If it's a number at the start of the folder like 20031122 with 8 digits then we are good to leave it alone. There are a few folders I named like 500dollar-camera, 500px, and 22mm-challenging-to-use-09092018 that the program missed."

    • (Self-correction: The user also provided a preferred way to start the DoWork method with null checks for sender and e.Argument, which was good practice and readily incorporated).

  • The Problem: The initial "skip if starts with a number" logic was too broad. char.IsDigit(currentSubFolderName[0]) would skip 500dollar-camera. The intent was to skip only folders already following the YYYYMMDD pattern.

  • The Fix: Switched to a more precise regular expression.

    // Replaced the simple char.IsDigit check with:
    if (Regex.IsMatch(currentSubFolderName, @"^\d{8}"))
    {
        worker.ReportProgress(i + 1, new ProgressReportUpdate($"Skipped: '{currentSubFolderName}' (already starts with an 8-digit number, assumed yyyyMMdd format)."));
        continue;
    }
        

    The pattern ^\d{8} specifically checks for exactly 8 digits at the beginning of the folder name.


The Result

After these iterations, the application became much more robust and accurate in its renaming process. It correctly handled various edge cases, used the intended dates, and skipped folders appropriately.

There was never a situation where errors came from Gemini. More about my own tasks merging everything together in Visual Studio.


Key Takeaways and Helper Code Snippets

This project highlights several important aspects of software development:

  1. Iterative Refinement: The first version is rarely the last. Real-world testing and user feedback are crucial.

  2. Clear Requirements (Even if Evolving): While the initial prompt was good, nuances always emerge.

  3. Robust Error Handling: Thinking about null arguments, access permissions, and unexpected file system states.

  4. Background Processing for UI Apps: BackgroundWorker is invaluable for keeping the UI responsive.

  5. Precise Logic: Moving from char.IsDigit to Regex.IsMatch for more accurate pattern matching.

Here are a couple of the key helper components we used:

SanitizeFolderName Method: 

public static string SanitizeFolderName(string name)
{
    if (string.IsNullOrWhiteSpace(name))
        return string.Empty;

    string invalidCharsPattern = "[" + Regex.Escape(new string(Path.GetInvalidFileNameChars())) + "]";
    string sanitized = Regex.Replace(name, invalidCharsPattern, "_");

    sanitized = sanitized.Trim(' ', '.');
    sanitized = Regex.Replace(sanitized, @"\.+$", ""); // Remove trailing dots
    sanitized = Regex.Replace(sanitized, @"_+", "_");   // Consolidate multiple underscores
    sanitized = sanitized.Trim('_'); 

    int maxLength = 200;
    if (sanitized.Length > maxLength)
    {
        sanitized = sanitized.Substring(0, maxLength);
        sanitized = sanitized.TrimEnd('_', ' ', '.').Trim('_');
    }
    return sanitized;
}
    

ProgressReportUpdate Class:

public class ProgressReportUpdate
{
    public int? TotalFoldersToProcess { get; private set; }
    public string Message { get; private set; }
    public bool IsInitialReport { get; private set; }

    public ProgressReportUpdate(string message) // For regular updates
    { Message = message; IsInitialReport = false; TotalFoldersToProcess = null; }

    public ProgressReportUpdate(int totalFolders, string message, bool isInitial = false) // For initial/total updates
    { TotalFoldersToProcess = totalFolders; Message = message; IsInitialReport = isInitial; }
}

This journey from a simple idea to a functional tool was a great example of collaborative problem-solving with a human and AI. Hopefully, this walkthrough provides some insight into the development process and inspires you in your own coding projects!


The human's thoughts on this project...

As you might have guessed, Gemini created this article based on our chat. I revised it here and there, but it provided a pretty solid website article based on a programming conversation. 

Here is my prompt: Let's use this project and chat as a website article on my blogger based website for coding. Start with the idea, show the initial prompt. Then detail our back and forth. I can provide screenshots of visual studio and the result afterward.

Most of my issues with this project came from getting the code into Visual Studio so that all of the buttons and other functions were linked properly. That means using the interface to assign the events even though the names of the controls were already correct... you still have to go through the process of linking each function to each control. Once things are linked, deleting or replacing things can effectively crash Visual Studio's form designer so I had to mess with that as I revised the code with Gemini. Don't completely delete the function definitions once they are linked and you should be good. 

Assigning the background worker functions through Visual Studio's interface.

Of course, I could have told Gemini to create the program completely programmatically where it would generate the controls, which could have avoided most of my hassles, but then I would lose functionality as a human wanting a visual interface that the Winforms form designer provides.  

I ran a few tests on folders as I went and revised the design with Gemini as it outlined in the above text. It was nearly flawless and I was the one missing edge cases and adjusting what I needed as I went through the development process. 

Gemini covered cases I didn't even think of initially. We are coming to the point of using technology and past knowledge of humanity in ways that can really multiply that power in future work. We as humans don't have the capacity to take massive amounts of information and form it into new work like this, so it really excited me to see AI actually starting to get closer to it reaching new levels of . 

This reminds me of how close we are getting to something like Star Trek The Next Generation where Geordi programs the Holodeck by voice. I'm being a lot more specific here, but I want much more specific things and my mindset is in software development mode. 

I could see programming being a verbal or textual collaboration for software development in situations where absolute efficiency isn't needed. At some point the output would be pseudo code instead of specific existing languages like C#, Java, Python, or whatever else. 

Now I'm trying to shift my mind into somehow profiting off of this technology and my existing skills, which is probably going to be the most difficult part for me. So far I've been focused on using it for increasing my efficiency and tackling tasks I've wanted to do but didn't want to spend the time coding by hand. I'd like to eventually figure out things people would be interested in paying for. 

A tough order.. especially considering a lot of people could do this exact thing themselves so maybe I'm better off being a teacher of sorts as I learn and experiment. Maybe at some point I'll produce more videos for the programming YouTube channel

There is some sort of application development system in place for collaborative development with Gemini already.

I'm sure that my code on GitHub, and likely this website, have been used to train AI so I feel justified in using it. My main concern is if these companies start pay-walling it to a higher degree. At least for now Gemini through their web interface feels like it offers a good amount of "free" access because it did this entire thing without blocking me. 

I'd love to have the hardware and this model locally, but I'm not in a position to buy whatever level of hardware this is running on. Not to mention Gemini 2.5 Pro is not available to people, but Google does have a small GEMMA model that people can use locally. I'm going to take a guess and say that  Gemini 2.5 Pro is absolutely massive compared to GEMMA. 


Popular posts from this blog

ChatGPT is a new, and faster, way to do programming!

Currently ChatGPT is in a free “initial research preview” . One of its well known use cases at this point is generating software code. I’ve also just used it to write most of this article… Well, actually a future article about cleaning up SRT subtitle files of their metadata faster than I have been by hand with Notepad++ and its replace functionality. Update: I recorded a screencast of writing the SRT subtitle cleaner application loading and processing portion. I relied heavily on ChatGPT for code. It was a fun process! https://youtu.be/TkEW39OloUA ChatGPT, developed by OpenAI, is a powerful language model that can assist developers in a variety of tasks, including natural language processing and text generation. One such task that ChatGPT can help with is creating an SRT cleaner program. SRT, or SubRip Subtitle, files are commonly used to add subtitles to video files. However, these files can become cluttered with unnecessary information, such as timing lines or blank spaces. To clean...

Theme error in 2010s Android App after AppCompat Migration

I plan on releasing a lot of my old work as GPL open source, but most of it has aged to the point that it no longer functions, or if it does work it’s running in compatibility mode. Basically it’s no longer best practices. Not a good way to start off any new public GPL projects, in my opinion. The current project I’m working on is an Android app that calculates star trails meant to help photographers get or avoid that in their night time photos. For now I’m going to skip some of the import process because I didn’t document it exactly. It’s been mostly trial and error as I poke around Android Studio post import. The Android Studio import process… Removing Admob Google Play code before the project would run at all. After removing dependencies, it kind of worked, but when running it in the emulator it shows a pop-up message saying that the app was developed for an old version of Android. Going through the process of updating code to match current best practices… I had the IDE convert the ...

Printing to file in Linux WINE

I noticed that this post has been sitting as a draft since 2011. At this point I have no idea if it’s useful or what I was even doing, but I might as well make it public in case someone can find it helpful! So I’ve been trying to get one of those PDF print drivers working in WINE without success. I then came upon a process that might work. When printing you need to select the checkbox “Print to file” that creates a .prn file. Just Linux things... I was using a program that only has printing facilities, but I want to export around 100 pages of text and images. Once you have the .prn (postscript) file, you can do any number of things to it. In my case I want the postscript file to be converted to HTML. I am also considering PDF format because that has more conversion options to eventually get me to HTML or plain text. sudo apt-get install cups-pdf Or it looks like that package might have changed to this… sudo apt-get install printer-driver-cups-pdf Where PDFs would be generated in /home/...