diff --git a/.gitignore b/.gitignore index 8d87b71..f377f05 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,10 @@ bin Packages Windows API Code Pack 1.1 obj -Windows API Code Pack 1.1.zip \ No newline at end of file +Windows API Code Pack 1.1.zip +.venv +.vscode +__pycache__ +*.whl +pyvenv.cfg +*suo \ No newline at end of file diff --git a/ImgGPS2KML.suo b/ImgGPS2KML.suo index d21a5f3..65627c6 100644 Binary files a/ImgGPS2KML.suo and b/ImgGPS2KML.suo differ diff --git a/自动归类/core.py b/自动归类/core.py new file mode 100644 index 0000000..c0b5dff --- /dev/null +++ b/自动归类/core.py @@ -0,0 +1,118 @@ +import os +import glob +from typing import List +from exif import Image +from datetime import datetime +import os +from PySide6.QtCore import QThread, Signal # 导入 QThread 和 Signal 类 + + +# import shutil +def get_jpg_files(file_path: str): + """获取当前目录下所有.jpg文件的完整路径""" + current_dir = os.path.abspath(file_path) + return glob.glob(os.path.join(current_dir, "*.jpg")) + + +def get_datetime_from_image_exif(image_path: str): + with open(image_path, "rb") as image_f: + my_image = Image(image_f) + if hasattr(my_image, "datetime_original"): + return my_image.datetime_original + else: + print(my_image.list_all()) + raise Exception(f"{image_path}该图片没有时间信息") + + +def convert_to_unix_timestamp(date_time_str): + # 将字符串解析为 datetime 对象 + date_time_obj = datetime.strptime(date_time_str, "%Y:%m:%d %H:%M:%S") + + # 将 datetime 对象转换为 Unix 时间戳 + unix_timestamp = date_time_obj.timestamp() + + return unix_timestamp + + +# 把时间戳相差在2分钟以内的分为一组 +def group_datetime(image_with_datetime: list): + """把时间戳相差在2分钟以内的分为一组""" + image_with_datetime.sort(key=lambda x: x[1]) + grouped_images = [] + current_group = [] + + for i in range(len(image_with_datetime)): + if i == 0: + current_group.append(image_with_datetime[i]) + else: + # 计算当前图像与前一个图像的时间差 + time_diff = image_with_datetime[i][1] - image_with_datetime[i - 1][1] + if time_diff <= 120: # 2分钟 = 120秒 + current_group.append(image_with_datetime[i]) + else: + grouped_images.append(current_group) + current_group = [image_with_datetime[i]] + + # 添加最后一组 + grouped_images.append(current_group) + return grouped_images + + +def get_file_name_before_bracket(file_path): + # 获取文件名 + file_name = os.path.basename(file_path) + + # 查找括号的位置 + bracket_index = file_name.find("(") + + # 如果找到了括号,返回括号前的部分 + if bracket_index != -1: + return file_name[:bracket_index] + else: + # 如果没有找到括号,返回整个文件名 + return file_name + + +class Worker(QThread): + progress = Signal(int) # 定义一个信号,用于传递进度 + + def __init__(self, images_path: List[str], start_index: int = 0): + super().__init__() + self.images_path = images_path + self.start_index = start_index + + def run(self): + if len(self.images_path) == 0: + return + # 以下是接需要设置的参数 + # image_path = r"D:/现场查看/" + # 提取目录路径 + new_dir_location = os.path.join(os.path.dirname(self.images_path[0]), "整理后") + # start_index = 156 # 目录编号 + # 以下是实际代码 + # all_images = get_jpg_files(image_path) + all_images = self.images_path + image_with_datetime = [] + for image_path in all_images: + image_datetime = get_datetime_from_image_exif(image_path) + image_with_datetime.append( + (image_path, convert_to_unix_timestamp(image_datetime)) + ) # 第一个是图片路径,第二个是时间戳 + grouped_image = group_datetime(image_with_datetime) + # total_files = sum(len(group) for group in grouped_image) + processed_files = 0 + for group in grouped_image: + dir_name = get_file_name_before_bracket(group[0][0]) + new_dir_name = f"{new_dir_location}/{self.start_index}.{dir_name}" + os.makedirs(new_dir_name, exist_ok=True) + for image in group: + processed_files += 1 + self.progress.emit(processed_files) # 发射进度信号 + new_file_path_name = new_dir_name.replace("/", "//") + os.system( + f'copy "{os.path.normpath(image[0])}" "{os.path.normpath(new_file_path_name)}" /Y' + ) + # shutil.copy(image[0], destination_path) + print(f"copy {image[0]} {new_file_path_name}") + # print(group) + self.start_index += 1 diff --git a/自动归类/main.py b/自动归类/main.py new file mode 100644 index 0000000..fe42d4a --- /dev/null +++ b/自动归类/main.py @@ -0,0 +1,12 @@ +from ui import FolderSelectorApp + +from PySide6.QtWidgets import ( + QApplication, +) +import sys + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = FolderSelectorApp() + window.show() + app.exec() diff --git a/自动归类/ui.py b/自动归类/ui.py new file mode 100644 index 0000000..8bb29ab --- /dev/null +++ b/自动归类/ui.py @@ -0,0 +1,194 @@ +from PySide6.QtWidgets import ( + QApplication, + QFileDialog, + QMainWindow, + QPushButton, + QVBoxLayout, + QWidget, + QListView, + QAbstractItemView, + QTreeView, + QTableView, + QLabel, + QLineEdit, + QHBoxLayout, + QMessageBox, + QProgressBar, # 导入 QProgressBar 类 +) +from PySide6.QtWidgets import QHeaderView +from PySide6.QtGui import QStandardItemModel, QStandardItem +from PySide6.QtCore import Slot # 导入 Slot 装饰器 +import core + + +class FolderSelectorApp(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("照片整理") + self.setGeometry(100, 100, 600, 400) + + # 创建按钮 + # TODO: 暂时不用选择文件夹按钮 + self.select_folders_button = QPushButton("选择多个文件夹", None) + self.select_folders_button.clicked.connect(self.select_folders) + + self.select_files_button = QPushButton("选择多个文件", self) + self.select_files_button.clicked.connect(self.select_files) + + # 整理照片按钮 + self.arrage_button = QPushButton("开始整理", self) + self.arrage_button.clicked.connect(self.arrage_pic) + + # 创建文件开始计数 + self.start_index_label = QLabel("目录开始基数:", self) + self.start_index_text = QLineEdit(self) + self.start_index_text.setText("0") + + # 创建QTableView + self.table_view = QTableView(self) + self.model = QStandardItemModel(0, 2, self) + self.model.setHorizontalHeaderLabels(["文件夹名称", "路径"]) + self.table_view.setModel(self.model) + self.table_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed) + self.table_view.setColumnWidth(0, int(self.width() * 0.3)) + self.table_view.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) + + # 创建进度条 + self.progress_bar = QProgressBar(self) + self.progress_bar.setValue(0) + + # 布局 + # 设置计数的布局 + start_index_layout = QHBoxLayout() + start_index_layout.addWidget(self.start_index_label) + start_index_layout.addWidget(self.start_index_text) + start_index_layout.addStretch(2) + # 选择文件的布局 + layout = QVBoxLayout() + # layout.addWidget(self.select_folders_button) + layout.addLayout(start_index_layout) + layout.addWidget(self.select_files_button) + layout.addWidget(self.arrage_button) + layout.addWidget(self.table_view) + layout.addWidget(self.progress_bar) # 添加进度条到布局 + + container = QWidget(self) + # container.setLayout(start_index_layout) + container.setLayout(layout) + + # container.layout.addLayout(start_index_layout) + self.setCentralWidget(container) + + self.worker = None # 初始化 worker 为 None + + def select_folders(self): + # 打开文件夹选择对话框,允许多选 + dialog = QFileDialog(self) + dialog.setWindowTitle("请选择多个文件夹") + dialog.setFileMode(QFileDialog.Directory) # 设置为目录选择模式 + dialog.setOption( + QFileDialog.DontUseNativeDialog, True + ) # 使用Qt对话框以支持多选 + dialog.setOption(QFileDialog.ShowDirsOnly, True) # 只显示文件夹 + dialog.setOption(QFileDialog.DontResolveSymlinks, True) # 不解析符号链接 + + # 设置列表视图和树视图为多选模式 + for view in dialog.findChildren(QListView) + dialog.findChildren(QTreeView): + view.setSelectionMode(QAbstractItemView.ExtendedSelection) + # 设置样式表,使选中项的背景色为蓝色 + view.setStyleSheet( + """QAbstractItemView::item:selected { + background-color: #0078D7; + color: white; + } + QAbstractItemView::item:selected:!active { + background-color: #CCE8FF; + color: black; + }""" + ) + + if dialog.exec(): + folder_paths = dialog.selectedFiles() # 获取选择的文件夹路径 + print("选择的文件夹路径:", folder_paths) + + # 清空当前模型 + self.model.removeRows(0, self.model.rowCount()) + + # 添加新的文件夹路径到模型 + for path in folder_paths: + folder_name = ( + path.split("/")[-1] if "/" in path else path.split("\\")[-1] + ) + name_item = QStandardItem(folder_name) + path_item = QStandardItem(path) + self.model.appendRow([name_item, path_item]) + + @Slot(int) # 使用 Slot 装饰器 + def update_progress(self, value): + """更新进度条的值""" + self.progress_bar.setValue(value) + + def arrage_pic(self): + # 获取table_view中第一列的数据 + name_list = [] + for row in range(self.model.rowCount()): + item = self.model.item(row, 1) + if item is not None: + name_list.append(item.text()) + if len(name_list) == 0: + QMessageBox.warning(self, "警告", "请选择文件!") + return + + # 设置进度条的最大值 + self.progress_bar.setMaximum(len(name_list)) + self.progress_bar.setValue(0) + + # 创建 Worker 实例 + self.worker = core.Worker( + name_list, start_index=int(self.start_index_text.text()) + ) + self.worker.progress.connect(self.update_progress) # 连接信号到槽函数 + self.worker.finished.connect(self.worker.deleteLater) # 任务完成后删除 worker + self.worker.start() # 启动线程 + + def resizeEvent(self, event): + super().resizeEvent(event) + self.table_view.setColumnWidth(0, int(self.width() * 0.3)) + + def select_files(self): + # 打开文件选择对话框,允许多选 + dialog = QFileDialog(self) + dialog.setWindowTitle("请选择多个文件") + dialog.setFileMode(QFileDialog.ExistingFiles) # 设置为文件选择模式 + dialog.setOption( + QFileDialog.DontUseNativeDialog, False + ) # 使用Qt对话框以支持多选 + + # 设置列表视图和树视图为多选模式 + for view in dialog.findChildren(QListView) + dialog.findChildren(QTreeView): + view.setSelectionMode(QAbstractItemView.ExtendedSelection) + # 设置样式表,使选中项的背景色为蓝色 + view.setStyleSheet( + """QAbstractItemView::item:selected { + background-color: #0078D7; + color: white; + } + QAbstractItemView::item:selected:!active { + background-color: #CCE8FF; + color: black; + }""" + ) + + if dialog.exec(): + file_paths = dialog.selectedFiles() # 获取选择的文件路径 + print("选择的文件路径:", file_paths) + + # 清空当前模型 + self.model.removeRows(0, self.model.rowCount()) + + # 添加新的文件路径到模型 + for path in file_paths: + file_name = path.split("/")[-1] if "/" in path else path.split("\\")[-1] + name_item = QStandardItem(file_name) + path_item = QStandardItem(path) + self.model.appendRow([name_item, path_item])