JPG Comment

Share your advanced PureBasic knowledge/code with the community.
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

JPG Comment

Post by infratec »

Hi,

since it was needed to answer a coding question, I worked a bit on JPG comment.
Here is the result:
Save it as JPGComment.pbi

Code: Select all

Procedure.q JPGComment_Search(Filename$)
 
  Dim Buffer.a(1)
 
  Result.q = 0
   
  File = ReadFile(#PB_Any, Filename$)
  If File
    EOI = #False
    Length.l = 0
    FilePos.q = 0
    Repeat
      If ReadData(File, @Buffer(0), 2) = 2
        FilePos + 2
        If Buffer(0) = $FF
          ;Debug "Filepos: " + Hex(FilePos)
          ;Debug Hex(Buffer(1))
          Select Buffer(1)
            Case $D8  ; SOI
            Case $D9  ; EOI
              EOI = #True
            Case $DA  ; SOS
              EOI = #True
            Case $FE  ; COM
              Result = FilePos - 2             
              EOI = #True
            Default
              If ReadData(File, @Buffer(0), 2) = 2
                Length = Buffer(0) << 8 | Buffer(1)
                FilePos + Length
                FileSeek(File, FilePos)
              EndIf
          EndSelect
        EndIf
      EndIf
      If Eof(File)
        EOI = #True
      EndIf
    Until EOI
    CloseFile(File)
  EndIf
 
  ProcedureReturn Result
 
EndProcedure


Procedure.s JPGComment_Read(Filename$)
 
  Result$ = ""
 
  CommentPos.q = JPGComment_Search(Filename$)
  If CommentPos
    File = ReadFile(#PB_Any, Filename$)
    If File
      If FileSeek(File, CommentPos + 4)
        Result$ = ReadString(File)
      EndIf
      CloseFile(File)
    EndIf
  EndIf
 
  ProcedureReturn Result$
 
EndProcedure


Procedure JPGComment_Write(Filename$, Comment$)
 
  Result = #False
 
  File = ReadFile(#PB_Any, Filename$)
  If File
    Size = Lof(File)
    *Buffer = AllocateMemory(Size)
    If *Buffer
      If ReadData(File, *Buffer, Size) = Size
        CloseFile(File)
        If PeekU(*Buffer) = $D8FF
          
          ; to be a bit more conform
          InsertPos = 2
          If PeekU(*Buffer + 2) = $E0FF     ; APP0
            If PeekU(*Buffer + 6) = $464A   ; "JF"
              InsertPos = 4 + ((PeekA(*Buffer + 4) << 8) + PeekA(*Buffer + 5))
            EndIf
          EndIf
          
          File = CreateFile(#PB_Any, Filename$ + ".tmp")
          If File
            WriteData(File, *Buffer, InsertPos)
            WriteByte(File, $FF)
            WriteByte(File, $FE)
            CommentLength = Len(Comment$) + 2 + 1
            WriteByte(File, CommentLength >> 8)
            WriteByte(File, CommentLength & $FF)
            WriteString(File, Comment$, #PB_Ascii)
            WriteByte(File, $00)
            WriteData(File, *Buffer + InsertPos, Size - InsertPos)
            CloseFile(File)
            If DeleteFile(Filename$)
              If RenameFile(Filename$ + ".tmp", Filename$)
                Result = #True
              EndIf
            EndIf
          EndIf
        EndIf
      EndIf
      FreeMemory(*Buffer)
    EndIf
    If IsFile(File) : CloseFile(File) : EndIf
  EndIf
 
  ProcedureReturn Result
 
EndProcedure


Procedure JPGComment_Replace(Filename$, Comment$)
 
  Result = #False
 
  CommentPos.q = JPGComment_Search(Filename$)
  If CommentPos
    File = ReadFile(#PB_Any, Filename$)
    If File
      Size = Lof(File)
      *Buffer = AllocateMemory(Size)
      If *Buffer
        If ReadData(File, *Buffer, Size) = Size
          CloseFile(File)
          File = CreateFile(#PB_Any, Filename$ + ".tmp")
          If File
            WriteData(File, *Buffer, CommentPos)
            If Len(Comment$)
              WriteByte(File, $FF)
              WriteByte(File, $FE)
              NewCommentLength = Len(Comment$) + 2 + 1
              WriteByte(File, NewCommentLength >> 8)
              WriteByte(File, NewCommentLength & $FF)
              WriteString(File, Comment$, #PB_Ascii)
              WriteByte(File, $00)
            EndIf
            CommentLength = Len(PeekS(*Buffer + CommentPos + 4))
            WriteData(File, *Buffer + CommentPos + 4 + CommentLength + 1, Size - CommentPos - 4 - CommentLength - 1)
            CloseFile(File)
            If DeleteFile(Filename$)
              If RenameFile(Filename$ + ".tmp", Filename$)
                Result = #True
              EndIf
            EndIf
          EndIf
        EndIf
        FreeMemory(*Buffer)
      EndIf
      If IsFile(File) : CloseFile(File) : EndIf
    EndIf
  Else  ; no comment in jpg
    If Len(Comment$)  ; a valid comment
      If JPGComment_Write(Filename$, Comment$) : Result = #True : EndIf
    Else  ; no comment
      Result = #True
    EndIf
  EndIf
 
  ProcedureReturn Result
 
EndProcedure


Procedure JPGComment_Delete(Filename$)
 
  Result = JPGComment_Replace(Filename$, "")
 
  ProcedureReturn Result
 
EndProcedure



;JPGComment_Filename$ = "c:\tmp\1.jpg"
;Debug Hex(JPGComment_Search(JPGComment_Filename$))
;Debug JPGComment_Read(JPGComment_Filename$)
;Debug JPGComment_Write(JPGComment_Filename$, "Write")
;Debug JPGComment_Read(JPGComment_Filename$)
;Debug JPGComment_Replace(JPGComment_Filename$, "Replace")
;Debug JPGComment_Read(JPGComment_Filename$)
;Debug JPGComment_Delete(JPGComment_Filename$)
;Debug JPGComment_Read(JPGComment_Filename$)
And now the example:
Save it as JPGCommenter.pb

Code: Select all

IncludeFile "JPGComment.pbi"


Procedure AddComment(Directory$, Comment$)
 
  If ExamineDirectory(0, Directory$, "*.jpg")

    While NextDirectoryEntry(0)
      FileName$ = Directory$ + DirectoryEntryName(0)
      StatusBarText(0, 0, Filename$)
      JPGComment_Replace(Filename$, Comment$)
    Wend
   
  EndIf

EndProcedure


OpenWindow(0, 0, 0, 500, 150, "JPG Commenter V1.10", #PB_Window_MinimizeGadget|#PB_Window_ScreenCentered)

TextGadget(0, 10, 10, 50, 20, "Comment:")
StringGadget(1, 70, 10, 350, 20, "")
TextGadget(2, 10, 40, 50, 20, "Directory:")
TextGadget(3, 70, 40, 350, 20, "", #PB_Text_Border)
ButtonGadget(4, 440, 40, 50, 20, "...")
ButtonGadget(5, 200, 80, 100, 30, "Start")
DisableGadget(5, #True)

CreateStatusBar(0, WindowID(0))
AddStatusBarField(500)

Exit = #False
Directory$ = ""
Repeat
 
  Event = WaitWindowEvent()
 
  Select Event
    Case #PB_Event_Gadget
      Select EventGadget()
        Case 4
          Directory$ = PathRequester("Choose a directory", Directory$)
          If Len(Directory$)
            SetGadgetText(3, Directory$)
            DisableGadget(5, #False)
          Else
            SetGadgetText(3, "")
            DisableGadget(5, #True)
          EndIf
        Case 5
          DisableGadget(5, #True)
          AddComment(Directory$, GetGadgetText(1))
          DisableGadget(5, #False)
      EndSelect
    Case #PB_Event_CloseWindow
      Exit = #True
  EndSelect
 
Until Exit
Now it is also possible to remove the comment if you let the comment empty.

Have fun :mrgreen:

Bernd
Last edited by infratec on Mon Dec 05, 2011 8:00 pm, edited 3 times in total.
User avatar
electrochrisso
Addict
Addict
Posts: 989
Joined: Mon May 14, 2007 2:13 am
Location: Darling River

Re: JPG Comment

Post by electrochrisso »

Good One.
Thanks for the post good buddy. :)
PureBasic! Purely the best 8)
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: JPG Comment

Post by infratec »

Hi,

I completed the comment functions.
Now there are:

JPGComment_Search(JPGComment_Filename$)
JPGComment_Read(JPGComment_Filename$)
JPGComment_Write(JPGComment_Filename$, Comment$)
JPGComment_Replace(JPGComment_Filename$, Comment$)
JPGComment_Delete(JPGComment_Filename$)

The first post is modified.

Bernd
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: JPG Comment

Post by infratec »

A second example:
Save it as JPGCommentReader.pb

Code: Select all

IncludeFile "JPGComment.pbi"


Procedure ReadComments(Directory$)
 
  Result = 0
 
  If ExamineDirectory(0, Directory$, "*.jpg")
   
    While NextDirectoryEntry(0)
      FileName$ = Directory$ + DirectoryEntryName(0)
      Comment$ = JPGComment_Read(Filename$)
      AddGadgetItem(6, -1, GetFilePart(Filename$) + #LF$ + Comment$)
      Result + 1
    Wend
   
  EndIf
 
  ProcedureReturn Result
 
EndProcedure


Procedure Export(Directory$)
  Filename$ = ReplaceString(ProgramFilename(), "exe", "csv")
  File = OpenFile(#PB_Any, Filename$)
  If File
    If Lof(File) = 0
      Line$ = #DQUOTE$ + "Directory" + #DQUOTE$
      Line$ + ","
      Line$ + #DQUOTE$ + "Filename" + #DQUOTE$
      Line$ + ","
      Line$ + #DQUOTE$ + "Comment" + #DQUOTE$
      WriteStringN(File,  Line$)
    Else
      FileSeek(File, Lof(File))
    EndIf
    Count = CountGadgetItems(6) - 1
    For i = 0 To Count
      Line$ = #DQUOTE$ + Directory$ + #DQUOTE$
      Line$ + ","
      Line$ + #DQUOTE$ + GetGadgetItemText(6, i, 0) + #DQUOTE$
      Line$ + ","
      Line$ + #DQUOTE$ + GetGadgetItemText(6, i, 1)+ #DQUOTE$
      WriteStringN(File, Line$)
    Next i
    CloseFile(File)
    DisableGadget(7, #True)
  EndIf
EndProcedure


OpenWindow(0, 0, 0, 500, 340, "JPG CommentReader V1.20", #PB_Window_MinimizeGadget|#PB_Window_ScreenCentered)

TextGadget(2, 10, 40, 50, 20, "Directory:")
TextGadget(3, 70, 40, 350, 20, "", #PB_Text_Border)
ButtonGadget(4, 440, 40, 50, 20, "...")

ListIconGadget(6, 10, 130, 480, 200, "Filename", 200)
AddGadgetColumn(6, 1, "Comment", 480)

ButtonGadget(7, 200, 80, 100, 30, "Export")
DisableGadget(7, #True)


Exit = #False
Directory$ = ""
Repeat
 
  Event = WaitWindowEvent()
 
  Select Event
    Case #PB_Event_Gadget
      Select EventGadget()
        Case 4
          Directory$ = PathRequester("Choose a directory", Directory$)
          If Len(Directory$)
            SetGadgetText(3, Directory$)
            ClearGadgetItems(6)
            If ReadComments(Directory$)
              DisableGadget(7, #False)
            EndIf
          Else
            SetGadgetText(3, "")
          EndIf
        Case 7 : Export(Directory$)
      EndSelect
    Case #PB_Event_CloseWindow
      Exit = #True
  EndSelect
 
Until Exit
Bernd
Last edited by infratec on Mon Dec 05, 2011 1:45 pm, edited 2 times in total.
Lost
User
User
Posts: 63
Joined: Fri Dec 19, 2008 12:24 am
Location: Tasmania

Re: JPG Comment

Post by Lost »

infratec wrote: since it was needed to answer a coding question, I worked a bit on JPG comment.
http://www.canyoucrackit.co.uk/ ? :P
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: JPG Comment

Post by infratec »

Hi Lost,

where was the problem:

http://www.canyoucrackit.co.uk/soyoudidit.asp

:mrgreen: :mrgreen: :mrgreen:
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: JPG Comment

Post by infratec »

Hi,

a jpg viewer with comment functions:
Save it as JPGComment.pb

Code: Select all

IncludeFile "JPGComment.pbi"


Procedure ReadDirectory(Directory$, List DirList.s())
 
  Result = 0
 
  If ExamineDirectory(0, Directory$, "*.jpg")
    ClearList(DirList())
    While NextDirectoryEntry(0)
      FileName$ = Directory$ + DirectoryEntryName(0)
      AddElement(DirList())
      DirList() = FileName$
      Result + 1
    Wend
    
    If Result : FirstElement(DirList()) : EndIf
    
  EndIf
 
  ProcedureReturn Result
 
EndProcedure


Procedure ShowPicture(List DirList.s())
 
  SetGadgetText(13, "")
 
  StatusBarText(0, 0, Str(ListIndex(DirList()) + 1) + "/" + Str(ListSize(DirList())), #PB_StatusBar_Center)
  StatusBarText(0, 1, "Loading...", #PB_StatusBar_Center)
 
  If LoadImage(0, DirList())
   
    SetGadgetText(11, JPGComment_Read(DirList()))
   
    Width = ImageWidth(0)
    Height = ImageHeight(0)
    WidthFactor.f = 1.0
    If Width > 480 : WidthFactor = 480 / Width : EndIf
    HeightFactor.f = 1.0
    If Height > 360 : HeightFactor = 360 / Height : EndIf
    Factor.f = 1.0
    If WidthFactor < Factor : Factor = WidthFactor : EndIf
    If HeightFactor < Factor : Factor = HeightFactor : EndIf
   
    If Factor < 1.0
      StatusBarText(0, 1, "Resizing...", #PB_StatusBar_Center)
      ResizeImage(0, Width * Factor, Height * Factor)
    EndIf
   
    SetGadgetState(5, ImageID(0))   
    StatusBarText(0, 1, GetFilePart(DirList()), #PB_StatusBar_Center)
  EndIf
EndProcedure


Procedure DirHome(List DirList.s())
  If ListIndex(DirList()) <> -1
    FirstElement(DirList())
    ShowPicture(DirList())
  EndIf
EndProcedure

Procedure DirBackward(List DirList.s())
  If ListIndex(DirList()) <> -1
    If PreviousElement(DirList()) = 0 : LastElement(DirList()) : EndIf
    ShowPicture(DirList())
  EndIf
EndProcedure

Procedure DirForward(List DirList.s())
  If ListIndex(DirList()) <> -1
    If NextElement(DirList()) = 0 : FirstElement(DirList()) : EndIf
    ShowPicture(DirList())
  EndIf
EndProcedure

Procedure DirEnd(List DirList.s())
  If ListIndex(DirList()) <> -1
    LastElement(DirList())
    ShowPicture(DirList())
  EndIf
EndProcedure


Procedure SaveComment(List DirList.s())
  If ListIndex(DirList()) <> -1
    If JPGComment_Replace(DirList(), GetGadgetText(13))
      SetGadgetText(11, GetGadgetText(13))
      SetGadgetText(13, "")
    EndIf
  EndIf
EndProcedure




UseJPEGImageDecoder()


OpenWindow(0, 0, 0, 500, 550, "JPG Comment V1.11", #PB_Window_MinimizeGadget|#PB_Window_ScreenCentered)

TextGadget(2, 10, 40, 50, 20, "Directory:")
TextGadget(3, 70, 40, 350, 20, "", #PB_Text_Border)
ButtonGadget(4, 440, 40, 50, 20, "...")

CreateImage(0, 480, 360)
ImageGadget(5, 10, 70, 0, 0, ImageID(0), #PB_Image_Border)

ButtonGadget(6, 10, 440, 50, 20, "|<<")
ButtonGadget(7, 80, 440, 50, 20, "<")
ButtonGadget(8, 150, 440, 50, 20, ">")
ButtonGadget(9, 220, 440, 50, 20, ">>|")
DisableGadget(6, #True)
DisableGadget(7, #True)
DisableGadget(8, #True)
DisableGadget(9, #True)

TextGadget(10, 10, 470, 50, 20, "Comment:")
TextGadget(11, 70, 470, 350, 20, "", #PB_Text_Border)
;EditorGadget(11, 70, 470, 350, 20)

TextGadget(12, 10, 500, 50, 20, "New:")
StringGadget(13, 70, 500, 350, 20, "")
ButtonGadget(14, 430, 500, 50, 20, "Save")
DisableGadget(13, #True)
DisableGadget(14, #True)

CreateStatusBar(0, WindowID(0))
AddStatusBarField(100)
AddStatusBarField(400)
StatusBarText(0, 0, "0/0", #PB_StatusBar_Center)
StatusBarText(0, 1, "no jpg pictures", #PB_StatusBar_Center)

AddKeyboardShortcut(0, #PB_Shortcut_Home, 6)
AddKeyboardShortcut(0, #PB_Shortcut_Left, 7)
AddKeyboardShortcut(0, #PB_Shortcut_Right, 8)
AddKeyboardShortcut(0, #PB_Shortcut_End, 9)

NewList DirList.s()


Exit = #False
CurrentDirectory$ = ""
Repeat
 
  Event = WaitWindowEvent()
 
  Select Event
    Case #PB_Event_Menu
      Select EventMenu()
        Case 6 : DirHome(DirList())
        Case 7 : DirBackward(DirList())
        Case 8 : DirForward(DirList())
        Case 9 : DirEnd(DirList())
      EndSelect
    Case #PB_Event_Gadget
      Select EventGadget()
        Case 4
          Directory$ = PathRequester("Choose a directory", CurrentDirectory$)
          If Len(Directory$)
            CurrentDirectory$ = Directory$
            SetGadgetText(3, CurrentDirectory$)
            If  ReadDirectory(CurrentDirectory$, DirList())
              ShowPicture(DirList())
              DisableGadget(6, #False)
              DisableGadget(7, #False)
              DisableGadget(8, #False)
              DisableGadget(9, #False)
              DisableGadget(13, #False)
              DisableGadget(14, #False)
            Else
              StatusBarText(0, 0, "0/0", #PB_StatusBar_Center)
              StatusBarText(0, 1, "no jpg pictures", #PB_StatusBar_Center)
              DisableGadget(6, #True)
              DisableGadget(7, #True)
              DisableGadget(8, #True)
              DisableGadget(9, #True)
              DisableGadget(13, #True)
              DisableGadget(14, #True)
            EndIf
          EndIf
        Case 6 : DirHome(DirList())
        Case 7 : DirBackward(DirList())
        Case 8 : DirForward(DirList())
        Case 9 : DirEnd(DirList())
        Case 14 : SaveComment(DirList())
      EndSelect
    Case #PB_Event_CloseWindow
      Exit = #True
  EndSelect
 
Until Exit
Have fun,

Bernd
Last edited by infratec on Mon Dec 05, 2011 9:35 pm, edited 2 times in total.
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: JPG Comment

Post by infratec »

Hi,

I updated the JPGCommentReader.pb listing to V1.10

Now you can export the ListIconGadget() to a CSV file.
So you can easily archive your pictures in a spreadsheet (I prefer calc from LibreOffice)

If you run it on different directories the result is attached to the csv file.

Bernd
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: JPG Comment

Post by infratec »

Hi,

I updated the JPGCommenter.pb listing to V1.11
The PathRequester() starts now with the last directory.
Clear the list when read a new directory.

I updated the JPGCommentReader.pb listing to V1.20
The PathRequester() starts now with the last directory.
The comment column is now larger.

I updated the JPGComment.pb listing to V1.10
The PathRequester() starts now with the last directory.

Bernd
Last edited by infratec on Mon Dec 05, 2011 9:36 pm, edited 1 time in total.
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: JPG Comment

Post by infratec »

Hi,

I think that's my last improvement:

I made JPGComment.pbi a bit more 'conform'.
Older applications may check the signature of the JPG file in an 'old' way.
So when the comment is directly behind the FFD8 (SOI) the detection will fail.

When I now detect the 'original' signature at the file start, I add the comment behind.
If I didn't found the 'original' signature I add it like before directly behind the SOI.

I also found one application which inserts more than one COMment.
I found no spec in the net about this.

But if you need to read 'more' comments, look at
http://www.purebasic.fr/english/viewtop ... 94&start=0
(that's the thread why you can read this :mrgreen: )

There I put the comments in a list.

Best regards,

Bernd
Post Reply