Home > Uncategorized > A better title setter

A better title setter

In a previous post I copied my code for a program that added the title to picture files. After using the program I discovered two problems:

1) If there were 700+ files the program would crash. The problem was that I was spawning off so many threads that it was causing the heap to overflow.

2) If the picture didn’t have enough metadata already it just wouldn’t save the title. This is the main reason I wanted to investigate what was going on. It turns out it’s the whole TrySave() stuff. If a picture doesn’t have enough buffer it won’t save the updates to the metadata. Which is dumb. Why wasn’t Save() just programmed to extend the metadata? Makes no sense. Either way, here’s the new, slower, but working code:

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, filesToMove;

        [STAThread]
        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;
        }

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

        /// <summary>
        /// Go through all of the files found in the directory that match the pattern and set
        /// the picture title
        /// </summary>
        public void setTitles()
        {
            outStandingTasks = 0;
            filesDetermined = 0;
            filesToMove = 0;

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

            foreach (String fileName in fileNames)
            {
                FileInfo file = new FileInfo(fileName);
                try
                {
                    Interlocked.Increment(ref outStandingTasks);
                    setTitle(file);
                }
                catch (NonImageMetadataWritableFile ni)
                {
                    writeOutput(ni.ToString());
                }
                catch (Exception ex)
                {
                    writeOutput("Exception caught for file {0}{1}{2}", file.Name, Environment.NewLine, ex);
                }
            }

            fileNames = null;
            while (0 != filesToMove)
            {
                Thread.Sleep(250);
            }
        }

        /// <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 fs = new FileStream(file.FullName, FileMode.Open, FileAccess.ReadWrite))
            {
                BitmapDecoder decoder = BitmapDecoder.Create(fs,
                    BitmapCreateOptions.None,
                    BitmapCacheOption.None);

                BitmapEncoder encoder;
                if (decoder.CodecInfo.FileExtensions.Contains("jpg"))
                {
                    encoder = new JpegBitmapEncoder();
                }
                else if (decoder.CodecInfo.FileExtensions.Contains("png"))
                {
                    encoder = new PngBitmapEncoder();
                }
                else if (decoder.CodecInfo.FileExtensions.Contains("tif"))
                {
                    encoder = new TiffBitmapEncoder();
                }
                else if (decoder.CodecInfo.FileExtensions.Contains("gif"))
                {
                    encoder = new GifBitmapEncoder();
                }
                else if (decoder.CodecInfo.FileExtensions.Contains("wdp"))
                {
                    encoder = new WmpBitmapEncoder();
                }
                else
                {
                    throw new InvalidDataException(String.Format("The file {0} has unrecognized file extentions of {1}.",
                        file, decoder.CodecInfo.FileExtensions));
                }

                InPlaceBitmapMetadataWriter bitmeta = decoder.Frames[0].CreateInPlaceBitmapMetadataWriter();
                String originalTitle = bitmeta.Title;
                if (String.IsNullOrEmpty(originalTitle))
                {
                    String newTitle = Path.GetFileNameWithoutExtension(file.Name);
                    bitmeta.Title = newTitle;
                    if (bitmeta.TrySave())
                    {
                        writeOutput("%{0,-3} {1}", (100 * Interlocked.Increment(ref filesDetermined)) / totalFiles, file.Name);
                    }
                    else
                    {
                        // Need to add more padding
                        // This requires writing out a whole new file
                        BitmapMetadata newMeta = decoder.Frames[0].Metadata.Clone() as BitmapMetadata;
                        uint paddingAmount = 1024;

                        newMeta.SetQuery("/app1/ifd/PaddingSchema:Padding", paddingAmount);
                        newMeta.SetQuery("/app1/ifd/exif/PaddingSchema:Padding", paddingAmount);
                        newMeta.SetQuery("/xmp/PaddingSchema:Padding", paddingAmount);
                        newMeta.Title = newTitle;

                        encoder.Frames.Add(BitmapFrame.Create(
                            decoder.Frames[0],
                            decoder.Frames[0].Thumbnail,
                            newMeta,
                            decoder.Frames[0].ColorContexts));
                        for (int frameNum = 1; frameNum < decoder.Frames.Count; frameNum++)
                        {
                            encoder.Frames.Add(decoder.Frames[frameNum]);
                        }

                        String tempName = Path.Combine(file.DirectoryName, Path.GetFileNameWithoutExtension(file.Name) + "_title" + Path.GetExtension(file.Name));
                        using (FileStream fsWithTitle = new FileStream(tempName, FileMode.CreateNew, FileAccess.ReadWrite))
                        {
                            encoder.Save(fsWithTitle);
                        }
                        fs.Close();
                        moveAsync ma = new moveAsync(moveAndOverWriteFile);
                        ma.BeginInvoke(tempName, file.FullName, endMove, ma);
                        Interlocked.Increment(ref filesToMove);
                        writeOutput("%{0,-3} {1}", (100 * Interlocked.Increment(ref filesDetermined)) / totalFiles, file.Name);
                    }
                }
                else
                {
                    writeOutput("%{0,-3} {1} is already set to \"{2}\"", (100 * Interlocked.Increment(ref filesDetermined)) / totalFiles, file.Name, originalTitle);
                }
            }
        }

        private delegate void moveAsync(String sourceFileName, String destFileName);

        /// <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);
            }
        }

        private void moveAndOverWriteFile(String sourceFileName, String destFileName)
        {
            Thread.CurrentThread.IsBackground = false;
            if (File.Exists(destFileName))
            {
                File.Delete(destFileName);
            }
            File.Move(sourceFileName, destFileName);
        }

        private void endMove(IAsyncResult result)
        {
            moveAsync ma = (moveAsync)result.AsyncState;
            try
            {
                ma.EndInvoke(result);
            }
            catch (Exception ex)
            {
                writeOutput("{0}", ex);
            }
            Interlocked.Decrement(ref filesToMove);
        }
    }

    public class NonImageMetadataWritableFile : FileFormatException
    {
        public NonImageMetadataWritableFile(FileInfo badFile) :
            base(String.Format("The file {0} does not have a metadata writable codec extention of jpg, png, tif, gif or wdp", badFile.Name))
        {

        }
    }
}
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: