st4ck commited on
Commit
1346a5c
Β·
1 Parent(s): 2940c12

Rewrite app.py with JS-based file upload, Gradio 5.12.0

Browse files
Files changed (3) hide show
  1. README.md +1 -2
  2. app.py +256 -93
  3. requirements.txt +2 -1
README.md CHANGED
@@ -4,8 +4,7 @@ emoji: πŸ—œοΈ
4
  colorFrom: blue
5
  colorTo: indigo
6
  sdk: gradio
7
- sdk_version: "4.44.0"
8
- python_version: "3.11"
9
  app_file: app.py
10
  pinned: false
11
  ---
 
4
  colorFrom: blue
5
  colorTo: indigo
6
  sdk: gradio
7
+ sdk_version: "5.12.0"
 
8
  app_file: app.py
9
  pinned: false
10
  ---
app.py CHANGED
@@ -1,124 +1,287 @@
1
- import gradio as gr
2
- import subprocess
 
3
  import os
 
4
  import tempfile
5
- import shutil
 
6
 
7
  # ---------------------------------------------------------------------------
8
  # Build the mdc binary once at startup
9
  # ---------------------------------------------------------------------------
10
 
11
- BINARY = os.path.join(os.path.dirname(__file__), "mdc")
 
12
 
13
  def _build():
14
- src = os.path.join(os.path.dirname(__file__), "mdc.c")
15
- result = subprocess.run(
16
- ["gcc", "-O3", "-o", BINARY, src, "-lm"],
17
- capture_output=True, text=True
18
- )
19
  if result.returncode != 0:
20
- raise RuntimeError(f"Failed to compile mdc:\n{result.stderr}")
 
21
 
22
  if not os.path.isfile(BINARY):
23
  _build()
24
 
 
25
  # ---------------------------------------------------------------------------
26
- # Core helpers
27
  # ---------------------------------------------------------------------------
28
 
