st4ck commited on
Commit
3f05a44
Β·
1 Parent(s): 3d052c5

Add text input, base64 output/input, 3-tab UI (Compress Text / Compress File / Decompress)

Browse files
Files changed (1) hide show
  1. app.py +286 -87
app.py CHANGED
@@ -1,64 +1,182 @@
1
  #!/usr/bin/env python3
2
  """Midicoth β€” Micro-Diffusion Compression β€” HuggingFace Space Demo."""
3
 
4
- import os, subprocess, tempfile
5
  from flask import Flask, request, send_file, render_template_string
6
 
7
  app = Flask(__name__)
8
  BINARY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mdc")
9
 
10
- HTML = """<!DOCTYPE html>
11
- <html lang="en">
12
- <head>
13
- <meta charset="UTF-8">
14
- <meta name="viewport" content="width=device-width,initial-scale=1">
15
- <title>Midicoth β€” Micro-Diffusion Compression</title>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  <style>
17
- body{font-family:system-ui,sans-serif;max-width:720px;margin:40px auto;padding:0 20px;background:#0f172a;color:#e2e8f0}
18
- h1{font-size:1.8em;margin-bottom:4px}
19
- p.sub{color:#94a3b8;margin-top:0}
20
- .card{background:#1e293b;border-radius:12px;padding:24px;margin:20px 0}
21
- h2{font-size:1.1em;margin-top:0;color:#7dd3fc}
22
- label{display:block;margin-bottom:8px;font-size:.9em;color:#94a3b8}
23
- input[type=file]{width:100%;padding:10px;background:#0f172a;border:1px solid #334155;border-radius:8px;color:#e2e8f0;box-sizing:border-box}
24
- button{margin-top:12px;padding:10px 28px;background:#6366f1;color:white;border:none;border-radius:8px;font-size:1em;font-weight:600;cursor:pointer}
25
- button:hover{background:#4f46e5}
26
- .error{color:#f87171;margin-top:10px;font-size:.9em}
27
- .stats{margin-top:10px;font-size:.9em;color:#94a3b8;background:#0f172a;padding:10px;border-radius:6px}
28
- a.dl{display:inline-block;margin-top:10px;padding:10px 24px;background:#22c55e;color:white;border-radius:8px;text-decoration:none;font-weight:600}
29
- footer{margin-top:40px;font-size:.8em;color:#475569;text-align:center}
30
- table{border-collapse:collapse;width:100%;margin-top:10px}
31
- td,th{padding:6px 12px;border:1px solid #334155;font-size:.9em}
32
- th{color:#7dd3fc;font-weight:600}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  </style>
34
- </head>
35
- <body>
 
36
  <h1>πŸ—œοΈ Midicoth</h1>
37
- <p class="sub">Micro-Diffusion Compression Β· Binary Tree Tweedie Denoising Β· No neural network Β· No GPU</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
- <div class="card">
40
- <h2>Compress</h2>
41
- <form method="post" action="/compress" enctype="multipart/form-data">
42
  <label>Upload any file:</label>
43
  <input type="file" name="file" required>
44
  <button type="submit">Compress β†’</button>
45
  </form>
46
- {% if compress_error %}<p class="error">{{ compress_error }}</p>{% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  </div>
48
 
49
- <div class="card">
50
- <h2>Decompress</h2>
51
- <form method="post" action="/decompress" enctype="multipart/form-data">
52
- <label>Upload a <code>.mdc</code> file:</label>
53
- <input type="file" name="file" accept=".mdc" required>
54
- <button type="submit">Decompress β†’</button>
 
 
 
 
55
  </form>
56
- {% if decompress_error %}<p class="error">{{ decompress_error }}</p>{% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  </div>
58
 
59
- <div class="card" style="font-size:.88em;color:#94a3b8">
60
- <b style="color:#e2e8f0">How it works:</b> PPM (orders 0–4) β†’ Match Model β†’ Word Model β†’
61
- High-Order Context (orders 5–8) β†’ Micro-Diffusion Tweedie Denoiser β†’ Arithmetic Coder.<br><br>
62
  <table>
63
  <tr><th>Benchmark</th><th>Midicoth</th><th>xz -9</th><th>Improvement</th></tr>
64
  <tr><td>alice29.txt (152 KB)</td><td>2.119 bpb</td><td>2.551 bpb</td><td>+16.9%</td></tr>
@@ -66,72 +184,153 @@ HTML = """<!DOCTYPE html>
66
  </table>
