aigorithm commited on
Commit
adef775
·
verified ·
1 Parent(s): c204e52

Upload 2 files

Browse files
Files changed (2) hide show
  1. index.html +46 -87
  2. main.js +153 -70
index.html CHANGED
@@ -4,117 +4,76 @@
4
  <head>
5
  <meta charset="UTF-8" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
7
- <title>Full Fake Chat Animator</title>
8
  <style>
9
- body {
10
  margin: 0;
11
- font-family: sans-serif;
12
  background: #000;
13
- color: white;
 
 
14
  display: flex;
15
  flex-direction: column;
16
  align-items: center;
17
- padding: 16px;
18
- }
19
- .phone-frame {
20
- position: relative;
21
- width: 360px;
22
- height: 640px;
23
- overflow: hidden;
24
- border-radius: 24px;
25
- box-shadow: 0 0 10px rgba(0,0,0,0.4);
26
- z-index: 1;
27
- }
28
- #background-video {
29
- position: absolute;
30
- width: 100%;
31
- height: 100%;
32
- object-fit: cover;
33
- z-index: 1;
34
- }
35
- #chat {
36
- position: absolute;
37
  width: 100%;
38
- height: 100%;
 
 
39
  padding: 10px;
40
- overflow-y: auto;
41
- z-index: 2;
42
  }
43
- .message {
44
  display: flex;
45
- align-items: flex-end;
46
- margin-bottom: 8px;
 
47
  }
48
- .avatar {
49
- width: 28px;
50
- height: 28px;
51
  border-radius: 50%;
52
- margin: 0 8px;
53
  }
54
  .bubble {
55
- padding: 10px;
56
- border-radius: 18px;
57
  max-width: 70%;
58
- font-size: 14px;
59
  opacity: 0;
60
- animation: fadeIn 0.5s forwards;
61
  }
