foo
This commit is contained in:
parent
96111a4f65
commit
c5959a945f
209
BitMover.ui
209
BitMover.ui
|
@ -11,7 +11,13 @@
|
|||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
<string>BitMover</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset theme="applications-science"/>
|
||||
</property>
|
||||
<property name="unifiedTitleAndToolBarOnMac">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<widget class="QWidget" name="gridLayoutWidget">
|
||||
|
@ -88,7 +94,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>910</x>
|
||||
<y>650</y>
|
||||
<y>610</y>
|
||||
<width>311</width>
|
||||
<height>211</height>
|
||||
</rect>
|
||||
|
@ -292,7 +298,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>910</x>
|
||||
<y>630</y>
|
||||
<y>590</y>
|
||||
<width>371</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
|
@ -334,11 +340,55 @@
|
|||
<rect>
|
||||
<x>910</x>
|
||||
<y>50</y>
|
||||
<width>182</width>
|
||||
<height>91</height>
|
||||
<width>541</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0" columnstretch="0,1">
|
||||
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0" columnstretch="0,0,0,0,0">
|
||||
<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">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="font">
|
||||
|
@ -351,120 +401,25 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLCDNumber" name="lcd_files_found"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLCDNumber" name="lcd_files_imported"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>18</pointsize>
|
||||
</font>
|
||||
<item row="0" column="4">
|
||||
<widget class="QLCDNumber" name="lcd_files_imported">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Files Imported</string>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<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 name="maximumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -474,7 +429,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>910</x>
|
||||
<y>150</y>
|
||||
<y>110</y>
|
||||
<width>541</width>
|
||||
<height>371</height>
|
||||
</rect>
|
||||
|
@ -493,7 +448,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>910</x>
|
||||
<y>530</y>
|
||||
<y>490</y>
|
||||
<width>541</width>
|
||||
<height>91</height>
|
||||
</rect>
|
||||
|
@ -643,7 +598,8 @@
|
|||
<string>Import Media</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="drive-harddisk"/>
|
||||
<iconset theme="drive-harddisk">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -685,7 +641,8 @@
|
|||
<string>Images</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="camera-photo"/>
|
||||
<iconset theme="camera-photo">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
|
@ -698,7 +655,8 @@
|
|||
<string>Video</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="camera-video"/>
|
||||
<iconset theme="camera-video">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
|
@ -711,7 +669,8 @@
|
|||
<string>Audio</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="multimedia-player"/>
|
||||
<iconset theme="multimedia-player">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
|
@ -755,7 +714,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1463</width>
|
||||
<height>24</height>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuBit_Mover">
|
||||
|
|
486
BitMover_ui.py
486
BitMover_ui.py
|
@ -2,23 +2,19 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from os import path, rename
|
||||
|
||||
from PyQt6.QtCore import QThreadPool
|
||||
from PyQt6.QtGui import QIcon, QPixmap
|
||||
from PyQt6.QtWidgets import QMainWindow, QApplication, QFileDialog
|
||||
|
||||
from _BitMover_MainWindow import Ui_MainWindow
|
||||
from _import_dialog import DialogImport
|
||||
from _configure import CONFIG_FILE, Configure
|
||||
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 _find_files import FindFiles
|
||||
from _find_files_dialog import FindProgress
|
||||
from _import_dialog import DialogImport
|
||||
from _media_import import MediaImporter
|
||||
from _preview import MediaPreview
|
||||
from _thread_my_stuff import Worker
|
||||
|
||||
log = timber(__name__)
|
||||
basedir = os.path.dirname(__file__)
|
||||
|
||||
# TODO: verify source dir actually exists
|
||||
|
@ -41,9 +37,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self.file_types = self.config['file_types']
|
||||
# File Stuff
|
||||
self.total_files = 0
|
||||
self.file_total = 0
|
||||
self.files = {}
|
||||
self.imp_dialog = DialogImport()
|
||||
self.find_files_dialog = FindProgress()
|
||||
|
||||
self.widgets_config()
|
||||
|
||||
|
@ -60,13 +56,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self.toggle_scan_button(True)
|
||||
self.toggle_import_button(False)
|
||||
self.lcd_files_found.display(int(0))
|
||||
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.set_default_thumbnail()
|
||||
self.file_list.currentItemChanged.connect(self.index_changed)
|
||||
self.checkBox_verify_checksum.setChecked(self.verify_checksum)
|
||||
self.checkBox_cleanup_files.setChecked(self.cleanup_files)
|
||||
|
@ -79,6 +69,35 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
# Setup thread pool
|
||||
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 verify_checksum_changed(self):
|
||||
if self.checkBox_verify_checksum.isChecked():
|
||||
self.config['verify_checksum'] = True
|
||||
|
@ -100,30 +119,38 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self.config['store_originals'] = False
|
||||
print(f"store_originals: {self.config['store_originals']}")
|
||||
|
||||
def toggle_scan_button(self,enable=True):
|
||||
self.pushButton_3_scan_dir.setEnabled(enable)
|
||||
def set_thumbnail(self,thumb_file,scaled=True,ratio=None):
|
||||
self.img_preview.setPixmap(QPixmap(thumb_file))
|
||||
self.img_preview.setScaledContents(scaled)
|
||||
if ratio is not None:
|
||||
self.img_preview.setFixedHeight(self.img_preview.width() / ratio)
|
||||
|
||||
def toggle_import_button(self,enable=True):
|
||||
self.pushButton_import.setEnabled(enable)
|
||||
def set_default_thumbnail(self):
|
||||
self.set_thumbnail(os.path.join(basedir,
|
||||
'assets',
|
||||
'preview_placeholder.jpg'))
|
||||
|
||||
def update_preview(self,i):
|
||||
preview = ImgPreview(file=i.text(), event=self.get_event(), config=self.config)
|
||||
def get_preview(self,i):
|
||||
preview = MediaPreview(path_file_name = i.text(),
|
||||
media_files = self.files)
|
||||
return preview
|
||||
|
||||
self.l_meta_content_date_time_c.setText(preview.dtc)
|
||||
path_hash = preview.path_hash
|
||||
def update_preview(self,preview):
|
||||
self.set_thumbnail(preview.thumbnail,ratio=preview.thumbnail_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']
|
||||
f_video = f['video']
|
||||
self.l_data_file_source_path.setText(
|
||||
self.files[path_hash]['folders']['source_path'])
|
||||
self.l_data_file_dest_path.setText(
|
||||
self.files[path_hash]['folders']['destination'])
|
||||
|
||||
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()
|
||||
self.l_meta_content_date_time_c.setText(
|
||||
f"{f_date['y']}/{f_date['m']}/{f_date['d']}"
|
||||
)
|
||||
if preview.file_type == 'image':
|
||||
self.l_meta_01.setText('Size')
|
||||
self.l_meta_02.setText('dpi')
|
||||
|
@ -133,14 +160,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self.l_meta_06.setText('Camera')
|
||||
self.l_meta_07.setText('Aperture')
|
||||
self.l_meta_08.setText('Megapixels')
|
||||
self.l_meta_content_01.setText(str(preview.size))
|
||||
self.l_meta_content_02.setText(str(preview.dpi))
|
||||
self.l_meta_content_03.setText(str(preview.iso))
|
||||
self.l_meta_content_04.setText(str(preview.lens))
|
||||
self.l_meta_content_05.setText(str(preview.zoom))
|
||||
self.l_meta_content_06.setText(str(preview.camera))
|
||||
self.l_meta_content_07.setText(str(preview.aperture))
|
||||
self.l_meta_content_08.setText(str(preview.mpixels))
|
||||
self.l_meta_content_01.setText(str(f['photo']['size']['width_height']))
|
||||
self.l_meta_content_02.setText(str(f['photo']['dpi']))
|
||||
self.l_meta_content_03.setText(str(f['photo']['iso']))
|
||||
self.l_meta_content_04.setText(str(f['photo']['lens_model']))
|
||||
self.l_meta_content_05.setText(str(f['photo']['focal_length']))
|
||||
self.l_meta_content_06.setText(str(f"{f['photo']['camera_brand']} {f['photo']['camera_model']}"))
|
||||
self.l_meta_content_07.setText(str(f['photo']['aperture']))
|
||||
self.l_meta_content_08.setText(str(f['photo']['megapixels']))
|
||||
|
||||
elif preview.file_type == 'video':
|
||||
self.l_meta_01.setText('Size')
|
||||
|
@ -152,14 +179,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self.l_meta_07.setText('Profile')
|
||||
self.l_meta_08.setText('Pix Format')
|
||||
|
||||
self.l_meta_content_01.setText(str(preview.size))
|
||||
self.l_meta_content_02.setText(str(preview.video_framerate))
|
||||
self.l_meta_content_03.setText(str(preview.video_bit_depth))
|
||||
self.l_meta_content_04.setText(str(preview.video_duration))
|
||||
self.l_meta_content_05.setText(str(preview.video_encoding))
|
||||
self.l_meta_content_06.setText(str(preview.video_codec))
|
||||
self.l_meta_content_07.setText(str(preview.video_profile))
|
||||
self.l_meta_content_08.setText(str(preview.video_pix_format))
|
||||
self.l_meta_content_01.setText(str(f_video['size']['width_height']))
|
||||
self.l_meta_content_02.setText(str(f_video['r_framerate']))
|
||||
self.l_meta_content_03.setText(str(f_video['bit_depth']))
|
||||
self.l_meta_content_04.setText(str(f_video['duration']))
|
||||
self.l_meta_content_05.setText(str(f_video['encoding']))
|
||||
self.l_meta_content_06.setText(str(f_video['codec']))
|
||||
self.l_meta_content_07.setText(str(f_video['profile']))
|
||||
self.l_meta_content_08.setText(str(f_video['pix_format']))
|
||||
|
||||
def clear_metadata(self):
|
||||
self.l_meta_01.setText('')
|
||||
|
@ -182,241 +209,23 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
def index_changed(self,i):
|
||||
self.clear_metadata()
|
||||
if i is None:
|
||||
self.img_preview.setPixmap(QPixmap(os.path.join(basedir,
|
||||
'assets',
|
||||
'preview_placeholder.jpg')))
|
||||
self.set_default_thumbnail()
|
||||
else:
|
||||
self.update_preview(i)
|
||||
preview = self.get_preview(i)
|
||||
self.update_preview(preview)
|
||||
self.update_metadata(preview)
|
||||
|
||||
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_event(self):
|
||||
event_name = self.eventName.text()
|
||||
return event_name
|
||||
|
||||
def set_total_files(self,t):
|
||||
self.total_files = t
|
||||
self.lcd_files_found.display(self.total_files)
|
||||
total_file_count = t
|
||||
self.lcd_files_found.display(total_file_count)
|
||||
|
||||
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 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):
|
||||
self.file_total += int(1)
|
||||
else:
|
||||
print(f"Skipping {current_file} as it does not look like a real file.")
|
||||
|
||||
def t_find_files(self,
|
||||
progress_callback,
|
||||
import_progress_callback,
|
||||
current_file_progress_callback,):
|
||||
file_count = int(0)
|
||||
search_types = []
|
||||
|
||||
if self.checkBox_search_for_images.isChecked():
|
||||
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
|
||||
def print_output(s):
|
||||
print(s)
|
||||
|
@ -438,29 +247,39 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
def thread_complete():
|
||||
print("THREAD COMPLETE.")
|
||||
|
||||
def add_found_file_to_list(self,f):
|
||||
self.file_list.addItem(f)
|
||||
|
||||
def gen_file_dict(self,d):
|
||||
self.files = d
|
||||
|
||||
def find_files(self):
|
||||
""" find files to build a dictionary out of """
|
||||
|
||||
# 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 Stuff
|
||||
self.total_files = 0
|
||||
self.file_total = 0
|
||||
self.files = {}
|
||||
|
||||
worker = Worker(self.t_find_files)
|
||||
worker.signals.started.connect(self.worker_thread_started)
|
||||
worker.signals.result.connect(self.print_output)
|
||||
worker.signals.finished.connect(self.thread_complete)
|
||||
worker.signals.finished.connect(self.worker_thread_done)
|
||||
worker.signals.progress.connect(self.set_progress_processing)
|
||||
self.set_total_files(0)
|
||||
|
||||
file_finder = FindFiles()
|
||||
worker = Worker(file_finder.t_find_files)
|
||||
|
||||
worker.signals.started.connect(
|
||||
self.worker_thread_started)
|
||||
worker.signals.started.connect(
|
||||
self.find_files_dialog.open_find_files_dialog)
|
||||
worker.signals.progress.connect(
|
||||
self.find_files_dialog.set_progress_finding_files)
|
||||
worker.signals.found_file.connect(
|
||||
self.add_found_file_to_list)
|
||||
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.
|
||||
self.threadpool.start(worker)
|
||||
|
@ -470,69 +289,42 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
Import found files
|
||||
"""
|
||||
|
||||
# Open Dialog
|
||||
# imp_dialog = DialogImport()
|
||||
# imp_dialog.open_import_dialog()
|
||||
|
||||
# Initialize Widgets
|
||||
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_current_file(0)
|
||||
|
||||
worker = Worker(self.t_copy_files)
|
||||
worker.signals.started.connect(self.worker_thread_started)
|
||||
worker.signals.started.connect(self.imp_dialog.open_import_dialog)
|
||||
worker.signals.result.connect(self.print_output)
|
||||
worker.signals.finished.connect(self.thread_complete)
|
||||
worker.signals.finished.connect(self.worker_thread_done)
|
||||
worker.signals.import_progress.connect(self.set_progress_importing)
|
||||
worker.signals.import_progress.connect(self.imp_dialog.set_progress_importing)
|
||||
worker.signals.current_file_progress.connect(self.set_progress_current_file)
|
||||
worker.signals.current_file_progress.connect(self.imp_dialog.set_progress_current_file)
|
||||
importer = MediaImporter()
|
||||
|
||||
worker = Worker(importer.t_copy_files)
|
||||
|
||||
worker.signals.started.connect(
|
||||
self.worker_thread_started)
|
||||
worker.signals.started.connect(
|
||||
self.imp_dialog.open_import_dialog)
|
||||
|
||||
worker.signals.import_progress.connect(
|
||||
self.imp_dialog.set_progress_importing)
|
||||
worker.signals.current_file_progress.connect(
|
||||
self.imp_dialog.set_progress_current_file)
|
||||
worker.signals.imported_file_count.connect(
|
||||
self.set_imported_files)
|
||||
|
||||
worker.signals.result.connect(
|
||||
self.print_output)
|
||||
worker.signals.finished.connect(
|
||||
self.thread_complete)
|
||||
worker.signals.finished.connect(
|
||||
self.worker_thread_done)
|
||||
|
||||
# Execute
|
||||
self.threadpool.start(worker)
|
||||
|
||||
def get_event(self):
|
||||
event_name = self.eventName.text()
|
||||
return event_name
|
||||
def verify_checksum(self):
|
||||
# fh_match = FileHash()
|
||||
print(self.config)
|
||||
|
||||
def process_file(self,p):
|
||||
""" gather information and add to dictionary """
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@ class Ui_MainWindow(object):
|
|||
def setupUi(self, MainWindow):
|
||||
MainWindow.setObjectName("MainWindow")
|
||||
MainWindow.resize(1463, 928)
|
||||
icon = QtGui.QIcon.fromTheme("applications-science")
|
||||
MainWindow.setWindowIcon(icon)
|
||||
MainWindow.setUnifiedTitleAndToolBarOnMac(True)
|
||||
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.gridLayoutWidget = QtWidgets.QWidget(parent=self.centralwidget)
|
||||
|
@ -47,7 +50,7 @@ class Ui_MainWindow(object):
|
|||
self.file_list.setGeometry(QtCore.QRect(20, 160, 871, 701))
|
||||
self.file_list.setObjectName("file_list")
|
||||
self.gridLayoutWidget_2 = QtWidgets.QWidget(parent=self.centralwidget)
|
||||
self.gridLayoutWidget_2.setGeometry(QtCore.QRect(910, 650, 311, 211))
|
||||
self.gridLayoutWidget_2.setGeometry(QtCore.QRect(910, 610, 311, 211))
|
||||
self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2")
|
||||
self.grid_metadata = QtWidgets.QGridLayout(self.gridLayoutWidget_2)
|
||||
self.grid_metadata.setContentsMargins(0, 0, 0, 0)
|
||||
|
@ -154,7 +157,7 @@ class Ui_MainWindow(object):
|
|||
self.grid_metadata.addWidget(self.l_meta_content_02, 2, 1, 1, 1)
|
||||
self.grid_metadata.setColumnStretch(1, 1)
|
||||
self.l_exif_ffprobe_title = QtWidgets.QLabel(parent=self.centralwidget)
|
||||
self.l_exif_ffprobe_title.setGeometry(QtCore.QRect(910, 630, 371, 16))
|
||||
self.l_exif_ffprobe_title.setGeometry(QtCore.QRect(910, 590, 371, 16))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(18)
|
||||
font.setBold(True)
|
||||
|
@ -173,87 +176,48 @@ class Ui_MainWindow(object):
|
|||
self.eventName.setObjectName("eventName")
|
||||
self.gridLayout.addWidget(self.eventName, 0, 1, 1, 1)
|
||||
self.gridLayoutWidget_4 = QtWidgets.QWidget(parent=self.centralwidget)
|
||||
self.gridLayoutWidget_4.setGeometry(QtCore.QRect(910, 50, 182, 91))
|
||||
self.gridLayoutWidget_4.setGeometry(QtCore.QRect(910, 50, 541, 41))
|
||||
self.gridLayoutWidget_4.setObjectName("gridLayoutWidget_4")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.gridLayoutWidget_4)
|
||||
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
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)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(18)
|
||||
self.label_3.setFont(font)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.gridLayout_2.addWidget(self.label_3, 0, 0, 1, 1)
|
||||
self.lcd_files_found = QtWidgets.QLCDNumber(parent=self.gridLayoutWidget_4)
|
||||
self.lcd_files_found.setObjectName("lcd_files_found")
|
||||
self.gridLayout_2.addWidget(self.lcd_files_found, 0, 1, 1, 1)
|
||||
self.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.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.gridLayout_2.addWidget(self.lcd_files_imported, 0, 4, 1, 1)
|
||||
self.img_preview = QtWidgets.QLabel(parent=self.centralwidget)
|
||||
self.img_preview.setGeometry(QtCore.QRect(910, 150, 541, 371))
|
||||
self.img_preview.setGeometry(QtCore.QRect(910, 110, 541, 371))
|
||||
self.img_preview.setAutoFillBackground(True)
|
||||
self.img_preview.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel)
|
||||
self.img_preview.setText("")
|
||||
self.img_preview.setObjectName("img_preview")
|
||||
self.gridLayoutWidget_6 = QtWidgets.QWidget(parent=self.centralwidget)
|
||||
self.gridLayoutWidget_6.setGeometry(QtCore.QRect(910, 530, 541, 91))
|
||||
self.gridLayoutWidget_6.setGeometry(QtCore.QRect(910, 490, 541, 91))
|
||||
self.gridLayoutWidget_6.setObjectName("gridLayoutWidget_6")
|
||||
self.grid_metadata_2 = QtWidgets.QGridLayout(self.gridLayoutWidget_6)
|
||||
self.grid_metadata_2.setContentsMargins(0, 0, 0, 0)
|
||||
|
@ -298,8 +262,8 @@ class Ui_MainWindow(object):
|
|||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2)
|
||||
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.horizontalLayout_2.addItem(spacerItem)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.horizontalLayout_2.addItem(spacerItem1)
|
||||
self.checkBox_verify_checksum = QtWidgets.QCheckBox(parent=self.horizontalLayoutWidget_2)
|
||||
self.checkBox_verify_checksum.setChecked(True)
|
||||
self.checkBox_verify_checksum.setObjectName("checkBox_verify_checksum")
|
||||
|
@ -327,8 +291,8 @@ class Ui_MainWindow(object):
|
|||
self.label = QtWidgets.QLabel(parent=self.horizontalLayoutWidget_3)
|
||||
self.label.setObjectName("label")
|
||||
self.horizontalLayout_3.addWidget(self.label)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.horizontalLayout_3.addItem(spacerItem1)
|
||||
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
self.horizontalLayout_3.addItem(spacerItem2)
|
||||
self.checkBox_search_for_images = QtWidgets.QCheckBox(parent=self.horizontalLayoutWidget_3)
|
||||
icon = QtGui.QIcon.fromTheme("camera-photo")
|
||||
self.checkBox_search_for_images.setIcon(icon)
|
||||
|
@ -360,7 +324,7 @@ class Ui_MainWindow(object):
|
|||
self.horizontalLayout_3.addWidget(self.pushButton_3_scan_dir)
|
||||
MainWindow.setCentralWidget(self.centralwidget)
|
||||
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
|
||||
self.menubar.setGeometry(QtCore.QRect(0, 0, 1463, 24))
|
||||
self.menubar.setGeometry(QtCore.QRect(0, 0, 1463, 21))
|
||||
self.menubar.setObjectName("menubar")
|
||||
self.menuBit_Mover = QtWidgets.QMenu(parent=self.menubar)
|
||||
self.menuBit_Mover.setObjectName("menuBit_Mover")
|
||||
|
@ -377,7 +341,7 @@ class Ui_MainWindow(object):
|
|||
|
||||
def retranslateUi(self, MainWindow):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
|
||||
MainWindow.setWindowTitle(_translate("MainWindow", "BitMover"))
|
||||
self.pushButton_src_browse.setText(_translate("MainWindow", "Browse"))
|
||||
self.pushButton_dst_browse.setText(_translate("MainWindow", "Browse"))
|
||||
self.label_1_src_dir.setText(_translate("MainWindow", "Source Directory"))
|
||||
|
@ -393,14 +357,8 @@ class Ui_MainWindow(object):
|
|||
self.l_meta_08.setText(_translate("MainWindow", "Focal Length"))
|
||||
self.l_exif_ffprobe_title.setText(_translate("MainWindow", "Exif / ffprobe Data"))
|
||||
self.labelEvent.setText(_translate("MainWindow", "Event"))
|
||||
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.label_3.setText(_translate("MainWindow", "Files Found"))
|
||||
self.l_file_source_path.setText(_translate("MainWindow", "Source 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."))
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
<?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>
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env python
|
||||
import os.path
|
||||
import ffmpeg
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from _media_file import MediaFile
|
||||
|
||||
class AudioFile(MediaFile):
|
||||
def __init__(self,*args,**kwargs):
|
||||
super(AudioFile, self).__init__(*args, **kwargs)
|
||||
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.file = kwargs['file']
|
||||
self.probe = ffmpeg.probe(self.path_file_name)
|
||||
self.audio_capture_date = self.get_audio_capture_date()
|
||||
|
||||
if 'video' == self.probe['streams'][0]['codec_type'].lower():
|
||||
self.video_stream = self.probe['streams'][0]
|
||||
elif 'video' == self.probe['streams'][1]['codec_type'].lower():
|
||||
self.video_stream = self.probe['streams'][1]
|
||||
elif 'video' == self.probe['streams'][2]['codec_type'].lower():
|
||||
self.video_stream = self.probe['streams'][2]
|
||||
|
||||
if 'audio' == self.probe['streams'][0]['codec_type'].lower():
|
||||
self.audio_stream = self.probe['streams'][0]
|
||||
elif 'audio' == self.probe['streams'][1]['codec_type'].lower():
|
||||
self.audio_stream = self.probe['streams'][1]
|
||||
elif 'audio' == self.probe['streams'][2]['codec_type'].lower():
|
||||
self.audio_stream = self.probe['streams'][2]
|
||||
|
||||
self.format_stream = self.probe['format']
|
||||
self.stream = {}
|
||||
|
||||
@staticmethod
|
||||
def convert_from_seconds(seconds):
|
||||
return time.strftime("%H:%M:%S", time.gmtime(seconds))
|
||||
|
||||
def get_audio_capture_date(self):
|
||||
#TODO: refactor this try/except logic.
|
||||
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_video_meta(self):
|
||||
self.stream = {
|
||||
'video': {
|
||||
'bits_per_raw_sample': self.video_stream['bits_per_raw_sample'],
|
||||
'codec_long_name': self.video_stream['codec_long_name'],
|
||||
'duration': self.convert_from_seconds(float(self.video_stream['duration'])),
|
||||
'encoding_brand': self.format_stream['tags']['major_brand'],
|
||||
'pix_fmt': self.video_stream['pix_fmt'],
|
||||
'profile': self.video_stream['profile'],
|
||||
'r_frame_rate': self.video_stream['r_frame_rate'],
|
||||
'size': {
|
||||
'width_height': self.size,
|
||||
'height': self.video_stream['height'],
|
||||
'width': self.video_stream['width']
|
||||
}
|
||||
},
|
||||
'audio': {},
|
||||
'format': {}
|
||||
}
|
||||
|
||||
return self.stream
|
|
@ -0,0 +1,91 @@
|
|||
<?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>
|
|
@ -0,0 +1,50 @@
|
|||
# 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"))
|
|
@ -0,0 +1,73 @@
|
|||
# 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"))
|
|
@ -6,12 +6,10 @@ Load the config file from yaml file
|
|||
import sys
|
||||
import os
|
||||
import yaml
|
||||
from _lumberjack import timber
|
||||
|
||||
basedir = os.path.dirname(__file__)
|
||||
files = {}
|
||||
CONFIG_FILE = os.path.join(basedir, 'config.yaml')
|
||||
log = timber(__name__)
|
||||
|
||||
class Configure:
|
||||
""" Configure Class """
|
||||
|
|
|
@ -9,49 +9,32 @@ import yaml
|
|||
from tqdm import tqdm
|
||||
|
||||
### Local Imports
|
||||
# from configure import Configure, CONFIG_FILE
|
||||
from _hashing import xx_hash
|
||||
from _lumberjack import timber
|
||||
|
||||
def check_log_dir(d):
|
||||
create_folder(d)
|
||||
|
||||
|
||||
# c = Configure(CONFIG_FILE)
|
||||
# config = c.load_config()
|
||||
log = timber(__name__)
|
||||
log.info("Starting")
|
||||
|
||||
def dump_yaml(dictionary,file):
|
||||
""" dump dictionary to yaml file """
|
||||
with open(file, 'w', encoding="utf-8") as f:
|
||||
yaml.dump(dictionary, f)
|
||||
|
||||
f.close()
|
||||
|
||||
def path_exists(p):
|
||||
""" determine if the path exists """
|
||||
log.debug(f'path_exists({p})')
|
||||
pe = os.path.exists(p)
|
||||
# print(f'Path Exists: {pe}')
|
||||
|
||||
return pe
|
||||
|
||||
def is_dir(d):
|
||||
""" determine if object is a dir or not """
|
||||
log.debug(f'is_dir({d})')
|
||||
|
||||
return os.path.isdir(d)
|
||||
|
||||
def is_file(f):
|
||||
""" determine if object is a file or not """
|
||||
log.debug(f'is_file({f})')
|
||||
|
||||
return os.path.isfile(f)
|
||||
|
||||
def cmp_files(f1,f2):
|
||||
""" compare two files """
|
||||
log.debug(f'cmp_files({f1},{f2})')
|
||||
#TODO: Determine if path is actually a file
|
||||
#TODO: Determine if the hash has already been stored and use it if so
|
||||
|
||||
|
@ -61,30 +44,22 @@ def cmp_files(f1,f2):
|
|||
|
||||
def path_access_read(path):
|
||||
""" make sure we can read from the path """
|
||||
log.debug(f'path_access_read({path})')
|
||||
|
||||
val = os.access(path, os.R_OK)
|
||||
|
||||
if val is False:
|
||||
log.error(f'Can not read from {path}')
|
||||
|
||||
print(f'path_access_read check: cannot read from {path}')
|
||||
return val
|
||||
|
||||
def path_access_write(path):
|
||||
""" make sure we can write to the path """
|
||||
log.debug(f'path_access_write({path})')
|
||||
|
||||
val = os.access(path, os.W_OK)
|
||||
|
||||
if val is False:
|
||||
log.error(f'Can not write to {path}')
|
||||
|
||||
print(f'path_access_write check: cannot write to {path}')
|
||||
return val
|
||||
|
||||
def create_folder(file):
|
||||
""" Function to create folder """
|
||||
log.debug(f'create_folder({file})')
|
||||
|
||||
if path_exists(file) is False:
|
||||
os.makedirs(file)
|
||||
elif is_dir(file) is False:
|
||||
|
@ -92,22 +67,17 @@ def create_folder(file):
|
|||
|
||||
def cleanup_sd(f,config):
|
||||
""" If we should clean up the SD, nuke the copied files. """
|
||||
log.debug(f'cleanup_sd({f})')
|
||||
|
||||
if config['cleanup_sd'] is True:
|
||||
os.system('clear')
|
||||
for file in tqdm(f, desc = "Cleaning Up SD:"):
|
||||
if f[file]['source_cleanable'] is True:
|
||||
log.debug(f"Cleanup SD: Removing File -\n{os.path.join(f[file]['folders']['source_path'],f[file]['name'])}")
|
||||
|
||||
print(f"Cleanup SD: Removing File -\n{os.path.join(f[file]['folders']['source_path'],f[file]['name'])}")
|
||||
os.remove(os.path.join(f[file]['folders']['source_path'],f[file]['name']))
|
||||
else:
|
||||
log.debug(f"Cleanup SD: Not Removing File -\n{os.path.join(f[file]['folders']['source_path'],f[file]['name'])}")
|
||||
print(f"Cleanup SD: Not Removing File -\n{os.path.join(f[file]['folders']['source_path'],f[file]['name'])}")
|
||||
|
||||
def validate_config_dir_access(config):
|
||||
""" Validate we can operate in the defined directories """
|
||||
log.debug('validate_config_dir_access')
|
||||
|
||||
check = path_access_write(config['folders']['destination']['base'])
|
||||
if check is False:
|
||||
accessible = False
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
import os
|
||||
|
||||
from PyQt6.QtGui import QPixmap
|
||||
|
||||
from _file_stuff import is_file
|
||||
from BitMover_ui import MainWindow
|
||||
from _media_file import MediaFile
|
||||
|
||||
basedir = os.path.dirname(__file__)
|
||||
|
||||
class FindFiles(MainWindow):
|
||||
def __init__(self):
|
||||
super(FindFiles,self).__init__()
|
||||
self.search_types = None
|
||||
self.event = self.get_event()
|
||||
self.src_dir = self.config['folders']['source']['base']
|
||||
self.dst_dir = self.config['folders']['destination']['base']
|
||||
self.file_types = self.config['file_types']
|
||||
|
||||
self.file_total = 0
|
||||
self.file_list = {}
|
||||
|
||||
def get_search_types(self):
|
||||
self.search_types = []
|
||||
if self.checkBox_search_for_images.isChecked():
|
||||
self.search_types.append('image')
|
||||
if self.checkBox_search_for_video.isChecked():
|
||||
self.search_types.append('video')
|
||||
if self.checkBox_search_for_audio.isChecked():
|
||||
self.search_types.append('audio')
|
||||
return self.search_types
|
||||
|
||||
def get_t_files(self):
|
||||
self.file_list.clear()
|
||||
self.img_preview.setPixmap(QPixmap(os.path.join(basedir,
|
||||
'assets',
|
||||
'preview_placeholder.jpg')))
|
||||
|
||||
for folder, subfolders, filename in os.walk(self.src_dir):
|
||||
for f_type in self.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):
|
||||
self.file_total += int(1)
|
||||
else:
|
||||
print(f"Skipping {current_file} as it does not look like a real file.")
|
||||
|
||||
def t_find_files(self,
|
||||
progress_callback,
|
||||
found_file,
|
||||
total_file_count):
|
||||
file_count = int(0)
|
||||
self.search_types = self.get_search_types()
|
||||
self.get_t_files()
|
||||
|
||||
if len(self.search_types) > 0:
|
||||
for folder, subfolders, filename in os.walk(self.src_dir):
|
||||
for f_type in self.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)
|
||||
found_file.emit(current_file)
|
||||
else:
|
||||
print(f"Skipping {current_file} as it does not look like a real file.")
|
||||
total_file_count.emit(file_count)
|
||||
progress_callback.emit(round((file_count / self.file_total) * 100, 0))
|
||||
else:
|
||||
print("Nothing to search for.")
|
||||
|
||||
return self.file_list
|
||||
|
||||
def process_file(self, p):
|
||||
""" gather information and add to dictionary """
|
||||
|
||||
media_file = MediaFile(p)
|
||||
i = media_file.source_path_hash
|
||||
|
||||
self.file_list[i] = media_file.media_meta()
|
|
@ -0,0 +1,31 @@
|
|||
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)
|
|
@ -0,0 +1,59 @@
|
|||
<?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>
|
|
@ -0,0 +1,44 @@
|
|||
# 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", "%"))
|
|
@ -1,98 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Get EXIF information from image
|
||||
"""
|
||||
from uu import Error
|
||||
|
||||
import exifread
|
||||
from datetime import datetime
|
||||
from _lumberjack import timber
|
||||
|
||||
log = timber(__name__)
|
||||
|
||||
def get_exif_date(tags,tag,f):
|
||||
t = ''
|
||||
log.debug(f'function: get_exif_tag(tags:{tags},tag:{tag},format:{f}')
|
||||
try:
|
||||
t = datetime.strptime(str(tags[tag]),f)
|
||||
except:
|
||||
pass
|
||||
# log.debug(f'Error: {e}. Format: {f}')
|
||||
|
||||
return t
|
||||
|
||||
def get_os_ctime(path):
|
||||
# Don't pull the file ctime anymore, more often than not, it's wrong.
|
||||
# t = datetime.fromtimestamp(os.path.getctime(path))
|
||||
|
||||
#raise an error so it will except and move on.
|
||||
raise ValueError(f"{path}: Using ctime like this is usually not good.")
|
||||
|
||||
|
||||
def set_generic_date_time(date = '1900:01:01',
|
||||
time = '00:00:00',
|
||||
f = '%Y:%m:%d %H:%M:%S'):
|
||||
|
||||
t = datetime.strptime(str(f'{date} {time}'),f)
|
||||
return t
|
||||
|
||||
|
||||
def get_img_date(p):
|
||||
with open(p, 'rb') as file:
|
||||
tags = exifread.process_file(file)
|
||||
|
||||
if 'Composite DateTimeOriginal' in tags:
|
||||
get_exif_date(tags,'Composite DateTimeOriginal','%Y:%m:%d %H:%M')
|
||||
|
||||
def get_exif_tag(p,t):
|
||||
with open(p, "rb") as f:
|
||||
try:
|
||||
tags = exifread.process_file(f)
|
||||
# print(f'{p}: {tags}')
|
||||
except Error as e:
|
||||
return f'Received Error: {e} when trying to open {p}'
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
for tag in tags:
|
||||
# print(tag)
|
||||
if t in tag.lower():
|
||||
# print(tags[tag])
|
||||
return tags[tag]
|
||||
else:
|
||||
pass
|
||||
|
||||
def get_image_date(path):
|
||||
t = ''
|
||||
exif_dt = ''
|
||||
|
||||
with open(path, "rb") as file:
|
||||
try:
|
||||
tags = exifread.process_file(file)
|
||||
except Error as e:
|
||||
log.error(e)
|
||||
finally:
|
||||
file.close()
|
||||
|
||||
for tag in tags:
|
||||
if "DateTime" in tag:
|
||||
t = tag
|
||||
break
|
||||
|
||||
if '' == t:
|
||||
exif_dt = set_generic_date_time()
|
||||
else:
|
||||
for f in ['%Y:%m:%d %H:%M:%S',
|
||||
'%Y/%m/%d %H:%M:%S',
|
||||
'%Y-%m-%d-%H-%M-%S']:
|
||||
log.debug(f'Trying... {t}, {f}, {path} ')
|
||||
exif_dt = get_exif_date(tags, t, f)
|
||||
|
||||
if '' != exif_dt:
|
||||
break
|
||||
|
||||
if '' == exif_dt:
|
||||
exif_dt = set_generic_date_time()
|
||||
# s = get_os_ctime(path) # This could produce wildly incorrect results
|
||||
return exif_dt
|
18
_hashing.py
18
_hashing.py
|
@ -7,12 +7,6 @@ dump the dictionary generated from findings into a yaml file for later inspectio
|
|||
import os
|
||||
import xxhash
|
||||
from tqdm import tqdm
|
||||
# from configure import Configure, CONFIG_FILE
|
||||
from _lumberjack import timber
|
||||
|
||||
# c = Configure(CONFIG_FILE)
|
||||
# config = c.load_config()
|
||||
log = timber(__name__)
|
||||
|
||||
def xx_hash(file):
|
||||
""" calculates and returns file hash based on xxHash """
|
||||
|
@ -39,19 +33,13 @@ def hash_path(path):
|
|||
|
||||
def gen_xxhashes(f):
|
||||
""" Generate xxHashes """
|
||||
log.debug(f'gen_xxhashes({f})')
|
||||
for file in tqdm(f, desc = "Generating xx Hashes:"):
|
||||
os.system('clear')
|
||||
log.debug(f[file])
|
||||
f[file]['xx_checksums'] = {}
|
||||
for folder in f[file]['folders']:
|
||||
k = os.path.join(f[file]['folders'][folder], f[file]['name'])
|
||||
if k != f[file]['name']:
|
||||
# k = f[file]['folders'][folder]
|
||||
log.debug(k)
|
||||
f[file]['xx_checksums'][k] = xx_hash(k)
|
||||
log.debug(f"{k}: {f[file]['xx_checksums'][k]}")
|
||||
log.debug(f[file])
|
||||
|
||||
def validate_xx_checksums(f):
|
||||
""" Validate Checksums """
|
||||
|
@ -67,8 +55,8 @@ def validate_xx_checksums(f):
|
|||
f[file]['source_cleanable'] = True
|
||||
else:
|
||||
f[file]['source_cleanable'] = False
|
||||
log.critical(f'FATAL: Checksum validation failed for: \
|
||||
print(f'FATAL: Checksum validation failed for: \
|
||||
{f[file]["name"]} \n{c[i]}\n is not equal to \n{c[p]}\n')
|
||||
log.debug('\n File Meta:\n')
|
||||
log.debug(f[file])
|
||||
print('\n File Meta:\n')
|
||||
print(f[file])
|
||||
i = i + 1
|
|
@ -0,0 +1,61 @@
|
|||
import exifread
|
||||
from datetime import datetime
|
||||
from _media_file import MediaFile
|
||||
|
||||
class ImageTag(MediaFile):
|
||||
def __init__(self,path_file_name=None,*args,**kwargs):
|
||||
super(ImageTag,self).__init__(*args,**kwargs)
|
||||
if path_file_name is not None:
|
||||
self.path_file_name = path_file_name
|
||||
self.date_time_original = self.get_img_date()
|
||||
self.image_tags = self.get_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
|
||||
|
||||
@staticmethod
|
||||
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_image_tags(self):
|
||||
with open(self.path_file_name,'rb') as f:
|
||||
tags = exifread.process_file(f)
|
||||
f.close()
|
||||
return tags
|
||||
|
||||
def process_img_date_tag(self, tag, date_time_format):
|
||||
t = None
|
||||
try:
|
||||
t = datetime.strptime(str(self.image_tags[tag]), date_time_format)
|
||||
except:
|
||||
pass
|
||||
return t
|
||||
|
||||
def get_img_date(self):
|
||||
t = None
|
||||
dt_tag = None
|
||||
for tag in self.image_tags:
|
||||
if 'DateTime' in tag:
|
||||
t = tag
|
||||
break
|
||||
if t is None:
|
||||
dt_tag = self.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']:
|
||||
dt_tag = self.process_img_date_tag(t,f)
|
||||
|
||||
if dt_tag is not None:
|
||||
break
|
||||
if dt_tag is None:
|
||||
dt_tag = self.set_generic_date_time()
|
||||
return dt_tag
|
124
_img_preview.py
124
_img_preview.py
|
@ -1,124 +0,0 @@
|
|||
#!/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)
|
|
@ -8,52 +8,39 @@ from os import system,path,rename
|
|||
from tqdm import tqdm
|
||||
|
||||
from _file_stuff import create_folder, cmp_files, path_exists
|
||||
from _lumberjack import timber
|
||||
from _hashing import xx_hash
|
||||
# from configure import Configure, CONFIG_FILE
|
||||
|
||||
# c = Configure(CONFIG_FILE)
|
||||
# config = c.load_config()
|
||||
log = timber(__name__)
|
||||
|
||||
def copy_with_progress(s,d,f):
|
||||
def copy_with_progress(s,d):
|
||||
""" 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(d, 'wb') as fd:
|
||||
# with tqdm(total=size, unit='B', unit_scale=True, desc=f'Copying {f}') as pbar:
|
||||
while True:
|
||||
chunk = fs.read(4096)
|
||||
if not chunk:
|
||||
break
|
||||
fd.write(chunk)
|
||||
# pbar.update(len(chunk))
|
||||
|
||||
def copy_from_source(source_path,dest_path,file_name):
|
||||
""" Copy file from source to destination """
|
||||
log.debug(f'copy_from_source({source_path},{dest_path},{file_name}')
|
||||
|
||||
file_exists = path_exists(path.join(dest_path,file_name))
|
||||
|
||||
if file_exists is True:
|
||||
log.debug(f'\nFound {file_name} at destination, checking if they match.')
|
||||
print(f'\nFound {file_name} at destination, checking if they match.')
|
||||
check_match = cmp_files(
|
||||
path.join(source_path,file_name),
|
||||
path.join(dest_path, file_name))
|
||||
if check_match is False:
|
||||
log.warn(f'\nFound duplicate for {source_path}/{file_name}, \
|
||||
print(f'\nFound duplicate for {source_path}/{file_name}, \
|
||||
renaming destination with hash appended.')
|
||||
base, extension = path.splitext(file_name)
|
||||
#md5 = md5_hash(os.path.join(dest_path, file_name))
|
||||
f_xxhash = xx_hash(path.join(dest_path, file_name))
|
||||
#file_name_hash = base + '_' + md5 + extension
|
||||
file_name_hash = base + '_' + f_xxhash + extension
|
||||
rename(path.join(dest_path, file_name),
|
||||
path.join(dest_path, file_name_hash))
|
||||
else:
|
||||
log.info(f'\n{file_name} hashes match')
|
||||
print(f'\n{file_name} hashes match')
|
||||
# remove(path.join(source_path,file_name))
|
||||
# f.pop(file_name)
|
||||
return
|
||||
|
@ -61,13 +48,10 @@ def copy_from_source(source_path,dest_path,file_name):
|
|||
# create_folder(dest_path)
|
||||
# shutil.copy(os.path.join(source_path,file_name), dest_path)
|
||||
copy_with_progress(path.join(source_path, file_name),
|
||||
path.join(dest_path, file_name),
|
||||
file_name)
|
||||
system('clear')
|
||||
path.join(dest_path, file_name))
|
||||
|
||||
def copy_files(f,config):
|
||||
""" Copy Files. """
|
||||
log.debug(f'copy_files({f})')
|
||||
system('clear')
|
||||
for file in tqdm(f, desc="Copying Files:"):
|
||||
create_folder(f[file]['folders']['destination'])
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
#!/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
160
_media.py
|
@ -1,160 +0,0 @@
|
|||
#!/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
|
|
@ -0,0 +1,152 @@
|
|||
import os.path
|
||||
import sys
|
||||
|
||||
from _audio import AudioFile
|
||||
from BitMover_ui import MainWindow
|
||||
from _hashing import hash_path
|
||||
from _video import VideoFile
|
||||
from _photo import PhotoFile
|
||||
from datetime import datetime
|
||||
|
||||
class MediaFile(MainWindow):
|
||||
def __init__(self,path_file_name,*args,**kwargs):
|
||||
super(MediaFile,self).__init__(*args,**kwargs)
|
||||
self.event_name = self.get_event()
|
||||
self.src_dir = self.config['folders']['source']['base']
|
||||
self.base_dst_dir = self.config['folders']['destination']['base']
|
||||
self.file_types = self.config['file_types']
|
||||
self.path_file_name = path_file_name
|
||||
self.source_path_hash = hash_path(self.path_file_name)
|
||||
self.file_name = self.get_file_name()
|
||||
self.dotted_file_ext = self.get_dotted_file_ext()
|
||||
self.file_ext = self.get_file_ext()
|
||||
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.video_media = VideoFile()
|
||||
self.audio_media = AudioFile()
|
||||
self.photo_media = PhotoFile()
|
||||
|
||||
def get_file_name(self):
|
||||
return os.path.basename(self.path_file_name)
|
||||
|
||||
def get_dotted_file_ext(self):
|
||||
return os.path.splitext(self.file_name)[1].lower()
|
||||
|
||||
def get_file_ext(self):
|
||||
return self.dotted_file_ext.split('.')[1]
|
||||
|
||||
def get_file_type(self,file=None):
|
||||
""" determine if the extension is in the list of file types """
|
||||
if file is not None:
|
||||
self.file_name = file
|
||||
|
||||
if not self.file_ext:
|
||||
self.file_ext = self.get_file_ext()
|
||||
|
||||
for t in self.config['file_types']:
|
||||
for e in self.config['file_types'][t]:
|
||||
if self.file_ext.lower().endswith(e):
|
||||
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 = os.path.join(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':
|
||||
stamp = self.photo_media.photo_capture_date
|
||||
elif self.file_type == 'video':
|
||||
stamp = self.video_media.get_video_capture_date()
|
||||
elif self.file_type == 'audio':
|
||||
stamp = self.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 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,
|
||||
'type': self.file_type
|
||||
}
|
||||
|
||||
if self.file_type == 'video':
|
||||
self.media['video_meta'] = self.video_media.get_video_meta()
|
||||
self.media['image_meta'] = None
|
||||
self.media['audio_meta'] = None
|
||||
|
||||
if self.file_type == 'image':
|
||||
photo = PhotoFile()
|
||||
self.media['image_meta'] = photo.get_photo_meta()
|
||||
self.media['video_meta'] = None
|
||||
self.media['audio_meta'] = None
|
|
@ -0,0 +1,116 @@
|
|||
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
|
||||
|
||||
def is_video(self,f):
|
||||
if self.files[f]['type'] == 'video':
|
||||
r = True
|
||||
else:
|
||||
r = False
|
||||
|
||||
return r
|
||||
|
||||
def is_image(self,f):
|
||||
if self.files[f]['type'] == 'image':
|
||||
r = True
|
||||
else:
|
||||
r = False
|
||||
|
||||
return r
|
||||
|
||||
def t_copy_files(self,
|
||||
import_progress_callback,
|
||||
current_file_progress_callback,
|
||||
imported_file_count_callback
|
||||
):
|
||||
""" Copy Files. """
|
||||
|
||||
count = int(0)
|
||||
|
||||
for file in self.files:
|
||||
self.src_dir = self.files[file]['folders']['source_path']
|
||||
self.dst_dir = self.files[file]['folders']['destination']
|
||||
self.path_file_source = path.join(self.src_dir,
|
||||
self.files[file]['name'])
|
||||
self.path_file_destination = path.join(self.dst_dir,
|
||||
self.files[file]['name'])
|
||||
self.destination_original_path = path.join(self.dst_dir,
|
||||
self.files[file]['folders']['destination_original'])
|
||||
self.path_file_destination_original = path.join(self.dst_dir,
|
||||
self.files[file]['folders']['destination_original'],
|
||||
self.files[file]['name'])
|
||||
|
||||
self.imp_dialog.set_importing_file(self.path_file_source)
|
||||
|
||||
self.copy_a_file(
|
||||
file,
|
||||
current_file_progress_callback)
|
||||
|
||||
if self.check_store_original(file) is True:
|
||||
self.copy_a_file(file,
|
||||
current_file_progress_callback)
|
||||
|
||||
count += 1
|
||||
|
||||
imported_file_count_callback.emit(count)
|
||||
import_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,
|
||||
_f,
|
||||
current_file_progress_callback):
|
||||
|
||||
size = path.getsize(self.path_file_source)
|
||||
create_folder(self.dst_dir)
|
||||
|
||||
self.check_duplicate(_f)
|
||||
|
||||
if self.is_video(_f):
|
||||
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,_f):
|
||||
if self.config['store_originals'] is True:
|
||||
if self.is_image(_f):
|
||||
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,_f):
|
||||
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[_f]['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))
|
|
@ -0,0 +1,159 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from _media_file import MediaFile
|
||||
from _image_tag import ImageTag
|
||||
|
||||
# https://exiftool.org/TagNames/EXIF.html
|
||||
# https://exiftool.org/TagNames/Sony.html
|
||||
|
||||
class PhotoFile(MediaFile):
|
||||
def __init__(self,*args,**kwargs):
|
||||
super(PhotoFile, self).__init__(*args, **kwargs)
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.image_tag = ImageTag()
|
||||
self.is_jpg = False
|
||||
self.is_raw = False
|
||||
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.img = Image.open(self.path_file_name)
|
||||
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.file_name.lower().endswith('jpg') \
|
||||
or self.file_name.lower().endswith('.jpeg'):
|
||||
self.is_raw = False
|
||||
self.is_jpg = True
|
||||
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):
|
||||
return self.img.width
|
||||
|
||||
def get_photo_height(self):
|
||||
return self.img.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):
|
||||
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 self.photo_meta
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env python
|
||||
from PIL import Image
|
||||
|
||||
from BitMover_ui import MainWindow
|
||||
from _media_file import MediaFile
|
||||
from _raw_photo import extract_jpg_thumb
|
||||
from _video import VideoFile
|
||||
|
||||
class MediaPreview(MainWindow):
|
||||
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 = self.media_file.source_path_hash
|
||||
self.thumbnail_ratio = self.media_files_list[self.source_path_hash]['photo']['size']['ratio']
|
||||
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._img_preview()
|
||||
elif self.media_files_list[self.source_path_hash]['file_type'] == 'video':
|
||||
self._video_preview()
|
||||
|
||||
self.mpixels = self.media_files_list[self.source_path_hash]['photo']['megapixels']
|
||||
|
||||
def _img_preview(self):
|
||||
if self.media_files_list[self.source_path_hash]['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]['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(file=self.path_file_name)
|
||||
self.thumbnail = vid.gen_video_thumbnail()
|
||||
|
||||
self.width = self.media_files_list[self.source_path_hash]['video']['size']['width']
|
||||
self.height = self.media_files_list[self.source_path_hash]['video']['size']['height']
|
||||
self.video_framerate = round(
|
||||
int(self.media_files_list[self.source_path_hash]['video']['r_frame_rate'].split('/')[0]) /
|
||||
int(self.media_files_list[self.source_path_hash]['video']['r_frame_rate'].split('/')[1]),2)
|
||||
self.video_bit_depth = self.media_files_list[self.source_path_hash]['video']['bits_per_raw_sample']
|
||||
self.video_duration = self.media_files_list[self.source_path_hash]['video']['duration']
|
||||
self.video_encoding = self.media_files_list[self.source_path_hash]['video']['encoding_brand']
|
||||
self.video_codec = self.media_files_list[self.source_path_hash]['video']['codec_long_name']
|
||||
self.video_profile = self.media_files_list[self.source_path_hash]['video']['profile']
|
||||
self.video_pix_format = self.media_files_list[self.source_path_hash]['video']['pix_fmt']
|
|
@ -29,6 +29,9 @@ class WorkerSignals(QObject):
|
|||
progress = pyqtSignal(int)
|
||||
import_progress = pyqtSignal(int)
|
||||
current_file_progress = pyqtSignal(float)
|
||||
imported_file_count = pyqtSignal(int)
|
||||
found_file = pyqtSignal(str)
|
||||
total_file_count = pyqtSignal(int)
|
||||
# current_import_file = pyqtSignal()
|
||||
|
||||
class Worker(QRunnable):
|
||||
|
@ -57,6 +60,9 @@ class Worker(QRunnable):
|
|||
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['imported_file_count_callback'] = self.signals.imported_file_count
|
||||
self.kwargs['found_file'] = self.signals.found_file
|
||||
self.kwargs['total_file_count'] = self.signals.total_file_count
|
||||
# self.kwargs['current_import_file'] = self.signals.current_import_file
|
||||
|
||||
@pyqtSlot()
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
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
|
174
_video.py
174
_video.py
|
@ -1,65 +1,47 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
import ffmpeg
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
class Video:
|
||||
from _media_file import MediaFile
|
||||
|
||||
class VideoFile(MediaFile):
|
||||
def __init__(self,max_width=1024,*args,**kwargs):
|
||||
super(Video,self).__init__()
|
||||
super(VideoFile, self).__init__(*args, **kwargs)
|
||||
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.file = kwargs['file']
|
||||
self.out = 'thumbnail.jpg'
|
||||
self.max_width = max_width
|
||||
self.probe = ffmpeg.probe(self.path_file_name)
|
||||
self.video_capture_date = self.get_video_capture_date()
|
||||
|
||||
self.stream = {
|
||||
'video': {},
|
||||
'format': {},
|
||||
'audio': {}
|
||||
}
|
||||
if 'video' == self.probe['streams'][0]['codec_type'].lower():
|
||||
self.video_stream = self.probe['streams'][0]
|
||||
elif 'video' == self.probe['streams'][1]['codec_type'].lower():
|
||||
self.video_stream = self.probe['streams'][1]
|
||||
elif 'video' == self.probe['streams'][2]['codec_type'].lower():
|
||||
self.video_stream = self.probe['streams'][2]
|
||||
|
||||
self.stream['video']['duration'] = ''
|
||||
self.stream['video']['creation_time'] = ''
|
||||
self.stream['video']['encoding_brand'] = ''
|
||||
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'] = ''
|
||||
if 'audio' == self.probe['streams'][0]['codec_type'].lower():
|
||||
self.audio_stream = self.probe['streams'][0]
|
||||
elif 'audio' == self.probe['streams'][1]['codec_type'].lower():
|
||||
self.audio_stream = self.probe['streams'][1]
|
||||
elif 'audio' == self.probe['streams'][2]['codec_type'].lower():
|
||||
self.audio_stream = self.probe['streams'][2]
|
||||
|
||||
self.stream['format'] = {}
|
||||
self.stream['format']['major_brand'] = ''
|
||||
self.stream['format']['compatible_brands'] = ''
|
||||
self.stream['format']['creation_time'] = ''
|
||||
self.format_stream = self.probe['format']
|
||||
self.size_width = self.get_video_width()
|
||||
self.size_height = self.get_video_height()
|
||||
self.size = self.get_video_size()
|
||||
|
||||
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'] = ''
|
||||
self.stream = {}
|
||||
|
||||
@staticmethod
|
||||
def convert_from_seconds(s):
|
||||
seconds = s
|
||||
def convert_from_seconds(seconds):
|
||||
return time.strftime("%H:%M:%S", time.gmtime(seconds))
|
||||
|
||||
def gen_video_thumbnail(self):
|
||||
|
@ -67,20 +49,34 @@ class Video:
|
|||
Generate a thumbnail from a video
|
||||
"""
|
||||
self.get_video_meta()
|
||||
time = self.stream['video']['seconds'] // 5
|
||||
time_seconds = self.stream['video']['seconds'] // 5
|
||||
v_width = int(self.stream['video']['size']['width'])
|
||||
width = self.set_thumb_width(v_width)
|
||||
|
||||
try:
|
||||
(
|
||||
ffmpeg.input(self.file, ss=time)
|
||||
.filter('scale', width, -1)
|
||||
.output(self.out, vframes=1)
|
||||
ffmpeg.input(
|
||||
self.file,
|
||||
ss = time_seconds
|
||||
)
|
||||
.filter(
|
||||
'scale',
|
||||
width,
|
||||
-1
|
||||
)
|
||||
.output(
|
||||
self.out,
|
||||
vframes = 1
|
||||
)
|
||||
.overwrite_output()
|
||||
.run(capture_stdout=True, capture_stderr=True)
|
||||
.run(
|
||||
capture_stdout = True,
|
||||
capture_stderr = True
|
||||
)
|
||||
)
|
||||
except ffmpeg.Error as e:
|
||||
print(e.stderr.decode(), file=sys.stderr)
|
||||
print(e.stderr.decode(),
|
||||
file = sys.stderr)
|
||||
|
||||
return self.out
|
||||
|
||||
|
@ -92,36 +88,58 @@ class Video:
|
|||
|
||||
return width
|
||||
|
||||
def get_video_meta(self):
|
||||
probe = ffmpeg.probe(self.file)
|
||||
def get_video_width(self):
|
||||
return self.video_stream['width']
|
||||
|
||||
if 'video' == probe['streams'][0]['codec_type'].lower():
|
||||
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_height(self):
|
||||
return self.video_stream['height']
|
||||
|
||||
if 'audio' == probe['streams'][0]['codec_type'].lower():
|
||||
audio_stream = probe['streams'][0]
|
||||
elif 'audio' == probe['streams'][1]['codec_type'].lower():
|
||||
audio_stream = probe['streams'][1]
|
||||
elif 'audio' == probe['streams'][2]['codec_type'].lower():
|
||||
audio_stream = probe['streams'][2]
|
||||
def get_video_size(self):
|
||||
if self.size_width is None:
|
||||
self.size_width = self.get_video_width()
|
||||
if self.size_height is None:
|
||||
self.size_height = self.get_video_height()
|
||||
return f'{self.size_width}x{self.size_height}'
|
||||
|
||||
format_stream = probe['format']
|
||||
|
||||
self.stream['video']['size']['width'] = video_stream['width']
|
||||
self.stream['video']['size']['height'] = video_stream['height']
|
||||
self.stream['video']['r_frame_rate'] = video_stream['r_frame_rate']
|
||||
self.stream['video']['bits_per_raw_sample'] = video_stream['bits_per_raw_sample']
|
||||
self.stream['video']['seconds'] = float(video_stream['duration'])
|
||||
self.stream['video']['duration'] = self.convert_from_seconds(
|
||||
self.stream['video']['seconds']
|
||||
def get_video_capture_date(self):
|
||||
#TODO: refactor this try/except logic.
|
||||
try:
|
||||
stamp = datetime.strptime(
|
||||
self.format_stream['tags']['creation_time'],
|
||||
'%Y-%m-%dT%H:%M:%S.%f%z'
|
||||
)
|
||||
self.stream['video']['encoding_brand'] = format_stream['tags']['major_brand']
|
||||
self.stream['video']['codec_long_name'] = video_stream['codec_long_name']
|
||||
self.stream['video']['profile'] = video_stream['profile']
|
||||
self.stream['video']['pix_fmt'] = video_stream['pix_fmt']
|
||||
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 get_video_meta(self):
|
||||
self.stream = {
|
||||
'video': {
|
||||
'bits_per_raw_sample': self.video_stream['bits_per_raw_sample'],
|
||||
'codec_long_name': self.video_stream['codec_long_name'],
|
||||
'duration': self.convert_from_seconds(float(self.video_stream['duration'])),
|
||||
'encoding_brand': self.format_stream['tags']['major_brand'],
|
||||
'pix_fmt': self.video_stream['pix_fmt'],
|
||||
'profile': self.video_stream['profile'],
|
||||
'r_frame_rate': self.video_stream['r_frame_rate'],
|
||||
'size': {
|
||||
'width_height': self.size,
|
||||
'height': self.video_stream['height'],
|
||||
'width': self.video_stream['width']
|
||||
}
|
||||
},
|
||||
'audio': {},
|
||||
'format': {}
|
||||
}
|
||||
|
||||
return self.stream
|
|
@ -1,2 +1,5 @@
|
|||
pyuic6 BitMover.ui -o _BitMover_MainWindow.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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
qt6-tools designer
|
Loading…
Reference in New Issue