29
- def _run(args, label):
30
- result = subprocess.run(args, capture_output=True, text=True)
31
- if result.returncode != 0:
32
- raise RuntimeError(f"{label} failed:\n{result.stderr or result.stdout}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- def compress(input_file):
35
- if input_file is None:
36
- raise gr.Error("Please upload a file.")
37
- workdir = tempfile.mkdtemp()
38
  try:
39
- out_path = os.path.join(workdir, os.path.basename(input_file) + ".mdc")
40
- _run([BINARY, "compress", input_file, out_path], "Compression")
41
- in_size = os.path.getsize(input_file)
42
- out_size = os.path.getsize(out_path)
43
- ratio = out_size / in_size * 100
44
- bpb = out_size * 8 / in_size
45
- info = (
46
- f"Input: {in_size:,} bytes\n"
47
- f"Output: {out_size:,} bytes\n"
48
- f"Ratio: {ratio:.1f}% ({bpb:.3f} bpb)"
49
- )
50
- # Copy to a stable temp file that Gradio can serve
51
- dest = tempfile.NamedTemporaryFile(
52
- delete=False, suffix=".mdc",
53
- dir=workdir
54
- )
55
- dest.close()
56
- shutil.copy2(out_path, dest.name)
57
- return dest.name, info
58
- except RuntimeError as e:
59
- shutil.rmtree(workdir, ignore_errors=True)
60
- raise gr.Error(str(e))
61
-
62
- def decompress(input_file):
63
- if input_file is None:
64
- raise gr.Error("Please upload a .mdc file.")
65
- workdir = tempfile.mkdtemp()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  try:
67
- base = os.path.basename(input_file)
68
- if base.endswith(".mdc"):
69
- out_name = base[:-4] or "restored"
70
- else:
71
- out_name = base + ".restored"
72
- out_path = os.path.join(workdir, out_name)
73
- _run([BINARY, "decompress", input_file, out_path], "Decompression")
74
- out_size = os.path.getsize(out_path)
75
- info = f"Restored: {out_size:,} bytes"
76
- dest = tempfile.NamedTemporaryFile(
77
- delete=False,
78
- suffix=os.path.splitext(out_name)[1] or ".bin",
79
- dir=workdir
80
- )
81
- dest.close()
82
- shutil.copy2(out_path, dest.name)
83
- return dest.name, info
84
- except RuntimeError as e:
85
- shutil.rmtree(workdir, ignore_errors=True)
86
- raise gr.Error(str(e))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
  # ---------------------------------------------------------------------------
89
- # Gradio UI
90
  # ---------------------------------------------------------------------------
91
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  with gr.Blocks(title="Midicoth β€” Micro-Diffusion Compression") as demo:
93
- gr.Markdown(
94
- """
95
- # Midicoth β€” Micro-Diffusion Compression
96
- Lossless text compressor using **Binary Tree Tweedie Denoising**.
97
- Outperforms xz, zstd, Brotli, and bzip2 on text data. No neural network, no GPU.
98
-
99
- > **Note:** Files are processed in memory and deleted after the session.
100
- > Large files (>10 MB) may be slow on free-tier hardware.
101
- """
102
- )
103
 
 
104
  with gr.Tab("Compress"):
105
- with gr.Row():
106
- c_input = gr.File(label="Input file", file_types=["*"])
107
- with gr.Row():
108
- c_btn = gr.Button("Compress", variant="primary")
109
- with gr.Row():
110
- c_output = gr.File(label="Compressed (.mdc)")
111
- c_info = gr.Textbox(label="Stats", lines=3, interactive=False)
112
- c_btn.click(compress, inputs=c_input, outputs=[c_output, c_info])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
 
114
  with gr.Tab("Decompress"):
115
- with gr.Row():
116
- d_input = gr.File(label="Input file (.mdc)", file_types=[".mdc"])
117
- with gr.Row():
118
- d_btn = gr.Button("Decompress", variant="primary")
119
- with gr.Row():
120
- d_output = gr.File(label="Restored file")
121
- d_info = gr.Textbox(label="Stats", lines=2, interactive=False)
122
- d_btn.click(decompress, inputs=d_input, outputs=[d_output, d_info])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
 
124
  demo.launch()
 
1
+ #!/usr/bin/env python3
2
+ """Midicoth -- Micro-Diffusion Compression -- Hugging Face Space Demo."""
3
+
4
  import os
5
+ import base64
6
  import tempfile
7
+ import subprocess
8
+ import gradio as gr
9
 
10
  # ---------------------------------------------------------------------------
11
  # Build the mdc binary once at startup
12
  # ---------------------------------------------------------------------------
13
 
14
+ BINARY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mdc")
15
+
16
 
17
  def _build():
18
+ src = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mdc.c")
19
+ result = subprocess.run(["gcc", "-O3", "-o", BINARY, src, "-lm"],
20
+ capture_output=True, text=True)
 
 
21
  if result.returncode != 0:
22
+ raise RuntimeError(f"Compilation failed:\n{result.stderr}")
23
+
24
 
25
  if not os.path.isfile(BINARY):
26
  _build()
27
 
28
+
29
  # ---------------------------------------------------------------------------
30
+ # Helpers
31
  # ---------------------------------------------------------------------------
32
 
33
+ def _fmt(n):
34
+ if n < 1024:
35
+ return f"{n} B"
36
+ if n < 1024 ** 2:
37
+ return f"{n / 1024:.1f} KB"
38
+ return f"{n / 1024 ** 2:.2f} MB"
39
+
40
+
41
+ def _download_link(b64_data, filename, label, color="#6366f1"):
42
+ size = len(base64.b64decode(b64_data))
43
+ return (
44
+ f'<a download="{filename}" '
45
+ f'href="data:application/octet-stream;base64,{b64_data}" '
46
+ f'style="display:inline-block;padding:10px 24px;background:{color};color:white;'
47
+ f'border-radius:8px;text-decoration:none;font-weight:bold;font-size:14px;'
48
+ f'margin-top:8px;cursor:pointer;">'
49
+ f'{label} ({_fmt(size)})</a>'
50
+ )
51
+
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Compress
55
+ # ---------------------------------------------------------------------------
56
+
57
+ def compress_b64(b64_data, filename):
58
+ if not b64_data or not b64_data.strip():
59
+ return "Please upload a file using the button above.", ""
60
 
 
 
 
 
61
  try:
62
+ data = base64.b64decode(b64_data.strip())
63
+ except Exception:
64
+ return "**Failed to read uploaded file.**", ""
65
+
66
+ if len(data) == 0:
67
+ return "**Empty file.**", ""
68
+
69
+ with tempfile.TemporaryDirectory() as workdir:
70
+ in_path = os.path.join(workdir, "input")
71
+ out_path = os.path.join(workdir, "output.mdc")
72
+ with open(in_path, "wb") as f:
73
+ f.write(data)
74
+ result = subprocess.run([BINARY, "compress", in_path, out_path],
75
+ capture_output=True, text=True)
76
+ if result.returncode != 0:
77
+ return f"**Compression failed:**\n{result.stderr or result.stdout}", ""
78
+ with open(out_path, "rb") as f:
79
+ compressed = f.read()
80
+
81
+ in_size = len(data)
82
+ out_size = len(compressed)
83
+ ratio = out_size / in_size * 100
84
+ bpb = out_size * 8 / in_size
85
+ out_name = (filename.strip() + ".mdc") if filename and filename.strip() else "compressed.mdc"
86
+ b64_out = base64.b64encode(compressed).decode("ascii")
87
+
88
+ md = f"""## Compression Results
89
+
90
+ | | Size | Ratio |
91
+ |---|---|---|
92
+ | **Original** | {_fmt(in_size)} | 100% |
93
+ | **Midicoth** | {_fmt(out_size)} | {ratio:.1f}% |
94
+
95
+ **bpb:** {bpb:.3f} &nbsp;|&nbsp; **Space saved:** {100 - ratio:.1f}%
96
+ """
97
+ return md, _download_link(b64_out, out_name, f"Download {out_name}")
98
+
99
+
100
+ # ---------------------------------------------------------------------------
101
+ # Decompress
102
+ # ---------------------------------------------------------------------------
103
+
104
+ def decompress_b64(b64_data):
105
+ if not b64_data or not b64_data.strip():
106
+ return "Please upload a .mdc file using the button above.", ""
107
+
108
  try:
109
+ data = base64.b64decode(b64_data.strip())
110
+ except Exception:
111
+ return "**Failed to read uploaded file.**", ""
112
+
113
+ if len(data) < 12:
114
+ return "**Invalid file.** Too short to be a Midicoth file.", ""
115
+
116
+ if data[:4] != b"MDC7":
117
+ return "**Invalid file.** Not a Midicoth file (wrong magic bytes).", ""
118
+
119
+ with tempfile.TemporaryDirectory() as workdir:
120
+ in_path = os.path.join(workdir, "input.mdc")
121
+ out_path = os.path.join(workdir, "output")
122
+ with open(in_path, "wb") as f:
123
+ f.write(data)
124
+ result = subprocess.run([BINARY, "decompress", in_path, out_path],
125
+ capture_output=True, text=True)
126
+ if result.returncode != 0:
127
+ return f"**Decompression failed:**\n{result.stderr or result.stdout}", ""
128
+ with open(out_path, "rb") as f:
129
+ restored = f.read()
130
+
131
+ in_size = len(data)
132
+ out_size = len(restored)
133
+ b64_out = base64.b64encode(restored).decode("ascii")
134
+
135
+ md = f"""## Decompression Results
136
+
137
+ - Compressed: {_fmt(in_size)}
138
+ - Restored: {_fmt(out_size)}
139
+ - **Lossless reconstruction successful**
140
+ """
141
+ return md, _download_link(b64_out, "decompressed.bin", "Download decompressed", color="#22c55e")
142
+
143
+
144
+ # ---------------------------------------------------------------------------
145
+ # JavaScript for binary file upload (reads as base64, injects into hidden Textbox)
146
+ # ---------------------------------------------------------------------------
147
+
148
+ COMPRESS_UPLOAD_JS = """
149
+ function setupCompressUpload() {
150
+ const input = document.getElementById('compress-file-input');
151
+ if (!input) return;
152
+ input.addEventListener('change', function(e) {
153
+ const file = e.target.files[0];
154
+ if (!file) return;
155
+ const nameSpan = document.getElementById('compress-file-name');
156
+ if (nameSpan) nameSpan.textContent = file.name + ' (' + (file.size / 1024).toFixed(1) + ' KB)';
157
+ const fnBoxes = document.querySelectorAll('#compress-filename textarea');
158
+ if (fnBoxes.length > 0) {
159
+ fnBoxes[0].value = file.name;
160
+ fnBoxes[0].dispatchEvent(new Event('input', {bubbles: true}));
161
+ }
162
+ const reader = new FileReader();
163
+ reader.onload = function(ev) {
164
+ const b64 = ev.target.result.split(',')[1];
165
+ const textareas = document.querySelectorAll('#compress-b64-data textarea');
166
+ if (textareas.length > 0) {
167
+ textareas[0].value = b64;
168
+ textareas[0].dispatchEvent(new Event('input', {bubbles: true}));
169
+ }
170
+ };
171
+ reader.readAsDataURL(file);
172
+ });
173
+ }
174
+ setTimeout(setupCompressUpload, 1000);
175
+ """
176
+
177
+ DECOMPRESS_UPLOAD_JS = """
178
+ function setupDecompressUpload() {
179
+ const input = document.getElementById('decompress-file-input');
180
+ if (!input) return;
181
+ input.addEventListener('change', function(e) {
182
+ const file = e.target.files[0];
183
+ if (!file) return;
184
+ const nameSpan = document.getElementById('decompress-file-name');
185
+ if (nameSpan) nameSpan.textContent = file.name + ' (' + (file.size / 1024).toFixed(1) + ' KB)';
186
+ const reader = new FileReader();
187
+ reader.onload = function(ev) {
188
+ const b64 = ev.target.result.split(',')[1];
189
+ const textareas = document.querySelectorAll('#decompress-b64-data textarea');
190
+ if (textareas.length > 0) {
191
+ textareas[0].value = b64;
192
+ textareas[0].dispatchEvent(new Event('input', {bubbles: true}));
193
+ }
194
+ };
195
+ reader.readAsDataURL(file);
196
+ });
197
+ }
198
+ setTimeout(setupDecompressUpload, 1200);
199
+ """
200
 
201
  # ---------------------------------------------------------------------------
202
+ # UI
203
  # ---------------------------------------------------------------------------
204
 
205
+ HEADER_HTML = """
206
+ <div style="text-align:center;margin-bottom:0.5em;">
207
+ <h1 style="font-size:2em;margin-bottom:0.2em;">πŸ—œοΈ Midicoth</h1>
208
+ <p style="font-size:1.1em;color:#aaa;margin-top:4px;">Micro-Diffusion Compression &mdash; Binary Tree Tweedie Denoising</p>
209
+ <p style="font-size:0.9em;">
210
+ <a href="https://github.com/robtacconelli/midicoth">GitHub</a> |
211
+ No neural network &bull; No GPU &bull; ~2,000 lines of C &bull;
212
+ Outperforms xz, zstd, Brotli, bzip2 on text
213
+ </p>
214
+ </div>
215
+ """
216
+
217
  with gr.Blocks(title="Midicoth β€” Micro-Diffusion Compression") as demo:
218
+ gr.HTML(HEADER_HTML)
 
 
 
 
 
 
 
 
 
219
 
220
+ # ---- Tab 1: Compress ----
221
  with gr.Tab("Compress"):
222
+ gr.Markdown("Upload any file to compress with Midicoth.")
223
+ gr.HTML(
224
+ '<div style="margin:8px 0;">'
225
+ '<label style="display:inline-block;padding:10px 24px;background:#6366f1;color:white;'
226
+ 'border-radius:8px;cursor:pointer;font-weight:bold;font-size:14px;">'
227
+ 'Choose file to compress'
228
+ '<input id="compress-file-input" type="file" style="display:none;">'
229
+ '</label>'
230
+ '<span id="compress-file-name" style="margin-left:10px;color:#aaa;"></span>'
231
+ '</div>'
232
+ )
233
+ compress_b64_data = gr.Textbox(visible=False, elem_id="compress-b64-data")
234
+ compress_filename = gr.Textbox(visible=False, elem_id="compress-filename")
235
+ compress_btn = gr.Button("Compress", variant="primary", size="lg")
236
+ compress_results = gr.Markdown()
237
+ compress_download = gr.HTML()
238
+
239
+ compress_btn.click(
240
+ fn=compress_b64,
241
+ inputs=[compress_b64_data, compress_filename],
242
+ outputs=[compress_results, compress_download],
243
+ )
244
 
245
+ # ---- Tab 2: Decompress ----
246
  with gr.Tab("Decompress"):
247
+ gr.Markdown("Upload a `.mdc` file to decompress.")
248
+ gr.HTML(
249
+ '<div style="margin:8px 0;">'
250
+ '<label style="display:inline-block;padding:10px 24px;background:#6366f1;color:white;'
251
+ 'border-radius:8px;cursor:pointer;font-weight:bold;font-size:14px;">'
252
+ 'Upload .mdc file'
253
+ '<input id="decompress-file-input" type="file" accept=".mdc" style="display:none;">'
254
+ '</label>'
255
+ '<span id="decompress-file-name" style="margin-left:10px;color:#aaa;"></span>'
256
+ '</div>'
257
+ )
258
+ decompress_b64_data = gr.Textbox(visible=False, elem_id="decompress-b64-data")
259
+ decompress_btn = gr.Button("Decompress", variant="primary", size="lg")
260
+ decompress_results = gr.Markdown()
261
+ decompress_download = gr.HTML()
262
+
263
+ decompress_btn.click(
264
+ fn=decompress_b64,
265
+ inputs=[decompress_b64_data],
266
+ outputs=[decompress_results, decompress_download],
267
+ )
268
+
269
+ gr.Markdown("""
270
+ ---
271
+ **How it works:** Midicoth processes input through a five-layer cascade β€”
272
+ PPM (orders 0–4) β†’ Match Model β†’ Word Model β†’ High-Order Context (orders 5–8) β†’
273
+ Micro-Diffusion Tweedie Denoiser β€” feeding a 32-bit arithmetic coder.
274
+
275
+ | Benchmark | Midicoth | xz -9 | Improvement |
276
+ |-----------|----------|--------|-------------|
277
+ | alice29.txt (152 KB) | 2.119 bpb | 2.551 bpb | +16.9% |
278
+ | enwik8 (100 MB) | 1.753 bpb | 1.989 bpb | +11.9% |
279
+
280
+ Apache 2.0 | [Roberto Tacconelli](https://github.com/robtacconelli) | [arXiv:2603.08771](https://arxiv.org/abs/2603.08771)
281
+ """)
282
+
283
+ gr.HTML(f"<script>{COMPRESS_UPLOAD_JS}</script>")
284
+ gr.HTML(f"<script>{DECOMPRESS_UPLOAD_JS}</script>")
285
 
286
+ demo.queue()
287
  demo.launch()
requirements.txt CHANGED
@@ -1 +1,2 @@
1
- gradio==4.44.0
 
 
1
+ spaces>=0.20.0
2
+ gradio>=4.0.0