#!/usr/local/bin/python2.7
# -*- coding: utf-8 -*-

"""
################################################################################
 @brief Wikimedia Commons JPEG Bulk Upload Tool

 @author: Wojciech Migda (Wmigda)

 @license: GPLv3: http://www.gnu.org/licenses/gpl-3.0.html

 Based on Nichalp's Upload Script
 http://commons.wikimedia.org/wiki/User:Nichalp/Upload_script

 To be used in association with Nichalp's csv_create.pl script as a
 replacement of upload.pl

################################################################################
 History:
 Date         Who     Description
 -------------------------------------------------------------------------------
 2010-Jun-21  wmigda  Initial version
 2010-Jul-04  wmigda  Support for command line options
 2010-Jul-05  wmigda  BUG602008 - Make mediawiki domain name selectable through
                      command line options
################################################################################
"""

import os
import sys
import re
import mechanize    # http://wwwsearch.sourceforge.net/mechanize/
import cookielib
import csv          # http://docs.python.org/library/csv.html
import random       # http://docs.python.org/library/random.html
import getpass      # http://docs.python.org/library/getpass.html
import optparse     # http://docs.python.org/library/optparse.html

"""
################################################################################

    Python Singleton implementation using metaclass programming
    
    Ref:
        http://en.wikipedia.org/wiki/Singleton_pattern#Python

################################################################################
"""
class SingletonType(type):
    def __call__(cls):
        if getattr(cls, '__instance__', None) is None:
            instance = cls.__new__(cls)
            instance.__init__()
            cls.__instance__ = instance
        return cls.__instance__

"""
################################################################################

    This is a global configuration singleton class.

    To create an instance:
        gconf = gConf()

    To add an attribute:
        gconf.add_param('magic_number', 47)
    
    To access the attribute:
        print gconf.magic_number
    
    To change attribute:
        gconf.magic_number = 83

################################################################################
"""
class gConf:
    # singleton, see en.wiki
    __metaclass__ = SingletonType

    def add_param(self, name, value):
        setattr(self, name, value)
        return
    pass
### gConf ######################################################################

"""
################################################################################
 @brief Command line parameters global access class
################################################################################
 History:
 Date         Who     Description
 -------------------------------------------------------------------------------
 2010-Jul-04  wmigda  Initial version
 2010-Jul-05  wmigda  BUG602008 - Make mediawiki domain name selectable through
                      command line options
 -------------------------------------------------------------------------------
 With first instance creation the parser is invoked.
 Then 'options' and 'args' are available as class attributes
################################################################################
"""
class clParms:
    # singleton, see en.wiki
    __metaclass__ = SingletonType
    
    def __init__(self):
        self.options = None
        self.args = None

        self.parser = optparse.OptionParser(usage = "usage: %prog [options]", version = "%prog 0.0.2")

        self.parser.add_option("--scan", dest="scanImages", action="store_true", default=False, help="scan images in the current diretory and create initial CSV file. Default name of the CSV file can be changed with the --images-csv option.")
        self.parser.add_option("--images-csv", dest="fnCsvImages", type="string", default='upload.csv', help="name of CSV file with files to upload", metavar="FILE")
        self.parser.add_option("--session-tags-csv", dest="fnCsvSession", type="string", help="name of CSV file with 'session' EXIF and IPCT tags to embed", metavar="FILE")
        self.parser.add_option("--identity-tags-csv", dest="fnCsvIdentity", type="string", help="name of CSV file with 'identity' EXIF and IPCT tags to embed", metavar="FILE")
        self.parser.add_option("--mediawiki-domain", dest="mediawikiDomain", type="string", default='commons.wikimedia.org', help="domain name of the mediawiki to upload files to, defaults to 'commons.wikimedia.org'.", metavar="DOMAIN")

        (self.options, self.args) = self.parser.parse_args()
    pass
### clParms ####################################################################


