Compare commits

..

No commits in common. "aa973011c60ece9abc003796fd5102514c45448f" and "96111a4f65ce7016e46d6fa2db5cfefc82cf0491" have entirely different histories.

32 changed files with 1170 additions and 1814 deletions

View File

@ -11,13 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>BitMover</string> <string>MainWindow</string>
</property>
<property name="windowIcon">
<iconset theme="applications-science"/>
</property>
<property name="unifiedTitleAndToolBarOnMac">
<bool>true</bool>
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<widget class="QWidget" name="gridLayoutWidget"> <widget class="QWidget" name="gridLayoutWidget">
@ -94,7 +88,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>910</x> <x>910</x>
<y>610</y> <y>650</y>
<width>311</width> <width>311</width>
<height>211</height> <height>211</height>
</rect> </rect>
@ -298,7 +292,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>910</x> <x>910</x>
<y>590</y> <y>630</y>
<width>371</width> <width>371</width>
<height>16</height> <height>16</height>
</rect> </rect>
@ -340,55 +334,11 @@
<rect> <rect>
<x>910</x> <x>910</x>
<y>50</y> <y>50</y>
<width>541</width> <width>182</width>
<height>41</height> <height>91</height>
</rect> </rect>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0" columnstretch="0,0,0,0,0"> <layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0" columnstretch="0,1">
<item row="0" column="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>180</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property>
<property name="text">
<string>Files Imported</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLCDNumber" name="lcd_files_found">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="font"> <property name="font">
@ -401,25 +351,120 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="4"> <item row="0" column="1">
<widget class="QLCDNumber" name="lcd_files_imported"> <widget class="QLCDNumber" name="lcd_files_found"/>
<property name="sizePolicy"> </item>
<sizepolicy hsizetype="Minimum" vsizetype="Minimum"> <item row="1" column="1">
<horstretch>0</horstretch> <widget class="QLCDNumber" name="lcd_files_imported"/>
<verstretch>0</verstretch> </item>
</sizepolicy> <item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property> </property>
<property name="minimumSize"> <property name="text">
<size> <string>Files Imported</string>
<width>80</width>
<height>0</height>
</size>
</property> </property>
<property name="maximumSize"> </widget>
<size> </item>
<width>80</width> </layout>
<height>16777215</height> </widget>
</size> <widget class="QWidget" name="gridLayoutWidget_5">
<property name="geometry">
<rect>
<x>1100</x>
<y>50</y>
<width>351</width>
<height>93</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="1">
<widget class="QProgressBar" name="progressBar_importing_2">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLCDNumber" name="lcd_import_progress">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLCDNumber" name="lcd_current_file_progress">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="l_proecessing_progress">
<property name="text">
<string>Processing Progress</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="l_current_file_progress">
<property name="text">
<string>Current File Progress</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QProgressBar" name="progressBar_processing">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QProgressBar" name="progressBar_importing">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="l_import_progress">
<property name="text">
<string>Import Progress</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLCDNumber" name="lcd_processing_progress">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_2">
<property name="text">
<string>%</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QLabel" name="label_5">
<property name="text">
<string>%</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QLabel" name="label_6">
<property name="text">
<string>%</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -429,7 +474,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>910</x> <x>910</x>
<y>110</y> <y>150</y>
<width>541</width> <width>541</width>
<height>371</height> <height>371</height>
</rect> </rect>
@ -448,7 +493,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>910</x> <x>910</x>
<y>490</y> <y>530</y>
<width>541</width> <width>541</width>
<height>91</height> <height>91</height>
</rect> </rect>
@ -598,8 +643,7 @@
<string>Import Media</string> <string>Import Media</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset theme="drive-harddisk"> <iconset theme="drive-harddisk"/>
<normaloff>.</normaloff>.</iconset>
</property> </property>
</widget> </widget>
</item> </item>
@ -641,8 +685,7 @@
<string>Images</string> <string>Images</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset theme="camera-photo"> <iconset theme="camera-photo"/>
<normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
@ -655,8 +698,7 @@
<string>Video</string> <string>Video</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset theme="camera-video"> <iconset theme="camera-video"/>
<normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
@ -669,8 +711,7 @@
<string>Audio</string> <string>Audio</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset theme="multimedia-player"> <iconset theme="multimedia-player"/>
<normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
@ -714,7 +755,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1463</width> <width>1463</width>
<height>21</height> <height>24</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuBit_Mover"> <widget class="QMenu" name="menuBit_Mover">

View File

