Files
PICT_numtool_NG/PICT_numtool.py

773 lines
32 KiB
Python
Executable File

#!/usr/bin/python
'''
=========================================================================================================
T&T Software and Systems
Project: Renumbering tool
Module: Main module
Description: Tool to renumber ohoto and video replace VB one
Tiziano Trabattoni | 2016/12/10 | first version
Tiziano Trabattoni | 2022/07/31 | Porting su MacOS, PyQt5, python3
Tiziano Trabattoni | 2023/05/22 | Porting su MacOS per pyqt6
Tiziano Trabattoni | 2023/08/14 | Sorting by Creation date with exiftool
Tiziano Trabattoni | 2024/08/19 | Changed movie tags to include filemodification date for .mp4 from Android
Tiziano Trabattoni | 2024/12/28 | Introduced possibility to ignore metadata and sort only by filename for Undetermined Metadata (old slides)
Tiziano Trabattoni | 2025/01/08 | Added steps for numbering
Tiziano Trabattoni | 2025/08/20 | Added Canon .CR2 RAW format
####### Developed under poetry (with pyqt6 M1 ARM64 code available)
~/Develop/Personal/PICT_poetry » poetry remove pyqt5
~/Develop/Personal/PICT_poetry » poetry add pyqt6
~/Develop/Personal/PICT_poetry » pip freeze ttrabatt@TTRABATT-M-3QXG
PyQt6==6.5.0
PyQt6-Qt6==6.5.0
PyQt6-sip==13.5.1
(pict-numtool-py3.9) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
~/Develop/Personal/PICT_poetry » poetry show ttrabatt@TTRABATT-M-3QXG
pyqt6 6.5.0 Python bindings for the Qt cross platform application toolkit
pyqt6-qt6 6.5.0 The subset of a Qt installation needed by PyQt6.
pyqt6-sip 13.5.1 The sip module support for PyQt6
(pict-numtool-py3.9) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
~/Develop/Personal/PICT_poetry »
=========================================================================================================
'''
import sys
import os
import shutil
import time
import logging
import threading
import subprocess
import tempfile
import json
from datetime import datetime
from collections import OrderedDict
''' PyQT6 imports'''
from PICT_numtool_ui import Ui_MainWindow
# from PyQt import QtGui
# from PyQt.QtWidgets import QTreeView, QLabel, QTreeWidgetItem, QMainWindow, QApplication, QFileDialog, QAbstractItemView
# from PyQt.QtGui import QPixmap
from PICT_numtool_ui import Ui_MainWindow
from PyQt6 import QtGui
from PyQt6.QtWidgets import QTreeView, QLabel, QTreeWidgetItem, QMainWindow, QApplication, QFileDialog, QAbstractItemView
from PyQt6.QtGui import QPixmap
#from PyQt5.QtCore import *
#from PyQt6 import QtCore
global LOGGER
''' Module version '''
mod_ver = "v2.4"
mod_dver = "2025:08:20"
''' Logger Variables '''
LOG_FORMAT = '%(asctime)s|%(levelname)-7s|%(funcName)-15s|%(lineno)-5d: %(message)-50s'
LOG_TIME_FORMAT = '%m-%d %H:%M:%S'
CONSOLE_DEBUG = logging.INFO
FILE_DEBUG = logging.INFO
PHOTO_filter = 'Images (*.jpg *.jpeg *.png *.tiff *.heic, *.tif, *.HEIC , *.CR2)'
MOVIE_filter = 'Movies (*.mov *.mp4 )'
# file format and extension variables
EXIFTOOL = "exiftool"
EXIF_PHOTO_EXT = (".jpg", ".JPG", ".jpeg", ".JPEG", ".png", ".PNG", ".heic", ".HEIC", ".tiff", ".TIFF", ".tif", ".TIF", ".cr2", ".CR2")
EXIF_MOVIE_EXT = (".mov", ".MOV", ".mp4", ".MP4")
EXIF_PHOTO_TAGS = ("-datetimeoriginal", "-filecreatedate", "-filemodifydate",)
EXIF_MOVIE_TAGS = ("-mediacreatedate","-filemodifydate",)
FILE_TREE_CNTL = [('Filename.', 300, QTreeView),
('FullNAme', 800, QtGui.QTextItem), ('--', 20, QLabel)]
YEARS_RANGE = [1970, 2040]
COL_WHITE = "QPushButton { color: white;}"
COL_RED = "QPushButton { color: red;}"
COL_GREEN = "QPushButton { color: green;}"
COL_YELLOW = "QPushButton { color: yellow;}"
COL_BLUE = "QPushButton { color: blue;}"
COL_BLACK = "QPushButton { color: black;}"
BCOL_WHITE = "background-color: white;"
BCOL_RED = "background-color: red;"
BCOL_GREEN = "background-color: green;"
BCOL_YELLOW = "background-color: yellow;"
BCOL_CYAN = "background-color: cyan;"
BCOL_BLACK = "background-color: black;"
''' Default instances '''
DEF_INSTANCE = 'select'
NON_FILE_CHARACTERS = '\\|/*$?:;<>+\"\''
''' Ordering dictionary '''
WorkData = {}
WorkDir = ''
TmpDir = ''
class PICTitem(QTreeWidgetItem):
'''
Custom QTreeWidgetItem with Widgets
'''
_basename = ""
_name = ""
@property
def basename(self):
return self._basename
@property
def name(self):
return self._name
def __init__(self, parent, basename, name):
'''
parent (QTreeWidget) : Item's QTreeWidget parent.
value (str) : Item's name. just an example.
'''
## Init super class ( QtGui.QTreeWidgetItem )
super(PICTitem, self).__init__(parent)
''' '''
self.setText(0, basename)
#self.setTextAlignment(0, QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.setText(1, name)
#self.setTextAlignment(1, QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self._name=name
self._basename=basename
def __repr__(self) -> str:
return str(self)
def __str__(self):
return f"<class '{self.__class__.__name__}'> Filename:{self.basename}"
''' ======================== MAIN WINDOW CLASS with all methods ================================ '''
class main(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
''' create Progressbar element '''
self.pb = PB(self.ui.pb_Run,mi = 0, ma =100)
''' set default values '''
self.day = 0
self.month = 0
self.year = 1900
self.name = ''
self.fullName = ''
self.ineditname = False
self.filecriteria = PHOTO_filter
self.TmpDir = ''
self.WorkDir = ''
self.WorkData = {}
self.toRename = []
''' set the tree_OUT widget'''
self.ui.tree_OUT.setColumnCount(len(FILE_TREE_CNTL))
self.ui.tree_OUT.setHeaderLabels(self.set_qtree_headers(FILE_TREE_CNTL))
self.ui.tree_OUT.setUniformRowHeights(True)
#self.ui.tree_OUT.setSelectionMode(QabsW.ExtendedSelection)
''' set all Methods '''
self.ui.btn_Renumber.setEnabled(False)
self.ui.btn_Renumber.clicked.connect(self.click_Renumber)
self.ui.btn_Open.clicked.connect(self.click_Open)
self.ui.btn_Exit.clicked.connect(self.click_Exit)
#self.ui.txt_Name.editingFinished.connect(self.edited_Name)
self.ui.rbtn_Photo.setChecked(True)
''' set all combo '''
for i in range(1,32):
self.ui.cmb_Day.addItem(str(i))
for i in range(1,13):
self.ui.cmb_Month.addItem(str(i))
for i in range(YEARS_RANGE[0],YEARS_RANGE[1]+1):
self.ui.cmb_Year.addItem(str(i))
for i in range(1,11):
self.ui.cmb_Step.addItem(str(i))
''' set date and time '''
# print datetime.now().strftime('%d/%m/%Y')
self.day = int(datetime.now().strftime('%d'))
self.month = int(datetime.now().strftime('%m'))
self.year = int(datetime.now().strftime('%Y'))
self.ui.cmb_Day.setCurrentIndex(self.day - 1)
self.ui.cmb_Month.setCurrentIndex(self.month - 1)
self.ui.cmb_Year.setCurrentIndex(self.year - YEARS_RANGE[0])
self.ui.date_Day.setDate(datetime.now())
self.ui.txt_Name.textChanged.connect(self.changed_Name)
######## These are OLD PyQT4 signal sintax ###########
#''' set Object signals '''
#''' visualization selection via Radio Button '''
#self.ui.rbtn_Movies.connect(self.ui.rbtn_Movies, QtCore.SIGNAL("clicked()"),self.selected_rbtn_Movies)
#self.ui.rbtn_Photo.connect(self.ui.rbtn_Photo, QtCore.SIGNAL("clicked()"), self.selected_rbtn_Photos)
#self.ui.date_Day.connect(self.ui.date_Day, QtCore.SIGNAL("dateChanged(QDate)"), self.change_date)
#
#''' Signals '''
#self.ui.cmb_Day.connect(self.ui.cmb_Day, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.choose_Day)
#self.ui.cmb_Month.connect(self.ui.cmb_Month, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.choose_Month)
#self.ui.cmb_Year.connect(self.ui.cmb_Year, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.choose_Year)
## self.ui.txt_Name.connect(self.ui.txt_Name, QtCore.SIGNAL("insertPlainText (const Qstring&"), self.changed_Name)
''' set Object signals '''
''' visualization selection via Radio Button '''
self.ui.rbtn_Movies.clicked.connect(self.selected_rbtn_Movies)
self.ui.rbtn_Photo.clicked.connect(self.selected_rbtn_Photos)
self.ui.date_Day.dateChanged.connect(self.change_date)
self.ui.tree_OUT.itemSelectionChanged.connect(self.on_selectionChanged)
''' Signals '''
self.ui.cmb_Day.currentIndexChanged.connect(self.choose_Day)
self.ui.cmb_Month.currentIndexChanged.connect(self.choose_Month)
self.ui.cmb_Year.currentIndexChanged.connect(self.choose_Year)
''' other setting '''
self.ui.pb_Run.setMinimum(0)
self.ui.pb_Run.setMaximum(100)
self.ui.pb_Run.setVisible(False)
self.ui.chk_IgnMeta.setChecked(False)
def click_Exit(self):
sys.exit(0)
return
def QaddFILEChild(self, parent, basename, name):
item = PICTitem(parent, basename, name)
return item
def on_selectionChanged(self):
selected = [ s.basename for s in self.ui.tree_OUT.selectedItems() ]
for f in self.WorkData:
if f in selected:
self.WorkData[f]['rename'] = True
else:
self.WorkData[f]['rename'] = False
return
def click_Open(self):
''' open source directory '''
#files = QFileDialog.getOpenFileNames(self, "Open Directory", "" , self.filecriteria, None, QFileDialog.DontUseNativeDialog)
files = QFileDialog.getOpenFileNames(self, "Open Directory", "" , self.filecriteria, None)
nf = len(files)
# SHIT now QfileDialog return a Tuple instead of list
# Has to be addressed as [0]
# because files[1] contains the Type: <class 'str'>, - Value >Images (*.jpg *.jpeg *.png *.tiff)
if len(files[0]) > 0:
''' clear and set the tree_OUT widget'''
self.ui.tree_OUT.clear()
self.ui.tree_OUT.setColumnCount(len(FILE_TREE_CNTL))
self.ui.tree_OUT.setHeaderLabels(self.set_qtree_headers(FILE_TREE_CNTL))
self.ui.tree_OUT.setUniformRowHeights(True)
self.pb.start(steps=nf)
''' initialized work list dictionary '''
self.WorkData = {}
# LOGGER.debug("Type of files[] >%s<" % (str(type(files))))
# self.WorkDir = os.path.dirname(str(files[0]))
# SHIT now QfileDialog return a Tuple instead of list
# Has to be addressed as [0]
# because files[1] contains the Type: <class 'str'>, - Value >Images (*.jpg *.jpeg *.png *.tiff)
fileuno = files[0][0]
#LOGGER.debug("fileuno >%s<" % fileuno)
self.WorkDir = os.path.dirname(fileuno)
LOGGER.debug(f"Workdir >{self.WorkDir}<")
''' tempory dir must be on same disk otherwise is too slow'''
basedir = os.path.dirname(self.WorkDir)
self.TmpDir = basedir + '/PICTtool-tmp'
if not os.path.exists(self.TmpDir):
os.mkdir(self.TmpDir)
count = 1
#for fname in files[0]:
# LOGGER.debug("Type: %s, - Value >%s<" % (type(fname), fname))
''' for all selected files '''
for fname in files[0]:
self.pb.incr()
self.repaint()
''' add an item to Qtree including resizing of pictures '''
# self.add_Qtree_item(fname)
basename = os.path.basename(str(fname))
LOGGER.info(f"Reading Metadata from: >{basename}< ")
# Get File Metadata via external exiftool
# requires installation
# https://exiftool.org/index.html#running
#
if self.ui.chk_IgnMeta:
# dont look at meta data of file just sort for name
''' fill dictionary '''
self.WorkData.update({
# int(time.mktime(time.strptime(dt, '%Y:%m:%d|%H:%M:%S'))) :
basename :
{
#'itemNum' : str(count),
'fname' : basename,
'fileExt' : self.get_extension(str(fname)),
'tmpName' : self.TmpDir + '/PICTtmp-' + next(tempfile._get_candidate_names()),
'rename' : True,
'date' : "",
'time' : ""
}
})
count += 1
else:
# Normal way looking at Metadata to get file sosted with creation time
# exiftool has produced with json and TAGS
if basename.endswith(EXIF_MOVIE_EXT):
EXIF_TAGS = EXIF_MOVIE_TAGS
else:
EXIF_TAGS = EXIF_PHOTO_TAGS
args=[EXIFTOOL, "-json", *EXIF_TAGS, os.path.join(self.WorkDir,basename)]
LOGGER.debug(str(args))
process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
res=json.loads(process.stdout.read())
LOGGER.debug(json.dumps(res,indent=2))
dat = res[0]
# reset of all tag values
dt = odate = fmdate = fcdate = mcdate = None
if basename.endswith(EXIF_MOVIE_EXT):
# MOVIEs
try:
fmdate = dat['FileModifyDate']
except KeyError:
LOGGER.debug(f"File {basename} does not contain tag FileModifyDate")
except Exception as e:
LOGGER.error(f"File {basename} parsing error {e}")
continue # for fname in files[0]:
try:
mcdate = dat['MediaCreateDate']
except KeyError:
LOGGER.debug(f"File {basename} does not contain tag MediaCreateDate")
except Exception as e:
LOGGER.error(f"File {basename} parsing error {e}")
continue
if not mcdate and not fmdate:
LOGGER.error(f"File {basename} does NOT contain expected metadata tags, please verify results")
continue # for fname in files[0]:
if fmdate:
try:
# dt = f"{mcdate[0]}:{mcdate[1]}:{mcdate[2].split(' ')[0]}|{mcdate[2].split(' ')[1]}:{mcdate[3]}:{(mcdate[4].split('+'))[0]}".strip()
pd = int(time.mktime(time.strptime(fmdate, '%Y:%m:%d %H:%M:%S%z')))
pod = datetime.fromtimestamp(pd)
dt = pod.strftime('%Y:%m:%d|%H:%M:%S')
except Exception as e:
LOGGER.error(f"File {basename} parsing tag value: {e} ...Skipping...")
continue # for fname in files[0]:
else:
try:
# dt = f"{mcdate[0]}:{mcdate[1]}:{mcdate[2].split(' ')[0]}|{mcdate[2].split(' ')[1]}:{mcdate[3]}:{(mcdate[4].split('+'))[0]}".strip()
pd = int(time.mktime(time.strptime(mcdate, '%Y:%m:%d %H:%M:%S')))
pod = datetime.fromtimestamp(pd)
dt = pod.strftime('%Y:%m:%d|%H:%M:%S')
except Exception as e:
LOGGER.error(f"File {basename} parsing tag value: {e} ...Skipping...")
continue # for fname in files[0]:
else:
# PICTUREs
try:
odate = dat['DateTimeOriginal']
except KeyError:
LOGGER.debug(f"File {basename} does not contain tag DateTimeOriginal")
except Exception as e:
LOGGER.error(f"File {basename} parsing error {e}")
continue # for fname in files[0]:
try:
fcdate = dat['FileCreateDate']
except KeyError:
LOGGER.debug(f"File {basename} does not contain tag FileCreateDate")
except Exception as e:
LOGGER.error(f"File {basename} parsing error {e}")
continue # for fname in files[0]:
try:
fmdate = dat['FileModifyDate']
except KeyError:
LOGGER.debug(f"File {basename} does not contain tag FileModifyDate")
except Exception as e:
LOGGER.error(f"File {basename} parsing error {e}")
continue # for fname in files[0]:
if not odate and not fcdate and not fmdate:
LOGGER.error(f"File {basename} does NOT contain expected metadata tags, please verify results")
continue # for fname in files[0]:
if odate:
#take DateTimeOriginal
od = int(time.mktime(time.strptime(odate, '%Y:%m:%d %H:%M:%S')))
ood = datetime.fromtimestamp(od)
dt = ood.strftime('%Y:%m:%d|%H:%M:%S')
elif fcdate and fmdate:
# take older between FileCreateDate and FileModifyDate
fcfmdate = min(int(time.mktime(time.strptime(fcdate, '%Y:%m:%d %H:%M:%S%z'))), int(time.mktime(time.strptime(fmdate, '%Y:%m:%d %H:%M:%S%z'))))
ofcfmdate = datetime.fromtimestamp(fcfmdate)
dt = ofcfmdate.strftime('%Y:%m:%d|%H:%M:%S')
elif fcdate and not fmdate:
# take fcdate
fc = int(time.mktime(time.strptime(fcdate, '%Y:%m:%d %H:%M:%S%z')))
dt = fc.strftime('%Y:%m:%d|%H:%M:%S')
else:
# take fmdate
fm = int(time.mktime(time.strptime(fmdate, '%Y:%m:%d %H:%M:%S%z')))
dt = fm.strftime('%Y:%m:%d|%H:%M:%S')
if not dt:
LOGGER.error(f"File {basename} does NOT contain expected metadata tags, please verify results")
continue # for fname in files[0]:
# check if same modification time exist so it loops adding one second to modification time
adt = int(time.mktime(time.strptime(dt, '%Y:%m:%d|%H:%M:%S')))
while adt in self.WorkData.keys():
adt+=1
LOGGER.info(f"Time: {dt} --> {basename}")
''' fill dictionary '''
self.WorkData.update({
# int(time.mktime(time.strptime(dt, '%Y:%m:%d|%H:%M:%S'))) :
adt :
{
#'itemNum' : str(count),
'fname' : basename,
'fileExt' : self.get_extension(str(fname)),
'tmpName' : self.TmpDir + '/PICTtmp-' + next(tempfile._get_candidate_names()),
'rename' : True,
'date': dt.split('|')[0],
'time': dt.split('|')[1]
}
})
count += 1
# end of sorting with meta data
self.pb.end()
# print unsorted dictionary
# print(f"{json.dumps(self.WorkData, indent=4)}")
# transpose disctionary and sort it by date of creation of the file
flts = dict(sorted(self.WorkData.items()))
# self.WorkData = flts
# print(f"{json.dumps(self.WorkData, indent=4)}")
for _,v in self.WorkData.items():
''' add an item to Qtree including resizing of pictures '''
# self.add_Qtree_item(f"{v['fname']}-->{v['date']}|{v['time']}")
self.add_Qtree_item(f"{v['fname']}")
return
def click_Renumber(self):
''' renumber and create a new WorkData for next phases '''
newWorkData = {}
''' only if WorkData Contains elements '''
nf = len(self.WorkData)
if nf > 0:
self.pb.start(steps=nf * 2)
''' for all files move to temp to avoid overlapping'''
for _,v in self.WorkData.items():
LOGGER.debug(f"Moving file {v}")
if v['rename']:
self.pb.incr()
try:
tmpfile = v['tmpName']
origfile = self.WorkDir + '/' + v['fname']
except Exception as e:
LOGGER.error(f"Parsing file attributes [{v['fname']} : {e} ... Skipping ...")
else:
try:
shutil.move(origfile, tmpfile)
#os.rename(origfile, tmpfile)
except Exception as e:
LOGGER.error(f"Renaming file [{origfile}] to tmpfile: {tmpfile} : {e} ... Skipping ...")
else:
# LOGGER.debug(f"Origfile [{origfile}] ==> TmpFile [{tmpfile}]')
pass
''' for all files move to current directory with new name'''
count = 1
for _,v in self.WorkData.items():
LOGGER.debug(f"Moving file {v}")
if v['rename']:
self.pb.incr()
try:
tmpfile = v['tmpName']
extension = v['fileExt']
#count = int(self.WorkData[fname]['itemNum'])
except Exception as e:
LOGGER.error(f"Parsing file attributes [{v['fname']} : {e} ... Skipping ...")
else:
targetfile = self.WorkDir + '/' + self.fullName + '-{0:04d}'.format(count) + extension
targetfname = self.fullName + '-{0:04d}'.format(count) + extension
try:
shutil.move(tmpfile, targetfile)
#os.rename(tmpfile, targetfile)
except Exception as e:
LOGGER.error(f"Renaming tmpfile [{tmpfile}] to: {targetfname} : {e} ... Skipping ...")
else:
#LOGGER.debug('TmpFile [%s] ==> TargetFIle [%s]' % (tmpfile, targetfile))
LOGGER.info(f"Renamed [{tmpfile}] ==> [{targetfile}]")
pass
''' fill dictionary '''
newWorkData.update({
targetfname : {
'itemNum' : str(count),
'fileExt' : self.get_extension(str(targetfname)),
'tmpName' : self.TmpDir + '/PICTtmp-' + next(tempfile._get_candidate_names()),
'rename' : True
},
})
count += int(self.ui.cmb_Step.currentText())
self.pb.end()
''' also rename directory '''
basedir = os.path.dirname(self.WorkDir)
olddir = self.WorkDir
newdir = basedir + '/' + self.fullName
try:
shutil.move(olddir, newdir)
except Exception as e:
LOGGER.error(f"Rename of directory to [{basedir}] failed {e} ...Skipping...")
''' sel new Current working directory and Workdata to new dictionary '''
self.WorkDir = newdir
os.chdir(self.WorkDir)
self.WorkData = newWorkData
''' empties Qtree '''
self.ui.tree_OUT.clear()
self.ui.tree_OUT.setColumnCount(len(FILE_TREE_CNTL))
self.ui.tree_OUT.setHeaderLabels(self.set_qtree_headers(FILE_TREE_CNTL))
self.ui.tree_OUT.setUniformRowHeights(True)
''' and refill it again '''
nf = len (self.WorkData)
WorkData_s = OrderedDict(self.WorkData)
# redraw the dialog
self.pb.start(steps=nf)
for fname in WorkData_s:
self.pb.incr()
self.add_Qtree_item(self.WorkDir + '/' + fname)
self.pb.end()
return
def add_Qtree_item(self, fname):
basefile = os.path.basename(str(fname))
if self.ui.chk_Preview.isChecked():
''' review is activated need to elaborate thumbnail '''
item = self.QaddFILEChild(self.ui.tree_OUT, basefile, str(fname))
try:
''' rezize file '''
#img = QtGui.QImage(fname)
# qim = img.scaled(64, 64, QtCore.Qt.KeepAspectRatioByExpanding, QtCore.Qt.SmoothTransformation)
# qim = img.scaled(64, 64)
# pict = QPixmap.fromImage(qim)
pict = QPixmap(fname)
except Exception as e:
LOGGER.error(f"Unable to process image [{fname}] {e} ... Skipping ...")
label = QLabel()
label.setText("Preview Error")
else:
''' create a Label with picture as background '''
label = QLabel()
label.setPixmap(pict)
''' add column 2 a Label with picture in pixmap '''
item.treeWidget().setItemWidget(item, 2, label)
else:
item = self.QaddFILEChild(self.ui.tree_OUT, basefile, str(fname))
return item
def get_extension(self, fname):
basename = os.path.basename(fname) # os independent
ext = '.'.join(basename.split('.')[1:])
return '.' + ext if ext else None
def change_date(self):
self.day = self.ui.date_Day.date().day()
self.month = self.ui.date_Day.date().month()
self.year = self.ui.date_Day.date().year()
self.ui.cmb_Day.setCurrentIndex(self.day - 1)
self.ui.cmb_Month.setCurrentIndex(self.month - 1)
self.ui.cmb_Year.setCurrentIndex(self.year - YEARS_RANGE[0] )
''' set fullname '''
self.set_fullName()
return
def changed_Name(self):
if self.ineditname:
return
else:
tc = self.ui.txt_Name.textCursor()
name = str(self.ui.txt_Name.toPlainText())
filename = "".join(i for i in name if i not in NON_FILE_CHARACTERS)
self.ineditname = True
self.ui.txt_Name.setPlainText(filename)
self.ui.txt_Name.setTextCursor(tc)
self.ineditname = False
self.name = filename
self.set_fullName()
return
def selected_rbtn_Movies(self):
self.filecriteria = MOVIE_filter
return
def selected_rbtn_Photos(self):
self.filecriteria = PHOTO_filter
return
def choose_Day(self):
self.day = int(self.ui.cmb_Day.currentText())
self.set_fullName()
return
def choose_Month(self):
self.month = int(self.ui.cmb_Month.currentText())
self.set_fullName()
return
def choose_Year(self):
self.year = int(self.ui.cmb_Year.currentText())
self.set_fullName()
return
def set_qtree_headers(self, tree_ctnl):
tree_HEADERS = []
i = 0
for treeitem in tree_ctnl:
tree_HEADERS.append(treeitem[0])
self.ui.tree_OUT.setColumnWidth(i, treeitem[1])
i += 1
return tree_HEADERS
def set_fullName(self):
self.fullName = '{0:4d}{1:02d}{2:02d}-{3:s}'.format(self.year, self.month, self.day, self.name)
self.ui.txt_FullName.setText(self.fullName)
if len(self.fullName) > 9:
self.ui.btn_Renumber.setEnabled(True)
else:
self.ui.btn_Renumber.setEnabled(False)
# def msg_print(self, msg):
# self.ui.txt_Error.setText(str(msg))
# self.repaint()
''' ========================= Other Functions ==================================================='''
''' progress bar class and methods '''
class PB():
def __init__(self, progressbar, mi = 0, ma = 100 ):
# super(PB, self).__init__(parent)
self.val = 0
self.min = mi
self.max = ma
self.steps = 10
self.step = (self.max - self.min)/self.steps
self.progressbar = progressbar
self.progressbar.setMinimum(self.min)
self.progressbar.setMaximum(self.max)
def start(self, steps=10):
self.steps = (steps if steps > 0 else 10)
self.progressbar.setVisible(True)
self.progressbar.setValue(0)
def end(self):
self.progressbar.setValue(self.max)
# start a timer to make progressbar invisible
threading.Timer(3,self.clear).start()
def clear(self):
self.progressbar.setVisible(False)
def upd(self, val):
self.val = val
self.progressbar.setValue(self.val)
def incr(self):
actv = self.progressbar.value()
newv = actv + self.step
if newv <= self.max:
self.progressbar.setValue(int(newv))
'''=========================================================================================================================='''
''' Main Program '''
if __name__ == '__main__':
''' Enabling Logger
'''
prog_name = os.path.basename(sys.argv[0:][0])
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)
LOGGER.propagate = False
logging.captureWarnings(True)
formatter = logging.Formatter(LOG_FORMAT, LOG_TIME_FORMAT)
''' File logging
'''
log_name = "LOG-" + prog_name + ".log"
fh = logging.FileHandler(log_name)
fh.setLevel(FILE_DEBUG)
fh.setFormatter(formatter)
LOGGER.addHandler(fh)
''' Console logging
'''
cl = logging.StreamHandler(sys.stdout)
cl.setLevel(CONSOLE_DEBUG)
cl.setFormatter(formatter)
LOGGER.addHandler(cl)
time.sleep(1)
LOGGER.info("%s: Ver %s - Date: %s" % (prog_name, mod_ver, mod_dver))
''' Open window and give control to QTPY '''
app = QApplication(sys.argv)
window = main()
window.show()
sys.exit(app.exec())