Compare commits
No commits in common. "fc32855c861f46583037fcc4df875a24bed696c8" and "63a4124837a27b452101000238f8840ddeb3d5e6" have entirely different histories.
fc32855c86
...
63a4124837
|
@ -1,9 +1,3 @@
|
||||||
*.swp
|
*.swp
|
||||||
*.orig
|
*.orig
|
||||||
config.yaml
|
config.yaml
|
||||||
.DS_Store
|
|
||||||
.idea
|
|
||||||
__pycache__
|
|
||||||
log
|
|
||||||
thumbnail.jpg
|
|
||||||
files_dict.yaml
|
|
||||||
|
|
418
BitMover.ui
418
BitMover.ui
|
@ -1,418 +0,0 @@
|
||||||
<?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>1473</width>
|
|
||||||
<height>928</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>MainWindow</string>
|
|
||||||
</property>
|
|
||||||
<widget class="QWidget" name="centralwidget">
|
|
||||||
<widget class="QWidget" name="gridLayoutWidget">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>20</x>
|
|
||||||
<y>10</y>
|
|
||||||
<width>871</width>
|
|
||||||
<height>121</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="grid_dir_selector">
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="label_1_src_dir">
|
|
||||||
<property name="text">
|
|
||||||
<string>Source Directory</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="2">
|
|
||||||
<widget class="QPushButton" name="pushButton_src_browse">
|
|
||||||
<property name="text">
|
|
||||||
<string>Browse</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1">
|
|
||||||
<widget class="QLineEdit" name="lineEdit_dst_dir">
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="label_2_dst_dir">
|
|
||||||
<property name="text">
|
|
||||||
<string>Destination Directory</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QLineEdit" name="lineEdit_src_dir">
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="2">
|
|
||||||
<widget class="QPushButton" name="pushButton_dst_browse">
|
|
||||||
<property name="text">
|
|
||||||
<string>Browse</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QPushButton" name="pushButton_3_scan_dir">
|
|
||||||
<property name="baseSize">
|
|
||||||
<size>
|
|
||||||
<width>0</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<pointsize>16</pointsize>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Scan Directory</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset theme="edit-find">
|
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="flat">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QListWidget" name="file_list">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>20</x>
|
|
||||||
<y>150</y>
|
|
||||||
<width>871</width>
|
|
||||||
<height>701</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QWidget" name="gridLayoutWidget_2">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>910</x>
|
|
||||||
<y>580</y>
|
|
||||||
<width>551</width>
|
|
||||||
<height>251</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="grid_metadata" columnstretch="0,1">
|
|
||||||
<property name="leftMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="rightMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="horizontalSpacing">
|
|
||||||
<number>20</number>
|
|
||||||
</property>
|
|
||||||
<item row="6" column="0">
|
|
||||||
<widget class="QLabel" name="l_camera">
|
|
||||||
<property name="text">
|
|
||||||
<string>Camera</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0">
|
|
||||||
<widget class="QLabel" name="l_iso">
|
|
||||||
<property name="text">
|
|
||||||
<string>ISO</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QLabel" name="label_data_width_height">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="l_date_time_created">
|
|
||||||
<property name="text">
|
|
||||||
<string>Date / Time Created</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="7" column="0">
|
|
||||||
<widget class="QLabel" name="l_lens">
|
|
||||||
<property name="text">
|
|
||||||
<string>Lens</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="l_dpi">
|
|
||||||
<property name="text">
|
|
||||||
<string>Resolution (DPI)</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="0">
|
|
||||||
<widget class="QLabel" name="l_aperture">
|
|
||||||
<property name="text">
|
|
||||||
<string>Aperture</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="1">
|
|
||||||
<widget class="QLabel" name="label_data_iso">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="1">
|
|
||||||
<widget class="QLabel" name="label_data_aperture">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="7" column="1">
|
|
||||||
<widget class="QLabel" name="label_data_lens">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QLabel" name="l_megapixels">
|
|
||||||
<property name="text">
|
|
||||||
<string>Megapixels</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="QLabel" name="label_data_megapixels">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="l_width_height">
|
|
||||||
<property name="text">
|
|
||||||
<string>Width / Height</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QLabel" name="label_data_date_time_created">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="6" column="1">
|
|
||||||
<widget class="QLabel" name="label_data_camera">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="8" column="0">
|
|
||||||
<widget class="QLabel" name="l_zoom">
|
|
||||||
<property name="text">
|
|
||||||
<string>Zoom</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="8" column="1">
|
|
||||||
<widget class="QLabel" name="label_data_zoom">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1">
|
|
||||||
<widget class="QLabel" name="label_data_dpi">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QLabel" name="l_exif_ffprobe_title">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>910</x>
|
|
||||||
<y>550</y>
|
|
||||||
<width>371</width>
|
|
||||||
<height>16</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<pointsize>18</pointsize>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Exif / ffprobe Data</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QWidget" name="gridLayoutWidget_3">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>910</x>
|
|
||||||
<y>10</y>
|
|
||||||
<width>541</width>
|
|
||||||
<height>71</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLineEdit" name="eventName"/>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="labelEvent">
|
|
||||||
<property name="text">
|
|
||||||
<string>Event Label</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QWidget" name="gridLayoutWidget_4">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>910</x>
|
|
||||||
<y>90</y>
|
|
||||||
<width>221</width>
|
|
||||||
<height>41</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0" columnstretch="0,1">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<pointsize>18</pointsize>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Files Found</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QLCDNumber" name="lcd_files_found"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QWidget" name="gridLayoutWidget_5">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>1140</x>
|
|
||||||
<y>90</y>
|
|
||||||
<width>311</width>
|
|
||||||
<height>46</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout_3">
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QProgressBar" name="progressBar_overall">
|
|
||||||
<property name="value">
|
|
||||||
<number>24</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
|
||||||
<string>Current Progress</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="text">
|
|
||||||
<string>Overall Progress</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QProgressBar" name="progressBar_current">
|
|
||||||
<property name="value">
|
|
||||||
<number>24</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QLabel" name="img_preview">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>910</x>
|
|
||||||
<y>150</y>
|
|
||||||
<width>541</width>
|
|
||||||
<height>371</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="autoFillBackground">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="frameShape">
|
|
||||||
<enum>QFrame::StyledPanel</enum>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</widget>
|
|
||||||
<widget class="QMenuBar" name="menubar">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>1473</width>
|
|
||||||
<height>24</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<widget class="QMenu" name="menuBit_Mover">
|
|
||||||
<property name="title">
|
|
||||||
<string>Bit Mover</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<addaction name="menuBit_Mover"/>
|
|
||||||
</widget>
|
|
||||||
<widget class="QStatusBar" name="statusbar"/>
|
|
||||||
<action name="actionScan_Directory">
|
|
||||||
<property name="text">
|
|
||||||
<string>Scan Directory</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
|
@ -1,231 +0,0 @@
|
||||||
# Form implementation generated from reading ui file 'BitMover.ui'
|
|
||||||
#
|
|
||||||
# Created by: PyQt6 UI code generator 6.4.2
|
|
||||||
#
|
|
||||||
# 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(1473, 928)
|
|
||||||
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
|
|
||||||
self.centralwidget.setObjectName("centralwidget")
|
|
||||||
self.gridLayoutWidget = QtWidgets.QWidget(parent=self.centralwidget)
|
|
||||||
self.gridLayoutWidget.setGeometry(QtCore.QRect(20, 10, 871, 121))
|
|
||||||
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
|
|
||||||
self.grid_dir_selector = QtWidgets.QGridLayout(self.gridLayoutWidget)
|
|
||||||
self.grid_dir_selector.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.grid_dir_selector.setObjectName("grid_dir_selector")
|
|
||||||
self.label_1_src_dir = QtWidgets.QLabel(parent=self.gridLayoutWidget)
|
|
||||||
self.label_1_src_dir.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop)
|
|
||||||
self.label_1_src_dir.setObjectName("label_1_src_dir")
|
|
||||||
self.grid_dir_selector.addWidget(self.label_1_src_dir, 1, 0, 1, 1)
|
|
||||||
self.pushButton_src_browse = QtWidgets.QPushButton(parent=self.gridLayoutWidget)
|
|
||||||
self.pushButton_src_browse.setObjectName("pushButton_src_browse")
|
|
||||||
self.grid_dir_selector.addWidget(self.pushButton_src_browse, 1, 2, 1, 1)
|
|
||||||
self.lineEdit_dst_dir = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
|
|
||||||
self.lineEdit_dst_dir.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop)
|
|
||||||
self.lineEdit_dst_dir.setObjectName("lineEdit_dst_dir")
|
|
||||||
self.grid_dir_selector.addWidget(self.lineEdit_dst_dir, 2, 1, 1, 1)
|
|
||||||
self.label_2_dst_dir = QtWidgets.QLabel(parent=self.gridLayoutWidget)
|
|
||||||
self.label_2_dst_dir.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop)
|
|
||||||
self.label_2_dst_dir.setObjectName("label_2_dst_dir")
|
|
||||||
self.grid_dir_selector.addWidget(self.label_2_dst_dir, 2, 0, 1, 1)
|
|
||||||
self.lineEdit_src_dir = QtWidgets.QLineEdit(parent=self.gridLayoutWidget)
|
|
||||||
self.lineEdit_src_dir.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop)
|
|
||||||
self.lineEdit_src_dir.setObjectName("lineEdit_src_dir")
|
|
||||||
self.grid_dir_selector.addWidget(self.lineEdit_src_dir, 1, 1, 1, 1)
|
|
||||||
self.pushButton_dst_browse = QtWidgets.QPushButton(parent=self.gridLayoutWidget)
|
|
||||||
self.pushButton_dst_browse.setObjectName("pushButton_dst_browse")
|
|
||||||
self.grid_dir_selector.addWidget(self.pushButton_dst_browse, 2, 2, 1, 1)
|
|
||||||
self.pushButton_3_scan_dir = QtWidgets.QPushButton(parent=self.gridLayoutWidget)
|
|
||||||
self.pushButton_3_scan_dir.setBaseSize(QtCore.QSize(0, 0))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setPointSize(16)
|
|
||||||
self.pushButton_3_scan_dir.setFont(font)
|
|
||||||
icon = QtGui.QIcon.fromTheme("edit-find")
|
|
||||||
self.pushButton_3_scan_dir.setIcon(icon)
|
|
||||||
self.pushButton_3_scan_dir.setCheckable(False)
|
|
||||||
self.pushButton_3_scan_dir.setFlat(False)
|
|
||||||
self.pushButton_3_scan_dir.setObjectName("pushButton_3_scan_dir")
|
|
||||||
self.grid_dir_selector.addWidget(self.pushButton_3_scan_dir, 3, 0, 1, 1)
|
|
||||||
self.file_list = QtWidgets.QListWidget(parent=self.centralwidget)
|
|
||||||
self.file_list.setGeometry(QtCore.QRect(20, 150, 871, 701))
|
|
||||||
self.file_list.setObjectName("file_list")
|
|
||||||
self.gridLayoutWidget_2 = QtWidgets.QWidget(parent=self.centralwidget)
|
|
||||||
self.gridLayoutWidget_2.setGeometry(QtCore.QRect(910, 580, 551, 251))
|
|
||||||
self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2")
|
|
||||||
self.grid_metadata = QtWidgets.QGridLayout(self.gridLayoutWidget_2)
|
|
||||||
self.grid_metadata.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.grid_metadata.setHorizontalSpacing(20)
|
|
||||||
self.grid_metadata.setObjectName("grid_metadata")
|
|
||||||
self.l_camera = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.l_camera.setObjectName("l_camera")
|
|
||||||
self.grid_metadata.addWidget(self.l_camera, 6, 0, 1, 1)
|
|
||||||
self.l_iso = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.l_iso.setObjectName("l_iso")
|
|
||||||
self.grid_metadata.addWidget(self.l_iso, 4, 0, 1, 1)
|
|
||||||
self.label_data_width_height = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.label_data_width_height.setText("")
|
|
||||||
self.label_data_width_height.setObjectName("label_data_width_height")
|
|
||||||
self.grid_metadata.addWidget(self.label_data_width_height, 1, 1, 1, 1)
|
|
||||||
self.l_date_time_created = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.l_date_time_created.setObjectName("l_date_time_created")
|
|
||||||
self.grid_metadata.addWidget(self.l_date_time_created, 0, 0, 1, 1)
|
|
||||||
self.l_lens = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.l_lens.setObjectName("l_lens")
|
|
||||||
self.grid_metadata.addWidget(self.l_lens, 7, 0, 1, 1)
|
|
||||||
self.l_dpi = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.l_dpi.setObjectName("l_dpi")
|
|
||||||
self.grid_metadata.addWidget(self.l_dpi, 2, 0, 1, 1)
|
|
||||||
self.l_aperture = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.l_aperture.setObjectName("l_aperture")
|
|
||||||
self.grid_metadata.addWidget(self.l_aperture, 5, 0, 1, 1)
|
|
||||||
self.label_data_iso = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.label_data_iso.setText("")
|
|
||||||
self.label_data_iso.setObjectName("label_data_iso")
|
|
||||||
self.grid_metadata.addWidget(self.label_data_iso, 4, 1, 1, 1)
|
|
||||||
self.label_data_aperture = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.label_data_aperture.setText("")
|
|
||||||
self.label_data_aperture.setObjectName("label_data_aperture")
|
|
||||||
self.grid_metadata.addWidget(self.label_data_aperture, 5, 1, 1, 1)
|
|
||||||
self.label_data_lens = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.label_data_lens.setText("")
|
|
||||||
self.label_data_lens.setObjectName("label_data_lens")
|
|
||||||
self.grid_metadata.addWidget(self.label_data_lens, 7, 1, 1, 1)
|
|
||||||
self.l_megapixels = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.l_megapixels.setObjectName("l_megapixels")
|
|
||||||
self.grid_metadata.addWidget(self.l_megapixels, 3, 0, 1, 1)
|
|
||||||
self.label_data_megapixels = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.label_data_megapixels.setText("")
|
|
||||||
self.label_data_megapixels.setObjectName("label_data_megapixels")
|
|
||||||
self.grid_metadata.addWidget(self.label_data_megapixels, 3, 1, 1, 1)
|
|
||||||
self.l_width_height = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.l_width_height.setObjectName("l_width_height")
|
|
||||||
self.grid_metadata.addWidget(self.l_width_height, 1, 0, 1, 1)
|
|
||||||
self.label_data_date_time_created = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.label_data_date_time_created.setText("")
|
|
||||||
self.label_data_date_time_created.setObjectName("label_data_date_time_created")
|
|
||||||
self.grid_metadata.addWidget(self.label_data_date_time_created, 0, 1, 1, 1)
|
|
||||||
self.label_data_camera = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.label_data_camera.setText("")
|
|
||||||
self.label_data_camera.setObjectName("label_data_camera")
|
|
||||||
self.grid_metadata.addWidget(self.label_data_camera, 6, 1, 1, 1)
|
|
||||||
self.l_zoom = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.l_zoom.setObjectName("l_zoom")
|
|
||||||
self.grid_metadata.addWidget(self.l_zoom, 8, 0, 1, 1)
|
|
||||||
self.label_data_zoom = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.label_data_zoom.setText("")
|
|
||||||
self.label_data_zoom.setObjectName("label_data_zoom")
|
|
||||||
self.grid_metadata.addWidget(self.label_data_zoom, 8, 1, 1, 1)
|
|
||||||
self.label_data_dpi = QtWidgets.QLabel(parent=self.gridLayoutWidget_2)
|
|
||||||
self.label_data_dpi.setText("")
|
|
||||||
self.label_data_dpi.setObjectName("label_data_dpi")
|
|
||||||
self.grid_metadata.addWidget(self.label_data_dpi, 2, 1, 1, 1)
|
|
||||||
self.grid_metadata.setColumnStretch(1, 1)
|
|
||||||
self.l_exif_ffprobe_title = QtWidgets.QLabel(parent=self.centralwidget)
|
|
||||||
self.l_exif_ffprobe_title.setGeometry(QtCore.QRect(910, 550, 371, 16))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setPointSize(18)
|
|
||||||
font.setBold(True)
|
|
||||||
self.l_exif_ffprobe_title.setFont(font)
|
|
||||||
self.l_exif_ffprobe_title.setObjectName("l_exif_ffprobe_title")
|
|
||||||
self.gridLayoutWidget_3 = QtWidgets.QWidget(parent=self.centralwidget)
|
|
||||||
self.gridLayoutWidget_3.setGeometry(QtCore.QRect(910, 10, 541, 71))
|
|
||||||
self.gridLayoutWidget_3.setObjectName("gridLayoutWidget_3")
|
|
||||||
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget_3)
|
|
||||||
self.gridLayout.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.gridLayout.setObjectName("gridLayout")
|
|
||||||
self.eventName = QtWidgets.QLineEdit(parent=self.gridLayoutWidget_3)
|
|
||||||
self.eventName.setObjectName("eventName")
|
|
||||||
self.gridLayout.addWidget(self.eventName, 1, 0, 1, 1)
|
|
||||||
self.labelEvent = QtWidgets.QLabel(parent=self.gridLayoutWidget_3)
|
|
||||||
self.labelEvent.setObjectName("labelEvent")
|
|
||||||
self.gridLayout.addWidget(self.labelEvent, 0, 0, 1, 1)
|
|
||||||
self.gridLayoutWidget_4 = QtWidgets.QWidget(parent=self.centralwidget)
|
|
||||||
self.gridLayoutWidget_4.setGeometry(QtCore.QRect(910, 90, 221, 41))
|
|
||||||
self.gridLayoutWidget_4.setObjectName("gridLayoutWidget_4")
|
|
||||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.gridLayoutWidget_4)
|
|
||||||
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
|
||||||
self.label_3 = QtWidgets.QLabel(parent=self.gridLayoutWidget_4)
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setPointSize(18)
|
|
||||||
self.label_3.setFont(font)
|
|
||||||
self.label_3.setObjectName("label_3")
|
|
||||||
self.gridLayout_2.addWidget(self.label_3, 0, 0, 1, 1)
|
|
||||||
self.lcd_files_found = QtWidgets.QLCDNumber(parent=self.gridLayoutWidget_4)
|
|
||||||
self.lcd_files_found.setObjectName("lcd_files_found")
|
|
||||||
self.gridLayout_2.addWidget(self.lcd_files_found, 0, 1, 1, 1)
|
|
||||||
self.gridLayout_2.setColumnStretch(1, 1)
|
|
||||||
self.gridLayoutWidget_5 = QtWidgets.QWidget(parent=self.centralwidget)
|
|
||||||
self.gridLayoutWidget_5.setGeometry(QtCore.QRect(1140, 90, 311, 46))
|
|
||||||
self.gridLayoutWidget_5.setObjectName("gridLayoutWidget_5")
|
|
||||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.gridLayoutWidget_5)
|
|
||||||
self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
|
||||||
self.progressBar_overall = QtWidgets.QProgressBar(parent=self.gridLayoutWidget_5)
|
|
||||||
self.progressBar_overall.setProperty("value", 0)
|
|
||||||
self.progressBar_overall.setObjectName("progressBar_overall")
|
|
||||||
self.gridLayout_3.addWidget(self.progressBar_overall, 0, 1, 1, 1)
|
|
||||||
self.label_2 = QtWidgets.QLabel(parent=self.gridLayoutWidget_5)
|
|
||||||
self.label_2.setObjectName("label_2")
|
|
||||||
self.gridLayout_3.addWidget(self.label_2, 1, 0, 1, 1)
|
|
||||||
self.label = QtWidgets.QLabel(parent=self.gridLayoutWidget_5)
|
|
||||||
self.label.setObjectName("label")
|
|
||||||
self.gridLayout_3.addWidget(self.label, 0, 0, 1, 1)
|
|
||||||
self.progressBar_current = QtWidgets.QProgressBar(parent=self.gridLayoutWidget_5)
|
|
||||||
self.progressBar_current.setProperty("value", 0)
|
|
||||||
self.progressBar_current.setObjectName("progressBar_current")
|
|
||||||
self.gridLayout_3.addWidget(self.progressBar_current, 1, 1, 1, 1)
|
|
||||||
self.img_preview = QtWidgets.QLabel(parent=self.centralwidget)
|
|
||||||
self.img_preview.setGeometry(QtCore.QRect(910, 150, 541, 371))
|
|
||||||
self.img_preview.setAutoFillBackground(True)
|
|
||||||
self.img_preview.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
|
|
||||||
self.img_preview.setText("")
|
|
||||||
self.img_preview.setObjectName("img_preview")
|
|
||||||
MainWindow.setCentralWidget(self.centralwidget)
|
|
||||||
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
|
|
||||||
self.menubar.setGeometry(QtCore.QRect(0, 0, 1473, 24))
|
|
||||||
self.menubar.setObjectName("menubar")
|
|
||||||
self.menuBit_Mover = QtWidgets.QMenu(parent=self.menubar)
|
|
||||||
self.menuBit_Mover.setObjectName("menuBit_Mover")
|
|
||||||
MainWindow.setMenuBar(self.menubar)
|
|
||||||
self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
|
|
||||||
self.statusbar.setObjectName("statusbar")
|
|
||||||
MainWindow.setStatusBar(self.statusbar)
|
|
||||||
self.actionScan_Directory = QtGui.QAction(parent=MainWindow)
|
|
||||||
self.actionScan_Directory.setObjectName("actionScan_Directory")
|
|
||||||
self.menubar.addAction(self.menuBit_Mover.menuAction())
|
|
||||||
|
|
||||||
self.retranslateUi(MainWindow)
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
|
||||||
|
|
||||||
def retranslateUi(self, MainWindow):
|
|
||||||
_translate = QtCore.QCoreApplication.translate
|
|
||||||
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
|
|
||||||
self.label_1_src_dir.setText(_translate("MainWindow", "Source Directory"))
|
|
||||||
self.pushButton_src_browse.setText(_translate("MainWindow", "Browse"))
|
|
||||||
self.label_2_dst_dir.setText(_translate("MainWindow", "Destination Directory"))
|
|
||||||
self.pushButton_dst_browse.setText(_translate("MainWindow", "Browse"))
|
|
||||||
self.pushButton_3_scan_dir.setText(_translate("MainWindow", "Scan Directory"))
|
|
||||||
self.l_camera.setText(_translate("MainWindow", "Camera"))
|
|
||||||
self.l_iso.setText(_translate("MainWindow", "ISO"))
|
|
||||||
self.l_date_time_created.setText(_translate("MainWindow", "Date / Time Created"))
|
|
||||||
self.l_lens.setText(_translate("MainWindow", "Lens"))
|
|
||||||
self.l_dpi.setText(_translate("MainWindow", "Resolution (DPI)"))
|
|
||||||
self.l_aperture.setText(_translate("MainWindow", "Aperture"))
|
|
||||||
self.l_megapixels.setText(_translate("MainWindow", "Megapixels"))
|
|
||||||
self.l_width_height.setText(_translate("MainWindow", "Width / Height"))
|
|
||||||
self.l_zoom.setText(_translate("MainWindow", "Zoom"))
|
|
||||||
self.l_exif_ffprobe_title.setText(_translate("MainWindow", "Exif / ffprobe Data"))
|
|
||||||
self.labelEvent.setText(_translate("MainWindow", "Event Label"))
|
|
||||||
self.label_3.setText(_translate("MainWindow", "Files Found"))
|
|
||||||
self.label_2.setText(_translate("MainWindow", "Current Progress"))
|
|
||||||
self.label.setText(_translate("MainWindow", "Overall Progress"))
|
|
||||||
self.menuBit_Mover.setTitle(_translate("MainWindow", "Bit Mover"))
|
|
||||||
self.actionScan_Directory.setText(_translate("MainWindow", "Scan Directory"))
|
|
312
BitMover_ui.py
312
BitMover_ui.py
|
@ -1,312 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
# import time
|
|
||||||
|
|
||||||
from PyQt6.QtCore import *
|
|
||||||
from PyQt6.QtGui import *
|
|
||||||
from PyQt6.QtWidgets import *
|
|
||||||
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from configure import CONFIG_FILE, Configure
|
|
||||||
from file_stuff import is_file, get_t_files
|
|
||||||
from BitMover_MainWindow import Ui_MainWindow
|
|
||||||
|
|
||||||
from get_image_tag import get_exif_tag
|
|
||||||
|
|
||||||
from media import Media
|
|
||||||
|
|
||||||
|
|
||||||
from lumberjack import timber
|
|
||||||
from raw_photo import extract_jpg_thumb
|
|
||||||
|
|
||||||
log = timber(__name__)
|
|
||||||
|
|
||||||
class WorkerSignals(QObject):
|
|
||||||
"""
|
|
||||||
Defines the signals available from a running worker thread.
|
|
||||||
|
|
||||||
Supported signals are:
|
|
||||||
|
|
||||||
finished
|
|
||||||
No data
|
|
||||||
|
|
||||||
error
|
|
||||||
tuple (exctype, value, traceback.format_exc() )
|
|
||||||
|
|
||||||
result
|
|
||||||
object data returned from processing, anything
|
|
||||||
|
|
||||||
progress
|
|
||||||
int indicating % progress
|
|
||||||
|
|
||||||
"""
|
|
||||||
finished = pyqtSignal()
|
|
||||||
error = pyqtSignal(tuple)
|
|
||||||
result = pyqtSignal(object)
|
|
||||||
progress = pyqtSignal(int)
|
|
||||||
|
|
||||||
|
|
||||||
class Worker(QRunnable):
|
|
||||||
"""
|
|
||||||
Worker thread
|
|
||||||
|
|
||||||
Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
|
|
||||||
|
|
||||||
:param callback: The function callback to run on this worker thread. Supplied args and
|
|
||||||
kwargs will be passed through to the runner.
|
|
||||||
:type callback: function
|
|
||||||
:param args: Arguments to pass to the callback function
|
|
||||||
:param kwargs: Keywords to pass to the callback function
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, fn, *args, **kwargs):
|
|
||||||
super(Worker, self).__init__()
|
|
||||||
|
|
||||||
# Store constructor arguments (re-used for processing)
|
|
||||||
self.fn = fn
|
|
||||||
self.args = args
|
|
||||||
self.kwargs = kwargs
|
|
||||||
self.signals = WorkerSignals()
|
|
||||||
|
|
||||||
# Add the callback to our kwargs
|
|
||||||
self.kwargs['progress_callback'] = self.signals.progress
|
|
||||||
|
|
||||||
@pyqtSlot()
|
|
||||||
def run(self):
|
|
||||||
"""
|
|
||||||
Initialise the runner function with passed args, kwargs.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Retrieve args/kwargs here; and fire processing using them
|
|
||||||
try:
|
|
||||||
result = self.fn(*self.args, **self.kwargs)
|
|
||||||
except:
|
|
||||||
traceback.print_exc()
|
|
||||||
exctype, value = sys.exc_info()[:2]
|
|
||||||
self.signals.error.emit((exctype, value, traceback.format_exc()))
|
|
||||||
else:
|
|
||||||
self.signals.result.emit(result) # Return the result of the processing
|
|
||||||
finally:
|
|
||||||
self.signals.finished.emit() # Done
|
|
||||||
|
|
||||||
# Subclass QMainWindow to customize your application's main window
|
|
||||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(MainWindow,self).__init__(*args,**kwargs)
|
|
||||||
self.setupUi(self)
|
|
||||||
# uic.loadUi('BitMover.ui',self)
|
|
||||||
c = Configure(CONFIG_FILE)
|
|
||||||
self.config = c.load_config()
|
|
||||||
self.total_files = 0
|
|
||||||
|
|
||||||
# self.t_find_files = threading.Thread(target=self.find_files)
|
|
||||||
|
|
||||||
self.setWindowTitle("BitMover")
|
|
||||||
self.setWindowIcon(QIcon('assets/forklift.png'))
|
|
||||||
|
|
||||||
self.src_dir = self.config['folders']['source']['base']
|
|
||||||
self.dst_dir = self.config['folders']['destination']['base']
|
|
||||||
|
|
||||||
self.file_types = self.config['file_types']
|
|
||||||
|
|
||||||
self.lineEdit_src_dir.setText(self.src_dir)
|
|
||||||
self.lineEdit_dst_dir.setText(self.dst_dir)
|
|
||||||
|
|
||||||
self.pushButton_src_browse.clicked.connect(self.select_src_directory)
|
|
||||||
self.pushButton_dst_browse.clicked.connect(self.select_dst_directory)
|
|
||||||
self.pushButton_3_scan_dir.clicked.connect(self.find_files)
|
|
||||||
# self.pushButton_3_scan_dir.clicked.connect(self.t_find_files.start)
|
|
||||||
self.lcd_files_found.display(int(0))
|
|
||||||
self.set_progress(0,0)
|
|
||||||
|
|
||||||
self.img_preview.setPixmap(QPixmap('assets/preview_placeholder.jpg'))
|
|
||||||
self.img_preview.setScaledContents(True)
|
|
||||||
# self.img_preview.setFixedWidth(preview_width)
|
|
||||||
# self.img_preview.setFixedHeight(preview_height)
|
|
||||||
|
|
||||||
self.file_list.currentItemChanged.connect(self.index_changed)
|
|
||||||
self.threadpool = QThreadPool()
|
|
||||||
|
|
||||||
self.files = {}
|
|
||||||
|
|
||||||
def index_changed(self,i):
|
|
||||||
f = i.text()
|
|
||||||
|
|
||||||
event = self.get_event()
|
|
||||||
c = self.config
|
|
||||||
|
|
||||||
m = Media(f,event,c)
|
|
||||||
|
|
||||||
dtc = f'{m.capture_date[0]}/{m.capture_date[1]}/{m.capture_date[2]}'
|
|
||||||
self.label_data_date_time_created.setText(dtc)
|
|
||||||
|
|
||||||
if m.file_type == 'image':
|
|
||||||
# img = Image.open(f)
|
|
||||||
dpi = get_exif_tag(f,"xresolution")
|
|
||||||
width = get_exif_tag(f,"image width")
|
|
||||||
height = get_exif_tag(f,"image height")
|
|
||||||
if width is None:
|
|
||||||
get_exif_tag(f,"exifimagewidth")
|
|
||||||
if height is None:
|
|
||||||
get_exif_tag(f,"exifimagelength")
|
|
||||||
# width = img.width
|
|
||||||
# height = img.height
|
|
||||||
size = f'{width}x{height}'
|
|
||||||
if width is not None and height is not None:
|
|
||||||
mpixels = (width * height) / 1000000
|
|
||||||
else:
|
|
||||||
mpixels = ''
|
|
||||||
iso = get_exif_tag(f,"iso")
|
|
||||||
aperture = get_exif_tag(f,"fnumber")
|
|
||||||
camera = get_exif_tag(f,"cameramodelname")
|
|
||||||
if camera is None:
|
|
||||||
camera = get_exif_tag(f,"image model")
|
|
||||||
lens = get_exif_tag(f,"lensmodel")
|
|
||||||
zoom = get_exif_tag(f,"focallength")
|
|
||||||
|
|
||||||
print(f'size: {size}')
|
|
||||||
print(f'dpi: {dpi}')
|
|
||||||
print(f'iso: {iso}')
|
|
||||||
print(f'lens: {lens}')
|
|
||||||
print(f'zoom: {zoom}')
|
|
||||||
print(f'camera: {camera}')
|
|
||||||
print(f'aperture: {aperture}')
|
|
||||||
print(f'mpixels: {mpixels}')
|
|
||||||
|
|
||||||
self.label_data_width_height.setText(str(size))
|
|
||||||
self.label_data_dpi.setText(str(dpi))
|
|
||||||
self.label_data_iso.setText(str(iso))
|
|
||||||
self.label_data_lens.setText(str(lens))
|
|
||||||
self.label_data_zoom.setText(str(zoom))
|
|
||||||
self.label_data_camera.setText(str(camera))
|
|
||||||
self.label_data_aperture.setText(str(aperture))
|
|
||||||
self.label_data_megapixels.setText(str(mpixels))
|
|
||||||
|
|
||||||
if f.lower().endswith("jpg") or f.lower().endswith("jpeg"):
|
|
||||||
self.img_preview.setPixmap(QPixmap(f))
|
|
||||||
else:
|
|
||||||
# jpg = img.convert("RGB")
|
|
||||||
jpg = extract_jpg_thumb(f)
|
|
||||||
self.img_preview.setPixmap(QPixmap(jpg))
|
|
||||||
|
|
||||||
|
|
||||||
def select_src_directory(self):
|
|
||||||
directory = QFileDialog.getExistingDirectory(self,
|
|
||||||
"Select Directory",
|
|
||||||
self.src_dir)
|
|
||||||
if directory:
|
|
||||||
print("Selected Directory:", directory)
|
|
||||||
# path = Path(directory)
|
|
||||||
self.src_dir = directory
|
|
||||||
self.lineEdit_src_dir.setText(self.src_dir)
|
|
||||||
|
|
||||||
def select_dst_directory(self):
|
|
||||||
directory = QFileDialog.getExistingDirectory(self,
|
|
||||||
"Select Directory",
|
|
||||||
self.dst_dir)
|
|
||||||
if directory:
|
|
||||||
print("Selected Directory:", directory)
|
|
||||||
# path = Path(directory)
|
|
||||||
self.dst_dir = directory
|
|
||||||
self.lineEdit_dst_dir.setText(self.dst_dir)
|
|
||||||
|
|
||||||
def set_progress(self,p,t):
|
|
||||||
"""
|
|
||||||
set progress for bar,
|
|
||||||
p = progress counter
|
|
||||||
t = target total
|
|
||||||
o = progress bar object
|
|
||||||
"""
|
|
||||||
if int(t) == 0:
|
|
||||||
t += 1
|
|
||||||
#
|
|
||||||
# while QPainter.isActive(Ui_MainWindow.QPainter()):
|
|
||||||
# print('painter active')
|
|
||||||
# time.sleep(0.02)
|
|
||||||
|
|
||||||
percent_complete = (int(p) / int(t)) * 100
|
|
||||||
self.progressBar_overall.setValue(int(percent_complete))
|
|
||||||
|
|
||||||
def print_output(self, s):
|
|
||||||
print(s)
|
|
||||||
|
|
||||||
def thread_complete(self):
|
|
||||||
print("THREAD COMPLETE!")
|
|
||||||
|
|
||||||
def thread_find_files(self):
|
|
||||||
print('in thread_find_files')
|
|
||||||
print(self.src_dir)
|
|
||||||
worker = Worker(self.find_files())
|
|
||||||
worker.signals.result.connect(self.print_output)
|
|
||||||
worker.signals.finished.connect(self.thread_complete)
|
|
||||||
worker.signals.progress.connect(self.progress_fn)
|
|
||||||
self.threadpool.start(worker)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def find_files(self):
|
|
||||||
""" find files to build a dictionary out of """
|
|
||||||
log.info('In find_files')
|
|
||||||
print(self.src_dir)
|
|
||||||
|
|
||||||
file_count = int(0)
|
|
||||||
file_total = get_t_files(self.src_dir,self.file_types)
|
|
||||||
|
|
||||||
for folder, subfolders, filename in os.walk(self.src_dir):
|
|
||||||
for f_type in self.file_types:
|
|
||||||
for ext in self.file_types[f_type]:
|
|
||||||
# for file in tqdm(filename,
|
|
||||||
# desc='Finding ' + ext + ' Files in ' + folder):
|
|
||||||
for file in filename:
|
|
||||||
if file.lower().endswith(ext):
|
|
||||||
current_file = os.path.join(folder, file)
|
|
||||||
if is_file(current_file):
|
|
||||||
file_count += int(1)
|
|
||||||
self.process_file(folder, file)
|
|
||||||
self.set_progress(file_count,file_total)
|
|
||||||
# time.sleep(.02)
|
|
||||||
|
|
||||||
else:
|
|
||||||
print(f"Skipping {current_file} as it does not look like a real file.")
|
|
||||||
|
|
||||||
progress_callback.emit((file_count / file_total) * 100)
|
|
||||||
|
|
||||||
def get_event(self):
|
|
||||||
event_name = self.eventName.text()
|
|
||||||
return event_name
|
|
||||||
|
|
||||||
def process_file(self,path_name, f_name):
|
|
||||||
""" gather information and add to dictionary """
|
|
||||||
|
|
||||||
event = self.get_event()
|
|
||||||
c = self.config
|
|
||||||
|
|
||||||
log.debug(f'process_file({path_name}, {f_name}, {event}, {c})')
|
|
||||||
|
|
||||||
m = Media(os.path.join(path_name,f_name),event, c)
|
|
||||||
i = m.source_path_hash
|
|
||||||
log.debug(f'Source Path Hash: {i}')
|
|
||||||
|
|
||||||
self.files[i] = { 'folders': {}, 'date': {} }
|
|
||||||
|
|
||||||
self.files[i]['folders']['source_path'] = m.source_path_dir
|
|
||||||
self.files[i]['type'] = m.file_type
|
|
||||||
self.files[i]['name'] = m.file_name
|
|
||||||
self.files[i]['extension'] = m.file_ext
|
|
||||||
self.files[i]['date']['capture_date'] = {}
|
|
||||||
self.files[i]['date']['capture_date']['y'] = m.capture_date[0]
|
|
||||||
self.files[i]['date']['capture_date']['m'] = m.capture_date[1]
|
|
||||||
self.files[i]['date']['capture_date']['d'] = m.capture_date[2]
|
|
||||||
self.files[i]['folders']['destination'] = m.destination_path
|
|
||||||
self.files[i]['folders']['destination_original'] = m.destination_originals_path
|
|
||||||
self.file_list.addItem(f"{self.files[i]['folders']['source_path']}/{self.files[i]['name']}")
|
|
||||||
|
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
|
|
||||||
window = MainWindow()
|
|
||||||
window.show()
|
|
||||||
|
|
||||||
app.exec()
|
|
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.9 MiB |
85
bitmover.py
85
bitmover.py
|
@ -1,85 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
"""
|
|
||||||
Functions to copy bits around ...
|
|
||||||
"""
|
|
||||||
|
|
||||||
from os import system,path,rename
|
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
from file_stuff import create_folder, cmp_files, path_exists
|
|
||||||
from lumberjack import timber
|
|
||||||
from hashing import xx_hash
|
|
||||||
# from configure import Configure, CONFIG_FILE
|
|
||||||
|
|
||||||
# c = Configure(CONFIG_FILE)
|
|
||||||
# config = c.load_config()
|
|
||||||
log = timber(__name__)
|
|
||||||
|
|
||||||
def copy_with_progress(s,d,f):
|
|
||||||
""" Copy a file with the progress bar """
|
|
||||||
log.debug(f'copy_with_progress({s},{d},{f})')
|
|
||||||
|
|
||||||
size = path.getsize(s)
|
|
||||||
with open(s, 'rb') as fs:
|
|
||||||
with open(d, 'wb') as fd:
|
|
||||||
with tqdm(total=size, unit='B', unit_scale=True, desc=f'Copying {f}') as pbar:
|
|
||||||
while True:
|
|
||||||
chunk = fs.read(4096)
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
fd.write(chunk)
|
|
||||||
pbar.update(len(chunk))
|
|
||||||
|
|
||||||
def copy_from_source(source_path,dest_path,file_name):
|
|
||||||
""" Copy file from source to destination """
|
|
||||||
log.debug(f'copy_from_source({source_path},{dest_path},{file_name}')
|
|
||||||
|
|
||||||
file_exists = path_exists(path.join(dest_path,file_name))
|
|
||||||
|
|
||||||
if file_exists is True:
|
|
||||||
log.debug(f'\nFound {file_name} at destination, checking if they match.')
|
|
||||||
check_match = cmp_files(
|
|
||||||
path.join(source_path,file_name),
|
|
||||||
path.join(dest_path, file_name))
|
|
||||||
if check_match is False:
|
|
||||||
log.warn(f'\nFound duplicate for {source_path}/{file_name}, \
|
|
||||||
renaming destination with hash appended.')
|
|
||||||
base, extension = path.splitext(file_name)
|
|
||||||
#md5 = md5_hash(os.path.join(dest_path, file_name))
|
|
||||||
f_xxhash = xx_hash(path.join(dest_path, file_name))
|
|
||||||
#file_name_hash = base + '_' + md5 + extension
|
|
||||||
file_name_hash = base + '_' + f_xxhash + extension
|
|
||||||
rename(path.join(dest_path, file_name),
|
|
||||||
path.join(dest_path, file_name_hash))
|
|
||||||
else:
|
|
||||||
log.info(f'\n{file_name} hashes match')
|
|
||||||
# remove(path.join(source_path,file_name))
|
|
||||||
# f.pop(file_name)
|
|
||||||
return
|
|
||||||
|
|
||||||
# create_folder(dest_path)
|
|
||||||
# shutil.copy(os.path.join(source_path,file_name), dest_path)
|
|
||||||
copy_with_progress(path.join(source_path, file_name),
|
|
||||||
path.join(dest_path, file_name),
|
|
||||||
file_name)
|
|
||||||
system('clear')
|
|
||||||
|
|
||||||
def copy_files(f,config):
|
|
||||||
""" Copy Files. """
|
|
||||||
log.debug(f'copy_files({f})')
|
|
||||||
system('clear')
|
|
||||||
for file in tqdm(f, desc="Copying Files:"):
|
|
||||||
create_folder(f[file]['folders']['destination'])
|
|
||||||
|
|
||||||
copy_from_source(f[file]['folders']['source_path'],
|
|
||||||
f[file]['folders']['destination'],
|
|
||||||
f[file]['name'])
|
|
||||||
|
|
||||||
if config['store_originals'] is True:
|
|
||||||
if f[file]['type'] == 'image':
|
|
||||||
create_folder(f[file]['folders']['destination_original'])
|
|
||||||
|
|
||||||
copy_from_source(f[file]['folders']['destination'],
|
|
||||||
f[file]['folders']['destination_original'],
|
|
||||||
f[file]['name'])
|
|
|
@ -1,26 +0,0 @@
|
||||||
---
|
|
||||||
folders:
|
|
||||||
destination:
|
|
||||||
base: '/path/to/DESTINATION'
|
|
||||||
source:
|
|
||||||
base: '/path/to/SD_CARD'
|
|
||||||
originals: 'Originals' # Concatenated onto destination folder to keep a local 'clean' copy
|
|
||||||
backup: '/Volumes/Multimedia' # A path to a separate disk or NAS to store a backup copy
|
|
||||||
store_originals: TRUE
|
|
||||||
store_backup: TRUE
|
|
||||||
cleanup_sd: FALSE # Delete files from the SD after checksum validation
|
|
||||||
file_types:
|
|
||||||
image:
|
|
||||||
- 'jpg'
|
|
||||||
- 'jpeg'
|
|
||||||
- 'raw'
|
|
||||||
- 'dng'
|
|
||||||
- 'rw2'
|
|
||||||
- 'arw'
|
|
||||||
- 'nef'
|
|
||||||
video:
|
|
||||||
- 'mov'
|
|
||||||
- 'mp4'
|
|
||||||
audio:
|
|
||||||
- 'wav'
|
|
||||||
|
|
31
configure.py
31
configure.py
|
@ -1,31 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
"""
|
|
||||||
Load the config file from yaml file
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import yaml
|
|
||||||
from lumberjack import timber
|
|
||||||
|
|
||||||
files = {}
|
|
||||||
CONFIG_FILE = 'config.yaml'
|
|
||||||
log = timber(__name__)
|
|
||||||
|
|
||||||
class Configure:
|
|
||||||
""" Configure Class """
|
|
||||||
def __init__(self,config_file):
|
|
||||||
""" init """
|
|
||||||
self.config_file = config_file
|
|
||||||
self.config = ''
|
|
||||||
|
|
||||||
def load_config(self):
|
|
||||||
""" load configuration from yaml file """
|
|
||||||
try:
|
|
||||||
with open(self.config_file, 'r', encoding="utf-8") as cf:
|
|
||||||
self.config = yaml.load(cf, Loader=yaml.FullLoader)
|
|
||||||
except FileNotFoundError as fnf_err:
|
|
||||||
print(f'{fnf_err}: {self.config_file}') # This cannot be log b/c we haven't validated the logger yet.
|
|
||||||
print(f'Copy config.yaml.EXAMPLE to {self.config_file}, and update accordingly.') # This cannot be log b/c we haven't validated the logger yet.
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
return self.config
|
|
92
dedup.py
92
dedup.py
|
@ -1,92 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
from bitmover import copy_from_source
|
|
||||||
from file_stuff import create_folder, cmp_files
|
|
||||||
from lumberjack import timber
|
|
||||||
|
|
||||||
log = timber(__name__)
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("-f", "--folder", help = "folder with files to rename")
|
|
||||||
parser.add_argument("-d", "--dryrun", help = "dry run, no action")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.folder:
|
|
||||||
FOLDER = args.folder
|
|
||||||
else:
|
|
||||||
print("you need to specify a folder.")
|
|
||||||
sys.exit()
|
|
||||||
if args.dryrun:
|
|
||||||
dry_run = True
|
|
||||||
else:
|
|
||||||
dry_run = False
|
|
||||||
|
|
||||||
def get_file_size(f_name):
|
|
||||||
return os.path.getsize(f_name)
|
|
||||||
|
|
||||||
|
|
||||||
l = []
|
|
||||||
files = os.listdir(FOLDER)
|
|
||||||
dup_folder = os.path.join(FOLDER,"__dups")
|
|
||||||
SIZE = 0
|
|
||||||
MAX_SIZE = 0
|
|
||||||
BIGGEST_FILE = ''
|
|
||||||
|
|
||||||
create_folder(dup_folder)
|
|
||||||
f_list = {}
|
|
||||||
dictionary = {}
|
|
||||||
for x in files:
|
|
||||||
if os.path.isfile(os.path.join(FOLDER,x)):
|
|
||||||
if x.lower().endswith("jpg") or x.lower().endswith("jpeg"):
|
|
||||||
group = dictionary.get(x[:23],[])
|
|
||||||
group.append(x)
|
|
||||||
dictionary[x[:23]] = group
|
|
||||||
|
|
||||||
for g in dictionary:
|
|
||||||
f_list[g] = {'files': {}}
|
|
||||||
|
|
||||||
for f in dictionary[g]:
|
|
||||||
p = os.path.join(FOLDER,f)
|
|
||||||
size = os.path.getsize(p)
|
|
||||||
f_list[g]['files'][f] = {}
|
|
||||||
f_list[g]['files'][f]['path'] = p
|
|
||||||
f_list[g]['files'][f]['size'] = size
|
|
||||||
|
|
||||||
# print(f_list)
|
|
||||||
|
|
||||||
for g in f_list:
|
|
||||||
MAX_SIZE = 0
|
|
||||||
log.debug(g)
|
|
||||||
if len(f_list[g]['files']) > 1:
|
|
||||||
for f in f_list[g]['files']:
|
|
||||||
log.debug(f"{f_list[g]['files'][f]['path']}: {f_list[g]['files'][f]['size']}")
|
|
||||||
SIZE = f_list[g]['files'][f]['size']
|
|
||||||
|
|
||||||
if SIZE > MAX_SIZE:
|
|
||||||
MAX_SIZE = SIZE
|
|
||||||
BIGGEST_FILE = f_list[g]['files'][f]['path']
|
|
||||||
log.debug(f'New Biggest File: {BIGGEST_FILE}, {MAX_SIZE*1024}KB')
|
|
||||||
f_list[g]['biggest_file'] = BIGGEST_FILE
|
|
||||||
else:
|
|
||||||
log.debug(f'Only 1 file in {g}')
|
|
||||||
|
|
||||||
for g in f_list:
|
|
||||||
# log.debug(g)
|
|
||||||
if len(f_list[g]['files']) > 1:
|
|
||||||
for f in f_list[g]['files']:
|
|
||||||
if f_list[g]['biggest_file'] != f_list[g]['files'][f]['path']:
|
|
||||||
copy_from_source(FOLDER, dup_folder, os.path.basename(f_list[g]['files'][f]['path']))
|
|
||||||
|
|
||||||
file_match = cmp_files(f_list[g]['files'][f]['path'],
|
|
||||||
os.path.join(dup_folder,
|
|
||||||
os.path.basename(f_list[g]['files'][f]['path'])))
|
|
||||||
|
|
||||||
if file_match is True:
|
|
||||||
os.remove(f_list[g]['files'][f]['path'])
|
|
||||||
else:
|
|
||||||
print(f"{f_list[g]['files'][f]['path']} does not match {os.path.join(dup_folder, os.path.basename(f_list[g]['files'][f]['path']))}")
|
|
|
@ -1,78 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
|
|
||||||
root_dir="$1"
|
|
||||||
|
|
||||||
if [[ $root_dir == '' ]]; then
|
|
||||||
echo "You must pass a directory as the first argument."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
function dedup {
|
|
||||||
echo "renaming: $1"
|
|
||||||
./rename.py -f "$1"
|
|
||||||
|
|
||||||
echo "deduping: $1"
|
|
||||||
./dedup.py -f "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in `ls -1 $root_dir`; do
|
|
||||||
echo $i
|
|
||||||
if [[ -d "${root_dir}/$i" ]]; then
|
|
||||||
echo "${root_dir}/$i is a dir"
|
|
||||||
if [[ $i == *JPG* || $i == *RAW* ]]; then
|
|
||||||
echo "${root_dir}/$i contains JPG/RAW"
|
|
||||||
dedup ${root_dir}/$i
|
|
||||||
else
|
|
||||||
echo "${root_dir}/$i does not contain JPG/RAW"
|
|
||||||
for s1 in `ls -1 ${root_dir}/$i`; do
|
|
||||||
echo "$s1"
|
|
||||||
if [[ -d "${root_dir}/${i}/$s1" ]]; then
|
|
||||||
echo "${root_dir}/${i}/$s1 is a dir"
|
|
||||||
if [[ $s1 == *JPG* || $s1 == *RAW* ]]; then
|
|
||||||
echo "${root_dir}/${i}/$s1 contains JPG/RAW"
|
|
||||||
dedup ${root_dir}/${i}/$s1
|
|
||||||
else
|
|
||||||
echo "${root_dir}/${i}/$s1 does not contain JPG/RAW"
|
|
||||||
for s2 in `ls -1 ${root_dir}/${i}/$s1`; do
|
|
||||||
echo ${root_dir}/${i}/${s1}/$s2
|
|
||||||
if [[ -d "${root_dir}/${i}/${s1}/$s2" ]]; then
|
|
||||||
echo "${root_dir}/${i}/${s1}/$s2 is a dir"
|
|
||||||
if [[ ${root_dir}/${i}/${s1}/$s2 == *JPG* || ${root_dir}/${i}/${s1}/$s2 == *RAW* ]]; then
|
|
||||||
echo "${root_dir}/${i}/${s1}/$s2 contains JPG/RAW"
|
|
||||||
dedup ${root_dir}/${i}/${s1}/$s2
|
|
||||||
else
|
|
||||||
echo "${root_dir}/${i}/${s1}/$s2 does not contain JPG/RAW"
|
|
||||||
for s3 in `ls -1 ${root_dir}/${i}/${s1}/$s2`; do
|
|
||||||
echo ${root_dir}/${i}/${s1}/${s2}/$s3
|
|
||||||
if [[ -d "${root_dir}/${i}/${s1}/${s2}/$s3" ]]; then
|
|
||||||
echo "${root_dir}/${i}/${s1}/${s2}/$s3 is a dir"
|
|
||||||
if [[ ${root_dir}/${i}/${s1}/${s2}/$s3 == *JPG* || ${root_dir}/${i}/${s1}/${s2}/$s3 == *RAW* ]]; then
|
|
||||||
echo "${root_dir}/${i}/${s1}/${s2}/$s3 contains JPG/RAW"
|
|
||||||
dedup ${root_dir}/${i}/${s1}/${s2}/$s3
|
|
||||||
else
|
|
||||||
echo "${root_dir}/${i}/${s1}/${s2}/${s3} does not contain JPG/RAW"
|
|
||||||
for s4 in `ls -1 ${root_dir}/${i}/${s1}/${s2}/${s3}`; do
|
|
||||||
echo ${root_dir}/${i}/${s1}/${s2}/${s3}/$s4
|
|
||||||
if [[ -d "${root_dir}/${i}/${s1}/${s2}/${s3}/$s4" ]]; then
|
|
||||||
echo "${root_dir}/${i}/${s1}/${s2}/${s3}/$s4 is a dir"
|
|
||||||
if [[ ${root_dir}/${i}/${s1}/${s2}/${s3}/$s4 == *JPG* || ${root_dir}/${i}/${s1}/${s2}/${s3}/$s4 == *RAW* ]]; then
|
|
||||||
echo "${root_dir}/${i}/${s1}/${s2}/${s3}/$s4 contains JPG/RAW"
|
|
||||||
dedup ${root_dir}/${i}/${s1}/${s2}/${s3}/$s4
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
143
file_stuff.py
143
file_stuff.py
|
@ -1,143 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
"""
|
|
||||||
dump the dictionary generated from findings into a yaml file for later inspection
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import yaml
|
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
### Local Imports
|
|
||||||
# from configure import Configure, CONFIG_FILE
|
|
||||||
from hashing import xx_hash
|
|
||||||
from lumberjack import timber
|
|
||||||
|
|
||||||
def check_log_dir(d):
|
|
||||||
create_folder(d)
|
|
||||||
|
|
||||||
|
|
||||||
# c = Configure(CONFIG_FILE)
|
|
||||||
# config = c.load_config()
|
|
||||||
log = timber(__name__)
|
|
||||||
log.info("Starting")
|
|
||||||
|
|
||||||
def dump_yaml(dictionary,file):
|
|
||||||
""" dump dictionary to yaml file """
|
|
||||||
with open(file, 'w', encoding="utf-8") as f:
|
|
||||||
yaml.dump(dictionary, f)
|
|
||||||
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def path_exists(p):
|
|
||||||
""" determine if the path exists """
|
|
||||||
log.debug(f'path_exists({p})')
|
|
||||||
pe = os.path.exists(p)
|
|
||||||
# print(f'Path Exists: {pe}')
|
|
||||||
|
|
||||||
return pe
|
|
||||||
|
|
||||||
def is_dir(d):
|
|
||||||
""" determine if object is a dir or not """
|
|
||||||
log.debug(f'is_dir({d})')
|
|
||||||
|
|
||||||
return os.path.isdir(d)
|
|
||||||
|
|
||||||
def is_file(f):
|
|
||||||
""" determine if object is a file or not """
|
|
||||||
log.debug(f'is_file({f})')
|
|
||||||
|
|
||||||
return os.path.isfile(f)
|
|
||||||
|
|
||||||
def cmp_files(f1,f2):
|
|
||||||
""" compare two files """
|
|
||||||
log.debug(f'cmp_files({f1},{f2})')
|
|
||||||
#TODO: Determine if path is actually a file
|
|
||||||
#TODO: Determine if the hash has already been stored and use it if so
|
|
||||||
|
|
||||||
hash1 = xx_hash(f1)
|
|
||||||
hash2 = xx_hash(f2)
|
|
||||||
return hash1 == hash2
|
|
||||||
|
|
||||||
def path_access_read(path):
|
|
||||||
""" make sure we can read from the path """
|
|
||||||
log.debug(f'path_access_read({path})')
|
|
||||||
|
|
||||||
val = os.access(path, os.R_OK)
|
|
||||||
|
|
||||||
if val is False:
|
|
||||||
log.error(f'Can not read from {path}')
|
|
||||||
|
|
||||||
return val
|
|
||||||
|
|
||||||
def path_access_write(path):
|
|
||||||
""" make sure we can write to the path """
|
|
||||||
log.debug(f'path_access_write({path})')
|
|
||||||
|
|
||||||
val = os.access(path, os.W_OK)
|
|
||||||
|
|
||||||
if val is False:
|
|
||||||
log.error(f'Can not write to {path}')
|
|
||||||
|
|
||||||
return val
|
|
||||||
|
|
||||||
def create_folder(file):
|
|
||||||
""" Function to create folder """
|
|
||||||
log.debug(f'create_folder({file})')
|
|
||||||
|
|
||||||
if path_exists(file) is False:
|
|
||||||
os.makedirs(file)
|
|
||||||
elif is_dir(file) is False:
|
|
||||||
pass # this needs to turn into bailing out as there is a collision.
|
|
||||||
|
|
||||||
def cleanup_sd(f,config):
|
|
||||||
""" If we should clean up the SD, nuke the copied files. """
|
|
||||||
log.debug(f'cleanup_sd({f})')
|
|
||||||
|
|
||||||
if config['cleanup_sd'] is True:
|
|
||||||
os.system('clear')
|
|
||||||
for file in tqdm(f, desc = "Cleaning Up SD:"):
|
|
||||||
if f[file]['source_cleanable'] is True:
|
|
||||||
log.debug(f"Cleanup SD: Removing File -\n{os.path.join(f[file]['folders']['source_path'],f[file]['name'])}")
|
|
||||||
|
|
||||||
os.remove(os.path.join(f[file]['folders']['source_path'],f[file]['name']))
|
|
||||||
else:
|
|
||||||
log.debug(f"Cleanup SD: Not Removing File -\n{os.path.join(f[file]['folders']['source_path'],f[file]['name'])}")
|
|
||||||
|
|
||||||
def validate_config_dir_access(config):
|
|
||||||
""" Validate we can operate in the defined directories """
|
|
||||||
log.debug('validate_config_dir_access')
|
|
||||||
|
|
||||||
check = path_access_write(config['folders']['destination']['base'])
|
|
||||||
if check is False:
|
|
||||||
accessible = False
|
|
||||||
else:
|
|
||||||
check = path_access_read(config['folders']['source']['base'])
|
|
||||||
if check is False:
|
|
||||||
accessible = False
|
|
||||||
else:
|
|
||||||
if config['store_backup'] is True:
|
|
||||||
check = path_access_write(config['folders']['backup'])
|
|
||||||
if check is False:
|
|
||||||
accessible = False
|
|
||||||
else:
|
|
||||||
accessible = True
|
|
||||||
else:
|
|
||||||
accessible = True
|
|
||||||
return accessible
|
|
||||||
|
|
||||||
def get_t_files(p,t):
|
|
||||||
total = int(0)
|
|
||||||
for folder, subfolders, filename in os.walk(p):
|
|
||||||
for f_type in t:
|
|
||||||
for ext in t[f_type]:
|
|
||||||
# for file in tqdm(filename,
|
|
||||||
# desc='Finding ' + ext + ' Files in ' + folder):
|
|
||||||
for file in filename:
|
|
||||||
if file.lower().endswith(ext):
|
|
||||||
current_file = os.path.join(folder, file)
|
|
||||||
if is_file(current_file):
|
|
||||||
total += int(1)
|
|
||||||
else:
|
|
||||||
print(f"Skipping {current_file} as it does not look like a real file.")
|
|
||||||
return total
|
|
|
@ -1,98 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
"""
|
|
||||||
Get EXIF information from image
|
|
||||||
"""
|
|
||||||
from uu import Error
|
|
||||||
|
|
||||||
import exifread
|
|
||||||
from datetime import datetime
|
|
||||||
from lumberjack import timber
|
|
||||||
|
|
||||||
log = timber(__name__)
|
|
||||||
|
|
||||||
def get_exif_date(tags,tag,f):
|
|
||||||
t = ''
|
|
||||||
log.debug(f'function: get_exif_tag(tags:{tags},tag:{tag},format:{f}')
|
|
||||||
try:
|
|
||||||
t = datetime.strptime(str(tags[tag]),f)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
# log.debug(f'Error: {e}. Format: {f}')
|
|
||||||
|
|
||||||
return t
|
|
||||||
|
|
||||||
def get_os_ctime(path):
|
|
||||||
# Don't pull the file ctime anymore, more often than not, it's wrong.
|
|
||||||
# t = datetime.fromtimestamp(os.path.getctime(path))
|
|
||||||
|
|
||||||
#raise an error so it will except and move on.
|
|
||||||
raise ValueError(f"{path}: Using ctime like this is usually not good.")
|
|
||||||
|
|
||||||
|
|
||||||
def set_generic_date_time(date = '1900:01:01',
|
|
||||||
time = '00:00:00',
|
|
||||||
f = '%Y:%m:%d %H:%M:%S'):
|
|
||||||
|
|
||||||
t = datetime.strptime(str(f'{date} {time}'),f)
|
|
||||||
return t
|
|
||||||
|
|
||||||
|
|
||||||
def get_img_date(p):
|
|
||||||
with open(p, 'rb') as file:
|
|
||||||
tags = exifread.process_file(file)
|
|
||||||
|
|
||||||
if 'Composite DateTimeOriginal' in tags:
|
|
||||||
get_exif_date(tags,'Composite DateTimeOriginal','%Y:%m:%d %H:%M')
|
|
||||||
|
|
||||||
def get_exif_tag(p,t):
|
|
||||||
with open(p, "rb") as f:
|
|
||||||
try:
|
|
||||||
tags = exifread.process_file(f)
|
|
||||||
print(f'{p}: {tags}')
|
|
||||||
except Error as e:
|
|
||||||
return f'Received Error: {e} when trying to open {p}'
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
for tag in tags:
|
|
||||||
print(tag)
|
|
||||||
if t in tag.lower():
|
|
||||||
print(tags[tag])
|
|
||||||
return tags[tag]
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_image_date(path):
|
|
||||||
t = ''
|
|
||||||
exif_dt = ''
|
|
||||||
|
|
||||||
with open(path, "rb") as file:
|
|
||||||
try:
|
|
||||||
tags = exifread.process_file(file)
|
|
||||||
except Error as e:
|
|
||||||
log.error(e)
|
|
||||||
finally:
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
for tag in tags:
|
|
||||||
if "DateTime" in tag:
|
|
||||||
t = tag
|
|
||||||
break
|
|
||||||
|
|
||||||
if '' == t:
|
|
||||||
exif_dt = set_generic_date_time()
|
|
||||||
else:
|
|
||||||
for f in ['%Y:%m:%d %H:%M:%S',
|
|
||||||
'%Y/%m/%d %H:%M:%S',
|
|
||||||
'%Y-%m-%d-%H-%M-%S']:
|
|
||||||
log.debug(f'Trying... {t}, {f}, {path} ')
|
|
||||||
exif_dt = get_exif_date(tags, t, f)
|
|
||||||
|
|
||||||
if '' != exif_dt:
|
|
||||||
break
|
|
||||||
|
|
||||||
if '' == exif_dt:
|
|
||||||
exif_dt = set_generic_date_time()
|
|
||||||
# s = get_os_ctime(path) # This could produce wildly incorrect results
|
|
||||||
return exif_dt
|
|
74
hashing.py
74
hashing.py
|
@ -1,74 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
"""
|
|
||||||
dump the dictionary generated from findings into a yaml file for later inspection
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import xxhash
|
|
||||||
from tqdm import tqdm
|
|
||||||
# from configure import Configure, CONFIG_FILE
|
|
||||||
from lumberjack import timber
|
|
||||||
|
|
||||||
# c = Configure(CONFIG_FILE)
|
|
||||||
# config = c.load_config()
|
|
||||||
log = timber(__name__)
|
|
||||||
|
|
||||||
def xx_hash(file):
|
|
||||||
""" calculates and returns file hash based on xxHash """
|
|
||||||
size = os.path.getsize(file)
|
|
||||||
hasher = xxhash.xxh64()
|
|
||||||
|
|
||||||
with open(file, 'rb') as f:
|
|
||||||
with tqdm(total=size,
|
|
||||||
unit='B',
|
|
||||||
unit_scale=True,
|
|
||||||
desc=f'Getting hash for {os.path.basename(file)}') as pbar:
|
|
||||||
for chunk in iter(lambda: f.read(4096), b""):
|
|
||||||
hasher.update(chunk)
|
|
||||||
pbar.update(len(chunk))
|
|
||||||
file_hash = hasher.hexdigest()
|
|
||||||
|
|
||||||
return file_hash
|
|
||||||
|
|
||||||
def hash_path(path):
|
|
||||||
""" hashes a string passed as a path """
|
|
||||||
|
|
||||||
hasher = xxhash.xxh64(path)
|
|
||||||
return hasher.hexdigest()
|
|
||||||
|
|
||||||
def gen_xxhashes(f):
|
|
||||||
""" Generate xxHashes """
|
|
||||||
log.debug(f'gen_xxhashes({f})')
|
|
||||||
for file in tqdm(f, desc = "Generating xx Hashes:"):
|
|
||||||
os.system('clear')
|
|
||||||
log.debug(f[file])
|
|
||||||
f[file]['xx_checksums'] = {}
|
|
||||||
for folder in f[file]['folders']:
|
|
||||||
k = os.path.join(f[file]['folders'][folder], f[file]['name'])
|
|
||||||
if k != f[file]['name']:
|
|
||||||
# k = f[file]['folders'][folder]
|
|
||||||
log.debug(k)
|
|
||||||
f[file]['xx_checksums'][k] = xx_hash(k)
|
|
||||||
log.debug(f"{k}: {f[file]['xx_checksums'][k]}")
|
|
||||||
log.debug(f[file])
|
|
||||||
|
|
||||||
def validate_xx_checksums(f):
|
|
||||||
""" Validate Checksums """
|
|
||||||
for file in tqdm(f, desc = "Verifying Checksums:"):
|
|
||||||
os.system('clear')
|
|
||||||
i = 0
|
|
||||||
c = {}
|
|
||||||
for checksum in f[file]['xx_checksums']:
|
|
||||||
c[i] = f[file]['xx_checksums'][checksum]
|
|
||||||
if i > 0:
|
|
||||||
p = i - 1
|
|
||||||
if c[i] == c[p]:
|
|
||||||
f[file]['source_cleanable'] = True
|
|
||||||
else:
|
|
||||||
f[file]['source_cleanable'] = False
|
|
||||||
log.critical(f'FATAL: Checksum validation failed for: \
|
|
||||||
{f[file]["name"]} \n{c[i]}\n is not equal to \n{c[p]}\n')
|
|
||||||
log.debug('\n File Meta:\n')
|
|
||||||
log.debug(f[file])
|
|
||||||
i = i + 1
|
|
|
@ -1,110 +1,194 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
"""
|
'''
|
||||||
Import photos from SD card into folder with today's date + nickname
|
Import photos from SD card into folder with todays date + nickname
|
||||||
Use: import_media.py (--jpg|--raw|--both) <nickname of folder (optional)>
|
Use: importphotos (--jpg|--raw|--both) <nickname of folder (optional)>
|
||||||
Add script to path
|
Add script to path
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
TODO:
|
TODO:
|
||||||
8. Optionally allow specification of a backup location on another disk
|
1. Import configuration from config file
|
||||||
|
2. Set raw file extension based on camera specified in configuration
|
||||||
|
3. Create destination folders based on concatination of configuration,
|
||||||
|
metadata, and event name passed from ARG
|
||||||
|
4. Create destination sub-folder based on filetype
|
||||||
|
5. Copy files to appropriate folder
|
||||||
|
6. Compare files from source
|
||||||
|
7. Create 'originals' with copy of files from destination after
|
||||||
|
checksum for photos only
|
||||||
|
8. Optinally allow specification of a backup location on another disk
|
||||||
or NAS to ship a 3rd copy to
|
or NAS to ship a 3rd copy to
|
||||||
|
9. Optionally cleanup SD only after checksum matching
|
||||||
10. Every config option has an arg override
|
10. Every config option has an arg override
|
||||||
11. Optionally rename file if EVENT name was passed in
|
11. Optionally rename file if event name was passed in
|
||||||
-- STRETCH --
|
-- STRETCH --
|
||||||
12. Make a graphical interface
|
12. Make a graphical interface
|
||||||
"""
|
'''
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
import os
|
||||||
from tqdm import tqdm
|
import sys
|
||||||
|
import yaml
|
||||||
|
import argparse
|
||||||
|
import shutil
|
||||||
|
import hashlib
|
||||||
|
from datetime import datetime
|
||||||
|
import exifread
|
||||||
|
|
||||||
### Local Imports
|
config_file = 'config.yaml'
|
||||||
from configure import CONFIG_FILE, Configure, files
|
|
||||||
from file_stuff import cleanup_sd, validate_config_dir_access, is_file
|
|
||||||
from hashing import gen_xxhashes, validate_xx_checksums
|
|
||||||
from bitmover import copy_files
|
|
||||||
from lumberjack import timber
|
|
||||||
from media import process_file
|
|
||||||
|
|
||||||
c = Configure(CONFIG_FILE)
|
# Read configuration from file
|
||||||
config = c.load_config()
|
try:
|
||||||
log = timber(__name__)
|
with open(config_file, 'r') as f:
|
||||||
log.info("Starting import_media")
|
config = yaml.load(f, Loader=yaml.FullLoader)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Configuration file not found: ", config_file)
|
||||||
|
print("Copy config.yaml.EXAMPLE to ", config_file, " and update accordingly.")
|
||||||
|
|
||||||
|
''' Parse Arguments '''
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("-e", "--event", help = "Event Name")
|
parser.add_argument("-e", "--event", help = "Event Name")
|
||||||
parser.add_argument("-s", "--source", help = "Source Directory to search for files")
|
|
||||||
parser.add_argument("-d", "--destination", help = "Destination Directory to put files")
|
|
||||||
parser.add_argument("-o", "--create-originals", help = "For images only, create an originals \
|
|
||||||
folder for safe keeping")
|
|
||||||
parser.add_argument("-b", "--backup-destination", help = "Create a backup of everything at the \
|
|
||||||
specified location")
|
|
||||||
parser.add_argument("-D", "--delete-source-files", help = "Delete files from SD after validating \
|
|
||||||
checksum of copied files")
|
|
||||||
parser.add_argument("-v", "--verify", help = "[True|False] Verify the checksum of \
|
|
||||||
the copied file")
|
|
||||||
parser.add_argument("-c", "--config", help = "Load the specified config file instead \
|
|
||||||
of the default " + CONFIG_FILE)
|
|
||||||
parser.add_argument("-g", "--generate-config", help = "Generate config file based on options \
|
|
||||||
passed from command arguments")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.event:
|
if args.event:
|
||||||
EVENT = args.event
|
event = args.event
|
||||||
else:
|
|
||||||
EVENT = False
|
|
||||||
if args.source:
|
|
||||||
config['folders']['source']['base'] = args.source
|
|
||||||
if args.destination:
|
|
||||||
config['folders']['destination']['base'] = args.source
|
|
||||||
#if args.create-originals:
|
|
||||||
# pass
|
|
||||||
#if args.backup-destination:
|
|
||||||
# pass
|
|
||||||
#if args.delete-source-files:
|
|
||||||
# pass
|
|
||||||
#if args.config:
|
|
||||||
# pass
|
|
||||||
#if args.generate-config:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
def md5_hash(f):
|
||||||
|
print("calculating md5 for ", f)
|
||||||
|
md5 = hashlib.md5(open(f, 'rb').read()).hexdigest()
|
||||||
|
return md5
|
||||||
|
|
||||||
def find_files(directory):
|
def cmp_files(f1,f2):
|
||||||
""" find files to build a dictionary out of """
|
print('comparing md5 hashes...')
|
||||||
log.debug(f'find_files({directory})')
|
return md5_hash(f1) == md5_hash(f2)
|
||||||
os.system('clear')
|
|
||||||
for folder, subfolders, filename in os.walk(directory):
|
def file_classification(f):
|
||||||
log.debug(f'{folder},{filename}')
|
print('Classifying media for: ', f)
|
||||||
for f_type in config['file_types']:
|
for classification in config['file_types']:
|
||||||
log.debug(f'Type: {f_type}')
|
for ext in config['file_types'][classification]:
|
||||||
for ext in config['file_types'][f_type]:
|
if f.lower().endswith(ext):
|
||||||
log.debug(f'Extension: {ext}')
|
c = classification
|
||||||
os.system('clear')
|
return classification
|
||||||
for file in tqdm(filename,
|
|
||||||
desc = 'Finding ' + ext + ' Files in ' + folder):
|
def get_capture_date(p, t):
|
||||||
log.debug(f'File: {file}')
|
if t == 'image':
|
||||||
if file.lower().endswith(ext):
|
with open(p, 'rb') as f:
|
||||||
current_file = os.path.join(folder,file)
|
tags = exifread.process_file(f)
|
||||||
log.debug(f'Current File: {current_file}')
|
captured = tags['EXIF DateTimeOriginal']
|
||||||
if is_file(current_file):
|
year = str(captured).split(' ')[0].split(':')[0]
|
||||||
log.debug(f'Is File: {current_file}')
|
month = str(captured).split(' ')[0].split(':')[1]
|
||||||
log.debug(f'Call function: process_file({folder}, {file}, {EVENT}, {config})')
|
day = str(captured).split(' ')[0].split(':')[2]
|
||||||
#process_file(folder, f_type, file, ext)
|
|
||||||
process_file(folder, file, EVENT, config)
|
|
||||||
else:
|
else:
|
||||||
log.warn(f"Skipping {current_file} as it does not look like a real file.")
|
stamp = datetime.fromtimestamp(os.path.getctime(p))
|
||||||
|
year = stamp.strftime("%Y")
|
||||||
|
month = stamp.strftime("%m")
|
||||||
|
day = stamp.strftime("%d")
|
||||||
|
return year, month, day
|
||||||
|
|
||||||
GO = validate_config_dir_access(config)
|
def create_folder(f):
|
||||||
if GO is True:
|
try:
|
||||||
find_files(config['folders']['source']['base'])
|
os.makedirs(f)
|
||||||
copy_files(files,config)
|
except FileExistsError as exists:
|
||||||
gen_xxhashes(files)
|
print()
|
||||||
validate_xx_checksums(files)
|
|
||||||
cleanup_sd(files,config)
|
def copy_from_source(p, dest_folder, dest_orig_folder, file):
|
||||||
else:
|
if os.path.exists(os.path.join(dest_folder, file)):
|
||||||
log.critical('There was a problem accessing one or more directories defined in the configuration.')
|
check_match = cmp_files(p, os.path.join(dest_folder, file))
|
||||||
|
if check_match == False:
|
||||||
|
base, extension = os.path.splitext(file)
|
||||||
|
file_name_hash = base + '_' + md5_hash(os.path.join(dest_folder, file)) + extension
|
||||||
|
os.rename(os.path.join(dest_folder, file), os.path.join(dest_folder, file_name_hash))
|
||||||
|
|
||||||
|
shutil.copy(p, dest_folder)
|
||||||
|
check_match = cmp_files(p, dest_folder + '/' + file)
|
||||||
|
if check_match == False:
|
||||||
|
print(f'CRITICAL: md5 hash does not match for {file}')
|
||||||
|
print(p, ': ', md5_hash(p))
|
||||||
|
print(dest_folder + '/' + file, ': ', md5_hash(dest_folder + '/' + file))
|
||||||
|
exit
|
||||||
|
|
||||||
|
if dest_orig_folder != False:
|
||||||
|
shutil.copy(dest_folder + '/' + file, dest_orig_folder)
|
||||||
|
check_match = cmp_files(dest_folder + '/' + file, dest_orig_folder + '/' + file)
|
||||||
|
if check_match == False:
|
||||||
|
print(f'CRITICAL: md5 hash does not match for {file}')
|
||||||
|
print(dest_folder + '/' + file, ': ', md5_hash(dest_folder + '/' + file))
|
||||||
|
print(dest_orig_folder + '/' + file, ': ', md5_hash(dest_orig_folder + '/' + file))
|
||||||
|
exit
|
||||||
|
else:
|
||||||
|
shutil.copy(p, dest_folder)
|
||||||
|
check_match = cmp_files(p, dest_folder + '/' + file)
|
||||||
|
if check_match == False:
|
||||||
|
print(f'CRITICAL: md5 hash does not match for {file}')
|
||||||
|
print(p, ': ', md5_hash(p))
|
||||||
|
print(dest_folder + '/' + file, ': ', md5_hash(dest_folder + '/' + file))
|
||||||
|
exit
|
||||||
|
|
||||||
|
if dest_orig_folder != False:
|
||||||
|
shutil.copy(dest_folder + '/' + file, dest_orig_folder)
|
||||||
|
check_match = cmp_files(dest_folder + '/' + file, dest_orig_folder + '/' + file)
|
||||||
|
if check_match == False:
|
||||||
|
print(f'CRITICAL: md5 hash does not match for {file}')
|
||||||
|
print(dest_folder + '/' + file, ': ', md5_hash(dest_folder + '/' + file))
|
||||||
|
print(dest_orig_folder + '/' + file, ': ', md5_hash(dest_orig_folder + '/' + file))
|
||||||
|
exit
|
||||||
|
|
||||||
|
def process_file(p, t, file, ext):
|
||||||
|
capture_date = get_capture_date(p, t)
|
||||||
|
y = capture_date[0]
|
||||||
|
m = capture_date[1]
|
||||||
|
d = capture_date[2]
|
||||||
|
|
||||||
|
if event:
|
||||||
|
dest_folder = config['folders']['destination']['base'] + '/' + y + '/' + y + '-' + m + '/' + y + '-' + m + '-' + d + '-' + event
|
||||||
|
else:
|
||||||
|
dest_folder = config['folders']['destination']['base'] + '/' + y + '/' + y + '-' + m + '/' + y + '-' + m + '-' + d
|
||||||
|
|
||||||
|
if t == 'image':
|
||||||
|
dest_folder = dest_folder + '/photos'
|
||||||
|
|
||||||
|
if config['store_originals'] == True:
|
||||||
|
dest_orig_folder = dest_folder + '/ORIGINALS'
|
||||||
|
|
||||||
|
if ext in ('jpg', 'jpeg'):
|
||||||
|
dest_folder = dest_folder + '/JPG'
|
||||||
|
if dest_orig_folder:
|
||||||
|
dest_orig_folder = dest_orig_folder + '/JPG'
|
||||||
|
else:
|
||||||
|
dest_folder = dest_folder + '/RAW'
|
||||||
|
if dest_orig_folder:
|
||||||
|
dest_orig_folder = dest_orig_folder + '/RAW'
|
||||||
|
|
||||||
|
elif t == 'video':
|
||||||
|
dest_folder = dest_folder + '/VIDEO'
|
||||||
|
|
||||||
|
elif t == 'audio':
|
||||||
|
dest_folder = dest_folder + '/AUDIO'
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f'WARN: {t} is not a known type and you never should have landed here.')
|
||||||
|
|
||||||
|
create_folder(dest_folder)
|
||||||
|
|
||||||
|
try:
|
||||||
|
dest_orig_folder
|
||||||
|
except NameError:
|
||||||
|
dest_orig_folder = False
|
||||||
|
else:
|
||||||
|
create_folder(dest_orig_folder)
|
||||||
|
|
||||||
|
copy_from_source(p, dest_folder, dest_orig_folder, file)
|
||||||
|
|
||||||
|
|
||||||
# dump_yaml(files, 'files_dict.yaml')
|
def file_list(directory):
|
||||||
log.info('done.')
|
for folder, subfolders, filename in os.walk(directory):
|
||||||
|
for t in config['file_types']:
|
||||||
|
for ext in config['file_types'][t]:
|
||||||
|
for file in filename:
|
||||||
|
if file.lower().endswith(ext):
|
||||||
|
p = folder + '/' + file
|
||||||
|
process_file(p, t, file, ext)
|
||||||
|
|
||||||
|
file_list(config['folders']['source']['base'])
|
||||||
|
print('done.')
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
"""
|
|
||||||
A class for logging... no, not timber
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# class Logger(object):
|
|
||||||
# level_relations = {
|
|
||||||
# 'debug': logging.DEBUG,
|
|
||||||
# 'info': logging.INFO,
|
|
||||||
# 'warning': logging.WARNING,
|
|
||||||
# 'error': logging.ERROR,
|
|
||||||
# 'crit': logging.CRITICAL
|
|
||||||
# } # relationship mapping
|
|
||||||
#
|
|
||||||
# def __init__(self, filename, level='info', when='D', backCount=3,
|
|
||||||
# fmt='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
|
|
||||||
# self.logger = logging.getLogger(filename)
|
|
||||||
# format_str = logging.Formatter(fmt) # Setting the log format
|
|
||||||
# self.logger.setLevel(self.level_relations.get(level)) # Setting the log level
|
|
||||||
# console_handler = logging.StreamHandler() # on-screen output
|
|
||||||
# console_handler .setFormatter(format_str) # Setting the format
|
|
||||||
# th = logging.handlers.TimedRotatingFileHandler(filename=filename, when=when, backupCount=backCount,encoding='utf-8') # automatically generates the file at specified intervals
|
|
||||||
# th.setFormatter(format_str) # Setting the format
|
|
||||||
# self.logger.addHandler(console_handler) # Add the object to the logger
|
|
||||||
# self.logger.addHandler(th)
|
|
||||||
|
|
||||||
def timber(name):
|
|
||||||
file_formatter = logging.Formatter('%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s')
|
|
||||||
file_handler = logging.FileHandler("log/all.log")
|
|
||||||
file_handler.setLevel(logging.DEBUG)
|
|
||||||
file_handler.setFormatter(file_formatter)
|
|
||||||
|
|
||||||
console_handler = logging.StreamHandler()
|
|
||||||
console_handler.setLevel(logging.INFO)
|
|
||||||
console_handler.setFormatter(file_formatter)
|
|
||||||
|
|
||||||
logger = logging.getLogger(name)
|
|
||||||
logger.addHandler(file_handler)
|
|
||||||
logger.addHandler(console_handler)
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
return logger
|
|
170
media.py
170
media.py
|
@ -1,170 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
"""
|
|
||||||
Create the media object
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
from datetime import datetime
|
|
||||||
import ffmpeg
|
|
||||||
import sys
|
|
||||||
|
|
||||||
#Local Imports
|
|
||||||
from hashing import xx_hash, hash_path
|
|
||||||
from get_image_tag import get_image_date
|
|
||||||
# from configure import files
|
|
||||||
from lumberjack import timber
|
|
||||||
|
|
||||||
log = timber(__name__)
|
|
||||||
|
|
||||||
class Media:
|
|
||||||
""" media class """
|
|
||||||
|
|
||||||
def __init__(self, source_path,event,config):
|
|
||||||
""" init """
|
|
||||||
|
|
||||||
self.configuration = config
|
|
||||||
self.source_path_dir = os.path.dirname(source_path)
|
|
||||||
self.source_path_hash = hash_path(source_path)
|
|
||||||
self.event_name = event
|
|
||||||
self.file_name = os.path.basename(source_path)
|
|
||||||
self.dotted_file_ext = os.path.splitext(self.file_name)[1].lower()
|
|
||||||
self.file_ext = self.dotted_file_ext.split('.')[1]
|
|
||||||
self.file_type = self.get_file_type()
|
|
||||||
self.capture_date = get_capture_date(source_path,self.file_type)
|
|
||||||
self.store_originals = config['store_originals']
|
|
||||||
self.destination_path = self.set_destination_path()
|
|
||||||
self.destination_originals_path = ''
|
|
||||||
|
|
||||||
|
|
||||||
self.filehash = '' # Only populate this one if it's called upon.
|
|
||||||
|
|
||||||
def set_destination_path(self):
|
|
||||||
""" set the destination path for the file """
|
|
||||||
|
|
||||||
p = self.configuration['folders']['destination']['base'] + '/' \
|
|
||||||
+ self.capture_date[0] + '/' \
|
|
||||||
+ self.capture_date[0] + '-' \
|
|
||||||
+ self.capture_date[1] + '/' \
|
|
||||||
+ self.capture_date[0] + '-' \
|
|
||||||
+ self.capture_date[1] + '-' \
|
|
||||||
+ self.capture_date[2]
|
|
||||||
|
|
||||||
if self.event_name:
|
|
||||||
p = p + '-' + self.event_name
|
|
||||||
|
|
||||||
if self.file_type == 'image':
|
|
||||||
p = p + '/PHOTO'
|
|
||||||
if self.file_ext.lower() in ('jpg', 'jpeg'):
|
|
||||||
if self.store_originals is True:
|
|
||||||
self.destination_originals_path = p + '/ORIGINALS/JPG'
|
|
||||||
p = p + '/JPG'
|
|
||||||
else:
|
|
||||||
if self.store_originals is True:
|
|
||||||
self.destination_originals_path = p + '/ORIGINALS/RAW'
|
|
||||||
p = p + '/RAW'
|
|
||||||
elif self.file_type == 'video':
|
|
||||||
p = p + '/VIDEO'
|
|
||||||
elif self.file_type == 'audio':
|
|
||||||
p = p + '/AUDIO'
|
|
||||||
else:
|
|
||||||
log.critical(f'{self.file_type} is not known. You should have never landed here.')
|
|
||||||
|
|
||||||
return p
|
|
||||||
|
|
||||||
def generate_hash(self):
|
|
||||||
""" generate the hash and store it to self.filehash """
|
|
||||||
return xx_hash(self.source_path_dir)
|
|
||||||
|
|
||||||
def set_hash(self):
|
|
||||||
"""set the hash for the file """
|
|
||||||
self.filehash = self.generate_hash()
|
|
||||||
|
|
||||||
def get_hash(self):
|
|
||||||
""" a function to get return the hash """
|
|
||||||
|
|
||||||
if not self.filehash:
|
|
||||||
self.set_hash()
|
|
||||||
|
|
||||||
return self.filehash
|
|
||||||
|
|
||||||
def get_file_type(self):
|
|
||||||
""" determine if the extension is in the list of file types """
|
|
||||||
log.debug(f'get_file_type():')
|
|
||||||
for t in self.configuration['file_types']:
|
|
||||||
log.debug(f'Checking if file is part of {t}')
|
|
||||||
|
|
||||||
for e in self.configuration['file_types'][t]:
|
|
||||||
log.debug(f'Checking if {self.file_ext.lower()} ends with {e}')
|
|
||||||
|
|
||||||
if self.file_ext.lower().endswith(e):
|
|
||||||
log.debug(self.file_ext + ' ends with ' + e)
|
|
||||||
return t
|
|
||||||
else:
|
|
||||||
log.debug(self.file_ext + f' does not end with {e}')
|
|
||||||
|
|
||||||
def get_capture_date(path, f_type):
|
|
||||||
""" get capture date from meta """
|
|
||||||
log.debug(f'get_capture_date({path}, {f_type}')
|
|
||||||
|
|
||||||
if f_type == 'image':
|
|
||||||
stamp = get_image_date(path)
|
|
||||||
|
|
||||||
elif f_type == 'video':
|
|
||||||
try:
|
|
||||||
stamp = datetime.strptime(
|
|
||||||
ffmpeg.probe(path)['format']['tags']['creation_time'],
|
|
||||||
'%Y-%m-%dT%H:%M:%S.%f%z')
|
|
||||||
except:
|
|
||||||
try:
|
|
||||||
stamp = datetime.fromtimestamp(os.path.getctime(path))
|
|
||||||
except:
|
|
||||||
try:
|
|
||||||
stamp = datetime.strptime(
|
|
||||||
str('1900:01:01 00:00:00'), '%Y:%m:%d %H:%M:%S')
|
|
||||||
except:
|
|
||||||
log.critical(f'\nCould not get timestamp for {path}. Giving up.')
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
elif f_type == 'audio':
|
|
||||||
try:
|
|
||||||
stamp = datetime.strptime(ffmpeg.probe(
|
|
||||||
path)['format']['tags']['date'], '%Y-%m-%d')
|
|
||||||
except KeyError as ke:
|
|
||||||
log.warning(f'\nError: {ke} for {path}. Trying getctime...')
|
|
||||||
try:
|
|
||||||
stamp = datetime.fromtimestamp(os.path.getctime(path))
|
|
||||||
except:
|
|
||||||
log.critical(f'\nCould not get timestamp for {path}. Giving up.')
|
|
||||||
sys.exit()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
stamp = datetime.fromtimestamp(os.path.getctime(path))
|
|
||||||
except:
|
|
||||||
log.critical(f'\nCould not get timestamp for {path}. Giving up.')
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
year = stamp.strftime("%Y")
|
|
||||||
month = stamp.strftime("%m")
|
|
||||||
day = stamp.strftime("%d")
|
|
||||||
return year, month, day
|
|
||||||
|
|
||||||
# def process_file(path_name, f_name, event, c):
|
|
||||||
# """ gather information and add to dictionary """
|
|
||||||
# log.debug(f'process_file({path_name}, {f_name}, {event}, {c})')
|
|
||||||
#
|
|
||||||
# m = Media(os.path.join(path_name,f_name),event, c)
|
|
||||||
# i = m.source_path_hash
|
|
||||||
# log.debug(f'Source Path Hash: {i}')
|
|
||||||
#
|
|
||||||
# files[i] = { 'folders': {}, 'date': {} }
|
|
||||||
#
|
|
||||||
# files[i]['folders']['source_path'] = m.source_path_dir
|
|
||||||
# files[i]['type'] = m.file_type
|
|
||||||
# files[i]['name'] = m.file_name
|
|
||||||
# files[i]['extension'] = m.file_ext
|
|
||||||
# files[i]['date']['capture_date'] = {}
|
|
||||||
# files[i]['date']['capture_date']['y'] = m.capture_date[0]
|
|
||||||
# files[i]['date']['capture_date']['m'] = m.capture_date[1]
|
|
||||||
# files[i]['date']['capture_date']['d'] = m.capture_date[2]
|
|
||||||
# files[i]['folders']['destination'] = m.destination_path
|
|
||||||
# files[i]['folders']['destination_original'] = m.destination_originals_path
|
|
29
qodana.yaml
29
qodana.yaml
|
@ -1,29 +0,0 @@
|
||||||
#-------------------------------------------------------------------------------#
|
|
||||||
# Qodana analysis is configured by qodana.yaml file #
|
|
||||||
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
|
|
||||||
#-------------------------------------------------------------------------------#
|
|
||||||
version: "1.0"
|
|
||||||
|
|
||||||
#Specify inspection profile for code analysis
|
|
||||||
profile:
|
|
||||||
name: qodana.starter
|
|
||||||
|
|
||||||
#Enable inspections
|
|
||||||
#include:
|
|
||||||
# - name: <SomeEnabledInspectionId>
|
|
||||||
|
|
||||||
#Disable inspections
|
|
||||||
#exclude:
|
|
||||||
# - name: <SomeDisabledInspectionId>
|
|
||||||
# paths:
|
|
||||||
# - <path/where/not/run/inspection>
|
|
||||||
|
|
||||||
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
|
|
||||||
#bootstrap: sh ./prepare-qodana.sh
|
|
||||||
|
|
||||||
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
|
|
||||||
#plugins:
|
|
||||||
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
|
|
||||||
|
|
||||||
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
|
|
||||||
linter: jetbrains/qodana-<linter>:latest
|
|
28
raw_photo.py
28
raw_photo.py
|
@ -1,28 +0,0 @@
|
||||||
from rawpy import imread
|
|
||||||
import imageio
|
|
||||||
from rawpy._rawpy import LibRawNoThumbnailError, LibRawUnsupportedThumbnailError, ThumbFormat
|
|
||||||
|
|
||||||
|
|
||||||
def extract_jpg_thumb(raw_file_path):
|
|
||||||
|
|
||||||
with imread(raw_file_path) as raw:
|
|
||||||
try:
|
|
||||||
thumb = raw.extract_thumb()
|
|
||||||
except (LibRawNoThumbnailError, LibRawUnsupportedThumbnailError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
if thumb.format == ThumbFormat.JPEG:
|
|
||||||
with open('thumbnail.jpg', 'wb') as f:
|
|
||||||
f.write(thumb.data)
|
|
||||||
elif thumb.format == ThumbFormat.BITMAP:
|
|
||||||
imageio.imsave('thumbnail.jpg', thumb.data)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return 'thumbnail.jpg'
|
|
||||||
|
|
||||||
# thumbnail_path = extract_jpg_thumb('your_raw_image.nef')
|
|
||||||
# if thumbnail_path:
|
|
||||||
# print("Thumbnail extracted to:", thumbnail_path)
|
|
||||||
# else:
|
|
||||||
# print("No thumbnail found or unsupported format.")
|
|
116
rename.py
116
rename.py
|
@ -1,116 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
from bitmover import copy_from_source
|
|
||||||
from file_stuff import path_exists, cmp_files
|
|
||||||
from get_image_tag import get_image_date, get_exif_tag
|
|
||||||
from hashing import xx_hash
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("-f", "--folder", help = "folder with files to rename")
|
|
||||||
parser.add_argument("-o", "--keeporiginalname", help = "keeps the original name attached to the file.")
|
|
||||||
parser.add_argument("-d", "--dryrun", help = "dry run, no action")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.folder:
|
|
||||||
FOLDER = args.folder
|
|
||||||
else:
|
|
||||||
print("you need to specify a folder.")
|
|
||||||
sys.exit()
|
|
||||||
if args.dryrun:
|
|
||||||
dry_run = True
|
|
||||||
else:
|
|
||||||
dry_run = False
|
|
||||||
|
|
||||||
if args.keeporiginalname:
|
|
||||||
keep_orig_name = True
|
|
||||||
else:
|
|
||||||
keep_orig_name = False
|
|
||||||
|
|
||||||
def get_file_size(f):
|
|
||||||
return os.path.getsize(f)
|
|
||||||
|
|
||||||
for file in os.listdir(FOLDER):
|
|
||||||
if file.lower().endswith("gif"):
|
|
||||||
copy_from_source(FOLDER,'/Volumes/VIDEO_ARRAY_01/Multimedia/gif',file)
|
|
||||||
file_match = cmp_files(os.path.join(FOLDER,file),
|
|
||||||
os.path.join('/Volumes/VIDEO_ARRAY_01/Multimedia/gif',file))
|
|
||||||
|
|
||||||
if file_match is True:
|
|
||||||
os.remove(os.path.join(FOLDER,file))
|
|
||||||
|
|
||||||
if file.lower().endswith("png"):
|
|
||||||
copy_from_source(FOLDER,'/Volumes/VIDEO_ARRAY_01/Multimedia/png',file)
|
|
||||||
|
|
||||||
file_match = cmp_files(os.path.join(FOLDER, file),
|
|
||||||
os.path.join('/Volumes/VIDEO_ARRAY_01/Multimedia/gif', file))
|
|
||||||
|
|
||||||
if file_match is True:
|
|
||||||
os.remove(os.path.join(FOLDER, file))
|
|
||||||
|
|
||||||
if file.lower().endswith("heic"):
|
|
||||||
copy_from_source(FOLDER,'/Volumes/VIDEO_ARRAY_01/Multimedia/heic',file)
|
|
||||||
|
|
||||||
file_match = cmp_files(os.path.join(FOLDER, file),
|
|
||||||
os.path.join('/Volumes/VIDEO_ARRAY_01/Multimedia/heic', file))
|
|
||||||
|
|
||||||
if file_match is True:
|
|
||||||
os.remove(os.path.join(FOLDER, file))
|
|
||||||
|
|
||||||
if file.lower().endswith("jpg") or \
|
|
||||||
file.lower().endswith("jpeg") or \
|
|
||||||
file.lower().endswith("nef") or \
|
|
||||||
file.lower().endswith("rw2") or \
|
|
||||||
file.lower().endswith("arw") or \
|
|
||||||
file.lower().endswith("dng"):
|
|
||||||
|
|
||||||
old_path = os.path.join(FOLDER,file)
|
|
||||||
|
|
||||||
lowered_name = file.lower()
|
|
||||||
file_size = get_file_size(old_path)
|
|
||||||
file_extension = os.path.splitext(lowered_name)[1]
|
|
||||||
image_date = get_image_date(old_path)
|
|
||||||
image_date_year = image_date.strftime("%Y")
|
|
||||||
image_date_month = image_date.strftime("%m")
|
|
||||||
image_date_day = image_date.strftime("%d")
|
|
||||||
image_date_hour = image_date.strftime("%H")
|
|
||||||
image_date_minute = image_date.strftime("%M")
|
|
||||||
image_date_second = image_date.strftime("%S")
|
|
||||||
image_date_microsecond = image_date.strftime("%f")
|
|
||||||
image_date_subsecond = str(get_exif_tag(old_path,'EXIF SubSec'))
|
|
||||||
image_hash = xx_hash(old_path)
|
|
||||||
|
|
||||||
if image_date_subsecond:
|
|
||||||
subsecond_desired_length = 6
|
|
||||||
|
|
||||||
if image_date_subsecond == 'None':
|
|
||||||
image_date_subsecond = subsecond_desired_length*str('0')
|
|
||||||
|
|
||||||
l_image_date_subsecond = len(image_date_subsecond)
|
|
||||||
|
|
||||||
if subsecond_desired_length > l_image_date_subsecond:
|
|
||||||
pad = subsecond_desired_length - l_image_date_subsecond
|
|
||||||
image_date_subsecond = image_date_subsecond + pad*str("0")
|
|
||||||
|
|
||||||
new_name = f'{image_date_year}-{image_date_month}-{image_date_day}-{image_date_hour}{image_date_minute}{image_date_second}{image_date_subsecond}_{file_size}_{image_hash}'
|
|
||||||
else:
|
|
||||||
new_name = f'{image_date_year}-{image_date_month}-{image_date_day}-{image_date_hour}{image_date_minute}{image_date_second}000_{file_size}_{image_hash}'
|
|
||||||
|
|
||||||
if keep_orig_name is True:
|
|
||||||
new_file_name = f'{new_name}-{lowered_name}'
|
|
||||||
else:
|
|
||||||
new_file_name = f'{new_name}{file_extension}'
|
|
||||||
|
|
||||||
new_path = os.path.join(FOLDER,new_file_name)
|
|
||||||
|
|
||||||
if path_exists(new_path):
|
|
||||||
print(f"{new_path} exists.. skipping.")
|
|
||||||
elif dry_run is True:
|
|
||||||
print(f'Dry run: {old_path} becomes {new_path}')
|
|
||||||
else:
|
|
||||||
print(f'Renaming {old_path} to: {new_path}')
|
|
||||||
os.rename(old_path,new_path)
|
|
|
@ -1,99 +0,0 @@
|
||||||
from PyQt6.QtCore import QSize, Qt
|
|
||||||
from PyQt6.QtGui import QPixmap
|
|
||||||
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QLabel, QLineEdit, QWidget, \
|
|
||||||
QHBoxLayout, QListWidget
|
|
||||||
from ui_dialogues import select_directory, update_textbox_from_path
|
|
||||||
|
|
||||||
|
|
||||||
def create_layout1():
|
|
||||||
layout1 = QVBoxLayout()
|
|
||||||
layout1_1 = QHBoxLayout()
|
|
||||||
layout1_2 = QHBoxLayout()
|
|
||||||
layout1_3 = QHBoxLayout()
|
|
||||||
|
|
||||||
### Spacing and Alignment
|
|
||||||
layout1.setContentsMargins(10, 10, 10, 10)
|
|
||||||
layout1.setSpacing(10)
|
|
||||||
layout1.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
|
|
||||||
layout1_1.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
|
|
||||||
layout1_2.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
|
|
||||||
layout1_3.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
|
|
||||||
|
|
||||||
return layout1,layout1_1,layout1_2,layout1_3
|
|
||||||
|
|
||||||
def layout1_widgets(config):
|
|
||||||
l1,l1_1,l1_2,l1_3 = create_layout1()
|
|
||||||
|
|
||||||
### Create Top Row
|
|
||||||
lbl_src_directory = QLabel('Source Directory')
|
|
||||||
txt_box_src_directory = QLineEdit()
|
|
||||||
txt_box_src_directory.setPlaceholderText(config['folders']['source']['base'])
|
|
||||||
|
|
||||||
btn_src_browse = QPushButton('Browse')
|
|
||||||
btn_src_browse.setFixedSize(100, 40)
|
|
||||||
btn_src_browse.clicked.connect(update_textbox_from_path(
|
|
||||||
txt_box_src_directory,
|
|
||||||
config['folders']['source']['base']))
|
|
||||||
|
|
||||||
### Create Second Row
|
|
||||||
lbl_dst_directory = QLabel('Destination Directory')
|
|
||||||
txt_box_dst_directory = QLineEdit()
|
|
||||||
txt_box_dst_directory.setPlaceholderText(config['folders']['destination']['base'])
|
|
||||||
|
|
||||||
btn_dst_browse = QPushButton('Browse')
|
|
||||||
btn_dst_browse.setFixedSize(100, 40)
|
|
||||||
btn_dst_browse.clicked.connect(update_textbox_from_path(
|
|
||||||
txt_box_dst_directory,
|
|
||||||
config['folders']['destination']['base']))
|
|
||||||
|
|
||||||
### Create Third Row
|
|
||||||
btn_scan_dir = QPushButton("Scan Directory")
|
|
||||||
btn_scan_dir.setFixedSize(100, 40)
|
|
||||||
btn_scan_dir.setCheckable(False)
|
|
||||||
|
|
||||||
### Add Widgets to Layouts
|
|
||||||
l1_1.addWidget(lbl_src_directory)
|
|
||||||
l1_1.addWidget(txt_box_src_directory)
|
|
||||||
l1_1.addWidget(btn_src_browse)
|
|
||||||
l1_2.addWidget(lbl_dst_directory)
|
|
||||||
l1_2.addWidget(txt_box_dst_directory)
|
|
||||||
l1_2.addWidget(btn_dst_browse)
|
|
||||||
l1_3.addWidget(btn_scan_dir)
|
|
||||||
|
|
||||||
### Add Layouts to primary Layout 1
|
|
||||||
l1.addLayout(l1_1)
|
|
||||||
l1.addLayout(l1_2)
|
|
||||||
l1.addLayout(l1_3)
|
|
||||||
|
|
||||||
return l1
|
|
||||||
|
|
||||||
def create_layout2():
|
|
||||||
layout2 = QHBoxLayout()
|
|
||||||
layout2_1 = QHBoxLayout()
|
|
||||||
layout2_2 = QVBoxLayout()
|
|
||||||
layout2_2_1 = QHBoxLayout()
|
|
||||||
layout2_2_2 = QVBoxLayout()
|
|
||||||
|
|
||||||
return layout2,layout2_1,layout2_2,layout2_2_1,layout2_2_2
|
|
||||||
|
|
||||||
def layout2_widgets():
|
|
||||||
l2,l2_1,l2_2,l2_2_1,l2_2_2 = create_layout2()
|
|
||||||
preview_width = int(220)
|
|
||||||
preview_height = int(preview_width * 0.75)
|
|
||||||
|
|
||||||
src_file_list = QListWidget()
|
|
||||||
|
|
||||||
src_file_list.setFixedWidth(550)
|
|
||||||
|
|
||||||
preview_placeholder = QLabel("Placeholder")
|
|
||||||
preview_placeholder.setPixmap(QPixmap('assets/preview_placeholder.jpg'))
|
|
||||||
preview_placeholder.setScaledContents(True)
|
|
||||||
preview_placeholder.setFixedWidth(preview_width)
|
|
||||||
preview_placeholder.setFixedHeight(preview_height)
|
|
||||||
|
|
||||||
l2_1.addWidget(src_file_list)
|
|
||||||
l2_2.addWidget(preview_placeholder)
|
|
||||||
l2_1.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
|
|
||||||
l2_2.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
|
|
||||||
l2.addLayout(l2_1)
|
|
||||||
l2.addLayout(l2_2)
|
|
Loading…
Reference in New Issue