import streamlit as st import os import logging import warnings import shutil from io import BytesIO from io import BytesIO import tempfile import zipfile from pathlib import Path import glob import platform import subprocess from main import ( load_workbook_with_possible_passwords, copy_excel_file, remove_all_protection_tags, setup_logging ) # Configure page st.set_page_config( page_title="Office Protection Remover", page_icon="🔓", layout="wide" ) # Setup logging setup_logging() warnings.filterwarnings('ignore', category=UserWarning, module='openpyxl.reader.workbook') def add_to_error_log(error_dict, filepath, error): """Add error to the error dictionary with file path as key""" error_dict[filepath] = str(error) def save_uploaded_file(uploaded_file): """Save an uploaded file to a temporary location""" with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(uploaded_file.name)[1]) as tmp_file: tmp_file.write(uploaded_file.getvalue()) return tmp_file.name def get_file_path(title="Select File", file_types=[("Text Files", "*.txt")]): """Get file path using native file dialog when possible""" try: if platform.system() == "Windows": import tkinter as tk from tkinter import filedialog root = tk.Tk() root.withdraw() # Hide the main window root.attributes('-topmost', True) # Bring dialog to front path = filedialog.askopenfilename(title=title, filetypes=file_types) return path if path else None elif platform.system() == "Linux": # Use zenity for Linux systems try: result = subprocess.run( ['zenity', '--file-selection', '--title', title], capture_output=True, text=True ) return result.stdout.strip() if result.returncode == 0 else None except FileNotFoundError: return None elif platform.system() == "Darwin": # macOS try: result = subprocess.run( ['osascript', '-e', f'choose file with prompt "{title}"'], capture_output=True, text=True ) return result.stdout.strip() if result.returncode == 0 else None except FileNotFoundError: return None except Exception as e: st.error(f"Error opening file dialog: {str(e)}") return None return None def get_directory_path(title="Select Directory"): """Get directory path using native file dialog when possible""" try: if platform.system() == "Windows": import tkinter as tk from tkinter import filedialog root = tk.Tk() root.withdraw() # Hide the main window root.attributes('-topmost', True) # Bring dialog to front path = filedialog.askdirectory(title=title) return path if path else None elif platform.system() == "Linux": # Use zenity for Linux systems try: result = subprocess.run( ['zenity', '--file-selection', '--directory', '--title', title], capture_output=True, text=True ) return result.stdout.strip() if result.returncode == 0 else None except FileNotFoundError: return None elif platform.system() == "Darwin": # macOS try: result = subprocess.run( ['osascript', '-e', f'choose folder with prompt "{title}"'], capture_output=True, text=True ) return result.stdout.strip() if result.returncode == 0 else None except FileNotFoundError: return None except Exception as e: st.error(f"Error opening file dialog: {str(e)}") return None return None def create_zip_file(files_dict): """Create a zip file containing all processed files""" zip_buffer = BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: for filename, content in files_dict.items(): zip_file.writestr(filename, content) return zip_buffer # Main UI st.title("🔓 Office File Protection Remover") st.write("Remove protection from Excel and Word files easily") # Sidebar with st.sidebar: st.header("Settings") file_type = st.radio( "Choose file type:", ("Excel", "Word"), help="Select the type of files you want to process" ) # Main content area st.header(f"{file_type} File Processing") col1, col2 = st.columns(2) with col1: # Input method selection input_method = st.radio( "Choose input method:", ("Upload Files", "Select Directory"), help="Upload files directly or process from local directory" ) if input_method == "Upload Files": uploaded_files = st.file_uploader( f"Upload {file_type} files", type=["xlsx", "xlsm"] if file_type == "Excel" else ["docx", "docm"], accept_multiple_files=True, help=f"You can upload multiple {file_type} files" ) else: # Select Directory source_dir = st.text_input("Source Directory Path", value=st.session_state.get('source_dir', ''), help="Enter the full path to the directory containing files to process") source_browse = st.button("Browse Source Directory") if source_browse: path = get_directory_path("Select Source Directory") if path: st.session_state['source_dir'] = path st.experimental_rerun() dest_dir = st.text_input("Destination Directory Path", value=st.session_state.get('dest_dir', ''), help="Enter the full path where processed files will be saved") dest_browse = st.button("Browse Destination Directory") if dest_browse: path = get_directory_path("Select Destination Directory") if path: st.session_state['dest_dir'] = path st.experimental_rerun() with col2: if file_type == "Excel": password_option = st.radio( "Password Option:", ("No Password", "Single Password", "Password File") ) passwords = [] if password_option == "Single Password": password = st.text_input("Enter password:", type="password") if password: passwords = [password] elif password_option == "Password File": if input_method == "Upload Files": password_file = st.file_uploader("Upload password file", type=["txt"]) if password_file: content = password_file.getvalue().decode() passwords = [line.strip() for line in content.splitlines() if line.strip()] st.info(f"Loaded {len(passwords)} passwords") else: # Select Directory password_path = st.text_input("Password File Path", help="Enter the full path to the text file containing passwords", value=st.session_state.get('password_path', '')) password_browse = st.button("Browse Password File") if password_browse: path = get_file_path("Select Password File", [("Text Files", "*.txt")]) if path: st.session_state['password_path'] = path st.experimental_rerun() if password_path and os.path.exists(password_path): with open(password_path, 'r', encoding='utf-8') as pf: passwords = [line.strip() for line in pf if line.strip()] st.info(f"Loaded {len(passwords)} passwords from file") # Process button and logic if input_method == "Upload Files" and uploaded_files and st.button("Process Files", type="primary"): progress_bar = st.progress(0) status_text = st.empty() processed_files = {} for idx, uploaded_file in enumerate(uploaded_files): try: with st.expander(f"Processing {uploaded_file.name}", expanded=True): st.write(f"📝 Processing {uploaded_file.name}...") temp_input_path = save_uploaded_file(uploaded_file) temp_output_path = f"{temp_input_path}_processed" if file_type == "Excel": copy_excel_file(temp_input_path, temp_output_path, passwords) else: # Word remove_all_protection_tags(temp_input_path, temp_output_path) with open(temp_output_path, "rb") as f: processed_file = f.read() processed_files[f"processed_{uploaded_file.name}"] = processed_file mime_type = ("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" if file_type == "Excel" else "application/vnd.openxmlformats-officedocument.wordprocessingml.document") st.download_button( label=f"⬇️ Download processed file", data=processed_file, file_name=f"processed_{uploaded_file.name}", mime=mime_type ) os.unlink(temp_input_path) os.unlink(temp_output_path) st.success("✅ Processing complete!") progress = (idx + 1) / len(uploaded_files) progress_bar.progress(progress) status_text.text(f"Processed {idx + 1} of {len(uploaded_files)} files") except Exception as e: error_msg = f"❌ Error processing {uploaded_file.name}: {str(e)}" st.error(error_msg) st.session_state.error_log[uploaded_file.name] = str(e) if len(processed_files) > 1: zip_buffer = create_zip_file(processed_files) st.download_button( label="⬇️ Download all files as ZIP", data=zip_buffer.getvalue(), file_name="processed_files.zip", mime="application/zip", ) elif input_method == "Select Directory" and source_dir and dest_dir and st.button("Process Directory", type="primary"): if not os.path.exists(source_dir): st.error(f"Source directory does not exist: {source_dir}") elif not os.path.exists(os.path.dirname(dest_dir)): st.error(f"Parent of destination directory does not exist: {os.path.dirname(dest_dir)}") else: os.makedirs(dest_dir, exist_ok=True) # Get all files recursively all_files = [] files_to_process = [] for root, _, files in os.walk(source_dir): for file in files: full_path = os.path.join(root, file) file_lower = file.lower() if file_type == "Excel" and file_lower.endswith(('.xlsx', '.xlsm')): files_to_process.append(full_path) elif file_type == "Word" and file_lower.endswith(('.docx', '.docm')): files_to_process.append(full_path) else: all_files.append(full_path) if not files_to_process: st.warning(f"No {file_type} files found in the source directory") total_files = len(files_to_process) + len(all_files) progress_bar = st.progress(0) status_text = st.empty() files_processed = 0 # Process Office files for source_path in files_to_process: try: relative_path = os.path.relpath(source_path, source_dir) dest_path = os.path.join(dest_dir, relative_path) with st.expander(f"Processing {relative_path}", expanded=True): st.write(f"📝 Processing {relative_path}...") os.makedirs(os.path.dirname(dest_path), exist_ok=True) if file_type == "Excel": copy_excel_file(source_path, dest_path, passwords) else: # Word remove_all_protection_tags(source_path, dest_path) st.success("✅ Processing complete!") files_processed += 1 progress = files_processed / total_files progress_bar.progress(progress) status_text.text(f"Processed {files_processed} of {total_files} files") except Exception as e: error_msg = f"❌ Error processing {relative_path}: {str(e)}" st.error(error_msg) st.session_state.error_log[source_path] = str(e) # Copy all other files with st.expander("Copying other files", expanded=True): for source_path in all_files: try: relative_path = os.path.relpath(source_path, source_dir) dest_path = os.path.join(dest_dir, relative_path) # Create destination directory if it doesn't exist os.makedirs(os.path.dirname(dest_path), exist_ok=True) # Copy the file import shutil shutil.copy2(source_path, dest_path) files_processed += 1 progress = files_processed / total_files progress_bar.progress(progress) status_text.text(f"Processed {files_processed} of {total_files} files") except Exception as e: error_msg = f"❌ Error copying {relative_path}: {str(e)}" st.error(error_msg) st.session_state.error_log[source_path] = str(e) st.success(f"✨ All files processed and saved to: {dest_dir}") if len(all_files) > 0: st.info(f"📁 Copied {len(all_files)} additional files to maintain folder structure") # Error Log Section st.markdown("---") st.header("Error Log") if 'error_log' not in st.session_state: st.session_state.error_log = {} # Display error log if there are errors if st.session_state.error_log: st.error("The following errors were encountered:") error_text = "\n\n".join([f"File: {path}\nError: {error}" for path, error in st.session_state.error_log.items()]) st.text_area("Error Details", error_text, height=200) # Add copy button if st.button("Copy Error Log"): st.write("Error log copied to clipboard!") st.session_state.error_log_copied = error_text # Add clear button if st.button("Clear Error Log"): st.session_state.error_log = {} st.experimental_rerun() else: st.success("No errors encountered in current session") # Footer st.sidebar.markdown("---") st.sidebar.markdown("### Instructions") st.sidebar.markdown(""" 1. Select file type (Excel or Word) 2. Choose input method: - Upload Files: Process files via web upload - Select Directory: Process files from local directories 3. For Excel files, set password options if needed 4. Click Process button 5. Monitor progress and download processed files """) st.sidebar.markdown("---") st.sidebar.markdown("### About") st.sidebar.markdown(""" This tool helps you remove protection from: - Excel workbooks (.xlsx, .xlsm) - Word documents (.docx, .docm) Upload Limits: - Individual files: up to 200MB each - Total upload size: up to 800MB per session For larger files or bulk processing, use the 'Select Directory' option to process files locally. No files are stored on the server - all processing happens in your browser! """)