@ -1,80 +1,52 @@
#!/usr/bin/env python #!/usr/bin/env python
import os import os
from os import path
from os import rename
import sys import sys
from os import path, rename
from PyQt6.QtCore import QThreadPool from PyQt6.QtCore import QThreadPool
from PyQt6.QtGui import QIcon, QPixmap from PyQt6.QtGui import QIcon, QPixmap
from PyQt6.QtWidgets import QMainWindow, QApplication, QFileDialog from PyQt6.QtWidgets import QMainWindow, QApplication, QFileDialog
from _BitMover_MainWindow import Ui_MainWindow from _BitMover_MainWindow import Ui_MainWindow
from _configure import CONFIG_FILE, Configure
from _find_files import FindFiles
from _find_files_dialog import FindProgress
from _import_dialog import DialogImport from _import_dialog import DialogImport
# from _media_import import MediaImporter from _configure import CONFIG_FILE, Configure
from _preview import MediaPreview from _file_stuff import is_file, create_folder, path_exists, cmp_files
from _hashing import xx_hash
from _img_preview import ImgPreview
from _lumberjack import timber
from _media import Media
from _thread_my_stuff import Worker from _thread_my_stuff import Worker
from _media_file import MediaFile
from _file_stuff import path_exists,is_dir,is_file,create_folder,cmp_files
from _hashing import hash_path
log = timber(__name__)
basedir = os.path.dirname(__file__) basedir = os.path.dirname(__file__)
# TODO: verify source dir actually exists # TODO: verify source dir actually exists
# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow, Ui_MainWindow): class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(MainWindow,self).__init__(*args,**kwargs) super(MainWindow,self).__init__(*args,**kwargs)
self.setupUi(self) self.setupUi(self)
self.setWindowTitle("BitMover") self.setWindowTitle("BitMover")
self.setWindowIcon(QIcon(os.path.join(basedir,'assets', 'forklift.ico'))) self.setWindowIcon(QIcon(os.path.join(basedir,'assets', 'forklift.ico')))
self.threadpool = QThreadPool() self.threadpool = QThreadPool()
self.config = None c = Configure(CONFIG_FILE)
self.src_dir = None self.config = c.load_config()
self.dst_dir = None self.src_dir = self.config['folders']['source']['base']
self.verify_checksum = None self.dst_dir = self.config['folders']['destination']['base']
self.cleanup_files = None self.verify_checksum = self.config['verify_checksum']
self.store_originals = None self.cleanup_files = self.config['cleanup_sd']
self.file_types = None self.store_originals = self.config['store_originals']
self.source_path_hash = None self.file_types = self.config['file_types']
self.search_types = None
self.chunk_size = (16 * 1024) * 1
self.load_config()
self.destination_original_path = None
self.path_file_source = None
self.path_file_destination = None
self.path_file_destination_original = None
# File Stuff # File Stuff
self.total_files = 0 self.total_files = 0
self.file_total = 0 self.file_total = 0
self.files = {} self.files = {}
self.imp_dialog = DialogImport()
self.event_name = None
self.imp_dialog = DialogImport()
self.find_files_dialog = FindProgress()
self.widgets_config() self.widgets_config()
def load_config(self):
try:
c = Configure(CONFIG_FILE)
self.config = c.load_config()
self.src_dir = self.config['folders']['source']['base']
self.dst_dir = self.config['folders']['destination']['base']
self.verify_checksum = self.config.get('verify_checksum', False)
self.cleanup_files = self.config.get('cleanup_sd', False)
self.store_originals = self.config.get('store_originals', False)
self.file_types = self.config.get('file_types', {})
except KeyError as e:
log.error(f"Missing configuration key: {e}")
sys.exit(1) # or provide a fallback
def widgets_config(self): def widgets_config(self):
# Button Setup # Button Setup
self.pushButton_src_browse.clicked.connect(self.select_src_directory) self.pushButton_src_browse.clicked.connect(self.select_src_directory)
@ -88,7 +60,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.toggle_scan_button(True) self.toggle_scan_button(True)
self.toggle_import_button(False) self.toggle_import_button(False)
self.lcd_files_found.display(int(0)) self.lcd_files_found.display(int(0))
self.set_default_thumbnail() self.set_progress_processing(0)
self.set_progress_importing(0)
self.set_progress_current_file(0)
self.img_preview.setPixmap(QPixmap(os.path.join(basedir,
'assets',
'preview_placeholder.jpg')))
self.img_preview.setScaledContents(True)
self.file_list.currentItemChanged.connect(self.index_changed) self.file_list.currentItemChanged.connect(self.index_changed)
self.checkBox_verify_checksum.setChecked(self.verify_checksum) self.checkBox_verify_checksum.setChecked(self.verify_checksum)
self.checkBox_cleanup_files.setChecked(self.cleanup_files) self.checkBox_cleanup_files.setChecked(self.cleanup_files)
@ -101,102 +79,52 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Setup thread pool # Setup thread pool
print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount()) print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())
def toggle_scan_button(self,enable=True):
print(f'toggle_scan_button.enabled: {enable}')
self.pushButton_3_scan_dir.setEnabled(enable)
def toggle_import_button(self,enable=True):
self.pushButton_import.setEnabled(enable)
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 get_source_path_hash(self,f):
self.source_path_hash = hash_path(f)
return self.source_path_hash
def verify_checksum_changed(self): def verify_checksum_changed(self):
if self.checkBox_verify_checksum.isChecked(): if self.checkBox_verify_checksum.isChecked():
self.config['verify_checksum'] = True self.config['verify_checksum'] = True
else: else:
self.config['verify_checksum'] = False self.config['verify_checksum'] = False
print(f"verify_checksums: {self.config['verify_checksums']}") print(f"verify_checksums: {self.config['verify_checksums']}")
def cleanup_files_changed(self): def cleanup_files_changed(self):
if self.checkBox_cleanup_files.isChecked(): if self.checkBox_cleanup_files.isChecked():
self.config['cleanup_sd'] = True self.config['cleanup_sd'] = True
else: else:
self.config['cleanup_sd'] = False self.config['cleanup_sd'] = False
print(f"cleanup_sd: {self.config['cleanup_sd']}") print(f"cleanup_sd: {self.config['cleanup_sd']}")
def store_originals_changed(self): def store_originals_changed(self):
if self.checkBox_store_originals.isChecked(): if self.checkBox_store_originals.isChecked():
self.config['store_originals'] = True self.config['store_originals'] = True
else: else:
self.config['store_originals'] = False self.config['store_originals'] = False
print(f"store_originals: {self.config['store_originals']}") print(f"store_originals: {self.config['store_originals']}")
def set_thumbnail(self,thumb_file,scaled=True,ratio=None): def toggle_scan_button(self,enable=True):
self.img_preview.setPixmap(QPixmap(thumb_file)) self.pushButton_3_scan_dir.setEnabled(enable)
self.img_preview.setScaledContents(scaled)
if ratio is not None:
self.img_preview.setFixedHeight(self.img_preview.width() / ratio)
def set_default_thumbnail(self): def toggle_import_button(self,enable=True):
self.set_thumbnail(os.path.join(basedir, self.pushButton_import.setEnabled(enable)
'assets',
'preview_placeholder.jpg'))
def get_preview(self,path_file_name): def update_preview(self,i):
preview = MediaPreview(path_file_name=path_file_name, preview = ImgPreview(file=i.text(), event=self.get_event(), config=self.config)
media_files=self.files)
return preview
def update_preview(self,preview): self.l_meta_content_date_time_c.setText(preview.dtc)
if preview.file_type.lower() == 'video': path_hash = preview.path_hash
thumb_ratio = 16 / 9 # 1.77
elif preview.file_type.lower() == 'image':
thumb_ratio = preview.thumbnail_ratio
else:
thumb_ratio = 1
self.set_thumbnail(preview.thumbnail,
ratio=thumb_ratio)
def update_metadata(self,preview):
self.clear_metadata()
path_hash = preview.source_path_hash
f = self.files[path_hash]
f_date = f['date']['capture_date']
self.l_data_file_source_path.setText( self.l_data_file_source_path.setText(
self.files[path_hash]['folders']['source_path']) self.files[path_hash]['folders']['source_path'])
self.l_data_file_dest_path.setText( self.l_data_file_dest_path.setText(
self.files[path_hash]['folders']['destination']) self.files[path_hash]['folders']['destination'])
self.l_meta_content_date_time_c.setText(
f"{f_date['y']}/{f_date['m']}/{f_date['d']}"
)
if f['file_type'] == 'image':
f_photo = f['image_meta']['photo']
self.img_preview.setPixmap(QPixmap(preview.thumbnail))
self.img_preview.setFixedHeight(self.img_preview.width() / preview.thumbnail_ratio)
self.update_metadata(preview)
def update_metadata(self,preview):
self.clear_metadata()
if preview.file_type == 'image':
self.l_meta_01.setText('Size') self.l_meta_01.setText('Size')
self.l_meta_02.setText('dpi') self.l_meta_02.setText('dpi')
self.l_meta_03.setText('ISO') self.l_meta_03.setText('ISO')
@ -205,18 +133,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.l_meta_06.setText('Camera') self.l_meta_06.setText('Camera')
self.l_meta_07.setText('Aperture') self.l_meta_07.setText('Aperture')
self.l_meta_08.setText('Megapixels') self.l_meta_08.setText('Megapixels')
self.l_meta_content_01.setText(str(f_photo['size']['width_height'])) self.l_meta_content_01.setText(str(preview.size))
self.l_meta_content_02.setText(str(f_photo['dpi'])) self.l_meta_content_02.setText(str(preview.dpi))
self.l_meta_content_03.setText(str(f_photo['iso'])) self.l_meta_content_03.setText(str(preview.iso))
self.l_meta_content_04.setText(str(f_photo['lens_model'])) self.l_meta_content_04.setText(str(preview.lens))
self.l_meta_content_05.setText(str(f_photo['lens_focal_length'])) self.l_meta_content_05.setText(str(preview.zoom))
self.l_meta_content_06.setText(str(f"{f_photo['camera_brand']} {f_photo['camera_model']}")) self.l_meta_content_06.setText(str(preview.camera))
self.l_meta_content_07.setText(str(f_photo['aperture'])) self.l_meta_content_07.setText(str(preview.aperture))
self.l_meta_content_08.setText(str(f_photo['size']['megapixels'])) self.l_meta_content_08.setText(str(preview.mpixels))
elif f['file_type'] == 'video':
f_video = f['video_meta']['video']
elif preview.file_type == 'video':
self.l_meta_01.setText('Size') self.l_meta_01.setText('Size')
self.l_meta_02.setText('Frames / Second') self.l_meta_02.setText('Frames / Second')
self.l_meta_03.setText('Bit Depth') self.l_meta_03.setText('Bit Depth')
@ -226,14 +152,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.l_meta_07.setText('Profile') self.l_meta_07.setText('Profile')
self.l_meta_08.setText('Pix Format') self.l_meta_08.setText('Pix Format')
self.l_meta_content_01.setText(str(f_video['size']['width_height'])) self.l_meta_content_01.setText(str(preview.size))
self.l_meta_content_02.setText(str(f_video['r_frame_rate'])) self.l_meta_content_02.setText(str(preview.video_framerate))
self.l_meta_content_03.setText(str(f_video['bits_per_raw_sample'])) self.l_meta_content_03.setText(str(preview.video_bit_depth))
self.l_meta_content_04.setText(str(f_video['duration'])) self.l_meta_content_04.setText(str(preview.video_duration))
self.l_meta_content_05.setText(str(f_video['encoding_brand'])) self.l_meta_content_05.setText(str(preview.video_encoding))
self.l_meta_content_06.setText(str(f_video['codec_long_name'])) self.l_meta_content_06.setText(str(preview.video_codec))
self.l_meta_content_07.setText(str(f_video['profile'])) self.l_meta_content_07.setText(str(preview.video_profile))
self.l_meta_content_08.setText(str(f_video['pix_fmt'])) self.l_meta_content_08.setText(str(preview.video_pix_format))
def clear_metadata(self): def clear_metadata(self):
self.l_meta_01.setText('') self.l_meta_01.setText('')
@ -256,57 +182,244 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def index_changed(self,i): def index_changed(self,i):
self.clear_metadata() self.clear_metadata()
if i is None: if i is None:
self.set_default_thumbnail() self.img_preview.setPixmap(QPixmap(os.path.join(basedir,
'assets',
'preview_placeholder.jpg')))
else: else:
self.process_file(i.text()) self.update_preview(i)
print(f'index changed to: {i.text()}')
preview = self.get_preview(i.text())
self.update_preview(preview)
self.update_metadata(preview)
def get_event(self): def select_src_directory(self):
self.event_name = self.eventName.text() directory = QFileDialog.getExistingDirectory(self,
return self.event_name "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 get_search_types(self): def select_dst_directory(self):
self.search_types = [] directory = QFileDialog.getExistingDirectory(self,
if self.checkBox_search_for_images.isChecked(): "Select Directory",
self.search_types.append('image') self.dst_dir)
if self.checkBox_search_for_video.isChecked(): if directory:
self.search_types.append('video') print("Selected Directory:", directory)
if self.checkBox_search_for_audio.isChecked(): # path = Path(directory)
self.search_types.append('audio') self.dst_dir = directory
return self.search_types self.lineEdit_dst_dir.setText(self.dst_dir)
def get_t_files(self): def set_total_files(self,t):
file_total = 0 self.total_files = t
self.file_list.clear() self.lcd_files_found.display(self.total_files)
self.img_preview.setPixmap(QPixmap(os.path.join(basedir,
'assets',
'preview_placeholder.jpg')))
def set_imported_files(self,t):
self.lcd_files_imported.display(t)
def set_progress_processing(self, n):
# print("%d%% done" % n)
self.progressBar_processing.setValue(int(n))
self.lcd_processing_progress.display(n)
def set_progress_importing(self, n):
# print("%d%% done" % n)
self.progressBar_importing.setValue(int(n))
self.lcd_import_progress.display(n)
def set_progress_current_file(self, n):
# print("%d%% done" % n)
self.progressBar_importing_2.setValue(int(n))
self.lcd_current_file_progress.display(n)
def get_t_files(self,search_types):
for folder, subfolders, filename in os.walk(self.src_dir): for folder, subfolders, filename in os.walk(self.src_dir):
for f_type in self.search_types: for f_type in search_types:
for ext in self.file_types[f_type]: for ext in self.file_types[f_type]:
for file in filename: for file in filename:
if file.lower().endswith(ext): if file.lower().endswith(ext):
current_file = os.path.join(folder, file) current_file = os.path.join(folder, file)
if is_file(current_file): if is_file(current_file):
file_total += int(1) self.file_total += int(1)
else: else:
print(f"Skipping {current_file} as it does not look like a real file.") print(f"Skipping {current_file} as it does not look like a real file.")
return file_total
def set_total_files(self,t): def t_find_files(self,
total_file_count = t progress_callback,
self.lcd_files_found.display(total_file_count) import_progress_callback,
current_file_progress_callback,):
file_count = int(0)
search_types = []
def set_imported_files(self,t): if self.checkBox_search_for_images.isChecked():
self.lcd_files_imported.display(t) search_types.append('image')
if self.checkBox_search_for_video.isChecked():
search_types.append('video')
if self.checkBox_search_for_audio.isChecked():
search_types.append('audio')
self.get_t_files(search_types)
self.set_total_files(self.file_total)
if len(search_types) > 0:
for folder, subfolders, filename in os.walk(self.src_dir):
for f_type in search_types:
for ext in self.file_types[f_type]:
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(current_file)
else:
print(f"Skipping {current_file} as it does not look like a real file.")
progress_callback.emit(round((file_count / self.file_total) * 100, 0))
else:
print("Nothing to search for.")
return "Done."
def t_copy_files(self,
progress_callback,
import_progress_callback,
current_file_progress_callback):
""" Copy Files. """
# imp_dialog = DialogImport()
count = int(0)
chunk_size = 16 * 1024
for file in self.files:
self.imp_dialog.set_importing_file(path.join(
self.files[file]['folders']['source_path'],
self.files[file]['name']))
create_folder(self.files[file]['folders']['destination'])
if self.files[file]['type'] == 'video':
chunk_size = (1024 * 1024) * 5
file_exists = path_exists(path.join(
self.files[file]['folders']['destination'],
self.files[file]['name']))
if file_exists is True:
check_match = cmp_files(
path.join(
self.files[file]['folders']['source_path'],
self.files[file]['name']),
path.join(
self.files[file]['folders']['destination'],
self.files[file]['name']
)
)
if check_match is False:
print(f'\nFound duplicate for {self.files[file]["folders"]["source_path"]}/{self.files[file]["name"]}, renaming destination with hash appended.')
base, extension = path.splitext(self.files[file]['name'])
f_xxhash = xx_hash(path.join(
self.files[file]['folders']['destination'],
self.files[file]['name']))
file_name_hash = base + '_' + f_xxhash + extension
rename(path.join(
self.files[file]['folders']['destination'],
self.files[file]['name']),
path.join(
self.files[file]['folders']['destination'],
file_name_hash))
size = path.getsize(
path.join(
self.files[file]['folders']['source_path'],
self.files[file]['name'])
)
with open(path.join(
self.files[file]['folders']['source_path'],
self.files[file]['name']), 'rb') as fs:
with open(
path.join(
self.files[file]['folders']['destination'],
self.files[file]['name']), 'wb') as fd:
while True:
chunk = fs.read(chunk_size)
if not chunk:
break
fd.write(chunk)
dst_size = path.getsize(
path.join(
self.files[file]['folders']['destination'],
self.files[file]['name']
)
)
current_file_progress_callback.emit(round(( dst_size / size ) * 100, 1))
if self.config['store_originals'] is True:
if self.files[file]['type'] == 'image':
create_folder(self.files[file]['folders']['destination_original'])
file_exists = path_exists(path.join(
self.files[file]['folders']['destination_original'],
self.files[file]['name']))
if file_exists is True:
check_match = cmp_files(
path.join(
self.files[file]['folders']['source_path'],
self.files[file]['name']),
path.join(
self.files[file]['folders']['destination_original'],
self.files[file]['name']
)
)
if check_match is False:
print(f'\nFound duplicate for {self.files[file]["folders"]["source_path"]}/{self.files[file]["name"]}, renaming destination with hash appended.')
base, extension = path.splitext(self.files[file]['name'])
f_xxhash = xx_hash(path.join(
self.files[file]['folders']['destination_original'],
self.files[file]['name']))
file_name_hash = base + '_' + f_xxhash + extension
rename(path.join(
self.files[file]['folders']['destination_original'],
self.files[file]['name']),
path.join(
self.files[file]['folders']['destination_original'],
file_name_hash))
size = path.getsize(
path.join(
self.files[file]['folders']['source_path'],
self.files[file]['name'])
)
with open(path.join(
self.files[file]['folders']['source_path'],
self.files[file]['name']), 'rb') as fs:
with open(
path.join(
self.files[file]['folders']['destination_original'],
self.files[file]['name']), 'wb') as fd:
while True:
chunk = fs.read(chunk_size)
if not chunk:
break
fd.write(chunk)
dst_size = path.getsize(
path.join(
self.files[file]['folders']['destination_original'],
self.files[file]['name']
)
)
current_file_progress_callback.emit(round((dst_size / size) * 100, 1))
count += 1
self.set_imported_files(count)
import_progress_callback.emit(round((count / self.file_total) * 100, 1))
self.imp_dialog.add_to_imported_list(
path.join(
self.files[file]['folders']['source_path'],
self.files[file]['name']))
@staticmethod @staticmethod
def print_output(s): def print_output(s):
print(f'output: {s}') print(s)
def worker_thread_started(self): def worker_thread_started(self):
print('scan thread started') print('scan thread started')
@ -316,7 +429,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def worker_thread_done(self): def worker_thread_done(self):
print('scan thread complete.') print('scan thread complete.')
self.toggle_scan_button(True) self.toggle_scan_button(True)
if 0 < len(self.file_list): if 0 < len(self.files):
self.toggle_import_button(True) self.toggle_import_button(True)
else: else:
self.toggle_import_button(False) self.toggle_import_button(False)
@ -325,225 +438,105 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def thread_complete(): def thread_complete():
print("THREAD COMPLETE.") print("THREAD COMPLETE.")
def add_found_file_to_list(self,f):
sph = self.get_source_path_hash(f)
self.files[sph] = {}
self.file_list.addItem(f)
def gen_file_dict(self,d):
self.files = d
def process_file(self,p):
""" gather information and add to dictionary """
media_file = MediaFile(path_file_name=p,
config=self.config,
event_name=self.event_name)
i = self.get_source_path_hash(p)
print(f'processing: {p}\nhash: {i}')
self.files[i] = media_file.media
def find_files(self): def find_files(self):
""" find files to build a dictionary out of """ """ find files to build a dictionary out of """
self.files = {}
self.set_total_files(0)
self.get_event()
self.get_search_types()
self.file_total = self.get_t_files()
self.set_total_files(self.file_total)
# time.sleep(3) # Initialize widgets
self.lcd_files_found.display(int(0))
self.set_progress_processing(0)
self.set_progress_importing(0)
self.img_preview.setPixmap(QPixmap(os.path.join(basedir,
'assets',
'preview_placeholder.jpg')))
self.file_list.clear()
file_finder = FindFiles(search_types=self.search_types, # File Stuff
src_dir=self.src_dir, self.total_files = 0
file_types=self.file_types, self.file_total = 0
file_total=self.file_total) self.files = {}
worker = Worker(file_finder.t_find_files)
worker.signals.started.connect( worker = Worker(self.t_find_files)
self.worker_thread_started) worker.signals.started.connect(self.worker_thread_started)
worker.signals.started.connect( worker.signals.result.connect(self.print_output)
self.find_files_dialog.open_find_files_dialog) worker.signals.finished.connect(self.thread_complete)
worker.signals.progress.connect( worker.signals.finished.connect(self.worker_thread_done)
self.find_files_dialog.set_progress_finding_files) worker.signals.progress.connect(self.set_progress_processing)
worker.signals.found_file.connect(
self.add_found_file_to_list)
# worker.signals.found_file.connect(
# self.process_file)
worker.signals.total_file_count.connect(
self.set_total_files)
# worker.signals.result.connect(
# self.gen_file_dict)
worker.signals.finished.connect(
self.thread_complete)
worker.signals.finished.connect(
self.worker_thread_done)
worker.signals.finished.connect(
self.find_files_dialog.close_find_files_dialog)
# Execute. # Execute.
self.threadpool.start(worker) self.threadpool.start(worker)
# From _media_import.py
def is_video(self,sph):
print(f"Should be sph: {sph}")
print(f"Should be filetype: {self.files[sph]['file_type']}")
if self.files[sph]['file_type'] == 'video':
r = True
else:
r = False
return r
def is_image(self,sph):
if self.files[sph]['file_type'] == 'image':
r = True
else:
r = False
return r
def t_copy_files(self,
progress_callback,
current_file_progress_callback,
imported_file_count_callback,
found_file_callback,
total_file_count_callback
):
""" Copy Files. """
count = int(0)
for line in range(self.file_list.count()):
item = self.file_list.item(line)
file = item.text()
self.process_file(file)
sph = self.get_source_path_hash(file)
print(sph)
self.src_dir = self.files[sph]['folders']['source_path']
self.dst_dir = self.files[sph]['folders']['destination']
self.path_file_source = path.join(self.src_dir,
self.files[sph]['name'])
self.path_file_destination = path.join(self.dst_dir,
self.files[sph]['name'])
self.destination_original_path = path.join(self.dst_dir,
self.files[sph]['folders']['destination_original'])
self.path_file_destination_original = path.join(self.dst_dir,
self.files[sph]['folders']['destination_original'],
self.files[sph]['name'])
self.imp_dialog.set_importing_file(self.path_file_source)
self.copy_a_file(
sph,
progress_callback,
current_file_progress_callback,
imported_file_count_callback,
found_file_callback,
total_file_count_callback
)
if self.check_store_original(sph) is True:
self.copy_a_file(sph,
progress_callback,
current_file_progress_callback,
imported_file_count_callback,
found_file_callback,
total_file_count_callback)
count += 1
imported_file_count_callback.emit(count)
progress_callback.emit(round((count / self.file_total) * 100, 1))
self.imp_dialog.add_to_imported_list(self.path_file_source)
def copy_a_file(self,
fph,
progress_callback,
current_file_progress_callback,
imported_file_count_callback,
found_file_callback,
total_file_count_callback):
size = path.getsize(self.path_file_source)
create_folder(self.dst_dir)
self.check_duplicate(fph)
if self.is_video(fph):
self.chunk_size = (1024 * 1024) * 5
else:
self.chunk_size = (16 * 1024) * 1
with open(self.path_file_source, 'rb') as fs:
with open(self.path_file_destination, 'wb') as fd:
while True:
chunk = fs.read(self.chunk_size)
if not chunk:
break
fd.write(chunk)
dst_size = path.getsize(self.path_file_destination)
current_file_progress_callback.emit(round((dst_size / size) * 100, 1))
def check_store_original(self,sph):
if self.config['store_originals'] is True:
if self.is_image(sph):
self.dst_dir = self.destination_original_path
self.path_file_destination = self.path_file_destination_original
r = True
else:
r = False
else:
r = False
return r
def check_duplicate(self,sph):
if path_exists(self.path_file_destination):
check_match = cmp_files(self.path_file_source, self.path_file_destination)
if check_match is False:
print(f'\nFound duplicate for {self.path_file_source}, renaming destination with hash appended.')
base, extension = path.splitext(self.files[sph]['name'])
f_xxhash = xx_hash(self.path_file_destination)
file_name_hash = base + '_' + f_xxhash + extension
rename(self.path_file_destination, path.join(self.dst_dir, file_name_hash))
# END from _media_import.py
def import_files(self): def import_files(self):
""" """
Import found files Import found files
""" """
# Open Dialog
# imp_dialog = DialogImport()
# imp_dialog.open_import_dialog()
# Initialize Widgets # Initialize Widgets
self.lcd_files_imported.display(int(0)) self.lcd_files_imported.display(int(0))
self.set_progress_importing(0)
self.set_progress_current_file(0)
self.imp_dialog.set_progress_importing(0) self.imp_dialog.set_progress_importing(0)
self.imp_dialog.set_progress_current_file(0) self.imp_dialog.set_progress_current_file(0)
worker = Worker(self.t_copy_files)
worker.signals.started.connect( worker = Worker(self.t_copy_files)
self.worker_thread_started) worker.signals.started.connect(self.worker_thread_started)
worker.signals.started.connect( worker.signals.started.connect(self.imp_dialog.open_import_dialog)
self.imp_dialog.open_import_dialog) worker.signals.result.connect(self.print_output)
worker.signals.progress.connect( worker.signals.finished.connect(self.thread_complete)
self.imp_dialog.set_progress_importing) worker.signals.finished.connect(self.worker_thread_done)
worker.signals.current_file_progress.connect( worker.signals.import_progress.connect(self.set_progress_importing)
self.imp_dialog.set_progress_current_file) worker.signals.import_progress.connect(self.imp_dialog.set_progress_importing)
worker.signals.imported_file_count.connect( worker.signals.current_file_progress.connect(self.set_progress_current_file)
self.set_imported_files) worker.signals.current_file_progress.connect(self.imp_dialog.set_progress_current_file)
worker.signals.result.connect(
self.print_output)
worker.signals.finished.connect(
self.thread_complete)
worker.signals.finished.connect(
self.worker_thread_done)
# Execute # Execute
self.threadpool.start(worker) self.threadpool.start(worker)
def verify_checksum(self): def get_event(self):
# fh_match = FileHash() event_name = self.eventName.text()
print(f'verify_checksum,self.config: {self.config}') return event_name
app = QApplication(sys.argv) def process_file(self,p):
""" gather information and add to dictionary """
window = MainWindow() path_name = os.path.dirname(p)
f_name = os.path.basename(p)
event = self.get_event()
c = self.config
# print(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': {},
'event': {}
}
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.files[i]['event']['name'] = m.event_name
self.file_list.addItem(f"{self.files[i]['folders']['source_path']}/{self.files[i]['name']}")
app = QApplication(sys.argv)
window = MainWindow()
window.show() window.show()
app.exec() app.exec()

