<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2013 <NAME>
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in 
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

"""
Application: Picture Timeliner (pic_timeline_p2.py)

 - Add sources.
 - Double click source to enter timeshift information (optional).
 - Override photo's time information by double clicking photos (optional).
 - Use preview feature to verify output order.
 - Select output directory.
 - Specify photo set's prefix.
 - Go!
"""

import os
import tempfile
import shutil
from Tkinter import *
from tkFileDialog import askdirectory
from tkMessageBox import showinfo, showerror, askyesno
from datetime import datetime, timedelta
from time import strptime, mktime
from operator import attrgetter
import ConfigParser
import logging
# third party modules
import EXIF
from appdirs import AppDirs
# my support modules
from constants import *
from custom_dlgs import TimeShiftDialog, DateTimeDialog

# -----------------------------------------------------------------------------
# Constants
# -----------------------------------------------------------------------------        
APP_GUID = "{75a56efb-b786-424f-b6a3-9649a5d22a83}" # Not used anymore
TEMP_DIR = "temp"
# appdirs
APP_NAME = "Picture Timeliner"
# Tkinter
MIN_WIDTH = 480
MIN_HEIGHT = 400
DEF_SIZE = "640x480"
COLS = 4
# ConfigParser
INI_FILENAME    = "pictime.ini"
SECT_SETTINGS   = "SETTINGS"
OPT_ASKDIRPATH  = "OPT_ASKDIRPATH"
# Logging
LOG_FILE = "pictime.log"
DEF_LEVEL = 'warning'
#DEF_LEVEL = 'info'

# -----------------------------------------------------------------------------
# class TimeShiftDialog
#        Custom dialog that prompts for a timeshift value in seconds
# -----------------------------------------------------------------------------        
class PicTimelineApp(Frame):

    # -----------------------------------------------------------------------------
    # class SourceListData
    #        Data defining a source path
    # -----------------------------------------------------------------------------        
    class SourceListData(object):
        """Stores the time shift and color data for a source item."""
        
        # Queue of color combinations to use.  As a source is added, the first
        # combo is popped and used for that source.  If that source is later
        # deleted, then the colorcombo is pushed back for reuse.  If an
        # item to process is datetime overriden then the color combo inverts
        # (white on color).  If all the color combos are used up, default
        # to black/white.
        colors = [{'fg':'red', 'bg':'white'}, {'fg':'forestgreen', 'bg':'white'},
                  {'fg':'blue', 'bg':'white'}, {'fg':'brown', 'bg':'white'},
                  {'fg':'magenta', 'bg':'white'}, {'fg':'gold', 'bg':'white'},
                  {'fg':'cyan', 'bg':'white'}, {'fg':'greenyellow', 'bg':'white'},
                  {'fg':'coral', 'bg':'white'}, {'fg':'orange', 'bg':'white'},
                  {'fg':'yellow', 'bg':'white'}, {'fg':'gray', 'bg':'white'}]
        DEFAULT_COLORS = {'fg':'black', 'bg':'white'}
        
        def __init__(self):
            self.time_shift = timedelta()
            
            # pop first item out of colors list and assign it to this source
            # if this source is deleted, then it's pushed back onto the list.
            # If no colors are left in the list, use the default of black on white. 
            self.color = (PicTimelineApp.SourceListData.colors.pop(0) 
                            if PicTimelineApp.SourceListData.colors 
                            else PicTimelineApp.SourceListData.DEFAULT_COLORS)
            
    # -----------------------------------------------------------------------------
    # class OutputsListData
    #        Data defining a file to be timelined
    # -----------------------------------------------------------------------------        
    class OutputsListData(object):
        """Stores the data for an file to process.
        id: Reference to a source item.  Can be used as key to sources dict.
        filename: Name of original image name to process
        data: Reference to source data object.  Used to get timeshift/color info.
        _dt: datetime information from file.  "dt" property can shift this
             with source's timeshift info (if any) or completely overridden
             by _dt_override.
        """
        def __init__(self, id, filename, data, dt):
            self.id = id
            self.filename = filename
            self.data = data
            
            # immutable datetime
            self._dt = dt
        
        # do this is str or repr?  str makes more sense. 
        def __str__(self):
            date_str = self.dt.strftime("%B %d, %H:%M:%S")
            return "{} ({})".format(self.filename, date_str)
        
        @property
        def colors(self):
            """Custom property getter that returns color on white (ex: red on white)
            if the item to process has not been overriden.  It flips to white on color
            if the ITEM has been manually overriden.
            """
            if self.is_overriden():
                retval = {'fg':self.data.color['bg'], 'bg':self.data.color['fg']}
                return retval
            else:
                return self.data.color
            
        @property 
        def dt(self):
            """dt property: returns original image file datetime.
            Can be modified with a source shift value or completely
            overriden by an Item override.
            """
            # property getter that returns the stored datetime value
            # shifted by the shift amount.
            if self.is_overriden():
                return self._dt_override
            else:
                # not overriden.  use original datetime with any shift value available
                retval = self._dt + self.data.time_shift
            return retval
        
        @dt.setter
        def dt(self, value):
            # the initial dt is immutable but can be overriden
            self._dt_override = value

        @dt.deleter
        def dt(self):
            """Delete item override and revert to photo's datetime + source's shift
            """
            # initial dt immutable
            if self.is_overriden():
                del self._dt_override
            
        # is this pythonic
        def is_overriden(self):
            return hasattr(self, "_dt_override")
        

    # -----------------------------------------------------------------------------
    # class PicTimelineApp members
    # -----------------------------------------------------------------------------        

    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.sources_data = {}
        self.list_data = []
        
        # use appdirs to get machine specific path to appdata
        dirs = AppDirs(APP_NAME, DEVELOPER_NAME)
        self.appdata_dir = dirs.user_data_dir
        print "App data dir: " + self.appdata_dir
        
        # initiliaze logging<commit_msg>Fix for bug in windows version with appdata dir<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2013 <NAME>
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in 
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

