Hi,
I am running 3 threads that each start an ffmpeg process to extract 3 snapshots form a video file.
I need to be able to call this method multiple times, with different video files.
It writes the three snapshots to the system.
However If I try to delete these files using File.Delete, it throws an exception.

Here is the code:

namespace runFFmpeg
{
    public struct ThreadData
    {
        public string time;
        public string inputFilename;
        public string outputFilename;
    }
    /// <summary>
    /// Description of MyClass.
    /// </summary>
    public class FfmpegBitmapCapture
    {
        private string output1;
        private string output2;
        private string pathToFfmpg = "..\\..\\lib\\ffmpeg.exe";
        private SnapshotItem[] snapshotItems = new SnapshotItem[3];
        
        private TimeSpan executionTime;
        
        public SnapshotItem[] SnapshotItems
        {
            get { return snapshotItems; }
        }
        
        public TimeSpan ExecutionTime
        {
            get { return this.executionTime;}
        }
        
        
        public FfmpegBitmapCapture(string filename, int percentFirst, int percentSecond, int percentThird)
        {
            GetSnapshotTimes(percentFirst,percentSecond,percentThird,filename);
            SmallLogger.SmallLogger.LogMessageToFile("ffmpeg: filename " + filename);
            
            ThreadData tdStart = new ThreadData();
            tdStart.inputFilename = filename;
            tdStart.outputFilename = this.GetTempPath() + "vidLib_snapshot[1].jpg";

            tdStart.time = snapshotItems[0].Time.ToString();
            Thread startThread = new Thread(new ParameterizedThreadStart(ExtractSnapshot));
            
            ThreadData tdMiddle = new ThreadData();
            tdMiddle.inputFilename = filename;
            tdMiddle.outputFilename = this.GetTempPath() + "vidLib_snapshot[2].jpg";

            tdMiddle.time = snapshotItems[1].Time.ToString();
            Thread middleThread = new Thread(new ParameterizedThreadStart(ExtractSnapshot));

            ThreadData tdEnd = new ThreadData();
            tdEnd.inputFilename = filename;
            tdEnd.outputFilename = this.GetTempPath() + "vidLib_snapshot[3].jpg";
            tdEnd.time = snapshotItems[2].Time.ToString();
            Thread endThread = new Thread(new ParameterizedThreadStart(ExtractSnapshot));
            
            DateTime startTime = DateTime.Now;

            startThread.Start(tdStart);
            middleThread.Start(tdMiddle);
            endThread.Start(tdEnd);
            
            startThread.Join();
            middleThread.Join();
            endThread.Join();

            this.snapshotItems[0].Thumbnail = new Bitmap(tdStart.outputFilename);
            this.snapshotItems[1].Thumbnail = new Bitmap(tdMiddle.outputFilename);
            this.snapshotItems[2].Thumbnail = new Bitmap(tdEnd.outputFilename);

            try
            {
                File.Delete(tdStart.outputFilename);
                File.Delete(tdMiddle.outputFilename);
                File.Delete(tdEnd.outputFilename);
            }
            catch(Exception ex)
            {
                System.Console.Out.WriteLine(ex);
            }
            DateTime stopTime = DateTime.Now;
            
            this.executionTime = stopTime - startTime;
        }

        void ExtractSnapshot(object threadData)
        {
            ThreadData td = (ThreadData)threadData;
            string outFileName = td.outputFilename;

            //from 
            ProcessStartInfo start = new ProcessStartInfo();

            start.FileName = this.pathToFfmpg;
            start.Arguments = "-i \"" + td.inputFilename + "\"  -r 1 -f mjpeg -vframes 1 -ss " + td.time + " -y " + outFileName;
            //start.Arguments = "-h";
            start.UseShellExecute = false;
            start.RedirectStandardOutput = true;
            start.RedirectStandardError = true;
            start.CreateNoWindow = true;
            
            //using(Process process = Process.Start(start))
            //{
            Process process = new Process();
            
            process.StartInfo = start;
            process.Start();
            
            using(StreamReader reader = process.StandardOutput)
            {
                output1 = reader.ReadToEnd();
                
            }
            using (StreamReader reader2 = process.StandardError)
            {
                output2 = reader2.ReadToEnd();
            }
            process.WaitForExit();
            process.Close();

            
            //}
        }

        private string GetTempPath()
        {
            string path = System.Environment.GetEnvironmentVariable("TEMP");
            if (!path.EndsWith("\\")) path += "\\";
            return path;
        }
        