View File

@ -13,9 +13,6 @@ class Ui_MainWindow(object):
def setupUi(self, MainWindow): def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow") MainWindow.setObjectName("MainWindow")
MainWindow.resize(1463, 928) MainWindow.resize(1463, 928)
icon = QtGui.QIcon.fromTheme("applications-science")
MainWindow.setWindowIcon(icon)
MainWindow.setUnifiedTitleAndToolBarOnMac(True)
self.centralwidget = QtWidgets.QWidget(parent=MainWindow) self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
self.centralwidget.setObjectName("centralwidget") self.centralwidget.setObjectName("centralwidget")
self.gridLayoutWidget = QtWidgets.QWidget(parent=self.centralwidget) self.gridLayoutWidget = QtWidgets.QWidget(parent=self.centralwidget)
@ -50,7 +47,7 @@ class Ui_MainWindow(object):
self.file_list.setGeometry(QtCore.QRect(20, 160, 871, 701)) self.file_list.setGeometry(QtCore.QRect(20, 160, 871, 701))
self.file_list.setObjectName("file_list") self.file_list.setObjectName("file_list")
self.gridLayoutWidget_2 = QtWidgets.QWidget(parent=self.centralwidget) self.gridLayoutWidget_2 = QtWidgets.QWidget(parent=self.centralwidget)
self.gridLayoutWidget_2.setGeometry(QtCore.QRect(910, 610, 311, 211)) self.gridLayoutWidget_2.setGeometry(QtCore.QRect(910, 650, 311, 211))
self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2") self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2")
self.grid_metadata = QtWidgets.QGridLayout(self.gridLayoutWidget_2) self.grid_metadata = QtWidgets.QGridLayout(self.gridLayoutWidget_2)
self.grid_metadata.setContentsMargins(0, 0, 0, 0) self.grid_metadata.setContentsMargins(0, 0, 0, 0)
@ -157,7 +154,7 @@ class Ui_MainWindow(object):
self.grid_metadata.addWidget(self.l_meta_content_02, 2, 1, 1, 1) self.grid_metadata.addWidget(self.l_meta_content_02, 2, 1, 1, 1)
self.grid_metadata.setColumnStretch(1, 1) self.grid_metadata.setColumnStretch(1, 1)
self.l_exif_ffprobe_title = QtWidgets.QLabel(parent=self.centralwidget) self.l_exif_ffprobe_title = QtWidgets.QLabel(parent=self.centralwidget)
self.l_exif_ffprobe_title.setGeometry(QtCore.QRect(910, 590, 371, 16)) self.l_exif_ffprobe_title.setGeometry(QtCore.QRect(910, 630, 371, 16))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(18) font.setPointSize(18)
font.setBold(True) font.setBold(True)
@ -176,48 +173,87 @@ class Ui_MainWindow(object):
self.eventName.setObjectName("eventName") self.eventName.setObjectName("eventName")
self.gridLayout.addWidget(self.eventName, 0, 1, 1, 1) self.gridLayout.addWidget(self.eventName, 0, 1, 1, 1)
self.gridLayoutWidget_4 = QtWidgets.QWidget(parent=self.centralwidget) self.gridLayoutWidget_4 = QtWidgets.QWidget(parent=self.centralwidget)
self.gridLayoutWidget_4.setGeometry(QtCore.QRect(910, 50, 541, 41)) self.gridLayoutWidget_4.setGeometry(QtCore.QRect(910, 50, 182, 91))
self.gridLayoutWidget_4.setObjectName("gridLayoutWidget_4") self.gridLayoutWidget_4.setObjectName("gridLayoutWidget_4")
self.gridLayout_2 = QtWidgets.QGridLayout(self.gridLayoutWidget_4) self.gridLayout_2 = QtWidgets.QGridLayout(self.gridLayoutWidget_4)
self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
self.gridLayout_2.setObjectName("gridLayout_2") self.gridLayout_2.setObjectName("gridLayout_2")
spacerItem = QtWidgets.QSpacerItem(180, 20, QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Minimum)
self.gridLayout_2.addItem(spacerItem, 0, 2, 1, 1)
self.label_4 = QtWidgets.QLabel(parent=self.gridLayoutWidget_4)
font = QtGui.QFont()
font.setPointSize(18)
self.label_4.setFont(font)
self.label_4.setObjectName("label_4")
self.gridLayout_2.addWidget(self.label_4, 0, 3, 1, 1)
self.lcd_files_found = QtWidgets.QLCDNumber(parent=self.gridLayoutWidget_4)
self.lcd_files_found.setMinimumSize(QtCore.QSize(80, 0))
self.lcd_files_found.setMaximumSize(QtCore.QSize(80, 16777215))
self.lcd_files_found.setObjectName("lcd_files_found")
self.gridLayout_2.addWidget(self.lcd_files_found, 0, 1, 1, 1)
self.label_3 = QtWidgets.QLabel(parent=self.gridLayoutWidget_4) self.label_3 = QtWidgets.QLabel(parent=self.gridLayoutWidget_4)
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(18) font.setPointSize(18)
self.label_3.setFont(font) self.label_3.setFont(font)
self.label_3.setObjectName("label_3") self.label_3.setObjectName("label_3")
self.gridLayout_2.addWidget(self.label_3, 0, 0, 1, 1) 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.lcd_files_imported = QtWidgets.QLCDNumber(parent=self.gridLayoutWidget_4) self.lcd_files_imported = QtWidgets.QLCDNumber(parent=self.gridLayoutWidget_4)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lcd_files_imported.sizePolicy().hasHeightForWidth())
self.lcd_files_imported.setSizePolicy(sizePolicy)
self.lcd_files_imported.setMinimumSize(QtCore.QSize(80, 0))
self.lcd_files_imported.setMaximumSize(QtCore.QSize(80, 16777215))
self.lcd_files_imported.setObjectName("lcd_files_imported") self.lcd_files_imported.setObjectName("lcd_files_imported")
self.gridLayout_2.addWidget(self.lcd_files_imported, 0, 4, 1, 1) self.gridLayout_2.addWidget(self.lcd_files_imported, 1, 1, 1, 1)
self.label_4 = QtWidgets.QLabel(parent=self.gridLayoutWidget_4)
font = QtGui.QFont()
font.setPointSize(18)
self.label_4.setFont(font)
self.label_4.setObjectName("label_4")
self.gridLayout_2.addWidget(self.label_4, 1, 0, 1, 1)
self.gridLayout_2.setColumnStretch(1, 1)
self.gridLayoutWidget_5 = QtWidgets.QWidget(parent=self.centralwidget)
self.gridLayoutWidget_5.setGeometry(QtCore.QRect(1100, 50, 351, 93))
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_importing_2 = QtWidgets.QProgressBar(parent=self.gridLayoutWidget_5)
self.progressBar_importing_2.setProperty("value", 24)
self.progressBar_importing_2.setObjectName("progressBar_importing_2")
self.gridLayout_3.addWidget(self.progressBar_importing_2, 2, 1, 1, 1)
self.lcd_import_progress = QtWidgets.QLCDNumber(parent=self.gridLayoutWidget_5)
self.lcd_import_progress.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
self.lcd_import_progress.setObjectName("lcd_import_progress")
self.gridLayout_3.addWidget(self.lcd_import_progress, 1, 2, 1, 1)
self.lcd_current_file_progress = QtWidgets.QLCDNumber(parent=self.gridLayoutWidget_5)
self.lcd_current_file_progress.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
self.lcd_current_file_progress.setObjectName("lcd_current_file_progress")
self.gridLayout_3.addWidget(self.lcd_current_file_progress, 2, 2, 1, 1)
self.l_proecessing_progress = QtWidgets.QLabel(parent=self.gridLayoutWidget_5)
self.l_proecessing_progress.setObjectName("l_proecessing_progress")
self.gridLayout_3.addWidget(self.l_proecessing_progress, 0, 0, 1, 1)
self.l_current_file_progress = QtWidgets.QLabel(parent=self.gridLayoutWidget_5)
self.l_current_file_progress.setObjectName("l_current_file_progress")
self.gridLayout_3.addWidget(self.l_current_file_progress, 2, 0, 1, 1)
self.progressBar_processing = QtWidgets.QProgressBar(parent=self.gridLayoutWidget_5)
self.progressBar_processing.setProperty("value", 24)
self.progressBar_processing.setObjectName("progressBar_processing")
self.gridLayout_3.addWidget(self.progressBar_processing, 0, 1, 1, 1)
self.progressBar_importing = QtWidgets.QProgressBar(parent=self.gridLayoutWidget_5)
self.progressBar_importing.setProperty("value", 24)
self.progressBar_importing.setObjectName("progressBar_importing")
self.gridLayout_3.addWidget(self.progressBar_importing, 1, 1, 1, 1)
self.l_import_progress = QtWidgets.QLabel(parent=self.gridLayoutWidget_5)
self.l_import_progress.setObjectName("l_import_progress")
self.gridLayout_3.addWidget(self.l_import_progress, 1, 0, 1, 1)
self.lcd_processing_progress = QtWidgets.QLCDNumber(parent=self.gridLayoutWidget_5)
self.lcd_processing_progress.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
self.lcd_processing_progress.setFrameShadow(QtWidgets.QFrame.Shadow.Plain)
self.lcd_processing_progress.setObjectName("lcd_processing_progress")
self.gridLayout_3.addWidget(self.lcd_processing_progress, 0, 2, 1, 1)
self.label_2 = QtWidgets.QLabel(parent=self.gridLayoutWidget_5)
self.label_2.setObjectName("label_2")
self.gridLayout_3.addWidget(self.label_2, 0, 3, 1, 1)
self.label_5 = QtWidgets.QLabel(parent=self.gridLayoutWidget_5)
self.label_5.setObjectName("label_5")
self.gridLayout_3.addWidget(self.label_5, 1, 3, 1, 1)
self.label_6 = QtWidgets.QLabel(parent=self.gridLayoutWidget_5)
self.label_6.setObjectName("label_6")
self.gridLayout_3.addWidget(self.label_6, 2, 3, 1, 1)
self.img_preview = QtWidgets.QLabel(parent=self.centralwidget) self.img_preview = QtWidgets.QLabel(parent=self.centralwidget)
self.img_preview.setGeometry(QtCore.QRect(910, 110, 541, 371)) self.img_preview.setGeometry(QtCore.QRect(910, 150, 541, 371))
self.img_preview.setAutoFillBackground(True) self.img_preview.setAutoFillBackground(True)
self.img_preview.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) self.img_preview.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
self.img_preview.setText("") self.img_preview.setText("")
self.img_preview.setObjectName("img_preview") self.img_preview.setObjectName("img_preview")
self.gridLayoutWidget_6 = QtWidgets.QWidget(parent=self.centralwidget) self.gridLayoutWidget_6 = QtWidgets.QWidget(parent=self.centralwidget)
self.gridLayoutWidget_6.setGeometry(QtCore.QRect(910, 490, 541, 91)) self.gridLayoutWidget_6.setGeometry(QtCore.QRect(910, 530, 541, 91))
self.gridLayoutWidget_6.setObjectName("gridLayoutWidget_6") self.gridLayoutWidget_6.setObjectName("gridLayoutWidget_6")
self.grid_metadata_2 = QtWidgets.QGridLayout(self.gridLayoutWidget_6) self.grid_metadata_2 = QtWidgets.QGridLayout(self.gridLayoutWidget_6)
self.grid_metadata_2.setContentsMargins(0, 0, 0, 0) self.grid_metadata_2.setContentsMargins(0, 0, 0, 0)
@ -262,8 +298,8 @@ class Ui_MainWindow(object):
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2) self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1) self.horizontalLayout_2.addItem(spacerItem)
self.checkBox_verify_checksum = QtWidgets.QCheckBox(parent=self.horizontalLayoutWidget_2) self.checkBox_verify_checksum = QtWidgets.QCheckBox(parent=self.horizontalLayoutWidget_2)
self.checkBox_verify_checksum.setChecked(True) self.checkBox_verify_checksum.setChecked(True)
self.checkBox_verify_checksum.setObjectName("checkBox_verify_checksum") self.checkBox_verify_checksum.setObjectName("checkBox_verify_checksum")
@ -291,8 +327,8 @@ class Ui_MainWindow(object):
self.label = QtWidgets.QLabel(parent=self.horizontalLayoutWidget_3) self.label = QtWidgets.QLabel(parent=self.horizontalLayoutWidget_3)
self.label.setObjectName("label") self.label.setObjectName("label")
self.horizontalLayout_3.addWidget(self.label) self.horizontalLayout_3.addWidget(self.label)
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.horizontalLayout_3.addItem(spacerItem2) self.horizontalLayout_3.addItem(spacerItem1)
self.checkBox_search_for_images = QtWidgets.QCheckBox(parent=self.horizontalLayoutWidget_3) self.checkBox_search_for_images = QtWidgets.QCheckBox(parent=self.horizontalLayoutWidget_3)
icon = QtGui.QIcon.fromTheme("camera-photo") icon = QtGui.QIcon.fromTheme("camera-photo")
self.checkBox_search_for_images.setIcon(icon) self.checkBox_search_for_images.setIcon(icon)
@ -324,7 +360,7 @@ class Ui_MainWindow(object):
self.horizontalLayout_3.addWidget(self.pushButton_3_scan_dir) self.horizontalLayout_3.addWidget(self.pushButton_3_scan_dir)
MainWindow.setCentralWidget(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow) self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1463, 21)) self.menubar.setGeometry(QtCore.QRect(0, 0, 1463, 24))
self.menubar.setObjectName("menubar") self.menubar.setObjectName("menubar")
self.menuBit_Mover = QtWidgets.QMenu(parent=self.menubar) self.menuBit_Mover = QtWidgets.QMenu(parent=self.menubar)
self.menuBit_Mover.setObjectName("menuBit_Mover") self.menuBit_Mover.setObjectName("menuBit_Mover")
@ -341,7 +377,7 @@ class Ui_MainWindow(object):
def retranslateUi(self, MainWindow): def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "BitMover")) MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton_src_browse.setText(_translate("MainWindow", "Browse")) self.pushButton_src_browse.setText(_translate("MainWindow", "Browse"))
self.pushButton_dst_browse.setText(_translate("MainWindow", "Browse")) self.pushButton_dst_browse.setText(_translate("MainWindow", "Browse"))
self.label_1_src_dir.setText(_translate("MainWindow", "Source Directory")) self.label_1_src_dir.setText(_translate("MainWindow", "Source Directory"))
@ -357,8 +393,14 @@ class Ui_MainWindow(object):
self.l_meta_08.setText(_translate("MainWindow", "Focal Length")) self.l_meta_08.setText(_translate("MainWindow", "Focal Length"))
self.l_exif_ffprobe_title.setText(_translate("MainWindow", "Exif / ffprobe Data")) self.l_exif_ffprobe_title.setText(_translate("MainWindow", "Exif / ffprobe Data"))
self.labelEvent.setText(_translate("MainWindow", "Event")) self.labelEvent.setText(_translate("MainWindow", "Event"))
self.label_4.setText(_translate("MainWindow", "Files Imported"))
self.label_3.setText(_translate("MainWindow", "Files Found")) self.label_3.setText(_translate("MainWindow", "Files Found"))
self.label_4.setText(_translate("MainWindow", "Files Imported"))
self.l_proecessing_progress.setText(_translate("MainWindow", "Processing Progress"))
self.l_current_file_progress.setText(_translate("MainWindow", "Current File Progress"))
self.l_import_progress.setText(_translate("MainWindow", "Import Progress"))
self.label_2.setText(_translate("MainWindow", "%"))
self.label_5.setText(_translate("MainWindow", "%"))
self.label_6.setText(_translate("MainWindow", "%"))
self.l_file_source_path.setText(_translate("MainWindow", "Source Path")) self.l_file_source_path.setText(_translate("MainWindow", "Source Path"))
self.l_file_dest_path.setText(_translate("MainWindow", "Destination Path")) self.l_file_dest_path.setText(_translate("MainWindow", "Destination Path"))
self.checkBox_verify_checksum.setToolTip(_translate("MainWindow", "After copying, verify that the hash of the original file equals the hash of the copied file.")) self.checkBox_verify_checksum.setToolTip(_translate("MainWindow", "After copying, verify that the hash of the original file equals the hash of the copied file."))

View File

@ -1,153 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FileComparisonDialog</class>
<widget class="QDialog" name="FileComparisonDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1588</width>
<height>753</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<widget class="QTableWidget" name="tableWidget">
<property name="geometry">
<rect>
<x>20</x>
<y>90</y>
<width>1551</width>
<height>651</height>
</rect>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="tabKeyNavigation">
<bool>false</bool>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="rowCount">
<number>2</number>
</property>
<property name="columnCount">
<number>5</number>
</property>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>300</number>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>true</bool>
</attribute>
<row/>
<row/>
<column/>
<column/>
<column/>
<column/>
<column/>
</widget>
<widget class="QWidget" name="gridLayoutWidget">
<property name="geometry">
<rect>
<x>240</x>
<y>11</y>
<width>981</width>
<height>71</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_2">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="maximumSize">
<size>
<width>80</width>
<height>16777215</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Source</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="maximumSize">
<size>
<width>80</width>
<height>16777215</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Destination</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QLabel" name="label_5">
<property name="geometry">
<rect>
<x>20</x>
<y>10</y>
<width>231</width>
<height>61</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>28</pointsize>
</font>
</property>
<property name="text">
<string>Comparing Files</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,47 +0,0 @@
m = {
'f5c7c1a4e55d6288': {
'date': {
'capture_date': {
'y': '2024',
'm': '09',
'd': '08'
}
},
'event': {
'name': ''
},
'extension': 'JPG',
'folders': {
'destination': '/Users/kkenny/import/testing_dst/2024/2024-09/2024-09-08/PHOTO/JPG',
'destination_original': '',
'source_path': '/Users/kkenny/import/testing_src'
},
'name': 'DSC_6481.JPG',
'source_path_hash': 'f5c7c1a4e55d6288',
'type': 'image',
'image_meta': {
'photo': {
'aperture': (0x829D) Ratio = 11 @ 736,
'camera_brand': (0x010F)ASCII = NIKON CORPORATION @ 148,
'camera_model': (0x0110)ASCII = NIKON D5100 @ 168,
'dpi': < bound method PhotoFile.get_dpi of < _photo.PhotoFile object at 0x130f05fd0 >>,
'is_jpg': True,
'is_raw': False,
'iso': (0x8827) Short = 3200 @ 274,
'lens_make': None,
'lens_model': None,
'lens_focal_length': (0x920A) Ratio = 135 @ 808,
'size': {
'height': 3264,
'width': 4928,
'width_height': '4928x3264',
'megapixels': 16084992,
'ratio': 1.5098039215686274
}
}
},
'video_meta': None,
'audio_meta': None
}
}

View File

@ -1,62 +0,0 @@
#!/usr/bin/env python
import os.path
import ffmpeg
from datetime import datetime
from _time_and_date_utils import convert_from_seconds
class AudioFile:
def __init__(self,path_file_name,*args,**kwargs):
# super(AudioFile, self).__init__(*args, **kwargs)
self.path_file_name = path_file_name
self.probe = ffmpeg.probe(self.path_file_name)
self.audio_capture_date = self.get_audio_capture_date()
self.video_stream = None
self.audio_stream = None
self.format_stream = None
for i in self.probe['streams']:
if self.video_stream is None:
if 'video' == i['codec_type'].lower():
self.video_stream = i
if self.audio_stream is None:
if 'audio' == i['codec_type'].lower():
self.audio_stream = i
try:
self.format_stream = self.probe['format']
except AttributeError as e:
print(f"{e}: Audio file has no format string")
self.format_stream = None
self.stream = {}
def get_audio_capture_date(self):
#TODO: refactor this try/except logic.
stamp = None
if self.format_stream is not None:
try:
stamp = datetime.strptime(
self.format_stream['tags']['date'],'%Y-%m-%d')
except KeyError:
try:
stamp = datetime.fromtimestamp(os.path.getctime(self.path_file_name))
except:
stamp = datetime.strptime(
str('1900:01:01 00:00:00'),
'%Y:%m:%d %H:%M:%S'
)
return stamp
def get_audio_meta(self):
self.stream = {
'audio': {
'audio_channels': self.audio_stream['channels'],
'bits_per_sample': self.audio_stream['bits_per_raw_sample'],
'codec_long_name': self.audio_stream['codec_long_name'],
'duration': convert_from_seconds(float(self.audio_stream['duration'])),
'encoding_brand': self.format_stream['tags']['encoded_by'],
'sample_rate': self.audio_stream['sample_rate']
},
'video': {},
'format': {}
}
return self.stream

