#!/usr/bin/python

# tz_image_convert.py
#       --copyright--                   Copyright 2013 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       May 15, 2013            bar
#       May 19, 2013            bar     out_dir arg to do_it()
#                                       just convert/copy files with no comments
#                                       convert to gray for .pgm and RGB for .ppm
#       July 24, 2013           bar     properly handle default params
#                                       able to rename the files, slightly, sorta
#       July 25, 2013           bar     use pil_tostring
#       September 23, 2013      bar     break out the routine to get the image and comment
#       September 27, 2013      bar     comment filter
#       October 1, 2013         bar     yeah, maybe test it
#       March 10, 2014          bar     pass down jpeg options
#       March 23, 2014          bar     change_jpeg_comment()
#       May 12, 2014            bar     use copy2 rather than copyfile to copy so date/times are maintained for copied files
#       July 1, 2014            bar     backup param
#       July 22, 2014           bar     cmd line works with PIL (Pillow works with those commented options)
#       November 27, 2015       bar     --force
#       January 25, 2016        bar     force forces jpeg rewrite even if only the comment has changed
#       --eodstamps--
##      \file
#       \namespace              tzpython.tz_image_convert
#
#
#       Convert image files - maintaining the comments.
#
#


import  cStringIO
import  os
import  shutil

from    PIL     import  Image, PngImagePlugin

import  replace_file
import  tz_jpg
import  tz_netpbm
import  tz_png
import  tzlib




def change_jpeg_comment(from_file_name, to_file_name, comment, backup = True) :
    """ Just change the comment in the JPEG file. """

    fi  = tz_jpg.a_jpg(from_file_name)
    fi.comment(comment)
    tfn = to_file_name + ".tmp"
    fi.save(tfn)
    replace_file.replace_file(to_file_name, tfn, (backup and (to_file_name + ".bak")) or None)      # make the file after it's written so someone else won't try reading a half-written file



def file_write(file_name, comment_name, comment, img, backup = True, **jpeg_options) :
    """ Write the image with comment to the given output file. """

    tfn     = file_name + ".tmp"

    ext     = os.path.splitext(file_name)[1].lower()
    if      ext == '.png' :
        pi  = None
        if  comment :
            pi  = PngImagePlugin.PngInfo()
            pi.add_text(comment_name, comment)
        img.save(tfn, format = 'PNG', pnginfo = pi)
    elif    ext in [ '.jpg', '.jpeg', ] :
        if  comment :
            fo  = cStringIO.StringIO()
            img.save(fo, format = 'JPEG', **jpeg_options)
            fo.seek(0)
            jpg = tz_jpg.a_jpg(fo)
            fo.close()
            jpg.comment(comment)
            jpg.save(tfn)
            del(jpg)
        else :
            img.save(tfn, format = 'JPEG')
        pass
    elif    ext in [ '.pgm', '.ppm', ] :
        comment = comment or ""
        p   = ((ext == '.pgm') and '5') or '6'
        if  ext == '.pgm' :
            img = img.convert('L')
        else    :
            img = img.convert('RGB')
        fd  = "P%s\n# %s\n%u %u\n255\n%s" % ( p, comment.strip(), img.size[0], img.size[1], tzlib.pil_tostring(img), )
        tzlib.safe_write_whole_binary_file(tfn, fd)
    else    :
        raise ValueError('Bad image file extension: ' + ext)

    replace_file.replace_file(file_name, tfn, (backup and (file_name + ".bak")) or None)            # make the file after it's written so someone else won't try reading a half-written file





def get_image_and_comment(fn, comment_name = None) :
    """
        Return the image and comment from the given, named file.

        Return None for the image if the file is of an unknown type.

        Return None for the comment if there is none.

        Handles .jpg, .png, .pgm and .ppm files.

    """

    comment_name    = comment_name  or 'tz_image_convert'

    x               = os.path.splitext(fn)[1].lower()

    if  x in [ '.jpg', '.jpeg' ] :
        img         = Image.open(fn)
        fi          = tz_jpg.a_jpg(fn)
        fi.comments = [ fi.comment() ]
    elif x == '.png' :
        img         = Image.open(fn)
        fi          = tz_png.a_png(fn)
        fi.comments = [ c[1] for c in fi.get_comments(comment_name) ]
    elif x in [ '.pgm', '.ppm' ] :
        fi          = tz_netpbm.a_netpbm_file(fn)
        img         = Image.open(cStringIO.StringIO(fi.all))
        img.load()
    else            :
        return(None, None)

    try             :
        cmt         = fi.comments[0]
    except IndexError :
        if  False   :
            raise ValueError("No comment in [%s]!" % fn)
        cmt         = None

    return(img, cmt)




