iPhone等で撮影した画像をiCloudからダウンロードすると、画像ファイルのタイムスタンプがダウンロード時のものになってしまい不便なので補正したい。
画像ファイルを選択して、プログラムのフォーム上にDrag and Dropすると、JPEGデータ内のExif情報から撮影日時を拾い出して、ファイルのタイムスタンプをそちらに合わせるとしましょうかね?
Drag and Drop関係は、せっかくクラスを作ったので、
こちらを参考に、なので珍しくFMXのプログラムになりますね。またExif情報の抜き出しは、
exif データ 構造
とかで検索するといくらでも出てきます。さすがのCopilot君も沈黙しています。
ライブラリもあるようですが、JPEG画像データを全部読み込んだりして非効率なので、自作してみました。公式の規格書、
は理解しにくいので、
ここを参考にしました。

引用させてもらった上図が分かりやすいです。Unit1.cppは、
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include <memory>
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
#define SOI 0xFFD8
#define EOI 0xFFD9
#define APP1 0xFFE1
//TForm1 *Form1;
TFileStream* fp;
bool BigEndian;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
Memo1->Lines->Clear();
DragAcceptFiles(this->Handle, true);
}
//---------------------------------------------------------------------------
void Read16(TFileStream* p,unsigned short* k)
{
unsigned char u,l;
p->Read(&u,1);
p->Read(&l,1);
*k = u*0x100 + l;
}
void BinDump_Buffer(Byte* start, unsigned length)
{
Byte it;
Byte* p;
char obuf[100];
int i;
UnicodeString str;
UnicodeString sub;
p = start;
for( i = 0 ; i < length ; i++ ){
sub.sprintf(L"%02x ",*p++);
str += sub;
}
//Form1->Memo1->Lines->Add(str);
}
void Dump_Buffer(Byte* start, unsigned length)
{
Byte it;
Byte* p;
char obuf[100];
int i;
p = start;
for( i = 0 ; i < length ; i++ ){
it = *p++;
if( it == 0x0 )
obuf[i] = '0';
else
obuf[i] = it;
}
obuf[i] = 0x0;
//Form1->Memo1->Lines->Add(UnicodeString(obuf));
}
void Detect_Endian(Byte* start, unsigned length)
{
unsigned char *p = start;
BigEndian = false;
if( *p == 'M' && *(p+1) == 'M' )
BigEndian = true;
else if( *p == 'I' && *(p+1) == 'I' )
BigEndian = false;
else
ShowMessage("Endian error");
if( !BigEndian )
Form1->Label2->Caption = "Little_Endian";
else
Form1->Label2->Caption = "Big_Endian";
return;
}
unsigned conv(Byte* p)
{
unsigned retval = 0;
if( BigEndian )
retval = *p*0x100000 + *(p+1)*0x10000 + *(p+2)*0x100 + *(p+3);
else
retval = *(p+3)*0x100000 + *(p+2)*0x10000 + *(p+1)*0x100 + *p;
return retval;
}
void IFD_Dump(Byte* address)
{
Byte* start;
char *p;
struct a_tag {
unsigned short tag;
unsigned short tag_type;
unsigned count;
unsigned val2;
} tags[100];
unsigned short ntag;
UnicodeString o;
UnicodeString o2;
start = address;
address += 8;
if(BigEndian ){
ntag = 0x100*(*address);
ntag += *++address;
}
else {
ntag = *address++;
ntag += 0x100*(*address);
}
address++;
//Read16(fp,&ntag);
o.sprintf(L"%u",ntag);
//Form1->Memo1->Lines->Add("# of tags " + o);
for( int i= 0; i < ntag ; i++ ){
if( BigEndian ){
tags[i].tag = 0x100*(*address);
tags[i].tag += *++address;
}
else{
tags[i].tag = *address++;
tags[i].tag += 0x100*(*address);
}
address++;
if( BigEndian ){
tags[i].tag_type = 0x100*(*address);
tags[i].tag_type += *++address;
}
else {
tags[i].tag_type = *address++;
tags[i].tag_type += 0x100*(*address);
}
address++;
//tags[i].count = *(unsigned*)(address);
tags[i].count = conv(address);
address += 4;
tags[i].val2 = conv(address);
if( tags[i].tag == 306 && tags[i].tag_type == 2 ){ // should get null terminated string
p= (char*)(start + tags[i].val2);
//o2.sprintf(L"%s",p);
o2 = UnicodeString(p);
Form1->Memo1->Lines->Add(o2);
}
/*
else
o.sprintf(L"%d %u %u %lu %lu",i,tags[i].tag,tags[i].tag_type,tags[i].count,tags[i].val2);
Form1->Memo1->Lines->Add(o);
*/
address += 4;
}
}
void Parse_Exif(Byte* buffer)
{
Byte* ptr;
Dump_Buffer(buffer,6); // "Exif"\0\0
Detect_Endian(buffer+6,2); //TIFF header "MM" or "II"
BinDump_Buffer(buffer+8,2); // TIFF code 002a
BinDump_Buffer(buffer+10,4); // pointer to oth IFD
IFD_Dump(buffer+6);
}
void Parse_File(UnicodeString filename)
{
Form1->Memo1->Lines->Add(filename);
Form1->Label1->Caption = filename;
unsigned short sig;
UnicodeString out,out2;
Byte* buffer;
//Memo1->Lines->Clear();
fp = new TFileStream(filename, fmOpenRead);
if( !fp ){
ShowMessage("file not found");
Application->Terminate();
}
//fp->Read(&sig,2);
Read16(fp,&sig);
out.sprintf(L"%04X",sig);
//Form1->Memo1->Lines->Add(out);
if( sig != SOI ){
ShowMessage("sig error ");
return;
}
//unsigned short sig;
unsigned short length;
UnicodeString output;
int i = 0;
do {
Read16(fp,&sig);
output.sprintf(L"%d %04X",i++,sig);
//Form1->Memo1->Lines->Add(output);
Read16(fp,&length);
output.sprintf(L"length %04X",length);
//Form1->Memo1->Lines->Add(output);
if( sig == APP1 ){
buffer = new Byte[length]; // was Byte(length)
if( !buffer ){
ShowMessage("can't allocate memory");
Application->Terminate();
}
fp->Read(buffer,length);
Parse_Exif(buffer);
break; // exim block found
}
fp->Seek(length-2,soFromCurrent);
}while( sig != EOI && i < 10 );
delete buffer;
delete fp;
}
void __fastcall TForm1::WMDropFiles(TWMDropFiles &Message)
{
//ドロップされたファイルの数を取得する
const unsigned int count = DragQueryFile((HDROP)Message.Drop, -1, NULL, 0);
for (unsigned int i = 0; i < count; ++i)
{
//ファイル名を格納するのに必要なバッファを取得する
const unsigned int length = DragQueryFile((HDROP)Message.Drop, i, NULL, 0);
std::unique_ptr<wchar_t[]> filename(new wchar_t[length + 1]);
//ドロップされたファイルの名前を取得する
DragQueryFile((HDROP)Message.Drop, i, filename.get(), length + 1);
//ファイル名をMemo1に登録する
String extension = ExtractFileExt(filename.get());
if( extension.CompareIC(".jpg") == 0 || extension.CompareIC(".jpeg") == 0 )
Parse_File(filename.get());
else
Memo1->Lines->Add(filename.get() + String(" not jpeg file"));
}
}冒頭でFMXと言いながら、こちらはVCL版のUnit1.cppですのでご注意ください。おまけとして、VCL版の方のタイムスタンプ書き換えないバージョンをGithubのパブリックレポジトリで公開しているので、そちらのURLを示します。
$ git remote -v
origin https://github.com/DoctorOrbits/exif-handling.git (fetch)
origin https://github.com/DoctorOrbits/exif-handling.git (push)
C++ Builder CEからこのレポジトリを取ってくるのは、IDEを起動して、

このバージョン管理云々を選択。

“OK”を選択。

ソースに上で書いたURLをpasteして、保存先は…をクリックして、適切なプロジェクトフォルダーを作成しつつそれを選択します。

これで”OK”を選択。

このプログレスの後で、

が出たら”OK”。”F9″でbuild and runです。運が良ければ動くはずです。

何かのJPEGファイルをdropしてみましょう。

ファイル自体のプロパティーは、

なので、”撮影日時”が得られています。良さそうですね。またもや長くなったので、この撮影日時をタイムスタンプにする手順は別記事で。いよいよ本丸ですね。


コメント