View File

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChecksumProgressDialog</class>
<widget class="QDialog" name="ChecksumProgressDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>640</width>
<height>86</height>
</rect>
</property>
<property name="windowTitle">
<string>Checksum Progress</string>
</property>
<widget class="QWidget" name="gridLayoutWidget">
<property name="geometry">
<rect>
<x>20</x>
<y>20</y>
<width>601</width>
<height>21</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="l_title_getting_checksum">
<property name="minimumSize">
<size>
<width>100</width>
<height>1</height>
</size>
</property>
<property name="baseSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="text">
<string>Getting Checksum For</string>
</property>
<property name="margin">
<number>3</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="l_content_checksum_filename">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QProgressBar" name="progressBar_getting_checksum">
<property name="geometry">
<rect>
<x>20</x>
<y>50</y>
<width>611</width>
<height>19</height>
</rect>
</property>
<property name="value">
<number>0</number>
</property>
<property name="textVisible">
<bool>true</bool>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,50 +0,0 @@
# Form implementation generated from reading ui file '_checksum_progress_dialog.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_ChecksumProgressDialog(object):
def setupUi(self, ChecksumProgressDialog):
ChecksumProgressDialog.setObjectName("ChecksumProgressDialog")
ChecksumProgressDialog.resize(640, 86)
self.gridLayoutWidget = QtWidgets.QWidget(parent=ChecksumProgressDialog)
self.gridLayoutWidget.setGeometry(QtCore.QRect(20, 20, 601, 21))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.l_title_getting_checksum = QtWidgets.QLabel(parent=self.gridLayoutWidget)
self.l_title_getting_checksum.setMinimumSize(QtCore.QSize(100, 1))
self.l_title_getting_checksum.setBaseSize(QtCore.QSize(100, 0))
self.l_title_getting_checksum.setLineWidth(0)
self.l_title_getting_checksum.setObjectName("l_title_getting_checksum")
self.gridLayout.addWidget(self.l_title_getting_checksum, 0, 0, 1, 1)
self.l_content_checksum_filename = QtWidgets.QLabel(parent=self.gridLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(1)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.l_content_checksum_filename.sizePolicy().hasHeightForWidth())
self.l_content_checksum_filename.setSizePolicy(sizePolicy)
self.l_content_checksum_filename.setMinimumSize(QtCore.QSize(150, 0))
self.l_content_checksum_filename.setText("")
self.l_content_checksum_filename.setObjectName("l_content_checksum_filename")
self.gridLayout.addWidget(self.l_content_checksum_filename, 0, 1, 1, 1)
self.progressBar_getting_checksum = QtWidgets.QProgressBar(parent=ChecksumProgressDialog)
self.progressBar_getting_checksum.setGeometry(QtCore.QRect(20, 50, 611, 19))
self.progressBar_getting_checksum.setProperty("value", 0)
self.progressBar_getting_checksum.setTextVisible(True)
self.progressBar_getting_checksum.setObjectName("progressBar_getting_checksum")
self.retranslateUi(ChecksumProgressDialog)
QtCore.QMetaObject.connectSlotsByName(ChecksumProgressDialog)
def retranslateUi(self, ChecksumProgressDialog):
_translate = QtCore.QCoreApplication.translate
ChecksumProgressDialog.setWindowTitle(_translate("ChecksumProgressDialog", "Checksum Progress"))
self.l_title_getting_checksum.setText(_translate("ChecksumProgressDialog", "Getting Checksum For"))

View File

@ -1,73 +0,0 @@
# Form implementation generated from reading ui file '_ComparisonDialog.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_FileComparisonDialog(object):
def setupUi(self, FileComparisonDialog):
FileComparisonDialog.setObjectName("FileComparisonDialog")
FileComparisonDialog.resize(1588, 753)
self.tableWidget = QtWidgets.QTableWidget(parent=FileComparisonDialog)
self.tableWidget.setGeometry(QtCore.QRect(20, 90, 1551, 651))
self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
self.tableWidget.setTabKeyNavigation(False)
self.tableWidget.setProperty("showDropIndicator", False)
self.tableWidget.setRowCount(2)
self.tableWidget.setColumnCount(5)
self.tableWidget.setObjectName("tableWidget")
self.tableWidget.horizontalHeader().setDefaultSectionSize(300)
self.tableWidget.verticalHeader().setVisible(True)
self.gridLayoutWidget = QtWidgets.QWidget(parent=FileComparisonDialog)
self.gridLayoutWidget.setGeometry(QtCore.QRect(240, 11, 981, 71))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.label_4 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
self.label_4.setObjectName("label_4")
self.gridLayout.addWidget(self.label_4, 2, 2, 1, 1)
self.label = QtWidgets.QLabel(parent=self.gridLayoutWidget)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 1, 1, 1)
self.label_3 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 2, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 0, 2, 1, 1)
self.label_6 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
self.label_6.setMaximumSize(QtCore.QSize(80, 16777215))
self.label_6.setBaseSize(QtCore.QSize(0, 0))
self.label_6.setObjectName("label_6")
self.gridLayout.addWidget(self.label_6, 0, 0, 1, 1)
self.label_7 = QtWidgets.QLabel(parent=self.gridLayoutWidget)
self.label_7.setMaximumSize(QtCore.QSize(80, 16777215))
self.label_7.setBaseSize(QtCore.QSize(0, 0))
self.label_7.setObjectName("label_7")
self.gridLayout.addWidget(self.label_7, 2, 0, 1, 1)
self.label_5 = QtWidgets.QLabel(parent=FileComparisonDialog)
self.label_5.setGeometry(QtCore.QRect(20, 10, 231, 61))
font = QtGui.QFont()
font.setPointSize(28)
self.label_5.setFont(font)
self.label_5.setObjectName("label_5")
self.retranslateUi(FileComparisonDialog)
QtCore.QMetaObject.connectSlotsByName(FileComparisonDialog)
def retranslateUi(self, FileComparisonDialog):
_translate = QtCore.QCoreApplication.translate
FileComparisonDialog.setWindowTitle(_translate("FileComparisonDialog", "Dialog"))
self.label_4.setText(_translate("FileComparisonDialog", "TextLabel"))
self.label.setText(_translate("FileComparisonDialog", "TextLabel"))
self.label_3.setText(_translate("FileComparisonDialog", "TextLabel"))
self.label_2.setText(_translate("FileComparisonDialog", "TextLabel"))
self.label_6.setText(_translate("FileComparisonDialog", "Source"))
self.label_7.setText(_translate("FileComparisonDialog", "Destination"))
self.label_5.setText(_translate("FileComparisonDialog", "Comparing Files"))

View File

@ -6,10 +6,12 @@ Load the config file from yaml file
import sys import sys
import os import os
import yaml import yaml
from _lumberjack import timber
basedir = os.path.dirname(__file__) basedir = os.path.dirname(__file__)
files = {} files = {}
CONFIG_FILE = os.path.join(basedir, 'config.yaml') CONFIG_FILE = os.path.join(basedir, 'config.yaml')
log = timber(__name__)
class Configure: class Configure:
""" Configure Class """ """ Configure Class """

View File

@ -9,32 +9,49 @@ import yaml
from tqdm import tqdm from tqdm import tqdm
### Local Imports ### Local Imports
# from configure import Configure, CONFIG_FILE
from _hashing import xx_hash from _hashing import xx_hash
from _lumberjack import timber
def check_log_dir(d): def check_log_dir(d):
create_folder(d) create_folder(d)
# c = Configure(CONFIG_FILE)
# config = c.load_config()
log = timber(__name__)
log.info("Starting")
def dump_yaml(dictionary,file): def dump_yaml(dictionary,file):
""" dump dictionary to yaml file """ """ dump dictionary to yaml file """
with open(file, 'w', encoding="utf-8") as f: with open(file, 'w', encoding="utf-8") as f:
yaml.dump(dictionary, f) yaml.dump(dictionary, f)
f.close() f.close()
def path_exists(p): def path_exists(p):
""" determine if the path exists """ """ determine if the path exists """
log.debug(f'path_exists({p})')
pe = os.path.exists(p) pe = os.path.exists(p)
# print(f'Path Exists: {pe}')
return pe return pe
def is_dir(d): def is_dir(d):
""" determine if object is a dir or not """ """ determine if object is a dir or not """
log.debug(f'is_dir({d})')
return os.path.isdir(d) return os.path.isdir(d)
def is_file(f): def is_file(f):
""" determine if object is a file or not """ """ determine if object is a file or not """
log.debug(f'is_file({f})')
return os.path.isfile(f) return os.path.isfile(f)
def cmp_files(f1,f2): def cmp_files(f1,f2):
""" compare two files """ """ compare two files """
log.debug(f'cmp_files({f1},{f2})')
#TODO: Determine if path is actually a file #TODO: Determine if path is actually a file
#TODO: Determine if the hash has already been stored and use it if so #TODO: Determine if the hash has already been stored and use it if so
@ -44,22 +61,30 @@ def cmp_files(f1,f2):
def path_access_read(path): def path_access_read(path):
""" make sure we can read from the path """ """ make sure we can read from the path """
log.debug(f'path_access_read({path})')
val = os.access(path, os.R_OK) val = os.access(path, os.R_OK)
if val is False: if val is False:
print(f'path_access_read check: cannot read from {path}') log.error(f'Can not read from {path}')
return val return val
def path_access_write(path): def path_access_write(path):
""" make sure we can write to the path """ """ make sure we can write to the path """
log.debug(f'path_access_write({path})')
val = os.access(path, os.W_OK) val = os.access(path, os.W_OK)
if val is False: if val is False:
print(f'path_access_write check: cannot write to {path}') log.error(f'Can not write to {path}')
return val return val
def create_folder(file): def create_folder(file):
""" Function to create folder """ """ Function to create folder """
log.debug(f'create_folder({file})')
if path_exists(file) is False: if path_exists(file) is False:
os.makedirs(file) os.makedirs(file)
elif is_dir(file) is False: elif is_dir(file) is False:
@ -67,17 +92,22 @@ def create_folder(file):
def cleanup_sd(f,config): def cleanup_sd(f,config):
""" If we should clean up the SD, nuke the copied files. """ """ If we should clean up the SD, nuke the copied files. """
log.debug(f'cleanup_sd({f})')
if config['cleanup_sd'] is True: if config['cleanup_sd'] is True:
os.system('clear') os.system('clear')
for file in tqdm(f, desc = "Cleaning Up SD:"): for file in tqdm(f, desc = "Cleaning Up SD:"):
if f[file]['source_cleanable'] is True: if f[file]['source_cleanable'] is True:
print(f"Cleanup SD: Removing File -\n{os.path.join(f[file]['folders']['source_path'],f[file]['name'])}") 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'])) os.remove(os.path.join(f[file]['folders']['source_path'],f[file]['name']))
else: else:
print(f"Cleanup SD: Not Removing File -\n{os.path.join(f[file]['folders']['source_path'],f[file]['name'])}") 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): def validate_config_dir_access(config):
""" Validate we can operate in the defined directories """ """ Validate we can operate in the defined directories """
log.debug('validate_config_dir_access')
check = path_access_write(config['folders']['destination']['base']) check = path_access_write(config['folders']['destination']['base'])
if check is False: if check is False:
accessible = False accessible = False
@ -96,11 +126,3 @@ def validate_config_dir_access(config):
accessible = True accessible = True
return accessible return accessible
def get_file_name(path_file_name):
return os.path.basename(path_file_name)
def get_dotted_file_ext(file_name):
return os.path.splitext(file_name)[1]
def get_file_ext(file_name):
return get_dotted_file_ext(file_name).split('.')[1]

View File

@ -1,51 +0,0 @@
import os
from _file_stuff import is_file
basedir = os.path.dirname(__file__)
class FindFiles:
def __init__(self,
search_types,
src_dir,
file_total,
file_types,
*args,
**kwargs):
super(FindFiles,self).__init__(*args,**kwargs)
self.file_total = file_total
self.src_dir = src_dir
self.search_types = search_types
self.file_types = file_types
self.file_name = None
self.path_file_source = None
self.file_type = None
self.file_ext = None
def t_find_files(self,
progress_callback,
current_file_progress_callback,
imported_file_count_callback,
found_file_callback,
total_file_count_callback):
file_count = int(0)
if len(self.search_types) > 0:
for folder, subfolders, filename in os.walk(self.src_dir):
for self.file_type in self.search_types:
for self.file_ext in self.file_types[self.file_type]:
for self.file_name in filename:
if self.file_name.lower().endswith(self.file_ext):
self.path_file_source = os.path.join(folder, self.file_name)
if is_file(self.path_file_source):
file_count += int(1)
# process_file(current_file)
found_file_callback.emit(self.path_file_source)
else:
print(f"Skipping {self.path_file_source} as it does not look like a real file.")
total_file_count_callback.emit(file_count)
progress_callback.emit(round((file_count / self.file_total) * 100, 0))
else:
print("Nothing to search for.")

View File

@ -1,31 +0,0 @@
from PyQt6.QtWidgets import QDialog
from _finding_files_dialog_Window import Ui_FindProgress
class FindProgress(QDialog, Ui_FindProgress):
def __init__(self,*args,**kwargs):
super(FindProgress,self).__init__(*args,**kwargs)
self.setupUi(self)
# self.set_progress_finding_files(0)
def is_shown(self):
print(f'is_shown: {self.isVisible()}')
return self.isVisible()
def open_find_files_dialog(self):
print(f'open_import_dialog: {self.is_shown()}')
if not self.is_shown():
print('Inside if not self.is_shown')
print('showing window')
self.show()
def close_find_files_dialog(self):
print('close_import_dialog')
if self.is_shown():
print('inside self.is_shown()')
print('hiding window')
self.hide()
def set_progress_finding_files(self,n):
print("%d%% done" % n)
self.progressBar_importing.setValue(float(n))
self.lcd_import_progress.display(n)

