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

BIN
.DS_Store vendored Normal file

Binary file not shown.

1
.gitignore vendored Executable file
View File

@@ -0,0 +1 @@
/PICT_numtool_ui.pyc

17
.project Executable file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>PICT_numTool</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.python.pydev.PyDevBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.python.pydev.pythonNature</nature>
</natures>
</projectDescription>

8
.pydevproject Executable file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?eclipse-pydev version="1.0"?><pydev_project>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/${PROJECT_DIR_NAME}</path>
</pydev_pathproperty>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python interpreter</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">python</pydev_property>
</pydev_project>

3550
LOG-PICT_numtool.py.log Executable file

File diff suppressed because it is too large Load Diff

764
PICT_numtool.py Executable file
View File

@@ -0,0 +1,764 @@
#!/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)
####### 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 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.1"
mod_dver = "2023:08:17"
''' 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)'
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")
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))
''' 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 += 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 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(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())

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_())

742
PICT_numtool_240819_save.py Executable file
View File

@@ -0,0 +1,742 @@
#!/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 | Chenged movie tags to include filemodification date for .mp4 from Android
####### 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 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.1"
mod_dver = "2023:08:17"
''' 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)'
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",)
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', 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)
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
#
# 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
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 += 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 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(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())

263
PICT_numtool_ui.py Executable file
View File

@@ -0,0 +1,263 @@
# Form implementation generated from reading ui file 'PICT_numtool_ui.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1728, 838)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
MainWindow.setSizePolicy(sizePolicy)
self.wdg_Main = QtWidgets.QWidget(parent=MainWindow)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.wdg_Main.sizePolicy().hasHeightForWidth())
self.wdg_Main.setSizePolicy(sizePolicy)
self.wdg_Main.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.wdg_Main.setObjectName("wdg_Main")
self.verticalLayout = QtWidgets.QVBoxLayout(self.wdg_Main)
self.verticalLayout.setObjectName("verticalLayout")
self.layH_DateName = QtWidgets.QHBoxLayout()
self.layH_DateName.setObjectName("layH_DateName")
self.date_Day = QtWidgets.QDateEdit(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.date_Day.sizePolicy().hasHeightForWidth())
self.date_Day.setSizePolicy(sizePolicy)
self.date_Day.setMinimumSize(QtCore.QSize(100, 20))
self.date_Day.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.date_Day.setTime(QtCore.QTime(0, 0, 0))
self.date_Day.setCalendarPopup(True)
self.date_Day.setTimeSpec(QtCore.Qt.TimeSpec.LocalTime)
self.date_Day.setObjectName("date_Day")
self.layH_DateName.addWidget(self.date_Day)
self.lbl_day = QtWidgets.QLabel(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_day.sizePolicy().hasHeightForWidth())
self.lbl_day.setSizePolicy(sizePolicy)
self.lbl_day.setMinimumSize(QtCore.QSize(40, 20))
self.lbl_day.setObjectName("lbl_day")
self.layH_DateName.addWidget(self.lbl_day)
self.cmb_Day = QtWidgets.QComboBox(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.cmb_Day.sizePolicy().hasHeightForWidth())
self.cmb_Day.setSizePolicy(sizePolicy)
self.cmb_Day.setMinimumSize(QtCore.QSize(50, 20))
self.cmb_Day.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.cmb_Day.setObjectName("cmb_Day")
self.layH_DateName.addWidget(self.cmb_Day)
self.lbl_Month = QtWidgets.QLabel(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_Month.sizePolicy().hasHeightForWidth())
self.lbl_Month.setSizePolicy(sizePolicy)
self.lbl_Month.setMinimumSize(QtCore.QSize(50, 20))
self.lbl_Month.setObjectName("lbl_Month")
self.layH_DateName.addWidget(self.lbl_Month)
self.cmb_Month = QtWidgets.QComboBox(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.cmb_Month.sizePolicy().hasHeightForWidth())
self.cmb_Month.setSizePolicy(sizePolicy)
self.cmb_Month.setMinimumSize(QtCore.QSize(50, 20))
self.cmb_Month.setMaximumSize(QtCore.QSize(16777215, 16777215))
self.cmb_Month.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.cmb_Month.setObjectName("cmb_Month")
self.layH_DateName.addWidget(self.cmb_Month)
self.lbl_Year = QtWidgets.QLabel(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_Year.sizePolicy().hasHeightForWidth())
self.lbl_Year.setSizePolicy(sizePolicy)
self.lbl_Year.setObjectName("lbl_Year")
self.layH_DateName.addWidget(self.lbl_Year)
self.cmb_Year = QtWidgets.QComboBox(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.cmb_Year.sizePolicy().hasHeightForWidth())
self.cmb_Year.setSizePolicy(sizePolicy)
self.cmb_Year.setMinimumSize(QtCore.QSize(80, 20))
self.cmb_Year.setMaximumSize(QtCore.QSize(80, 16777215))
self.cmb_Year.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.cmb_Year.setObjectName("cmb_Year")
self.layH_DateName.addWidget(self.cmb_Year)
self.lbl_Name_2 = QtWidgets.QLabel(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_Name_2.sizePolicy().hasHeightForWidth())
self.lbl_Name_2.setSizePolicy(sizePolicy)
self.lbl_Name_2.setMinimumSize(QtCore.QSize(0, 20))
self.lbl_Name_2.setObjectName("lbl_Name_2")
self.layH_DateName.addWidget(self.lbl_Name_2)
self.txt_Name = QtWidgets.QPlainTextEdit(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.txt_Name.sizePolicy().hasHeightForWidth())
self.txt_Name.setSizePolicy(sizePolicy)
self.txt_Name.setMinimumSize(QtCore.QSize(200, 20))
self.txt_Name.setMaximumSize(QtCore.QSize(16777215, 30))
self.txt_Name.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhNoAutoUppercase)
self.txt_Name.setObjectName("txt_Name")
self.layH_DateName.addWidget(self.txt_Name)
self.verticalLayout.addLayout(self.layH_DateName)
self.lay_Properties = QtWidgets.QHBoxLayout()
self.lay_Properties.setObjectName("lay_Properties")
self.lbl_Name = QtWidgets.QLabel(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_Name.sizePolicy().hasHeightForWidth())
self.lbl_Name.setSizePolicy(sizePolicy)
self.lbl_Name.setMinimumSize(QtCore.QSize(0, 20))
self.lbl_Name.setObjectName("lbl_Name")
self.lay_Properties.addWidget(self.lbl_Name)
self.txt_FullName = QtWidgets.QLineEdit(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.txt_FullName.sizePolicy().hasHeightForWidth())
self.txt_FullName.setSizePolicy(sizePolicy)
self.txt_FullName.setMinimumSize(QtCore.QSize(200, 30))
self.txt_FullName.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.txt_FullName.setReadOnly(True)
self.txt_FullName.setObjectName("txt_FullName")
self.lay_Properties.addWidget(self.txt_FullName)
self.verticalLayout.addLayout(self.lay_Properties)
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.tree_OUT = QtWidgets.QTreeWidget(parent=self.wdg_Main)
self.tree_OUT.setMinimumSize(QtCore.QSize(0, 20))
self.tree_OUT.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.tree_OUT.setColumnCount(2)
self.tree_OUT.setObjectName("tree_OUT")
self.tree_OUT.headerItem().setText(0, "1")
self.tree_OUT.headerItem().setText(1, "2")
self.verticalLayout_2.addWidget(self.tree_OUT)
self.verticalLayout.addLayout(self.verticalLayout_2)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.grp_Type = QtWidgets.QGroupBox(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.grp_Type.sizePolicy().hasHeightForWidth())
self.grp_Type.setSizePolicy(sizePolicy)
self.grp_Type.setMinimumSize(QtCore.QSize(200, 30))
self.grp_Type.setMaximumSize(QtCore.QSize(200, 16777215))
self.grp_Type.setTitle("")
self.grp_Type.setObjectName("grp_Type")
self.rbtn_Movies = QtWidgets.QRadioButton(parent=self.grp_Type)
self.rbtn_Movies.setGeometry(QtCore.QRect(90, 0, 101, 31))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.rbtn_Movies.sizePolicy().hasHeightForWidth())
self.rbtn_Movies.setSizePolicy(sizePolicy)
self.rbtn_Movies.setMinimumSize(QtCore.QSize(50, 20))
self.rbtn_Movies.setChecked(True)
self.rbtn_Movies.setObjectName("rbtn_Movies")
self.rbtn_Photo = QtWidgets.QRadioButton(parent=self.grp_Type)
self.rbtn_Photo.setGeometry(QtCore.QRect(0, 0, 78, 31))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.rbtn_Photo.sizePolicy().hasHeightForWidth())
self.rbtn_Photo.setSizePolicy(sizePolicy)
self.rbtn_Photo.setMinimumSize(QtCore.QSize(25, 0))
self.rbtn_Photo.setChecked(False)
self.rbtn_Photo.setObjectName("rbtn_Photo")
self.horizontalLayout.addWidget(self.grp_Type, 0, QtCore.Qt.AlignmentFlag.AlignVCenter)
self.chk_IgnMeta = QtWidgets.QCheckBox(parent=self.wdg_Main)
self.chk_IgnMeta.setMinimumSize(QtCore.QSize(150, 20))
self.chk_IgnMeta.setMaximumSize(QtCore.QSize(150, 20))
self.chk_IgnMeta.setObjectName("chk_IgnMeta")
self.horizontalLayout.addWidget(self.chk_IgnMeta)
self.chk_Preview = QtWidgets.QCheckBox(parent=self.wdg_Main)
self.chk_Preview.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.chk_Preview.sizePolicy().hasHeightForWidth())
self.chk_Preview.setSizePolicy(sizePolicy)
self.chk_Preview.setMinimumSize(QtCore.QSize(150, 20))
self.chk_Preview.setObjectName("chk_Preview")
self.horizontalLayout.addWidget(self.chk_Preview)
self.btn_Open = QtWidgets.QPushButton(parent=self.wdg_Main)
self.btn_Open.setMinimumSize(QtCore.QSize(100, 30))
self.btn_Open.setObjectName("btn_Open")
self.horizontalLayout.addWidget(self.btn_Open)
self.btn_Renumber = QtWidgets.QPushButton(parent=self.wdg_Main)
self.btn_Renumber.setMinimumSize(QtCore.QSize(100, 0))
self.btn_Renumber.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus)
self.btn_Renumber.setObjectName("btn_Renumber")
self.horizontalLayout.addWidget(self.btn_Renumber)
self.verticalLayout.addLayout(self.horizontalLayout)
self.lay_ErrExit = QtWidgets.QHBoxLayout()
self.lay_ErrExit.setObjectName("lay_ErrExit")
self.pb_Run = QtWidgets.QProgressBar(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.pb_Run.sizePolicy().hasHeightForWidth())
self.pb_Run.setSizePolicy(sizePolicy)
self.pb_Run.setMinimumSize(QtCore.QSize(600, 0))
self.pb_Run.setMaximumSize(QtCore.QSize(900, 16777215))
self.pb_Run.setProperty("value", 0)
self.pb_Run.setObjectName("pb_Run")
self.lay_ErrExit.addWidget(self.pb_Run)
self.btn_Exit = QtWidgets.QPushButton(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.btn_Exit.sizePolicy().hasHeightForWidth())
self.btn_Exit.setSizePolicy(sizePolicy)
self.btn_Exit.setMinimumSize(QtCore.QSize(100, 30))
self.btn_Exit.setMaximumSize(QtCore.QSize(100, 30))
self.btn_Exit.setObjectName("btn_Exit")
self.lay_ErrExit.addWidget(self.btn_Exit, 0, QtCore.Qt.AlignmentFlag.AlignRight)
self.verticalLayout.addLayout(self.lay_ErrExit)
MainWindow.setCentralWidget(self.wdg_Main)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Photo and Video renumbering tool"))
self.date_Day.setDisplayFormat(_translate("MainWindow", "dd/MM/yyyy"))
self.lbl_day.setText(_translate("MainWindow", "Day"))
self.lbl_Month.setText(_translate("MainWindow", "Month"))
self.lbl_Year.setText(_translate("MainWindow", "Year"))
self.lbl_Name_2.setText(_translate("MainWindow", "Name"))
self.lbl_Name.setText(_translate("MainWindow", "New Name"))
self.rbtn_Movies.setWhatsThis(_translate("MainWindow", "<html><head/><body><p>Show/Edit Networks for this L3Out</p></body></html>"))
self.rbtn_Movies.setText(_translate("MainWindow", "Movies"))
self.rbtn_Photo.setWhatsThis(_translate("MainWindow", "<html><head/><body><p>Show/Edit Logical Node Profiles</p></body></html>"))
self.rbtn_Photo.setText(_translate("MainWindow", "Photos"))
self.chk_IgnMeta.setText(_translate("MainWindow", "Ignore Metadata"))
self.chk_Preview.setText(_translate("MainWindow", "Show Preview"))
self.btn_Open.setText(_translate("MainWindow", "Open Folder"))
self.btn_Renumber.setText(_translate("MainWindow", "Renumber Photo and Movies"))
self.btn_Exit.setText(_translate("MainWindow", "Exit"))

531
PICT_numtool_ui.ui Executable file
View File

@@ -0,0 +1,531 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1728</width>
<height>838</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Photo and Video renumbering tool</string>
</property>
<widget class="QWidget" name="wdg_Main">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="layH_DateName">
<item>
<widget class="QDateEdit" name="date_Day">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>20</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="time">
<time>
<hour>0</hour>
<minute>0</minute>
<second>0</second>
</time>
</property>
<property name="displayFormat">
<string>dd/MM/yyyy</string>
</property>
<property name="calendarPopup">
<bool>true</bool>
</property>
<property name="timeSpec">
<enum>Qt::LocalTime</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lbl_day">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
<property name="text">
<string>Day</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cmb_Day">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>20</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lbl_Month">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>20</height>
</size>
</property>
<property name="text">
<string>Month</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cmb_Month">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lbl_Year">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Year</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cmb_Year">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>80</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lbl_Name_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="txt_Name">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
<property name="inputMethodHints">
<set>Qt::ImhNoAutoUppercase</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="lay_Properties">
<item>
<widget class="QLabel" name="lbl_Name">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="text">
<string>New Name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="txt_FullName">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>30</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTreeWidget" name="tree_OUT">
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="columnCount">
<number>2</number>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
<column>
<property name="text">
<string notr="true">2</string>
</property>
</column>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item alignment="Qt::AlignVCenter">
<widget class="QGroupBox" name="grp_Type">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="title">
<string/>
</property>
<widget class="QRadioButton" name="rbtn_Movies">
<property name="geometry">
<rect>
<x>90</x>
<y>0</y>
<width>101</width>
<height>31</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>20</height>
</size>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Show/Edit Networks for this L3Out&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Movies</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
<widget class="QRadioButton" name="rbtn_Photo">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>78</width>
<height>31</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>25</width>
<height>0</height>
</size>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Show/Edit Logical Node Profiles&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Photos</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chk_IgnMeta">
<property name="minimumSize">
<size>
<width>150</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>20</height>
</size>
</property>
<property name="text">
<string>Ignore Metadata</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chk_Preview">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>20</height>
</size>
</property>
<property name="text">
<string>Show Preview</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_Open">
<property name="minimumSize">
<size>
<width>100</width>
<height>30</height>
</size>
</property>
<property name="text">
<string>Open Folder</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_Renumber">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="text">
<string>Renumber Photo and Movies</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="lay_ErrExit">
<item>
<widget class="QProgressBar" name="pb_Run">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>600</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>900</width>
<height>16777215</height>
</size>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item alignment="Qt::AlignRight">
<widget class="QPushButton" name="btn_Exit">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>30</height>
</size>
</property>
<property name="text">
<string>Exit</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

263
PICT_numtool_ui_ui.py Normal file
View File

@@ -0,0 +1,263 @@
# Form implementation generated from reading ui file '/Users/Tiziano/Library/CloudStorage/OneDrive-Personal/Documents/Develop/Develop-Personal/PICT_poetry_devel/PICT_numtool_ui.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1728, 838)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
MainWindow.setSizePolicy(sizePolicy)
self.wdg_Main = QtWidgets.QWidget(parent=MainWindow)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.wdg_Main.sizePolicy().hasHeightForWidth())
self.wdg_Main.setSizePolicy(sizePolicy)
self.wdg_Main.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.wdg_Main.setObjectName("wdg_Main")
self.verticalLayout = QtWidgets.QVBoxLayout(self.wdg_Main)
self.verticalLayout.setObjectName("verticalLayout")
self.layH_DateName = QtWidgets.QHBoxLayout()
self.layH_DateName.setObjectName("layH_DateName")
self.date_Day = QtWidgets.QDateEdit(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.date_Day.sizePolicy().hasHeightForWidth())
self.date_Day.setSizePolicy(sizePolicy)
self.date_Day.setMinimumSize(QtCore.QSize(100, 20))
self.date_Day.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.date_Day.setTime(QtCore.QTime(0, 0, 0))
self.date_Day.setCalendarPopup(True)
self.date_Day.setTimeSpec(QtCore.Qt.TimeSpec.LocalTime)
self.date_Day.setObjectName("date_Day")
self.layH_DateName.addWidget(self.date_Day)
self.lbl_day = QtWidgets.QLabel(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_day.sizePolicy().hasHeightForWidth())
self.lbl_day.setSizePolicy(sizePolicy)
self.lbl_day.setMinimumSize(QtCore.QSize(40, 20))
self.lbl_day.setObjectName("lbl_day")
self.layH_DateName.addWidget(self.lbl_day)
self.cmb_Day = QtWidgets.QComboBox(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.cmb_Day.sizePolicy().hasHeightForWidth())
self.cmb_Day.setSizePolicy(sizePolicy)
self.cmb_Day.setMinimumSize(QtCore.QSize(50, 20))
self.cmb_Day.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.cmb_Day.setObjectName("cmb_Day")
self.layH_DateName.addWidget(self.cmb_Day)
self.lbl_Month = QtWidgets.QLabel(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_Month.sizePolicy().hasHeightForWidth())
self.lbl_Month.setSizePolicy(sizePolicy)
self.lbl_Month.setMinimumSize(QtCore.QSize(50, 20))
self.lbl_Month.setObjectName("lbl_Month")
self.layH_DateName.addWidget(self.lbl_Month)
self.cmb_Month = QtWidgets.QComboBox(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.cmb_Month.sizePolicy().hasHeightForWidth())
self.cmb_Month.setSizePolicy(sizePolicy)
self.cmb_Month.setMinimumSize(QtCore.QSize(50, 20))
self.cmb_Month.setMaximumSize(QtCore.QSize(16777215, 16777215))
self.cmb_Month.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.cmb_Month.setObjectName("cmb_Month")
self.layH_DateName.addWidget(self.cmb_Month)
self.lbl_Year = QtWidgets.QLabel(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_Year.sizePolicy().hasHeightForWidth())
self.lbl_Year.setSizePolicy(sizePolicy)
self.lbl_Year.setObjectName("lbl_Year")
self.layH_DateName.addWidget(self.lbl_Year)
self.cmb_Year = QtWidgets.QComboBox(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.cmb_Year.sizePolicy().hasHeightForWidth())
self.cmb_Year.setSizePolicy(sizePolicy)
self.cmb_Year.setMinimumSize(QtCore.QSize(80, 20))
self.cmb_Year.setMaximumSize(QtCore.QSize(80, 16777215))
self.cmb_Year.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.cmb_Year.setObjectName("cmb_Year")
self.layH_DateName.addWidget(self.cmb_Year)
self.lbl_Name_2 = QtWidgets.QLabel(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_Name_2.sizePolicy().hasHeightForWidth())
self.lbl_Name_2.setSizePolicy(sizePolicy)
self.lbl_Name_2.setMinimumSize(QtCore.QSize(0, 20))
self.lbl_Name_2.setObjectName("lbl_Name_2")
self.layH_DateName.addWidget(self.lbl_Name_2)
self.txt_Name = QtWidgets.QPlainTextEdit(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.txt_Name.sizePolicy().hasHeightForWidth())
self.txt_Name.setSizePolicy(sizePolicy)
self.txt_Name.setMinimumSize(QtCore.QSize(200, 20))
self.txt_Name.setMaximumSize(QtCore.QSize(16777215, 30))
self.txt_Name.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhNoAutoUppercase)
self.txt_Name.setObjectName("txt_Name")
self.layH_DateName.addWidget(self.txt_Name)
self.verticalLayout.addLayout(self.layH_DateName)
self.lay_Properties = QtWidgets.QHBoxLayout()
self.lay_Properties.setObjectName("lay_Properties")
self.lbl_Name = QtWidgets.QLabel(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_Name.sizePolicy().hasHeightForWidth())
self.lbl_Name.setSizePolicy(sizePolicy)
self.lbl_Name.setMinimumSize(QtCore.QSize(0, 20))
self.lbl_Name.setObjectName("lbl_Name")
self.lay_Properties.addWidget(self.lbl_Name)
self.txt_FullName = QtWidgets.QLineEdit(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.txt_FullName.sizePolicy().hasHeightForWidth())
self.txt_FullName.setSizePolicy(sizePolicy)
self.txt_FullName.setMinimumSize(QtCore.QSize(200, 30))
self.txt_FullName.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.txt_FullName.setReadOnly(True)
self.txt_FullName.setObjectName("txt_FullName")
self.lay_Properties.addWidget(self.txt_FullName)
self.verticalLayout.addLayout(self.lay_Properties)
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.tree_OUT = QtWidgets.QTreeWidget(parent=self.wdg_Main)
self.tree_OUT.setMinimumSize(QtCore.QSize(0, 20))
self.tree_OUT.setFocusPolicy(QtCore.Qt.FocusPolicy.TabFocus)
self.tree_OUT.setColumnCount(2)
self.tree_OUT.setObjectName("tree_OUT")
self.tree_OUT.headerItem().setText(0, "1")
self.tree_OUT.headerItem().setText(1, "2")
self.verticalLayout_2.addWidget(self.tree_OUT)
self.verticalLayout.addLayout(self.verticalLayout_2)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.grp_Type = QtWidgets.QGroupBox(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.grp_Type.sizePolicy().hasHeightForWidth())
self.grp_Type.setSizePolicy(sizePolicy)
self.grp_Type.setMinimumSize(QtCore.QSize(200, 30))
self.grp_Type.setMaximumSize(QtCore.QSize(200, 16777215))
self.grp_Type.setTitle("")
self.grp_Type.setObjectName("grp_Type")
self.rbtn_Movies = QtWidgets.QRadioButton(parent=self.grp_Type)
self.rbtn_Movies.setGeometry(QtCore.QRect(90, 0, 101, 31))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.rbtn_Movies.sizePolicy().hasHeightForWidth())
self.rbtn_Movies.setSizePolicy(sizePolicy)
self.rbtn_Movies.setMinimumSize(QtCore.QSize(50, 20))
self.rbtn_Movies.setChecked(True)
self.rbtn_Movies.setObjectName("rbtn_Movies")
self.rbtn_Photo = QtWidgets.QRadioButton(parent=self.grp_Type)
self.rbtn_Photo.setGeometry(QtCore.QRect(0, 0, 78, 31))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.rbtn_Photo.sizePolicy().hasHeightForWidth())
self.rbtn_Photo.setSizePolicy(sizePolicy)
self.rbtn_Photo.setMinimumSize(QtCore.QSize(25, 0))
self.rbtn_Photo.setChecked(False)
self.rbtn_Photo.setObjectName("rbtn_Photo")
self.horizontalLayout.addWidget(self.grp_Type, 0, QtCore.Qt.AlignmentFlag.AlignVCenter)
self.chk_IgnMeta = QtWidgets.QCheckBox(parent=self.wdg_Main)
self.chk_IgnMeta.setMinimumSize(QtCore.QSize(150, 20))
self.chk_IgnMeta.setMaximumSize(QtCore.QSize(150, 20))
self.chk_IgnMeta.setObjectName("chk_IgnMeta")
self.horizontalLayout.addWidget(self.chk_IgnMeta)
self.chk_Preview = QtWidgets.QCheckBox(parent=self.wdg_Main)
self.chk_Preview.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.chk_Preview.sizePolicy().hasHeightForWidth())
self.chk_Preview.setSizePolicy(sizePolicy)
self.chk_Preview.setMinimumSize(QtCore.QSize(150, 20))
self.chk_Preview.setObjectName("chk_Preview")
self.horizontalLayout.addWidget(self.chk_Preview)
self.btn_Open = QtWidgets.QPushButton(parent=self.wdg_Main)
self.btn_Open.setMinimumSize(QtCore.QSize(100, 30))
self.btn_Open.setObjectName("btn_Open")
self.horizontalLayout.addWidget(self.btn_Open)
self.btn_Renumber = QtWidgets.QPushButton(parent=self.wdg_Main)
self.btn_Renumber.setMinimumSize(QtCore.QSize(100, 0))
self.btn_Renumber.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus)
self.btn_Renumber.setObjectName("btn_Renumber")
self.horizontalLayout.addWidget(self.btn_Renumber)
self.verticalLayout.addLayout(self.horizontalLayout)
self.lay_ErrExit = QtWidgets.QHBoxLayout()
self.lay_ErrExit.setObjectName("lay_ErrExit")
self.pb_Run = QtWidgets.QProgressBar(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.pb_Run.sizePolicy().hasHeightForWidth())
self.pb_Run.setSizePolicy(sizePolicy)
self.pb_Run.setMinimumSize(QtCore.QSize(600, 0))
self.pb_Run.setMaximumSize(QtCore.QSize(900, 16777215))
self.pb_Run.setProperty("value", 0)
self.pb_Run.setObjectName("pb_Run")
self.lay_ErrExit.addWidget(self.pb_Run)
self.btn_Exit = QtWidgets.QPushButton(parent=self.wdg_Main)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.btn_Exit.sizePolicy().hasHeightForWidth())
self.btn_Exit.setSizePolicy(sizePolicy)
self.btn_Exit.setMinimumSize(QtCore.QSize(100, 30))
self.btn_Exit.setMaximumSize(QtCore.QSize(100, 30))
self.btn_Exit.setObjectName("btn_Exit")
self.lay_ErrExit.addWidget(self.btn_Exit, 0, QtCore.Qt.AlignmentFlag.AlignRight)
self.verticalLayout.addLayout(self.lay_ErrExit)
MainWindow.setCentralWidget(self.wdg_Main)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Photo and Video renumbering tool"))
self.date_Day.setDisplayFormat(_translate("MainWindow", "dd/MM/yyyy"))
self.lbl_day.setText(_translate("MainWindow", "Day"))
self.lbl_Month.setText(_translate("MainWindow", "Month"))
self.lbl_Year.setText(_translate("MainWindow", "Year"))
self.lbl_Name_2.setText(_translate("MainWindow", "Name"))
self.lbl_Name.setText(_translate("MainWindow", "New Name"))
self.rbtn_Movies.setWhatsThis(_translate("MainWindow", "<html><head/><body><p>Show/Edit Networks for this L3Out</p></body></html>"))
self.rbtn_Movies.setText(_translate("MainWindow", "Movies"))
self.rbtn_Photo.setWhatsThis(_translate("MainWindow", "<html><head/><body><p>Show/Edit Logical Node Profiles</p></body></html>"))
self.rbtn_Photo.setText(_translate("MainWindow", "Photos"))
self.chk_IgnMeta.setText(_translate("MainWindow", "Ignore Metadata"))
self.chk_Preview.setText(_translate("MainWindow", "Show Preview"))
self.btn_Open.setText(_translate("MainWindow", "Open Folder"))
self.btn_Renumber.setText(_translate("MainWindow", "Renumber Photo and Movies"))
self.btn_Exit.setText(_translate("MainWindow", "Exit"))

7
poetry.lock generated Normal file
View File

@@ -0,0 +1,7 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
package = []
[metadata]
lock-version = "2.0"
python-versions = "^3.9"
content-hash = "c595a0588c25d58f3e3834ad7169126836d262b925fe6ca9b5d540dcf301d254"

15
pyproject.toml Executable file
View File

@@ -0,0 +1,15 @@
[tool.poetry]
name = "pict-poetry-devel"
version = "0.1.0"
description = ""
authors = ["tiziano"]
readme = "README.md"
packages = [{include = "pict_poetry_devel"}]
[tool.poetry.dependencies]
python = "^3.9"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

1
qtc Executable file
View File

@@ -0,0 +1 @@
pyuic6 $1_ui.ui > $1_ui.py

50
requirements.txt Executable file
View File

@@ -0,0 +1,50 @@
attrs==22.1.0
CacheControl==0.12.11
cachy==0.3.0
certifi==2022.9.24
cffi==1.15.1
charset-normalizer==2.1.1
cleo==1.0.0a5
crashtest==0.3.1
distlib==0.3.6
dulwich==0.20.50
ExifRead==3.0.0
filelock==3.8.0
html5lib==1.1
idna==3.4
importlib-metadata==4.13.0
jaraco.classes==3.2.3
jsonschema==4.17.0
keyring==23.11.0
lockfile==0.12.2
more-itertools==9.0.0
msgpack==1.0.4
packaging==21.3
pexpect==4.8.0
Pillow==10.0.0
pkginfo==1.8.3
platformdirs==2.5.3
poetry==1.2.2
poetry-core==1.3.2
poetry-plugin-export==1.2.0
ptyprocess==0.7.0
pycparser==2.21
pyheif @ git+https://github.com/carsales/pyheif.git@2eaefe983acc01d52ca6b0094d986739cd7b32a5
pylev==1.4.0
pyparsing==3.0.9
PyQt6==6.5.1
PyQt6-Qt6==6.5.1
PyQt6-sip==13.5.1
pyrsistent==0.19.2
requests==2.28.1
requests-toolbelt==0.9.1
shellingham==1.5.0
six==1.16.0
support-developer==1.0.5
tomlkit==0.11.6
transpose-dict==1.1.3
urllib3==1.26.12
virtualenv==20.16.6
webencodings==0.5.1
xattr==0.9.9
zipp==3.10.0

93
tt.py Executable file
View File

@@ -0,0 +1,93 @@
from transpose_dict import transpose_dict
import os
import io
import time
from datetime import datetime
import json
import subprocess
# import metadata file objects
from PIL import Image
from PIL.ExifTags import TAGS
import exifread
import pyheif
EXIFTOOL="exiftool"
def get_pict_meta(exifdata):
for tag_id in exifdata:
# get the tag name, instead of human unreadable tag id
tag = TAGS.get(tag_id, tag_id)
if tag == "DateTime":
data = exifdata.get(tag_id)
# decode bytes
if isinstance(data, bytes):
data = data.decode()
return(data)
def get_heic_meta(heif_file):
for metadata in heif_file.metadata:
file_stream = io.BytesIO(metadata['data'][6:])
tags = exifread.process_file(file_stream, details=False)
for k,v in tags.items():
#print(f"TAG:{k}->{v}")
if k == "Image DateTime":
return(str(v))
# this is the main
extensions = (".jpg", ".JPG", ".heic", ".HEIC", ".mov", ".MOV")
#extensions = (".jpg", ".JPG")
fl = {}
for file in os.listdir("./test/work"):
if file.endswith(extensions):
# if file.endswith(('.jpg','.JPG')):
# image = Image.open(os.path.join("./test/work", file))
# exifdata = image.getexif()
# dt = (get_pict_meta(exifdata)).replace(' ','|')
# #print(f"{file}-->{dt}<")
#
# elif file.endswith(('.heic','.HEIC')):
# heif_file = pyheif.read_heif(os.path.join("./test/work", file))
#
# dt = (get_heic_meta(heif_file)).replace(' ','|')
# #print(f"{file}++>{dt}<")
# elif file.endswith(('.mov','.MOV')):
# Open image file for reading (must be in binary mode)
# f = open(os.path.join("./test/work", file) , 'rb')
dt="2023:08:14|12:00:00"
# -createdate for .mov provides Zulu time while for other format timezone time
# for .mov need to use -creationdate TAG
if file.endswith(('.mov','.MOV')):
tags = '-creationdate'
else:
tags = '-createdate'
process = subprocess.Popen([EXIFTOOL,os.path.join("./test/work", file), tags ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for out in process.stdout:
print(f"{out.decode()}")
#if ((out.decode().split(':'))[0]).strip() == "Creation Date":
line = ((out.decode().split(':'))[1:])
print(f"{line} -- {os.path.join('./test/work', file)}")
dt = f"{line[0]}:{line[1]}:{line[2].split(' ')[0]}|{line[2].split(' ')[1]}:{line[3]}:{(line[4].split('+'))[0]}".strip()
print(f"{dt}")
fl.update({file : {
int(time.mktime(time.strptime(dt, '%Y:%m:%d|%H:%M:%S'))) :
{
'date': dt.split('|')[0],
'time': dt.split('|')[1]
}
}
})
# print unsorted dictionary
#print(f"{json.dumps(fl, indent=4)}")
# transpose disctionary and sort it by date of creation of the file
flt = dict(sorted(transpose_dict(fl,1).items()))
# print
print(f"{json.dumps(flt, indent=4)}")