Home > Uncategorized > Programmatically setting the metadata title of an image file

Programmatically setting the metadata title of an image file

So different applications I use (think Windows Live Skydrive) don’t make the name of a file predominant, but they do have features around the title of the picture file. I find this odd because I put effort into naming the file, but not into the title. Why would a picture have a different title from file name?! Okay, maybe if you had the same picture with different resolutions that could happen, but that’s the only thing I can think of. Either way I want a programmatic way to set the title to the file name. I thought a PowerShell script would be able to do it, but Windows seem to have gone out of it’s way to make modifying picture metadata difficult. So I spent way more time on this than I thought I would but I’m proud of my results. Here’s the C# code should anyone want the same.

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Windows.Media.Imaging;

namespace TitleSetter
{
    /// <summary>
    /// Class for setting the title of an image if one does not already exist
    /// </summary>
    /// <author>jader3rd</author>
    public class TitleSetter
    {
        private readonly String dir, pat;
        private int outStandingTasks, filesDetermined, totalFiles;
        ManualResetEvent tasksComplete;

        static int Main(string[] args)
        {
            int returnee = 0;
            try
            {
                if (2 == args.Length)
                {
                    TitleSetter ts = new TitleSetter(args[0], args[1]);
                    ts.Output = Console.Out;
                    ts.setTitles();
                }
                else
                {
                    Console.WriteLine("Usage:{0} <Directory Path> <File Pattern>", Process.GetCurrentProcess().ProcessName);
                }
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex.ToString());
                returnee = ex.GetHashCode();
            }

            return returnee;
        }

        /// <summary>
        /// Initialize the Title Setter object
        /// </summary>
        /// <param name="directory">The directory to scan for pictures</param>
        /// <param name="pattern">The pattern of the file name to look for pictures for</param>
        public TitleSetter(String directory, String pattern)
        {
            dir = directory;
            pat = pattern;
            tasksComplete = new ManualResetEvent(false);
        }

        /// <summary>
        /// The stream to send output to
        /// </summary>
        public TextWriter Output { get; set; }

        /// <summary>
        /// This process was supposed to be way more asynchronous file I/O friendly, but I couldn't find a way
        /// to get the BitmapFrame class to go to a byte array. As a result I couldn't do async I/O. So this 
        /// delegate is needed. Of course this now means that async calls are doing synchronous I/O, which is 
        /// a sin, but I thought it would be more efficient none the less.
        /// </summary>
        /// <param name="file"></param>
        delegate void FileAction(FileInfo file);

        /// <summary>
        /// Go through all of the files found in the directory that match the pattern and set
        /// the picture title
        /// </summary>
        public void setTitles()
        {
            tasksComplete.Reset();
            outStandingTasks = 0;
            filesDetermined = 0;
            FileAction fa = new FileAction(setTitle);

            String[] fileNames = Directory.GetFiles(dir, pat);
            totalFiles = fileNames.Length;

            if (700 < totalFiles)
            {
                writeOutput("Warning: There may be performance issues with more than 700 files");
            }

            foreach (String fileName in fileNames)
            {
                FileInfo file = new FileInfo(fileName);
                fa.BeginInvoke(file, endSetTitle, file);
                Interlocked.Increment(ref outStandingTasks);
            }

            fileNames = null;
            if (0 == outStandingTasks)
            {
                tasksComplete.Set();
            }
            
            tasksComplete.WaitOne();
        }

        /// <summary>
        /// Set the title for an individual file
        /// </summary>
        /// <param name="file">The file to set the title for</param>
        private void setTitle(FileInfo file)
        {
            if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
            {
                Thread.CurrentThread.Name = file.Name;
            }
            using (FileStream stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None))
            {
                BitmapFrame frame = BitmapFrame.Create(stream);
                InPlaceBitmapMetadataWriter bitmeta = frame.CreateInPlaceBitmapMetadataWriter();
                String originalTitle = bitmeta.Title;
                if (String.IsNullOrEmpty(originalTitle))
                {
                    if (bitmeta.TrySave())
                    {
                        writeOutput("%{0,-3} {1}", (100 * Interlocked.Increment(ref filesDetermined)) / totalFiles, file.Name);
                        bitmeta.Title = Path.GetFileNameWithoutExtension(file.Name);
                        // I don't know why this TrySave is needed, but it won't save without it.
                        bitmeta.TrySave();
                    }
                    else
                    {
                        Interlocked.Increment(ref filesDetermined);
                        throw new IOException("Unable to change title for " + file.Name);
                    }
                }
                else
                {
                    writeOutput("%{0,-3} {1} is already set to \"{2}\"", (100 * Interlocked.Increment(ref filesDetermined)) / totalFiles, file.Name, originalTitle);
                }
                stream.Close();
            }
        }

        /// <summary>
        /// Write output to the given output stream
        /// </summary>
        /// <param name="format">The message</param>
        /// <param name="arguments">message arguments</param>
        private void writeOutput(String format, params Object[] arguments)
        {
            if (null != Output)
            {
                Output.WriteLine(format, arguments);
            }
        }

        /// <summary>
        /// Finishes the action of setting the title
        /// </summary>
        /// <param name="result">The result from the setTitle call</param>
        private void endSetTitle(IAsyncResult result)
        {
            FileInfo file = result.AsyncState as FileInfo;
            try
            {
                FileAction fa = (FileAction)((AsyncResult)result).AsyncDelegate;
                fa.EndInvoke(result);

            }
            catch (Exception ex)
            {
                writeOutput("Exception caught for file {0}{1}{2}", file.Name, Environment.NewLine, ex);
            }

            Interlocked.Decrement(ref outStandingTasks);
            if (0 == outStandingTasks)
            {
                tasksComplete.Set();
            }
        }
    }
}
Categories: Uncategorized
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: