00001
00002
00003
00004 import sys
00005
00006 from util import *
00007 from help import *
00008 from referencefile import *
00009 from jpg import *
00010 from png import *
00011 from gif import *
00012 from filetester import *
00013 from multiprocessing import *
00014 import re
00015 import globals
00016 import os
00017
00018 class FileAddress:
00019 """Stores file address for an input file.
00020 """
00021 def __init__(self, infname, reffname, outfname):
00022 """Constructor.
00023
00024 Data members:
00025 In : Filename of the input file.
00026 Ref : Filename of the reference temporary file relative to temp dir.
00027 Out : Filename of the output file relative to output dir.
00028 """
00029 self.In = infname
00030 self.Ref = reffname
00031 self.Out = outfname
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050 class ImageOptimizer:
00051 """The main class of IMGCrush.
00052
00053 Handles input and high-level commands to other modules.
00054 """
00055 def __init__(self, argv):
00056 """Constructor.
00057
00058 Initializes the optimizer and its modules and processes input.
00059
00060 Data members:
00061
00062 Files and directories:
00063 SaveDir : Directory to save output files.
00064 FileList : Addresses of all input files.
00065
00066 Resizing:
00067 By default, resizing preserves aspect ratio, therefore Width and
00068 Height are maximum bounds of the resized image. If only one
00069 bound is specified, e.g. Width, the image is resized to fill
00070 the given bound and the other is resized accordingly to preserve
00071 aspect ratio. If no bounds are specified or are 0, resizing is
00072 turned off. Other members modify resizing behavior.
00073 NOTE: Resizing options are deprecated!
00074 Width : Maximum width of resized output.
00075 Height : Maximum height of resized output.
00076 Stretch : If given bounds aren't filled by resized image,
00077 stretch the image to fill them.
00078
00079 File format modules:
00080 UseJPG : Use the JPG module? I.e. consider JPG output for optimization.
00081 UsePNG : Use the PNG module? I.e. consider PNG output for optimization.
00082 UseGIF : Use the GIF module? I.e. consider GIF output for optimization.
00083
00084 Other:
00085 ProcMult : Multiplier of the number of processes used when processing
00086 files. Number of processes actually depends on number of
00087 formats used as every format is processed in a separate
00088 process. E.g. if ProcMult==2 and we're using JPG and GIF,
00089 the actual number of processes is 4
00090 """
00091 globals.OUTDIR = "./optimized/"
00092 globals.TEMPDIR = "./temp/"
00093 self.FileList = []
00094 self.Width = ""
00095 self.Height = ""
00096 self.Stretch = False
00097 try:
00098 self.ProcMult = cpu_count()
00099 output(str(self.ProcMult) + " cores detected.")
00100 except NotImplementedError:
00101 warning("Cannot determine number of CPUs. Setting ProcMult to default value of 2")
00102 self.ProcMult = 2
00103 globals.JPG = JPGProcessor()
00104 self.UseJPG = False
00105 globals.PNG = PNGProcessor(self.ProcMult)
00106 self.UsePNG = False
00107 globals.GIF = GIFProcessor()
00108 self.UseGIF = False
00109 globals.TESTER = FileTester()
00110 self.UseStats = False
00111 self.RecursiveDirs = False
00112 self.process_args(argv)
00113 if not (self.UseJPG or self.UsePNG or self.UseGIF):
00114 warning("No file format selected. Using default (PNG).")
00115 self.UsePNG = True
00116
00117 def add_file(self, f, prependdir=None):
00118 """Adds an input file to the list of files to process.
00119
00120 Ignores the file if it's invalid.
00121
00122 Arguments:
00123 f : Filename of the input file.
00124 r : Filename of the corresponding temporary reference file
00125 relative to the temp directory.
00126 o : Filename of the corresponding output file relative to the optput
00127 directory.
00128 """
00129 output("Adding file " + f)
00130 for file in self.FileList:
00131 if file.In == f:
00132 warning("File " + f +
00133 " specified multiple times in input. Ignoring.")
00134 return
00135 o = f
00136
00137 o = o[o.rfind("/") + 1:]
00138 if(o.rfind(".") > 0):
00139 o = o[ :o.rfind(".")]
00140 if prependdir is not None:
00141 o = prependdir + o
00142
00143
00144 if run_bash("identify -format '%n' '" + f + "'")== "1":
00145 self.FileList.append(FileAddress(f, o+"-ref.png",o))
00146 else :
00147 output("unreadable or multi frame file: " + f + " :ignoring", 3)
00148
00149 def add_dir(self, dirname):
00150 """Adds all image files in a directory to the list of files to process.
00151
00152 Does not add files in subdirectories.
00153
00154 Arguments:
00155 dirname : Name of the directory to add files from.
00156 """
00157
00158 if dirname[-1] != "/":
00159 dirname = dirname + "/"
00160 lscmd = "ls '" + dirname + "'"
00161 if self.RecursiveDirs:
00162 lscmd = "ls -R '" + dirname + "'"
00163 dirls = run_bash(lscmd)
00164
00165 if(dirls == ""):
00166 return
00167
00168 absdir = os.path.abspath(dirname)
00169 dirstrip = absdir[absdir.rfind("/") :] + "/"
00170
00171
00172 subdir = ""
00173 for f in dirls.split("\n"):
00174
00175 if f[0:len(dirname)] == dirname and f[-1] == ":":
00176 subdir = f[len(dirname):-1]
00177 subprocess.call("mkdir -p '" + globals.TEMPDIR + dirstrip +
00178 subdir + "'", shell=True)
00179 subprocess.call("mkdir -p '" + globals.OUTDIR + dirstrip +
00180 subdir + "'", shell=True)
00181 continue
00182 if f != "":
00183 self.add_file(dirname + subdir + "/" + f, dirstrip + subdir + "/")
00184
00185 subprocess.call("mkdir -p '" + globals.TEMPDIR + dirstrip+"'", shell = True)
00186 subprocess.call("mkdir -p '" + globals.OUTDIR + dirstrip + "'", shell = True)
00187 if not os.path.exists(globals.TEMPDIR + dirstrip) or\
00188 not os.path.exists(globals.OUTDIR + dirstrip):
00189 error("Cannot create one of temporary or output directories")
00190 clean_up()
00191 sys.exit(-1)
00192
00193 def process_files(self):
00194 """Generates temp files, selects the best and cleans up.
00195
00196 This is the main method of imgcrush:
00197 it handles generation of files based on input using file format
00198 modules,
00199 compares them using tester and outputs the best files.
00200 In the end it deletes the temp directory along with temporary files.
00201
00202 If multiple processes are used, files are divided into groups, each
00203 processed by it's own process.
00204 """
00205
00206 fcounter = Value("d", 0.0)
00207 if self.ProcMult <= 1:
00208 self.process_file_list(self.FileList, fcounter)
00209 else:
00210
00211 FileGroups = []
00212 for g in range(0, self.ProcMult):
00213 FileGroups.append([])
00214 for f in range(0, len(self.FileList)):
00215 FileGroups[f % self.ProcMult].append(self.FileList[f])
00216
00217 processes = []
00218 for g in range(0, len(FileGroups)):
00219 processes.append(Process(target = self.process_file_list,
00220 args = (FileGroups[g], fcounter, g)))
00221 for p in processes:
00222 p.start()
00223 for p in processes:
00224 p.join()
00225 clean_up()
00226
00227 def process_file_list(self, files, fcounter, procnum = 0):
00228 """Processes given list of files.
00229
00230 For each file in list, this generates temp files,
00231 compares them using tester and outputs the best file.
00232 """
00233 for file in files:
00234 reffilename = globals.TEMPDIR + file.Ref
00235 outfilename = globals.OUTDIR + file.Out
00236 reffile = ReferenceFile(file.In, reffilename, self.Width,
00237 self.Height, self.Stretch)
00238 outfilelist = []
00239 jqueue = Queue()
00240 pqueue = Queue()
00241 gqueue = Queue()
00242
00243
00244 jpgproc = Process(target = globals.JPG.process,
00245 args = (reffile, file.Out, jqueue, procnum))
00246 pngproc = Process(target = globals.PNG.process,
00247 args = (reffile, file.Out, pqueue, procnum))
00248 gifproc = Process(target = globals.GIF.process,
00249 args = (reffile, file.Out, gqueue))
00250 if self.UseJPG :
00251 if self.ProcMult > 0:
00252 jpgproc.start()
00253 else:
00254 outfilelist.extend(globals.JPG.process(reffile, file.Out))
00255 if self.UsePNG :
00256 if self.ProcMult > 0:
00257 pngproc.start()
00258 else:
00259 outfilelist.extend(globals.PNG.process(reffile, file.Out))
00260 if self.UseGIF :
00261 if self.ProcMult > 0:
00262 gifproc.start()
00263 else:
00264 outfilelist.extend(globals.GIF.process(reffile, file.Out))
00265 if self.ProcMult > 0:
00266 if self.UseJPG:
00267 outfilelist.extend(jqueue.get())
00268 jpgproc.join()
00269 if self.UsePNG:
00270 outfilelist.extend(pqueue.get())
00271 pngproc.join()
00272 if self.UseGIF:
00273 outfilelist.extend(gqueue.get())
00274 gifproc.join()
00275 globals.TESTER.select_best_file(outfilelist, reffile, outfilename)
00276 subprocess.call("rm '" + reffile.Name + "'", shell = True)
00277 fcounter.value += 1.0
00278 output(str(fcounter.value / len(self.FileList) * 100) + " %")
00279
00280 def try_tempdir(self, temp):
00281 """Tries to create given temp directory.
00282
00283 If temp dir already exists, it appends a number (0) to its name
00284 and tries to create that, if unsuccesful again, appends incremented
00285 number (1) and so on.
00286 If temp dir can't be created, it returns False, otherwise returns
00287 its path.
00288 """
00289
00290
00291 basetemp = temp
00292 if(basetemp[-1] == "/"):
00293 basetemp = basetemp[:-1]
00294 temp = basetemp
00295 num = 0
00296
00297
00298 while os.path.isdir(temp):
00299 temp = basetemp + str(num)
00300 num += 1
00301 temp += "/"
00302 try:
00303 os.makedirs(temp)
00304 return temp
00305 except os.error:
00306 return False
00307
00308 def init_dirs(self):
00309 """Initializes temp and output dirs and ends with error if not possible.
00310 """
00311 temp = self.try_tempdir(globals.TEMPDIR)
00312
00313 if temp:
00314 globals.TEMPDIR = temp
00315
00316 else:
00317 warning("Cannot create temp directory: " + globals.TEMPDIR +
00318 " Using default")
00319 globals.TEMPDIR = "./temp/"
00320 tempdef = self.try_tempdir(globals.TEMPDIR)
00321
00322 if not tempdef:
00323 error("Cannot create default temp directory.")
00324 clean_up()
00325 sys.exit(-1)
00326 if globals.OUTDIR[-1] != "/":
00327 globals.OUTDIR += "/"
00328
00329 if not os.path.exists(globals.OUTDIR):
00330 try:
00331 os.makedirs(globals.OUTDIR)
00332
00333 except os.error:
00334 warning("Cannot create output directory: " + globals.OUTDIR +
00335 " Using default.")
00336 globals.OUTDIR = "./optimized/"
00337 try:
00338 os.makedirs(globals.OUTDIR)
00339
00340 except os.error:
00341 error("Cannot create default output directory.")
00342 clean_up()
00343 sys.exit(-1)
00344
00345
00346
00347 def process_args(self,argv):
00348 """Processes all command-line input.
00349
00350 Input is validated in objects that are affected by it.
00351 i.e. , input that sets properties of ImageOptimizer is validated here,
00352 other input is passed to objects that use it and validated there.
00353 If an argument given to command line option is wrong, default value
00354 is usually used.
00355 If an unrecognizable option is given or an option that requires
00356 an argument is given without an argument, help is displayed and
00357 the program is aborted.
00358
00359 Arguments:
00360 argv : List of command line options and their arguments.
00361 """
00362 opts = []
00363 args = []
00364 try:
00365 opts, args = getopt.getopt(argv, globals.OPTS_SHORT, globals.OPTS_LONG)
00366
00367 except getopt.GetoptError:
00368 command_line_help()
00369 sys.exit(2)
00370
00371 for opt, arg in opts:
00372
00373 if opt in ("-h", "--help"):
00374 command_line_help()
00375 sys.exit()
00376 elif opt in ("-o", "--out-dir"):
00377 globals.OUTDIR = arg
00378 elif opt in ("-t", "--temp-dir"):
00379 globals.TEMPDIR = arg
00380 elif opt in ("-q", "--quiet"):
00381 globals.VERBOSITY -= 1
00382 elif opt in ("-v", "--verbose"):
00383 globals.VERBOSITY += 1
00384 elif opt in ("-m", "--prc-mult"):
00385 self.ProcMult = valid_number(arg, 0, "--prc-mult (-m)", 0,
00386 16384)
00387 globals.PNG.set_proc_count(self.ProcMult)
00388 elif opt in ("-r", "--recur"):
00389 self.RecursiveDirs = True
00390
00391 elif opt in ("-j", "--jpg"):
00392 self.UseJPG = True
00393 elif opt in ("-p", "--png"):
00394 self.UsePNG = True
00395 elif opt in ("-g", "--gif"):
00396 self.UseGIF = True
00397
00398 elif opt in ("-J", "--j-set"):
00399 globals.JPG.set_all(arg)
00400 elif opt in ("-U", "--j-qual"):
00401 globals.JPG.set_min_quality(arg)
00402 elif opt in ("-T", "--j-step"):
00403 globals.JPG.set_step(arg)
00404 elif opt in ("-D", "--j-inter"):
00405 globals.JPG.set_interlace()
00406 elif opt in ("-O", "--j-optim"):
00407 globals.JPG.set_optimize()
00408 elif opt in ("-Z", "--j-s-fact"):
00409 globals.JPG.set_sfactors(arg)
00410 elif opt in ("-A","--j-opt-lv"):
00411 globals.JPG.set_optimizations(arg)
00412 elif opt in ("-K","--j-gray"):
00413 globals.JPG.set_grayscale(arg)
00414
00415 elif opt in ("-P", "--p-set"):
00416 globals.PNG.set_all(arg)
00417 elif opt in ("-L", "--p-lev"):
00418 globals.PNG.set_min_level(arg)
00419 elif opt in ("-H", "--p-filt"):
00420 globals.PNG.set_filters(arg)
00421 elif opt in ("-X", "--p-no-tru"):
00422 globals.PNG.set_truecolor(False)
00423 elif opt in ("-Y", "--p-pal"):
00424 globals.PNG.set_min_pal_depth(arg)
00425 elif opt in ("-R", "--p-gray"):
00426 globals.PNG.set_min_gray_depth(arg)
00427 elif opt in ("-E", "--p-inter"):
00428 globals.PNG.set_interlace()
00429 elif opt in ("-B", "--p-opt-lv"):
00430 globals.PNG.set_optimizations(arg)
00431
00432 elif opt in ("-G", "--g-opt"):
00433 globals.GIF.set_all(arg)
00434 elif opt in ("-F", "--g-inter"):
00435 globals.GIF.set_interlace()
00436 elif opt in ("-I", "--g-depth"):
00437 globals.GIF.set_min_depth(arg)
00438 elif opt in ("-C", "--g-opt-lv"):
00439 globals.GIF.set_optimizations(arg)
00440
00441 elif opt in ("-N", "--no-strip"):
00442 globals.NOSTRIP = True
00443 elif opt in ("-M", "--mode"):
00444 globals.TESTER.set_mode(arg)
00445 elif opt in ("-Q", "--min-qual"):
00446 globals.TESTER.set_min_quality(arg)
00447 elif opt in ("-S", "--max-size"):
00448 globals.TESTER.set_max_size(arg)
00449
00450 elif opt in ("-W", "--width"):
00451 if(arg[-1] == "%"):
00452 self.Width = valid_number_str_suffix(arg, "", "%",
00453 "--width (-w)", 0,
00454 1048576, True)
00455 else:
00456 self.Width = valid_number_str(arg, "", "--width (-w)", 1,
00457 1048576)
00458 if self.Width == "0" or self.Width == "0%":
00459 self.Width=""
00460 elif opt in ("-H", "--height"):
00461 if(arg[-1]=="%"):
00462 self.Height = valid_number_str_suffix(arg, "", "%",
00463 "--height (-h)", 0,
00464 1048576, True)
00465 else:
00466 self.Height = valid_number_str(arg, "", "--height (-h)",
00467 1,1048576)
00468 if self.Height == "0" or self.Height == "0%":
00469 self.Height=""
00470 elif opt in ("-s", "--stretch"):
00471 self.Stretch = True
00472
00473 self.init_dirs()
00474 for arg in args:
00475 if run_bash("[ -d '" + arg + "' ] && echo \"True\"") == "True":
00476 self.add_dir(arg)
00477 elif run_bash("[ -f '" + arg + "' ] && echo \"True\"") == "True":
00478 self.add_file(arg)
00479 else:
00480 warning("Input file or directory: " + arg + " does not exist.")
00481 if len(self.FileList) == 0:
00482 error("No readable input files")
00483 command_line_help()
00484 sys.exit(0)