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

Drag and Dropをサポートするものとして最終的にFMXプログラムとして統合

まだ修正すべき部分がありますが、一応一段落とします。さて、フォームは、

こんな感じです。上図のTCheckBoxはIsChecked=trueが吉ですかね。さて、Unit1.hは、

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

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <FMX.Controls.hpp>
#include <FMX.Forms.hpp>
#include <FMX.Controls.Presentation.hpp>
#include <FMX.Memo.hpp>
#include <FMX.Memo.Types.hpp>
#include <FMX.ScrollBox.hpp>
#include <FMX.Types.hpp>
#include <FMX.StdCtrls.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TMemo *Memo1;
	TCheckBox *RemoveExif;
	TLabel *Label2;
private:	// ユーザー宣言
public:		// ユーザー宣言
	__fastcall TForm1(TComponent* Owner);
	virtual void __fastcall DragDrop(const Fmx::Types::TDragObject &Data, const System::Types::TPointF &Point);
	virtual void __fastcall DragOver(const Fmx::Types::TDragObject &Data, const System::Types::TPointF &Point, Fmx::Types::TDragOperation &Operation);

};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

Unit1.cppは、

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

#include <fmx.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 "*.fmx"

namespace fs = std::filesystem;
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)
{

	Memo1->Lines->Add("working....");

}

bool CreateProcess(String cmdline)
{
        STARTUPINFOW si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    si.dwFlags = STARTF_USESHOWWINDOW;
	si.wShowWindow = SW_HIDE;
    // 実行ファイルと引数(1つの文字列にまとめる)
	//wchar_t cmdLine[] = L"notepad.exe C:\\Temp\\test.txt";

    if (CreateProcessW(
        NULL,       // 実行ファイル名(NULLなら cmdLine から取得)
		cmdline.w_str(),    // コマンドライン
        NULL,       // プロセスセキュリティ属性
        NULL,       // スレッドセキュリティ属性
        FALSE,      // ハンドル継承
        0,          // 作成フラグ
        NULL,       // 環境変数
        NULL,       // カレントディレクトリ
        &si,        // STARTUPINFO
        &pi         // PROCESS_INFORMATION
    )) {
        // プロセス終了を待つ場合
        //WaitForSingleObject(pi.hProcess, INFINITE);

        // ハンドルを閉じる
        CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		return true;
    } else {
		ShowMessage(L"CreateProcess に失敗しました。");
		return false;
    }

}

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 ProcessFile(String fname)
{
	String comm = "c:\\program files\\python311\\exiv2.exe";
	comm += " -k -d e ";
	comm += "\"" + fname + "\"";

	//Form1->Label1->Text = comm;

	if( CreateProcess(comm) )
		Form1->Label2->Text = "success";
	else
		Form1->Label2->Text = "failed";

}


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 ) {
        delete buffer;
		//SetFileTimeStamp(filename,dt);
		if( SetFileCreationTime(filename,dt) )
			Form1->Memo1->Lines->Add("set to " + dt.toAnsiString());


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

}

bool TouchFileTimestamp(String path)
{
	Form1->Memo1->Lines->Add(path);
	// at first extract exif.recordedtime
	Parse_File(path);   // where is data Memo1->Lines->Add(o2) in function



	return true;
}
void DigDirectory(String path)
{
	fs::path target_path = path.c_str();
	try {
		// directory_iteratorを使用してディレクトリ内を走査
		for (const fs::directory_entry& entry : fs::directory_iterator(target_path)) {
			// entry.path() でフルパスを取得し、filename() でファイル名のみを抽出
			//std::cout << entry.path().filename() << std::endl;
			//Form1->Memo1->Lines->Add( path + "\\" + entry.path().filename().c_str());
			//Form1->Memo1->Lines->Add(entry.path().c_str());
			TouchFileTimestamp(entry.path().c_str());

		}
	} catch (const fs::filesystem_error& e) {
		// ディレクトリが存在しない場合などのエラー処理
		//std::cerr << "エラーが発生しました: " << e.what() << std::endl;
		ShowMessage("error");
	}


}


void __fastcall TForm1::DragDrop(const Fmx::Types::TDragObject &Data, const System::Types::TPointF &Point)
{
	TForm::DragDrop(Data,Point);
	for( int i = 0 ; i < Data.Files.Length ; i ++ ){
		if(DirectoryExists(Data.Files[i]))
			DigDirectory(Data.Files[i]);
		else
			//Memo1->Lines->Add(Data.Files[i]);
			TouchFileTimestamp(Data.Files[i]);
	}


}

void __fastcall TForm1::DragOver(const Fmx::Types::TDragObject &Data, const System::Types::TPointF &Point, Fmx::Types::TDragOperation &Operation)
{
	TForm::DragOver(Data,Point,Operation);
	Operation = TDragOperation::Copy;

}

起動すると、

フォーム全体でファイルエクスプローラーからのファイルないしフォルダーまるごとのDrag and Dropを受け付けます。以下のフォルダーを落としてみましょうか。

すると、

のように、Exif情報から撮影日時を抽出してファイル作成日時にセットして、その後でExif全体を削除しています。確認してみますか?

よいみたいですね。ただし、Exif情報全体を抜いてしまったので、下図のように画像の向きが変になることがあるようです。

書籍の画像なので、本来は縦長でした。GPS情報だけを削除すればいいので、現在修正中です。

コメント