""" Streamlit File Extension Renamer -------------------------------- A lightweight Streamlit app intended for deployment on Hugging Face Spaces. The app lets users upload one or more files and rename (not convert) their extensions to any common file-type extension the user selects. Executable and binary files are blocked for security reasons. """ from __future__ import annotations import os, pathlib # 1️⃣ give Streamlit a real home BEFORE importing it os.environ.setdefault("STREAMLIT_HOME", "/tmp/.streamlit") pathlib.Path(os.environ["STREAMLIT_HOME"]).mkdir(parents=True, exist_ok=True) import streamlit as st import zipfile from datetime import datetime from pathlib import Path # ----------------------------------------------------------------------------- # Configuration # ----------------------------------------------------------------------------- # List of target extensions displayed to the user. Extend this list as needed. ALLOWED_TARGET_EXTS: list[str] = [ ".txt", ".pdf", ".doc", ".docx", ".json", ".csv", ".xml", ".html", ".css", ".js", ".md", ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".svg", ".ico", ".mp3", ".wav", ".mp4", ".avi", ".mkv", ".mov", ".zip", ".tar", ".gz", ".7z", ] # Source‐file extensions that are disallowed (will be skipped if uploaded). DISALLOWED_SOURCE_EXTS: set[str] = {".exe", ".bin"} # ----------------------------------------------------------------------------- # UI helpers # ----------------------------------------------------------------------------- def make_sidebar() -> str: """Return the user-chosen target extension from sidebar controls.""" st.sidebar.header("Settings") target_ext = st.sidebar.selectbox( label="Target extension (applied to **all** uploaded files)", options=ALLOWED_TARGET_EXTS, index=ALLOWED_TARGET_EXTS.index(".pdf"), ) st.sidebar.markdown( """❗ **Note**: This tool only renames the file extension. It does **not** convert the underlying file format. Opening a renamed file with an incompatible program may fail or lead to data corruption.""" ) return target_ext def make_uploader(): """Return the list of files uploaded by the user.""" return st.file_uploader( "Upload one or more files", accept_multiple_files=True, type=[ext.lstrip(".") for ext in ALLOWED_TARGET_EXTS], help="Drag-and-drop or browse for files to rename.", ) # ----------------------------------------------------------------------------- # Core logic # ----------------------------------------------------------------------------- def write_zip(uploaded_files: list[st.runtime.uploaded_file_manager.UploadedFile], target_ext: str) -> io.BytesIO: """Return an in-memory ZIP archive of the uploaded files with the new extension.""" buffer = io.BytesIO() with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as zf: for file in uploaded_files: orig_path = Path(file.name) if orig_path.suffix.lower() in DISALLOWED_SOURCE_EXTS: st.warning(f"⏭️ Skipping disallowed file: **{orig_path.name}**") continue renamed = orig_path.with_suffix(target_ext) zf.writestr(renamed.name, file.read()) st.success(f"✅ Renamed **{orig_path.name}** → **{renamed.name}**") buffer.seek(0) return buffer # ----------------------------------------------------------------------------- # Main app # ----------------------------------------------------------------------------- def main() -> None: st.set_page_config("Extension Renamer", page_icon="📄", layout="centered") st.title("📄 Universal File-Extension Renamer") st.write( "Upload files, choose a new extension, and download them renamed in a single ZIP archive." ) target_ext = make_sidebar() uploaded_files = make_uploader() if uploaded_files and st.button("🔄 Rename & Package"): zip_buffer = write_zip(uploaded_files, target_ext) timestamp = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ") st.download_button( label="⬇️ Download ZIP", data=zip_buffer, file_name=f"renamed_{timestamp}.zip", mime="application/zip", ) st.caption("© 2025 File-Extension Renamer • Built with Streamlit • Deployed on Hugging Face Spaces") if __name__ == "__main__": main()