Исследование метаданных EXIF для формата изображений WebP

 

Я уже писал о разборе порций формата WebP, и сейчас продолжил исследование. После компиляции libwebp я нашел пару утилит - webpinfo показывает информацию на экране


webpmux - управление метаданными. Я извлек поля EXIF и начал изучать. Файлы начинаются одинаково - байты 4D, 4D, 00, 2A, ?? ?? ?? ?? ExifMeta. Стал искать описание и нашел одну статью.

Получается, что данные EXIF хранятся в формате TIFF. Дальнейшие поиски вывели на описание формата. Я написал код разбора данных и получил короткую часть без комментария.

TIFF состоит из данных: два  байта, обозначающих порядок байтов 4D 4D для Motorola, 49 49 для Intel.Следующее слово обозначает версию 2A или 2B. За ними хранится 32-битное слово смещение данных - 8 или 16 для моих файлов.

Следующие данные IFD(Image File Directories) - слово число тегов, массив тегов размером 12*число и смещение для следующего IFD. Если смещение равно 0, то это означает, что список закончен. В моем примере смещение равнялось 0, но был тег $8769, который указывал на следующее IFD.

Массив тегов состоит из слово TagID(идентификатор тега), слово DataType(тип данных), слово 32бит Data Count(длина), 32бит DataOffset(смещение данных). Список тегов отдельно.

Теперь код готов, но как его проверить? Я уже перепробовал разные варианты запросов к Google, но нашел один вариант - tiffdump.Скомпилировать не удалось, нашел гнутый вариант. Программа выплюнула только первую часть и остановилась. В командной строке указал смещение в файле и получил остатки. Вчера вечером нашел еще один вариант - tiffinfo(TIFF Tag Information Reader), который показывает подробную информацию, но не показывает текстовую информацию. Жаль, что исходник не опубликован. Сегодня я решил продолжить обработку тегов, меня интересовал тег $9286(UserComment):

Использовал описание, и получил текст.

;26.03.2025 8:55 startr parser
;27.03.2025 7:38 fix finale version
Global byteswap
Procedure mesg(m$)
  Debug m$
EndProcedure
; Procedure.l xchEndianL(e.l)
;   ProcedureReturn (e & $ff) << 24 + (e & $ff00) << 8 + (e >> 8) & $ff00 + (e >> 24) & $ff
; EndProcedure
; Procedure.u swap16(v.u)
;   ProcedureReturn ((v&255)<<8)|(((v&$FF00)>>8)&255)
; EndProcedure
Procedure.u fetchw(v.u)
  If byteswap
    v=((v&255)<<8)|(((v&$FF00)>>8)&255)
  EndIf
  ProcedureReturn v
EndProcedure
Procedure.l fetchl(e.l)
  If byteswap
    e=(e & $ff) << 24 + (e & $ff00) << 8 + (e >> 8) & $ff00 + (e >> 24) & $ff
  EndIf
  ProcedureReturn e