View File

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FindProgress</class>
<widget class="QDialog" name="FindProgress">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>640</width>
<height>60</height>
</rect>
</property>
<property name="windowTitle">
<string>Finding Files</string>
</property>
<widget class="QWidget" name="gridLayoutWidget_5">
<property name="geometry">
<rect>
<x>20</x>
<y>10</y>
<width>601</width>
<height>41</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="l_find_progress">
<property name="text">
<string>Progress</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLCDNumber" name="lcd_import_progress">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QProgressBar" name="progressBar_importing">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_5">
<property name="text">
<string>%</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,44 +0,0 @@
# Form implementation generated from reading ui file '_finding_files_dialog.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_FindProgress(object):
def setupUi(self, FindProgress):
FindProgress.setObjectName("FindProgress")
FindProgress.resize(640, 60)
self.gridLayoutWidget_5 = QtWidgets.QWidget(parent=FindProgress)
self.gridLayoutWidget_5.setGeometry(QtCore.QRect(20, 10, 601, 41))
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.l_find_progress = QtWidgets.QLabel(parent=self.gridLayoutWidget_5)
self.l_find_progress.setObjectName("l_find_progress")
self.gridLayout_3.addWidget(self.l_find_progress, 0, 0, 1, 1)
self.lcd_import_progress = QtWidgets.QLCDNumber(parent=self.gridLayoutWidget_5)
self.lcd_import_progress.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
self.lcd_import_progress.setObjectName("lcd_import_progress")
self.gridLayout_3.addWidget(self.lcd_import_progress, 0, 2, 1, 1)
self.progressBar_importing = QtWidgets.QProgressBar(parent=self.gridLayoutWidget_5)
self.progressBar_importing.setProperty("value", 24)
self.progressBar_importing.setObjectName("progressBar_importing")
self.gridLayout_3.addWidget(self.progressBar_importing, 0, 1, 1, 1)
self.label_5 = QtWidgets.QLabel(parent=self.gridLayoutWidget_5)
self.label_5.setObjectName("label_5")
self.gridLayout_3.addWidget(self.label_5, 0, 3, 1, 1)
self.retranslateUi(FindProgress)
QtCore.QMetaObject.connectSlotsByName(FindProgress)
def retranslateUi(self, FindProgress):
_translate = QtCore.QCoreApplication.translate
FindProgress.setWindowTitle(_translate("FindProgress", "Finding Files"))
self.l_find_progress.setText(_translate("FindProgress", "Progress"))
self.label_5.setText(_translate("FindProgress", "%"))

98
_get_image_tag.py Normal file
View File

@ -0,0 +1,98 @@
#!/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

View File

@ -7,6 +7,12 @@ dump the dictionary generated from findings into a yaml file for later inspectio
import os import os
import xxhash import xxhash
from tqdm import tqdm 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): def xx_hash(file):
""" calculates and returns file hash based on xxHash """ """ calculates and returns file hash based on xxHash """
@ -33,13 +39,19 @@ def hash_path(path):
def gen_xxhashes(f): def gen_xxhashes(f):
""" Generate xxHashes """ """ Generate xxHashes """
log.debug(f'gen_xxhashes({f})')
for file in tqdm(f, desc = "Generating xx Hashes:"): for file in tqdm(f, desc = "Generating xx Hashes:"):
os.system('clear') os.system('clear')
log.debug(f[file])
f[file]['xx_checksums'] = {} f[file]['xx_checksums'] = {}
for folder in f[file]['folders']: for folder in f[file]['folders']:
k = os.path.join(f[file]['folders'][folder], f[file]['name']) k = os.path.join(f[file]['folders'][folder], f[file]['name'])
if k != f[file]['name']: if k != f[file]['name']:
# k = f[file]['folders'][folder]
log.debug(k)
f[file]['xx_checksums'][k] = xx_hash(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): def validate_xx_checksums(f):
""" Validate Checksums """ """ Validate Checksums """
@ -55,8 +67,8 @@ def validate_xx_checksums(f):
f[file]['source_cleanable'] = True f[file]['source_cleanable'] = True
else: else:
f[file]['source_cleanable'] = False f[file]['source_cleanable'] = False
print(f'FATAL: Checksum validation failed for: \ log.critical(f'FATAL: Checksum validation failed for: \
{f[file]["name"]} \n{c[i]}\n is not equal to \n{c[p]}\n') {f[file]["name"]} \n{c[i]}\n is not equal to \n{c[p]}\n')
print('\n File Meta:\n') log.debug('\n File Meta:\n')
print(f'f[file]: {f[file]}') log.debug(f[file])
i = i + 1 i = i + 1

View File

@ -1,23 +0,0 @@
import exifread
from _time_and_date_utils import get_img_date
class ImageTag:
def __init__(self,path_file_name,*args,**kwargs):
super(ImageTag,self).__init__(*args,**kwargs)
self.path_file_name = path_file_name
self.image_tags = self.get_image_tags()
self.date_time_original = get_img_date(self.image_tags)
def get_image_tag(self,t):
tag_data = None
for tag in self.image_tags:
if t.lower() in tag.lower():
tag_data = self.image_tags[tag]
break
return tag_data
def get_image_tags(self):
print(f'get_image_tags, self.path_file_name: {self.path_file_name}')
with open(self.path_file_name,'rb') as f:
tags = exifread.process_file(f)
return tags

124
_img_preview.py Normal file
View File