67
  </div>
68
 
69
- <footer>Apache 2.0 Β· <a href="https://github.com/robtacconelli/midicoth" style="color:#6366f1">GitHub</a>
70
- Β· <a href="https://arxiv.org/abs/2603.08771" style="color:#6366f1">arXiv:2603.08771</a></footer>
71
- </body></html>"""
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
- def fmt(n):
75
- if n < 1024: return f"{n} B"
76
- if n < 1024**2: return f"{n/1024:.1f} KB"
77
- return f"{n/1024**2:.2f} MB"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  @app.route("/health")
81
  def health():
82
  return "ok", 200
83
 
84
-
85
  @app.route("/")
86
  def index():
87
- return render_template_string(HTML)
88
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
- @app.route("/compress", methods=["POST"])
91
- def compress():
92
  f = request.files.get("file")
93
  if not f or not f.filename:
94
- return render_template_string(HTML, compress_error="No file uploaded.")
95
- with tempfile.TemporaryDirectory() as d:
96
- inp = os.path.join(d, "input")
97
- out = os.path.join(d, "output.mdc")
98
- f.save(inp)
99
- r = subprocess.run([BINARY, "compress", inp, out], capture_output=True, text=True)
100
- if r.returncode != 0:
101
- return render_template_string(HTML, compress_error=f"Compression failed: {r.stderr}")
102
- in_sz = os.path.getsize(inp)
103
- out_sz = os.path.getsize(out)
104
- bpb = out_sz * 8 / in_sz
105
- stats = (f"Original: {fmt(in_sz)} β†’ Compressed: {fmt(out_sz)} "
106
- f"({out_sz/in_sz*100:.1f}%, {bpb:.3f} bpb, "
107
- f"saved {100-out_sz/in_sz*100:.1f}%)")
108
- return send_file(out, as_attachment=True,
109
- download_name=f.filename + ".mdc",
110
- mimetype="application/octet-stream")
 
 
 
 
 
 
 
 
111
 
 
 
 
 
 
 
112
 
113
- @app.route("/decompress", methods=["POST"])
114
- def decompress():
 
 
115
  f = request.files.get("file")
116
  if not f or not f.filename:
117
- return render_template_string(HTML, decompress_error="No file uploaded.")
118
- with tempfile.TemporaryDirectory() as d:
119
- inp = os.path.join(d, "input.mdc")
120
- out = os.path.join(d, "output")
121
- f.save(inp)
122
- # Validate magic
123
- with open(inp, "rb") as fh:
124
- magic = fh.read(4)
125
- if magic != b"MDC7":
126
- return render_template_string(HTML, decompress_error="Not a valid Midicoth (.mdc) file.")
127
- r = subprocess.run([BINARY, "decompress", inp, out], capture_output=True, text=True)
128
- if r.returncode != 0:
129
- return render_template_string(HTML, decompress_error=f"Decompression failed: {r.stderr}")
130
- name = f.filename[:-4] if f.filename.endswith(".mdc") else f.filename + ".restored"
131
- return send_file(out, as_attachment=True,
132
- download_name=name,
133
- mimetype="application/octet-stream")
134
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
  if __name__ == "__main__":
137
  app.run(host="0.0.0.0", port=7860, debug=False)
 
1
  #!/usr/bin/env python3
2
  """Midicoth β€” Micro-Diffusion Compression β€” HuggingFace Space Demo."""
3
 
4
+ import os, subprocess, tempfile, base64
5
  from flask import Flask, request, send_file, render_template_string
6
 
7
  app = Flask(__name__)
8
  BINARY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mdc")
9
 
10
+ # ---------------------------------------------------------------------------
11
+ # Helpers
12
+ # ---------------------------------------------------------------------------
13
+
14
+ def fmt(n):
15
+ if n < 1024: return f"{n} B"
16
+ if n < 1024**2: return f"{n/1024:.1f} KB"
17
+ return f"{n/1024**2:.2f} MB"
18
+
19
+ def is_utf8(data):
20
+ try:
21
+ data.decode("utf-8"); return True
22
+ except UnicodeDecodeError:
23
+ return False
24
+
25
+ def dl_link(b64, name, label, color="#22c55e"):
26
+ return (f'<a class="dl" style="background:{color}" '
27
+ f'href="data:application/octet-stream;base64,{b64}" download="{name}">'
28
+ f'{label}</a>')
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # HTML
32
+ # ---------------------------------------------------------------------------
33
+
34
+ STYLE = """
35
  <style>