"""
Application: Picture Timeliner (pic_timeline_p2.py)

 - Add sources.
 - Double click source to enter timeshift information (optional).
 - Override photo's time information by double clicking photos (optional).
 - Use preview feature to verify output order.
 - Select output directory.
 - Specify photo set's prefix.
 - Go!
"""

import os
import tempfile
import shutil
from Tkinter import *
from tkFileDialog import askdirectory
from tkMessageBox import showinfo, showerror, askyesno
from datetime import datetime, timedelta
from time import strptime, mktime
from operator import attrgetter
import ConfigParser
import logging
# third party modules
import EXIF
from appdirs import AppDirs
# my support modules
from constants import *
from custom_dlgs import TimeShiftDialog, DateTimeDialog

# -----------------------------------------------------------------------------
# Constants
# -----------------------------------------------------------------------------        
APP_GUID = "{75a56efb-b786-424f-b6a3-9649a5d22a83}" # Not used anymore
TEMP_DIR = "temp"
# appdirs
APP_NAME = "Picture Timeliner"
# Tkinter
MIN_WIDTH = 480
MIN_HEIGHT = 400
DEF_SIZE = "640x480"
COLS = 4
# ConfigParser
INI_FILENAME    = "pictime.ini"
SECT_SETTINGS   = "SETTINGS"
OPT_ASKDIRPATH  = "OPT_ASKDIRPATH"
# Logging
LOG_FILE = "pictime.log"
DEF_LEVEL = 'warning'
#DEF_LEVEL = 'info'

