Working with RARs in ColdFusion - Why I did it...

This post is more than 2 years old.

A few months ago I wrote a blog post about working with RAR files in ColdFusion. The short story is that RAR, a file archive format, is not supported by cfzip. Surprisingly there doesn't seem to be in Java packages out there that provide full integration with the format. I ended up writing a wrapper around the free 7-Zip command line program. I mentioned this in passing but the real reason I wanted to build this was to do something with the CBR files (Comic Book files) I had on my hard drive. I thought it would be interesting to write code to extract the cover image from the files. You could imagine many uses for this. Perhaps simply creating a nice HTML page of covers would be neat. Whatever. To be honest, it's kind of pointless - but that hasn't stopped me before so why now. Here is what I built.

I began by creating a list of comics from my hard drive. I assumed I was going to have multiple screw ups as I worked on my code so I also added basic caching in.

<cfset dir = "H:\comics"> <cfset comics = cacheGet("comics")> <cfif isNull(comics)> <cfoutput> <p>Getting initial list...</p> </cfoutput> <cfset comics = directoryList(dir,true,"list","*.cbr")> <cfset cachePut("comics", comics,createTimeSpan(0,0,10,0))> </cfif>

<!--- make a copy since arraydelat will change copy ---> <cfset comics = duplicate(comics)>

That last bit of code there just handles breaking the reference to the cache since I end up manipulating the results a bit. Ok - now a quick output:

<cfoutput> <p>There are #arrayLen(comics)# total comics.</p> </cfoutput>

I then decided to grab 100 comics from the set:

<!--- Given our list, pick a set ---> <cfset chosen = []> <cfset total = min(100, arrayLen(comics))> <cfloop condition="arrayLen(chosen) lt total"> <cfset target = randRange(1, arrayLen(comics))> <!--- I had a few ._ files ---> <cfif not find("\._",comics[target])> <cfset arrayAppend(chosen, comics[target])> <cfset arrayDeleteAt(comics, target)> </cfif> </cfloop>

And just to be sure - output that size as well:

<cfoutput> <p>Ok, we have #arrayLen(chosen)# comics.</p> </cfoutput>

Next - I set up a temporary directory. Remember that I'm going to be shelling out to an executable. This means I won't be able to use RAM drives:

<!--- make a temp dir ---> <cfset tmpDir = expandPath("./comics")> <cfif not directoryExists(tmpDir)> <cfdirectory action="create" directory="#tmpDir#"> </cfif>

Ok - now let's make our CFC:

<!--- initialize our CFC ---> <cfset sevenZipexe = "C:\Program Files\7-Zip\7z.exe"> <cfset sevenzipcfc = new sevenzip(sevenzipexe)>

Now we are going to loop through our comics and try to get the cover. In general, the first item in a list of files from a CBR RAR file is either an image, or a directory. So my code handles that and looks in the second item if the first is a directory:

<!--- now get our covers ---> <cfloop index="c" array="#chosen#"> <cftry> <cfset files = sevenzipcfc.list(c)> <!--- first item may be a directory - for now we see if size is 0, and if 0, skip to the next ---> <cfif files.size[1] neq 0 and listLast(files.name[1],".") is "jpg"> <cfset sevenzipcfc.extract(c,files.name[1],tmpdir)> <cfoutput>Just extracted #files.name[1]# from #c#<br/></cfoutput> <cfelseif files.recordCount gte 2 and files.size[2] neq 0 and listLast(files.name[2],".") is "jpg"> <cfset sevenzipcfc.extract(c,files.name[2],tmpdir)> <cfoutput>Just extracted #files.name[2]# from #c#<br/></cfoutput> </cfif> <cfcatch> <cfoutput><b>Error: #cfcatch.message#</b><br/></cfoutput> </cfcatch> </cftry> </cfloop>

At this point I had a bunch of images! It worked. Then I thought - let's do something neat with the images...

<cfset images = directoryList(tmpdir)> <cfset canvas = imageNew("", 1500, 1145)> <cfset processed = 0> <cfset i = 1> <cfset x = 0> <cfset y = 0> <cfloop condition="processed lt 50"> <cfif isImageFile(images[i])> <cfset myimg = imageRead(images[i])> <cfset imageResize(myimg,150,229)> <!--- Position is based on the index ---> <cfset imagePaste(canvas, myimg, x, y)> <cfset processed++> <cfset x+= 150> <cfif x is 1500> <cfset x = 0> <cfset y+= 229> </cfif> </cfif> <cfset i++> </cfloop>

Basically - read in a list of images, process line by line and resize each one. I then paste it into a large canvas moving from left to right. And finally...

<cfset imageWrite(canvas, "c:\Users\Raymond\Desktop\finalcover.jpg")>

So the result? Click to make it bigger...

Not perfect - but kind of cool I think. Here is the entire template. Please note I wrote this rather quickly. It's not meant to be production ready.

<cfset dir = "H:\comics"> <cfset comics = cacheGet("comics")> <cfif isNull(comics) or 0> <cfoutput> <p>Getting initial list...</p> </cfoutput> <cfset comics = directoryList(dir,true,"list","*.cbr")> <cfset cachePut("comics", comics,createTimeSpan(0,0,10,0))> </cfif>

<!--- make a copy since arraydelat will change copy ---> <cfset comics = duplicate(comics)>

<cfoutput> <p>There are #arrayLen(comics)# total comics.</p> </cfoutput>

<!--- Given our list, pick a set ---> <cfset chosen = []> <cfset total = min(100, arrayLen(comics))> <cfloop condition="arrayLen(chosen) lt total"> <cfset target = randRange(1, arrayLen(comics))> <!--- I had a few ._ files ---> <cfif not find("._",comics[target])> <cfset arrayAppend(chosen, comics[target])> <cfset arrayDeleteAt(comics, target)> </cfif> </cfloop>

<cfoutput> <p>Ok, we have #arrayLen(chosen)# comics.</p> </cfoutput>

<!--- make a temp dir ---> <cfset tmpDir = expandPath("./comics")> <cfif not directoryExists(tmpDir)> <cfdirectory action="create" directory="#tmpDir#"> </cfif>

<!--- initialize our CFC ---> <cfset sevenZipexe = "C:\Program Files\7-Zip\7z.exe"> <cfset sevenzipcfc = new sevenzip(sevenzipexe)>

<!--- now get our covers ---> <cfloop index="c" array="#chosen#"> <cftry> <cfset files = sevenzipcfc.list(c)> <!--- first item may be a directory - for now we see if size is 0, and if 0, skip to the next ---> <cfif files.size[1] neq 0 and listLast(files.name[1],".") is "jpg"> <cfset sevenzipcfc.extract(c,files.name[1],tmpdir)> <cfoutput>Just extracted #files.name[1]# from #c#<br/></cfoutput> <cfelseif files.recordCount gte 2 and files.size[2] neq 0 and listLast(files.name[2],".") is "jpg"> <cfset sevenzipcfc.extract(c,files.name[2],tmpdir)> <cfoutput>Just extracted #files.name[2]# from #c#<br/></cfoutput> </cfif> <cfcatch> <cfoutput><b>Error: #cfcatch.message#</b><br/></cfoutput> </cfcatch> </cftry> </cfloop>

<cfset images = directoryList(tmpdir)> <cfset canvas = imageNew("", 1500, 1145)> <cfset processed = 0> <cfset i = 1> <cfset x = 0> <cfset y = 0> <cfloop condition="processed lt 50"> <cfif isImageFile(images[i])> <cfset myimg = imageRead(images[i])> <cfset imageResize(myimg,150,229)> <!--- Position is based on the index ---> <cfset imagePaste(canvas, myimg, x, y)> <cfset processed++> <cfset x+= 150> <cfif x is 1500> <cfset x = 0> <cfset y+= 229> </cfif> </cfif> <cfset i++> </cfloop>

<cfset imageWrite(canvas, "c:\Users\Raymond\Desktop\finalcover.jpg")>

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Mark Gregory posted on 4/15/2011 at 2:34 AM

Not seeing any Robotech covers, code must be broken!

Comment 2 by Robert Zehnder posted on 4/15/2011 at 3:06 AM

hahaha +1 Mark

Cool little project though.

Comment 3 by Topper Harley posted on 4/15/2011 at 3:29 AM

Ha, man I always crack up at the things you poor CF'ers have to struggle with. Why don't you guys knock some sense into Adobe? Don't they know you totally depend on them to think of and do everything? You guys should demand better treatment considering all the evangelism and free PR you give them!

Comment 4 by Joseph Potthast posted on 4/15/2011 at 5:06 AM

Ha ha, quite impressive as always Ray. Between this and that Zelda heart display you truly are a man who enjoys cf and all it's capabilities and knows how to not only use them but have fun with them.

Comment 5 by Raymond Camden posted on 4/15/2011 at 5:10 AM

@Mark: I bought a few Robotech comics, but not many. I did buy _all_ the novels though. I've only seen a few of the cartoons, but I love the story.

@Topper: Um... seriously? Maybe I'm reading you wrong, but it sounds like you are saying CF developers can't do anything unless Adobe does it for them. If so - that doesn't even make sense. This blog entry (well mainly the one before it) shows how easy it is to extend ColdFusion. I'm going to assume you didn't mean to - but your comment comes off pretty demeaning.

@Joseph: I actually forgot that demo - thanks. :)

Comment 6 by SuperAlly posted on 4/15/2011 at 7:53 AM

Ray. Cool as always. But really, I just want to read your comic collection now. I've got about 15,000 we can swap for a while :)

Comment 7 by Raymond Camden posted on 4/15/2011 at 2:18 PM

I collected pretty heavily in high school and a bit into college and then I stopped. In the past year I've gotten back into it again. I'm mostly just reading GI Joe but picking up random other comics. A guy in town here is actually a creator - he is doing the Sweets limited edition series which you should check out if you can.

Comment 8 by Edward - Florida SEO posted on 4/16/2011 at 2:48 AM

When I was a kid I used to walk ten miles up hill in the ice and snow to get to school ... You kids nowadays have to depend on fancy cars to get you places ... whats next? Enterprise level rapid application development frameworks that can make easy easier? How dare they ... I'd rather just keep walking ...

Comment 9 by Jaana Gilbert posted on 4/19/2011 at 3:08 AM

I raise my hat to your awesomeness Mr CF!

Comment 10 by Raymond Camden posted on 4/19/2011 at 3:02 PM

@Janna: Thanks. :)