"""
################################################################################
 @brief Browser class to access Wikimedia Commons
################################################################################
 History:
 Date         Who     Description
 -------------------------------------------------------------------------------
 2010-Jun-21  wmigda  Initial version
 2010-Jul-05  wmigda  BUG602008 - Make mediawiki domain name selectable through
                      command line options
################################################################################
"""
class CommonsBrowser:
    br = None
    cj = None
    domain = None
    
    """
    ############################################################################
     @brief Constructor
    ############################################################################
     History:
     Date         Who     Description  
     ---------------------------------------------------------------------------
     2010-Jun-21  wmigda  Initial version
     2010-Jul-05  wmigda  BUG602008 - Make mediawiki domain name selectable
                          through command line options
     ---------------------------------------------------------------------------
     Based on:
     http://stockrt.github.com/p/emulating-a-browser-in-python-with-mechanize/
    ############################################################################
    """
    def __init__(self):
        # Browser
        self.br = mechanize.Browser()
        
        # Cookie Jar
        self.cj = cookielib.LWPCookieJar()
        self.br.set_cookiejar(self.cj)
        
        # Browser options
        self.br.set_handle_equiv(True)
        #self.br.set_handle_gzip(True)
        self.br.set_handle_redirect(True)
        self.br.set_handle_referer(True)
        self.br.set_handle_robots(False)
        
        # Follows refresh 0 but not hangs on refresh > 0
        self.br.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(), max_time=1)
        
        # Want debugging messages?
        #self.br.set_debug_http(True)
        #self.br.set_debug_redirects(True)
        #self.br.set_debug_responses(True)
        
        # User-Agent (this is cheating, ok?)
        self.br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]

        # remember mediawiki domain name setting from command line
        clparms = clParms()
        self.domain = clparms.options.mediawikiDomain
    ### __init__ ###############################################################


    """
    ############################################################################
     @brief Login into Wikimedia Commons using provided credentials
    ############################################################################
     History:
     Date         Who     Description  
     ---------------------------------------------------------------------------
     2010-Jun-21  wmigda  Initial version
     2010-Jul-05  wmigda  BUG602008 - Make mediawiki domain name selectable
                          through command line options
     ---------------------------------------------------------------------------
     @param wpLogin Wikimedia Commons user name
     @param wpPassword Wikimedia Commons user password
    
     @return tuple of status code and browser response
     ---------------------------------------------------------------------------
     TODO:
     - save cookies for reuse
     - 
    ############################################################################
    """
    def login(self, wpLogin, wpPassword):
        self.br.open("http://%s/wiki/Special:UserLogin" % (self.domain))
        self.br.select_form(name="userlogin")
        
        self.br["wpName"] = wpLogin
        self.br["wpPassword"] = wpPassword
        # submit form,
        try:
            re = self.br.submit()
            status = re.code
        except HTTPError, e:
            print "Mechanize error: code=%" % e.code
            return (e.code, None)
        return (status, re)
    ### login ##################################################################


    """
    ############################################################################
     @brief Login into Wikimedia Commons using provided credentials
    ############################################################################
     History:
     Date         Who     Description  
     ---------------------------------------------------------------------------
     2010-Jun-21  wmigda  Initial version
     2010-Jul-05  wmigda  BUG602008 - Make mediawiki domain name selectable
                          through command line options
     ---------------------------------------------------------------------------
     @param localName local file name to read from
     @param commonsName destination file name on Wikimedia Commons
     @param metadata metadata to attach to the uploaded file
    
     @return tuple of status code and browser response
     ---------------------------------------------------------------------------
     TODO:
     - 
    ############################################################################
    """
    def upload(self, localName, commonsName, metadata):
        self.br.open("http://%s/wiki/Special:Upload" % (self.domain))
        self.br.select_form(nr = 0)
        
        self.br.add_file(open(localName), 'text/plain', localName, name="wpUploadFile")
        #self.br["wpUploadFile"] = localName
        self.br["wpDestFile"] = commonsName
        self.br["wpUploadDescription"] = metadata
        #self.br["wpUploadAffirm"] = "1"
        #self.br["wpIgnoreWarning"] = "1"
        #self.br["wpWatchthis"] = "1"
        try:
            re = self.br.submit()
            status = re.code
        except HTTPError, e:
            print "Mechanize error: code=%" % e.code
            return (e.code, None)
        return (status, re)
    ### upload #################################################################