def do_it(fns, ext = None, comment_name = None, out_dir = None, comment_filter = None, backup = True, verbose = 0, only_change_comments_in_jpegs = False, force = False, **jpeg_options) :
    """
        Convert the given files returning an array of the new file names.

        Note: The 'out_dir' can be a printf string rendering an integer item 0... to fully rename the files. E.g. '/blah/img_%03n.png'

    """

    ext             = ext           or '.png'
    comment_name    = comment_name  or 'tz_image_convert'

    ofns            = []

    if  isinstance(fns, basestring) :
        fns         = [ fns ]

    for fnum, fn in enumerate(fns) :
        ofn             = fn
        if  out_dir     :
            if  os.path.isdir(out_dir) :
                ofn     = os.path.join(out_dir, os.path.basename(fn))
            elif out_dir.find('%') >= 0 :
                ofn     = os.path.join(out_dir % fnum)
            else        :
                ofn     = out_dir                                                                       # this allow the caller to rename the file if there is only 1 file to be converted
                out_dir = os.path.join(os.path.dirname(out_dir), 'tz_img_convert_%05u' + ext)           # for subsequent files, if any, use the numbering scheme
            pass
        ofn             = os.path.splitext(ofn)[0] + ext

        if  verbose     :
            print fn, "--->", ofn

        if  comment_filter :
            img, cmt    = get_image_and_comment(fn, comment_name = comment_name)
            if  not img :
                raise ValueError("Input file of unknown type [%s]!" % fn)
            ncmt        = comment_filter(cmt, file_name = fn)
        else            :
            cmt         = None
            ncmt        = None

        x       = os.path.splitext(fn)[1].lower()

        if  (not force) and (x == ext.lower()) and (cmt == ncmt) :
            if  os.path.abspath(fn) == os.path.abspath(ofn) :
                raise ValueError("Input file [%s] same as output [%s]!" % ( fn, ofn ))

            shutil.copy2(fn, ofn)

        elif (not force) and only_change_comments_in_jpegs and (x == ext.lower()) and (x in [ '.jpg', '.jpeg', ]) :         # note: if this is true, the comment must have changed, so it must have been filtered
            if  os.path.abspath(fn) == os.path.abspath(ofn) :
                raise ValueError("Input file [%s] same as output [%s]!" % ( fn, ofn ))

            change_jpeg_comment(fn, ofn, ncmt, backup = backup)

        else    :
            if  not comment_filter :
                img, ncmt   = get_image_and_comment(fn, comment_name = comment_name)
                if  not img :
                    raise ValueError("Input file of unknown type [%s]!" % fn)

                pass
            file_write(ofn, comment_name, ncmt, img, backup = backup, **jpeg_options)

        ofns.append(ofn)

    return(ofns)



help_str    = """
%s (options) ambiguous_file_name...

    Converts the given (ambiguous) image files to the given .ext format.

Options:

    --ext               extension           Specify the output image file extension (.png .jpg .pgm .ppm)
                                            Default: %s
    --out_dir           directory_name      Set the output directory name (default to same directory).
    --no_backup                             Do not create a backup file.
    --comment_name      name                Name for the comment in .png files. (Default: %s)
    --force                                 Force a smart copy of the file (to change JPEG quality)
    --verbose                               Increase the verbosity level.
    --quality           1..100              Set JPEG quality level. (Default: 75)

This program converts image files, putting the original file's comment in the output file.
Many files can be converted. The new files will have the given extension and by of the appropriate type.

Input image files may be .png .jpg .jpeg .pgm .ppm files.

"""



if  __name__ == '__main__' :

    import  sys

    import  TZCommandLineAtFile
    import  tz_os_priority


    program_name    = sys.argv.pop(0)

    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)


    verbose         = 0
    ext             = '.png'
    out_dir         = None
    backup          = True
    quality         = 75
    force           = False
    comment_name    = os.path.basename(os.path.splitext(program_name)[0])

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--help", "-h", "-?", "/h", "/H", "/?" ] )
        if  oi < 0  :   break
        del sys.argv[oi]
        print help_str % ( os.path.basename(program_name), ext, comment_name, )
        sys.exit(254)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--verbose", "-v", ] )
        if  oi < 0  :   break
        del sys.argv[oi]
        verbose    += 1

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--ext", "--extension", "-x", ] )
        if  oi < 0  :   break
        del sys.argv[oi]
        ext         = sys.argv.pop(oi)
        if  not ext.startswith('.') :
            ext     = '.' + ext
        pass

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--out_dir", "--out-dir", "--outdir", "-o", ] )
        if  oi < 0  :   break
        del sys.argv[oi]
        out_dir     = sys.argv.pop(oi)

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--no_backup", "--no-backup", "--nobackup", "--nb", ] )
        if  oi < 0  :   break
        del sys.argv[oi]
        backup      = False

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--comment_name", "--comment-name", "--commentname", "--cmt_name", "--cmt-name", "--cmtname", "-c", ] )
        if  oi < 0  :   break
        del sys.argv[oi]
        comment_name    = sys.argv.pop(oi)

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--quality", "-q", ] )
        if  oi < 0  :   break
        del sys.argv[oi]
        quality         = int(sys.argv.pop(oi))

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--force", "-f", ] )
        if  oi < 0  :   break
        del sys.argv[oi]
        force           = False


    fns = []
    while len(sys.argv) :
        afn     = sys.argv.pop(0)
        fns    += tzlib.ambiguous_file_list(afn)

    if  not len(fns) :
        print "Tell me files to convert"
        sys.exit(101)

    tz_os_priority.set_proc_to_idle_priority(0)

    fns.sort(lambda a, b : cmp(a.lower(), b.lower()))

    do_it(fns, ext, comment_name = comment_name, out_dir = out_dir, verbose = verbose, quality = quality, backup = backup, force = force)       # PIL fails, Pillow is OK. optimize = True, progressive = False,


#
#
# eof
