First Commit of PICT_numtool NG
This commit is contained in:
1
.gitignore
vendored
Executable file
1
.gitignore
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
/PICT_numtool_ui.pyc
|
||||||
17
.project
Executable file
17
.project
Executable 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
8
.pydevproject
Executable 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
3550
LOG-PICT_numtool.py.log
Executable file
File diff suppressed because it is too large
Load Diff
764
PICT_numtool.py
Executable file
764
PICT_numtool.py
Executable 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
573
PICT_numtool_220731.py
Executable 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
742
PICT_numtool_240819_save.py
Executable 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
263
PICT_numtool_ui.py
Executable 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
531
PICT_numtool_ui.ui
Executable 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><html><head/><body><p>Show/Edit Networks for this L3Out</p></body></html></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><html><head/><body><p>Show/Edit Logical Node Profiles</p></body></html></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
263
PICT_numtool_ui_ui.py
Normal 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
7
poetry.lock
generated
Normal 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
15
pyproject.toml
Executable 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"
|
||||||
50
requirements.txt
Executable file
50
requirements.txt
Executable 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
93
tt.py
Executable 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)}")
|
||||||
Reference in New Issue
Block a user