"""
################################################################################
 @brief Utility to check if the variable is a parsable integer
################################################################################
 History:
 Date         Who     Description  
 -------------------------------------------------------------------------------
 2010-Jun-21  wmigda  Initial version
 -------------------------------------------------------------------------------
 @param x variable to test

 @return @c True if argument variable converts to integer
################################################################################
"""
def isNumber(x):
    try:
        int(x)
        return True
    except TypeError:
        return False

    
"""
################################################################################
 @brief Convenience function to login to Wikimedia Commons
################################################################################
 History:
 Date         Who     Description  
 -------------------------------------------------------------------------------
 2010-Jun-21  wmigda  Initial version
 -------------------------------------------------------------------------------
 @param browser browser instance to use for login
 @param wpLogin Wikimedia Commons user name
 @param wpPassword Wikimedia Commons user password

 @return @c True if login was successful
################################################################################
"""
def tryLogin(browser, wpLogin, wpPassword):
    (status, response) = browser.login(wpLogin, wpPassword)
    if status != 200:
        return False
    else:
        # TODO: check for successful login: wgUserName!=null
        return True


"""
################################################################################
 @brief Main function
################################################################################
 History:
 Date         Who     Description  
 -------------------------------------------------------------------------------
 2010-Jun-21  wmigda  Initial version
 -------------------------------------------------------------------------------
 TODO
 - refactor to extract code into callable functions
 - writting into gallery file
 - entering user defined permissions
 - logging trace/debug into file
 - parse city(pop) for success
################################################################################
"""
def main():
    # setup some global configuration variables using gConf
    gconf = gConf()
