Drag and DropしたファイルのタイムスタンプをExifの”撮影日時”に修正するプログラム#1

ファイルの作成日時をExifから取った撮影日時に設定するパート

この部分だけWin32ぽいので、FMXなプログラムとはまったく相容れないのですが、しょうが無いですね。”簡単”にはできないのだから。ちなみにWindowsのファイルエクスプローラー上でファイルのプロパティーを見ると、以下の例のように、

作成日時更新日時アクセス日時の三つがあります。このうち更新日時の変更であれば、C++ Builder CE界隈(delphi界隈でも同じ)から、

void __fastcall TForm1::Button1Click(TObject *Sender)
{
	String filename = ".\\test.txt";
	int ha;
	int fa;


	ha = FileOpen(filename, fmOpenReadWrite);
	if (ha == -1) {
		ShowMessage("ファイルを開けませんでした。");
		return;
	}

	try {
		// 現在時刻をファイルの更新日時に設定
		fa = DateTimeToFileDate(Now());

		if (FileSetDate(ha, fa) == 0)
			ShowMessage("変更しました。");
		else
			ShowMessage("エラー発生");
	}
	__finally {
		FileClose(ha);
	}

}

で可能ですが、やりたいことは作成日時をExif内部の撮影日時にしたいわけです。そうすれば、ファイルエクスプローラーの設定で、

等とすることで、古いものから順番にならんで吉です。これがやりたいので、なんとか作成日時をセットしたいわけですな。で解は、

bool SetFileCreationTime(String filePath, TDateTime dt) {
	// Convert SYSTEMTIME to FILETIME

	SYSTEMTIME  newTime;

	DateTimeToSystemTime(dt,newTime);

	FILETIME ft;
	if (!SystemTimeToFileTime(&newTime, &ft)) {
		return false;
	}

	// Open file with write attributes permission
	HANDLE hFile = CreateFileW(
		filePath.w_str(),
		FILE_WRITE_ATTRIBUTES,
		FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL
	);

	if (hFile == INVALID_HANDLE_VALUE) {
		return false;
	}

	// Set creation time (leave last access and write times unchanged)
	BOOL result = SetFileTime(hFile, &ft, NULL, NULL);

	CloseHandle(hFile);
	return (result != 0);
}

こんな感じでWindowsぽい関数で実現します。

	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);
			exif = true;
			break;  // exif block found
		}
		fp->Seek(length-2,soFromCurrent);



	}while( sig != EOI && i < 10 );


	delete fp;

	if( exif ) {
		Form1->Memo1->Lines->Add("exif found");
		delete buffer;
		if( SetFileCreationTime(filename,dt) )
			Form1->Memo1->Lines->Add("set to " + dt.toAnsiString());

		//if( Form1->RemoveExif->IsChecked )
		//	ProcessFile(filename);
	}

他にExifから拾った撮影日時は、“2026:04:03 15:15:45”というひねくれたフォーマットなので、“2026/04/03 15:15:45”という風に直します。それは、

		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); //original format
			TFormatSettings fs;
			fs = TFormatSettings::Create(); // Initialize with defaults
			//fs.DateSeparator = ':';
			fs.ShortDateFormat = "yyyy:mm:dd";

			dt = StrToDateTime(o2,fs);

			//dt = StrToDateTime(o2); // exception?
			Form1->Memo1->Lines->Add(DateTimeToStr(dt));

		}

で可能です。関数呼び出ししてるので速度の点で?ですけど、ま問題ないでしょう?String o2を直接文字変換(:->/)するよりかは綺麗?(ほんとか?笑)最終形ではないので、一応読者がrebuildできるようにフォームやソースを提示しておきますかね。フォームは、

Unit1.hは、

//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.Dialogs.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TOpenDialog *OpenDialog1;
	TButton *Pick;
	TMemo *Memo1;
	void __fastcall PickClick(TObject *Sender);
private:	// ユーザー宣言
public:		// ユーザー宣言
	__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

まだVCLですよ。最後の最後にFMXになります。Unit1.cppは、

include <vcl.h>
#pragma hdrstop
#include <windows.h>
#include <System.SysUtils.hpp>
#include <filesystem>
#include <System.SysUtils.hpp> // For StrToDateTime, Exception

#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;
bool status;
TDateTime dt;

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}

// Helper function to set file creation time
bool SetFileCreationTime(String filePath, TDateTime dt) {
	// Convert SYSTEMTIME to FILETIME

	SYSTEMTIME  newTime;

	DateTimeToSystemTime(dt,newTime);

	FILETIME ft;
	if (!SystemTimeToFileTime(&newTime, &ft)) {
		return false;
	}

	// Open file with write attributes permission
	HANDLE hFile = CreateFileW(
		filePath.w_str(),
		FILE_WRITE_ATTRIBUTES,
		FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL
	);

	if (hFile == INVALID_HANDLE_VALUE) {
		return false;
	}

	// Set creation time (leave last access and write times unchanged)
	BOOL result = SetFileTime(hFile, &ft, NULL, NULL);

	CloseHandle(hFile);
	return (result != 0);
}

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); //original format
			TFormatSettings fs;
			fs = TFormatSettings::Create(); // Initialize with defaults
			//fs.DateSeparator = ':';
			fs.ShortDateFormat = "yyyy:mm:dd";

			dt = StrToDateTime(o2,fs);

			//dt = StrToDateTime(o2); // exception?
			Form1->Memo1->Lines->Add(DateTimeToStr(dt));

		}
		/*
		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(String filename)
{
   //Form1->Memo1->Lines->Add(filename);
   //Form1->Label1->Caption = filename;
	unsigned  short sig;
	UnicodeString out,out2;
	Byte* buffer;
	bool exif = false;

		//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);
			exif = true;
			break;  // exif block found
		}
		fp->Seek(length-2,soFromCurrent);



	}while( sig != EOI && i < 10 );


	delete fp;

	if( exif ) {
		Form1->Memo1->Lines->Add("exif found");
		delete buffer;
		if( SetFileCreationTime(filename,dt) )
			Form1->Memo1->Lines->Add("set to " + dt.toAnsiString());

		//if( Form1->RemoveExif->IsChecked )
		//	ProcessFile(filename);
	}

}


//---------------------------------------------------------------------------
void __fastcall TForm1::PickClick(TObject *Sender)
{
	if( OpenDialog1->Execute())
		Parse_File(OpenDialog1->FileName);

}
//---------------------------------------------------------------------------

まだバルク(一括処理)ではありませんよ。

コメント