RSS

Archive for May, 2014

Image Renamer

Friday, May 2nd, 2014

Tornado Leaving York

This year has been busy and after the heavy workload at the start of the year April was a time for some much needed R&R. Time to break out the camera and explore a little of Yorkshire. During the month I took about 1,300 photographs storing both the raw sensor data and a high quality JPG image. This leaves me with a whole series of files with names like DSC_0001.nef and a corresponding JPG file DSC_0001.jpg. These names are not really descriptive of the contents of the image. Renaming the files is easy but I also need to guarantee that the link between the raw file and the JPG file is retained.

This project is trivial but I have decided to present this as I could not find any software which would do what I needed. Being a Software Engineer I decided to turn to Visual Studio and solve the problem myself.

The project files and binary are available for download (see the conclusion at the end of this article).

Image File Renamer

So the project brief is:

  • Rename all of the files in a directory with a specific root file name
  • Add a sequence number to the file name
  • If there is a raw file and a JPG file with the same source root file name then make sure that the two files have the same destination root file name

Should be simple enough to achieve with a small command line program.

Parsing the Command Line

First this we need to do is to find out what the user wants the application to do. In order to achieve this we need to parse the command line.

Parsing the command line is a simple task and I have written classes to do this many times in the past but recently I have come across the CommandLineParser library on CodePlex. This library is powerful and performs many of the common command line parsing tasks for you. All you need to do is provide a class with properties decorated by appropriate attributes (more on that later).

Installation is easy. Start Visual Studio and create a new C# Windows console line project. Next Open the Package Manager console (Tools -> NuGet Package Manager -> Package Manager Console) and enter the command Install-Package CommandLineParser. A few seconds later the package will have bee downloaded and added to the project. All we need to do now is use it.

The two obvious options which are needed for this application to work are the new name to be used as the root of the file name and the directory which contains the source files. These two options are represented by two properties in the options class. The code for this is as follows:

using CommandLine;
using CommandLine.Text;

namespace PhotoRenamer
{
    /// <summary>
    /// Class to parse and hold the command line options.
    /// </summary>
    class Options
    {
        /// <summary>
        /// Location of the source files.
        /// </summary>
        [Option('d', "directory", DefaultValue = ".", HelpText = "Directory containing files to be renamed (default = .)")]
        public string SourceDirectory { get; set; }

        /// <summary>
        /// New source name for the files.
        /// </summary>
        [Option('n', "name", Required = true, HelpText = "Root of the new file name.")]
        public string Name { get; set; }

        [ParserState]
        public IParserState LastParserState { get; set; }

        /// <summary>
        /// Build up the usage text.
        /// </summary>
        /// <returns>Options string</returns>
        [HelpOption]
        public string GetUsage()
        {
            return HelpText.AutoBuild(this, (HelpText current) => HelpText.DefaultParsingErrorsHandler(this, current));
        }
    }
}

As you can see, the properties within the class which represent the two command line options are decorated by an Option attribute. The attribute informs the CommandLineParser how it should deal with the property and the name and type of option it should expect.

The class is set up by creating a new instance of the Options class and executing the following code:

var options = new Options();
try
{
    if (CommandLine.Parser.Default.ParseArguments(args, options))
    {
        // Your application logic goes here.
    }
}
catch (Exception ex)
{
    Console.WriteLine(options.GetUsage());
}

This will parse the command line and set the properties accordingly. An exception will be thrown if there is a problem parsing the command line or if any of the mandatory options are not present.

The main application logic will create a list of all of the JPG files in a directory, rename the file and then look for an associated raw image file. If the raw image file exists then it is also renamed using the same root file name and sequence number. The source code is present here for completeness:

using System;
using System.IO;

namespace PhotoRenamer
{
    class Program
    {
        static void Main(string[] args)
        {
            var options = new Options();
            try
            {
                if (CommandLine.Parser.Default.ParseArguments(args, options))
                {
                    if (options.Verbose)
                    {
                        Console.WriteLine("Directory: {0}", options.SourceDirectory);
                        Console.WriteLine("Root name: {0}", options.Name);
                    }

                    if (Directory.Exists(options.SourceDirectory))
                    {
                        try
                        {
                            string[] files = Directory.GetFiles(options.SourceDirectory);
                            int sequenceNumber = 1;
                            int filesProcessed = 0;
                            string jpgExtension = string.Format(".{0}", options.JPGExtension.ToLower());
                            string rawExtension = string.Format(".{0}", options.RawExtension.ToLower());
                            foreach (string file in files)
                            {
                                if (Path.GetExtension(file).ToLower().Equals(jpgExtension))
                                {
                                    string newRootFileName = string.Format("{0}\\{1} {2:0000}", options.SourceDirectory, options.Name, sequenceNumber++);
                                    string jpgFileName = string.Format("{0}{1}", newRootFileName, jpgExtension);

                                    if (options.Verbose)
                                    {
                                        Console.WriteLine("Renaming file {0} to {1}", file, jpgFileName);
                                    }
                                    File.Move(file, jpgFileName);
                                    filesProcessed++;
                                    string sourceNEFFileName = string.Format("{0}\\{1}{2}", Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file), rawExtension);
                                    if (File.Exists(sourceNEFFileName))
                                    {
                                        string newNEFFileName = string.Format("{0}{1}", newRootFileName, rawExtension);
                                        if (options.Verbose)
                                        {
                                            Console.WriteLine("Renaming file {0} to {1}", sourceNEFFileName, newNEFFileName);
                                        }
                                        File.Move(sourceNEFFileName, newNEFFileName);
                                        filesProcessed++;
                                    }
                                }
                            }
                            Console.WriteLine("{0} Files processed.", filesProcessed);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e.Message);
                        }
                    }
                    else
                    {
                        Console.WriteLine("Directory '{0}' does not exist.", options.SourceDirectory);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(options.GetUsage());
            }
        }
    }
}

Three additional properties have been added to the Options class, two to support different raw and JPG file name extensions and a final option to put the application into verbose mode.

Conclusion

This application provides a simple methods of renaming image files keeping the link between the original raw and JPG files. The source code and compiled binary can be downloaded and used on a royalty free basis.

The source code and application are provided as is and no warranty is provided.