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.
The Initial Spark: The Problem and The Request
Identify folders that didn't already follow my new naming convention (YYYYMMDD-ProjectName).For those folders, find the oldest Magix Vegas Pro project file (.vf or .veg) inside. Use that file's creation date and its filename (without extension) to rename the parent folder. If no Vegas files were present, use the oldest file of any type to get the date.Provide a simple UI with a folder selector, start/cancel buttons, a status display, and a progress bar.
"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
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.
Get all subdirectories. 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
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.
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: Crucial Bug Fix: The DateTime oldestFileDate = DateTime.MaxValue; initialization needed to beinside the main for loop that iterates through each subfolder. This ensures each folder's date calculation starts fresh.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}."));
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 thetruly 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;
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
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
Iterative Refinement: The first version is rarely the last. Real-world testing and user feedback are crucial.Clear Requirements (Even if Evolving): While the initial prompt was good, nuances always emerge.Robust Error Handling: Thinking about null arguments, access permissions, and unexpected file system states.Background Processing for UI Apps: BackgroundWorker is invaluable for keeping the UI responsive.Precise Logic: Moving from char.IsDigit to Regex.IsMatch for more accurate pattern matching.
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;
}
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; }
}
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.