EndProcedure
Procedure.s gettype(td)
          ;  1 byte  8bit
          ;  2 ascii 8bit,null-terminated string
          ;  3 short 16bit unsigned
          ;  4 long  32bit unsigned
          ;  5 rational two 32bit unsigned integers
  Select td
      Case 1:t$="BYTE unsigned"
      Case 2:t$="ASCII 8bit, null terminate"
      Case 3:t$="SHORT 16bit unsigned"
      Case 4:t$="LONG 32bit unsigned"
      Case 5:t$="RATIONAL two 2bit unsigned"
      Case 6:t$="TIFF 6.0 SBYTE 8bit signed"
      Case 7:t$="TIFF 6.0 UNDEFINE 8bit byte"
      Case 8:t$="TIFF 6.0 SSHORT 16bit signed"
      Case 9:t$="TIFF 6.0 SLONG 32bit signed"
      Case 10:t$="TIFF 6.0 SRATIONAL two 32bit signed"
      Case 11:t$="TIFF 6.0 FLOAT 4-byte single-precision IEEE �oating-point value"
      Case 12:t$="TIFF 6.0 DOUBLE 8-byte double-precision IEEE �oating-point value"
    Default:
      t$="Unknown:"+Hex(td,#PB_Word)
  EndSelect
  ProcedureReturn t$
EndProcedure


f$="exif2.exif"
fs=FileSize(f$)
If fs>0
  *m=AllocateMemory(fs)
  If *m
    If ReadFile(0,f$)
      ReadData(0,*m,fs)
      CloseFile(0)
      ;https://www.media.mit.edu/pia/Research/deepview/exif.html
      byteswap=-1
      If PeekA(*m)=$49 And PeekA(*m+1)=$49 And PeekA(*m+2)=$2a And PeekA(*m+3)=$00;identifier
        byteswap=0
      ElseIf PeekA(*m)=$4D And PeekA(*m+1)=$4D And PeekA(*m+2)=$00 And PeekA(*m+3)=$2a
        byteswap=1
      EndIf
      If byteswap<>-1
        ofs=fetchl( PeekL(*m+4) );IFDOffset
        exitfd=0
        While ofs<fs
          NumDirEntries.u=fetchw(PeekU(*M+ofs));NumDirEntries
          ofs+2
          For i=1 To NumDirEntries
            
            TagId.u=fetchw( PeekU(*m+ofs) )
            DataType.u=fetchw( PeekU(*m+ofs+2) )
            DataCount=fetchl( PeekL(*m+ofs+4) )
            DataOffset=fetchl( PeekL(*m+ofs+8) )
            
            If DataOffset<fs-4 And DataOffset>0
            v$="v$="+Hex( fetchl( PeekL(*m+DataOffset) ),#PB_Long )
          Else
            v$="???"
          EndIf
            If tagid=$9286;UserComment
             ; ShowMemoryViewer(*m+DataOffset,DataCount+1)
; http://www.fifi.org/doc/jhead/exif-e.html
; 0x41,0x53,0x43,0x49,0x49,0x00,0x00,0x00':ASCII
              If PeekA(*m+DataOffset)=$41 And PeekA(*m+DataOffset+1)=$53 And PeekA(*m+DataOffset+2)=$43 And PeekA(*m+DataOffset+3)=$49  And PeekA(*m+DataOffset+4)=$49
                v$="text="+PeekS(*m+DataOffset+8,DataCount-7,#PB_Ascii)
              EndIf
; 0x4a,0x49,0x53,0x00,0x00,0x00,0x00,0x00':JIS
; 0x55,0x4e,0x49,0x43,0x4f,0x44,0x45,0x00':Unicode
; 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00':Undefined
            EndIf
            
            Debug "Tag:"+Hex(TagId)+",Type:("+Hex(DataType)+")"+gettype(DataType)+" ofs:"+Hex(DataOffset,#PB_Long)+",Len:"+Hex(DataCount,#PB_Long)+","+v$
            If tagid=$8769 Or tagid=$8825 Or tagid=$a005
              exitfd=DataOffset
              Break
            EndIf
            ofs+12
          Next i
          Debug "---IFD end---"
          If exitfd
            ofs=exitfd
            exitfd=0
          Else
            oo=fetchl( PeekL(*m+ofs) )
            ofs+4
            ;           If byteswap
            ;             oo=xchEndianL(oo)
            ;           EndIf
            If oo
              ofs=oo
            Else
              Break
            EndIf
          EndIf
          ;taglist-Array of Tags 12 bytes each
          ; word TagId
          ; word DataType
          ;NextIFDOffset-Offset to next IFD
        Wend
        Debug "*** Done ****"
      Else
        mesg("Bad format!")
      EndIf
    Else
      mesg("Read error!")
    EndIf;If ReadFile(0,f$)
    FreeMemory(*m)
  Else
    mesg("Memory error!")
  EndIf;If *m
Else
  mesg("Bad size:"+Str(fs))
EndIf;If fs>0

Полный разбор тегов я делать не буду. JPEG View умеет читать данные из WebP, да и нет особой необходимости. Разве что попробую сформировать поле с комментарием.

Файлы тут

Комментарии