Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Zip Thumbnails (Updated:version 4.6.2, 18.4.2011)


  • Please log in to reply
93 replies to this topic
EdwinD
  • Guests
  • Last active:
  • Joined: --

...
Also, the question is if e-books (*.epub....) fall under this topic, because it seems to me that it should be a project for itself, from my experience cb* files are more similar to archives then are e-books which only use compressed file as a container (wikipedia topic on ebooks talks about html, xml, svg, etc.).
...


Thx for adding some epub support anyway. :D It took me some time to get the registry keys in order but it works like a charm now (The keys didn't work when I put them at HKCR\.epub but it worked when I added them at HKCR\Adobe.EPUB which it pointed to).

maraskan_user
  • Members
  • 52 posts
  • Last active: Dec 08 2014 11:18 PM
  • Joined: 20 Jun 2008
CBXShell seems to extract an archived image file in memory after reading the archives index. Is it possible to use the same technique in AutoHotkey via dllcall, in order to use this image with ahk's GDIp library?

Edit:

I took a quick shot at it and wrote a few lines to parse non-compressed zip files and extract images in memory. Having no decompression might seem silly, but I tend to not compress cbz files since jpg/png images tend to have very bad compression ratios anyway.

You tell it the full path of the archive and the name of the file you want extracted, and it will show it as thumbnail for 3 seconds.


#SingleInstance,Force
#NoEnv
SetBatchLines,-1

;########################################################################################################################
; Set path to non-compressed zip file and contained image file

   ZipPath=E:\Temp\asf.zip
   WantedFile=100.jpg

;########################################################################################################################

   Zip_ReadFile(ZipPath,WantedFile)                     ; Read WantedFile into WantedFileContent variable
   ShowImage(WantedFileContent,LFH_DataLen)             ; Show thumbnail of WantedFileContent somewhere
   sleep,3000
   ;BinWriteSimple(A_ScriptDir "\" WantedFile,WantedFileContent,LFH_DataLen)   ; Use for debugging purposes
   Exitapp











;########################################################################################################################

   Zip_ReadFile(ZipPath,WantedFile)
      {
      global ZipSize,ZipContent,WantedFileContent,LFH_DataLen

      ;FileGetSize,ZipSize,%ZipPath%
      ;VarSetCapacity(ZipContent,ZipSize+1,0)
      ;FileRead,ZipContent,%ZipPath%

      ;#######################################################################
      ; Read 'End of Central Directory' header to find central directory offset

      VarSetCapacity(ZipContent,65,0)
      ZipSize:=BinReadSimple(ZipPath,ZipContent,-64,64)
      
      CDend=0x50|0x4B|0x05|0x06|
      Pos_CDend:=BinGetPos(CDend,0)                      ; 4 - End of central directory signature = 0x06054b50
      CDend_4:=NumGet(ZipContent,Pos_CDend+4,"UShort")   ; 2 - Number of this disk
      CDend_6:=NumGet(ZipContent,Pos_CDend+6,"UShort")   ; 2 - Disk where central directory starts
      CDend_8:=NumGet(ZipContent,Pos_CDend+8,"UShort")   ; 2 - Number of central directory records on this disk
      CDend_10:=NumGet(ZipContent,Pos_CDend+10,"UShort") ; 2 - Total number of central directory records
      CDend_12:=NumGet(ZipContent,Pos_CDend+12,"UInt")   ; 4 - Size of central directory (bytes)
      CDend_16:=NumGet(ZipContent,Pos_CDend+16,"UInt")   ; 4 - Offset of start of central directory, relative to start of archive
      CDend_20:=NumGet(ZipContent,Pos_CDend+20,"UShort") ; 2 - Zip file comment length (n)
      if CDend_20>0
         {
         CDend_22=
         loop,CDend_20
            {
            ThisByte:=NumGet(ZipContent,Pos_CDend+22+(A_Index-1),"UChar")
            ThisChar:=Chr(ThisByte)
            CDend_22=%CDend_22%%ThisChar%      ; n - Zip file comment
            }
         }

      ;#######################################################################
      ; Parse file list and check if the WantedFile exists in archive, and at which offset

      VarSetCapacity(ZipContent,CDend_12+1,0)
      ZipSize:=BinReadSimple(ZipPath,ZipContent,CDend_16,CDend_12)

      CD=0x50|0x4b|0x01|0x02|
      Pos_CD:=0               ; We start at the position specified by the 'End of central directory'
      loop
         {
         Pos_CD:=BinGetPos(CD,Pos_CD)            ; 4 - Central directory file header signature = 0x02014b50
         if Pos_CD=-1
            break                  ; Break loop if there are no more central directory entries

         CD_4:=NumGet(ZipContent,Pos_CD+4,"UShort")      ; 2 - Version made by
         CD_6:=NumGet(ZipContent,Pos_CD+6,"UShort")      ; 2 - Version needed to extract (minimum)
         CD_8:=NumGet(ZipContent,Pos_CD+8,"UShort")      ; 2 - General purpose bit flag
         CD_10:=NumGet(ZipContent,Pos_CD+10,"UShort")    ; 2 - Compression method
         CD_12:=NumGet(ZipContent,Pos_CD+12,"UShort")    ; 2 - File last modification time
         CD_14:=NumGet(ZipContent,Pos_CD+14,"UShort")    ; 2 - File last modification date
         CD_16:=NumGet(ZipContent,Pos_CD+16,"UInt")      ; 4 - CRC-32
         CD_20:=NumGet(ZipContent,Pos_CD+20,"UInt")      ; 4 - Compressed size
         CD_24:=NumGet(ZipContent,Pos_CD+24,"UInt")      ; 4 - Uncompressed size
         CD_28:=NumGet(ZipContent,Pos_CD+28,"UShort")    ; 2 - File name length (n)
         CD_30:=NumGet(ZipContent,Pos_CD+30,"UShort")    ; 2 - Extra field length (m)
         CD_32:=NumGet(ZipContent,Pos_CD+32,"UShort")    ; 2 - File comment length (k)
         CD_34:=NumGet(ZipContent,Pos_CD+34,"UShort")    ; 2 - Disk number where file starts
         CD_36:=NumGet(ZipContent,Pos_CD+36,"UShort")    ; 2 - Internal file attributes
         CD_38:=NumGet(ZipContent,Pos_CD+38,"UInt")      ; 4 - External file attributes
         CD_42:=NumGet(ZipContent,Pos_CD+42,"UInt")      ; 4 - Relative offset of local file header

         if CD_28>0
            {
            CD_46=
            loop,%CD_28%
               {
               ThisByte:=NumGet(ZipContent,Pos_CD+46+(A_Index-1),"UChar")
               ThisChar:=Chr(ThisByte)
               CD_46=%CD_46%%ThisChar%         ; n - File name
               }
            }
         
         if CD_30>0
            {
            CD_46n=
            loop,%CD_30%
               {
               ThisByte:=NumGet(ZipContent,Pos_CD+46+CD_28+(A_Index-1),"UChar")
               ThisChar:=Chr(ThisByte)
               CD_46n=%CD_46n%%ThisChar%      ; m - Extra field
               }
            }

         if CD_32>0
            {
            CD_46nm=
            loop,%CD_32%
               {
               ThisByte:=NumGet(ZipContent,Pos_CD+46+CD_28+CD_30+(A_Index-1),"UChar")
               ThisChar:=Chr(ThisByte)
               CD_46nm=%CD_46nm%%ThisChar%      ; k - File comment
               }
            }
         

         if instr(CD_46,WantedFile,0,1)
            break

         ;Move search offset for next loop
         Pos_CD:=Pos_CD+4
         }

      if (instr(CD_46,WantedFile,0,1)=0)
         {
         msgbox,,%A_ScriptName% (line %A_LineNumber%),Could not find file '%WantedFile%' in archive.
         Return,1
         }

      if (CD_10!=0)||(CD_20!=CD_24)
         {
         msgbox,,%A_ScriptName% (line %A_LineNumber%),File '%WantedFile%' is compressed.`nCan't continue.
         Return,1
         }

      ;#######################################################################
      ; Read at WantedFile offset position of LocalFileHeader

      LocalFileHeader=0x50|0x4b|0x03|0x04|

      Pos_LFH:=CD_42     ; 4 - Relative offset of local file header
      LFH_10:=CD_10      ; 2 - Compression method
      LFH_20:=CD_20      ; 4 - Compressed size
      LFH_24:=CD_24      ; 4 - Uncompressed size
      LFH_26:=CD_28      ; 2 - File name length (n)
      LFH_28:=CD_30      ; 2 - Extra field length (m)

      LFH_DataStart:=Pos_LFH+30+LFH_26+LFH_28
      LFH_DataLen:=LFH_20
      
      VarSetCapacity(WantedFileContent,LFH_DataLen+1,0)
      BinReadSimple(ZipPath,WantedFileContent,LFH_DataStart,LFH_DataLen)
      gosub,ShowDebugData
      Return,0
      
      ShowDebugData:
         result=
(
Pos_CDend`t%Pos_CDend%
CDend+4`t`t%CDend_4%`t2 - Number of this disk
CDend+6`t`t%CDend_6%`t2 - Disk where central directory starts
CDend+8`t`t%CDend_8%`t2 - Number of central directory records on this disk
CDend+10`t%CDend_10%`t2 - Total number of central directory records
CDend+12`t%CDend_12%`t4 - Size of central directory (bytes)
CDend+16`t%CDend_16%`t4 - Offset of start of central directory, relative to start of archive
CDend+20`t%CDend_20%`t2 - Zip file comment length (n)
CDend+22`t%CDend_22%`tn - Zip file comment

Pos_CD`t`t%Pos_CD%
CD+4`t`t%CD_4%`t2 - Version made by
CD+6`t`t%CD_6%`t2 - Version needed to extract (minimum)
CD+8`t`t%CD_8%`t2 - General purpose bit flag
CD+10`t`t%CD_10%`t2 - Compression method
CD+12`t`t%CD_12%`t2 - File last modification time
CD+14`t`t%CD_14%`t2 - File last modification date
CD+16`t`t%CD_16%`t4 - CRC-32
CD+20`t`t%CD_20%`t4 - Compressed size
CD+24`t`t%CD_24%`t4 - Uncompressed size
CD+28`t`t%CD_28%`t2 - File name length (n)
CD+30`t`t%CD_30%`t2 - Extra field length (m)
CD+32`t`t%CD_32%`t2 - File comment length (k)
CD+34`t`t%CD_34%`t2 - Disk number where file starts
CD+36`t`t%CD_36%`t2 - Internal file attributes
CD+38`t`t%CD_38%`t4 - External file attributes
CD+42`t`t%CD_42%`t4 - Relative offset of local file header
CD+46`t`t%CD_46%`tn - File name
CD+46+n`t%CD_46n%`tm - Extra field
CD+46+n+m`t%CD_46nm%`tk - File comment

LFH_Pos`t`t%LFH_Pos% ; 4 - Relative offset of local file header
LFH_10`t`t%LFH_10%   ; 2 - Compression method
LFH_20`t`t%LFH_20%   ; 4 - Compressed size
LFH_24`t`t%LFH_24%   ; 4 - Uncompressed size
LFH_26`t`t%LFH_26%   ; 2 - File name length (n)
LFH_28`t`t%LFH_28%   ; 2 - Extra field length (m)

LFH_DataStart`t`t%LFH_DataStart%
LFH_DataLen`t`t%LFH_DataLen%
)
         msgbox,%result%
         Return
      }

;########################################################################################################################

   BinGetPos(CurrText,StartOffset=0)
      {
      global ZipContent,ZipSize
      SearchStrLen := StrLen(CurrText)
      SearchStrLenUni := VarSetCapacity(SearchStrUni,SearchStrLen/5,0)
      Loop,Parse,CurrText,|
         {
         if A_LoopField=
            break
         ;msgbox,%A_LoopField%
         NumPut(A_LoopField,SearchStrUni,(A_Index-1),"UChar") 
         }
      ;msgbox,%SearchStrUni%
      Return,InBuf(&ZipContent,&SearchStrUni,ZipSize,SearchStrLenUni,StartOffset)
      }

;########################################################################################################################
; Code by wOxxOm

   InBuf(haystackAddr, needleAddr, haystackSize, needleSize, StartOffset=0)
      {
      Static fun
      IfEqual,fun,
         {
         h=
( LTrim join
5589E583EC0C53515256579C8B5D1483FB000F8EC20000008B4D108B451829C129D9410F8E
B10000008B7D0801C78B750C31C0FCAC4B742A4B742D4B74364B74144B753F93AD93F2AE0F
858B000000391F75F4EB754EADF2AE757F3947FF75F7EB68F2AE7574EB628A26F2AE756C38
2775F8EB569366AD93F2AE755E66391F75F7EB474E43AD8975FC89DAC1EB02895DF483E203
8955F887DF87D187FB87CAF2AE75373947FF75F789FB89CA83C7038B75FC8B4DF485C97404
F3A775DE8B4DF885C97404F3A675D389DF4F89F82B45089D5F5E5A595BC9C2140031C0F7D0EBF0
)
         VarSetCapacity(fun,StrLen(h)//2)
         Loop % StrLen(h)//2
            NumPut("0x" . SubStr(h,2*A_Index-1,2), fun, A_Index-1, "Char")
         }
      Return DllCall(&fun
         , "uint",haystackAddr, "uint",needleAddr
         , "uint",haystackSize, "uint",needleSize
         , "uint",StartOffset)
      }

;########################################################################################################################
; Mangled versions of Laszlo's original functions

   BinWriteSimple(file,ByRef data,datalen)
      {
      h := DllCall("CreateFile","str",file,"Uint",0x40000000,"Uint",0,"UInt",0,"UInt",4,"Uint",0,"UInt",0)
      If (h=-1)||(ErrorLevel!=0)
         Return,1
      r := DllCall("SetFilePointerEx","Uint",h,"Int64",0,"UInt *",p,"Int",0)
      If (r=0)||(ErrorLevel!=0)
         {
         DllCall("CloseHandle", "Uint", h)
         Return,1
         }

      DllCall("WriteFile","UInt",h,"str",data,"UInt",datalen,"UInt *",Written,"UInt",0)
      DllCall("CloseHandle", "Uint", h)
      Return
      }

   BinReadSimple(file,ByRef data,offset=0, n=0)
      {
      h := DllCall("CreateFile","Str",file,"Uint",0x80000000,"Uint",3,"UInt",0,"UInt",3,"Uint",0,"UInt",0)
      If (h=-1)||(ErrorLevel!=0)
         Return,1
      m = 0                  ; seek to offset
      If offset<0
         m:=2

      r := DllCall("SetFilePointerEx","Uint",h,"Int64",offset,"UInt *",p,"Int",m)
      If (r=0)||(ErrorLevel!=0)
         {
         DllCall("CloseHandle", "Uint", h)
         Return,1
         }

      If n=0
         n:=0xffffffff            ; almost infinite

      DllCall("ReadFile","UInt",h,"str",data,"UInt",n,"UInt *",BytesRead,"UInt",0)
      DllCall("CloseHandle", "Uint", h)
      Return,BytesRead
      }

;########################################################################################################################
; Don't know whose script this was based on. v_v

   ShowImage(byref Buffer,nSize)
      {
      Global pToken

      if pToken
         {
         Gdip_Shutdown(pToken)
         Gui,13: Destroy
         pToken=
         }

      pToken:=Gdip_Startup()

      hData:=DllCall("GlobalAlloc","UInt",2,"UInt",nSize)
      pData:=DllCall("GlobalLock","UInt",hData)
      DllCall("RtlMoveMemory","UInt",pData,"UInt",&Buffer,"UInt",nSize)
      DllCall("GlobalUnlock","UInt",hData)
      DllCall("ole32\CreateStreamOnHGlobal","UInt",hData,"Int",1,"uint*",pStream)

      DllCall("gdiplus\GdipCreateBitmapFromStream","UInt",pStream,"uint*",pBitmap)
      If !pBitmap=
         {
         Gdip_Shutdown(pToken)
         PrevThumbFile=
         pToken=
         Return
         }

      Gui,13: +Caption +E0x80000 +LastFound +ToolWindow +OwnDialogs
      Gui,13: Show,NA,ThumbIcon
      hwnd1 := WinExist(ThumbIcon ahk_class AutoHotkeyGUI)

      SysGet,MonitorWorkArea,MonitorWorkArea
      if MonitorWorkAreaRight<1280
         Thumbsize=200
      else
         Thumbsize=300

      Width := Gdip_GetImageWidth(pBitmap)
      Height := Gdip_GetImageHeight(pBitmap)
      if (Width > Height)
         Ratio := Width/Thumbsize, TargetW := Thumbsize, TargetH := Height/Ratio
      else
         Ratio := Height/Thumbsize, TargetW :=Width/Ratio, TargetH :=Thumbsize

      hbm := CreateDIBSection(TargetW, TargetH)
      hdc := CreateCompatibleDC()
      obm := SelectObject(hdc, hbm)
      G := Gdip_GraphicsFromHDC(hdc)

      Gdip_SetInterpolationMode(G,7)
      Gdip_DrawImage(G, pBitmap, 0, 0, TargetW, TargetH, 0, 0, Width, Height)

      GF_X := (MonitorWorkAreaRight/2)-278-TargetW
      GF_Y := (MonitorWorkAreaBottom/2)-(TargetH/2)
      UpdateLayeredWindow(hwnd1, hdc, GF_X, GF_Y, TargetW, TargetH)

      SelectObject(hdc, obm)
      DeleteObject(hbm)
      DeleteDC(hdc)
      Gdip_DeleteGraphics(G)
      Gdip_DisposeImage(pBitmap)
      Return
      }


Edit: Used with another 320mph like application of mine, it looks like this. ^^

Posted Image

T800
  • Members
  • 107 posts
  • Last active: Feb 01 2014 11:14 PM
  • Joined: 15 Oct 2006
You can try this small dll so you don't have to deal with COM interfaces directly.
See included source code for all details, I haven't tested it with Autohotkey, it's too late (1.30 am, gotta go to work tomorrow :wink: ).
<!-- m -->https://ahknet.autoh...CBX/cbxstub.zip<!-- m -->

maraskan_user
  • Members
  • 52 posts
  • Last active: Dec 08 2014 11:18 PM
  • Joined: 20 Jun 2008

You can try this small dll so you don't have to deal with COM interfaces directly.
See included source code for all details

Would it be used like this?
ArchiveName=D:\Test.cbz
hBitmap:=DllCall( "D:\Common\CBXshell\cbxstub.dll\ExtractThumbnail", "Str", ArchiveName, "UInt", 300, "UInt", 300)
It's returning 0 though. (CBXshell itself is properly registered and works.)

T800
  • Members
  • 107 posts
  • Last active: Feb 01 2014 11:14 PM
  • Joined: 15 Oct 2006
Got it - as with any COM stuff, you must first initialize, in the same way as calling thread (match single/multithreaded), check msdn here, here, and here.

Try like this:
DllCall("Ole32.dll\CoInitialize", "UInt", 0)

ArchiveName=E:\Pictures\ux5678.cbz ;your test archive path here

hBitmap:=DllCall( "cbxstub.dll\ExtractThumbnail", "Str", ArchiveName, "UInt", 300, "UInt", 300)

MsgBox, %hBitmap%         ;let's see - shouldn't be null
DllCall( "Gdi32.dll\DeleteObject", "UInt", hBitmap)        ;cleanup

DllCall("Ole32.dll\CoUninitialize")

I played with the dll code a bit further (trial and error, chop and trim), but in essence it's the same thing (zipped project).
Tell me if it all works now, I don't have that much time for extensive testing myself, and I see you already got working applications.

maraskan_user
  • Members
  • 52 posts
  • Last active: Dec 08 2014 11:18 PM
  • Joined: 20 Jun 2008

Tell me if it all works now, I don't have that much time for extensive testing myself, and I see you already got working applications.


Ah, it was needing COM initialization! It works fine now. :)
Tested it with RAR and compressed /uncompressed ZIP files. It took ~ 80 ms to get a random image from a zip archive to display, and around 55 ms with rar. Compressed zips actually took almost exactly the same time as uncomressed ones.

For comparisons sake, the quick and dirty ahk solution script from before (which only works with uncompressed zips!) also gets the same image on screen in ~30-50 ms.
So I'm going to use cbxshell for compressed files to get just the cover image, and, if it's an uncompressed zip, use the ahk solution to get cover as well as chapter images. :D

T800
  • Members
  • 107 posts
  • Last active: Feb 01 2014 11:14 PM
  • Joined: 15 Oct 2006
I uploaded version 4.6.
Now components are correctly registered and Windows2000 needs only Internet Explorer 6 as prerequisite.
I also tried to make help file less eye-pain, but that's really 'job for a cowboy'. :roll:

gazaarin
  • Members
  • 1 posts
  • Last active: Sep 03 2010 06:43 PM
  • Joined: 03 Sep 2010
My apologies if I'm asking something thats already been answered. I can't get the program to work. I do not have cdisplay or cdisplayex installed. I do not see CBXManager64.exe orr CBXShell64.dll in the directory. I have installed as administrator. I've installed the 4.6 release. Is there a specific download for the x64 files that I missed? Any suggestions or help appreciated.

T800
  • Members
  • 107 posts
  • Last active: Feb 01 2014 11:14 PM
  • Joined: 15 Oct 2006
Just for the record, there's a small info.txt file in the download package with my mail on which bug/problems reports can be sent (be sure to put correct message subject).

You might wanna check this thread/previous posts.

Can you be a little more specific? (accurate descriptions can save much time).
Installer will give the option to launch CBX Shell Manager upon succesful installation, or you can do it from start menu shortcut (if prompted to restart computer).
Clean install or upgrade?
Version 4.6 will correctly overwrite old installation.
Does it just fail to install or something else?
If installation fails, you can try with logging enabled, using /log command line switch. Open command prompt and type
<path of the installer> /log
It will create file named something like "Setup Log 2010-09-07 #001.txt" in your %TEMP% folder (type that in the address bar to go there). It's a text file with details of the install procedure. You can post it or mail it so I can see what it says.
I tested installer on Win7 x64 and it works as expected.
The installer contains 32 and 64-bit files, but they have identical names: CBXManager.exe/CBXShell.dll. If you check file properties in windows
explorer (also in the manager's about box), it will say if it's x86 or x64, eg. for dll: 'Image Archive Shell Extension 32-bit' or 'Image Archive
Shell Extension 64-bit'.

HAL 9000
  • Guests
  • Last active:
  • Joined: --
Hello T800 just wanted to thank you for CBX Shell it works great! I thought I would be googling forever to find something of its quality that was free but as it turns out it was at the top of the search results so I guess it is popular.

A note to future users with Windows 7: restart your computer for results to take effect.

tatt61880
  • Members
  • 1 posts
  • Last active: Jan 20 2011 07:45 PM
  • Joined: 27 Feb 2010
Hi T800,

Thank you for your software and your help. =)
I've sent you a PM just to make sure. ;)

Tatt

gamax92
  • Guests
  • Last active:
  • Joined: --
I had some program at one time that made a fake photo hanging out of the zipper. It was cool.

flanker37
  • Guests
  • Last active:
  • Joined: --
Hi T800,

Thank you very much for the excellent thumbnail maker. It has been invaluable in organizing some of my photo albums. I love how easy it is to set up.

I was wondering, is there any way to have thumbnails also generated for .7z archives as well? This would really help out with my albums that need a bit more compression than zip or rar. If it's not easy to accomplish, pay no mind to the request, the program as is has still been an excellent tool for me.

Thanks again

41
  • Guests
  • Last active:
  • Joined: --
No point in using 7zip since you can't compress files that are already compressed - jpg, png, gif, etc., only bmp is raw uncompressed format. Zip is universally compatible and has integrated support in windows explorer, and rar has archive recovery record (no worry about scratched cds).

Beside, 7-zip is under (L)GPL aka The Cancer.

Sandro
  • Guests
  • Last active:
  • Joined: --
Some suggestions

1 - Allow preview also tar / cbt, 7z/cb7 and ace / cba. Maybe pdf (no need to install Adobe Acrobat just for this purpose) and other formats of "e-books. "
2 - Images in Compressed Folders are not shown in some cases. Perhaps because of xml or txt files inside the archive, I can not say for sure. Fix it if possible.

Good job. Sorry for my English.