肝心要の実際のプロパティ値はどこにあるのかというと SystemIndex_1_PropertyStore
というテーブルにあります。
定義は次の通り。
CREATE TABLE SystemIndex_1_PropertyStore (
WorkId INTEGER NOT NULL,
ColumnId INTEGER NOT NULL,
Value BLOB NOT NULL,
PRIMARY KEY (WorkId, ColumnId)
) WITHOUT ROWID;
実際の中身ですが、こちらはBLOBが入っているせいで文字化けしたので .dump
した結果から抜粋しました。
WorkId |
ColumnId |
Value |
1 |
13 |
1 |
1 |
17 |
6 |
1 |
26 |
X'2925c18d31b6d901' |
1 |
29 |
0 |
1 |
30 |
'C:\ProgramData\Microsoft\Windows' |
1 |
33 |
'C:\ProgramData\Microsoft\Windows\スタート メニュー' |
1 |
35 |
0.0 |
1 |
39 |
'file:C:/ProgramData/Microsoft/Windows/Start Menu' |
1 |
188 |
X'00ea2bc7d261d801' |
1 |
295 |
0 |
1 |
306 |
0 |
1 |
308 |
X'00ea2bc7d261d801' |
1 |
342 |
X'66006f006c00640065007200' |
1 |
349 |
X'5600f5bf02b2cf41' |
1 |
376 |
'file' |
1 |
414 |
'Windows' |
1 |
420 |
'ファイル フォルダー' |
1 |
432 |
'スタート メニュー' |
1 |
438 |
8209 |
1 |
441 |
X'3868c8f55df9d801' |
1 |
444 |
X'00ea2bc7d261d801' |
1 |
445 |
X'00b352c6d261d801' |
1 |
448 |
X'3868c8f55df9d801' |
1 |
449 |
X'e3efa68d31b6d901' |
1 |
462 |
'スタート メニュー' |
1 |
463 |
'フォルダー' |
1 |
464 |
'Windows (C:\ProgramData\Microsoft)' |
1 |
466 |
'スタート メニュー' |
1 |
470 |
'スタート メニュー' |
1 |
564 |
'スタート メニュー (C:\ProgramData\Microsoft\Windows)' |
1 |
567 |
'Directory' |
1 |
581 |
'Start Menu' |
1 |
585 |
'Start Menu' |
1 |
587 |
1887437183 |
1 |
596 |
X'28126512' |
1 |
597 |
X'ba0a9bc7980a1de2357c65c73ae167b6' |
2 |
13 |
1 |
2 |
17 |
6 |
2 |
26 |
X'b7a34f8331b6d901' |
2 |
29 |
0 |
2 |
30 |
'C:\' |
2 |
33 |
'C:\ユーザー' |
2 |
35 |
0.109375 |
2 |
39 |
'file:C:/Users' |
以降もこの調子で1行に1つのプロパティが大量に並んでいます。
WorkId
というのがオブジェクトの識別番号のようです。
ColumnId
は先ほど見たテーブル SystemIndex_1_PropertyStore_Metadata
の Id
列に対応しています。どうも旧形式のデータベースでは一つの行が一つのファイルで、プロパティは一つ一つの列だったのだとか。SQLiteに移行したときに設計を変えたようです。プロパティは存在しないものが多いのでプロパティを列にするとnullだらけになってしまいます。それでもインタフェースとしてはそちらの方が直感的ですよね。
Value
はプロパティの値です。テーブルの定義上ではBLOB型(Value BLOB NOT NULL)となっているのですが、実際に格納されている型は様々なようで、 SystemIndex_1_PropertyStore_Metadata
の VariantType
列に書かれている形式で格納されているみたいです。
これも先ほどやったようにorg-modeの表でプロパティ名と型名を補ってみると次のようになります。
| WorkId | ColumnId | (PropertyName) | (VTName) | Value |
|--------+----------+------------------------------------+----------+------------------------------------------------------|
| 1 | 13 | System.IsFolder | BOOL | 1 |
| 1 | 17 | System.FilePlaceholderStatus | UI4 | 6 |
| 1 | 26 | System.Search.GatherTime | FILETIME | X'2925c18d31b6d901' |
| 1 | 29 | System.Search.AccessCount | UI4 | 0 |
| 1 | 30 | System.ItemFolderPathDisplay | LPWSTR | 'C:\ProgramData\Microsoft\Windows' |
| 1 | 33 | System.ItemPathDisplay | LPWSTR | 'C:\ProgramData\Microsoft\Windows\スタート メニュー' |
| 1 | 35 | System.Search.LastIndexedTotalTime | R8 | 0.0 |
| 1 | 39 | System.ItemUrl | LPWSTR | 'file:C:/ProgramData/Microsoft/Windows/Start Menu' |
| 1 | 188 | System.DateImported | FILETIME | X'00ea2bc7d261d801' |
| 1 | 295 | System.IsAttachment | BOOL | 0 |
| 1 | 306 | System.IsEncrypted | BOOL | 0 |
| 1 | 308 | System.ItemDate | FILETIME | X'00ea2bc7d261d801' |
| 1 | 342 | System.Kind | BLOB | X'66006f006c00640065007200' |
| 1 | 349 | System.ThumbnailCacheId | UI8 | X'5600f5bf02b2cf41' |
| 1 | 376 | System.Search.Store | LPWSTR | 'file' |
| 1 | 414 | System.ItemFolderNameDisplay | LPWSTR | 'Windows' |
| 1 | 420 | System.ItemTypeText | LPWSTR | 'ファイル フォルダー' |
| 1 | 432 | System.ItemNameDisplay | LPWSTR | 'スタート メニュー' |
#+TBLFM: $3='(my-wds-property-name $2);L::$4='(my-wds-property-vt-name $2);L
System.ItemUrl
が完全なパスを表しています。 System.ItemPathDisplay
は Display と付いているように一部のファイル名がローカライズされてしまうので使いづらいんですよね。以前EmacsからWindows Searchするものを作ったときに困った記憶があります。
WorkId=1はスタートメニューフォルダを表しているようですが、あまりピンとこないので何か写真のプロパティを取得してみましょう。
まずはファイルを検索。といってもこのデータベースはノートPCのものなのでインデックスの範囲は極力絞ってありますし、その範囲に写真は入っていません。とりあえずテスト画像を作ってPicturesフォルダに入れて認識させ、再度データベースをメインPCにコピー。そして次のSQLで検索。
select * from SystemIndex_1_PropertyStore where ColumnId=39 and Value LIKE 'file:C:/Users/USERNAME/Pictures/%.jpg';
WorkId |
ColumnId |
Value |
3338 |
39 |
file:C:/Users/USERNAME/Pictures/PXL_20241025_040712150.jpg |
新しい写真の WorkId
は 3338
になったようです。これをキーに全プロパティを取得してみましょう。
select * from SystemIndex_1_PropertyStore where WorkId=3338;
……と、実際やってみましたが、これもBLOBが文字化けしてしまったのでダンプしたデータから抜粋します(ついでにプロパティ名と型名も補っておきます)。
WorkId |
ColumnId |
(PropertyName) |
(VTName) |
Value |
3338 |
13 |
System.IsFolder |
BOOL |
0 |
3338 |
15 |
System.MIMEType |
LPWSTR |
'image/jpeg' |
3338 |
17 |
System.FilePlaceholderStatus |
UI4 |
6 |
3338 |
23 |
System.GPS.LatitudeRef |
LPWSTR |
'N' |
3338 |
26 |
System.Search.GatherTime |
FILETIME |
X'b183b6f95232db01' |
3338 |
29 |
System.Search.AccessCount |
UI4 |
0 |
3338 |
30 |
System.ItemFolderPathDisplay |
LPWSTR |
'C:\ユーザー\USERNAME\ピクチャ' |
3338 |
33 |
System.ItemPathDisplay |
LPWSTR |
'C:\ユーザー\USERNAME\ピクチャ\PXL_20241025_040712150.jpg' |
3338 |
35 |
System.Search.LastIndexedTotalTime |
R8 |
0.0 |
3338 |
39 |
System.ItemUrl |
LPWSTR |
'file:C:/Users/USERNAME/Pictures/PXL_20241025_040712150.jpg' |
3338 |
47 |
System.ItemParticipants |
BLOB |
X'5400610072006f000000480061006e0061006b006f00' |
3338 |
60 |
System.Photo.FlashText |
LPWSTR |
'フラッシュなし (強制)' |
3338 |
75 |
System.Photo.ExposureTime |
R8 |
0.00665899999999999998 |
3338 |
85 |
System.Photo.FNumber |
R8 |
2.20000000000000017 |
3338 |
93 |
System.FileOwner |
LPWSTR |
'MYCOMPUTER\USERNAME' |
3338 |
110 |
System.Image.HorizontalSize |
UI4 |
4080 |
3338 |
114 |
System.Photo.MeteringModeText |
LPWSTR |
'中央重点測光' |
3338 |
116 |
System.Image.VerticalSize |
UI4 |
3072 |
3338 |
122 |
System.Image.HorizontalResolution |
R8 |
96.0 |
3338 |
126 |
System.Image.VerticalResolution |
R8 |
96.0 |
3338 |
130 |
System.Image.BitDepth |
UI4 |
24 |
3338 |
143 |
System.Photo.WhiteBalance |
UI4 |
0 |
3338 |
153 |
System.Image.Dimensions |
LPWSTR |
'4080 x 3072' |
3338 |
172 |
System.GPS.Date |
FILETIME |
X'0020d86ad526db01' |
3338 |
185 |
System.GPS.LongitudeRef |
LPWSTR |
'E' |
3338 |
188 |
System.DateImported |
FILETIME |
X'00a2f2dd5032db01' |
3338 |
204 |
System.Media.ClassPrimaryID |
LPWSTR |
'{6FB2E74A-B8CB-40BB-93F3-FAC5F00FA203}' |
3338 |
270 |
System.Media.DlnaProfileID |
BLOB |
X'4a005000450047005f004c0052004700' |
3338 |
279 |
System.NotUserContent |
BOOL |
0 |
3338 |
287 |
System.Photo.FocalLengthInFilm |
UI2 |
16 |
3338 |
290 |
System.Photo.OrientationText |
LPWSTR |
'標準' |
3338 |
294 |
System.ItemAuthors |
BLOB |
X'5400610072006f000000480061006e0061006b006f00' |
3338 |
295 |
System.IsAttachment |
BOOL |
0 |
3338 |
306 |
System.IsEncrypted |
BOOL |
0 |
3338 |
308 |
System.ItemDate |
FILETIME |
X'60bb495e9326db01' |
3338 |
313 |
System.Photo.CameraManufacturer |
LPWSTR |
'Google' |
3338 |
316 |
System.Photo.CameraModel |
LPWSTR |
'Pixel 7' |
3338 |
322 |
System.Photo.Orientation |
UI2 |
1 |
3338 |
342 |
System.Kind |
BLOB |
X'7000690063007400750072006500' |
3338 |
349 |
System.ThumbnailCacheId |
UI8 |
X'e7698c11b08a5156' |
3338 |
356 |
System.Photo.ShutterSpeed |
R8 |
7.23000000000000042 |
3338 |
360 |
System.VolumeId |
CLSID |
X'89cae2bf542043d99ecc540d185f11f5' |
3338 |
361 |
System.Photo.Aperture |
R8 |
2.2799999999999998 |
3338 |
366 |
System.Photo.ExposureBias |
R8 |
0.0 |
3338 |
370 |
System.GPS.LatitudeDecimal |
R8 |
35.6262194166666645 |
3338 |
374 |
System.Photo.SubjectDistance |
R8 |
4294967295.0 |
3338 |
376 |
System.Search.Store |
LPWSTR |
'file' |
3338 |
377 |
System.Photo.MeteringMode |
UI2 |
2 |
3338 |
384 |
System.Photo.Flash |
UI1 |
16 |
3338 |
386 |
System.Photo.FocalLength |
R8 |
2.35000000000000008 |
3338 |
387 |
System.Photo.ExposureProgram |
UI4 |
2 |
3338 |
396 |
System.Photo.ISOSpeed |
UI2 |
470 |
3338 |
407 |
System.ContentType |
LPWSTR |
'image/jpeg' |
3338 |
414 |
System.ItemFolderNameDisplay |
LPWSTR |
'ピクチャ' |
3338 |
424 |
System.Title |
LPWSTR |
'Senburi near Icchodaira' |
3338 |
429 |
System.Author |
BLOB |
X'5400610072006f000000480061006e0061006b006f00' |
3338 |
430 |
System.Keywords |
BLOB |
X'530065006e0062007500720069000000540061006b0061006f00' |
3338 |
432 |
System.ItemNameDisplay |
LPWSTR |
'PXL_20241025_040712150.jpg' |
3338 |
434 |
System.FileExtension |
LPWSTR |
'.jpg' |
3338 |
436 |
System.Size |
UI8 |
X'bfdb4d0000000000' |
3338 |
438 |
System.FileAttributes |
UI4 |
32 |
3338 |
441 |
System.DateModified |
FILETIME |
X'00e4a1b35232db01' |
3338 |
444 |
System.Document.DateCreated |
FILETIME |
X'00a2f2dd5032db01' |
3338 |
445 |
System.DateCreated |
FILETIME |
X'ce669cdd5032db01' |
3338 |
448 |
System.Document.DateSaved |
FILETIME |
X'00e4a1b35232db01' |
3338 |
449 |
System.DateAccessed |
FILETIME |
X'919582f95232db01' |
3338 |
459 |
System.Photo.ProgramModeText |
LPWSTR |
'標準のプログラム' |
3338 |
461 |
System.ApplicationName |
LPWSTR |
'HDR+ 1.0.641377693zd' |
3338 |
462 |
System.ItemName |
LPWSTR |
'PXL_20241025_040712150.jpg' |
3338 |
463 |
System.KindText |
LPWSTR |
'ピクチャ' |
3338 |
464 |
System.ItemFolderPathDisplayNarrow |
LPWSTR |
'ピクチャ (C:\ユーザー\USERNAME)' |
3338 |
466 |
System.ItemNameDisplayWithoutExtension |
LPWSTR |
'PXL_20241025_040712150' |
3338 |
469 |
System.GPS.LongitudeDecimal |
R8 |
139.230677777777771 |
3338 |
477 |
System.GPS.Latitude |
BLOB |
X'000000000080414000000000008042400cb81e85eb314140' |
3338 |
478 |
System.Photo.MaxAperture |
R8 |
2.2799999999999998 |
3338 |
482 |
System.GPS.Longitude |
BLOB |
X'00000000006061400000000000002a40f91f85eb51384940' |
3338 |
483 |
System.Photo.TagViewAggregate |
BLOB |
X'530065006e0062007500720069000000540061006b0061006f00' |
3338 |
491 |
System.Photo.ExposureProgramText |
LPWSTR |
'標準' |
3338 |
493 |
System.Photo.SaturationText |
LPWSTR |
'標準' |
3338 |
520 |
System.Photo.WhiteBalanceText |
LPWSTR |
'自動' |
3338 |
557 |
System.ComputerName |
LPWSTR |
'MYCOMPUTER' |
3338 |
564 |
System.ItemPathDisplayNarrow |
LPWSTR |
'PXL_20241025_040712150 (C:\ユーザー\USERNAME\ピクチャ)' |
3338 |
567 |
System.ItemType |
LPWSTR |
'.jpg' |
3338 |
572 |
System.Photo.SharpnessText |
LPWSTR |
'標準' |
3338 |
579 |
System.Photo.DateTaken |
FILETIME |
X'60bb495e9326db01' |
3338 |
580 |
System.Photo.ContrastText |
LPWSTR |
'標準' |
3338 |
581 |
System.FileName |
LPWSTR |
'PXL_20241025_040712150.jpg' |
3338 |
584 |
System.Photo.DigitalZoom |
R8 |
0.0 |
3338 |
585 |
System.ParsingName |
LPWSTR |
'PXL_20241025_040712150.jpg' |
3338 |
587 |
System.SFGAOFlags |
UI4 |
1077936503 |
3338 |
596 |
InvertedOnlyPids |
BLOB |
X'28126512' |
3338 |
597 |
InvertedOnlyMD5 |
BLOB |
X'0ace870441760f5a3225b96131c5f0ba' |
様々なメタデータが格納されていることが分かります。
例えば System.Title
を見ると 'Senburi near Icchodaira'
となっています。この写真は先日高尾山でセンブリの花を撮ったものだったので、あらかじめexiftoolでテスト的にタイトルを付けておいたのでした。データベースの中でどうなっているのか知りたかったので。x-default(英語)、英語、日本語と三つの言語でタイトルを付けておいたのですが、x-default(英語)のみが収集されたみたいです。
System.Author
を見るとBLOB型で X'5400610072006f000000480061006e0061006b006f00' となっています。BLOBと言っていますがどう見てもWSTR(UTF-16LE)ですよね。次のようなEmacs Lispを使えばデコード出来ます。
(defun my-decode-wstr (hexstr)
(decode-coding-string
(apply 'unibyte-string
(cl-loop for i below (length hexstr) by 2
collect (string-to-number (substring hexstr i (+ i 2)) 16)))
'utf-16le))
実際にデコードすると次のようになります。
(my-decode-wstr "5400610072006f000000480061006e0061006b006f00")
"Taro\0Hanako"
あらかじめexiftoolで作者(dc:creator)をTaroとHanakoの二名にしておいたのですが、NUL文字区切りで記録されていました。なるほどだからBLOBにしているのかもしれませんね。(追記:SQLiteは文字列にNUL文字を含めることができるそうです。一部の文字列関数が最初のNUL文字までしか認識しないといった制限はあるみたいですが。なので必ずしもNULが入っているからといってBLOBにする必要は無かったかもしれません。参考:NUL Characters In Strings - sqlite.org)
System.Keywords
も同様にデコードしてみましょう。
(my-decode-wstr "530065006e0062007500720069000000540061006b0061006f00")
"Senburi\0Takao"
こちらもSenburiとTakaoの二つをdc:subjectに設定しておいたのですが、このようにNUL文字区切りで記録されていました。
GPS情報なんかも格納されているので是非場所を割り出してみてください。何かプレゼントでも隠しておけたら面白かったのですが(笑) って、Decimalバージョンもあるのね。
というわけで、最低限プロパティの部分がどのように格納されているかは分かったと思います。