JPEG画像ファイルのタイムスタンプを補正する

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してみましょう。

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

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

コメント