File size: 4,372 Bytes
29a56dd
 
 
 
 
 
 
 
 
 
 
 
d0d9535
29a56dd
d0d9535
7de3bfe
29a56dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de80961
29a56dd
 
de80961
 
29a56dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de80961
29a56dd
 
 
 
de80961
29a56dd
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
"""
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 io
import zipfile
from datetime import datetime
from pathlib import Path

import streamlit as st

# -----------------------------------------------------------------------------
# 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()