Here's what I have so far. I was hoping to include a "batch change" feature, but I just didin't have the time to include here.
The batch change would come in handy for me, because at one point, my camera timestamp was off by -1 hour -6 minutes. The batch change will adjust/change dates on all selected files by adding/subtracting the time offset you choose, in my case +1 hour + 6 minutes.
This was tested using images from a Canon A75 and Olympus D550. It may or may not work with other cameras, so if it doesn't work for you, let me know the model of your camera and I'll see if I can get it to work for you.
Code: Select all
UseJPEGImageDecoder()
;--> Window Constants
Enumeration 1
#Window_Main
EndEnumeration
;--> Gadget Constants
Enumeration
;--> These must be 0, 1, 2
#Date_Modified_Write
#Date_Taken_Write
#Date_Digitized_Write
;--> These can be any number
#Date_All_Write
#ExplorerTree_0
#Frame3D_Thumb
#ImageGad_Thumb
#Frame3D_Modified
#Text_Modified_Read
#Text_New_0
#Frame3D_Taken
#Text_Taken_Read
#Text_New_1
#Frame3D_Digitized
#Text_Digitized_Read
#Text_New_2
#Text_Date
#Frame3D_All
#Text_New_3
#Button_Write
#StatusBar_0
EndEnumeration
;--> Image Constants
#Image_No = 0
#Image_Thumb = 1
;--> DateGadget constants
#DTM_FIRST = $1000
#DTM_SETFORMATA = #DTM_FIRST + 5
#DTM_GETSYSTEMTIME = #DTM_FIRST + 1
#DTM_SETSYSTEMTIME = #DTM_FIRST + 2
#GDT_VALID = 0
;--> Exif Tag constants
#Exif_DT_Modified = $132 ; 306
#Exif_Sub = $8769 ; 34665
#Exif_DT_Taken = $9003 ; 36867
#Exif_DT_Digitized = $9004 ; 36868
#Exif_Thumb_Offset = $201 ; 513
#Exif_Thumb_Length = $202 ; 514
#LittleEndian = $4949 ; 18761
;--> Image Header size and Array constants
#Header = 12
#ModifiedDate = 0
#TakenDate = 1
#DigitizedDate = 2
;--> Globals
Global dates
;--> Structures
Structure EXIF
eDate.s
eOffset.l
EndStructure
;--> Arrays
;--> This holds Exif data: The dates and their offsets
;--> We use this for writing new dates
Dim exifDate.EXIF(2)
;--> Procedure to reset Display
Procedure DisplayReset()
SetGadgetState(#ImageGad_Thumb, UseImage(#Image_No))
SetGadgetText(#Text_Modified_Read, "")
SetGadgetText(#Text_Taken_Read, "")
SetGadgetText(#Text_Digitized_Read, "")
SetGadgetState(#Date_Modified_Write, 0)
SetGadgetState(#Date_Taken_Write, 0)
SetGadgetState(#Date_Digitized_Write, 0)
DisableGadget(#Button_Write, 1)
StatusBarText(#StatusBar_0, 0, "")
EndProcedure
;--> Procedure to set DateGadgets.
;--> PB doesn't support the seconds field so I use API
Procedure SetDate(gadget, date$)
st.SYSTEMTIME
st\wYear = Val(Left(date$, 4))
st\wMonth = Val(Mid(date$, 6, 2))
st\wDay = Val(Mid(date$, 9, 2))
st\wHour = Val(Mid(date$, 12, 2))
st\wMinute = Val(Mid(date$, 15, 2))
st\wSecond = Val(Mid(date$, 18, 2))
;--> Set the text
SendMessage_(GadgetID(gadget), #DTM_SETSYSTEMTIME, #GDT_VALID, st)
;--> Clear checkbox if All Dates is selected
If GetGadgetState(#Date_All_Write) <> 0
SetGadgetState(gadget, 0)
EndIf
EndProcedure
;--> Procedure for reading Exif Dates
Procedure GetExif(ExifLoc)
FileSeek(ExifLoc + #Header); + 2)
exifEntries = ReadWord()
For i = 1 To exifEntries
tag = ReadWord() &$FFFF
Select tag
Case #Exif_DT_Taken
;--> We'll need to reset Loc after reading date string, so mark it.
currentloc = Loc()
;--> By-pass tag type and length because we know it's
;--> Type = ASCII and Length = 20
FileSeek(Loc() + 6)
;--> Get the offset to Date Taken string
dateLoc = ReadLong()
;--> Move to that offset
FileSeek(dateLoc + #Header)
;--> Put data into our Structure for future use
exifDate(#TakenDate)\eOffset = Loc()
exifDate(#TakenDate)\eDate = ReadString()
;--> Display Date Taken
SetGadgetText(#Text_Taken_Read, exifDate(#TakenDate)\eDate)
SetDate(#Date_Taken_Write, exifDate(#TakenDate)\eDate)
dates = #True
;--> Move back to next tag
FileSeek(currentloc + 10)
Case #Exif_DT_Digitized
;--> We'll need to reset Loc after reading date string, so mark it.
currentloc = Loc()
;--> By-pass tag type and length because we know it's
;--> Type = ASCII and Length = 20
FileSeek(Loc() + 6)
;--> Get the offset to Date Digitized string
dateLoc = ReadLong()
;--> Move to that offset
FileSeek(dateLoc + #Header)
;--> Put data into our Structure for future use
exifDate(#DigitizedDate)\eOffset = Loc()
exifDate(#DigitizedDate)\eDate = ReadString()
;--> Display Date Digitized
SetGadgetText(#Text_Digitized_Read, exifDate(#DigitizedDate)\eDate)
SetDate(#Date_Digitized_Write, exifDate(#DigitizedDate)\eDate)
dates = #True
;--> Move back to next tag
FileSeek(currentloc + 10)
Default
;--> We're skipping tags so move on to the next one
FileSeek(Loc() + 10)
EndSelect
Next i
EndProcedure
;--> Proceure for reading thumbnail
Procedure GetThumb(thumbLoc)
;--> Go to thumbnail tags offset
FileSeek(thumbLoc + #Header)
;--> get total entries
entries = ReadWord()
;--> We're looking for thumbnail offset and size in bytes
For i = 1 To entries
tag = ReadWord() &$FFFF
Select tag
Case #Exif_Thumb_Offset
;--> Here's the starting offset for thumbnail
FileSeek(Loc() + 6)
thumbOffset = ReadLong()
Case #Exif_Thumb_Length
;--> here's the length of thumbnail in bytes
FileSeek(Loc() + 6)
thumbLength = ReadLong()
Default
;--> We're skipping some tags so move on to the next one
FileSeek(Loc() + 10)
EndSelect
Next i
FileSeek(thumbOffset + #Header)
;--> Read the thumbnail data into memory
*thumb = AllocateMemory(thumbLength)
ReadData(*thumb, thumbLength)
;--> Catch the thumbnail and put it into ImageGadget
CatchImage(#Image_Thumb, *thumb)
SetGadgetState(#ImageGad_Thumb, ImageID())
FreeMemory(*thumb)
EndProcedure
Procedure GetInfo(jpg$, writeIt, newDateTime$)
OpenFile(0, jpg$)
;--> Byte 0 of EXIF begins after JPEG header
FileSeek(#Header)
;--> Bytes 0-1 is word order 18761 ($4949) is Intel and 19789 ($4D4D) is Motorola
byteOrder = ReadWord()
;--> For now I only handle Little Endian
If byteOrder = #LittleEndian
; --> Bytes 2-3 is TIFF format, it's always 42 ($2A). If not, give up.
tifFormat = ReadWord()
;--> This is always $2A. If not, give up.
If tifFormat = $2A
;--> Bytes 4-7 is starting offset for IFD (Image File Directory)
ifd1 = ReadLong()
;--> Move to start of IFD
FileSeek(ifd1 + #Header)
;--> First 2 bytes of IFD is number of field entries
nFields = ReadWord()
;--> Loop through all fields to find Date/Time stamp
For i = 1 To nFields
;--> Bytes 0-1 contain the Tag for the field.
currentTag = ReadWord() &$FFFF
Select currentTag
Case #Exif_DT_Modified
;--> Bytes 2-3 contain the field Type.
;--> We know this will be 2 (ASCII) For Date/Time
fieldType = ReadWord()
;--> Bytes 4-7 contain the Length of the field.
fieldLength = ReadLong()
;--> We'll need to reset Loc after reading date string, so mark it.
currentloc = Loc()
;--> Bytes 8-11 contain a pointer to ASCII Date/Time
fieldValue = ReadLong()
;--> Move to that pointer
FileSeek(fieldValue + #Header)
;--> This is the start offset of Date/Time ASCII string
;--> Put data into our Structure for future use
exifDate(#ModifiedDate)\eOffset = Loc()
exifDate(#ModifiedDate)\eDate = ReadString()
;--> Display date
SetGadgetText(#Text_Modified_Read, exifDate(#ModifiedDate)\eDate)
SetDate(#Date_Modified_Write, exifDate(#ModifiedDate)\eDate)
dates = #True
;--> Go back to next tag
FileSeek(currentloc + 4)
Case #Exif_Sub
;--> Here's the offest to more Exif data tags
FileSeek(Loc() + 6)
exifStartLoc = ReadLong()
Default
;--> Move to next field. Each field is 12 bytes.
;--> currentTag (2 bytes) is current Loc() so we add 10
FileSeek(Loc() + 10)
EndSelect
Next i
;--> Offset to thumbnail is after the last tag
thumbStart = ReadLong()
;--> Go to Exif offset and get dates
GetExif(exifStartLoc)
;--> Go to Thumbnail offset and read Thumbnail
GetThumb(thumbStart)
;--> All done
CloseFile(0)
exifResult = 1
Else
;--> Wrong format, display Unavailable
DisplayReset()
exifResult = 0
EndIf
Else
;--> Wrong byte order, display Unavailable
DisplayReset()
exifResult = 0
EndIf
ProcedureReturn exifResult
EndProcedure
;--> Create image to display when no Exif found
CreateImage(#Image_No, 160, 120)
StartDrawing(ImageOutput())
DrawingMode(1)
FrontColor(255, 0, 0)
Locate(28, 50)
DrawText("Thumbnail n/a")
StopDrawing()
;--> Main window with gadgets
If OpenWindow(#Window_Main, 0, 0, 465, 500, #PB_Window_SystemMenu | #PB_Window_ScreenCentered, "Exif Dates") And CreateGadgetList(WindowID(#Window_Main))
hSB = CreateStatusBar(#StatusBar_0, WindowID(#Window_Main))
AddStatusBarField(WindowWidth())
SendMessage_(hSB, #SB_GETRECT, 0, @sbRect.RECT)
statusbarHeight = sbRect\bottom - sbRect\top
ExplorerTreeGadget(#ExplorerTree_0, 0, 0, 220, 500 - statusbarHeight, "*.jpg;*.jpeg")
SetWindowLong_(GadgetID(#ExplorerTree_0), #GWL_STYLE, GetWindowLong_(GadgetID(#ExplorerTree_0), #GWL_STYLE) | #TVS_SHOWSELALWAYS)
;--> Thumbnail
Frame3DGadget(#Frame3D_Thumb, 230, 10, 225, 145, "Thumbnail")
ImageGadget(#ImageGad_Thumb, 265, 25, 160, 120, UseImage(#Image_No))
;--> Date Modified
Frame3DGadget(#Frame3D_Modified, 230, 160, 225, 75,"Date Modified")
TextGadget(#Text_Modified_Read, 240, 180, 100, 22, "")
TextGadget(#Text_New_0, 240, 210, 50, 22, "Change to")
DateGadget(#Date_Modified_Write, 300, 205, 145, 22, "", Date(), #PB_Date_CheckBox|#PB_Date_UpDown)
;--> Date Taken
Frame3DGadget(#Frame3D_Taken, 230, 240, 225, 75, "Date Taken")
TextGadget(#Text_Taken_Read, 240, 260, 100, 22, "")
TextGadget(#Text_New_1, 240, 290, 50, 22, "Change to")
DateGadget(#Date_Taken_Write, 300, 285, 145, 22, "", Date(), #PB_Date_CheckBox | #PB_Date_UpDown)
;--> Date Digitized
Frame3DGadget(#Frame3D_Digitized, 230, 320, 225, 75, "Date Digitized")
TextGadget(#Text_Digitized_Read, 240, 340, 100, 22, "")
TextGadget(#Text_New_2, 240, 370, 50, 22, "Change to")
DateGadget(#Date_Digitized_Write, 300, 365, 145, 22, "", Date(), #PB_Date_CheckBox | #PB_Date_UpDown)
;--> All Dates
Frame3DGadget(#Frame3D_All, 230, 400, 225, 45, "All Dates")
TextGadget(#Text_New_3, 240, 420, 50, 22, "Change to")
DateGadget(#Date_All_Write, 300, 415, 145, 22, "", Date(), #PB_Date_CheckBox | #PB_Date_UpDown)
;--> Write dates
ButtonGadget(#Button_Write, 270, 455, 150, 20, "Write Selected Date(s)")
DisableGadget(#Button_Write, 1)
;--> Clear checkboxes
SetGadgetState(#Date_Modified_Write, 0)
SetGadgetState(#Date_Taken_Write, 0)
SetGadgetState(#Date_Digitized_Write, 0)
SetGadgetState(#Date_All_Write, 0)
;--> Set format for DateGadgets
dt$ = "yyyy':'MM':'dd HH':'mm':'ss"
SendMessage_(GadgetID(#Date_Modified_Write), #DTM_SETFORMATA, 0, @dt$)
SendMessage_(GadgetID(#Date_Taken_Write), #DTM_SETFORMATA, 0, @dt$)
SendMessage_(GadgetID(#Date_Digitized_Write), #DTM_SETFORMATA, 0, @dt$)
SendMessage_(GadgetID(#Date_All_Write), #DTM_SETFORMATA, 0, @dt$)
;-->Main Loop
quit = #False
Repeat
event = WaitWindowEvent()
Select event
Case #PB_Event_CloseWindow
If EventWindowID() = #Window_Main
quit = #True
EndIf
Case #PB_EventGadget
Select EventGadgetID()
Case #ExplorerTree_0
currentFile$ = GetGadgetText(#ExplorerTree_0)
fileExt$ = Left(LCase(GetExtensionPart(currentFile$)), 3)
If EventType() = #PB_EventType_Change And fileExt$ = "jpg"
dates = #False
exif = GetInfo(currentFile$, 0, "")
If dates
StatusBarText(#StatusBar_0, 0, currentFile$)
DisableGadget(#Button_Write, 0)
EndIf
Else
currentFile$ = ""
DisplayReset()
EndIf
Case #Button_Write
If currentFile$ And dates
If OpenFile(0, currentFile$)
For s = #Date_Modified_Write To #Date_Digitized_Write
If GetGadgetState(s) <> 0
newdate$ = GetGadgetText(s)
FileSeek(exifDate(s)\eOffset)
WriteString(newdate$)
ElseIf GetGadgetState(#Date_All_Write) <> 0
newdate$ = GetGadgetText(#Date_All_Write)
FileSeek(exifDate(s)\eOffset)
WriteString(newdate$)
EndIf
Next s
CloseFile(0)
Else
MessageRequester("Error", "Could not open requested file!", #MB_ICONEXCLAMATION)
EndIf
EndIf
;--> Sync date gadgets
;--> If All Dates is checked, dis-allow other DateGadgets being checked
Case #Date_Modified_Write
If GetGadgetState(#Date_All_Write) <> 0
SetGadgetState(#Date_Modified_Write, 0)
EndIf
Case #Date_Taken_Write
If GetGadgetState(#Date_All_Write) <> 0
SetGadgetState(#Date_Taken_Write, 0)
EndIf
Case #Date_Digitized_Write
If GetGadgetState(#Date_All_Write) <> 0
SetGadgetState(#Date_Digitized_Write, 0)
EndIf
Case #Date_All_Write
For g = #Date_Modified_Write To #Date_Digitized_Write
If GetGadgetState(g) <> 0
SetGadgetState(g, 0)
EndIf
If GetGadgetState(#Date_All_Write) <> 0
SetGadgetState(g, GetGadgetState(#Date_All_Write))
SetDate(g, GetGadgetText(#Date_All_Write))
EndIf
Next g
EndSelect
EndSelect
Until quit
EndIf
End