SuhasGholkar commited on
Commit
5267841
·
verified ·
1 Parent(s): 54304c7

Create video_export.py

Browse files
Files changed (1) hide show
  1. src/video_export.py +167 -0
src/video_export.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/video_export.py
2
+ import os
3
+ from datetime import datetime
4
+ import numpy as np
5
+ import pandas as pd
6
+ import streamlit as st
7
+ from PIL import Image, ImageDraw, ImageFont
8
+ from moviepy.editor import ImageClip, concatenate_videoclips
9
+ from moviepy.audio.io.AudioFileClip import AudioFileClip
10
+ from gtts import gTTS
11
+
12
+ from src.utils import APP_DIR
13
+
14
+ def ensure_video_deps():
15
+ # ensure font exists; DejaVu Sans is usually present via fonts-dejavu-core
16
+ pass
17
+
18
+ def _get_font(size: int):
19
+ for p in [
20
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
21
+ "/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed.ttf",
22
+ ]:
23
+ if os.path.exists(p):
24
+ return ImageFont.truetype(p, size=size)
25
+ return ImageFont.load_default()
26
+
27
+ def _draw_multiline(draw, text, xy, font, fill, max_w, leading=6):
28
+ text = (text or "").strip()
29
+ if not text:
30
+ text = "."
31
+ words = text.split()
32
+ lines, cur = [], []
33
+ for w in words:
34
+ test = " ".join(cur + [w]).strip()
35
+ w_px = draw.textbbox((0,0), test, font=font)[2]
36
+ if w_px <= max_w: cur.append(w)
37
+ else:
38
+ lines.append(" ".join(cur).strip()); cur = [w]
39
+ if cur: lines.append(" ".join(cur).strip())
40
+ x, y = xy
41
+ for line in lines:
42
+ draw.text((x,y), line, font=font, fill=fill)
43
+ _,_,_,h = draw.textbbox((x,y), line, font=font)
44
+ y += h + leading
45
+ return y
46
+
47
+ def build_slide(title, body, w=1280, h=720, seconds=3.5, bg=(245,247,250)):
48
+ img = Image.new("RGB", (w,h), color=bg)
49
+ d = ImageDraw.Draw(img)
50
+ title_font = _get_font(48)
51
+ body_font = _get_font(36)
52
+ left, right = 60, w-60
53
+ max_w = right - left
54
+ y0 = 80
55
+ y1 = _draw_multiline(d, title, (left,y0), title_font, "black", max_w, 8)
56
+ y_body = max(y0 + 80, y1 + 20)
57
+ _draw_multiline(d, body, (left,y_body), body_font, "black", max_w, 6)
58
+ frame = np.array(img)
59
+ return ImageClip(frame).set_duration(seconds)
60
+
61
+ def tts(text: str, lang: str, out_mp3: str):
62
+ safe = (text or ".").strip()
63
+ gTTS(text=safe, lang=lang, slow=False).save(out_mp3)
64
+ return out_mp3
65
+
66
+ def narration_text(title: str, body: str, max_chars: int = 600) -> str:
67
+ txt = f"{title}. {body}".strip()
68
+ return (txt[:max_chars-1].rsplit(" ", 1)[0] + "…") if len(txt) > max_chars else txt
69
+
70
+ def make_video(rows, title="Olist Chatbot — Conversation", narrate=True, tts_lang="en",
71
+ seconds_per_slide=3.5, out_path="chat_video.mp4"):
72
+ tmp_dir = APP_DIR / ".cache" / "tts_tmp"
73
+ if narrate:
74
+ os.makedirs(tmp_dir, exist_ok=True)
75
+
76
+ clips = []
77
+ def _add(title, body, bg=None):
78
+ duration = seconds_per_slide
79
+ audio = None
80
+ if narrate:
81
+ try:
82
+ mp3 = str(tmp_dir / f"tts_{len(clips):04d}.mp3")
83
+ tts(narration_text(title, body), tts_lang, mp3)
84
+ audio = AudioFileClip(mp3)
85
+ duration = max(seconds_per_slide, float(audio.duration) + 0.4)
86
+ except Exception:
87
+ audio = None
88
+ clip = build_slide(title, body, seconds=duration, bg=(245,247,250) if bg is None else bg)
89
+ if narrate and audio is not None:
90
+ clip = clip.set_audio(audio)
91
+ clips.append(clip)
92
+
93
+ _add(title, "Auto-generated summary video")
94
+ for _, r in rows.iterrows():
95
+ ts = str(r.get("timestamp",""))
96
+ msg = str(r.get("user_message",""))
97
+ rep = str(r.get("bot_reply",""))
98
+ _add(f"User • {ts}", msg, bg=(255,255,255))
99
+ _add("Agent", rep, bg=(242,255,242))
100
+ _add("Thanks for watching", "Exported from the Olist app")
101
+
102
+ video = concatenate_videoclips(clips, method="compose")
103
+ video.write_videofile(
104
+ out_path, fps=24, codec="libx264",
105
+ audio=narrate, audio_codec="aac" if narrate else None,
106
+ threads=2, preset="medium", ffmpeg_params=["-movflags","+faststart"]
107
+ )
108
+ # cleanup tts mp3s best effort
109
+ try:
110
+ if narrate and os.path.isdir(tmp_dir):
111
+ for f in os.listdir(tmp_dir):
112
+ if f.endswith(".mp3"):
113
+ os.remove(tmp_dir / f)
114
+ except Exception:
115
+ pass
116
+ return out_path
117
+
118
+ def render_admin_export_video_tab(conn):
119
+ st.title("📥 Export + 🎬 Video (Admin)")
120
+ st.write("Download CSV/TXT for all users, or render a narrated MP4.")
121
+
122
+ df = pd.read_sql_query(
123
+ "SELECT user_id, timestamp, user_message, bot_reply FROM chat_history ORDER BY timestamp DESC",
124
+ conn
125
+ )
126
+ if df.empty:
127
+ st.info("No chats yet.")
128
+ return
129
+
130
+ # CSV/TXT export for admins (all users)
131
+ csv_bytes = df.to_csv(index=False).encode("utf-8")
132
+ st.download_button("Download all chats (CSV)", csv_bytes, file_name="all_chats.csv", mime="text/csv")
133
+
134
+ txt_lines = []
135
+ for _, r in df.iterrows():
136
+ txt_lines.append(f"[{r['timestamp']}] user_id={r['user_id']} | {r['user_message']}")
137
+ txt_lines.append(f"Bot: {r['bot_reply']}")
138
+ txt_lines.append("---")
139
+ st.download_button("Download all chats (TXT)", "\n".join(txt_lines).encode("utf-8"),
140
+ file_name="all_chats.txt", mime="text/plain")
141
+
142
+ st.markdown("---")
143
+ st.subheader("🎬 Build video")
144
+ c1, c2 = st.columns(2)
145
+ with c1:
146
+ narrate = st.checkbox("Add voiceover (gTTS)", value=True)
147
+ seconds_per_slide = st.slider("Seconds per slide", 2.0, 8.0, 3.5, 0.5)
148
+ with c2:
149
+ tts_lang = st.selectbox("TTS language", ["en","hi","pt","es","fr"], index=0)
150
+ max_rows = st.number_input("Max messages (0 = all)", min_value=0, max_value=200, value=50, step=1)
151
+
152
+ rows = df if max_rows == 0 else df.head(int(max_rows))
153
+
154
+ if st.button("Make Video"):
155
+ try:
156
+ out_path = make_video(rows, narrate=narrate, tts_lang=tts_lang,
157
+ seconds_per_slide=seconds_per_slide, out_path="chat_video.mp4")
158
+ with open(out_path, "rb") as f:
159
+ data = f.read()
160
+ st.success("Video ready!")
161
+ st.video(data)
162
+ st.download_button(
163
+ "Download MP4", data, file_name=f"chat_video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4",
164
+ mime="video/mp4"
165
+ )
166
+ except Exception as e:
167
+ st.error(f"Video generation failed: {e}")