AVIファイルからBMPを作成する

id:Seasonsさんとこより。
http://d.hatena.ne.jp/Seasons/20071223/1198420293

今度は、Excel上で動画ですか! どーやっているんだろう・・・

私が実現するとしたら、

  1. AVIファイルからBMPを作成する
  2. BMP単位にExcelシートを作成する
  3. タイミングを取ってシート切り替え

って感じかしら。

じゃ、やってみようっと。
先ずは、AVIからBMPの切り出し。Video for Windows APIを使えば切り出せますが、API直呼びはやめて、C#でラッパークラスを作成します。C++/CLIを使わずにP/Invokeで押し通すのは私の趣味(^^;。

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.IO;

namespace AviFileUtil
{
    static class BitmapUtil
    {
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        struct BITMAPFILEHEADER
        {
            public ushort bfType;
            public uint bfSize;
            public ushort bfReserved1;
            public ushort bfReserved2;
            public uint bfOffBits;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        struct BITMAPINFOHEADER
        {
            public uint biSize;
            public int biWidth;
            public int biHeight;
            public ushort biPlanes;
            public ushort biBitCount;
            public uint biCompression;
            public uint biSizeImage;
            public int biXPelsPerMeter;
            public int biYPelsPerMeter;
            public uint biClrUsed;
            public uint biClrImportant;
            public const int BI_RGB = 0;
        }

        const uint DIB_RGB_COLORS = 0; /* color table in RGBs */
        const uint DIB_PAL_COLORS = 1; /* color table in palette indices */

        public static Bitmap ToBitmap(IntPtr pBITMAPINFOHEADER)
        {
            unsafe
            {
                BITMAPINFOHEADER* pbmi = (BITMAPINFOHEADER*)pBITMAPINFOHEADER;
                BITMAPFILEHEADER pbmfi;
                pbmfi.bfType = (int)'M' << 8 | (int)'B';
                pbmfi.bfOffBits = (uint)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER));
                pbmfi.bfSize = pbmfi.bfOffBits + pbmi->biSizeImage;

                MemoryStream stream = new MemoryStream();
                BinaryWriter bw = new BinaryWriter(stream);
                byte[] data = new byte[sizeof(BITMAPFILEHEADER)];
                Marshal.Copy((IntPtr)(&pbmfi), data, 0, data.Length);
                bw.Write(data);

                data = new byte[sizeof(BITMAPINFOHEADER)];
                Marshal.Copy(pBITMAPINFOHEADER, data, 0, data.Length);
                bw.Write(data);

                data = new byte[pbmi->biSizeImage];
                ++pbmi;
                Marshal.Copy((IntPtr)pbmi, data, 0, data.Length);
                bw.Write(data);
                bw.Flush();
                bw.BaseStream.Position = 0;

                return new Bitmap(bw.BaseStream);
            }
        }
    }

    /// <summary>
    /// Video for Windows APIを手軽に使うためのラッパークラス。
    /// </summary>
    public class AviFile : IDisposable
    {
        const string AVIFILE32 = "AVIFIL32";
        const int AVIGETFRAMEF_BESTDISPLAYFMT = 1;

        internal enum OpenFileFlags : uint
        {
            OF_READ = 0x00000000,
            OF_WRITE = 0x00000001,
            OF_READWRITE = 0x00000002,
            OF_SHARE_COMPAT = 0x00000000,
            OF_SHARE_EXCLUSIVE = 0x00000010,
            OF_SHARE_DENY_WRITE = 0x00000020,
            OF_SHARE_DENY_READ = 0x00000030,
            OF_SHARE_DENY_NONE = 0x00000040,
            OF_PARSE = 0x00000100,
            OF_DELETE = 0x00000200,
            OF_VERIFY = 0x00000400,
            OF_CANCEL = 0x00000800,
            OF_CREATE = 0x00001000,
            OF_PROMPT = 0x00002000,
            OF_EXIST = 0x00004000,
            OF_REOPEN = 0x00008000,
        }

        #region DllImport

        [DllImport(AVIFILE32)]
        extern internal static void AVIFileInit();

        [DllImport(AVIFILE32)]
        extern internal static void AVIFileExit();

        [DllImport(AVIFILE32)]
        extern internal static uint AVIFileOpen(out IntPtr ppfile, string szFile, OpenFileFlags mode, IntPtr pclsidHandler);

        [DllImport(AVIFILE32)]
        extern internal static int AVIFileRelease(IntPtr pfile);

        [DllImport(AVIFILE32)]
        extern internal static uint AVIFileGetStream(IntPtr pfile, out IntPtr ppavi, uint fccType, int lParam);

        [DllImport(AVIFILE32)]
        extern internal static int AVIStreamRelease(IntPtr pavi);

        [DllImport(AVIFILE32)]
        extern internal static IntPtr AVIStreamGetFrameOpen(IntPtr pavi, int lpbiWanted);

        [DllImport(AVIFILE32)]
        extern internal static IntPtr AVIStreamGetFrame(IntPtr pgf, int lPos);

        [DllImport(AVIFILE32)]
        extern internal static int AVIStreamLength(IntPtr pavi);

        [DllImport(AVIFILE32)]
        extern internal static uint AVIStreamGetFrameClose(IntPtr pget);

        #endregion

        static uint mmioFOURCC(char c0, char c1, char c2, char c3)
        {
            return (uint)c3 << 24 | (uint)c2 << 16 | (uint)c1 << 8 | (uint)c0;
        }

        static readonly uint streamtypeVIDEO = mmioFOURCC('v', 'i', 'd', 's');
        static readonly uint streamtypeAUDIO = mmioFOURCC('a', 'u', 'd', 's');
        static readonly uint streamtypeMIDI = mmioFOURCC('m', 'i', 'd', 's');
        static readonly uint streamtypeTEXT = mmioFOURCC('t', 'x', 't', 's');

        IntPtr aviFile = IntPtr.Zero;
        IntPtr aviStream = IntPtr.Zero;
        bool disposed = false;

        public static void Initialize()
        {
            AVIFileInit();
        }

        public static void Terminate()
        {
            AVIFileExit();
        }

        public AviFile(string filename)
        {
            uint result;
            result = AVIFileOpen(out aviFile, filename, OpenFileFlags.OF_READ, IntPtr.Zero);
            if (result != 0)
            {
                Release();
                throw new Exception("AVIFileOpen failure.");
            }

            result = AVIFileGetStream(aviFile, out aviStream, streamtypeVIDEO, 0);
            if (result != 0)
            {
                Release();
                throw new Exception("AVIFileGetStream failure.");
            }
        }

        ~AviFile()
        {
            Dispose(false);
        }

        void Release()
        {
            if (aviStream != IntPtr.Zero)
            {
                AVIStreamRelease(aviStream);
                aviStream = IntPtr.Zero;
            }

            if (aviFile != IntPtr.Zero)
            {
                AVIFileRelease(aviFile);
                aviFile = IntPtr.Zero;
            }
        }

        public int GetMaxFrameCount()
        {
            if (aviStream == IntPtr.Zero)
                throw new InvalidOperationException();
            return AVIStreamLength(aviStream);
        }

        public Bitmap GetFrame(int no)
        {
            if (aviStream == IntPtr.Zero)
                throw new InvalidOperationException();

            IntPtr frame = IntPtr.Zero;
            try
            {
                frame = AVIStreamGetFrameOpen(aviStream, AVIGETFRAMEF_BESTDISPLAYFMT);
                IntPtr pbmi = AVIStreamGetFrame(frame, no);
                return BitmapUtil.ToBitmap(pbmi);

            }
            finally
            {
                if (frame != IntPtr.Zero)
                    AVIStreamGetFrameClose(frame);
            }
        }

        protected void Dispose(bool disposing)
        {
            if (disposed)
                return;
            disposed = true;
            Release();
        }

        #region IDisposable Members

        public void Dispose()
        {
            GC.SuppressFinalize(this);
            Dispose(true);
        }

        #endregion
    }
}

このクラスを使って切り出しします。

using System;
using System.IO;
using AviFileUtil;
using System.Drawing;

namespace WindowsFormsApplication1
{
    static class Program
    {
        public static void Main() 
        {
            string filename = "op1_.avi";

            AviFile.Initialize();

            using (AviFile avi = new AviFile(filename))
            {
                int max = avi.GetMaxFrameCount();
                for (int i = 0; i < max; ++i)
                {
                    Bitmap bmp = avi.GetFrame(i);
                    bmp.Save(i + ".bmp");
                    bmp.Dispose();
                }
            }

            AviFile.Terminate();
        }
    }
}

試した動画では1000ファイルくらいのBMPが作成されました。で、これを以前作ったbmp2xls.pyで変換・・・なのですが、この処理は非常に時間が掛かります。連休も終わりなので、ここで時間切れ。技術的興味は満たせたので、ま、いいか(^^;