userIdc2024 commited on
Commit
44b0054
·
verified ·
1 Parent(s): 92d6446

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +308 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,310 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
1
+ from __future__ import annotations
2
+
 
3
  import streamlit as st
4
+ import tempfile
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Optional
8
+ import math, random, hashlib, base64, binascii, subprocess, shutil
9
+ import logging
10
+ from dotenv import load_dotenv
11
+
12
+ # Load environment variables
13
+ load_dotenv()
14
+
15
+ # Set up logging
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger(__name__)
18
+
19
+ def _ffmpeg_bin() -> str:
20
+ try:
21
+ from imageio_ffmpeg import get_ffmpeg_exe
22
+ return get_ffmpeg_exe()
23
+ except Exception:
24
+ p = os.environ.get("FFMPEG_BIN") or shutil.which("ffmpeg")
25
+ if not p:
26
+ raise FileNotFoundError(
27
+ "ffmpeg not found. Install Homebrew ffmpeg or `pip install imageio-ffmpeg`, "
28
+ "or set FFMPEG_BIN to the binary path."
29
+ )
30
+ return p
31
+
32
+ FFMPEG = _ffmpeg_bin()
33
+
34
+ def _run_ffmpeg(cmd: list[str]) -> None:
35
+ cmd = cmd[:] ; cmd[0] = FFMPEG
36
+ proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
37
+ if proc.returncode != 0:
38
+ raise RuntimeError(proc.stderr.decode(errors="ignore") or "ffmpeg failed")
39
+
40
+ def _next_available(p: Path) -> Path:
41
+ if not p.exists(): return p
42
+ stem, suf = p.stem, p.suffix
43
+ i = 1
44
+ while True:
45
+ q = p.with_name(f"{stem}_{i}{suf}")
46
+ if not q.exists(): return q
47
+ i += 1
48
+
49
+ def _derive_out(in_path: Path, out_path: Optional[str]) -> Path:
50
+ if out_path is None:
51
+ p = in_path.with_name(f"{in_path.stem}_augmented2.mp4")
52
+ else:
53
+ p = Path(out_path)
54
+ if p.is_dir() or str(out_path).endswith(("/", "\\")):
55
+ p = Path(out_path) / f"{in_path.stem}_augmented2.mp4"
56
+ if p.suffix == "":
57
+ p = p.with_suffix(".mp4")
58
+ p = _next_available(p)
59
+ p.parent.mkdir(parents=True, exist_ok=True)
60
+ return p
61
+
62
+ def _sha256(path: Path, chunk: int = 1 << 20) -> str:
63
+ h = hashlib.sha256()
64
+ with path.open("rb") as f:
65
+ for b in iter(lambda: f.read(chunk), b""): h.update(b)
66
+ return h.hexdigest()
67
+
68
+ def _hashid_short(sha256_hex: str, length: int = 12) -> str:
69
+ b = binascii.unhexlify(sha256_hex)
70
+ return base64.urlsafe_b64encode(b).decode().rstrip("=")[:length]
71
+
72
+ def _rand_color_hex() -> str:
73
+ return f"0x{random.randrange(0, 0xFFFFFF+1):06x}"
74
+
75
+ def augment_video_random(
76
+ *,
77
+ input_path: str,
78
+ output_path: Optional[str] = None,
79
+ temporal_radius: int = 5,
80
+ seed: Optional[int] = None,
81
+ crf: int = 20,
82
+ preset: str = "medium",
83
+ ) -> str:
84
+ if seed is not None:
85
+ random.seed(seed)
86
+
87
+ k_b = random.randint(-5, 5)
88
+ k_c = random.randint(-5, 5)
89
+ k_h = random.randint(-5, 5)
90
+ k_s = random.randint(-5, 5)
91
+ k_w = random.randint(-5, 5)
92
+
93
+ brightness = max(-1.0, min(1.0, k_b * 0.05))
94
+ contrast = max(0.0, min(2.0, 1.0 + k_c * 0.05))
95
+ hue_rad = math.radians(k_h * 5.0)
96
+ sat_scale = max(0.0, 1.0 + k_s * 0.05)
97
+ border_px = max(1, min(5, abs(k_w)))
98
+ color = _rand_color_hex()
99
+
100
+ parts = []
101
+ if temporal_radius > 0:
102
+ parts.append(f"deflicker=size={2*temporal_radius+1}")
103
+ parts += [
104
+ f"hue=h={hue_rad:.6f}:s={sat_scale:.4f}",
105
+ f"eq=contrast={contrast:.4f}:brightness={brightness:.4f}",
106
+ ]
107
+ if border_px:
108
+ parts.append(f"pad=iw+{2*border_px}:ih+{2*border_px}:{border_px}:{border_px}:color={color}")
109
+ vf_with = ",".join(parts)
110
+ vf_no = ",".join(parts[1:]) if temporal_radius > 0 else vf_with
111
+
112
+ inp = Path(input_path)
113
+ if not inp.exists(): raise FileNotFoundError(f"Input not found: {inp}")
114
+ out = _derive_out(inp, output_path)
115
+
116
+ def _encode(vf: str) -> None:
117
+ cmd = [
118
+ "ffmpeg", "-nostdin", "-y",
119
+ "-i", str(inp),
120
+ "-map", "0:v:0", "-map", "0:a?",
121
+ "-vf", vf,
122
+ "-c:v", "libx264", "-preset", preset, "-crf", str(crf),
123
+ "-pix_fmt", "yuv420p",
124
+ "-c:a", "copy",
125
+ "-movflags", "+faststart",
126
+ str(out),
127
+ ]
128
+ try:
129
+ _run_ffmpeg(cmd)
130
+ except RuntimeError as e:
131
+ msg = str(e).lower()
132
+ if "no such filter" in msg and "deflicker" in msg and vf != vf_no:
133
+ _encode(vf_no)
134
+ elif "could not find tag for codec" in msg:
135
+ cmd2 = cmd[:]
136
+ i = cmd2.index("-c:a") + 1
137
+ cmd2[i] = "aac"
138
+ cmd2.insert(i + 1, "192k")
139
+ cmd2.insert(i + 1, "-b:a")
140
+ _run_ffmpeg(cmd2)
141
+ else:
142
+ raise
143
+
144
+ _encode(vf_with)
145
+ return str(out)
146
+
147
+ def process_video_with_hash_info(input_path: str, output_path: Optional[str] = None) -> dict:
148
+ out_path = augment_video_random(input_path=input_path, output_path=output_path)
149
+ inp = Path(input_path); out = Path(out_path)
150
+ in_sha = _sha256(inp); in_id = _hashid_short(in_sha)
151
+ out_sha = _sha256(out); out_id = _hashid_short(out_sha)
152
+
153
+ return {
154
+ "input_path": str(inp),
155
+ "input_sha256": in_sha,
156
+ "input_hashid": in_id,
157
+ "output_path": str(out),
158
+ "output_name": out.name,
159
+ "output_sha256": out_sha,
160
+ "output_hashid": out_id,
161
+ }
162
+
163
+ def check_token(token: str) -> tuple[bool, str]:
164
+ """
165
+ Check if the provided token is valid.
166
+ Returns (is_valid, error_message)
167
+ """
168
+ # Get the access token from environment variable
169
+ access_token = os.getenv("ACCESS_TOKEN")
170
+
171
+ if not access_token:
172
+
173
+ if token in valid_tokens:
174
+ return True, valid_tokens[token]
175
+ else:
176
+ return False, "Invalid access token. Please contact administrator."
177
+
178
+ # Check against environment token
179
+ if token == access_token:
180
+ return True, "Access granted"
181
+ else:
182
+ return False, "Invalid access token. Please contact administrator."
183
+
184
+ def create_main_app():
185
+ """Main application content after authentication"""
186
+
187
+ # Header
188
+ st.markdown("# Video Hash Augmentation Tool")
189
+ st.markdown("---")
190
+
191
+ # Upload and Generate section
192
+ col_upload, col_generate = st.columns([2, 1])
193
+
194
+ with col_upload:
195
+ uploaded_file = st.file_uploader(
196
+ "Choose a video file",
197
+ type=['mp4', 'avi', 'mov', 'mkv', 'wmv', 'flv', 'webm'],
198
+ help="Upload your video to generate an augmented version with a different hash"
199
+ )
200
+
201
+ with col_generate:
202
+ generate_clicked = st.button(
203
+ "Generate Augmented Video",
204
+ type="primary",
205
+ use_container_width=True,
206
+ disabled=uploaded_file is None
207
+ )
208
+
209
+ if uploaded_file is None:
210
+ st.info("Upload a video and click 'Generate' to process.")
211
+ elif generate_clicked:
212
+ st.success("Video processed successfully!")
213
+
214
+ if uploaded_file is not None:
215
+ st.success(f"File uploaded: {uploaded_file.name} ({uploaded_file.size / 1024 / 1024:.1f} MB)")
216
+ st.markdown("---")
217
+
218
+ # Two column layout for videos
219
+ col1, col2 = st.columns([1, 1], gap="large")
220
+
221
+ with col1:
222
+ st.markdown("### Original Video")
223
+ st.video(uploaded_file)
224
+
225
+ # Calculate and display original hash
226
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f"_{uploaded_file.name}") as tmp_file:
227
+ tmp_file.write(uploaded_file.getvalue())
228
+ tmp_file_path = tmp_file.name
229
+
230
+ original_hash = _sha256(Path(tmp_file_path))
231
+ original_hashid = _hashid_short(original_hash)
232
+
233
+ st.markdown("**Hash:**")
234
+ st.code(original_hashid)
235
+
236
+ os.unlink(tmp_file_path)
237
+
238
+ with col2:
239
+ st.markdown("### Augmented Video")
240
+
241
+ if generate_clicked:
242
+ try:
243
+ with st.spinner("Processing video..."):
244
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f"_{uploaded_file.name}") as tmp_input:
245
+ tmp_input.write(uploaded_file.getvalue())
246
+ tmp_input_path = tmp_input.name
247
+
248
+ with tempfile.TemporaryDirectory() as tmp_dir:
249
+ result = process_video_with_hash_info(
250
+ input_path=tmp_input_path,
251
+ output_path=tmp_dir
252
+ )
253
+
254
+ with open(result["output_path"], "rb") as f:
255
+ processed_video_bytes = f.read()
256
+
257
+ st.video(processed_video_bytes)
258
+
259
+ st.markdown("**Hash:**")
260
+ st.code(result['output_hashid'])
261
+
262
+ # Download section
263
+ st.markdown("---")
264
+ st.download_button(
265
+ label="Download Augmented Video",
266
+ data=processed_video_bytes,
267
+ file_name=result['output_name'],
268
+ mime="video/mp4",
269
+ type="primary",
270
+ use_container_width=True
271
+ )
272
+
273
+ except Exception as e:
274
+ st.error(f"Error processing video: {str(e)}")
275
+ st.info("Make sure FFmpeg is properly installed on your system.")
276
+
277
+ finally:
278
+ try:
279
+ os.unlink(tmp_input_path)
280
+ except:
281
+ pass
282
+ else:
283
+ st.info("Please upload a video file to get started.")
284
+
285
+ def main():
286
+ st.set_page_config(page_title="Video Hash Augmentation Tool", layout="wide")
287
+
288
+ if "authenticated" not in st.session_state:
289
+ st.session_state["authenticated"] = False
290
+
291
+ if not st.session_state["authenticated"]:
292
+ st.markdown("## Access Required")
293
+ token_input = st.text_input("Enter Access Token", type="password")
294
+
295
+ if st.button("Unlock App"):
296
+ ok, error_msg = check_token(token_input)
297
+ if ok:
298
+ st.session_state["authenticated"] = True
299
+ st.rerun()
300
+ else:
301
+ st.error(error_msg)
302
+ else:
303
+ create_main_app()
304
 
305
+ if __name__ == "__main__":
306
+ try:
307
+ logger.info("Launching Streamlit app...")
308
+ main()
309
+ except Exception as e:
310
+ logger.exception("Unhandled error during app launch.")