""" Streamlit File Extension Renamer -------------------------------- Lightweight Streamlit app for Hugging Face Spaces. Updates (2025‑05‑22): * Ensures Streamlit has a writable config dir (`STREAMLIT_HOME`) inside container. * Sets a fallback `HOME` env so `~/.streamlit` resolves safely. * Compatible with Streamlit >=1.45.1 (bug‑fixed event‑loop). """ from __future__ import annotations import os import pathlib import io import zipfile from datetime import datetime from pathlib import Path # --------------------------------------------------------------------- # Environment hardening (MUST run before `import streamlit`) # --------------------------------------------------------------------- os.environ.setdefault("STREAMLIT_HOME", "/tmp/.streamlit") os.environ.setdefault("HOME", "/tmp") pathlib.Path(os.environ["STREAMLIT_HOME"]).mkdir(parents=True, exist_ok=True) import streamlit as st # --------------------------------------------------------------------- # Configuration # --------------------------------------------------------------------- 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", ] DISALLOWED_SOURCE_EXTS: set[str] = {".exe", ".bin"} # --------------------------------------------------------------------- # UI helpers # --------------------------------------------------------------------- def make_sidebar() -> str: st.sidebar.header("Settings") target_ext = st.sidebar.selectbox( "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 file extensions. It does **not** " "convert the underlying format." ) return target_ext def make_uploader(): 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: 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 # --------------------------------------------------------------------- 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 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 • Streamlit • Hugging Face Spaces") if __name__ == "__main__": main()