62
- .left { flex-direction: row; }
63
- .right { flex-direction: row-reverse; }
64
- .left .bubble { background: #eee; color: #000; }
65
- .right .bubble { background: #007aff; color: #fff; }
66
- .whatsapp .left .bubble { background: #e1ffc7; }
67
- .imessage .right .bubble { background: #007aff; }
68
- .instagram .bubble { background: #fff0f5; color: #000; }
69
- .reddit .bubble { background: #f6f7f8; border-left: 4px solid #ff4500; padding-left: 12px; }
70
-
71
- @keyframes fadeIn {
72
- to { opacity: 1; transform: translateY(0); }
73
- from { opacity: 0; transform: translateY(10px); }
74
- }
75
-
76
- .controls {
77
  width: 100%;
78
- max-width: 400px;
79
- margin-top: 20px;
80
  }
81
- .controls input, .controls select, .controls button {
82
- width: 100%;
83
  margin: 4px 0;
84
- padding: 8px;
85
- border-radius: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  }
87
  </style>
88
  </head>
89
  <body>
90
- <h2>Fake Chat Animator Pro</h2>
91
-
92
- <div class="controls">
93
- <label>Background MP4 Video:</label>
94
- <input type="file" accept="video/mp4" onchange="loadBackground(event)">
95
-
96
- <label>Choose Style:</label>
97
- <select id="styleSelect" onchange="switchStyle()">
98
- <option value="whatsapp">WhatsApp</option>
99
- <option value="imessage">iMessage</option>
100
- <option value="instagram">Instagram</option>
101
- <option value="reddit">Reddit</option>
102
- </select>
103
-
104
- <label>Chat Script (JSON):</label>
105
- <textarea id="chatScript" style="height: 120px; width: 100%;">[
106
- { "name": "Suren", "side": "left", "avatar": "https://i.pravatar.cc/30?img=1", "voice": "Rachel", "text": "Hey, are you there?" },
107
- { "name": "You", "side": "right", "avatar": "https://i.pravatar.cc/30?img=3", "voice": "Adam", "text": "Yeah, just got here!" }
108
- ]</textarea>
109
-
110
- <button onclick="renderAndRecord()">🎬 Export Chat with Voice</button>
111
- </div>
112
-
113
- <div class="phone-frame">
114
- <video id="background-video" autoplay loop muted></video>
115
- <div id="chat" class="whatsapp"></div>
116
- </div>
117
-
118
  <script type="module" src="main.js"></script>
119
  </body>
120
  </html>
 
4
  <head>
5
  <meta charset="UTF-8" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
7
+ <title>Chat Animator Fixed Recorder</title>
8
  <style>
9
+ html, body {
10
  margin: 0;
 
11
  background: #000;
12
+ height: 100%;
13
+ }
14
+ .app {
15
  display: flex;
16
  flex-direction: column;
17
  align-items: center;
18
+ background: #f0f2f5;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  width: 100%;
20
+ max-width: 360px;
21
+ height: 640px;
22
+ margin: auto;
23
  padding: 10px;
24
+ font-family: sans-serif;
25
+ box-sizing: border-box;
26
  }
27
+ .character, .message {
28
  display: flex;
29
+ align-items: center;
30
+ gap: 8px;
31
+ margin-bottom: 10px;
32
  }
33
+ .character img, .message img {
34
+ width: 30px;
35
+ height: 30px;
36
  border-radius: 50%;
 
37
  }
38
  .bubble {
39
+ padding: 10px 14px;
40
+ border-radius: 20px;
41
  max-width: 70%;
42
+ background: #fff;
43
  opacity: 0;
44
+ animation: fadeIn 1s forwards;
45
  }
46
+ .controls, .chat {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  width: 100%;
48
+ margin-top: 10px;
 
49
  }
50
+ input, select, button {
51
+ padding: 6px;
52
  margin: 4px 0;
53
+ width: 100%;
54
+ }
55
+ @keyframes fadeIn {
56
+ to {
57
+ opacity: 1;
58
+ }
59
+ }
60
+ .typing {
61
+ font-style: italic;
62
+ opacity: 0.6;
63
+ animation: fadeIn 0.8s forwards;
64
+ }
65
+ #recordArea {
66
+ background: white;
67
+ padding: 10px;
68
+ border-radius: 12px;
69
+ width: 360px;
70
+ height: 640px;
71
+ overflow-y: auto;
72
  }
73
  </style>
74
  </head>
75
  <body>
76
+ <div id="root"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  <script type="module" src="main.js"></script>
78
  </body>
79
  </html>
main.js CHANGED
@@ -1,82 +1,165 @@
1
 
2
- const chat = document.getElementById("chat");
3
- const bgVideo = document.getElementById("background-video");
4
- const voiceMap = {
 
5
  Rachel: "21m00Tcm4TlvDq8ikWAM",
6
  Adam: "pNInz6obpgDQGcFmaJgB",
7
  Bella: "EXAVITQu4vr4xnSDxMaL"
8
  };
9
  const apiKey = "sk_4e67c39c0e9cbc87462cd2643e1f4d1d9959d7d81203adc2";
10
 
11
- function switchStyle() {
12
- chat.className = document.getElementById("styleSelect").value;
13
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- function loadBackground(e) {
16
- const file = e.target.files[0];
17
- if (file) {
18
  const url = URL.createObjectURL(file);
19
- bgVideo.src = url;
20
- }
21
- }
22
-
23
- async function getVoiceBlob(text, voice) {
24
- const voiceId = voiceMap[voice] || voiceMap["Rachel"];
25
- const res = await fetch("https://api.elevenlabs.io/v1/text-to-speech/" + voiceId, {
26
- method: "POST",
27
- headers: {
28
- "Content-Type": "application/json",
29
- "xi-api-key": apiKey
30
- },
31
- body: JSON.stringify({ text, model_id: "eleven_monolingual_v1", voice_settings: { stability: 0.5, similarity_boost: 0.75 } })
32
- });
33
- return await res.blob();
34
- }
35
-
36
- async function renderAndRecord() {
37
- const script = JSON.parse(document.getElementById("chatScript").value);
38
- chat.innerHTML = "";
39
- bgVideo.pause();
40
- bgVideo.currentTime = 0;
41
-
42
- const stream = document.querySelector(".phone-frame").captureStream(30);
43
- const recorder = new MediaRecorder(stream);
44
- const chunks = [];
45
- recorder.ondataavailable = e => chunks.push(e.data);
46
- recorder.onstop = () => {
47
- const blob = new Blob(chunks, { type: "video/webm" });
48
- const a = document.createElement("a");
49
- a.href = URL.createObjectURL(blob);
50
- a.download = "fake_chat_with_voice.mp4";
51
- a.click();
52
  };
53
- recorder.start();
54
- bgVideo.play();
55
-
56
- for (const msg of script) {
57
- const wrapper = document.createElement("div");
58
- wrapper.className = "message " + msg.side;
59
-
60
- const avatar = document.createElement("img");
61
- avatar.src = msg.avatar;
62
- avatar.className = "avatar";
63
-
64
- const bubble = document.createElement("div");
65
- bubble.className = "bubble";
66
- bubble.textContent = msg.text;
67
-
68
- wrapper.appendChild(avatar);
69
- wrapper.appendChild(bubble);
70
- chat.appendChild(wrapper);
71
- chat.scrollTop = chat.scrollHeight;
72
-
73
- const voiceBlob = await getVoiceBlob(msg.text, msg.voice);
74
- const audio = new Audio(URL.createObjectURL(voiceBlob));
75
- await new Promise(res => {
76
- audio.onended = res;
77
- audio.play();
78
  });
79
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
- recorder.stop();
82
- }
 
1
 
2
+ import { createElement as h, useState, useRef } from "https://esm.sh/react";
3
+ import { createRoot } from "https://esm.sh/react-dom/client";
4
+
5
+ const voices = {
6
  Rachel: "21m00Tcm4TlvDq8ikWAM",
7
  Adam: "pNInz6obpgDQGcFmaJgB",
8
  Bella: "EXAVITQu4vr4xnSDxMaL"
9
  };
10
  const apiKey = "sk_4e67c39c0e9cbc87462cd2643e1f4d1d9959d7d81203adc2";
11
 
12
+ const App = () => {
13
+ const [characters, setCharacters] = useState([
14
+ { id: 0, name: "User", avatar: "https://i.pravatar.cc/30?img=3", voice: "Rachel" },
15
+ { id: 1, name: "Bot", avatar: "https://i.pravatar.cc/30?img=7", voice: "Adam" }
16
+ ]);
17
+ const [selectedChar, setSelectedChar] = useState(0);
18
+ const [messages, setMessages] = useState([]);
19
+ const [input, setInput] = useState("");
20
+ const [recording, setRecording] = useState(false);
21
+ const chatRef = useRef(null);
22
+ const recordRef = useRef(null);
23
+
24
+ const addCharacter = () => {
25
+ const id = characters.length;
26
+ setCharacters([...characters, { id, name: "New", avatar: "", voice: "Rachel" }]);
27
+ };
28
 
29
+ const handleAvatar = (id, file) => {
 
 
30
  const url = URL.createObjectURL(file);
31
+ setCharacters(characters.map(c => c.id === id ? { ...c, avatar: url } : c));
32
+ };
33
+
34
+ const handleNameChange = (id, name) => {
35
+ setCharacters(characters.map(c => c.id === id ? { ...c, name } : c));
36
+ };
37
+
38
+ const handleVoiceChange = (id, voice) => {
39
+ setCharacters(characters.map(c => c.id === id ? { ...c, voice } : c));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  };
41
+
42
+ const addMessage = () => {
43
+ if (!input.trim()) return;
44
+ setMessages([...messages, { charId: selectedChar, text: input }]);
45
+ setInput("");
46
+ };
47
+
48
+ const getVoiceBlob = async (text, voiceId) => {
49
+ const res = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, {
50
+ method: "POST",
51
+ headers: {
52
+ "Content-Type": "application/json",
53
+ "xi-api-key": apiKey
54
+ },
55
+ body: JSON.stringify({
56
+ text,
57
+ model_id: "eleven_monolingual_v1",
58
+ voice_settings: { stability: 0.4, similarity_boost: 0.75 }
59
+ })
 
 
 
 
 
 
60
  });
61
+ return await res.blob();
62
+ };
63
+
64
+ const playbackAndRecord = async () => {
65
+ setRecording(true);
66
+ const container = recordRef.current;
67
+ container.innerHTML = "";
68
+
69
+ const stream = container.captureStream(30);
70
+ const recorder = new MediaRecorder(stream);
71
+ const chunks = [];
72
+
73
+ recorder.ondataavailable = e => chunks.push(e.data);
74
+ recorder.onstop = () => {
75
+ const blob = new Blob(chunks, { type: "video/webm" });
76
+ const url = URL.createObjectURL(blob);
77
+ const a = document.createElement("a");
78
+ a.href = url;
79
+ a.download = "chat_voice_fixed.mp4";
80
+ a.click();
81
+ setRecording(false);
82
+ };
83
+
84
+ recorder.start();
85
+
86
+ for (const msg of messages) {
87
+ const char = characters.find(c => c.id === msg.charId);
88
+
89
+ const typing = document.createElement("div");
90
+ typing.className = "message typing";
91
+ typing.textContent = `${char.name} is typing...`;
92
+ container.appendChild(typing);
93
+ await new Promise(r => setTimeout(r, 1200));
94
+ container.removeChild(typing);
95
+
96
+ const msgDiv = document.createElement("div");
97
+ msgDiv.className = "message";
98
+ const avatar = document.createElement("img");
99
+ avatar.src = char.avatar;
100
+ const text = document.createElement("div");
101
+ text.className = "bubble";
102
+ text.textContent = msg.text;
103
+ msgDiv.appendChild(avatar);
104
+ msgDiv.appendChild(text);
105
+ container.appendChild(msgDiv);
106
+
107
+ const blob = await getVoiceBlob(msg.text, voices[char.voice]);
108
+ const audio = new Audio(URL.createObjectURL(blob));
109
+ await new Promise(res => {
110
+ audio.onended = res;
111
+ audio.play();
112
+ });
113
+ }
114
+
115
+ recorder.stop();
116
+ };
117
+
118
+ return h("div", { className: "app" }, [
119
+ h("h3", null, "Characters"),
120
+ ...characters.map(char =>
121
+ h("div", { className: "character", key: char.id }, [
122
+ h("img", { src: char.avatar || "https://via.placeholder.com/30" }),
123
+ h("input", {
124
+ value: char.name,
125
+ onChange: (e) => handleNameChange(char.id, e.target.value)
126
+ }),
127
+ h("select", {
128
+ value: char.voice,
129
+ onChange: (e) => handleVoiceChange(char.id, e.target.value)
130
+ }, Object.keys(voices).map(v =>
131
+ h("option", { value: v, key: v }, v)
132
+ )),
133
+ h("input", {
134
+ type: "file",
135
+ accept: "image/*",
136
+ onChange: (e) => handleAvatar(char.id, e.target.files[0])
137
+ })
138
+ ])
139
+ ),
140
+ h("button", { onClick: addCharacter }, "+ Add Character"),
141
+
142
+ h("div", { className: "controls" }, [
143
+ h("select", {
144
+ value: selectedChar,
145
+ onChange: (e) => setSelectedChar(parseInt(e.target.value))
146
+ }, characters.map(c => h("option", { value: c.id, key: c.id }, c.name))),
147
+ h("input", {
148
+ value: input,
149
+ onChange: (e) => setInput(e.target.value),
150
+ placeholder: "Type message..."
151
+ }),
152
+ h("button", { onClick: addMessage }, "Send Message")
153
+ ]),
154
+
155
+ h("div", { id: "recordArea", ref: recordRef }),
156
+
157
+ h("button", {
158
+ onClick: playbackAndRecord,
159
+ disabled: recording,
160
+ style: { marginTop: 20 }
161
+ }, recording ? "Recording..." : "📽 Export MP4 with Voice")
162
+ ]);
163
+ };
164
 
165
+ createRoot(document.getElementById("root")).render(h(App));