First Commit of PICT_numtool NG

This commit is contained in:
Tiziano Trabattoni
2025-01-08 22:16:11 +01:00
commit 1079814dd3
16 changed files with 6878 additions and 0 deletions

573
PICT_numtool_220731.py Executable file
View File

@@ -0,0 +1,573 @@
#!/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
=========================================================================================================
Usefull link: http://www.freeformatter.com/json-formatter.html#ad-output
'''
from collections import OrderedDict
import sys
import os
import shutil
import time
import logging
import threading
from datetime import datetime
import tempfile
import json
''' PyQT4 imports'''
from PICT_numtool_ui import Ui_MainWindow
from PyQt6.QtWidgets import QTreeView, QLabel, QTreeWidgetItem, QMainWindow, QApplication, QFileDialog, QAbstractItemView as QabsW
from PyQt6 import QtGui
#from PyQt5.QtCore import *
from PyQt6 import QtCore
''' Module version '''
mod_ver = "v1.1"
mod_dver = "2022:07:31"
''' Logger Variables '''
LOG_FORMAT = '%(asctime)s|%(levelname)-7s|%(funcName)-15s|%(lineno)-5d: %(message)-50s'
LOG_TIME_FORMAT = '%m-%d %H:%M:%S'
PHOTO_filter = 'Images (*.jpg *.jpeg *.png *.tiff *.heic)'
MOVIE_filter = 'Movies (*.mov *.avi *.mp4 *.wmk)'
FILE_TREE_CNTL = [('Filename.', 300, QTreeView),
('FullNAme', 600, QtGui.QTextItem), ('Preview', 100, 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))
''' 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)
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)
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("Workdir >%s<" % 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()
''' add an item to Qtree including resizing of pictures '''
self.add_Qtree_item(fname)
basename = os.path.basename(str(fname))
''' fill dictionary '''
self.WorkData.update({
basename : {
'itemNum' : str(count),
'fileExt' : self.get_extension(str(fname)),
'tmpName' : self.TmpDir + '/PICTtmp-' + next(tempfile._get_candidate_names()),
'rename' : True
},
})
count += 1
self.pb.end()
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)
# WorkDataKL = self.WorkData.keys()
# WorkDataKL.sort()
WorkData_s = OrderedDict(self.WorkData)
#print(json.dumps(WorkData_s, indent=2))
#WorkData_s = {k: self.WorkData[k] for k in sorted(self.WorkData)}
#for fname in WorkData_s:
# LOGGER.debug("fname: >%s<" % fname)
''' for all files move to temp to avoid overlapping'''
for fname in WorkData_s:
if WorkData_s[fname]['rename']:
self.pb.incr()
try:
tmpfile = self.WorkData[fname]['tmpName']
origfile = self.WorkDir + '/' + fname
except:
LOGGER.error('Parsing file attribites [%s] ... Skipping ...' % (fname))
else:
try:
shutil.move(origfile, tmpfile)
#os.rename(origfile, tmpfile)
except:
LOGGER.error('Renaming file [%s] to: %s ... Skipping ...' % (origfile, tmpfile))
else:
# LOGGER.debug('Origfile [%s] ==> TmpFile [%s]' % (origfile, tmpfile))
pass
''' for all files move to current directory with new name'''
count = 1
for fname in WorkData_s:
if WorkData_s[fname]['rename']:
self.pb.incr()
try:
tmpfile = self.WorkData[fname]['tmpName']
extension = self.WorkData[fname]['fileExt']
count = int(self.WorkData[fname]['itemNum'])
except:
LOGGER.error('Parsing file attributes [%s] ... Skipping ...' % (fname))
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:
LOGGER.error('Renaming file [%s] to: %s ... Skipping ...' % (tmpfile , targetfile))
else:
#LOGGER.debug('TmpFile [%s] ==> TargetFIle [%s]' % (tmpfile, targetfile))
LOGGER.info('Renanme [%s] ==> [%s]' % (fname, 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 += 1
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:
LOGGER.error('Renanme of directory to [%s] failed ...Skipping...' % (basedir))
''' 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)
# WorkDataKL = self.WorkData.keys()
# WorkDataKL.sort()
# WorkData_s = {k: self.WorkData[k] for k in sorted(self.WorkData)}
WorkData_s = OrderedDict(self.WorkData)
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)
pict = QPixmap.fromImage(qim)
except:
LOGGER.error('Unable to process image [%s] ... Skipping ...' % (fname))
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(newv)
'''=========================================================================================================================='''
''' Main Program '''
if __name__ == '__main__':
global LOGGER
''' 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(logging.DEBUG)
fh.setFormatter(formatter)
LOGGER.addHandler(fh)
''' Console logging
'''
cl = logging.StreamHandler(sys.stdout)
cl.setLevel(logging.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_())