import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk
import pandas as pd
import os
from openpyxl import load_workbook
import threading

def select_files():
    # 여러 개의 엑셀 파일 선택
    file_paths = filedialog.askopenfilenames(
        title="엑셀 파일 선택",
        filetypes=[("Excel files", "*.xlsx *.xls")]
    )
    if file_paths:
        files_list.delete(0, tk.END)
        for path in file_paths:
            files_list.insert(tk.END, path)
        progress['value'] = 0  # 리셋 진행 바

def select_output_folder():
    # 출력 폴더 선택
    folder_selected = filedialog.askdirectory(title="병합된 파일을 저장할 폴더 선택")
    if folder_selected:
        output_entry.delete(0, tk.END)
        output_entry.insert(0, folder_selected)
        progress['value'] = 0  # 리셋 진행 바

def make_unique_sheet_name(base_name, existing_names):
    """
    시트명이 중복되지 않도록 고유한 시트명을 생성합니다.
    """
    # 제한된 시트명 길이 (31자)
    max_length = 31
    base_name = base_name[:max_length]

    if base_name not in existing_names:
        return base_name
    else:
        counter = 1
        while True:
            suffix = f"_{counter}"
            # 시트명 길이 제한 고려
            truncated_base = base_name[:max_length - len(suffix)]
            new_name = f"{truncated_base}{suffix}"
            if new_name not in existing_names:
                return new_name
            counter += 1

def remove_duplicate_characters(s):
    """
    문자열에서 중복된 문자를 제외하고 유지하는 함수.
    예: 'ExcelFile' -> 'ExclFi'
    """
    seen = set()
    result = []
    for char in s:
        if char not in seen:
            seen.add(char)
            result.append(char)
    return ''.join(result)

def merge_files_thread(files, output_folder):
    try:
        total_files = len(files)
        if total_files == 0:
            messagebox.showerror("오류", "병합할 엑셀 파일을 선택하세요.")
            return
        if not output_folder:
            messagebox.showerror("오류", "출력 폴더를 선택하세요.")
            return

        # 병합된 엑셀 파일명 설정
        merged_filename = os.path.join(output_folder, "병합파일.xlsx")

        with pd.ExcelWriter(merged_filename, engine='openpyxl') as writer:
            merged_df = None
            existing_sheet_names = set()

            for idx, file_path in enumerate(files):
                # 파일명에서 확장자 제외
                base_sheet_name = os.path.splitext(os.path.basename(file_path))[0]
                # 중복된 글자 제거
                base_sheet_name = remove_duplicate_characters(base_sheet_name)
                # 고유한 시트명 생성
                sheet_name = make_unique_sheet_name(base_sheet_name, existing_sheet_names)
                existing_sheet_names.add(sheet_name)

                df = pd.read_excel(file_path)

                # 각 파일의 데이터를 별도의 시트로 저장
                df.to_excel(writer, sheet_name=sheet_name, index=False)

                # Merged 시트 용 데이터 병합
                if merged_df is None:
                    merged_df = df
                else:
                    # 동일한 열 기준으로 데이터를 추가, 헤더는 제외
                    merged_df = pd.concat([merged_df, df], ignore_index=True)

                # 진행 바 업데이트
                progress['value'] = ((idx + 1) / (total_files + 1)) * 100  # +1을 병합 시트 저장 시 포함
                root.update_idletasks()

            # Merged 시트를 추가
            merged_df.to_excel(writer, sheet_name='Merged', index=False)

            # 진행 바 업데이트
            progress['value'] = 100
            root.update_idletasks()

        # openpyxl으로 워크북 수정
        wb = load_workbook(merged_filename)

        if 'Merged' in wb.sheetnames:
            # 'Merged' 시트의 현재 인덱스 찾기
            current_index = wb.sheetnames.index('Merged')
            # 'Merged' 시트를 첫 번째 위치로 이동
            wb.move_sheet(wb['Merged'], offset=-current_index)
            merged_sheet = wb['Merged']
            # 'Merged' 시트의 탭 색을 푸른색으로 설정
            merged_sheet.sheet_properties.tabColor = "1072BA"  # 파란색
            wb.save(merged_filename)

        messagebox.showinfo("성공", f"파일이 성공적으로 병합되었습니다.\n{merged_filename}")
    except Exception as e:
        messagebox.showerror("오류", f"병합 중 오류가 발생했습니다:\n{e}")
    finally:
        progress['value'] = 0  # 병합 완료 후 리셋

def merge_files():
    files = files_list.get(0, tk.END)
    output_folder = output_entry.get()

    # 병합 작업을 별도의 스레드에서 실행하여 GUI가 멈추지 않도록 함
    threading.Thread(target=merge_files_thread, args=(files, output_folder), daemon=True).start()

# GUI 설정
root = tk.Tk()
root.title("엑셀 파일 병합기")
root.geometry("700x700")

# 파일 선택 프레임
file_frame = tk.LabelFrame(root, text="병합할 엑셀 파일 선택", padx=10, pady=10)
file_frame.pack(fill="both", expand=True, padx=10, pady=10)

select_button = tk.Button(file_frame, text="파일 선택", command=select_files, width=20)
select_button.pack(pady=5)

files_list = tk.Listbox(file_frame, selectmode=tk.MULTIPLE, width=100, height=10)
files_list.pack(pady=5)

# 출력 폴더 선택 프레임
output_frame = tk.LabelFrame(root, text="병합된 파일을 저장할 폴더 선택", padx=5, pady=5)
output_frame.pack(fill="both", expand=True, padx=5, pady=5)

output_entry = tk.Entry(output_frame, width=80)
output_entry.pack(side=tk.LEFT, padx=5)

output_button = tk.Button(output_frame, text="폴더 선택", command=select_output_folder, width=15)
output_button.pack(side=tk.LEFT, padx=5)

# 진행 바 프레임
progress_frame = tk.LabelFrame(root, text="진행 상황", padx=10, pady=10)
progress_frame.pack(fill="both", expand=True, padx=10, pady=10)

progress = ttk.Progressbar(progress_frame, orient=tk.HORIZONTAL, length=600, mode='determinate')
progress.pack(pady=20)

# 병합 버튼
merge_button = tk.Button(root, text="병합하기", command=merge_files, bg="blue", fg="white", font=("Arial", 12), width=20, height=2)
merge_button.pack(pady=20)

root.mainloop() 해당 코드를 하나의 배포용 파일로 생성하고자 합니다. 파일용량을 최적화하여 적은 용량으로 파일을 만듭니다. 가상환경과 spec를 활용해서 만드는 방법을 알려주세요