import sys import os import subprocess import stat import tkinter as tk from tkinter import ttk from tkinter import filedialog # Set path of MultiPar client_path = "../par2j64.exe" gui_path = "../MultiPar.exe" # Set options for par2j # Because /fe option is set to exclude .PAR2 files by default, no need to set here. # Don't use /ss, /sn, /sr, or /sm here. cmd_option = "/rr10 /rd2" # How to set slices initially (either /ss, /sn, or /sr) init_slice_option = "/sr10" # Optimization settings. Refer "each_folder.py" for detail. slice_size_multiplier = 4096 max_slice_count = 20000 max_slice_rate = 170 min_slice_count = 100 min_slice_rate = 30 min_efficiency_improvement = 0.3 # Initialize global variables current_dir = "./" sub_proc = None # Read "Efficiency rate" def read_efficiency(output_text): # Find from the last line_start = output_text.rfind("Efficiency rate\t\t:") if line_start != -1: line_start += 19 line_end = output_text.find("%\n", line_start) return float(output_text[line_start:line_end]) else: return -1 # Read "Input File Slice count" def read_slice_count(output_text): # Find from the top line_start = output_text.find("Input File Slice count\t:") if line_start != -1: line_start += 25 line_end = output_text.find("\n", line_start) return int(output_text[line_start:line_end]) else: return -1 # Read "Input File Slice size" def read_slice_size(output_text): # Find from the top line_start = output_text.find("Input File Slice size\t:") if line_start != -1: line_start += 24 line_end = output_text.find("\n", line_start) return int(output_text[line_start:line_end]) else: return -1 # Search setting of good efficiency def test_efficiency(par_path, source_path): min_size = 0 max_size = 0 best_count = 0 best_size = 0 best_efficiency = 0 best_efficiency_at_initial_count = 0 best_count_at_max_count = 0 best_size_at_max_count = 0 best_efficiency_at_max_count = 0 # First time to get initial value cmd = "\"" + client_path + "\" t /uo /fe\"**.par2\" " + init_slice_option + " /sm" + str(slice_size_multiplier) + " " + cmd_option + " \"" + par_path + "\" \"" + source_path + "\"" res = subprocess.run(cmd, shell=True, capture_output=True, encoding='utf8') if res.returncode != 0: return 0 efficiency_rate = read_efficiency(res.stdout) if efficiency_rate < 0: return 0 initial_count = read_slice_count(res.stdout) if initial_count <= 0: return 0 initial_size = read_slice_size(res.stdout) best_efficiency_at_initial_count = efficiency_rate #print("initial_size =", initial_size, ", initial_count =", initial_count, ", efficiency =", efficiency_rate) # Get min and max of slice count and size to be used at searching # maximum slice count is co-related to minimum slice size if max_slice_rate != 0: if initial_count > max_slice_count: if (initial_count * max_slice_rate / 100) > 32768: max_count = 32768 else: max_count = int(initial_count * max_slice_rate / 100) else: if (initial_count * max_slice_rate / 100) > max_slice_count: max_count = max_slice_count else: max_count = int(initial_count * max_slice_rate / 100) else: max_count = 32768 cmd = "\"" + client_path + "\" t /uo /fe\"**.par2\" /sn" + str(max_count) + " /sm" + str(slice_size_multiplier) + " " + cmd_option + " \"" + par_path + "\" \"" + source_path + "\"" res = subprocess.run(cmd, shell=True, capture_output=True, encoding='utf8') if res.returncode != 0: return 0 efficiency_rate = read_efficiency(res.stdout) if efficiency_rate < 0: return 0 best_count_at_max_count = read_slice_count(res.stdout) best_size_at_max_count = read_slice_size(res.stdout) best_efficiency_at_max_count = efficiency_rate max_count = read_slice_count(res.stdout) min_size = read_slice_size(res.stdout) #print("max_count =", max_count, ", min_size =", min_size, ", efficiency =", best_efficiency_at_max_count) # Minimum slice count is co-related to maximum slice size if min_slice_rate > 0 and (initial_count * min_slice_rate / 100) > min_slice_count: min_count = int(initial_count * min_slice_rate / 100) else: min_count = min_slice_count cmd = "\"" + client_path + "\" t /uo /fe\"**.par2\" /sn" + str(min_count) + " /sm" + str(slice_size_multiplier) + " " + cmd_option + " \"" + par_path + "\" \"" + source_path + "\"" res = subprocess.run(cmd, shell=True, capture_output=True, encoding='utf8') if res.returncode != 0: return 0 efficiency_rate = read_efficiency(res.stdout) if efficiency_rate < 0: return 0 min_count = read_slice_count(res.stdout) max_size = read_slice_size(res.stdout) best_count = read_slice_count(res.stdout) best_size = read_slice_size(res.stdout) best_efficiency = efficiency_rate #print("min_count =", min_count, ", max_size =", max_size, ", efficiency =", best_efficiency) # If the calculated maximum slice count is too small, no need to search (QUITE UNLIKELY to happen) if max_slice_rate > 0 and (initial_count * max_slice_rate / 100) <= min_slice_count: cmd = "\"" + client_path + "\" t /uo /fe\"**.par2\" /sn" + str(min_slice_count) + " /sm" + str(slice_size_multiplier) + " " + cmd_option + " \"" + par_path + "\" \"" + source_path + "\"" res = subprocess.run(cmd, shell=True, capture_output=True, encoding='utf8') if res.returncode != 0: return 0 efficiency_rate = read_efficiency(res.stdout) if efficiency_rate < 0: return 0 best_count = read_slice_count(res.stdout) best_size = read_slice_size(res.stdout) best_efficiency = efficiency_rate #print("initial_count too small, best_count =", best_count, ", best_size =", best_size, ", best_efficiency =", best_efficiency) # Return slice size to archive the best efficiency return best_size else: # Try every (step) slice count between min_count and max_count step_slice_count_int = int((min_count + 1) * 8 / 7) while step_slice_count_int < max_count: #print(f"Testing slice count: (around) {step_slice_count_int}, from {(step_slice_count_int - int(step_slice_count_int / 8))} to {int(step_slice_count_int * 17 / 16)}") cmd = "\"" + client_path + "\" t /uo /fe\"**.par2\" /sn" + str(step_slice_count_int) + " /sm" + str(slice_size_multiplier) + " " + cmd_option + " \"" + par_path + "\" \"" + source_path + "\"" res = subprocess.run(cmd, shell=True, capture_output=True, encoding='utf8') if res.returncode != 0: break efficiency_rate = read_efficiency(res.stdout) if efficiency_rate < 0: break if efficiency_rate > best_efficiency + min_efficiency_improvement: best_count = read_slice_count(res.stdout) best_size = read_slice_size(res.stdout) best_efficiency = efficiency_rate # Next count should be more than 17/16 of the input count. (Range to +6.25% was checked already.) step_slice_count_int = int((int(step_slice_count_int * 17 / 16) + 1) * 8 / 7) # Evaluate slice count searched with initial_count if initial_count < best_count and best_efficiency_at_initial_count > best_efficiency - min_efficiency_improvement: best_count = initial_count best_size = initial_size best_efficiency = best_efficiency_at_initial_count # Evaluate slice count searched with max_count. if best_efficiency_at_max_count > best_efficiency + min_efficiency_improvement: best_count = best_count_at_max_count best_size = best_size_at_max_count best_efficiency = best_efficiency_at_max_count #print("best_count =", best_count, "best_size =", best_size, ", best_efficiency =", best_efficiency) return best_size # Return zero for empty folder def check_empty(path='.'): total = 0 with os.scandir(path) as it: for entry in it: if entry.is_file(): # Ignore hidden file if entry.stat().st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN: continue # Ignore PAR file entry_ext = os.path.splitext(entry.name)[1] if entry_ext.lower() == ".par2": continue total += entry.stat().st_size elif entry.is_dir(): total += check_empty(entry.path) if total > 0: break return total # Search children folders or files in a parent folder def search_child_item(parent_path): parent_name = os.path.basename(parent_path) # Check the folder exists already item_count = listbox_list1.size() item_path = parent_path + "\\" item_index = 0 while item_index < item_count: index_path = listbox_list1.get(item_index) if os.path.samefile(item_path, index_path): label_status.config(text= "The folder \"" + parent_name + "\" is selected already.") return common_path = os.path.commonpath([item_path, index_path]) + "\\" if os.path.samefile(common_path, item_path): label_status.config(text= "The folder \"" + parent_name + "\" is parent of another selected item.") return if os.path.samefile(common_path, index_path): label_status.config(text= "The folder \"" + parent_name + "\" is child of another selected item.") return item_index += 1 # Add found items error_text = "" add_count = 0 for item_name in os.listdir(parent_path): # Ignore PAR files (extension ".par2") item_ext = os.path.splitext(item_name)[1] if item_ext.lower() == ".par2": error_text += " PAR file \"" + item_name + "\" is ignored." continue # Ignore hidden item item_path = os.path.join(parent_path, item_name) if os.stat(item_path).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN: error_text += " Hidden \"" + item_name + "\" is ignored." continue # Distinguish folder or file if os.path.isdir(item_path): # Ignore empty folder if check_empty(item_path) == 0: continue item_path += "\\" listbox_list1.insert(tk.END, item_path) add_count += 1 item_count = listbox_list1.size() label_head1.config(text= str(item_count) + " items") if item_count == 0: label_status.config(text= "There are no items." + error_text) button_start.config(state=tk.DISABLED) elif add_count == 0: label_status.config(text= "No items were found in folder \"" + parent_name + "\"." + error_text) else: label_status.config(text= str(add_count) + " items were found in folder \"" + parent_name + "\"." + error_text) button_reset.config(state=tk.NORMAL) button_start.config(state=tk.NORMAL) button_remove.config(state=tk.NORMAL) # Select a folder to search children folders or files def button_parent_clicked(): global current_dir if os.path.exists(current_dir) == False: current_dir = "./" search_dir = filedialog.askdirectory(initialdir=current_dir) if search_dir == "": return current_dir = search_dir search_child_item(search_dir) # Reset lists and display status def button_reset_clicked(): global current_dir current_dir = "./" # Clear list-box at first listbox_list1.delete(0, tk.END) listbox_list2.delete(0, tk.END) # Reset statues text label_head1.config(text= '0 items') label_head2.config(text= '0 finished items') label_status.config(text= 'Select folders and/or files to create PAR files.') # Reset button state button_parent.config(state=tk.NORMAL) button_child.config(state=tk.NORMAL) button_file.config(state=tk.NORMAL) button_reset.config(state=tk.DISABLED) button_start.config(state=tk.DISABLED) button_stop.config(state=tk.DISABLED) button_remove.config(state=tk.DISABLED) button_open2.config(state=tk.DISABLED) # Check and add items def add_argv_item(): # Add specified items error_text = "" for one_path in sys.argv[1:]: # Make sure to be absolute path item_path = os.path.abspath(one_path) if os.path.exists(item_path) == False: error_text += " \"" + item_path + "\" doesn't exist." continue #print(item_path) # Ignore PAR files (extension ".par2") item_name = os.path.basename(item_path) item_ext = os.path.splitext(item_name)[1] if item_ext.lower() == ".par2": error_text += " PAR file \"" + item_name + "\" is ignored." continue # Ignore hidden item if os.stat(item_path).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN: error_text += " Hidden \"" + item_name + "\" is ignored." continue # Distinguish folder or file if os.path.isdir(item_path): # Ignore empty folder if check_empty(item_path) == 0: continue item_path += "\\" # Check the item exists already or duplicates item_count = listbox_list1.size() item_index = 0 while item_index < item_count: index_path = listbox_list1.get(item_index) if os.path.samefile(item_path, index_path): error_text += " \"" + item_name + "\" is selected already." item_count = -1 break common_path = os.path.commonpath([item_path, index_path]) + "\\" if os.path.samefile(common_path, item_path): error_text += " \"" + item_name + "\" is parent of another selected item." item_count = -1 break if os.path.samefile(common_path, index_path): error_text += " \"" + item_name + "\" is child of another selected item." item_count = -1 break item_index += 1 if item_count < 0: continue listbox_list1.insert(tk.END, item_path) item_count = listbox_list1.size() label_head1.config(text= str(item_count) + " items") if item_count == 0: label_status.config(text= "There are no items." + error_text) else: label_status.config(text= str(item_count) + " items were selected at first." + error_text) button_reset.config(state=tk.NORMAL) button_start.config(state=tk.NORMAL) button_remove.config(state=tk.NORMAL) # Cretae the first PAR set def queue_run(): global sub_proc if sub_proc != None: return if "disabled" in button_stop.state(): button_parent.config(state=tk.NORMAL) button_child.config(state=tk.NORMAL) button_file.config(state=tk.NORMAL) button_start.config(state=tk.NORMAL) button_remove.config(state=tk.NORMAL) button_open2.config(state=tk.NORMAL) label_status.config(text= "Stopped queue") return item_path = listbox_list1.get(0) if item_path == "": label_status.config(text= "There are no items.") return # When it's a folder, create PAR2 files for inner files. if item_path[-1:] == "\\": base_name = os.path.basename(item_path[:-1]) + ".par2" source_path = item_path + "*" par_path = item_path + base_name # When it's a file, create PAR2 files for the file. else: base_name = os.path.basename(item_path) + ".par2" source_path = item_path par_path = item_path + ".par2" label_status.config(text= "Creating \"" + base_name + "\"") # Test setting for good efficiency slice_size = test_efficiency(par_path, source_path) if slice_size == 0: label_status.config(text= "Failed to test options.") return # Set command-line # Cover path by " for possible space cmd = "\"" + client_path + "\" c /fe\"**.par2\" /ss" + str(slice_size) + " " + cmd_option + " \"" + par_path + "\" \"" + source_path + "\"" # If you want to see creating result only, use "t" command instead of "c". #print(cmd) # Run PAR2 client sub_proc = subprocess.Popen(cmd, shell=True) # Wait finish of creation root.after(300, queue_result) # Wait and read created result def queue_result(): global sub_proc # When sub-process was not started yet if sub_proc == None: return # When sub-process is running still exit_code = sub_proc.poll() if exit_code == None: # Call self again root.after(300, queue_result) return sub_proc = None item_path = listbox_list1.get(0) # When fatal error happened in par2j if exit_code == 1: button_parent.config(state=tk.NORMAL) button_child.config(state=tk.NORMAL) button_file.config(state=tk.NORMAL) button_reset.config(state=tk.NORMAL) button_stop.config(state=tk.DISABLED) button_remove.config(state=tk.NORMAL) button_open2.config(state=tk.NORMAL) label_status.config(text= "Failed queue") return # When you cancel par2j on Command Prompt elif exit_code == 2: button_parent.config(state=tk.NORMAL) button_child.config(state=tk.NORMAL) button_file.config(state=tk.NORMAL) button_reset.config(state=tk.NORMAL) button_start.config(state=tk.NORMAL) button_stop.config(state=tk.DISABLED) button_remove.config(state=tk.NORMAL) button_open2.config(state=tk.NORMAL) label_status.config(text= "Canceled queue") return # When par files were created successfully else: #print("exit code =", exit_code) # Add to list of finished items listbox_list2.insert(tk.END, item_path) item_count = listbox_list2.size() label_head2.config(text= str(item_count) + " finished items") # Remove the first item from the list listbox_list1.delete(0) # Process next set item_count = listbox_list1.size() if item_count == 0: button_parent.config(state=tk.NORMAL) button_child.config(state=tk.NORMAL) button_file.config(state=tk.NORMAL) button_reset.config(state=tk.NORMAL) button_stop.config(state=tk.DISABLED) button_open2.config(state=tk.NORMAL) label_status.config(text= "Created all items") elif "disabled" in button_stop.state(): button_parent.config(state=tk.NORMAL) button_child.config(state=tk.NORMAL) button_file.config(state=tk.NORMAL) button_reset.config(state=tk.NORMAL) button_start.config(state=tk.NORMAL) button_remove.config(state=tk.NORMAL) button_open2.config(state=tk.NORMAL) label_status.config(text= "Interrupted queue") else: root.after(100, queue_run) # Select a child folder to add def button_child_clicked(): global current_dir if os.path.exists(current_dir) == False: current_dir = "./" one_path = filedialog.askdirectory(initialdir=current_dir) if one_path == "": return current_dir = os.path.dirname(one_path) # Check the folder has content one_name = os.path.basename(one_path) if check_empty(one_path) == 0: label_status.config(text= "Selected folder \"" + one_name + "\" is empty.") return # Check the folder is new one_path += "\\" item_count = listbox_list1.size() item_index = 0 while item_index < item_count: index_path = listbox_list1.get(item_index) if os.path.samefile(one_path, index_path): label_status.config(text= "Folder \"" + one_name + "\" is selected already.") return common_path = os.path.commonpath([one_path, index_path]) + "\\" if os.path.samefile(common_path, one_path): label_status.config(text= "Folder \"" + one_name + "\" is parent of another selected item.") return if os.path.samefile(common_path, index_path): label_status.config(text= "Folder \"" + one_name + "\" is child of another selected item.") return item_index += 1 listbox_list1.insert(tk.END, one_path) item_count += 1 label_head1.config(text= str(item_count) + " items") label_status.config(text= "Folder \"" + one_name + "\" was added.") button_reset.config(state=tk.NORMAL) button_start.config(state=tk.NORMAL) button_remove.config(state=tk.NORMAL) # Select multiple children files to add def button_file_clicked(): global current_dir if os.path.exists(current_dir) == False: current_dir = "./" multi_path = filedialog.askopenfilenames(initialdir=current_dir) if len(multi_path) == 0: return one_path = multi_path[0] current_dir = os.path.dirname(one_path) # Add selected items error_text = "" add_count = 0 for one_path in multi_path: # Ignore PAR file (extension ".par2") one_name = os.path.basename(one_path) item_ext = os.path.splitext(one_name)[1] if item_ext.lower() == ".par2": error_text += " PAR file \"" + one_name + "\" is ignored." continue # Ignore hidden file if os.stat(one_path).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN: error_text += " Hidden \"" + one_name + "\" is ignored." continue # Check the file is new item_count = listbox_list1.size() item_index = 0 while item_index < item_count: index_path = listbox_list1.get(item_index) if os.path.samefile(one_path, index_path): error_text += " \"" + one_name + "\" is selected already." item_count = -1 break common_path = os.path.commonpath([one_path, index_path]) + "\\" if os.path.samefile(common_path, index_path): error_text += " \"" + one_name + "\" is child of another selected item." item_count = -1 break item_index += 1 if item_count < 0: continue add_name = one_name listbox_list1.insert(tk.END, one_path) add_count += 1 item_count = listbox_list1.size() label_head1.config(text= str(item_count) + " items") if item_count == 0: label_status.config(text= "There are no items." + error_text) button_start.config(state=tk.DISABLED) elif add_count == 0: label_status.config(text= "No files were added." + error_text) else: if add_count == 1: label_status.config(text= "File \"" + add_name + "\" was added." + error_text) else: label_status.config(text= str(add_count) + " files were added." + error_text) button_reset.config(state=tk.NORMAL) button_start.config(state=tk.NORMAL) button_remove.config(state=tk.NORMAL) # Select a child file to add def button_file1_clicked(): global current_dir if os.path.exists(current_dir) == False: current_dir = "./" one_path = filedialog.askopenfilename(initialdir=current_dir) if one_path == "": return current_dir = os.path.dirname(one_path) # Ignore PAR file (extension ".par2") one_name = os.path.basename(one_path) item_ext = os.path.splitext(one_name)[1] # Compare filename in case insensitive item_ext = item_ext.lower() if item_ext == ".par2": label_status.config(text= "PAR file \"" + one_name + "\" is ignored.") return # Ignore hidden file if os.stat(one_path).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN: label_status.config(text= "Hidden \"" + one_name + "\" is ignored.") return # Check the file is new item_count = listbox_list1.size() item_index = 0 while item_index < item_count: index_path = listbox_list1.get(item_index) if os.path.samefile(one_path, index_path): label_status.config(text= "File \"" + one_name + "\" is selected already.") return common_path = os.path.commonpath([one_path, index_path]) + "\\" if os.path.samefile(common_path, index_path): label_status.config(text= "File \"" + one_name + "\" is child of another selected item.") return item_index += 1 listbox_list1.insert(tk.END, one_path) item_count += 1 label_head1.config(text= str(item_count) + " items") label_status.config(text= "File \"" + one_name + "\" was added.") button_reset.config(state=tk.NORMAL) button_start.config(state=tk.NORMAL) button_remove.config(state=tk.NORMAL) # Resume stopped queue def button_start_clicked(): global sub_proc item_count = listbox_list1.size() if item_count == 0: label_status.config(text= "There are no items.") return button_parent.config(state=tk.DISABLED) button_child.config(state=tk.DISABLED) button_file.config(state=tk.DISABLED) button_reset.config(state=tk.DISABLED) button_start.config(state=tk.DISABLED) button_stop.config(state=tk.NORMAL) button_remove.config(state=tk.DISABLED) button_open2.config(state=tk.DISABLED) if sub_proc == None: queue_run() else: queue_result() # Stop running queue def button_stop_clicked(): button_stop.config(state=tk.DISABLED) if sub_proc != None: label_status.config(text= "Waiting finish of current task") # Remove items from list def button_remove_clicked(): # It's possible to select multiple items. selected_indices = listbox_list1.curselection() selected_count = len(selected_indices) if selected_count == 0: label_status.config(text= "Select items to remove at first.") return label_status.config(text= "Removed " + str(selected_count) + " items.") while selected_count > 0: selected_count -= 1 selected_index = selected_indices[selected_count] # Remove selected items at once listbox_list1.delete(selected_index) item_count = listbox_list1.size() label_head1.config(text= str(item_count) + " items") if item_count == 0: button_start.config(state=tk.DISABLED) button_remove.config(state=tk.DISABLED) # Open a PAR set by MultiPar def button_open2_clicked(): if os.path.exists(gui_path) == False: label_status.config(text= "Cannot call \"" + gui_path + "\". Set path correctly.") return indices = listbox_list2.curselection() if len(indices) == 1: item_path = listbox_list2.get(indices[0]) if item_path[-1:] == "\\": base_name = os.path.basename(item_path[:-1]) + ".par2" par_path = item_path + base_name else: base_name = os.path.basename(item_path) + ".par2" par_path = item_path + ".par2" label_status.config(text= "Opening \"" + base_name + "\"") # Set command-line # Cover path by " for possible space cmd = "\"" + gui_path + "\" /verify \"" + par_path + "\"" # Open MultiPar GUI to see details # Because this doesn't wait finish of MultiPar, you may open some at once. subprocess.Popen(cmd) else: label_status.config(text= "Select one item to open at first.") # Window size and title root = tk.Tk() root.title('PAR Queue - Create') root.minsize(width=480, height=200) # Centering window init_width = 640 init_height = 480 init_left = (root.winfo_screenwidth() - init_width) // 2 init_top = (root.winfo_screenheight() - init_height) // 2 root.geometry('{}x{}+{}+{}'.format(init_width, init_height, init_left, init_top)) #root.geometry("640x480") root.columnconfigure(0, weight=1) root.rowconfigure(1, weight=1) # Control panel frame_top = ttk.Frame(root, padding=(3,4,3,2)) frame_top.grid(row=0, column=0, sticky=(tk.E,tk.W)) button_parent = ttk.Button(frame_top, text="Search inner folder", width=18, command=button_parent_clicked) button_parent.pack(side=tk.LEFT, padx=2) button_child = ttk.Button(frame_top, text="Add single folder", width=16, command=button_child_clicked) button_child.pack(side=tk.LEFT, padx=2) button_file = ttk.Button(frame_top, text="Add multi files", width=14, command=button_file_clicked) button_file.pack(side=tk.LEFT, padx=2) button_reset = ttk.Button(frame_top, text="Reset lists", width=11, command=button_reset_clicked, state=tk.DISABLED) button_reset.pack(side=tk.LEFT, padx=2) # List frame_middle = ttk.Frame(root, padding=(2,2,2,2)) frame_middle.grid(row=1, column=0, sticky=(tk.E,tk.W,tk.S,tk.N)) frame_middle.rowconfigure(0, weight=1) frame_middle.columnconfigure(0, weight=1) frame_middle.columnconfigure(1, weight=1) # List of children items (folders and files) frame_list1 = ttk.Frame(frame_middle, padding=(6,2,6,6), relief='groove') frame_list1.grid(row=0, column=0, padx=4, sticky=(tk.E,tk.W,tk.S,tk.N)) frame_list1.columnconfigure(0, weight=1) frame_list1.rowconfigure(2, weight=1) frame_top1 = ttk.Frame(frame_list1, padding=(0,4,0,3)) frame_top1.grid(row=0, column=0, columnspan=2, sticky=(tk.E,tk.W)) button_start = ttk.Button(frame_top1, text="Start", width=6, command=button_start_clicked, state=tk.DISABLED) button_start.pack(side=tk.LEFT, padx=2) button_stop = ttk.Button(frame_top1, text="Stop", width=6, command=button_stop_clicked, state=tk.DISABLED) button_stop.pack(side=tk.LEFT, padx=2) button_remove = ttk.Button(frame_top1, text="Remove", width=8, command=button_remove_clicked, state=tk.DISABLED) button_remove.pack(side=tk.LEFT, padx=2) label_head1 = ttk.Label(frame_list1, text='0 items') label_head1.grid(row=1, column=0, columnspan=2) s_list1 = tk.StringVar() listbox_list1 = tk.Listbox(frame_list1, listvariable=s_list1, activestyle='none', selectmode='extended') listbox_list1.grid(row=2, column=0, sticky=(tk.E,tk.W,tk.S,tk.N)) scrollbar_list1 = ttk.Scrollbar(frame_list1, orient=tk.VERTICAL, command=listbox_list1.yview) scrollbar_list1.grid(row=2, column=1, sticky=(tk.N, tk.S)) listbox_list1["yscrollcommand"] = scrollbar_list1.set xscrollbar_list1 = ttk.Scrollbar(frame_list1, orient=tk.HORIZONTAL, command=listbox_list1.xview) xscrollbar_list1.grid(row=3, column=0, sticky=(tk.E, tk.W)) listbox_list1["xscrollcommand"] = xscrollbar_list1.set # List of finished items frame_list2 = ttk.Frame(frame_middle, padding=(6,2,6,6), relief='groove') frame_list2.grid(row=0, column=1, padx=4, sticky=(tk.E,tk.W,tk.S,tk.N)) frame_list2.columnconfigure(0, weight=1) frame_list2.rowconfigure(2, weight=1) frame_top2 = ttk.Frame(frame_list2, padding=(0,4,0,3)) frame_top2.grid(row=0, column=0, columnspan=2, sticky=(tk.E,tk.W)) button_open2 = ttk.Button(frame_top2, text="Open with MultiPar", width=20, command=button_open2_clicked, state=tk.DISABLED) button_open2.pack(side=tk.LEFT, padx=2) label_head2 = ttk.Label(frame_list2, text='0 finished items') label_head2.grid(row=1, column=0, columnspan=2) s_list2 = tk.StringVar() listbox_list2 = tk.Listbox(frame_list2, listvariable=s_list2, activestyle='none') listbox_list2.grid(row=2, column=0, sticky=(tk.E,tk.W,tk.S,tk.N)) scrollbar_list2 = ttk.Scrollbar(frame_list2, orient=tk.VERTICAL, command=listbox_list2.yview) scrollbar_list2.grid(row=2, column=1, sticky=(tk.N, tk.S)) listbox_list2["yscrollcommand"] = scrollbar_list2.set xscrollbar_list2 = ttk.Scrollbar(frame_list2, orient=tk.HORIZONTAL, command=listbox_list2.xview) xscrollbar_list2.grid(row=3, column=0, sticky=(tk.E, tk.W)) listbox_list2["xscrollcommand"] = xscrollbar_list2.set # Status text frame_bottom = ttk.Frame(root) frame_bottom.grid(row=2, column=0, sticky=(tk.E,tk.W)) label_status = ttk.Label(frame_bottom, text='Select folders and/or files to create PAR files.') label_status.pack(side=tk.LEFT, padx=2) # When a folder is specified in command-line if len(sys.argv) == 2: one_path = os.path.abspath(sys.argv[1]) if os.path.isdir(one_path): search_child_item(one_path) else: add_argv_item() # When multiple items are specified elif len(sys.argv) > 2: add_argv_item() # If you want to start creation automatically, use below lines. #if listbox_list1.size() > 0: # button_parent.config(state=tk.DISABLED) # button_child.config(state=tk.DISABLED) # button_file.config(state=tk.DISABLED) # button_stop.config(state=tk.NORMAL) # root.after(100, queue_run) # Show window root.mainloop()