36
+ *{box-sizing:border-box}
37
+ body{font-family:system-ui,sans-serif;max-width:780px;margin:40px auto;padding:0 20px;
38
+ background:#0f172a;color:#e2e8f0}
39
+ h1{font-size:1.8em;margin-bottom:4px}
40
+ .sub{color:#94a3b8;margin-top:0;font-size:.95em}
41
+ /* tabs */
42
+ .tabs{display:flex;gap:4px;margin:24px 0 0}
43
+ .tab{padding:9px 18px;border-radius:8px 8px 0 0;cursor:pointer;font-size:.9em;font-weight:600;
44
+ background:#1e293b;color:#94a3b8;border:none;transition:.15s}
45
+ .tab.active{background:#6366f1;color:white}
46
+ .panel{display:none;background:#1e293b;border-radius:0 8px 8px 8px;padding:24px}
47
+ .panel.active{display:block}
48
+ /* form elements */
49
+ label{display:block;margin-bottom:6px;font-size:.88em;color:#94a3b8}
50
+ input[type=file]{width:100%;padding:10px;background:#0f172a;border:1px solid #334155;
51
+ border-radius:8px;color:#e2e8f0}
52
+ textarea{width:100%;padding:10px;background:#0f172a;border:1px solid #334155;border-radius:8px;
53
+ color:#e2e8f0;font-family:monospace;font-size:.85em;resize:vertical}
54
+ button[type=submit]{margin-top:12px;padding:10px 28px;background:#6366f1;color:white;
55
+ border:none;border-radius:8px;font-size:1em;font-weight:600;cursor:pointer}
56
+ button[type=submit]:hover{background:#4f46e5}
57
+ /* results */
58
+ .result{margin-top:18px;padding:16px;background:#0f172a;border-radius:8px;font-size:.9em}
59
+ .stats{color:#94a3b8;margin-bottom:10px;line-height:1.7}
60
+ .dl{display:inline-block;padding:9px 22px;border-radius:8px;text-decoration:none;
61
+ font-weight:600;font-size:.9em;margin-right:8px;margin-top:4px;color:white}
62
+ .b64box{margin-top:12px}
63
+ .b64box label{color:#7dd3fc;font-size:.82em;margin-bottom:4px}
64
+ .copy-btn{padding:5px 12px;background:#334155;border:none;border-radius:6px;color:#e2e8f0;
65
+ font-size:.78em;cursor:pointer;margin-left:8px;vertical-align:middle}
66
+ .copy-btn:hover{background:#475569}
67
+ .error{color:#f87171;font-size:.9em;margin-top:10px}
68
+ /* info table */
69
+ .info{margin-top:28px;background:#1e293b;border-radius:12px;padding:20px;font-size:.85em;color:#94a3b8}
70
+ .info b{color:#e2e8f0}
71
+ table{border-collapse:collapse;width:100%;margin-top:8px}
72
+ td,th{padding:6px 12px;border:1px solid #334155}
73
+ th{color:#7dd3fc;font-weight:600}
74
+ footer{margin-top:32px;font-size:.8em;color:#475569;text-align:center}
75
+ a{color:#6366f1}
76
  </style>
77
+ """
78
+
79
+ PAGE = STYLE + """
80
  <h1>πŸ—œοΈ Midicoth</h1>
81
+ <p class="sub">Micro-Diffusion Compression &middot; Binary Tree Tweedie Denoising
82
+ &middot; No neural network &middot; No GPU</p>
83
+
84
+ <div class="tabs">
85
+ <button class="tab {% if tab=='ct' %}active{% endif %}" onclick="show('ct')">Compress Text</button>
86
+ <button class="tab {% if tab=='cf' %}active{% endif %}" onclick="show('cf')">Compress File</button>
87
+ <button class="tab {% if tab=='dc' %}active{% endif %}" onclick="show('dc')">Decompress</button>
88
+ </div>
89
+
90
+ <!-- Compress Text -->
91
+ <div class="panel {% if tab=='ct' %}active{% endif %}" id="ct">
92
+ <form method="post" action="/compress_text">
93
+ <label>Input text (paste or type):</label>
94
+ <textarea name="text" rows="7" placeholder="Paste text here...">{{ form_text or '' }}</textarea>
95
+ <button type="submit">Compress β†’</button>
96
+ </form>
97
+ {% if ct_error %}<p class="error">{{ ct_error }}</p>{% endif %}
98
+ {% if ct_result %}
99
+ <div class="result">
100
+ <div class="stats">{{ ct_result.stats | safe }}</div>
101
+ <a class="dl" href="{{ ct_result.dl_href }}" download="{{ ct_result.dl_name }}">
102
+ ⬇ Download {{ ct_result.dl_name }} ({{ ct_result.out_sz }})
103
+ </a>
104
+ <div class="b64box">
105
+ <label>Compressed data (base64)
106
+ <button type="button" class="copy-btn" onclick="copyB64('ct-b64')">Copy</button>
107
+ </label>
108
+ <textarea id="ct-b64" rows="3" readonly>{{ ct_result.b64 }}</textarea>
109
+ </div>
110
+ </div>
111
+ {% endif %}
112
+ </div>
113
 
114
+ <!-- Compress File -->
115
+ <div class="panel {% if tab=='cf' %}active{% endif %}" id="cf">
116
+ <form method="post" action="/compress_file" enctype="multipart/form-data">
117
  <label>Upload any file:</label>
118
  <input type="file" name="file" required>
119
  <button type="submit">Compress β†’</button>
120
  </form>
121
+ {% if cf_error %}<p class="error">{{ cf_error }}</p>{% endif %}
122
+ {% if cf_result %}
123
+ <div class="result">
124
+ <div class="stats">{{ cf_result.stats | safe }}</div>
125
+ <a class="dl" href="{{ cf_result.dl_href }}" download="{{ cf_result.dl_name }}">
126
+ ⬇ Download {{ cf_result.dl_name }} ({{ cf_result.out_sz }})
127
+ </a>
128
+ <div class="b64box">
129
+ <label>Compressed data (base64)
130
+ <button type="button" class="copy-btn" onclick="copyB64('cf-b64')">Copy</button>
131
+ </label>
132
+ <textarea id="cf-b64" rows="3" readonly>{{ cf_result.b64 }}</textarea>
133
+ </div>
134
+ </div>
135
+ {% endif %}
136
  </div>
137
 
138
+ <!-- Decompress -->
139
+ <div class="panel {% if tab=='dc' %}active{% endif %}" id="dc">
140
+ <p style="color:#94a3b8;font-size:.88em;margin-top:0">
141
+ Upload a <code>.mdc</code> file <b>or</b> paste base64 data from the Compress tabs.
142
+ </p>
143
+
144
+ <form method="post" action="/decompress_file" enctype="multipart/form-data">
145
+ <label>Upload .mdc file:</label>
146
+ <input type="file" name="file" accept=".mdc">
147
+ <button type="submit">Decompress file β†’</button>
148
  </form>
149
+
150
+ <hr style="border-color:#334155;margin:20px 0">
151
+
152
+ <form method="post" action="/decompress_b64">
153
+ <label>Or paste base64 data:</label>
154
+ <textarea name="b64" rows="4" placeholder="Paste base64 here...">{{ form_b64 or '' }}</textarea>
155
+ <button type="submit">Decompress base64 β†’</button>
156
+ </form>
157
+
158
+ {% if dc_error %}<p class="error">{{ dc_error }}</p>{% endif %}
159
+ {% if dc_result %}
160
+ <div class="result">
161
+ <div class="stats">{{ dc_result.stats | safe }}</div>
162
+ {% if dc_result.text is not none %}
163
+ <div class="b64box">
164
+ <label>Decompressed text</label>
165
+ <textarea rows="10" readonly>{{ dc_result.text }}</textarea>
166
+ </div>
167
+ {% endif %}
168
+ {% if dc_result.dl_href %}
169
+ <a class="dl" href="{{ dc_result.dl_href }}" download="{{ dc_result.dl_name }}">
170
+ ⬇ Download {{ dc_result.dl_name }} ({{ dc_result.out_sz }})
171
+ </a>
172
+ {% endif %}
173
+ </div>
174
+ {% endif %}
175
  </div>
176
 
177
+ <div class="info">
178
+ <b>How it works:</b> PPM (orders 0–4) β†’ Match Model β†’ Word Model β†’
179
+ High-Order Context (orders 5–8) β†’ Micro-Diffusion Tweedie Denoiser β†’ Arithmetic Coder.
180
  <table>
181
  <tr><th>Benchmark</th><th>Midicoth</th><th>xz -9</th><th>Improvement</th></tr>
182
  <tr><td>alice29.txt (152 KB)</td><td>2.119 bpb</td><td>2.551 bpb</td><td>+16.9%</td></tr>
 
184
  </table>
185
  </div>
186
 
187
+ <footer>Apache 2.0 &middot; <a href="https://github.com/robtacconelli/midicoth">GitHub</a>
188
+ &middot; <a href="https://arxiv.org/abs/2603.08771">arXiv:2603.08771</a></footer>
 
189
 
190
+ <script>
191
+ function show(id){
192
+ document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
193
+ document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));
194
+ document.getElementById(id).classList.add('active');
195
+ event.target.classList.add('active');
196
+ }
197
+ function copyB64(id){
198
+ var ta=document.getElementById(id);
199
+ navigator.clipboard.writeText(ta.value).then(()=>{
200
+ var btn=ta.previousElementSibling.querySelector('.copy-btn');
201
+ btn.textContent='Copied!'; setTimeout(()=>btn.textContent='Copy',1500);
202
+ });
203
+ }
204
+ </script>
205
+ """
206
 
207
+ # ---------------------------------------------------------------------------
208
+ # Compression logic
209
+ # ---------------------------------------------------------------------------
210
+
211
+ def run_compress(data, filename):
212
+ """Returns (b64_str, in_sz, out_sz, bpb) or raises RuntimeError."""
213
+ with tempfile.TemporaryDirectory() as d:
214
+ inp = os.path.join(d, "input")
215
+ out = os.path.join(d, "output.mdc")
216
+ with open(inp, "wb") as fh:
217
+ fh.write(data)
218
+ r = subprocess.run([BINARY, "compress", inp, out], capture_output=True, text=True)
219
+ if r.returncode != 0:
220
+ raise RuntimeError(r.stderr or r.stdout)
221
+ in_sz = os.path.getsize(inp)
222
+ out_sz = os.path.getsize(out)
223
+ with open(out, "rb") as fh:
224
+ compressed = fh.read()
225
+ b64 = base64.b64encode(compressed).decode()
226
+ bpb = out_sz * 8 / in_sz
227
+ return b64, in_sz, out_sz, bpb
228
 
229
+ def run_decompress(data):
230
+ """Returns (restored_bytes,) or raises RuntimeError."""
231
+ with tempfile.TemporaryDirectory() as d:
232
+ inp = os.path.join(d, "input.mdc")
233
+ out = os.path.join(d, "output")
234
+ with open(inp, "wb") as fh:
235
+ fh.write(data)
236
+ r = subprocess.run([BINARY, "decompress", inp, out], capture_output=True, text=True)
237
+ if r.returncode != 0:
238
+ raise RuntimeError(r.stderr or r.stdout)
239
+ with open(out, "rb") as fh:
240
+ return fh.read()
241
+
242
+ def compress_result(b64, in_sz, out_sz, bpb, out_name):
243
+ dl_href = f"data:application/octet-stream;base64,{b64}"
244
+ stats = (f"<b>Original:</b> {fmt(in_sz)} &nbsp;β†’&nbsp; "
245
+ f"<b>Compressed:</b> {fmt(out_sz)} &nbsp;|&nbsp; "
246
+ f"{out_sz/in_sz*100:.1f}% &nbsp;|&nbsp; "
247
+ f"<b>{bpb:.3f} bpb</b> &nbsp;|&nbsp; "
248
+ f"Saved {100-out_sz/in_sz*100:.1f}%")
249
+ return dict(stats=stats, dl_href=dl_href, dl_name=out_name,
250
+ out_sz=fmt(out_sz), b64=b64)
251
+
252
+ # ---------------------------------------------------------------------------
253
+ # Routes
254
+ # ---------------------------------------------------------------------------
255
 
256
  @app.route("/health")
257
  def health():
258
  return "ok", 200
259
 
 
260
  @app.route("/")
261
  def index():
262
+ return render_template_string(PAGE, tab="ct")
263
 
264
+ @app.route("/compress_text", methods=["POST"])
265
+ def compress_text():
266
+ text = request.form.get("text", "").strip()
267
+ if not text:
268
+ return render_template_string(PAGE, tab="ct", ct_error="Please enter some text.")
269
+ try:
270
+ data = text.encode("utf-8")
271
+ b64, in_sz, out_sz, bpb = run_compress(data, "text")
272
+ result = compress_result(b64, in_sz, out_sz, bpb, "compressed.mdc")
273
+ return render_template_string(PAGE, tab="ct", ct_result=result, form_text=text)
274
+ except Exception as e:
275
+ return render_template_string(PAGE, tab="ct", ct_error=str(e), form_text=text)
276
 
277
+ @app.route("/compress_file", methods=["POST"])
278
+ def compress_file():
279
  f = request.files.get("file")
280
  if not f or not f.filename:
281
+ return render_template_string(PAGE, tab="cf", cf_error="No file uploaded.")
282
+ try:
283
+ data = f.read()
284
+ b64, in_sz, out_sz, bpb = run_compress(data, f.filename)
285
+ out_name = f.filename + ".mdc"
286
+ result = compress_result(b64, in_sz, out_sz, bpb, out_name)
287
+ return render_template_string(PAGE, tab="cf", cf_result=result)
288
+ except Exception as e:
289
+ return render_template_string(PAGE, tab="cf", cf_error=str(e))
290
+
291
+ def _decompress_response(data, tab, form_b64=""):
292
+ if data[:4] != b"MDC7":
293
+ return render_template_string(PAGE, tab=tab,
294
+ dc_error="Not a valid Midicoth (.mdc) file.",
295
+ form_b64=form_b64)
296
+ try:
297
+ restored = run_decompress(data)
298
+ except Exception as e:
299
+ return render_template_string(PAGE, tab=tab, dc_error=str(e), form_b64=form_b64)
300
+
301
+ in_sz = len(data)
302
+ out_sz = len(restored)
303
+ stats = (f"<b>Compressed:</b> {fmt(in_sz)} &nbsp;β†’&nbsp; "
304
+ f"<b>Restored:</b> {fmt(out_sz)} &nbsp;|&nbsp; Lossless βœ“")
305
+ result = dict(stats=stats, text=None, dl_href=None, dl_name=None, out_sz=fmt(out_sz))
306
 
307
+ if is_utf8(restored):
308
+ result["text"] = restored.decode("utf-8")
309
+ else:
310
+ b64 = base64.b64encode(restored).decode()
311
+ result["dl_href"] = f"data:application/octet-stream;base64,{b64}"
312
+ result["dl_name"] = "restored.bin"
313
 
314
+ return render_template_string(PAGE, tab=tab, dc_result=result, form_b64=form_b64)
315
+
316
+ @app.route("/decompress_file", methods=["POST"])
317
+ def decompress_file():
318
  f = request.files.get("file")
319
  if not f or not f.filename:
320
+ return render_template_string(PAGE, tab="dc", dc_error="No file uploaded.")
321
+ return _decompress_response(f.read(), tab="dc")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
 
323
+ @app.route("/decompress_b64", methods=["POST"])
324
+ def decompress_b64():
325
+ raw = request.form.get("b64", "").strip()
326
+ if not raw:
327
+ return render_template_string(PAGE, tab="dc", dc_error="Please paste base64 data.")
328
+ try:
329
+ data = base64.b64decode(raw)
330
+ except Exception:
331
+ return render_template_string(PAGE, tab="dc",
332
+ dc_error="Invalid base64 data.", form_b64=raw)
333
+ return _decompress_response(data, tab="dc", form_b64=raw)
334
 
335
  if __name__ == "__main__":
336
  app.run(host="0.0.0.0", port=7860, debug=False)