diff --git a/NFO.Editor.py b/NFO.Editor.py index b933d23..91a2dab 100644 --- a/NFO.Editor.py +++ b/NFO.Editor.py @@ -9,7 +9,7 @@ class NFOEditorApp: def __init__(self, root): self.root = root - self.root.title("大锤 v2.0.0 NFO Editor 20240629") + self.root.title("大锤 NFO Editor 20240630") self.current_file_path = None self.fields_entries = {} @@ -37,14 +37,6 @@ def __init__(self, root): image_toggle = tk.Checkbutton(top_frame, text="显示图片", variable=self.show_images_var, command=self.toggle_image_display) image_toggle.pack(side=tk.RIGHT, padx=5) - # 图片显示区域框架 - image_frame = tk.Frame(self.root, width=400, height=300, bg="gray") - image_frame.pack(side=tk.RIGHT, padx=10, pady=10) - image_frame.pack_propagate(0) - - self.image_label = tk.Label(image_frame, text="图片显示已关闭", bg="gray") - self.image_label.pack(expand=True) - # 创建排序选项 sorting_frame = tk.Frame(self.root) sorting_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5) @@ -61,19 +53,50 @@ def __init__(self, root): for text, value in sorting_options: tk.Radiobutton(sorting_frame, text=text, variable=self.sorting_var, value=value, command=self.sort_files).pack(side=tk.LEFT, padx=5) - # 创建文件列表框 - self.file_listbox = tk.Listbox(self.root, width=50, selectmode=tk.EXTENDED) + # 创建文件列表框和滚动条 + listbox_frame = tk.Frame(self.root) + listbox_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + self.file_listbox = tk.Listbox(listbox_frame, width=50, selectmode=tk.EXTENDED) self.file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) - scrollbar = tk.Scrollbar(self.root, orient=tk.VERTICAL) - scrollbar.pack(side=tk.LEFT, fill=tk.Y) + + scrollbar = tk.Scrollbar(listbox_frame, orient=tk.VERTICAL) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + self.file_listbox.config(yscrollcommand=scrollbar.set) scrollbar.config(command=self.file_listbox.yview) + self.file_listbox.bind('<>', self.on_file_select) # 创建字段编辑框架 self.fields_frame = tk.Frame(self.root, padx=10, pady=10) self.fields_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) + # 图片显示区域框架 + image_frame = tk.Frame(self.fields_frame) + image_frame.pack(anchor=tk.W, pady=10) + + image_label = tk.Label(image_frame, text="图片:", font=("Arial", 12, "bold")) + image_label.pack(side=tk.LEFT, padx=5, pady=5) + + poster_frame = tk.Frame(image_frame, width=165, height=225, bg="", highlightthickness=1, highlightbackground="black") + poster_frame.pack(side=tk.LEFT, padx=5) + poster_frame.pack_propagate(0) + + # 增加间距的空Frame + empty_frame = tk.Frame(image_frame, width=40, bg="") + empty_frame.pack(side=tk.LEFT) + + thumb_frame = tk.Frame(image_frame, width=333, height=225, bg="", highlightthickness=1, highlightbackground="black") + thumb_frame.pack(side=tk.LEFT, padx=5) + thumb_frame.pack_propagate(0) + + self.poster_label = tk.Label(poster_frame, text="封面图 (poster)", fg="black") # 设置文本颜色为黑色 + self.poster_label.pack(expand=True) + + self.thumb_label = tk.Label(thumb_frame, text="缩略图 (thumb)", fg="black") # 设置文本颜色为黑色 + self.thumb_label.pack(expand=True) + # 创建字段标签和输入框 self.create_field_labels() @@ -88,58 +111,79 @@ def __init__(self, root): def toggle_image_display(self): if self.show_images_var.get(): - self.image_label.config(text="") + self.poster_label.config(text="封面图 (poster)", fg="black") + self.thumb_label.config(text="缩略图 (thumb)", fg="black") self.display_image() else: - self.image_label.config(image="", text="图片显示已关闭") + self.poster_label.config(image="", text="封面图 (poster)", fg="black") + self.thumb_label.config(image="", text="缩略图 (thumb)", fg="black") def display_image(self): if self.current_file_path: folder = os.path.dirname(self.current_file_path) - image_files = [f for f in os.listdir(folder) if f.lower().endswith('.jpg') and 'thumb' in f.lower()] - if image_files: - image_path = os.path.join(folder, image_files[0]) - try: - img = Image.open(image_path) - img.thumbnail((400, 300), Image.LANCZOS) # 调整图片大小,保持比例 - img = ImageTk.PhotoImage(img) - self.image_label.config(image=img) - self.image_label.image = img # 保持引用防止图片被垃圾回收 - except Exception as e: - self.image_label.config(text="加载图片失败: " + str(e)) + poster_files = [f for f in os.listdir(folder) if f.lower().endswith('.jpg') and 'poster' in f.lower()] + thumb_files = [f for f in os.listdir(folder) if f.lower().endswith('.jpg') and 'thumb' in f.lower()] + + if poster_files: + self.load_image(poster_files[0], self.poster_label, (165, 225)) + else: + self.poster_label.config(text="文件夹内无poster图片", fg="black") + + if thumb_files: + self.load_image(thumb_files[0], self.thumb_label, (333, 225)) else: - self.image_label.config(text="文件夹内无thumb图片") + self.thumb_label.config(text="文件夹内无thumb图片", fg="black") + + def load_image(self, image_file, label, size): + folder = os.path.dirname(self.current_file_path) + image_path = os.path.join(folder, image_file) + try: + img = Image.open(image_path) + img.thumbnail(size, Image.LANCZOS) + img = ImageTk.PhotoImage(img) + label.config(image=img) + label.image = img # 保持引用防止图片被垃圾回收 + except Exception as e: + label.config(text="加载图片失败: " + str(e)) def create_field_labels(self): # 定义各字段的标签文本和高度 fields = { - 'title': ('标题 (Title)', 2), - 'plot': ('简介 (Plot)', 6), - 'actors': ('演员 (Actors)', 2), - 'series': ('系列 (Series)', 2), - 'tags': ('标签 (Tags)', 3), - 'genres': ('类别 (Genre)', 3), - 'rating': ('评分 (Rating)', 1) + 'title': ('标题', 2), + 'plot': ('简介', 5), + 'tags': ('标签', 3), + 'genres': ('类别', 3), + 'actors': ('演员', 1), + 'series': ('系列', 1), + 'rating': ('评分', 1) } # 创建标签和输入框,并存储到 fields_entries 中 - for row, (field, (label_text, height)) in enumerate(fields.items()): - label = tk.Label(self.fields_frame, text=label_text + ":", font=("Arial", 12, "bold")) - label.grid(row=row, column=0, sticky=tk.W, pady=5) - entry = tk.Text(self.fields_frame, width=60, height=height) - entry.grid(row=row, column=1, sticky=tk.W, pady=5) + for field, (label_text, height) in fields.items(): + frame = tk.Frame(self.fields_frame) + frame.pack(fill=tk.X) # 确保每个输入框在水平方向上填充父容器 + + label = tk.Label(frame, text=label_text + ":", font=("Arial", 12, "bold")) + label.pack(side=tk.LEFT, padx=5, pady=5, anchor=tk.W) # 左对齐标签 + + entry = tk.Text(frame, width=60, height=height) + entry.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True) + self.fields_entries[field] = entry def create_operations_panel(self): # 创建操作面板,包括保存更改按钮、批量替换按钮和保存时间标签 operations_frame = tk.Frame(self.fields_frame, padx=10, pady=10) - operations_frame.grid(row=len(self.fields_entries), column=0, columnspan=2, pady=10) + operations_frame.pack(fill=tk.X) - save_button = tk.Button(operations_frame, text="保存更改 (Save Changes)", command=self.save_changes) + save_button = tk.Button(operations_frame, text="保存更改 (Save Changes)", command=self.save_changes, width=25) save_button.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) - batch_replace_button = tk.Button(operations_frame, text="批量替换 (Batch Replace)", command=self.batch_replace) - batch_replace_button.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W) + batch_filling_button = tk.Button(operations_frame, text="批量填充 (Batch Filling)", command=self.batch_filling, width=25) + batch_filling_button.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W) + + batch_add_button = tk.Button(operations_frame, text="批量新增 (Batch Add)", command=self.batch_add, width=25) + batch_add_button.grid(row=0, column=2, padx=5, pady=5, sticky=tk.W) self.save_time_label = tk.Label(operations_frame, text="") self.save_time_label.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky=tk.W) @@ -319,19 +363,26 @@ def get_sort_key(nfo_file): try: tree = ET.parse(nfo_file) root = tree.getroot() + if sort_by == "actors": - actors = set() - for actor_elem in root.findall('actor'): - name_elem = actor_elem.find('name') - if name_elem is not None: - actors.add(name_elem.text) - return ', '.join(actors) + actors = {actor_elem.find('name').text.strip() for actor_elem in root.findall('actor') if actor_elem.find('name') is not None} + return ', '.join(sorted(actors)) if actors else "" + + elif sort_by == "series": + series_elem = root.find('series') + return series_elem.text.strip() if series_elem is not None and series_elem.text is not None else "" + + elif sort_by == "rating": + rating_elem = root.find('rating') + return rating_elem.text.strip() if rating_elem is not None and rating_elem.text is not None else "" + else: for child in root: - if child.tag == sort_by: - return child.text + if child.tag == sort_by and child.text is not None: + return child.text.strip() except ET.ParseError: - return "" + pass + return "" self.nfo_files.sort(key=get_sort_key) @@ -341,66 +392,128 @@ def get_sort_key(nfo_file): for nfo_file in self.nfo_files: self.file_listbox.insert(tk.END, os.path.relpath(nfo_file, self.folder_path)) - def batch_replace(self): - # 批量替换对话框逻辑 - def apply_replacement(): + def batch_filling(self): + # 批量填充对话框逻辑 + def apply_fill(): field = field_var.get() - original_text = original_entry.get() - new_text = new_entry.get() + fill_value = fill_entry.get() operation_log = "" - - if field and original_text and new_text: + + if field and fill_value: for nfo_file in self.nfo_files: try: tree = ET.parse(nfo_file) root = tree.getroot() - modified = False - - if field in ['series', 'rating']: - for child in root: - if child.tag == field and child.text == original_text: - child.text = new_text - modified = True - elif field == 'actors': - for actor_elem in root.findall('actor'): - name_elem = actor_elem.find('name') - if name_elem is not None and name_elem.text == original_text: - name_elem.text = new_text - modified = True - if modified: - xml_str = ET.tostring(root, encoding='utf-8') - parsed_str = minidom.parseString(xml_str) - pretty_str = parsed_str.toprettyxml(indent=" ", encoding='utf-8') - with open(nfo_file, 'wb') as file: - file.write(pretty_str) - operation_log += f"{nfo_file}: 修改成功\n" - else: - operation_log += f"{nfo_file}: 未找到匹配内容\n" + + # 如果字段不存在,则创建新的元素 + field_elem = root.find(field) + if field_elem is None: + field_elem = ET.Element(field) + root.append(field_elem) + + # 填充字段值 + field_elem.text = fill_value.strip() + + # 保存修改后的 XML 文件 + xml_str = ET.tostring(root, encoding='utf-8') + parsed_str = minidom.parseString(xml_str) + pretty_str = parsed_str.toprettyxml(indent=" ", encoding='utf-8') + + # 去除多余的空行 + pretty_lines = pretty_str.decode('utf-8').splitlines() + formatted_lines = [] + for line in pretty_lines: + if line.strip(): + formatted_lines.append(line) + formatted_str = "\n".join(formatted_lines) + + with open(nfo_file, 'w', encoding='utf-8') as file: + file.write(formatted_str) + + operation_log += f"{nfo_file}: {field}字段填充成功\n" except Exception as e: - operation_log += f"{nfo_file}: 修改失败 - {str(e)}\n" + operation_log += f"{nfo_file}: {field}字段填充失败 - {str(e)}\n" log_text.delete(1.0, tk.END) log_text.insert(1.0, operation_log) + # 创建批量填充对话框 dialog = Toplevel(self.root) - dialog.title("批量替换 (Batch Replace)") + dialog.title("批量填充 (Batch Fill)") dialog.geometry("400x300") - tk.Label(dialog, text="选择字段 (Field):").pack(pady=5) - field_var = tk.StringVar(value="title") - tk.Radiobutton(dialog, text="演员 (Actors)", variable=field_var, value="actors").pack(anchor=tk.W) + tk.Label(dialog, text="选择填充替换字段 (Select Field):").pack(pady=5) + field_var = tk.StringVar(value="series") tk.Radiobutton(dialog, text="系列 (Series)", variable=field_var, value="series").pack(anchor=tk.W) tk.Radiobutton(dialog, text="评分 (Rating)", variable=field_var, value="rating").pack(anchor=tk.W) - tk.Label(dialog, text="原内容 (Original Content):").pack(pady=5) - original_entry = tk.Entry(dialog, width=40) - original_entry.pack(pady=5) + tk.Label(dialog, text="填充替换值 (Fill Field Value):").pack(pady=5) + fill_entry = tk.Entry(dialog, width=40) + fill_entry.pack(pady=5) + + tk.Button(dialog, text="应用填充替换 (Apply Fill)", command=apply_fill).pack(pady=10) + + tk.Label(dialog, text="操作日志 (Operation Log):").pack(pady=5) + log_text = tk.Text(dialog, width=50, height=10) + log_text.pack(pady=5) + + # 添加批量新增功能的方法 batch_add + def batch_add(self): + # 批量新增对话框逻辑 + def apply_add(): + field = field_var.get() + add_value = add_entry.get() + operation_log = "" + + if field and add_value: + for nfo_file in self.nfo_files: + try: + tree = ET.parse(nfo_file) + root = tree.getroot() + + # 创建新的元素并添加新增值 + new_elem = ET.Element(field) + new_elem.text = add_value.strip() + root.append(new_elem) + + # 保存修改后的 XML 文件 + xml_str = ET.tostring(root, encoding='utf-8') + parsed_str = minidom.parseString(xml_str) + pretty_str = parsed_str.toprettyxml(indent=" ", encoding='utf-8') + + # 去除多余的空行 + pretty_lines = pretty_str.decode('utf-8').splitlines() + formatted_lines = [] + for line in pretty_lines: + if line.strip(): + formatted_lines.append(line) + formatted_str = "\n".join(formatted_lines) + + with open(nfo_file, 'w', encoding='utf-8') as file: + file.write(formatted_str) + + operation_log += f"{nfo_file}: {field}字段批量新增成功\n" + except Exception as e: + operation_log += f"{nfo_file}: {field}字段批量新增失败 - {str(e)}\n" + + log_text.delete(1.0, tk.END) + log_text.insert(1.0, operation_log) + + # 创建批量新增对话框 + dialog = Toplevel(self.root) + dialog.title("批量新增 (Batch Add)") + dialog.geometry("400x300") + + tk.Label(dialog, text="选择字段新增一个值 (Select Field):").pack(pady=5) + field_var = tk.StringVar(value="tag") + tk.Radiobutton(dialog, text="标签 (Tag)", variable=field_var, value="tag").pack(anchor=tk.W) + tk.Radiobutton(dialog, text="类型 (Genre)", variable=field_var, value="genre").pack(anchor=tk.W) - tk.Label(dialog, text="新内容 (New Content):").pack(pady=5) - new_entry = tk.Entry(dialog, width=40) - new_entry.pack(pady=5) + tk.Label(dialog, text="输入新增值 (Enter Value to Add):").pack(pady=5) + add_entry = tk.Entry(dialog, width=40) + add_entry.pack(pady=5) - tk.Button(dialog, text="应用 (Apply)", command=apply_replacement).pack(pady=10) + tk.Button(dialog, text="应用新增 (Apply Add)", command=apply_add).pack(pady=10) tk.Label(dialog, text="操作日志 (Operation Log):").pack(pady=5) log_text = tk.Text(dialog, width=50, height=10)