# -----------------------------------------------------------------------------
# class TimeShiftDialog
#        Custom dialog that prompts for a timeshift value in seconds
# -----------------------------------------------------------------------------        
class PicTimelineApp(Frame):

    # -----------------------------------------------------------------------------
    # class SourceListData
    #        Data defining a source path
    # -----------------------------------------------------------------------------        
    class SourceListData(object):
        """Stores the time shift and color data for a source item."""
        
        # Queue of color combinations to use.  As a source is added, the first
        # combo is popped and used for that source.  If that source is later
        # deleted, then the colorcombo is pushed back for reuse.  If an
        # item to process is datetime overriden then the color combo inverts
        # (white on color).  If all the color combos are used up, default
        # to black/white.
        colors = [{'fg':'red', 'bg':'white'}, {'fg':'forestgreen', 'bg':'white'},
                  {'fg':'blue', 'bg':'white'}, {'fg':'brown', 'bg':'white'},
                  {'fg':'magenta', 'bg':'white'}, {'fg':'gold', 'bg':'white'},
                  {'fg':'cyan', 'bg':'white'}, {'fg':'greenyellow', 'bg':'white'},
                  {'fg':'coral', 'bg':'white'}, {'fg':'orange', 'bg':'white'},
                  {'fg':'yellow', 'bg':'white'}, {'fg':'gray', 'bg':'white'}]
        DEFAULT_COLORS = {'fg':'black', 'bg':'white'}
        
        def __init__(self):
            self.time_shift = timedelta()
            
            # pop first item out of colors list and assign it to this source
            # if this source is deleted, then it's pushed back onto the list.
            # If no colors are left in the list, use the default of black on white. 
            self.color = (PicTimelineApp.SourceListData.colors.pop(0) 
                            if PicTimelineApp.SourceListData.colors 
                            else PicTimelineApp.SourceListData.DEFAULT_COLORS)
            
    # -----------------------------------------------------------------------------
    # class OutputsListData
    #        Data defining a file to be timelined
    # -----------------------------------------------------------------------------        
    class OutputsListData(object):
        """Stores the data for an file to process.
        id: Reference to a source item.  Can be used as key to sources dict.
        filename: Name of original image name to process
        data: Reference to source data object.  Used to get timeshift/color info.
        _dt: datetime information from file.  "dt" property can shift this
             with source's timeshift info (if any) or completely overridden
             by _dt_override.
        """
        def __init__(self, id, filename, data, dt):
            self.id = id
            self.filename = filename
            self.data = data
            
            # immutable datetime
            self._dt = dt
        
        # do this is str or repr?  str makes more sense. 
        def __str__(self):
            date_str = self.dt.strftime("%B %d, %H:%M:%S")
            return "{} ({})".format(self.filename, date_str)
        
        @property
        def colors(self):
            """Custom property getter that returns color on white (ex: red on white)
            if the item to process has not been overriden.  It flips to white on color
            if the ITEM has been manually overriden.
            """
            if self.is_overriden():
                retval = {'fg':self.data.color['bg'], 'bg':self.data.color['fg']}
                return retval
            else:
                return self.data.color
            
        @property 
        def dt(self):
            """dt property: returns original image file datetime.
            Can be modified with a source shift value or completely
            overriden by an Item override.
            """
            # property getter that returns the stored datetime value
            # shifted by the shift amount.
            if self.is_overriden():
                return self._dt_override
            else:
                # not overriden.  use original datetime with any shift value available
                retval = self._dt + self.data.time_shift
            return retval
        
        @dt.setter
        def dt(self, value):
            # the initial dt is immutable but can be overriden
            self._dt_override = value

        @dt.deleter
        def dt(self):
            """Delete item override and revert to photo's datetime + source's shift
            """
            # initial dt immutable
            if self.is_overriden():
                del self._dt_override
            
        # is this pythonic
        def is_overriden(self):
            return hasattr(self, "_dt_override")
        

    # -----------------------------------------------------------------------------
    # class PicTimelineApp members
    # -----------------------------------------------------------------------------        

    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.sources_data = {}
        self.list_data = []
        
        # use appdirs to get machine specific path to appdata
        dirs = AppDirs(APP_NAME, DEVELOPER_NAME)
        self.appdata_dir = dirs.user_data_dir
        if not os.path.exists(self.appdata_dir):
            os.makedirs(self.appdata_dir) # by default this makes the app dirs too.
        print "App data dir: " + self.appdata_dir
        
        # initiliaze logging
{'fraction_non_alphanumeric': 0.17242429781270943, 'fraction_numerical': 0.005483458234326449, 'mean_word_length': 1.6811499509964063, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3925168', 'n_tokens_mistral': 4189, 'n_tokens_neox': 3956, 'n_words': 1541}