#    gconf.add_param('curr_dir', os.getcwd())
#    gconf.add_param('wrk_dir', sys.path[0] + '/')

    # parse command line options
    clparms = clParms()

    # read Wikimedia Commons credentials from stdin
    try:
        wpLogin = raw_input("Login: ")
        wpPassword = getpass.getpass()
    except KeyboardInterrupt:
        print "\nCtrl-C pressed. Exiting..."
        sys.exit(0)
    except EOFError:
        print "\nCtrl-D pressed. Exiting..."
        sys.exit(0)

    # instantiate browser
    br = CommonsBrowser()

    # test login
    if not tryLogin(br, wpLogin, wpPassword):
        print "Cannot login into Wikimedia Commons with provided credentials!"
        sys.exit(1)


    # load the CSV file
    if not os.access(clparms.options.fnCsvImages, os.F_OK + os.R_OK):
        print "File %s doesn't exist or cannot be read. Exiting..." % (clparms.options.fnCsvImages)
        sys.exit(1)
    dict = csv.DictReader(open(clparms.options.fnCsvImages))

    for row in dict:
        # integrity check
        # uppercase/lowercase conversions
        row['Lat Ref'] = row['Lat Ref'].decode('utf-8').upper()
        row['Long Ref'] = row['Long Ref'].decode('utf-8').upper()
        row['Region'] = row['Region'].decode('utf-8').upper()
        row['Flickr?'] = row['Flickr?'].decode('utf-8').upper()
        row['Camera info?'] = row['Camera info?'].decode('utf-8').upper()
        row['Embed exif?'] = row['Embed exif?'].decode('utf-8').upper()
        row['Type'] = row['Type'].decode('utf-8').lower()
        row['Coordinate type'] = row['Coordinate type'].decode('utf-8').lower()

        if row['Current name'] == "":
            print "'Current name' is empty - skipped"
            continue
        if row['Description'] == "":
            print "Please add a description to " + row['Current name']
            continue
        
        # Permissions
        if row['Permissions'] == "":
            print "Please enter the permission details to " + row['Current name']
            continue
        if int(row['Permissions']) > 7 or int(row['Permissions']) < 0:
            print "Please enter a correct permissions value for " + row['Current name']
            continue
        
        # Category check
        if row['Category1'] == "":
            print "Please enter a category to " + row['Current name']
            continue
        
        # Latitude and longitude cannot exist without each other. So checking. If both do not exist, ignore, as the template is optional.
        if (row['Lat deg'] == "") ^ (row['Long deg'] == ""):
            if row['Lat deg'] == "":
                print "Please add the latitude to " + row['Current name']
            else:
                print "Please add the longitude to " + row['Current name']
            continue
        
        # Checking dms values
        if row['Coordinate type'] == "":
            row['Coordinate type'] = "d"
        if row['Coordinate type'] != "d" and row['Coordinate type'] != "dms":
            print "Invalid coordinate system. Please enter either 'd' or 'dms' only for " + row['Current name']
            continue
        
        # Checking for valid values if both are entered
        if row['Lat deg'] != "" and row['Long deg'] != "":
            if row['Lat Ref'] == "":
                print "Please add the latitude reference (N or S) to " + row['Current name']
                continue
            if row['Long Ref'] == "":
                print "Please add the longitude reference (E or W) to " + row['Current name']
                continue
            if row['Lat Ref'] != "N" and row['Lat Ref'] != "S":
                print "Invalid latitude reference for " + row['Current name']
                continue
            if row['Long Ref'] != "E" and row['Long Ref'] != "W":
                print "Invalid longitude reference for " + row['Current name']
                continue
            if int(row['Lat deg']) < 0 or int(row['Lat deg']) > 90:
                print row['Lat deg'] + " is not a valid latitude for " + row['Current name']
                continue
            if int(row['Long deg']) < -180 or int(row['Long deg']) > 180:
                print row['Long deg'] + " is not a valid longitude for " + row['Current name']
                continue
            
            if row['Coordinate type'] == "dms":
                if row['Lat min'] != "":
                    if int(row['Lat min']) < 0 or int(row['Lat min']) >= 60:
                        print "Invalid latitude minutes value for " + row['Current name']
                        continue
                if row['Lat sec'] != "":
                    if int(row['Lat sec']) < 0 or int(row['Lat sec']) >= 60:
                        print "Invalid latitude seconds value for " + row['Current name']
                        continue
                if row['Long min'] != "":
                    if int(row['Long min']) < 0 or int(row['Long min']) >= 60:
                        print "Invalid longitude minutes value for " + row['Current name']
                        continue
                if row['Long sec'] != "":
                    if int(row['Long sec']) < 0 or int(row['Long sec']) >= 60:
                        print "Invalid longitude seconds value for " + row['Current name']
                        continue

        # Testing Flickr values. URL and Title fields are compulsory if Flickr values are set
        if row['Flickr?'] == "Y":
            if row['Flickr URL'] == "":
                print "Please add the Flickr URL to " + row['Current name']
                continue
            if row['Flickr image title'] == "":
                print "Please add the Flickr image title to " + row['Current name']
                continue
        
        # Checking for valid geo_type parameters
        if row['Type'] != "":
            # http://en.wikipedia.org/wiki/Wikipedia:GEO#type:T
            if row['Type'] not in ["country", "satellite", "adm1st", "adm2nd", "adm3rd", "city", "airport", "mountain", "isle", "waterbody", "forest", "river", "glacier", "event", "edu", "pass", "railwaystation", "landmark"]:
                # for now city(pop) is invalid here :(, will need a regex
                print "Invalid 'Type' attributes for %s. Please enter a valid geotype." % (row['Current name'])
                continue

        # Checking for geoheading issues
        if row['Heading'] != "":
            if row['Heading'].decode('utf-8').upper() not in ["N", "NBE", "NNE", "NEBN", "NE", "NEBE", "ENE", "EBN", "E", "EBS", "ESE", "SEBE", "SE", "SEBS", "SSE", "SBE", "S", "SBW", "SSW", "SWBS", "SW", "SWBW", "WSW", "WBS", "W", "WBN", "WBW", "NWBW", "NW", "NWBN", "NNW", "NBW"]:
                if isNumber(row['Heading']):
                    if int(row['Heading']) < 0 or int(row['Heading']) > 360:
                        print "Invalid 'Heading' attribute for %s. Please enter a valid heading (0-360 or a compass point)." % (row['Current name'])
                        continue
                else:
                    print "Invalid 'Heading' attribute for %s. Please enter a valid heading (0-360 or a compass point)." % (row['Current name'])
                    continue

        # Checking for valid Xform (JPEG transform) parameters
        if row['Xform'] != "":
            if int(row['Xform']) not in range(1, 10) + [90, 180, 270]:
                print "Invalid transformation (Xform) attributes for %s. Please enter a valid number." % (row['Current name'])
                continue
            
        # Integration
        
        # Creating the gallery text file
        try:
            fGal = open("gallery.txt", "w")
        except IOError:
            print "Cannot open 'gallery.txt' for writting."
            sys.exit(1)
        fGal.write("<gallery>\n")
        
        # Replace spaces in the picture filename
        row['Current name'] = re.sub('\s', '_', row['Current name'])
        
        # Calculating the altitude reference
        if row['Altitude'] != "":
            if int(row['Altitude']) < 0:
                row['Altitude Ref'] = "Below Sea Level"
            else:
                row['Altitude Ref'] = "Above Sea Level"
        
        # This checks to see if the [[Template:Location]] geo-box is needed, and fills in the values
        geo_arr = []
        if row['Lat deg'] != "" and row['Long deg'] != "":
            if row['Type'] != "":
                geo_arr += ["type:" + row['Type']]
            if row['Scale'] != "":
                geo_arr += ["scale:" + row['Scale']]
            if row['Region'] != "":
                geo_arr += ["region:" + row['Region']]
            if row['Heading'] != "":
                geo_arr += ["heading:" + row['Heading']]
            if row['Source'] != "":
                geo_arr += ["source:" + row['Source']]
            row['Geo Params'] = '_'.join(geo_arr)
            
            if row['Coordinate type'] == "dms":
                row['Lat deg'] = abs(int(row['Lat deg']))
                row['Long deg'] = abs(int(row['Long deg']))
                row['Geo Ref'] = "{{Location|%s|%s|%s|%s|%s|%s|%s|%s|%s}}\n" % \
                                 (row['Lat deg'], row['Lat min'], row['Lat sec'], row['Lat Ref'], \
                                  row['Long deg'], row['Long min'], row['Long sec'], row['Long Ref'], \
                                  row['Geo Params'])
                row['GPS Lat'] = "%s %s %s" % (row['Lat deg'], row['Lat min'], row['Lat sec'])
                row['GPS Long'] = "%s %s %s" % (row['Long deg'], row['Long min'], row['Long sec'])
            else:
                if row['Lat Ref'] == "S":
                    # Negative values for southern hemisphere
                    row['Lat deg'] = -abs(int(row['Lat deg']))
                if row['Long Ref'] == "W":
                    # Negative values for western hemisphere
                    row['Long deg'] = -abs(int(row['Long deg']))
                row['Geo Ref'] = "{{Location dec|%s|%s|%s}}\n" % \
                                 (row['Lat deg'], row['Long deg'], row['Geo Params'])
                row['GPS Lat'] = abs(int(row['Lat deg']))
                row['GPS Long'] = abs(int(row['Long deg']))
            
            if row['Lat Ref'] == "S":
                row['GPS Lat Ref'] = "South"
            else:
                row['GPS Lat Ref'] = "North"
            if row['Long Ref'] == "E":
                row['GPS Long Ref'] = "East"
            else:
                row['GPS Long Ref'] = "West"
        
        # Author info
        if row['Author name'] == "":
            full_author = wpLogin
        else:
            full_author = "%s ([[User:%s|%s]])" % (row['Author name'], wpLogin, wpLogin)

        # Extracting the date
        row['Date'] = (row['Date'][0:10]).replace(":", "-")
        row['Year'] = row['Date'][0:4]
        
        # Permissions
        perms = \
            ["user defined",
             "{{self|cc-by-sa-3.0,2.5,2.0,1.0|GFDL}}",
             "{{self|cc-by-sa-3.0}}",
             "{{self|cc-by-sa-3.0,2.5,2.0,1.0}}",
             "{{self|cc-by-sa-3.0|GFDL}}",
             "{{PD-self}}",
             "{{self|cc-pd}}",
             "{{self|cc-by-3.0}}"]
        if row['Permissions'] == "0":
            # User defined permission
            # TODO
            license = "User defined permission - TODO"
        else:
            license = perms[int(row['Permissions'])]

        exif_perms = \
            ["user defined",
             "Released under the Creative Commons attribution and share-alike licences v1, 2, 2.5 and 3 and GFDL",
             "Released under Creative Commons attribution and share-alike licence v 3.0",
             "Released under the Creative Commons attribution and share-alike licences v1, 2, 2.5 and 3",
             "Released under Creative Commons licence attribution and share-alike v 3.0",
             "Released into Public Domain",
             "Released into public domain using the Creative Commons Public Domain Dedication",
             "Released under Creative Commons attribution licence v 3.0"]
        row['Exif perm'] = exif_perms[int(row['Permissions'])]
        
        rot_tmpl = ""
        if row['Xform'] != "":
            if row['Xform'] in ["90", "180", "270"]:
                rot_tmpl = "{{rotate|%s}}\n" % (row['Xform'])
        
        
        # Camera information: [[Template:Photo information]]
        photo_tmpl = ""
        if row['Camera info?'] == "Y":
            # Remove leading single quote
            row['Shutter'] = row['Shutter'][1:-1]
            print "Shutter value: %s" % (row['Shutter'])
            photo_tmpl = (\
                "{{Photo Information\n" + \
                "| Model\t\t= %s\n" + \
                "| Aperture\t= %s\n" + \
                "| Shutter\t= %s\n" + \
                "| Film\t\t= %s\n" + \
                "| ISO\t\t= %s\n" + \
                "| Lens\t\t= %s\n}}\n") % \
                (row['Camera Model'], row['Aperture'], row['Shutter'], row['Film'], row['ISO'], row['Lens'])

        flickr_tmpl = ""
        if row['Flickr?'] == "Y":
            if row['Flickr URL'] != "":
                flickr_tmpl = (\
                    "{{Flickr-self\n" + \
                    "| Description\t\t= %s\n" + \
                    "| Flickr_url\t\t= %s\n" + \
                    "| Title\t\t\t= %s\n" + \
                    "| Taken\t\t\t= %s\n" + \
                    "| Photographer_url\t= %s\n" + \
                    "| Photographer\t\t= %s\n" + \
                    "| Photographer_location\t= %s\n" + \
                    "| Reviewer\t\t= \n" + \
                    "| Permission\t\t=\n}}") % \
                    (row['Description'], row['Flickr URL'], row['Flickr image title'], row['Date'], row['Flickr photographer URL'], full_author, row['Flickr photographer location'])
            else:
                print "Please enter the Flickr URL for " + row['Current name']
                continue
        
        # Formatting metadata
        
        # Formatting the Description parameter
        if row['Description lang'] == "":
            row['Description lang'] = "en"
        full_desc = "\n{{%s|%s}}" % (row['Description lang'], row['Description']);
        if row['Description lang 2'] != "" and row['Description 2'] != "":
            full_desc += "\n{{%s|%s}}" % (row['Description lang 2'], row['Description 2']);
        if row['Description lang 3'] != "" and row['Description 3'] != "":
            full_desc += "\n{{%s|%s}}" % (row['Description lang 3'], row['Description 3']);

        # Categories: Combining all categories as one master category
        master_cat = "[[Category:%s]]" % (row['Category1']);
        if row['Category2'] != "":
            master_cat += "\n[[Category:%s]]" % (row['Category2']);
        if row['Category3'] != "":
            master_cat += "\n[[Category:%s]]" % (row['Category3']);
        if row['Category4'] != "":
            master_cat += "\n[[Category:%s]]" % (row['Category4']);
        if row['Category5'] != "":
            master_cat += "\n[[Category:%s]]" % (row['Category5']);
        
        # Other versions
        other_vers = ""
        if row['Other versions 1'] != "":
            other_vers += "\n* [[:File:%s|%s]]" % (row['Other versions 1'], row['Other versions 1'])
        if row['Other versions 2'] != "":
            other_vers += "\n* [[:File:%s|%s]]" % (row['Other versions 2'], row['Other versions 2'])
        
        # Information template
        meta = (\
            "\n== {{int:filedesc}} ==\n" + \
            "{{Information\n" + \
            "| Description\t= %s\n" + \
            "| Source\t= {{Own work}}\n" + \
            "| Author\t= %s\n" + \
            "| Date\t\t= %s\n" + \
            "| Permission\t= \n" + \
            "| Other versions= %s\n" + \
            "}}\n") % \
            (full_desc, full_author, row['Date'], other_vers);
        # Begin optional template integration
        if row.has_key('Geo Ref'):
            if row['Geo Ref'] != "":
                meta += row['Geo Ref'] + "\n"
        if photo_tmpl != "":
            meta += photo_tmpl + "\n"
        if flickr_tmpl != "":
            meta += flickr_tmpl + "\n"
        if rot_tmpl != "":
            meta += rot_tmpl + "\n"
        if row['Other information'] != "":
            meta += row['Other information'] + "\n"
        
        # Integrating remaining templates into one 
        meta += (\
            "\n== {{int:license}} ==\n" + \
            "%s\n\n\n" + \
            "%s\n") % \
            (license, master_cat)

        #print meta
        
        # Renaming and writing Exif data
        
        # convert spaces to underscores
        row['New name'] = re.sub('\s', '_', row['New name'])
        # Image manipulations (rotations, transformations, etc.)
        # I will use jpegtran just as the Nichalp's script, as it's best for this
        # PIL nor Imagemagick doesn't preserve JPEG's quality
        jpegtran_args = \
            {"2" : '-flip horizontal', 
             "3" : '-rotate 180', 
             "4" : '-flip vertical', 
             "5" : '-transpose', 
             "6" : '-rotate 90', 
             "7" : '-transverse', 
             "8" : '-rotate 270',
             "9" : '-greyscale'}
        if int(row['Xform']) in range(2, 10):
            print "Rotating %s into %s ..." % (row['Current name'], row['New name'])
            fnTemp = row['Current name'] + str(random.randint(1, 100000000)).zfill(8)
            os.system("jpegtran -copy all %s %s > %s" % (jpegtran_args[row['Xform']], row['Current name'], fnTemp));
            os.rename(fnTemp, row['New name'])
            os.remove(row['Current name'])
            print "Successful!"
        else:
            print "Renaming %s into %s ..." % (row['Current name'], row['New name'])
            os.rename(row['Current name'], row['New name'])
            print "Successful!"

        # Begin Exif addition
        exiv2_cmd = \
            "set Exif.Image.Artist %s \nset Exif.Image.Copyright © %s %s, %s \nset Exif.Image.ImageDescription %s \nset Exif.Photo.UserComment %s \n" % (\
             row['Author name'], \
             row['Year'], row['Author name'], row['Exif perm'], \
             row['Description'], \
             ", ".join([row['Category1'], row['Category2'], row['Category3'], row['Category4'], row['Category5']]))
        fnExiv2 = "exiv2_cmd.txt"
        fExiv2 = open(fnExiv2, "w")
        fExiv2.write(exiv2_cmd)
        fExiv2.close()
        os.system('exiv2 -m %s "%s"' % (fnExiv2, row['New name']))
        os.remove(fnExiv2)
        
        # Uploading images to commons
        print "Uploading %s to the Wikimedia Commons. \nDescription: %s" % (row['Current name'], row['Description'])
        br.upload(row['New name'], row['New name'], meta)
        pass


if __name__ == "__main__" :
    main()
