はじめに
僕は以前 Delphi でファイルの日時を変更する Windows アプリを作りました。
[簡単エクスプローラ拡張 EzExpEx]
(http://hp.vector.co.jp/authors/VA029585/EzExpEx/)
これは残念なことにユニコードの名称のファイルを扱えないんですよね。
最新の Delphi でビルドし直せばいいのですが、僕が持っている Delphi ではだめなんです。
これを VB.NET で作り直したいとずっと思っていました。
僕のアプリが扱えるファイルの日時は
・ファイル自体の作成日時、更新日時
・Exif 情報の撮影日時、更新日時
・PDF ファイルの作成日時、更新日時
・概要情報の作成日時、更新日時
ファイル自体の日時を変更するのは .NET の標準機能で対応できますが、Exif 情報の日時などはどうでしょう。
Exif 情報の日時を変更する
Exif 情報は、撮影日時や撮影条件など画像ファイルに埋込された情報です。
ファイルの Exif 情報は .NET の Image オブジェクトで変更できます。
・[画像のExif情報を取得する、設定する - DOBON.NET]
(https://dobon.net/vb/dotnet/graphics/getexifinfo.html)
・[.NET TIPS デジカメ画像のExif情報を取得するには? - C# - @IT]
(https://atmarkit.itmedia.co.jp/fdotnet/dotnettips/047exif/exif.html)
これは楽でいいのですが、この方法で Exif 情報を変更すると画像そのものが読込時点でデコードされ保存時点でエンコードし直されて、微妙に画質が落ちてしまいます。
僕が Delphi で作ったアプリは、メタデータだけ読取して変更するようにしていました。
これを VB.NET で書き直しました。C# のコードも用意しました。
Exif 情報の日時を取得する
Dim Path As String = "●●●●.jpg" '対象とするファイルのパス
Dim TimeUpdated As DateTime '更新日時を返す
Dim UpdatedValid As Boolean = False '更新日時を取得できたか返す
Dim TimeOriginal As DateTime '撮影日時を返す
Dim OriginalValid As Boolean = False '撮影日時を取得できたか返す
Dim TimeDigitized As DateTime 'デジタル化日時を返す
Dim DigitizedValid As Boolean = False 'デジタル化日時を取得できたか返す
If Not System.IO.File.Exists(Path) Then
Exit Function
End If
Try
'ファイルを読取
Using fs As New System.IO.FileStream(Path, System.IO.FileMode.Open, System.IO.FileAccess.Read)
'TiffHeader を読取
Dim TiffHeaderPos As Long
Dim IsLittleEndian As Boolean
Dim IfdNum As Integer
If Not ReadTiffHeader(fs, TiffHeaderPos, IsLittleEndian, IfdNum) Then
Exit Function
End If
Dim IfdPos As Long = fs.Position
For n As Integer = 0 To IfdNum - 1
Dim WordBuff As Byte() = {0, 0}
'IFD を読取
Dim IfdBuff As Byte() = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
fs.Seek(IfdPos + n * IfdBuff.Length, System.IO.SeekOrigin.Begin)
If fs.Read(IfdBuff, 0, IfdBuff.Length) <> IfdBuff.Length Then Exit Function
Dim Tag As UInt16 = BitConverter.ToUInt16(If(IsLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(0), IfdBuff(1)}, {IfdBuff(1), IfdBuff(0)}), 0)
Select Case Tag
Case &H132
'最後に変更された日時
If GetTimeFromIFD(fs, TiffHeaderPos, IsLittleEndian, IfdBuff, TimeUpdated) Then
UpdatedValid = True
End If
Case &H8769 'Exif データが見つかった
'エントリ数を取得
Dim Offset As Long = BitConverter.ToUInt32(If(IsLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(8), IfdBuff(9), IfdBuff(10), IfdBuff(11)}, {IfdBuff(11), IfdBuff(10), IfdBuff(9), IfdBuff(8)}), 0)
fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin)
If fs.Read(WordBuff, 0, WordBuff.Length) <> WordBuff.Length Then Exit Function
Dim EntryNum As Integer = BitConverter.ToUInt16(If(IsLittleEndian And BitConverter.IsLittleEndian, {WordBuff(0), WordBuff(1)}, {WordBuff(1), WordBuff(0)}), 0)
Dim EntryPos As Long = fs.Position
For i As Integer = 0 To EntryNum - 1
'エントリを読取
fs.Seek(EntryPos + i * IfdBuff.Length, System.IO.SeekOrigin.Begin)
If fs.Read(IfdBuff, 0, IfdBuff.Length) <> IfdBuff.Length Then Exit Function
Tag = BitConverter.ToUInt16(If(IsLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(0), IfdBuff(1)}, {IfdBuff(1), IfdBuff(0)}), 0)
Select Case Tag
Case &H9003
'撮影された日時
If GetTimeFromIFD(fs, TiffHeaderPos, IsLittleEndian, IfdBuff, TimeOriginal) Then
OriginalValid = True
End If
Case &H9004
'デジタル化された日時
If GetTimeFromIFD(fs, TiffHeaderPos, IsLittleEndian, IfdBuff, TimeDigitized) Then
DigitizedValid = True
End If
End Select
Next
End Select
Next
End Using
Catch ex As Exception
Exit Function
End Try
string Path = "●●●●.jpg"; // 対象とするファイルのパス
DateTime TimeUpdated; // 更新日時を返す
bool UpdatedValid = false; // 更新日時を取得できたか返す
DateTime TimeOrijinal; // 撮影日時を返す
bool OriginalValid = false; // 撮影日時を取得できたか返す
DateTime TimeDigitized; // デジタル化日時を返す
bool DigitizedValid = false; // デジタル化日時を取得できたか返す
if (!System.IO.File.Exists(Path)) {
return false;
}
try {
// ファイルを読取
using (System.IO.FileStream fs = new System.IO.FileStream(Path, System.IO.FileMode.Open, System.IO.FileAccess.Read)) {
// TiffHeader を読取
long TiffHeaderPos = 0;
bool isLittleEndian = true;
int IfdNum = 0;
if (!RaadTiffHeader(fs, ref TiffHeaderPos, ref isLittleEndian, ref IfdNum)) {
return false;
}
long IfdPos = fs.Position;
for (int n = 0; n < IfdNum; n++) {
byte[] WordBuff = new byte[2];
// IFD を読取
byte[] IfdBuff = new byte[12];
fs.Seek(IfdPos + n * IfdBuff.Length, System.IO.SeekOrigin.Begin);
if (fs.Read(IfdBuff, 0, IfdBuff.Length) != IfdBuff.Length) return false;
UInt16 Tag = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[0], IfdBuff[1]} : new byte[] {IfdBuff[1], IfdBuff[0]}, 0);
switch (Tag) {
case 0x0132:
// 最後に変更された日時
if (GetTimeFromIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, ref TimeUpdated)) {
UpdatedValid = true;
}
break;
case 0x8769: // Exif データが見つかった
// エントリ数を取得
long Offset = BitConverter.ToUInt32(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[8], IfdBuff[9], IfdBuff[10], IfdBuff[11]} : new byte[] {IfdBuff[11], IfdBuff[10], IfdBuff[9], IfdBuff[8]}, 0);
fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin);
if (fs.Read(WordBuff, 0, WordBuff.Length) != WordBuff.Length) return false;
int EntryNum = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {WordBuff[0], WordBuff[1]} : new byte[] {WordBuff[1], WordBuff[0]}, 0);
long EntryPos = fs.Position;
for (int i = 0; i < EntryNum; i++) {
// エントリを読取
fs.Seek(EntryPos + i * IfdBuff.Length, System.IO.SeekOrigin.Begin);
if (fs.Read(IfdBuff, 0, IfdBuff.Length) != IfdBuff.Length) return false;
Tag = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[0], IfdBuff[1]} : new byte[] {IfdBuff[1], IfdBuff[0]}, 0);
switch (Tag) {
case 0x9003:
// 撮影された日時
if (GetTimeFromIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, ref TimeOriginal))
{
OriginalValid = true;
}
break;
case 9004:
// デジタル化された日時
if (GetTimeFromIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, ref TimeDigitized))
{
DigitizedValid = true;
}
break;
}
}
break;
}
}
}
}
catch (Exception) {
return false;
}
以下はヘルパー関数です。変更するときも使用します。
Imports System.Runtime.InteropServices
Private Function ReadTiffHeader(fs As System.IO.FileStream, ByRef TiffHeaderPos As Long, ByRef isLittleEndian As Boolean, ByRef IfdNum As Integer)
ReadTiffHeader = False
Dim WordBuff As Byte() = {0, 0}
'JPEG ヘッダを確認
If fs.Read(WordBuff, 0, WordBuff.Length) <> WordBuff.Length Then Exit Function
If WordBuff(0) <> &HFF Or WordBuff(1) <> &HD8 Then Exit Function
'APP1 マーカーを確認
If fs.Read(WordBuff, 0, WordBuff.Length) <> WordBuff.Length Then Exit Function
If WordBuff(0) <> &HFF Or WordBuff(1) <> &HE1 Then Exit Function
'EXIF ヘッダを確認
fs.Seek(2, System.IO.SeekOrigin.Current)
Dim ExifHeaderBuff As Byte() = {0, 0, 0, 0, 0, 0}
If fs.Read(ExifHeaderBuff, 0, ExifHeaderBuff.Length) <> ExifHeaderBuff.Length Then Exit Function
If System.Text.Encoding.ASCII.GetString(ExifHeaderBuff).ToUpper().TrimEnd(vbNullChar) <> "EXIF" Then Exit Function
'TIFF ヘッダを読取
TiffHeaderPos = fs.Position
Dim TiffHeaderBuff As Byte() = {0, 0, 0, 0, 0, 0, 0, 0}
If fs.Read(TiffHeaderBuff, 0, TiffHeaderBuff.Length) <> TiffHeaderBuff.Length Then Exit Function
If (TiffHeaderBuff(0) <> &H49 Or TiffHeaderBuff(1) <> &H49) And (TiffHeaderBuff(0) <> &H4D Or TiffHeaderBuff(1) <> &H4D) Then Exit Function
isLittleEndian = (TiffHeaderBuff(0) = &H49 And TiffHeaderBuff(1) = &H49)
Dim Tag As UInt16 = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {TiffHeaderBuff(2), TiffHeaderBuff(3)}, {TiffHeaderBuff(3), TiffHeaderBuff(2)}), 0)
If Tag <> &H2A Then Exit Function
fs.Seek(2, System.IO.SeekOrigin.Current)
'IFD 数を取得
Dim Offset As Long = BitConverter.ToUInt32(If(isLittleEndian And BitConverter.IsLittleEndian, {TiffHeaderBuff(4), TiffHeaderBuff(5), TiffHeaderBuff(6), TiffHeaderBuff(7)}, {TiffHeaderBuff(7), TiffHeaderBuff(6), TiffHeaderBuff(5), TiffHeaderBuff(4)}), 0)
fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin)
If fs.Read(WordBuff, 0, WordBuff.Length) <> WordBuff.Length Then Exit Function
IfdNum = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {WordBuff(0), WordBuff(1)}, {WordBuff(1), WordBuff(0)}), 0)
ReadTiffHeader = True
End Function
Private Function GetTimeFromIFD(fs As System.IO.FileStream, TiffHeaderPos As Long, isLittleEndian As Boolean, IfdBuf As Byte(), ByRef Time As DateTime) As Boolean
GetTimeFromIFD = False
Dim DataType As UInt16 = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuf(2), IfdBuf(3)}, {IfdBuf(3), IfdBuf(2)}), 0)
If DataType <> 2 Then Exit Function
Dim Offset As Long = BitConverter.ToUInt32(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuf(8), IfdBuf(9), IfdBuf(10), IfdBuf(11)}, {IfdBuf(11), IfdBuf(10), IfdBuf(9), IfdBuf(8)}), 0)
fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin)
Dim Buff As Byte() = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
If fs.Read(Buff, 0, Buff.Length) <> Buff.Length Then Exit Function
Try
Dim yy As Integer = CInt(System.Text.Encoding.ASCII.GetString({Buff(0), Buff(1), Buff(2), Buff(3)}))
Dim mm As Integer = CInt(System.Text.Encoding.ASCII.GetString({Buff(5), Buff(6)}))
Dim dd As Integer = CInt(System.Text.Encoding.ASCII.GetString({Buff(8), Buff(9)}))
Dim hh As Integer = CInt(System.Text.Encoding.ASCII.GetString({Buff(11), Buff(12)}))
Dim nn As Integer = CInt(System.Text.Encoding.ASCII.GetString({Buff(14), Buff(15)}))
Dim ss As Integer = CInt(System.Text.Encoding.ASCII.GetString({Buff(17), Buff(18)}))
Time = New DateTime(yy, mm, dd, hh, nn, ss)
Catch ex As Exception
Exit Function
End Try
GetTimeFromIFD = True
End Function
private bool RaadTiffHeader(System.IO.FileStream fs, ref long TiffHeaderPos, ref bool isLittleEndian, ref int IfdNum)
{
byte[] WordBuff = new byte[2];
// JPEG ヘッダを確認
if (fs.Read(WordBuff, 0, WordBuff.Length) != WordBuff.Length) return false;
if (WordBuff[0] != 0xff || WordBuff[1] != 0xd8) return false;
// APP1 マーカーを確認
if (fs.Read(WordBuff, 0, WordBuff.Length) != WordBuff.Length) return false;
if (WordBuff[0] != 0xff || WordBuff[1] != 0xe1) return false;
// EXIF ヘッダを確認
fs.Seek(2, System.IO.SeekOrigin.Current);
byte[] ExifHeaderBuff = new byte[6];
if (fs.Read(ExifHeaderBuff, 0, ExifHeaderBuff.Length) != ExifHeaderBuff.Length) return false;
if (System.Text.Encoding.ASCII.GetString(ExifHeaderBuff).ToUpper().TrimEnd('\0') != "EXIF") return false;
// TIFF ヘッダを読取
TiffHeaderPos = fs.Position;
byte[] TiffHeaderBuff = new byte[8];
if (fs.Read(TiffHeaderBuff, 0, TiffHeaderBuff.Length) != TiffHeaderBuff.Length) return false;
if ((TiffHeaderBuff[0] != 0x49 || TiffHeaderBuff[1] != 0x49) && (TiffHeaderBuff[0] != 0x4d || TiffHeaderBuff[1] != 0x4d)) return false;
isLittleEndian = (TiffHeaderBuff[0] == 0x49 || TiffHeaderBuff[1] == 0x49);
UInt16 Tag = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {TiffHeaderBuff[2], TiffHeaderBuff[3]} : new byte[] {TiffHeaderBuff[3], TiffHeaderBuff[2]}, 0);
if (Tag != 0x2a) return false;
fs.Seek(2, System.IO.SeekOrigin.Current);
// IFD 数を取得
long Offset = BitConverter.ToUInt32(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {TiffHeaderBuff[4], TiffHeaderBuff[5], TiffHeaderBuff[6], TiffHeaderBuff[7]} : new byte[] {TiffHeaderBuff[7], TiffHeaderBuff[6], TiffHeaderBuff[5], TiffHeaderBuff[4]}, 0);
fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin);
if (fs.Read(WordBuff, 0, WordBuff.Length) != WordBuff.Length) return false;
IfdNum = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {WordBuff[0], WordBuff[1]} : new byte[] {WordBuff[1], WordBuff[0]}, 0);
return true;
}
private bool GetTimeFromIFD(System.IO.FileStream fs, long TiffHeaderPos, bool isLittleEndian, byte[] IfdBuff, ref DateTime Time)
{
UInt16 DataType = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[2], IfdBuff[3]} : new byte[] {IfdBuff[3], IfdBuff[2]}, 0);
if (DataType != 2) return false;
long Offset = BitConverter.ToUInt32(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[8], IfdBuff[9], IfdBuff[10], IfdBuff[11]} : new byte[] {IfdBuff[11], IfdBuff[10], IfdBuff[9], IfdBuff[8]}, 0);
fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin);
byte[] Buff = new byte[20];
if (fs.Read(Buff, 0, Buff.Length) != Buff.Length) return false;
try {
int yy = int.Parse(System.Text.Encoding.ASCII.GetString(new byte[] {Buff[0], Buff[1], Buff[2], Buff[3]}));
int mm = int.Parse(System.Text.Encoding.ASCII.GetString(new byte[] {Buff[5], Buff[6]}));
int dd = int.Parse(System.Text.Encoding.ASCII.GetString(new byte[] {Buff[8], Buff[9]}));
int hh = int.Parse(System.Text.Encoding.ASCII.GetString(new byte[] {Buff[11], Buff[12]}));
int nn = int.Parse(System.Text.Encoding.ASCII.GetString(new byte[] {Buff[14], Buff[15]}));
int ss = int.Parse(System.Text.Encoding.ASCII.GetString(new byte[] {Buff[17], Buff[18]}));
Time = new DateTime(yy, mm, dd, hh, nn, ss);
}
catch {
return false;
}
return true;
}
Exif 情報の日時を取得するだけなら、.NET の Image
オブジェクトを使えばいいかも知れません。
Exif 情報の日時を変更する
Dim Path As String = "●●●●.jpg" '対象とするファイルのパス
Dim TimeUpdated As DateTime '変更する更新日時を指定する
Dim UpdatedValid As Boolean = True '更新日時を変更するか指定する
Dim TimeOriginal As DateTime '変更する撮影日時を指定する
Dim OriginalValid As Boolean = True '撮影日時を変更するか指定する
Dim TimeDigitized As DateTime '変更するデジタル化日時を指定する
Dim DigitizedValid As Boolean = True 'デジタル化日時を変更するか指定する
If Not System.IO.File.Exists(Path) Then
Exit Function
End If
Try
'ファイルを読取
Using fs As New System.IO.FileStream(Path, System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite)
'TiffHeader を読取
Dim TiffHeaderPos As Long
Dim isLittleEndian As Boolean
Dim IfdNum As Integer
If Not ReadTiffHeader(fs, TiffHeaderPos, isLittleEndian, IfdNum) Then
Exit Function
End If
Dim IfdPos As Long = fs.Position
For n As Integer = 0 To IfdNum - 1
Dim WordBuff As Byte() = {0, 0}
'IFD を読取
Dim IfdBuff As Byte() = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
fs.Seek(IfdPos + n * IfdBuff.Length, System.IO.SeekOrigin.Begin)
If fs.Read(IfdBuff, 0, IfdBuff.Length) <> IfdBuff.Length Then Exit Function
Dim Tag As UInt16 = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(0), IfdBuff(1)}, {IfdBuff(1), IfdBuff(0)}), 0)
Select Case Tag
Case &H132
'最後に変更された日時
If Not UpdatedValid Then
Continue For
End If
If Not SetTimeToIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, TimeUpdated) Then
Exit Function
End If
Case &H8769
'Exif データが見つかった
Dim Offset As Long = BitConverter.ToUInt32(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(8), IfdBuff(9), IfdBuff(10), IfdBuff(11)}, {IfdBuff(11), IfdBuff(10), IfdBuff(9), IfdBuff(8)}), 0)
fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin)
'エントリ数を取得
If fs.Read(WordBuff, 0, WordBuff.Length) <> WordBuff.Length Then Exit Function
Dim EntryNum As Integer = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {WordBuff(0), WordBuff(1)}, {WordBuff(1), WordBuff(0)}), 0)
Dim EntryPos As Long = fs.Position
For i As Integer = 0 To EntryNum - 1
'エントリを読取
fs.Seek(EntryPos + i * IfdBuff.Length, System.IO.SeekOrigin.Begin)
If fs.Read(IfdBuff, 0, IfdBuff.Length) <> IfdBuff.Length Then Exit Function
Tag = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(0), IfdBuff(1)}, {IfdBuff(1), IfdBuff(0)}), 0)
Select Case Tag
Case &H9003
'撮影された日時
If Not OriginalValid Then
Continue For
End If
If Not SetTimeToIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, TimeOriginal) Then
Exit Function
End If
Case &H9004
'デジタル化された日時
If Not DigitizedValid Then
Continue For
End If
If Not SetTimeToIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, TimeDigitized) Then
Exit Function
End If
End Select
Next
End Select
Next
End Using
Catch ex As Exception
Exit Function
End Try
End Function
string Path = "●●●●.jpg"; // 対象とするファイルのパス
DateTime TimeUpdated; // 変更する更新日時を指定する
bool UpdatedValid = true; // 更新日時を変更するか指定する
DateTime TimeOrijinal; // 変更する撮影日時を指定する
bool OriginalValid = true; // 撮影日時を変更するか指定する
DateTime TimeDigitized; // 変更するデジタル化日時を指定する
bool DigitizedValid = true; // デジタル化日時を変更するか指定する
if (!System.IO.File.Exists(Path)) {
return false;
}
try {
// ファイルを読取
using (System.IO.FileStream fs = new System.IO.FileStream(Path, System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite)) {
// TiffHeader を読取
long TiffHeaderPos = 0;
bool isLittleEndian = true;
int IfdNum = 0;
if (!RaadTiffHeader(fs, ref TiffHeaderPos, ref isLittleEndian, ref IfdNum))
{
return false;
}
long IfdPos = fs.Position;
for (int n = 0; n < IfdNum; n++) {
byte[] WordBuff = new byte[2];
// IFD を読取
byte[] IfdBuff = new byte[12];
fs.Seek(IfdPos + n * IfdBuff.Length, System.IO.SeekOrigin.Begin);
if (fs.Read(IfdBuff, 0, IfdBuff.Length) != IfdBuff.Length) return false;
UInt16 Tag = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[0], IfdBuff[1]} : new byte[] {IfdBuff[1], IfdBuff[0]}, 0);
switch (Tag) {
case 0x0132:
// 最後に変更された日時
if (!UpdatedValid) {
continue;
}
if (!SetTimeToIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, TimeUpdated)) {
return false;
}
break;
case 0x8769: // Exif データが見つかった
// エントリ数を取得
long Offset = BitConverter.ToUInt32(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[8], IfdBuff[9], IfdBuff[10], IfdBuff[11]} : new byte[] {IfdBuff[11], IfdBuff[10], IfdBuff[9], IfdBuff[8]}, 0);
fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin);
if (fs.Read(WordBuff, 0, WordBuff.Length) != WordBuff.Length) return false;
int EntryNum = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {WordBuff[0], WordBuff[1]} : new byte[] {WordBuff[1], WordBuff[0]}, 0);
long EntryPos = fs.Position;
for (int i = 0; i < EntryNum; i++) {
// エントリを読取
fs.Seek(EntryPos + i * IfdBuff.Length, System.IO.SeekOrigin.Begin);
if (fs.Read(IfdBuff, 0, IfdBuff.Length) != IfdBuff.Length) return false;
Tag = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[0], IfdBuff[1]} : new byte[] {IfdBuff[1], IfdBuff[0]}, 0);
switch (Tag) {
case 0x9003:
// 撮影された日時
if (!OriginalValid) {
continue;
}
if (!SetTimeToIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, TimeOriginal)) {
return false;
}
break;
case 9004:
// デジタル化された日時
if (!DigitizedValid) {
continue;
}
if (!SetTimeToIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, TimeDigitized)) {
return false;
}
break;
}
}
break;
}
}
}
}
catch (Exception) {
return false;
}
以下はヘルパー関数です。
Private Function SetTimeToIFD(fs As System.IO.FileStream, TiffHeaderPos As Long, isLittleEndian As Boolean, IfdBuff As Byte(), Time As DateTime) As Boolean
SetTimeToIFD = False
Dim DataType As UInt16 = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(2), IfdBuff(3)}, {IfdBuff(3), IfdBuff(2)}), 0)
If DataType <> 2 Then Exit Function
Try
Dim Offset As Long = BitConverter.ToUInt32(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(8), IfdBuff(9), IfdBuff(10), IfdBuff(11)}, {IfdBuff(11), IfdBuff(10), IfdBuff(9), IfdBuff(8)}), 0)
fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin)
Dim Buff As Byte() = System.Text.Encoding.ASCII.GetBytes(Format(Time, "yyyy:MM:dd HH:mm:ss"))
fs.Write(Buff, 0, Buff.Length)
Catch ex As Exception
Exit Function
End Try
SetTimeToIFD = True
End Function
private bool SetTimeToIFD(System.IO.FileStream fs, long TiffHeaderPos, bool isLittleEndian, byte[] IfdBuff, DateTime Time)
{
UInt16 DataType = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[2], IfdBuff[3]} : new byte[] {IfdBuff[3], IfdBuff[2]}, 0);
if (DataType != 2) return false;
try {
long Offset = BitConverter.ToUInt32(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[8], IfdBuff[9], IfdBuff[10], IfdBuff[11]} : new byte[] {IfdBuff[11], IfdBuff[10], IfdBuff[9], IfdBuff[8]}, 0);
fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin);
byte[] Buff = System.Text.Encoding.ASCII.GetBytes(Time.ToString("yyyy:MM:dd HH:mm:ss"));
fs.Write(Buff, 0, Buff.Length);
}
catch {
return false;
}
return true;
}
上記のコードは、Exif 情報にセットされている既存の日時を書換しています。既存の日時がセットされていないと書換できません。
そのときは、.NET の Image
オブジェクトを使用するしかないですね。