17113


お世話になります。

Jpeg画像のexifタグの中の情報を書き換え、エクスプローラできちんと  
表示させるコードを書きたいと思っています。

<http://dobon.net/vb/dotnet/graphics/getexifinfo.html>

上記サイト様を見て、何とかタイトルは書き換えることに成功したのですが、  
件名とコメントの二つがどうしてもできません。

[![画像の説明をここに入力](https://i.stack.imgur.com/Y5JBw.jpg)](https://i.stack.imgur.com/Y5JBw.jpg)

調べてみたところ、idの値が、40092がコメント、40095が件名だというところまでは  
突き止めたのですが、TypeやValueの内容が解りません。

エクスプローラで表示させるには、TypeやValueにはどのようなデータを  
渡せばよいですか？そのほかにも、何かやらなければならないことが  
あるのでしょうか？

よろしくお願いいたします。


CC BY-SA 3.0
2015-09-30T09:03:49.460
17098
2015-10-01T08:21:37.363
9374
question
c#
C# Jpegの件名とコメントを編集して、エクスプローラで確認できるように書き換えたい


"exif 40092" で検索してみたところ、Exiv2 というC++向けのライブラリの情報が見つかりました。

<http://www.exiv2.org/tags.html>

それによると、 40092 の "Exif.Image.XPComment" と 40095 の "Exif.Image.XPSubject" はどちらも
Byte 型で、値はUCS2でエンコードされている、と書かれています。

もしくは実際にエクスプローラで書き換えてみてから、C#で情報を調べてみてもいいですね。

Byte型に相当する `PropertyInfo.Type` の値はdobon.netに書かれている通り `1`、UCS-2(UTF-16) への変換には
`Encoding.Unicode` が使えます。

以下のコードで既に設定されている「件名」を書き換えることができました。

[code]

    using (var bmp = new Bitmap(@"r:\in.jpg"))
    {
        var pi = bmp.GetPropertyItem(40095);
        pi.Value = Encoding.Unicode.GetBytes("これはテストです");
        bmp.SetPropertyItem(pi);
        bmp.Save(@"r:\out.jpg");
    }
    
[/code]

* * *

タイトルはASCIIなのに、何故件名やコメントはUCS-2なのか、という補足です。

EXIF は TIFF
という30年も前に策定された別の規格が元になっています。タグの格納方法や型、そしてタイトル（ImageDescription）をはじめとする基本的なタグ項目は
TIFF と同じものが使われています。この頃 Unicode はまだ形になっていませんでしたから、 UCS-2 なんて選択肢もないわけです。

一方で、 XPSubject や XPComment などの「XP～」というタグは WindowsXP が独自に使い始めたタグのようです。そのため現在の
Exif 2.3 の規格書にもこれらのタグは載っていません。


CC BY-SA 3.0
2015-09-30T13:41:54.260
17113
2015-10-01T08:21:37.363
2015-10-01T08:21:37.363
8000
8000
17098
answer


Windows
Vista以降のエクスプローラーでは表示だけでなくプロパティダイアログでの編集もサポートしています。そしてそのインターフェースは公開されています。ですので、
**JPEGのEXIFを編集したい** ではなく **エクスプローラーに表示される項目を編集したい**
という目的でしたら直接こちらのインターフェースを操作することをお勧めします。  
目的の項目を直接編集できるだけでなく、エクスプローラーがサポートしているファイル形式について同じ方法で扱えます。

コードとしてはこのような呼び出し方になります。

[code]

    static void SetSubjectAndComment(string fullpath, string subject, string comment) {
        IPropertyStore prop;
        SHGetPropertyStoreFromParsingName(fullpath, IntPtr.Zero, GETPROPERTYSTOREFLAGS.READWRITE, typeof(IPropertyStore).GUID, out prop);
    
        var keySubject = PROPERTYKEY.FromName("System.Subject");
        var valSubject = new PROPVARIANT(subject);
        prop.SetValue(ref keySubject, ref valSubject);
        PropVariantClear(ref valSubject);
    
        var keyComment = PROPERTYKEY.FromName("System.Comment");
        var valComment = new PROPVARIANT(comment);
        prop.SetValue(ref keyComment, ref valComment);
        PropVariantClear(ref valComment);
    
        prop.Commit();
    }
    
[/code]

これを実行するのに必要な定義は以下になります。（文字列型に必要な分の定義しかしていません。）

[code]

    [Flags]
    enum GETPROPERTYSTOREFLAGS {
        READWRITE = 0x00000002,
    }
    
    [StructLayout(LayoutKind.Sequential)]
    struct PROPERTYKEY {
        public Guid fmtid;
        public int pid;
    
        public static PROPERTYKEY FromName(string name) {
            PROPERTYKEY key;
            PSGetPropertyKeyFromName(name, out key);
            return key;
        }
    }
    
    enum VARTYPE : ushort {
        VT_LPWSTR = 31,
    }
    
    [StructLayout(LayoutKind.Explicit)]
    struct PROPVARIANT {
        [FieldOffset(0)]
        public VARTYPE vt;
        [FieldOffset(8)]
        public IntPtr bstrVal;
    
        public PROPVARIANT(string val) {
            vt = VARTYPE.VT_LPWSTR;
            bstrVal = Marshal.StringToCoTaskMemUni(val);
        }
    }
    
    [ComImport, Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IPropertyStore {
        void GetCount(out int cProps);
        void GetAt(int iProp, out PROPERTYKEY pkey);
        void GetValue([In] ref PROPERTYKEY key, out PROPVARIANT pv);
        void SetValue([In] ref PROPERTYKEY key, [In] ref PROPVARIANT propvar);
        void Commit();
    }
    
    [DllImport("Shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
    static extern void SHGetPropertyStoreFromParsingName(string pszPath, IntPtr pbc, GETPROPERTYSTOREFLAGS flags, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IPropertyStore ppv);
    [DllImport("Propsys.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
    static extern void PSGetPropertyKeyFromName(string pszString, out PROPERTYKEY pkey);
    [DllImport("ole32.dll", PreserveSig = false)]
    static extern void PropVariantClear(ref PROPVARIANT pvar);
    
[/code]

質問ではコメントと件名を設定したいということでしたが、設定できるプロパティは[Windows
Properties](https://msdn.microsoft.com/en-
us/library/dd561977\(v=vs.85\).aspx)にリストされていて、コード例にもあるようにコメント＝`System.Comment`、件名＝`System.Subject`のようになっています。


CC BY-SA 3.0
2015-09-30T13:58:23.327
17118
2015-09-30T23:17:37.210
2015-09-30T23:17:37.210
4236
4236
17098
answer


WPFであれば`BitmapFrame`と[`BitmapMetadata`](https://msdn.microsoft.com/ja-
jp/library/system.windows.media.imaging.bitmapmetadata%28v=vs.110%29.aspx)クラスを用いて比較的容易に画像のメタデータが編集可能です。プロジェクトの参照に「System.Xaml」「WindowsBase」「PresentationCore」「PresentationFramework」を追加して以下のように実行してください。

[code]

    // using System.Windows.Media.Imaging;
    
    // 入出力するファイルを開く
    using (var fs = new FileStream(originalPath, FileMode.Open, FileAccess.ReadWrite))
    {
        // ファイルを読み込み、BitmapFrameを作成する
        var f = BitmapFrame.Create(fs, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnDemand);
    
        // メタデータを編集するためのインスタンスを作成する
        var meta = f.CreateInPlaceBitmapMetadataWriter();
    
        // TODO:ここでメタデータを編集します。
        meta.Title += "タイトル";
        meta.Subject += "件名";
        meta.Comment += "コメント";
    
        // メタデータの変更を元のファイルに書き込む
        meta.TrySave();
    }
    
[/code]

また同時に画像のトランスコードを行う場合は`BitmapEncoder`を使用します。

[code]

    // メタデータの作成
    // BitmapFrame f;
    var meta = f.Metadata.Clone() as BitmapMetadata ?? new BitmapMetadata("jpg");
    meta.Title += "タイトル";
    meta.Subject += "件名";
    meta.Comment += "コメント";
    
    // JPEGファイルの出力
    var enc = new JpegBitmapEncoder();
    enc.Frames.Add(BitmapFrame.Create(f, f.Thumbnail, meta, f.ColorContexts));
    
    using (var fs = new FileStream(outputPath, FileMode.Create))
    {
        enc.Save(fs);
    }
    
[/code]

なお上記のコードでも利用していますが、読み取り専用のメタデータは`BitmapFrame.Metadata`プロパティで参照できます。読み込みと書き込みにタイムラグがある場合はこちらを利用したほうが良いかと思います。

[code]

    var meta = (BitmapMetadata)f.Metadata;
    
[/code]


CC BY-SA 3.0
2015-09-30T14:16:54.253
17119
2015-10-01T06:25:44.193
2015-10-01T06:25:44.193
5750
5750
17098
answer