refactor
This commit is contained in:
parent
3a79f22315
commit
410a0e3e87
269
BitMover_ui.py
269
BitMover_ui.py
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from PyQt6.QtCore import QThreadPool
|
||||
from PyQt6.QtGui import QIcon, QPixmap
|
||||
|
@ -11,15 +12,20 @@ from _configure import CONFIG_FILE, Configure
|
|||
from _find_files import FindFiles
|
||||
from _find_files_dialog import FindProgress
|
||||
from _import_dialog import DialogImport
|
||||
from _media_import import MediaImporter
|
||||
# from _media_import import MediaImporter
|
||||
from _preview import MediaPreview
|
||||
from _thread_my_stuff import Worker
|
||||
from _media_file import MediaFile
|
||||
from _file_stuff import (path_exists,
|
||||
is_dir,
|
||||
is_file)
|
||||
from _hashing import hash_path
|
||||
|
||||
|
||||
basedir = os.path.dirname(__file__)
|
||||
|
||||
# TODO: verify source dir actually exists
|
||||
|
||||
# Subclass QMainWindow to customize your application's main window
|
||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MainWindow,self).__init__(*args,**kwargs)
|
||||
|
@ -27,22 +33,49 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self.setWindowTitle("BitMover")
|
||||
self.setWindowIcon(QIcon(os.path.join(basedir,'assets', 'forklift.ico')))
|
||||
self.threadpool = QThreadPool()
|
||||
c = Configure(CONFIG_FILE)
|
||||
self.config = c.load_config()
|
||||
self.src_dir = self.config['folders']['source']['base']
|
||||
self.dst_dir = self.config['folders']['destination']['base']
|
||||
self.verify_checksum = self.config['verify_checksum']
|
||||
self.cleanup_files = self.config['cleanup_sd']
|
||||
self.store_originals = self.config['store_originals']
|
||||
self.file_types = self.config['file_types']
|
||||
self.config = None
|
||||
self.src_dir = None
|
||||
self.dst_dir = None
|
||||
self.verify_checksum = None
|
||||
self.cleanup_files = None
|
||||
self.store_originals = None
|
||||
self.file_types = None
|
||||
self.source_path_hash = None
|
||||
self.search_types = None
|
||||
self.chunk_size = (16 * 1024) * 1
|
||||
self.load_config()
|
||||
|
||||
self.destination_original_path = None
|
||||
self.path_file_source = None
|
||||
self.path_file_destination = None
|
||||
self.path_file_destination_original = None
|
||||
|
||||
# File Stuff
|
||||
self.total_files = 0
|
||||
self.file_total = 0
|
||||
self.files = {}
|
||||
|
||||
self.event_name = None
|
||||
|
||||
self.imp_dialog = DialogImport()
|
||||
self.find_files_dialog = FindProgress()
|
||||
|
||||
self.widgets_config()
|
||||
|
||||
def load_config(self):
|
||||
try:
|
||||
c = Configure(CONFIG_FILE)
|
||||
self.config = c.load_config()
|
||||
self.src_dir = self.config['folders']['source']['base']
|
||||
self.dst_dir = self.config['folders']['destination']['base']
|
||||
self.verify_checksum = self.config.get('verify_checksum', False)
|
||||
self.cleanup_files = self.config.get('cleanup_sd', False)
|
||||
self.store_originals = self.config.get('store_originals', False)
|
||||
self.file_types = self.config.get('file_types', {})
|
||||
except KeyError as e:
|
||||
log.error(f"Missing configuration key: {e}")
|
||||
sys.exit(1) # or provide a fallback
|
||||
|
||||
def widgets_config(self):
|
||||
# Button Setup
|
||||
self.pushButton_src_browse.clicked.connect(self.select_src_directory)
|
||||
|
@ -98,6 +131,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self.dst_dir = directory
|
||||
self.lineEdit_dst_dir.setText(self.dst_dir)
|
||||
|
||||
def get_source_path_hash(self,f):
|
||||
self.source_path_hash = hash_path(f)
|
||||
return self.source_path_hash
|
||||
|
||||
def verify_checksum_changed(self):
|
||||
if self.checkBox_verify_checksum.isChecked():
|
||||
self.config['verify_checksum'] = True
|
||||
|
@ -130,20 +167,20 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
'assets',
|
||||
'preview_placeholder.jpg'))
|
||||
|
||||
def get_preview(self,i):
|
||||
preview = MediaPreview(path_file_name = i.text(),
|
||||
media_files = self.files)
|
||||
def get_preview(self,path_file_name):
|
||||
preview = MediaPreview(path_file_name=path_file_name,
|
||||
media_files=self.files)
|
||||
return preview
|
||||
|
||||
def update_preview(self,preview):
|
||||
self.set_thumbnail(preview.thumbnail,ratio=preview.thumbnail_ratio)
|
||||
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(
|
||||
|
@ -151,7 +188,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self.l_meta_content_date_time_c.setText(
|
||||
f"{f_date['y']}/{f_date['m']}/{f_date['d']}"
|
||||
)
|
||||
if preview.file_type == 'image':
|
||||
if f['file_type'] == 'image':
|
||||
f_photo = f['image_meta']['photo']
|
||||
|
||||
self.l_meta_01.setText('Size')
|
||||
self.l_meta_02.setText('dpi')
|
||||
self.l_meta_03.setText('ISO')
|
||||
|
@ -160,16 +199,18 @@ 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(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']))
|
||||
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['lens_focal_length']))
|
||||
self.l_meta_content_06.setText(str("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['size']['megapixels']))
|
||||
|
||||
elif f['file_type'] == 'video':
|
||||
f_video = f['video_meta']['video']
|
||||
|
||||
elif preview.file_type == 'video':
|
||||
self.l_meta_01.setText('Size')
|
||||
self.l_meta_02.setText('Frames / Second')
|
||||
self.l_meta_03.setText('Bit Depth')
|
||||
|
@ -211,13 +252,43 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
if i is None:
|
||||
self.set_default_thumbnail()
|
||||
else:
|
||||
preview = self.get_preview(i)
|
||||
print(f'index changed to: {i.text()}')
|
||||
preview = self.get_preview(i.text())
|
||||
self.update_preview(preview)
|
||||
self.update_metadata(preview)
|
||||
|
||||
def get_event(self):
|
||||
event_name = self.eventName.text()
|
||||
return event_name
|
||||
self.event_name = self.eventName.text()
|
||||
return self.event_name
|
||||
|
||||
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):
|
||||
file_total = 0
|
||||
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):
|
||||
file_total += int(1)
|
||||
else:
|
||||
print(f"Skipping {current_file} as it does not look like a real file.")
|
||||
return file_total
|
||||
|
||||
def set_total_files(self,t):
|
||||
total_file_count = t
|
||||
|
@ -228,7 +299,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
|
||||
@staticmethod
|
||||
def print_output(s):
|
||||
print(s)
|
||||
print(f'output: {s}')
|
||||
|
||||
def worker_thread_started(self):
|
||||
print('scan thread started')
|
||||
|
@ -253,13 +324,30 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
def gen_file_dict(self,d):
|
||||
self.files = d
|
||||
|
||||
def process_file(self,p):
|
||||
""" gather information and add to dictionary """
|
||||
media_file = MediaFile(path_file_name=p,
|
||||
config=self.config,
|
||||
event_name=self.event_name)
|
||||
i = self.get_source_path_hash(p)
|
||||
print(f'processing: {p}\nhash: {i}')
|
||||
self.files[i] = media_file.media
|
||||
|
||||
def find_files(self):
|
||||
""" find files to build a dictionary out of """
|
||||
self.files = {}
|
||||
|
||||
self.set_total_files(0)
|
||||
self.get_event()
|
||||
self.get_search_types()
|
||||
self.file_total = self.get_t_files()
|
||||
self.set_total_files(self.file_total)
|
||||
|
||||
file_finder = FindFiles()
|
||||
time.sleep(3)
|
||||
|
||||
file_finder = FindFiles(search_types=self.search_types,
|
||||
src_dir=self.src_dir,
|
||||
file_types=self.file_types,
|
||||
file_total=self.file_total)
|
||||
worker = Worker(file_finder.t_find_files)
|
||||
|
||||
worker.signals.started.connect(
|
||||
|
@ -270,10 +358,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self.find_files_dialog.set_progress_finding_files)
|
||||
worker.signals.found_file.connect(
|
||||
self.add_found_file_to_list)
|
||||
worker.signals.found_file.connect(
|
||||
self.process_file)
|
||||
worker.signals.total_file_count.connect(
|
||||
self.set_total_files)
|
||||
worker.signals.result.connect(
|
||||
self.gen_file_dict)
|
||||
# worker.signals.result.connect(
|
||||
# self.gen_file_dict)
|
||||
worker.signals.finished.connect(
|
||||
self.thread_complete)
|
||||
worker.signals.finished.connect(
|
||||
|
@ -284,6 +374,108 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
# Execute.
|
||||
self.threadpool.start(worker)
|
||||
|
||||
# From _media_import.py
|
||||
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))
|
||||
|
||||
# END from _media_import.py
|
||||
|
||||
def import_files(self):
|
||||
"""
|
||||
Import found files
|
||||
|
@ -294,23 +486,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
|
||||
self.imp_dialog.set_progress_importing(0)
|
||||
self.imp_dialog.set_progress_current_file(0)
|
||||
|
||||
importer = MediaImporter()
|
||||
|
||||
worker = Worker(importer.t_copy_files)
|
||||
|
||||
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.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(
|
||||
|
@ -323,8 +509,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
|
||||
def verify_checksum(self):
|
||||
# fh_match = FileHash()
|
||||
print(self.config)
|
||||
|
||||
print(f'verify_checksum,self.config: {self.config}')
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
|
|
47
__model.py
47
__model.py
|
@ -0,0 +1,47 @@
|
|||
m = {
|
||||
'f5c7c1a4e55d6288': {
|
||||
'date': {
|
||||
'capture_date': {
|
||||
'y': '2024',
|
||||
'm': '09',
|
||||
'd': '08'
|
||||
}
|
||||
},
|
||||
'event': {
|
||||
'name': ''
|
||||
},
|
||||
'extension': 'JPG',
|
||||
'folders': {
|
||||
'destination': '/Users/kkenny/import/testing_dst/2024/2024-09/2024-09-08/PHOTO/JPG',
|
||||
'destination_original': '',
|
||||
'source_path': '/Users/kkenny/import/testing_src'
|
||||
},
|
||||
'name': 'DSC_6481.JPG',
|
||||
'source_path_hash': 'f5c7c1a4e55d6288',
|
||||
'type': 'image',
|
||||
'image_meta': {
|
||||
'photo': {
|
||||
'aperture': (0x829D) Ratio = 11 @ 736,
|
||||
'camera_brand': (0x010F)ASCII = NIKON CORPORATION @ 148,
|
||||
'camera_model': (0x0110)ASCII = NIKON D5100 @ 168,
|
||||
'dpi': < bound method PhotoFile.get_dpi of < _photo.PhotoFile object at 0x130f05fd0 >>,
|
||||
'is_jpg': True,
|
||||
'is_raw': False,
|
||||
'iso': (0x8827) Short = 3200 @ 274,
|
||||
'lens_make': None,
|
||||
'lens_model': None,
|
||||
'lens_focal_length': (0x920A) Ratio = 135 @ 808,
|
||||
'size': {
|
||||
'height': 3264,
|
||||
'width': 4928,
|
||||
'width_height': '4928x3264',
|
||||
'megapixels': 16084992,
|
||||
'ratio': 1.5098039215686274
|
||||
}
|
||||
}
|
||||
},
|
||||
'video_meta': None,
|
||||
'audio_meta': None
|
||||
}
|
||||
}
|
||||
|
95
_audio.py
95
_audio.py
|
@ -1,76 +1,61 @@
|
|||
#!/usr/bin/env python
|
||||
import os.path
|
||||
import ffmpeg
|
||||
import time
|
||||
from datetime import datetime
|
||||
from _time_and_date_utils import convert_from_seconds
|
||||
|
||||
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']
|
||||
class AudioFile:
|
||||
def __init__(self,path_file_name,*args,**kwargs):
|
||||
# super(AudioFile, self).__init__(*args, **kwargs)
|
||||
self.path_file_name = path_file_name
|
||||
self.probe = ffmpeg.probe(self.path_file_name)
|
||||
self.audio_capture_date = self.get_audio_capture_date()
|
||||
self.video_stream = None
|
||||
self.audio_stream = None
|
||||
self.format_stream = None
|
||||
|
||||
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']
|
||||
for i in self.probe['streams']:
|
||||
if self.video_stream is None:
|
||||
if 'video' == i['codec_type'].lower():
|
||||
self.video_stream = i
|
||||
if self.audio_stream is None:
|
||||
if 'audio' == i['codec_type'].lower():
|
||||
self.audio_stream = i
|
||||
try:
|
||||
self.format_stream = self.probe['format']
|
||||
except AttributeError as e:
|
||||
print(f"{e}: Audio file has no format string")
|
||||
self.format_stream = None
|
||||
self.stream = {}
|
||||
|
||||
@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:
|
||||
stamp = None
|
||||
if self.format_stream is not None:
|
||||
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'
|
||||
)
|
||||
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):
|
||||
def get_audio_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': {
|
||||
'audio_channels': self.audio_stream['channels'],
|
||||
'bits_per_sample': self.audio_stream['bits_per_raw_sample'],
|
||||
'codec_long_name': self.audio_stream['codec_long_name'],
|
||||
'duration': convert_from_seconds(float(self.audio_stream['duration'])),
|
||||
'encoding_brand': self.format_stream['tags']['encoded_by'],
|
||||
'sample_rate': self.audio_stream['sample_rate']
|
||||
},
|
||||
'audio': {},
|
||||
'video': {},
|
||||
'format': {}
|
||||
}
|
||||
|
||||
|
|
|
@ -96,3 +96,11 @@ def validate_config_dir_access(config):
|
|||
accessible = True
|
||||
return accessible
|
||||
|
||||
def get_file_name(path_file_name):
|
||||
return os.path.basename(path_file_name)
|
||||
|
||||
def get_dotted_file_ext(file_name):
|
||||
return os.path.splitext(file_name)[1]
|
||||
|
||||
def get_file_ext(file_name):
|
||||
return get_dotted_file_ext(file_name).split('.')[1]
|
|
@ -1,84 +1,51 @@
|
|||
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']
|
||||
class FindFiles:
|
||||
def __init__(self,
|
||||
search_types,
|
||||
src_dir,
|
||||
file_total,
|
||||
file_types,
|
||||
*args,
|
||||
**kwargs):
|
||||
super(FindFiles,self).__init__(*args,**kwargs)
|
||||
|
||||
self.file_total = 0
|
||||
self.file_list = {}
|
||||
self.file_total = file_total
|
||||
self.src_dir = src_dir
|
||||
self.search_types = search_types
|
||||
self.file_types = file_types
|
||||
|
||||
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.")
|
||||
self.file_name = None
|
||||
self.path_file_source = None
|
||||
self.file_type = None
|
||||
self.file_ext = None
|
||||
|
||||
def t_find_files(self,
|
||||
progress_callback,
|
||||
found_file,
|
||||
total_file_count):
|
||||
current_file_progress_callback,
|
||||
imported_file_count_callback,
|
||||
found_file_callback,
|
||||
total_file_count_callback):
|
||||
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):
|
||||
for self.file_type in self.search_types:
|
||||
for self.file_ext in self.file_types[self.file_type]:
|
||||
for self.file_name in filename:
|
||||
if self.file_name.lower().endswith(self.file_ext):
|
||||
self.path_file_source = os.path.join(folder, self.file_name)
|
||||
if is_file(self.path_file_source):
|
||||
file_count += int(1)
|
||||
self.process_file(current_file)
|
||||
found_file.emit(current_file)
|
||||
# process_file(current_file)
|
||||
found_file_callback.emit(self.path_file_source)
|
||||
else:
|
||||
print(f"Skipping {current_file} as it does not look like a real file.")
|
||||
total_file_count.emit(file_count)
|
||||
print(f"Skipping {self.path_file_source} as it does not look like a real file.")
|
||||
total_file_count_callback.emit(file_count)
|
||||
progress_callback.emit(round((file_count / self.file_total) * 100, 0))
|
||||
else:
|
||||
print("Nothing to search for.")
|
||||
|
||||
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()
|
|
@ -58,5 +58,5 @@ def validate_xx_checksums(f):
|
|||
print(f'FATAL: Checksum validation failed for: \
|
||||
{f[file]["name"]} \n{c[i]}\n is not equal to \n{c[p]}\n')
|
||||
print('\n File Meta:\n')
|
||||
print(f[file])
|
||||
print(f'f[file]: {f[file]}')
|
||||
i = i + 1
|
|
@ -1,14 +1,12 @@
|
|||
import exifread
|
||||
from datetime import datetime
|
||||
from _media_file import MediaFile
|
||||
from _time_and_date_utils import get_img_date
|
||||
|
||||
class ImageTag(MediaFile):
|
||||
def __init__(self,path_file_name=None,*args,**kwargs):
|
||||
class ImageTag:
|
||||
def __init__(self,path_file_name,*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.path_file_name = path_file_name
|
||||
self.image_tags = self.get_image_tags()
|
||||
self.date_time_original = get_img_date(self.image_tags)
|
||||
|
||||
def get_image_tag(self,t):
|
||||
tag_data = None
|
||||
|
@ -18,44 +16,8 @@ class ImageTag(MediaFile):
|
|||
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):
|
||||
print(f'get_image_tags, self.path_file_name: {self.path_file_name}')
|
||||
with open(self.path_file_name,'rb') as f:
|
||||
tags = exifread.process_file(f)
|
||||
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
|
||||
return tags
|
|
@ -2,56 +2,50 @@ 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 _file_stuff import (get_file_name,
|
||||
get_file_ext,
|
||||
get_dotted_file_ext)
|
||||
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
|
||||
class MediaFile:
|
||||
def __init__(self,
|
||||
path_file_name,
|
||||
config,
|
||||
event_name,
|
||||
*args,**kwargs):
|
||||
# super(MediaFile,self).__init__(*args,**kwargs)
|
||||
self.path_file_name = path_file_name
|
||||
self.config = config
|
||||
self.event_name = str(event_name)
|
||||
self.store_originals = self.config['store_originals']
|
||||
self.src_dir = self.config['folders']['source']['base']
|
||||
self.base_dst_dir = self.config['folders']['destination']['base']
|
||||
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_name = get_file_name(self.path_file_name)
|
||||
self.dotted_file_ext = get_dotted_file_ext(self.file_name)
|
||||
self.file_ext = get_file_ext(self.file_name)
|
||||
self.file_type = self.get_file_type()
|
||||
self.capture_date = self.get_capture_date()
|
||||
self.capture_date_year = self.capture_date[0]
|
||||
self.capture_date_month = self.capture_date[1]
|
||||
self.capture_Date_day = self.capture_date[2]
|
||||
self.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()
|
||||
self.media_meta = self.get_media_meta()
|
||||
|
||||
def get_file_name(self):
|
||||
return os.path.basename(self.path_file_name)
|
||||
# video_media = VideoFile(path_file_name=self.path_file_name)
|
||||
# audio_media = AudioFile(path_file_name=self.path_file_name)
|
||||
# photo_media = PhotoFile(path_file_name=self.path_file_name)
|
||||
|
||||
def get_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):
|
||||
def get_file_type(self):
|
||||
""" 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):
|
||||
if self.file_ext.lower().endswith(e.lower()):
|
||||
return t
|
||||
|
||||
def set_destination_path(self):
|
||||
|
@ -62,7 +56,7 @@ class MediaFile(MainWindow):
|
|||
"""
|
||||
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}'
|
||||
p3 = f'{self.capture_date_year}-{self.capture_date_month}-{self.capture_date_day}'
|
||||
|
||||
p = f'{p1}/{p2}/{p3}' # <--- Dumb.
|
||||
if self.event_name:
|
||||
|
@ -95,12 +89,16 @@ class MediaFile(MainWindow):
|
|||
|
||||
def get_capture_date(self):
|
||||
""" get capture date from meta """
|
||||
|
||||
if self.file_type == 'image':
|
||||
stamp = self.photo_media.photo_capture_date
|
||||
photo_media = PhotoFile(path_file_name=self.path_file_name)
|
||||
stamp = photo_media.photo_capture_date
|
||||
elif self.file_type == 'video':
|
||||
stamp = self.video_media.get_video_capture_date()
|
||||
video_media = VideoFile(path_file_name=self.path_file_name)
|
||||
stamp = video_media.get_video_capture_date()
|
||||
elif self.file_type == 'audio':
|
||||
stamp = self.audio_media.get_audio_capture_date()
|
||||
audio_media = AudioFile(path_file_name=self.path_file_name)
|
||||
stamp = audio_media.get_audio_capture_date()
|
||||
else:
|
||||
try:
|
||||
stamp = datetime.fromtimestamp(
|
||||
|
@ -117,13 +115,13 @@ class MediaFile(MainWindow):
|
|||
day = stamp.strftime("%d")
|
||||
return year, month, day
|
||||
|
||||
def media_meta(self):
|
||||
def get_media_meta(self):
|
||||
self.media = {
|
||||
'date': {
|
||||
'capture_date': {
|
||||
'y': self.capture_date_year,
|
||||
'm': self.capture_date_month,
|
||||
'd': self.capture_Date_day
|
||||
'd': self.capture_date_day
|
||||
}
|
||||
},
|
||||
'event': {
|
||||
|
@ -137,16 +135,26 @@ class MediaFile(MainWindow):
|
|||
},
|
||||
'name': self.file_name,
|
||||
'source_path_hash': self.source_path_hash,
|
||||
'type': self.file_type
|
||||
'file_type': self.file_type
|
||||
}
|
||||
|
||||
if self.file_type == 'video':
|
||||
self.media['video_meta'] = self.video_media.get_video_meta()
|
||||
video_media = VideoFile(path_file_name=self.path_file_name)
|
||||
self.media['video_meta'] = video_media.video_meta()
|
||||
self.media['image_meta'] = None
|
||||
self.media['audio_meta'] = None
|
||||
|
||||
if self.file_type == 'image':
|
||||
photo = PhotoFile()
|
||||
self.media['image_meta'] = photo.get_photo_meta()
|
||||
photo_media = PhotoFile(path_file_name=self.path_file_name)
|
||||
self.media['image_meta'] = photo_media.get_photo_meta()
|
||||
self.media['video_meta'] = None
|
||||
self.media['audio_meta'] = None
|
||||
self.media['audio_meta'] = None
|
||||
|
||||
if self.file_type == 'audio':
|
||||
audio_media = AudioFile(path_file_name=self.path_file_name)
|
||||
self.media['audio_meta'] = audio_media.get_audio_meta()
|
||||
self.media['image_meta'] = None
|
||||
self.media['video_meta'] = None
|
||||
|
||||
print(f'MediaFile, self.media: {self.media}')
|
||||
return self.media
|
100
_media_import.py
100
_media_import.py
|
@ -14,103 +14,3 @@ class MediaImporter(MainWindow):
|
|||
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))
|
48
_photo.py
48
_photo.py
|
@ -1,21 +1,22 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from _media_file import MediaFile
|
||||
from _raw_photo import get_raw_image_dimensions
|
||||
from _image_tag import ImageTag
|
||||
|
||||
# https://exiftool.org/TagNames/EXIF.html
|
||||
# https://exiftool.org/TagNames/Sony.html
|
||||
|
||||
class PhotoFile(MediaFile):
|
||||
def __init__(self,*args,**kwargs):
|
||||
class PhotoFile:
|
||||
def __init__(self,path_file_name,*args,**kwargs):
|
||||
super(PhotoFile, self).__init__(*args, **kwargs)
|
||||
self.path_file_name = path_file_name
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.image_tag = ImageTag()
|
||||
self.is_jpg = False
|
||||
self.is_raw = False
|
||||
self.img = None
|
||||
self.image_tag = ImageTag(path_file_name=self.path_file_name)
|
||||
self.is_jpg = self.is_photo_jpeg()
|
||||
self.is_raw = self.is_photo_raw()
|
||||
self.size_width = self.get_photo_width()
|
||||
self.size_height = self.get_photo_height()
|
||||
self.size = self.get_photo_size()
|
||||
|
@ -29,19 +30,20 @@ class PhotoFile(MediaFile):
|
|||
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'):
|
||||
if self.path_file_name.lower().endswith('jpg') \
|
||||
or self.path_file_name.lower().endswith('.jpeg'):
|
||||
self.is_raw = False
|
||||
self.is_jpg = True
|
||||
self.img = Image.open(self.path_file_name)
|
||||
else:
|
||||
self.is_raw = True
|
||||
self.is_jpg = False
|
||||
|
@ -54,10 +56,22 @@ class PhotoFile(MediaFile):
|
|||
return self.is_jpg
|
||||
|
||||
def get_photo_width(self):
|
||||
return self.img.width
|
||||
if self.is_jpg:
|
||||
width = self.img.width
|
||||
elif self.is_raw:
|
||||
width = get_raw_image_dimensions(self.path_file_name)[1]
|
||||
else:
|
||||
width = None
|
||||
return width
|
||||
|
||||
def get_photo_height(self):
|
||||
return self.img.height
|
||||
if self.is_jpg:
|
||||
height = self.img.height
|
||||
elif self.is_raw:
|
||||
height = get_raw_image_dimensions(self.path_file_name)[0]
|
||||
else:
|
||||
height = None
|
||||
return height
|
||||
|
||||
def get_photo_size(self):
|
||||
if self.size_width is None:
|
||||
|
@ -87,7 +101,7 @@ class PhotoFile(MediaFile):
|
|||
return self.image_tag.get_image_tag( 'iso')
|
||||
|
||||
def get_aperture(self):
|
||||
return self.image_tag.get_image_tag( 'fnumber')
|
||||
return self.image_tag.get_image_tag('fnumber')
|
||||
|
||||
def get_camera_brand(self):
|
||||
tag = self.image_tag.get_image_tag('Make')
|
||||
|
@ -122,8 +136,6 @@ class PhotoFile(MediaFile):
|
|||
def get_photographer(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def get_orientation(self):
|
||||
pass
|
||||
|
||||
|
@ -134,7 +146,7 @@ class PhotoFile(MediaFile):
|
|||
pass
|
||||
|
||||
def get_photo_meta(self):
|
||||
self.photo_meta = {
|
||||
photo_meta = {
|
||||
'photo': {
|
||||
'aperture': self.aperture,
|
||||
'camera_brand': self.camera_brand,
|
||||
|
@ -155,5 +167,5 @@ class PhotoFile(MediaFile):
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self.photo_meta
|
||||
|
||||
return photo_meta
|
40
_preview.py
40
_preview.py
|
@ -1,19 +1,21 @@
|
|||
#!/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
|
||||
from _hashing import hash_path
|
||||
|
||||
class MediaPreview(MainWindow):
|
||||
class MediaPreview:
|
||||
def __init__(self,path_file_name,media_files):
|
||||
super(MediaPreview, self).__init__()
|
||||
# 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.media_file = MediaFile(self.path_file_name)
|
||||
self.source_path_hash = hash_path(self.path_file_name)
|
||||
# print(f'_preview.py,MediaPreview:\n\tpath_file_name: {self.path_file_name}\n\thash: {self.source_path_hash}\n\tmedia_files_list: {self.media_files_list}\n')
|
||||
self.thumbnail = 'thumbnail.jpg'
|
||||
self.thumbnail_ratio = self.media_files_list[self.source_path_hash]['image_meta']['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':
|
||||
|
@ -21,10 +23,10 @@ class MediaPreview(MainWindow):
|
|||
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']
|
||||
self.mpixels = self.media_files_list[self.source_path_hash]['image_meta']['photo']['size']['megapixels']
|
||||
|
||||
def _img_preview(self):
|
||||
if self.media_files_list[self.source_path_hash]['photo']['is_jpg'] is True:
|
||||
if self.media_files_list[self.source_path_hash]['image_meta']['photo']['is_jpg'] is True:
|
||||
self._jpg_preview()
|
||||
else:
|
||||
self._raw_preview()
|
||||
|
@ -37,7 +39,7 @@ class MediaPreview(MainWindow):
|
|||
thumb_width = 500
|
||||
thumb_size = (
|
||||
thumb_width,
|
||||
int(thumb_width // self.media_files_list[self.source_path_hash]['photo']['size']['ratio'])
|
||||
int(thumb_width // self.media_files_list[self.source_path_hash]['image_meta']['photo']['size']['ratio'])
|
||||
)
|
||||
try:
|
||||
with Image.open(self.path_file_name) as img:
|
||||
|
@ -53,14 +55,14 @@ class MediaPreview(MainWindow):
|
|||
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.width = self.media_files_list[self.source_path_hash]['video_meta']['video']['size']['width']
|
||||
self.height = self.media_files_list[self.source_path_hash]['video_meta']['video']['size']['height']
|
||||
self.video_framerate = round(
|
||||
int(self.media_files_list[self.source_path_hash]['video']['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']
|
||||
int(self.media_files_list[self.source_path_hash]['video_meta']['video']['r_frame_rate'].split('/')[0]) /
|
||||
int(self.media_files_list[self.source_path_hash]['video_meta']['video']['r_frame_rate'].split('/')[1]),2)
|
||||
self.video_bit_depth = self.media_files_list[self.source_path_hash]['video_meta']['video']['bits_per_raw_sample']
|
||||
self.video_duration = self.media_files_list[self.source_path_hash]['video_meta']['video']['duration']
|
||||
self.video_encoding = self.media_files_list[self.source_path_hash]['video_meta']['video']['encoding_brand']
|
||||
self.video_codec = self.media_files_list[self.source_path_hash]['video_meta']['video']['codec_long_name']
|
||||
self.video_profile = self.media_files_list[self.source_path_hash]['video_meta']['video']['profile']
|
||||
self.video_pix_format = self.media_files_list[self.source_path_hash]['video_meta']['video']['pix_fmt']
|
|
@ -27,12 +27,10 @@ class WorkerSignals(QObject):
|
|||
error = pyqtSignal(tuple)
|
||||
result = pyqtSignal(object)
|
||||
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):
|
||||
"""
|
||||
|
@ -58,12 +56,10 @@ class Worker(QRunnable):
|
|||
|
||||
# Add the callback to our kwargs
|
||||
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
|
||||
self.kwargs['found_file_callback'] = self.signals.found_file
|
||||
self.kwargs['total_file_count_callback'] = self.signals.total_file_count
|
||||
|
||||
@pyqtSlot()
|
||||
def run(self):
|
||||
|
|
42
_video.py
42
_video.py
|
@ -5,33 +5,24 @@ import ffmpeg
|
|||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from _media_file import MediaFile
|
||||
|
||||
class VideoFile(MediaFile):
|
||||
def __init__(self,max_width=1024,*args,**kwargs):
|
||||
super(VideoFile, self).__init__(*args, **kwargs)
|
||||
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.file = kwargs['file']
|
||||
self.out = 'thumbnail.jpg'
|
||||
class VideoFile:
|
||||
def __init__(self,path_file_name,max_width=1024,*args,**kwargs):
|
||||
# super(VideoFile, self).__init__(*args, **kwargs)
|
||||
self.path_file_name = path_file_name
|
||||
self.max_width = max_width
|
||||
self.out = 'thumbnail.jpg'
|
||||
self.probe = ffmpeg.probe(self.path_file_name)
|
||||
self.video_capture_date = self.get_video_capture_date()
|
||||
self.video_stream = None
|
||||
self.audio_stream = None
|
||||
|
||||
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]
|
||||
for i in self.probe['streams']:
|
||||
if self.video_stream is None:
|
||||
if 'video' == i['codec_type'].lower():
|
||||
self.video_stream = i
|
||||
if self.audio_stream is None:
|
||||
if 'audio' == i['codec_type'].lower():
|
||||
self.audio_stream = i
|
||||
|
||||
self.format_stream = self.probe['format']
|
||||
self.size_width = self.get_video_width()
|
||||
|
@ -48,7 +39,6 @@ class VideoFile(MediaFile):
|
|||
"""
|
||||
Generate a thumbnail from a video
|
||||
"""
|
||||
self.get_video_meta()
|
||||
time_seconds = self.stream['video']['seconds'] // 5
|
||||
v_width = int(self.stream['video']['size']['width'])
|
||||
width = self.set_thumb_width(v_width)
|
||||
|
@ -56,7 +46,7 @@ class VideoFile(MediaFile):
|
|||
try:
|
||||
(
|
||||
ffmpeg.input(
|
||||
self.file,
|
||||
self.path_file_name,
|
||||
ss = time_seconds
|
||||
)
|
||||
.filter(
|
||||
|
@ -122,7 +112,7 @@ class VideoFile(MediaFile):
|
|||
)
|
||||
return stamp
|
||||
|
||||
def get_video_meta(self):
|
||||
def video_meta(self):
|
||||
self.stream = {
|
||||
'video': {
|
||||
'bits_per_raw_sample': self.video_stream['bits_per_raw_sample'],
|
||||
|
|
Loading…
Reference in New Issue