        private void GetSnapshotTimes(int percentFirst,
                                      int percentMiddle,
                                      int percentLast,
                                      string filename)
        {
            string outputList;
            TimeSpan middle;
            TimeSpan end;
            TimeSpan begin;
            
            ProcessStartInfo start = new ProcessStartInfo();
            //start.FileName = "C:\\Users\\daddy\\Downloads\\ffmpeg-latest\\bin\\ffmpeg.exe";
            start.FileName = this.pathToFfmpg;
            start.Arguments = "-i \"" + filename + "\"";
            start.UseShellExecute = false;
            start.RedirectStandardOutput = true;
            start.RedirectStandardError = true;
            start.CreateNoWindow = true;
            using(Process process = Process.Start(start))
            {
                using (StreamReader reader2 = process.StandardError)
                {
                    outputList = reader2.ReadToEnd();
                }
                process.WaitForExit();
            }
            Regex regex = new Regex("Duration: +(?<dur>\\d\\d:\\d\\d:\\d\\d.\\d+)");
            Match m = regex.Match(outputList);
            
            if(m.Success)
            {
                TimeSpan OneSecond = TimeSpan.Parse("00:00:01.00");
                long OneseconTicks = OneSecond.Ticks;
                string duration = m.Groups["dur"].Value;
                TimeSpan ts = TimeSpan.Parse(duration);
                long durTicks = ts.Ticks;
                if(durTicks <= OneseconTicks){
                    begin = TimeSpan.FromTicks((long)((double)durTicks * 0.05));
                    end = begin;
                    middle = begin;
                }
                else
                {
                    middle = TimeSpan.FromTicks((long)((double)durTicks * ((double)percentMiddle)/100.0));
                    end = TimeSpan.FromTicks((long)((double)durTicks * ((double)percentLast)/100.0));
                    begin = TimeSpan.FromTicks((long)((double)durTicks * ((double)percentFirst)/100.0));
                }
                this.snapshotItems[0].Time = begin;
                this.snapshotItems[1].Time = middle;
                this.snapshotItems[2].Time = end;
            }
        }
    }
}

Regards,
John

Dani AI

Generated

Good catch, — the root cause is that System.Drawing (GDI+) keeps the file handle open when an Image/Bitmap is created directly from a filename, so File.Delete fails until the Image is disposed. The using(...) solution shown in the thread will dispose the temporary bitmap but also leaves the assigned Thumbnail pointing to a disposed object if the disposed instance is assigned directly. The safe pattern is to make an independent in‑memory copy and dispose any temporary image/stream before attempting to delete the file.

Two practical options that avoid file locks:

  • Read the produced JPEG into memory and create a cloned Bitmap that does not reference the on‑disk file.
  • Ask ffmpeg to emit the frame to stdout (image2pipe) and read the bytes directly into a MemoryStream, then create a Bitmap from that stream.

Example (read-into-memory + copy):

byte[] data = File.ReadAllBytes(pathToJpeg);
using (var ms = new MemoryStream(data))
using (var src = Image.FromStream(ms))
{
    snapshotItem.Thumbnail = new Bitmap(src); // creates an independent copy
}
// at this point the file can be deleted safely

Additional notes: Image/Bitmap objects are not thread‑safe — construct and access them with proper synchronization or on a single thread. Always Dispose old thumbnails (implement IDisposable on SnapshotItem or provide explicit cleanup) to free handles and memory. If deletion still fails after these changes, confirm the ffmpeg process has exited and been disposed, and check for external locks (antivirus, indexing). Using an image‑pipe from ffmpeg avoids temp files entirely and scales better when generating many thumbnails.

Hi,
I am running 3 threads that each start an ffmpeg process to extract 3 snapshots form a video file.
I need to be able to call this method multiple times, with different video files.
It writes the three snapshots to the system.
However If I try to delete these files using File.Delete, it throws an exception.

Here is the code:

namespace runFFmpeg
{
    public struct ThreadData
    {
        public string time;
        public string inputFilename;
        public string outputFilename;
    }
    /// <summary>
    /// Description of MyClass.
    /// </summary>
    public class FfmpegBitmapCapture
    {
        private string output1;
        private string output2;
        private string pathToFfmpg = "..\\..\\lib\\ffmpeg.exe";
        private SnapshotItem[] snapshotItems = new SnapshotItem[3];
        
        private TimeSpan executionTime;
        
        public SnapshotItem[] SnapshotItems
        {
            get { return snapshotItems; }
        }
        