@ -0,0 +1,124 @@
#!/usr/bin/env python
from _media import Media
from _get_image_tag import get_exif_tag
from PIL import Image
from _raw_photo import get_raw_image_dimensions, extract_jpg_thumb
from _video import Video
class ImgPreview:
def __init__(self,*args,**kwargs):
super(ImgPreview,self).__init__()
self.args = args
self.kwargs = kwargs
self.file = kwargs['file']
self.event = kwargs['event']
self.config = kwargs['config']
self.m = Media(self.file,
self.event,
self.config)
self.file_type = self.m.file_type
self.is_jpg = False
self.is_raw = False
self.path_hash = self.m.source_path_hash
self.dtc = f'{self.m.capture_date[0]}/{self.m.capture_date[1]}/{self.m.capture_date[2]}'
self.size = None
self.mpixels = None
self.thumbnail = 'thumbnail.jpg'
self.ratio = None
self.thumbnail_width = None
self.thumbnail_height = None
self.thumbnail_ratio = None
self.video_framerate = None
self.video_bit_depth = None
self.video_duration = None
self.video_encoding = None
self.video_codec = None
self.video_profile = None
self.video_pix_format = None
if self.file_type == 'image':
self._img_preview()
print(f'size: {self.size}')
print(f'dpi: {self.dpi}')
print(f'iso: {self.iso}')
print(f'lens: {self.lens}')
print(f'zoom: {self.zoom}')
print(f'camera: {self.camera}')
print(f'aperture: {self.aperture}')
print(f'mpixels: {self.mpixels}')
elif self.file_type == 'video':
self._video_preview()
self.thumb_ratio()
self.size = f'{self.width}x{self.height}'
if self.width is not None \
and self.height is not None:
self.mpixels = round((self.width * self.height) / 1000000, 1)
else:
self.mpixels = 'Unknown :('
def _img_preview(self):
self.dpi = get_exif_tag(self.file, "xresolution")
self.iso = get_exif_tag(self.file, 'iso')
self.aperture = get_exif_tag(self.file, 'fnumber')
self.camera = get_exif_tag(self.file,'cameramodelname')
if self.camera is None:
self.camera = get_exif_tag(self.file,'image model')
self.lens = get_exif_tag(self.file,'lensmodel')
self.zoom = get_exif_tag(self.file,'focallength')
if self.file.lower().endswith("jpg") \
or self.file.lower().endswith("jpeg"):
self._jpg_preview()
else:
self._raw_preview()
def _jpg_preview(self):
self.is_jpg = True
img = Image.open(self.file)
self.width = img.width
self.height = img.height
self.gen_thumb_from_jpg()
def gen_thumb_from_jpg(self):
"""Generates a thumbnail image from the given input image."""
thumb_width = 500
thumb_size = (thumb_width, int(thumb_width // 1.5))
try:
with Image.open(self.file) as img:
img.thumbnail(thumb_size)
img.save(self.thumbnail, "JPEG")
except IOError:
print(f"Error: Cannot create thumbnail for '{self.file}'")
def _raw_preview(self):
self.is_raw = True
self.width = get_raw_image_dimensions(self.file)[1]
self.height = get_raw_image_dimensions(self.file)[0]
self.thumbnail = extract_jpg_thumb(self.file)
def _video_preview(self):
vid = Video(file=self.file)
self.thumbnail = vid.gen_video_thumbnail()
video_meta = vid.get_video_meta()
self.width = video_meta['video']['size']['width']
self.height = video_meta['video']['size']['height']
self.video_framerate = round(
int(video_meta['video']['r_frame_rate'].split('/')[0]) /
int(video_meta['video']['r_frame_rate'].split('/')[1]),2)
self.video_bit_depth = video_meta['video']['bits_per_raw_sample']
self.video_duration = video_meta['video']['duration']
self.video_encoding = video_meta['video']['encoding_brand']
self.video_codec = video_meta['video']['codec_long_name']
self.video_profile = video_meta['video']['profile']
self.video_pix_format = video_meta['video']['pix_fmt']
def thumb_ratio(self):
img = Image.open(self.thumbnail)
self.thumbnail_width = img.width
self.thumbnail_height = img.height
self.thumbnail_ratio = float(self.thumbnail_width / self.thumbnail_height)
print(self.thumbnail_width)
print(self.thumbnail_height)
print(self.thumbnail_ratio)

View File

@ -8,39 +8,52 @@ from os import system,path,rename
from tqdm import tqdm from tqdm import tqdm
from _file_stuff import create_folder, cmp_files, path_exists from _file_stuff import create_folder, cmp_files, path_exists
from _lumberjack import timber
from _hashing import xx_hash from _hashing import xx_hash
# from configure import Configure, CONFIG_FILE
def copy_with_progress(s,d): # 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 """ """ Copy a file with the progress bar """
log.debug(f'copy_with_progress({s},{d},{f})')
# size = path.getsize(s) size = path.getsize(s)
with open(s, 'rb') as fs: with open(s, 'rb') as fs:
with open(d, 'wb') as fd: with open(d, 'wb') as fd:
while True: # with tqdm(total=size, unit='B', unit_scale=True, desc=f'Copying {f}') as pbar:
chunk = fs.read(4096) while True:
if not chunk: chunk = fs.read(4096)
break if not chunk:
fd.write(chunk) break
fd.write(chunk)
# pbar.update(len(chunk))
def copy_from_source(source_path,dest_path,file_name): def copy_from_source(source_path,dest_path,file_name):
""" Copy file from source to destination """ """ 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)) file_exists = path_exists(path.join(dest_path,file_name))
if file_exists is True: if file_exists is True:
print(f'\nFound {file_name} at destination, checking if they match.') log.debug(f'\nFound {file_name} at destination, checking if they match.')
check_match = cmp_files( check_match = cmp_files(
path.join(source_path,file_name), path.join(source_path,file_name),
path.join(dest_path, file_name)) path.join(dest_path, file_name))
if check_match is False: if check_match is False:
print(f'\nFound duplicate for {source_path}/{file_name}, \ log.warn(f'\nFound duplicate for {source_path}/{file_name}, \
renaming destination with hash appended.') renaming destination with hash appended.')
base, extension = path.splitext(file_name) 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)) f_xxhash = xx_hash(path.join(dest_path, file_name))
#file_name_hash = base + '_' + md5 + extension
file_name_hash = base + '_' + f_xxhash + extension file_name_hash = base + '_' + f_xxhash + extension
rename(path.join(dest_path, file_name), rename(path.join(dest_path, file_name),
path.join(dest_path, file_name_hash)) path.join(dest_path, file_name_hash))
else: else:
print(f'\n{file_name} hashes match') log.info(f'\n{file_name} hashes match')
# remove(path.join(source_path,file_name)) # remove(path.join(source_path,file_name))
# f.pop(file_name) # f.pop(file_name)
return return
@ -48,10 +61,13 @@ def copy_from_source(source_path,dest_path,file_name):
# create_folder(dest_path) # create_folder(dest_path)
# shutil.copy(os.path.join(source_path,file_name), dest_path) # shutil.copy(os.path.join(source_path,file_name), dest_path)
copy_with_progress(path.join(source_path, file_name), copy_with_progress(path.join(source_path, file_name),
path.join(dest_path, file_name)) path.join(dest_path, file_name),
file_name)
system('clear')
def copy_files(f,config): def copy_files(f,config):
""" Copy Files. """ """ Copy Files. """
log.debug(f'copy_files({f})')
system('clear') system('clear')
for file in tqdm(f, desc="Copying Files:"): for file in tqdm(f, desc="Copying Files:"):
create_folder(f[file]['folders']['destination']) create_folder(f[file]['folders']['destination'])

48
_lumberjack.py Normal file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env python
"""
A class for logging... no, not timber
"""
import logging
import os
basedir = os.path.dirname(__file__)
# 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(os.path.join(basedir, "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

160
_media.py Normal file
View File

@ -0,0 +1,160 @@
#!/usr/bin/env python
"""
Create the media object
"""
import os
from os import path
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 = self.configuration['store_originals']
self.destination_originals_path = ''
self.destination_path = self.set_destination_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':
# print(f'store_originals: {self.store_originals}')
p = p + '/PHOTO'
if self.file_ext.lower() in ('jpg', 'jpeg'):
# print(f'store_originals: {self.store_originals}')
if self.store_originals is True:
self.destination_originals_path = path.join(
p,
self.configuration['folders']['originals'],
'JPG')
# print(self.destination_originals_path)
p = p + '/JPG'
else:
if self.store_originals is True:
self.destination_originals_path = path.join(
p,
self.configuration['folders']['originals'],
'RAW')
# print(self.destination_originals_path)
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(p, f_type):
""" get capture date from meta """
# log.debug(f'get_capture_date({p}, {f_type}')
if f_type == 'image':
stamp = get_image_date(p)
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(p))
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 {p}. Giving up.')
sys.exit()
elif f_type == 'audio':
try:
stamp = datetime.strptime(ffmpeg.probe(
p)['format']['tags']['date'], '%Y-%m-%d')
except KeyError as ke:
# log.warning(f'\nError: {ke} for {p}. Trying getctime...')
try:
stamp = datetime.fromtimestamp(os.path.getctime(p))
except:
# log.critical(f'\nCould not get timestamp for {p}. Giving up.')
sys.exit()
else:
try:
stamp = datetime.fromtimestamp(os.path.getctime(p))
except:
# log.critical(f'\nCould not get timestamp for {p}. Giving up.')
sys.exit()
year = stamp.strftime("%Y")
month = stamp.strftime("%m")
day = stamp.strftime("%d")
return year, month, day

View File

@ -1,160 +0,0 @@
import os.path
import sys
from _audio import AudioFile
from _hashing import hash_path
from _video import VideoFile
from _photo import PhotoFile
from _file_stuff import (get_file_name,
get_file_ext,
get_dotted_file_ext)
from datetime import datetime
class MediaFile:
def __init__(self,
path_file_name,
config,
event_name,
*args,**kwargs):
# super(MediaFile,self).__init__(*args,**kwargs)
self.path_file_name = path_file_name
self.config = config
self.event_name = str(event_name)
self.store_originals = self.config['store_originals']
self.src_dir = os.path.dirname(self.path_file_name)
self.base_dst_dir = self.config['folders']['destination']['base']
self.source_path_hash = hash_path(self.path_file_name)
self.file_name = get_file_name(self.path_file_name)
self.dotted_file_ext = get_dotted_file_ext(self.file_name)
self.file_ext = get_file_ext(self.file_name)
self.file_type = self.get_file_type()
self.capture_date = self.get_capture_date()
self.capture_date_year = self.capture_date[0]
self.capture_date_month = self.capture_date[1]
self.capture_date_day = self.capture_date[2]
self.dst_dir = self.set_destination_path()
self.destination_originals_path = ''
self.media = {}
self.media_meta = self.get_media_meta()
# video_media = VideoFile(path_file_name=self.path_file_name)
# audio_media = AudioFile(path_file_name=self.path_file_name)
# photo_media = PhotoFile(path_file_name=self.path_file_name)
def get_file_type(self):
""" determine if the extension is in the list of file types """
for t in self.config['file_types']:
for e in self.config['file_types'][t]:
if self.file_ext.lower().endswith(e.lower()):
return t
def set_destination_path(self):
"""
set the destination path for the file
base dir structure is:
base_dst_dir/YYYY/YYYY-MM/YYYY-MM-DD[-event_name]
"""
p1 = os.path.join(self.base_dst_dir,self.capture_date_year)
p2 = f'{self.capture_date_year}-{self.capture_date_month}'
p3 = f'{self.capture_date_year}-{self.capture_date_month}-{self.capture_date_day}'
p = f'{p1}/{p2}/{p3}' # <--- Dumb.
if self.event_name:
p4 = '-' + self.event_name
p = p + p4
if self.file_type == 'image':
p = os.path.join(p,'PHOTO')
if self.file_ext.lower() in ('jpg', 'jpeg'):
if self.store_originals is True:
self.destination_originals_path = os.path.join(
p,
self.config['folders']['originals'],
'JPG'
)
p = os.path.join(p, 'JPG')
else:
if self.store_originals is True:
self.destination_originals_path = os.path.join(
p,
self.config['folders']['originals'],
'RAW')
p = os.path.join(p,'RAW')
elif self.file_type == 'video':
p = os.path.join(p,'VIDEO')
elif self.file_type == 'audio':
p = os.path.join(p,'AUDIO')
return p
def get_capture_date(self):
""" get capture date from meta """
if self.file_type == 'image':
photo_media = PhotoFile(path_file_name=self.path_file_name)
stamp = photo_media.photo_capture_date
elif self.file_type == 'video':
video_media = VideoFile(path_file_name=self.path_file_name)
stamp = video_media.get_video_capture_date()
elif self.file_type == 'audio':
audio_media = AudioFile(path_file_name=self.path_file_name)
stamp = audio_media.get_audio_capture_date()
else:
try:
stamp = datetime.fromtimestamp(
os.path.getctime(
self.path_file_name
)
)
except:
print('end of the road for finding the date.')
sys.exit()
year = stamp.strftime("%Y")
month = stamp.strftime("%m")
day = stamp.strftime("%d")
return year, month, day
def get_media_meta(self):
self.media = {
'date': {
'capture_date': {
'y': self.capture_date_year,
'm': self.capture_date_month,
'd': self.capture_date_day
}
},
'event': {
'name': self.event_name
},
'extension': self.file_ext,
'folders': {
'destination': self.dst_dir,
'destination_original': self.destination_originals_path,
'source_path': self.src_dir
},
'name': self.file_name,
'source_path_hash': self.source_path_hash,
'file_type': self.file_type
}
if self.file_type == 'video':
video_media = VideoFile(path_file_name=self.path_file_name)
self.media['video_meta'] = video_media.video_meta()
self.media['image_meta'] = None
self.media['audio_meta'] = None
if self.file_type == 'image':
photo_media = PhotoFile(path_file_name=self.path_file_name)
self.media['image_meta'] = photo_media.get_photo_meta()
self.media['video_meta'] = None
self.media['audio_meta'] = None
if self.file_type == 'audio':
audio_media = AudioFile(path_file_name=self.path_file_name)
self.media['audio_meta'] = audio_media.get_audio_meta()
self.media['image_meta'] = None
self.media['video_meta'] = None
print(f'MediaFile, self.media: {self.media}')
return self.media

View File

@ -1,16 +0,0 @@
from os import path,rename
from BitMover_ui import MainWindow
from _file_stuff import create_folder, path_exists, cmp_files
from _hashing import xx_hash
class MediaImporter(MainWindow):
def __init__(self,*args,**kwargs):
super(MediaImporter,self).__init__(*args,**kwargs)
self.chunk_size = 16 * 1024
self.path_file_source = None
self.path_file_destination = None
self.destination_original_path = None
self.path_file_destination_original = None
self.path_file_destination = None

171
_photo.py
View File

@ -1,171 +0,0 @@
#!/usr/bin/env python
from PIL import Image
from _raw_photo import get_raw_image_dimensions
from _image_tag import ImageTag
# https://exiftool.org/TagNames/EXIF.html
# https://exiftool.org/TagNames/Sony.html
class PhotoFile:
def __init__(self,path_file_name,*args,**kwargs):
super(PhotoFile, self).__init__(*args, **kwargs)
self.path_file_name = path_file_name
self.args = args
self.kwargs = kwargs
self.img = None
self.image_tag = ImageTag(path_file_name=self.path_file_name)
self.is_jpg = self.is_photo_jpeg()
self.is_raw = self.is_photo_raw()
self.size_width = self.get_photo_width()
self.size_height = self.get_photo_height()
self.size = self.get_photo_size()
self.mpixels = self.get_photo_megapixels()
self.size_ratio = self.get_photo_size_ratio()
self.dpi = self.get_dpi()
self.iso = self.get_iso()
self.aperture = self.get_aperture()
self.camera_brand = self.get_camera_brand()
self.camera_model = self.get_camera_model()
self.lens_make = self.get_lens_make()
self.lens_model = self.get_lens_model()
self.focal_length = self.get_focal_length()
self.photo_meta = {}
self.photo_capture_date = self.get_photo_capture_date()
def get_photo_capture_date(self):
return self.image_tag.date_time_original
def is_photo_raw(self):
# TODO: Actually inspect the mime type instead of relying on an extension.
if self.path_file_name.lower().endswith('jpg') \
or self.path_file_name.lower().endswith('.jpeg'):
self.is_raw = False
self.is_jpg = True
self.img = Image.open(self.path_file_name)
else:
self.is_raw = True
self.is_jpg = False
return self.is_raw
def is_photo_jpeg(self):
# TODO: Stop making assumption. #Life-Lessons
self.is_photo_raw()
return self.is_jpg
def get_photo_width(self):
if self.is_jpg:
width = self.img.width
elif self.is_raw:
width = get_raw_image_dimensions(self.path_file_name)[1]
else:
width = None
return width
def get_photo_height(self):
if self.is_jpg:
height = self.img.height
elif self.is_raw:
height = get_raw_image_dimensions(self.path_file_name)[0]
else:
height = None
return height
def get_photo_size(self):
if self.size_width is None:
self.size_width = self.get_photo_width()
if self.size_height is None:
self.size_height = self.get_photo_height()
return f'{self.size_width}x{self.size_height}'
def get_photo_size_ratio(self):
if self.size_width is None:
self.size_width = self.get_photo_width()
if self.size_height is None:
self.size_height = self.get_photo_height()
return float(self.size_width / self.size_height)
def get_photo_megapixels(self):
if self.size_width is None:
self.size_width = self.get_photo_width()
if self.size_height is None:
self.size_height = self.get_photo_height()
return self.size_width * self.size_height
def get_dpi(self):
return self.image_tag.get_image_tag("xresolution")
def get_iso(self):
return self.image_tag.get_image_tag( 'iso')
def get_aperture(self):
return self.image_tag.get_image_tag('fnumber')
def get_camera_brand(self):
tag = self.image_tag.get_image_tag('Make')
return tag
def get_camera_model(self):
tag = self.image_tag.get_image_tag('cameramodelname')
if tag is None:
tag = self.image_tag.get_image_tag('image model')
return tag
def get_camera_firmware(self):
tag = self.image_tag.get_image_tag('CameraFirmware')
return tag
def get_camera_serial_number(self):
tag = self.image_tag.get_image_tag('CameraSerialNumber')
return tag
def get_lens_make(self):
return self.image_tag.get_image_tag('lensmake')
def get_lens_model(self):
return self.image_tag.get_image_tag('lensmodel')
def get_lens_serial(self):
pass
def get_focal_length(self):
return self.image_tag.get_image_tag('focallength')
def get_photographer(self):
pass
def get_orientation(self):
pass
def get_sony_raw_type(self):
pass
def get_exposure_program(self):
pass
def get_photo_meta(self):
photo_meta = {
'photo': {
'aperture': self.aperture,
'camera_brand': self.camera_brand,
'camera_model': self.camera_model,
'dpi': self.dpi,
'is_jpg': self.is_jpg,
'is_raw': self.is_raw,
'iso': self.iso,
'lens_make': self.lens_make,
'lens_model': self.lens_model,
'lens_focal_length': self.focal_length,
'size': {
'height': self.size_height,
'width': self.size_width,
'width_height': self.size,
'megapixels': self.mpixels,
'ratio': self.size_ratio
}
}
}
return photo_meta

View File

@ -1,68 +0,0 @@
#!/usr/bin/env python
from PIL import Image
from _media_file import MediaFile
from _raw_photo import extract_jpg_thumb
from _video import VideoFile
from _hashing import hash_path
class MediaPreview:
def __init__(self,path_file_name,media_files):
# super(MediaPreview, self).__init__()
self.path_file_name = path_file_name
self.media_files_list = media_files
# self.media_file = MediaFile(self.path_file_name)
self.source_path_hash = hash_path(self.path_file_name)
# print(f'_preview.py,MediaPreview:\n\tpath_file_name: {self.path_file_name}\n\thash: {self.source_path_hash}\n\tmedia_files_list: {self.media_files_list}\n')
self.thumbnail = 'thumbnail.jpg'
self.file_type = self.media_files_list[self.source_path_hash]['file_type']
if self.media_files_list[self.source_path_hash]['file_type'] == 'image':
self.thumbnail_ratio = self.media_files_list[self.source_path_hash]['image_meta']['photo']['size']['ratio']
self.mpixels = self.media_files_list[self.source_path_hash]['image_meta']['photo']['size']['megapixels']
self._img_preview()
elif self.media_files_list[self.source_path_hash]['file_type'] == 'video':
self._video_preview()
def _img_preview(self):
if self.media_files_list[self.source_path_hash]['image_meta']['photo']['is_jpg'] is True:
self._jpg_preview()
else:
self._raw_preview()
def _jpg_preview(self):
self.gen_thumb_from_jpg()
def gen_thumb_from_jpg(self):
"""Generates a thumbnail image from the given input image."""
thumb_width = 500
thumb_size = (
thumb_width,
int(thumb_width // self.media_files_list[self.source_path_hash]['image_meta']['photo']['size']['ratio'])
)
try:
with Image.open(self.path_file_name) as img:
img.thumbnail(thumb_size)
img.save(self.thumbnail, "JPEG")
except IOError:
print(f"Error: Cannot create thumbnail for '{self.path_file_name}'")
def _raw_preview(self):
self.thumbnail = extract_jpg_thumb(self.path_file_name)
def _video_preview(self):
vid = VideoFile(path_file_name=self.path_file_name)
self.thumbnail = vid.gen_video_thumbnail()
self.width = self.media_files_list[self.source_path_hash]['video_meta']['video']['size']['width']
self.height = self.media_files_list[self.source_path_hash]['video_meta']['video']['size']['height']
self.video_framerate = round(
int(self.media_files_list[self.source_path_hash]['video_meta']['video']['r_frame_rate'].split('/')[0]) /
int(self.media_files_list[self.source_path_hash]['video_meta']['video']['r_frame_rate'].split('/')[1]),2)
self.video_bit_depth = self.media_files_list[self.source_path_hash]['video_meta']['video']['bits_per_raw_sample']
self.video_duration = self.media_files_list[self.source_path_hash]['video_meta']['video']['duration']
self.video_encoding = self.media_files_list[self.source_path_hash]['video_meta']['video']['encoding_brand']
self.video_codec = self.media_files_list[self.source_path_hash]['video_meta']['video']['codec_long_name']
self.video_profile = self.media_files_list[self.source_path_hash]['video_meta']['video']['profile']
self.video_pix_format = self.media_files_list[self.source_path_hash]['video_meta']['video']['pix_fmt']

View File

@ -27,10 +27,9 @@ class WorkerSignals(QObject):
error = pyqtSignal(tuple) error = pyqtSignal(tuple)
result = pyqtSignal(object) result = pyqtSignal(object)
progress = pyqtSignal(int) progress = pyqtSignal(int)
import_progress = pyqtSignal(int)
current_file_progress = pyqtSignal(float) current_file_progress = pyqtSignal(float)
imported_file_count = pyqtSignal(int) # current_import_file = pyqtSignal()
found_file = pyqtSignal(str)
total_file_count = pyqtSignal(int)
class Worker(QRunnable): class Worker(QRunnable):
""" """
@ -56,10 +55,9 @@ class Worker(QRunnable):
# Add the callback to our kwargs # Add the callback to our kwargs
self.kwargs['progress_callback'] = self.signals.progress self.kwargs['progress_callback'] = self.signals.progress
self.kwargs['import_progress_callback'] = self.signals.import_progress
self.kwargs['current_file_progress_callback'] = self.signals.current_file_progress self.kwargs['current_file_progress_callback'] = self.signals.current_file_progress
self.kwargs['imported_file_count_callback'] = self.signals.imported_file_count # self.kwargs['current_import_file'] = self.signals.current_import_file
self.kwargs['found_file_callback'] = self.signals.found_file
self.kwargs['total_file_count_callback'] = self.signals.total_file_count
@pyqtSlot() @pyqtSlot()
def run(self): def run(self):

View File

@ -1,39 +0,0 @@
import time
from datetime import datetime
def convert_from_seconds(seconds):
return time.strftime("%H:%M:%S", time.gmtime(seconds))
def set_generic_date_time(datestamp='1900:01:01',
timestamp='00:00:00',
f='%Y:%m:%d %H:%M:%S'):
t = datetime.strptime(str(f'{datestamp} {timestamp}'), f)
return t
def process_img_date_tag(tag, image_tags, date_time_format):
try:
t = datetime.strptime(str(image_tags[tag]), date_time_format)
except:
t = None
return t
def get_img_date(image_tags):
t = None
for tag in image_tags:
if 'DateTime' in tag:
t = tag
break
for f in ['%Y:%m:%d %H:%M:%S',
'%Y/%m/%d %H:%M:%S',
'%Y-%m-%d-%H-%M-%S']:
dt_tag = process_img_date_tag(t,image_tags,f)
if dt_tag is not None:
break
if dt_tag is None:
dt_tag = set_generic_date_time()
return dt_tag

View File

@ -1,48 +0,0 @@
import os
import xxhash
class FileHash:
def __init__(self,files,*args,**kwargs):
super(FileHash,self).__init__(*args,**kwargs)
self.files = files
self.chunk_size = kwargs['chunk_size']
@staticmethod
def xx_hash(self,f,progress_callback):
size = os.path.getsize(f)
hasher = xxhash.xxh64()
#todo: add callbacks
chunk_count = 0
with open(f, 'rb') as f:
for chunk in iter(lambda: f.read(self.chunk_size),b""):
hasher.update(chunk)
chunk_count += 1
hashed_size = chunk_count * self.chunk_size
progress_callback.emit(round((hashed_size / size) * 100, 1))
file_hash = hasher.hexdigest()
return file_hash
@staticmethod
def t_verify_checksum(self,
progress_callback,
import_progress_callback,
current_file_progress_callback,
imported_file_count_callback,
found_file,
total_file_count):
for file in self.files:
i = 0
c = {}
for checksum in self.files[file]['xx_checksum']:
c[i] = self.files[file]['xx_checksum'][checksum]
if i > 0:
p = i - 1
if c[i] == c[p]:
self.files[file]['checksum_match'] = True
else:
self.files[file]['checksum_match'] = False
i += 1
return self.files

190
_video.py
View File

@ -1,137 +1,127 @@
#!/usr/bin/env python #!/usr/bin/env python
import os.path
import sys import sys
import ffmpeg import ffmpeg
import time import time
from datetime import datetime
class VideoFile: class Video:
def __init__(self,path_file_name,max_width=1024,*args,**kwargs): def __init__(self,max_width=1024,*args,**kwargs):
# super(VideoFile, self).__init__(*args, **kwargs) super(Video,self).__init__()
self.path_file_name = path_file_name self.args = args
self.max_width = max_width self.kwargs = kwargs
self.out = 'thumbnail.jpg' self.file = kwargs['file']
self.probe = ffmpeg.probe(self.path_file_name) self.out = 'thumbnail.jpg'
self.video_capture_date = self.get_video_capture_date() self.max_width = max_width
self.video_stream = None
self.audio_stream = None
for i in self.probe['streams']: self.stream = {
if self.video_stream is None: 'video': {},
if 'video' == i['codec_type'].lower(): 'format': {},
self.video_stream = i 'audio': {}
if self.audio_stream is None: }
if 'audio' == i['codec_type'].lower():
self.audio_stream = i
self.format_stream = self.probe['format'] self.stream['video']['duration'] = ''
self.size_width = self.get_video_width() self.stream['video']['creation_time'] = ''
self.size_height = self.get_video_height() self.stream['video']['encoding_brand'] = ''
self.size = self.get_video_size() self.stream['video']['codec_name'] = ''
self.stream['video']['codec_long_name'] = ''
self.stream['video']['profile'] = ''
self.stream['video']['size'] = {}
self.stream['video']['size']['width'] = int()
self.stream['video']['size']['height'] = int()
self.stream['video']['display_aspect_ratio'] = str()
self.stream['video']['level'] = ''
self.stream['video']['color_range'] = ''
self.stream['video']['field_order'] = ''
self.stream['video']['is_avc'] = ''
self.stream['video']['r_frame_rate'] = '' # -> expect for 120fps: 120000/1001
self.stream['video']['avg_frame_rate'] = '' # -> expect for 120fps: 120000/1001
self.stream['video']['time_base'] = '' # -> expect for 120fps: 1/120000
self.stream['video']['bit_rate'] = ''
self.stream['video']['bits_per_raw_sample'] = ''
self.stream['video']['nb_frames'] = ''
self.stream['video']['language'] = ''
self.stream['video']['handler_name'] = ''
self.stream['video']['encoder'] = ''
self.stream['video']['pix_fmt'] = ''
self.stream = {} self.stream['format'] = {}
self.stream['format']['major_brand'] = ''
self.stream['format']['compatible_brands'] = ''
self.stream['format']['creation_time'] = ''
self.stream['audio'] = {}
self.stream['audio']['codec_long_name'] = ''
self.stream['audio']['channels'] = ''
self.stream['audio']['sample_fmt'] = ''
self.stream['audio']['sample_rate'] = ''
self.stream['audio']['bits_per_sample'] = ''
self.stream['audio']['bit_rate'] = ''
@staticmethod @staticmethod
def convert_from_seconds(seconds): def convert_from_seconds(s):
seconds = s
return time.strftime("%H:%M:%S", time.gmtime(seconds)) return time.strftime("%H:%M:%S", time.gmtime(seconds))
def gen_video_thumbnail(self): def gen_video_thumbnail(self):
""" """
Generate a thumbnail from a video Generate a thumbnail from a video
""" """
self.video_meta() self.get_video_meta()
time_seconds = self.stream['video']['seconds'] // 5 time = self.stream['video']['seconds'] // 5
v_width = int(self.stream['video']['size']['width']) v_width = int(self.stream['video']['size']['width'])
width = self.set_thumb_width(v_width) width = self.set_thumb_width(v_width)
try: try:
( (
ffmpeg.input( ffmpeg.input(self.file, ss=time)
self.path_file_name, .filter('scale', width, -1)
ss = time_seconds .output(self.out, vframes=1)
)
.filter(
'scale',
width,
-1
)
.output(
self.out,
vframes = 1
)
.overwrite_output() .overwrite_output()
.run( .run(capture_stdout=True, capture_stderr=True)
capture_stdout = True,
capture_stderr = True
)
) )
except ffmpeg.Error as e: except ffmpeg.Error as e:
print(e.stderr.decode(), print(e.stderr.decode(), file=sys.stderr)
file = sys.stderr)
return self.out return self.out
def set_thumb_width(self, v_width): def set_thumb_width(self, v_width):
if v_width > self.max_width: if v_width > self.max_width:
width = self.max_width width = self.max_width
else: else:
width = v_width width = v_width
return width return width
def get_video_width(self): def get_video_meta(self):
return self.video_stream['width'] probe = ffmpeg.probe(self.file)
def get_video_height(self): if 'video' == probe['streams'][0]['codec_type'].lower():
return self.video_stream['height'] video_stream = probe['streams'][0]
elif 'video' == probe['streams'][1]['codec_type'].lower():
video_stream = probe['streams'][1]
elif 'video' == probe['streams'][2]['codec_type'].lower():
video_stream = probe['streams'][2]
def get_video_size(self): if 'audio' == probe['streams'][0]['codec_type'].lower():
if self.size_width is None: audio_stream = probe['streams'][0]
self.size_width = self.get_video_width() elif 'audio' == probe['streams'][1]['codec_type'].lower():
if self.size_height is None: audio_stream = probe['streams'][1]
self.size_height = self.get_video_height() elif 'audio' == probe['streams'][2]['codec_type'].lower():
return f'{self.size_width}x{self.size_height}' audio_stream = probe['streams'][2]
def get_video_capture_date(self): format_stream = probe['format']
#TODO: refactor this try/except logic.
try:
stamp = datetime.strptime(
self.format_stream['tags']['creation_time'],
'%Y-%m-%dT%H:%M:%S.%f%z'
)
except:
try:
stamp = datetime.fromtimestamp(
os.path.getctime(
self.path_file_name
)
)
except:
stamp = datetime.strptime(
str('1900:01:01 00:00:00'),
'%Y:%m:%d %H:%M:%S'
)
return stamp
def video_meta(self): self.stream['video']['size']['width'] = video_stream['width']
self.stream = { self.stream['video']['size']['height'] = video_stream['height']
'video': { self.stream['video']['r_frame_rate'] = video_stream['r_frame_rate']
'bits_per_raw_sample': self.video_stream['bits_per_raw_sample'], self.stream['video']['bits_per_raw_sample'] = video_stream['bits_per_raw_sample']
'codec_long_name': self.video_stream['codec_long_name'], self.stream['video']['seconds'] = float(video_stream['duration'])
'seconds': float(self.video_stream['duration']), self.stream['video']['duration'] = self.convert_from_seconds(
'duration': self.convert_from_seconds(float(self.video_stream['duration'])), self.stream['video']['seconds']
'encoding_brand': self.format_stream['tags']['major_brand'], )
'pix_fmt': self.video_stream['pix_fmt'], self.stream['video']['encoding_brand'] = format_stream['tags']['major_brand']
'profile': self.video_stream['profile'], self.stream['video']['codec_long_name'] = video_stream['codec_long_name']
'r_frame_rate': self.video_stream['r_frame_rate'], self.stream['video']['profile'] = video_stream['profile']
'size': { self.stream['video']['pix_fmt'] = video_stream['pix_fmt']
'width_height': self.size,
'height': self.video_stream['height'],
'width': self.video_stream['width']
}
},
'audio': {},
'format': {}
}
return self.stream return self.stream

View File

@ -1,5 +1,2 @@
pyuic6 BitMover.ui -o _BitMover_MainWindow.py pyuic6 BitMover.ui -o _BitMover_MainWindow.py
pyuic6 import_dialogue.ui -o _import_dialog_Window.py pyuic6 import_dialogue.ui -o _import_dialog_Window.py
pyuic6 _finding_files_dialog.ui -o _finding_files_dialog_Window.py
pyuic6 _ComparisonDialog.ui -o _comparison_dialog_Window.py
pyuic6 _checksum_progress_dialog.ui -o _checksum_progress_dialog_Window.py

View File

@ -1 +0,0 @@
qt6-tools designer