        public TimeSpan ExecutionTime
        {
            get { return this.executionTime;}
        }
        
        
        public FfmpegBitmapCapture(string filename, int percentFirst, int percentSecond, int percentThird)
        {
            GetSnapshotTimes(percentFirst,percentSecond,percentThird,filename);
            SmallLogger.SmallLogger.LogMessageToFile("ffmpeg: filename " + filename);
            
            ThreadData tdStart = new ThreadData();
            tdStart.inputFilename = filename;
            tdStart.outputFilename = this.GetTempPath() + "vidLib_snapshot[1].jpg";

            tdStart.time = snapshotItems[0].Time.ToString();
            Thread startThread = new Thread(new ParameterizedThreadStart(ExtractSnapshot));
            
            ThreadData tdMiddle = new ThreadData();
            tdMiddle.inputFilename = filename;
            tdMiddle.outputFilename = this.GetTempPath() + "vidLib_snapshot[2].jpg";

            tdMiddle.time = snapshotItems[1].Time.ToString();
            Thread middleThread = new Thread(new ParameterizedThreadStart(ExtractSnapshot));

            ThreadData tdEnd = new ThreadData();
            tdEnd.inputFilename = filename;
            tdEnd.outputFilename = this.GetTempPath() + "vidLib_snapshot[3].jpg";
            tdEnd.time = snapshotItems[2].Time.ToString();
            Thread endThread = new Thread(new ParameterizedThreadStart(ExtractSnapshot));
            
            DateTime startTime = DateTime.Now;

            startThread.Start(tdStart);
            middleThread.Start(tdMiddle);
            endThread.Start(tdEnd);
            
            startThread.Join();
            middleThread.Join();
            endThread.Join();

            this.snapshotItems[0].Thumbnail = new Bitmap(tdStart.outputFilename);
            this.snapshotItems[1].Thumbnail = new Bitmap(tdMiddle.outputFilename);
            this.snapshotItems[2].Thumbnail = new Bitmap(tdEnd.outputFilename);

            try
            {
                File.Delete(tdStart.outputFilename);
                File.Delete(tdMiddle.outputFilename);
                File.Delete(tdEnd.outputFilename);
            }
            catch(Exception ex)
            {
                System.Console.Out.WriteLine(ex);
            }
            DateTime stopTime = DateTime.Now;
            
            this.executionTime = stopTime - startTime;
        }

        void ExtractSnapshot(object threadData)
        {
            ThreadData td = (ThreadData)threadData;
            string outFileName = td.outputFilename;

            //from 
            ProcessStartInfo start = new ProcessStartInfo();

            start.FileName = this.pathToFfmpg;
            start.Arguments = "-i \"" + td.inputFilename + "\"  -r 1 -f mjpeg -vframes 1 -ss " + td.time + " -y " + outFileName;
            //start.Arguments = "-h";
            start.UseShellExecute = false;
            start.RedirectStandardOutput = true;
            start.RedirectStandardError = true;
            start.CreateNoWindow = true;
            
            //using(Process process = Process.Start(start))
            //{
            Process process = new Process();
            
            process.StartInfo = start;
            process.Start();
            
            using(StreamReader reader = process.StandardOutput)
            {
                output1 = reader.ReadToEnd();
                
            }
            using (StreamReader reader2 = process.StandardError)
            {
                output2 = reader2.ReadToEnd();
            }
            process.WaitForExit();
            process.Close();

            
            //}
        }

        private string GetTempPath()
        {
            string path = System.Environment.GetEnvironmentVariable("TEMP");
            if (!path.EndsWith("\\")) path += "\\";
            return path;
        }
        
        private void GetSnapshotTimes(int percentFirst,
                                      int percentMiddle,
                                      int percentLast,
                                      string filename)
        {
            string outputList;
            TimeSpan middle;
            TimeSpan end;
            TimeSpan begin;
            
            ProcessStartInfo start = new ProcessStartInfo();
            //start.FileName = "C:\\Users\\daddy\\Downloads\\ffmpeg-latest\\bin\\ffmpeg.exe";
            start.FileName = this.pathToFfmpg;
            start.Arguments = "-i \"" + filename + "\"";
            start.UseShellExecute = false;
            start.RedirectStandardOutput = true;
            start.RedirectStandardError = true;
            start.CreateNoWindow = true;
            using(Process process = Process.Start(start))
            {
                using (StreamReader reader2 = process.StandardError)
                {
                    outputList = reader2.ReadToEnd();
                }
                process.WaitForExit();
            }
            Regex regex = new Regex("Duration: +(?<dur>\\d\\d:\\d\\d:\\d\\d.\\d+)");
            Match m = regex.Match(outputList);
            
            if(m.Success)
            {
                TimeSpan OneSecond = TimeSpan.Parse("00:00:01.00");
                long OneseconTicks = OneSecond.Ticks;
                string duration = m.Groups["dur"].Value;
                TimeSpan ts = TimeSpan.Parse(duration);
                long durTicks = ts.Ticks;
                if(durTicks <= OneseconTicks){
                    begin = TimeSpan.FromTicks((long)((double)durTicks * 0.05));
                    end = begin;
                    middle = begin;
                }
                else
                {
                    middle = TimeSpan.FromTicks((long)((double)durTicks * ((double)percentMiddle)/100.0));
                    end = TimeSpan.FromTicks((long)((double)durTicks * ((double)percentLast)/100.0));
                    begin = TimeSpan.FromTicks((long)((double)durTicks * ((double)percentFirst)/100.0));
                }
                this.snapshotItems[0].Time = begin;
                this.snapshotItems[1].Time = middle;
                this.snapshotItems[2].Time = end;
            }
        }
    }
}

Regards,
John

OK, I fixed the problem myself.
I found that the lines, such as:
this.snapshotItems[0].Thumbnail = new Bitmap(tdStart.outputFilename);
lock the bitmap files that have been written out.

I changed these lines to:

using(Bitmap bm = new Bitmap(dStart.outputFilename))
{
    this.snapshotItems[0].Thumbnail = bm;
}

This closes and disposes of the Bitmap, and I can then delete the file.

I am sure there are other solutions, any help appreciated.

John

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.