Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -5,6 +5,7 @@
|
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
<title>YouTube Shorts Generator</title>
|
7 |
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
8 |
<script>
|
9 |
tailwind.config = {
|
10 |
darkMode: 'class',
|
@@ -35,6 +36,42 @@
|
|
35 |
border-color: rgba(255, 255, 255, 0.1);
|
36 |
border-left-color: #5D5CDE;
|
37 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
</style>
|
39 |
</head>
|
40 |
<body class="bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 min-h-screen">
|
@@ -42,62 +79,142 @@
|
|
42 |
<h1 class="text-3xl font-bold mb-4 text-center text-primary">YouTube Shorts Generator</h1>
|
43 |
|
44 |
<div class="mb-8 bg-gray-100 dark:bg-gray-800 p-6 rounded-lg shadow-md">
|
45 |
-
|
46 |
-
|
47 |
-
<
|
48 |
-
|
49 |
-
|
50 |
-
<div class="mb-4">
|
51 |
-
<label for="language" class="block text-sm font-medium mb-1">Language</label>
|
52 |
-
<select id="language" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base">
|
53 |
-
<option value="English">English</option>
|
54 |
-
<option value="Spanish">Spanish</option>
|
55 |
-
<option value="French">French</option>
|
56 |
-
<option value="German">German</option>
|
57 |
-
<option value="Italian">Italian</option>
|
58 |
-
<option value="Portuguese">Portuguese</option>
|
59 |
-
<option value="Russian">Russian</option>
|
60 |
-
<option value="Japanese">Japanese</option>
|
61 |
-
<option value="Chinese">Chinese</option>
|
62 |
-
<option value="Hindi">Hindi</option>
|
63 |
-
</select>
|
64 |
-
</div>
|
65 |
-
|
66 |
-
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
67 |
-
<div>
|
68 |
-
<label for="text-generator" class="block text-sm font-medium mb-1">Text Generator</label>
|
69 |
-
<select id="text-generator" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base">
|
70 |
-
<option value="Claude-3.7-Sonnet">Claude-3.7-Sonnet</option>
|
71 |
-
<option value="GPT-4o">GPT-4o</option>
|
72 |
-
<option value="GPT-4o-mini">GPT-4o-mini</option>
|
73 |
-
<option value="Gemini-2.0-Flash">Gemini-2.0-Flash</option>
|
74 |
-
</select>
|
75 |
-
</div>
|
76 |
-
|
77 |
-
<div>
|
78 |
-
<label for="image-generator" class="block text-sm font-medium mb-1">Image Generator</label>
|
79 |
-
<select id="image-generator" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base">
|
80 |
-
<option value="FLUX-pro-1.1">FLUX-pro-1.1</option>
|
81 |
-
<option value="FLUX-schnell">FLUX-schnell</option>
|
82 |
-
<option value="Dall-E-3">Dall-E-3</option>
|
83 |
-
</select>
|
84 |
</div>
|
85 |
|
86 |
-
<div>
|
87 |
-
<label for="
|
88 |
-
<select id="
|
89 |
-
<option value="
|
90 |
-
<option value="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
</select>
|
92 |
</div>
|
93 |
</div>
|
94 |
|
95 |
-
|
96 |
-
|
97 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
</div>
|
99 |
|
100 |
-
<button id="generate-btn" class="
|
101 |
Generate Video
|
102 |
</button>
|
103 |
</div>
|
@@ -111,10 +228,31 @@
|
|
111 |
<div id="results-container" class="hidden bg-gray-100 dark:bg-gray-800 p-6 rounded-lg shadow-md">
|
112 |
<h2 class="text-xl font-bold mb-3">Generated Video</h2>
|
113 |
|
114 |
-
<div id="video-player-container" class="mb-6 relative pt-[56.25%]">
|
115 |
-
|
116 |
-
|
117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
</div>
|
119 |
|
120 |
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
@@ -127,6 +265,16 @@
|
|
127 |
<p id="video-description" class="bg-white dark:bg-gray-700 p-3 rounded-md h-24 overflow-y-auto"></p>
|
128 |
</div>
|
129 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
</div>
|
131 |
</div>
|
132 |
|
@@ -144,15 +292,171 @@
|
|
144 |
}
|
145 |
});
|
146 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
147 |
// Handler for generating videos
|
148 |
document.getElementById('generate-btn').addEventListener('click', async function() {
|
149 |
// Get input values
|
150 |
const niche = document.getElementById('niche').value.trim();
|
151 |
const language = document.getElementById('language').value;
|
|
|
|
|
152 |
const textGenerator = document.getElementById('text-generator').value;
|
|
|
153 |
const imageGenerator = document.getElementById('image-generator').value;
|
154 |
-
const
|
155 |
-
const
|
|
|
|
|
|
|
|
|
|
|
156 |
|
157 |
// Validation
|
158 |
if (!niche) {
|
@@ -166,39 +470,54 @@
|
|
166 |
document.getElementById('results-container').classList.add('hidden');
|
167 |
|
168 |
try {
|
169 |
-
//
|
170 |
-
|
171 |
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
172 |
|
173 |
// Step 1: Generate topic
|
174 |
-
|
|
|
175 |
|
176 |
// Step 2: Generate script
|
177 |
updateProgress('Creating script...');
|
178 |
-
const script = await generateScript(topic, language
|
179 |
|
180 |
// Step 3: Generate metadata
|
181 |
updateProgress('Creating title and description...');
|
182 |
-
const metadata = await generateMetadata(topic, script
|
183 |
|
184 |
// Step 4: Generate image prompts
|
185 |
updateProgress('Creating image prompts...');
|
186 |
-
const imagePrompts = await generateImagePrompts(topic, script,
|
187 |
|
188 |
// Step 5: Generate images
|
189 |
updateProgress('Generating images...');
|
190 |
-
const imageUrls = await generateImages(imagePrompts
|
191 |
|
192 |
// Step 6: Generate speech
|
193 |
updateProgress('Creating voiceover...');
|
194 |
-
const
|
195 |
|
196 |
-
// Step 7: Generate
|
197 |
-
updateProgress('Creating
|
198 |
-
const
|
199 |
|
200 |
-
// Display results
|
201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
|
203 |
} catch (error) {
|
204 |
console.error('Error:', error);
|
@@ -212,11 +531,11 @@
|
|
212 |
}
|
213 |
|
214 |
// Function to generate topic based on niche
|
215 |
-
async function generateTopic(niche,
|
216 |
try {
|
217 |
const prompt = `Please generate a specific video idea that takes about the following topic: ${niche}. Make it exactly one sentence. Only return the topic, nothing else.`;
|
218 |
|
219 |
-
//
|
220 |
const handlerId = 'topic-generation-handler';
|
221 |
let topicResult = '';
|
222 |
|
@@ -231,7 +550,7 @@
|
|
231 |
});
|
232 |
|
233 |
// Send request to generate topic
|
234 |
-
await window.Poe.sendUserMessage(
|
235 |
handler: handlerId,
|
236 |
stream: false,
|
237 |
openChat: false
|
@@ -250,7 +569,7 @@
|
|
250 |
}
|
251 |
|
252 |
// Function to generate script based on topic
|
253 |
-
async function generateScript(topic, language
|
254 |
try {
|
255 |
const prompt = `
|
256 |
Generate a script for youtube shorts video, depending on the subject of the video.
|
@@ -284,7 +603,7 @@
|
|
284 |
});
|
285 |
|
286 |
// Send request to generate script
|
287 |
-
await window.Poe.sendUserMessage(
|
288 |
handler: handlerId,
|
289 |
stream: false,
|
290 |
openChat: false
|
@@ -303,7 +622,7 @@
|
|
303 |
}
|
304 |
|
305 |
// Function to generate metadata (title and description)
|
306 |
-
async function generateMetadata(topic, script
|
307 |
try {
|
308 |
const titlePrompt = `Please generate a YouTube Video Title for the following subject, including hashtags: ${topic}. Only return the title, nothing else. Limit the title under 100 characters.`;
|
309 |
|
@@ -322,7 +641,7 @@
|
|
322 |
});
|
323 |
|
324 |
// Send request to generate title
|
325 |
-
await window.Poe.sendUserMessage(
|
326 |
handler: titleHandlerId,
|
327 |
stream: false,
|
328 |
openChat: false
|
@@ -351,7 +670,7 @@
|
|
351 |
});
|
352 |
|
353 |
// Send request to generate description
|
354 |
-
await window.Poe.sendUserMessage(
|
355 |
handler: descHandlerId,
|
356 |
stream: false,
|
357 |
openChat: false
|
@@ -373,17 +692,17 @@
|
|
373 |
}
|
374 |
|
375 |
// Function to generate image prompts
|
376 |
-
async function generateImagePrompts(topic, script,
|
377 |
try {
|
378 |
const prompt = `
|
379 |
-
Generate
|
380 |
depending on the subject of a video.
|
381 |
Subject: ${topic}
|
382 |
|
383 |
The image prompts are to be returned as
|
384 |
a JSON-Array of strings.
|
385 |
|
386 |
-
Each
|
387 |
always add the main subject of the video.
|
388 |
|
389 |
Be emotional and use interesting adjectives to make the
|
@@ -411,7 +730,7 @@
|
|
411 |
});
|
412 |
|
413 |
// Send request to generate image prompts
|
414 |
-
await window.Poe.sendUserMessage(
|
415 |
handler: handlerId,
|
416 |
stream: false,
|
417 |
openChat: false
|
@@ -445,7 +764,7 @@
|
|
445 |
}
|
446 |
|
447 |
// Function to generate images based on prompts
|
448 |
-
async function generateImages(imagePrompts
|
449 |
try {
|
450 |
const imageUrls = [];
|
451 |
|
@@ -466,7 +785,7 @@
|
|
466 |
});
|
467 |
|
468 |
// Send request to generate image
|
469 |
-
await window.Poe.sendUserMessage(
|
470 |
handler: handlerId,
|
471 |
stream: false,
|
472 |
openChat: false
|
@@ -489,7 +808,7 @@
|
|
489 |
}
|
490 |
|
491 |
// Function to generate speech from script
|
492 |
-
async function generateSpeech(script, language,
|
493 |
try {
|
494 |
// Use Poe API to send user message
|
495 |
const handlerId = 'speech-generation-handler';
|
@@ -507,12 +826,12 @@
|
|
507 |
|
508 |
// Prepare the prompt
|
509 |
let prompt = script;
|
510 |
-
if (
|
511 |
-
prompt += ` --voice ${
|
512 |
}
|
513 |
|
514 |
// Send request to generate speech
|
515 |
-
await window.Poe.sendUserMessage(
|
516 |
handler: handlerId,
|
517 |
stream: false,
|
518 |
openChat: false
|
@@ -529,45 +848,57 @@
|
|
529 |
throw new Error('Failed to generate speech audio');
|
530 |
}
|
531 |
|
532 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
533 |
} catch (error) {
|
534 |
console.error('Error generating speech:', error);
|
535 |
throw new Error('Failed to generate speech');
|
536 |
}
|
537 |
}
|
538 |
|
539 |
-
// Function to generate
|
540 |
-
|
541 |
-
//
|
542 |
-
|
543 |
-
|
544 |
|
545 |
-
|
546 |
-
|
547 |
-
|
|
|
|
|
|
|
548 |
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
// Simulate processing time
|
556 |
-
await new Promise(resolve => setTimeout(resolve, 3000));
|
557 |
|
558 |
-
|
559 |
-
return {
|
560 |
-
audioUrl: audioUrl,
|
561 |
-
imageUrls: imageUrls
|
562 |
-
};
|
563 |
-
} catch (error) {
|
564 |
-
console.error('Error generating video:', error);
|
565 |
-
throw new Error('Failed to generate video');
|
566 |
}
|
|
|
|
|
567 |
}
|
568 |
|
569 |
// Function to display results
|
570 |
-
function displayResults(
|
571 |
// Hide loading container
|
572 |
document.getElementById('loading-container').classList.add('hidden');
|
573 |
document.getElementById('loading-container').classList.remove('flex');
|
@@ -576,39 +907,138 @@
|
|
576 |
document.getElementById('results-container').classList.remove('hidden');
|
577 |
|
578 |
// Set title and description
|
579 |
-
document.getElementById('video-title').textContent = title;
|
580 |
-
document.getElementById('video-description').textContent = description;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
581 |
|
582 |
-
// Set up
|
583 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
584 |
|
585 |
-
//
|
586 |
-
|
587 |
-
|
588 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
589 |
}
|
590 |
|
591 |
-
//
|
592 |
-
|
593 |
-
const audioSource = document.createElement('source');
|
594 |
-
audioSource.src = videoData.audioUrl;
|
595 |
-
audioSource.type = 'audio/mpeg';
|
596 |
-
videoPlayer.appendChild(audioSource);
|
597 |
|
598 |
-
//
|
599 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
603 |
-
|
604 |
-
|
605 |
-
|
606 |
-
|
607 |
-
|
608 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
609 |
|
610 |
-
//
|
611 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
612 |
}
|
613 |
</script>
|
614 |
</body>
|
|
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
<title>YouTube Shorts Generator</title>
|
7 |
<script src="https://cdn.tailwindcss.com"></script>
|
8 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
9 |
<script>
|
10 |
tailwind.config = {
|
11 |
darkMode: 'class',
|
|
|
36 |
border-color: rgba(255, 255, 255, 0.1);
|
37 |
border-left-color: #5D5CDE;
|
38 |
}
|
39 |
+
|
40 |
+
.subtitle-word {
|
41 |
+
display: inline-block;
|
42 |
+
margin-right: 5px;
|
43 |
+
padding: 2px 5px;
|
44 |
+
border-radius: 4px;
|
45 |
+
font-family: 'Helvetica', sans-serif;
|
46 |
+
font-weight: bold;
|
47 |
+
color: white;
|
48 |
+
}
|
49 |
+
|
50 |
+
.subtitle-word.highlighted {
|
51 |
+
background-color: blue;
|
52 |
+
transition: background-color 0.1s ease;
|
53 |
+
}
|
54 |
+
|
55 |
+
.subtitle-container {
|
56 |
+
position: absolute;
|
57 |
+
bottom: 15%;
|
58 |
+
left: 10%;
|
59 |
+
width: 80%;
|
60 |
+
text-align: center;
|
61 |
+
z-index: 10;
|
62 |
+
font-size: 24px;
|
63 |
+
line-height: 1.5;
|
64 |
+
}
|
65 |
+
|
66 |
+
.accordion-content {
|
67 |
+
max-height: 0;
|
68 |
+
overflow: hidden;
|
69 |
+
transition: max-height 0.3s ease;
|
70 |
+
}
|
71 |
+
|
72 |
+
.accordion-content.open {
|
73 |
+
max-height: 1000px;
|
74 |
+
}
|
75 |
</style>
|
76 |
</head>
|
77 |
<body class="bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 min-h-screen">
|
|
|
79 |
<h1 class="text-3xl font-bold mb-4 text-center text-primary">YouTube Shorts Generator</h1>
|
80 |
|
81 |
<div class="mb-8 bg-gray-100 dark:bg-gray-800 p-6 rounded-lg shadow-md">
|
82 |
+
<!-- Main Required Inputs -->
|
83 |
+
<div class="mb-6">
|
84 |
+
<div class="mb-4">
|
85 |
+
<label for="niche" class="block text-sm font-medium mb-1">Niche/Topic <span class="text-red-500">*</span></label>
|
86 |
+
<input type="text" id="niche" placeholder="E.g., Fitness tips, Technology facts, Travel destinations" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
</div>
|
88 |
|
89 |
+
<div class="mb-4">
|
90 |
+
<label for="language" class="block text-sm font-medium mb-1">Language <span class="text-red-500">*</span></label>
|
91 |
+
<select id="language" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base">
|
92 |
+
<option value="English">English</option>
|
93 |
+
<option value="Spanish">Spanish</option>
|
94 |
+
<option value="French">French</option>
|
95 |
+
<option value="German">German</option>
|
96 |
+
<option value="Italian">Italian</option>
|
97 |
+
<option value="Portuguese">Portuguese</option>
|
98 |
+
<option value="Russian">Russian</option>
|
99 |
+
<option value="Japanese">Japanese</option>
|
100 |
+
<option value="Chinese">Chinese</option>
|
101 |
+
<option value="Hindi">Hindi</option>
|
102 |
+
<option value="Arabic">Arabic</option>
|
103 |
+
<option value="Korean">Korean</option>
|
104 |
+
<option value="Dutch">Dutch</option>
|
105 |
+
<option value="Swedish">Swedish</option>
|
106 |
+
<option value="Turkish">Turkish</option>
|
107 |
</select>
|
108 |
</div>
|
109 |
</div>
|
110 |
|
111 |
+
<!-- Advanced Options Accordion -->
|
112 |
+
<div class="mb-6">
|
113 |
+
<button id="advanced-options-toggle" class="flex justify-between items-center w-full bg-gray-200 dark:bg-gray-700 p-3 rounded-md mb-2 text-left">
|
114 |
+
<span class="font-medium">Advanced Options</span>
|
115 |
+
<span class="transition-transform duration-300 transform">▼</span>
|
116 |
+
</button>
|
117 |
+
|
118 |
+
<div id="advanced-options" class="accordion-content bg-gray-50 dark:bg-gray-800 p-4 rounded-md">
|
119 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
120 |
+
<!-- Text Generator Options -->
|
121 |
+
<div>
|
122 |
+
<label for="text-generator" class="block text-sm font-medium mb-1">Text Generator</label>
|
123 |
+
<select id="text-generator" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base mb-2">
|
124 |
+
<option value="gemini">Google Gemini</option>
|
125 |
+
<option value="gpt3">GPT-3.5-Turbo</option>
|
126 |
+
<option value="gpt4">GPT-4</option>
|
127 |
+
<option value="claude">Claude</option>
|
128 |
+
<option value="llama">Llama</option>
|
129 |
+
<option value="mistral">Mistral</option>
|
130 |
+
<option value="command">Cohere Command</option>
|
131 |
+
</select>
|
132 |
+
|
133 |
+
<label for="text-model" class="block text-sm font-medium mb-1">Text Model</label>
|
134 |
+
<select id="text-model" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base">
|
135 |
+
<option value="gemini-2.0-flash">gemini-2.0-flash</option>
|
136 |
+
<option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option>
|
137 |
+
<option value="gemini-1.5-flash">gemini-1.5-flash</option>
|
138 |
+
<option value="gemini-1.5-flash-8b">gemini-1.5-flash-8b</option>
|
139 |
+
<option value="gemini-1.5-pro">gemini-1.5-pro</option>
|
140 |
+
<!-- Other models will be populated based on text generator selection -->
|
141 |
+
</select>
|
142 |
+
</div>
|
143 |
+
|
144 |
+
<!-- Image Generator Options -->
|
145 |
+
<div>
|
146 |
+
<label for="image-generator" class="block text-sm font-medium mb-1">Image Generator</label>
|
147 |
+
<select id="image-generator" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base mb-2">
|
148 |
+
<option value="prodia">Prodia</option>
|
149 |
+
<option value="hercai">Hercai</option>
|
150 |
+
<option value="g4f">G4F</option>
|
151 |
+
<option value="segmind">Segmind</option>
|
152 |
+
<option value="pollinations">Pollinations</option>
|
153 |
+
</select>
|
154 |
+
|
155 |
+
<label for="image-model" class="block text-sm font-medium mb-1">Image Model</label>
|
156 |
+
<select id="image-model" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base">
|
157 |
+
<option value="sdxl">SDXL</option>
|
158 |
+
<option value="realvisxl">RealVisXL</option>
|
159 |
+
<option value="juggernaut">Juggernaut</option>
|
160 |
+
<option value="dalle">DALL-E</option>
|
161 |
+
<option value="midjourney">Midjourney</option>
|
162 |
+
<!-- Other models will be populated based on image generator selection -->
|
163 |
+
</select>
|
164 |
+
</div>
|
165 |
+
</div>
|
166 |
+
|
167 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
168 |
+
<!-- Speech Generator Options -->
|
169 |
+
<div>
|
170 |
+
<label for="tts-engine" class="block text-sm font-medium mb-1">Speech Generator</label>
|
171 |
+
<select id="tts-engine" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base mb-2">
|
172 |
+
<option value="elevenlabs">ElevenLabs</option>
|
173 |
+
<option value="bark">Bark</option>
|
174 |
+
<option value="gtts">Google TTS</option>
|
175 |
+
<option value="openai">OpenAI TTS</option>
|
176 |
+
<option value="edge">Edge TTS</option>
|
177 |
+
<option value="local_tts">Local TTS</option>
|
178 |
+
<option value="xtts">XTTS</option>
|
179 |
+
<option value="rvc">RVC</option>
|
180 |
+
</select>
|
181 |
+
|
182 |
+
<label for="tts-voice" class="block text-sm font-medium mb-1">Voice</label>
|
183 |
+
<input type="text" id="tts-voice" placeholder="E.g., Sarah, Brian, Lily, Monika Sogam" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base">
|
184 |
+
</div>
|
185 |
+
|
186 |
+
<!-- Subtitle Options -->
|
187 |
+
<div>
|
188 |
+
<label for="subtitle-font" class="block text-sm font-medium mb-1">Subtitle Font</label>
|
189 |
+
<select id="subtitle-font" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base mb-2">
|
190 |
+
<option value="Helvetica-Bold">Helvetica Bold</option>
|
191 |
+
<option value="Arial-Bold">Arial Bold</option>
|
192 |
+
<option value="Roboto-Bold">Roboto Bold</option>
|
193 |
+
<option value="Verdana-Bold">Verdana Bold</option>
|
194 |
+
</select>
|
195 |
+
|
196 |
+
<div class="grid grid-cols-2 gap-2">
|
197 |
+
<div>
|
198 |
+
<label for="subtitle-color" class="block text-sm font-medium mb-1">Text Color</label>
|
199 |
+
<input type="color" id="subtitle-color" value="#FFFFFF" class="w-full h-10 px-1 py-1 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700">
|
200 |
+
</div>
|
201 |
+
<div>
|
202 |
+
<label for="highlight-color" class="block text-sm font-medium mb-1">Highlight Color</label>
|
203 |
+
<input type="color" id="highlight-color" value="#0000FF" class="w-full h-10 px-1 py-1 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700">
|
204 |
+
</div>
|
205 |
+
</div>
|
206 |
+
</div>
|
207 |
+
</div>
|
208 |
+
|
209 |
+
<!-- Image Prompt Options -->
|
210 |
+
<div class="mb-4">
|
211 |
+
<label for="prompt-count" class="block text-sm font-medium mb-1">Number of Image Prompts</label>
|
212 |
+
<input type="number" id="prompt-count" min="3" max="10" value="5" class="w-full px-4 py-2 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-base">
|
213 |
+
</div>
|
214 |
+
</div>
|
215 |
</div>
|
216 |
|
217 |
+
<button id="generate-btn" class="w-full bg-primary hover:bg-opacity-90 text-white py-3 px-4 rounded-md font-medium transition duration-200">
|
218 |
Generate Video
|
219 |
</button>
|
220 |
</div>
|
|
|
228 |
<div id="results-container" class="hidden bg-gray-100 dark:bg-gray-800 p-6 rounded-lg shadow-md">
|
229 |
<h2 class="text-xl font-bold mb-3">Generated Video</h2>
|
230 |
|
231 |
+
<div id="video-player-container" class="mb-6 relative pt-[56.25%] bg-black rounded-lg">
|
232 |
+
<!-- Video Player -->
|
233 |
+
<div id="video-container" class="absolute top-0 left-0 w-full h-full rounded-lg overflow-hidden">
|
234 |
+
<!-- Video or Images will be displayed here -->
|
235 |
+
<div id="image-slideshow" class="w-full h-full"></div>
|
236 |
+
|
237 |
+
<!-- Subtitles will be displayed here -->
|
238 |
+
<div id="subtitle-container" class="subtitle-container"></div>
|
239 |
+
|
240 |
+
<!-- Audio Player -->
|
241 |
+
<audio id="audio-player" class="hidden"></audio>
|
242 |
+
</div>
|
243 |
+
|
244 |
+
<!-- Video Controls -->
|
245 |
+
<div id="video-controls" class="absolute bottom-4 left-0 right-0 mx-auto flex justify-center items-center space-x-4">
|
246 |
+
<button id="play-btn" class="bg-white bg-opacity-20 hover:bg-opacity-30 text-white p-2 rounded-full">
|
247 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
248 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
249 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
250 |
+
</svg>
|
251 |
+
</button>
|
252 |
+
<div class="w-48 h-1 bg-white bg-opacity-20 rounded-full">
|
253 |
+
<div id="progress-bar" class="h-full bg-white rounded-full" style="width: 0%"></div>
|
254 |
+
</div>
|
255 |
+
</div>
|
256 |
</div>
|
257 |
|
258 |
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
265 |
<p id="video-description" class="bg-white dark:bg-gray-700 p-3 rounded-md h-24 overflow-y-auto"></p>
|
266 |
</div>
|
267 |
</div>
|
268 |
+
|
269 |
+
<div class="mt-6">
|
270 |
+
<h3 class="font-medium mb-2">Script</h3>
|
271 |
+
<div id="video-script" class="bg-white dark:bg-gray-700 p-3 rounded-md"></div>
|
272 |
+
</div>
|
273 |
+
|
274 |
+
<div class="mt-6">
|
275 |
+
<h3 class="font-medium mb-2">Image Prompts</h3>
|
276 |
+
<div id="image-prompts" class="bg-white dark:bg-gray-700 p-3 rounded-md"></div>
|
277 |
+
</div>
|
278 |
</div>
|
279 |
</div>
|
280 |
|
|
|
292 |
}
|
293 |
});
|
294 |
|
295 |
+
// Advanced options accordion
|
296 |
+
document.getElementById('advanced-options-toggle').addEventListener('click', function() {
|
297 |
+
const content = document.getElementById('advanced-options');
|
298 |
+
content.classList.toggle('open');
|
299 |
+
this.querySelector('span:last-child').classList.toggle('rotate-180');
|
300 |
+
});
|
301 |
+
|
302 |
+
// Populate models based on generator selection
|
303 |
+
document.getElementById('text-generator').addEventListener('change', function() {
|
304 |
+
const modelSelect = document.getElementById('text-model');
|
305 |
+
modelSelect.innerHTML = '';
|
306 |
+
|
307 |
+
switch(this.value) {
|
308 |
+
case 'gemini':
|
309 |
+
addOptions(modelSelect, [
|
310 |
+
{value: 'gemini-2.0-flash', text: 'gemini-2.0-flash'},
|
311 |
+
{value: 'gemini-2.0-flash-lite', text: 'gemini-2.0-flash-lite'},
|
312 |
+
{value: 'gemini-1.5-flash', text: 'gemini-1.5-flash'},
|
313 |
+
{value: 'gemini-1.5-flash-8b', text: 'gemini-1.5-flash-8b'},
|
314 |
+
{value: 'gemini-1.5-pro', text: 'gemini-1.5-pro'}
|
315 |
+
]);
|
316 |
+
break;
|
317 |
+
case 'gpt3':
|
318 |
+
addOptions(modelSelect, [
|
319 |
+
{value: 'gpt-3.5-turbo', text: 'gpt-3.5-turbo'},
|
320 |
+
{value: 'gpt-3.5-turbo-16k', text: 'gpt-3.5-turbo-16k'}
|
321 |
+
]);
|
322 |
+
break;
|
323 |
+
case 'gpt4':
|
324 |
+
addOptions(modelSelect, [
|
325 |
+
{value: 'gpt-4', text: 'gpt-4'},
|
326 |
+
{value: 'gpt-4o', text: 'gpt-4o'},
|
327 |
+
{value: 'gpt-4-turbo', text: 'gpt-4-turbo'}
|
328 |
+
]);
|
329 |
+
break;
|
330 |
+
case 'claude':
|
331 |
+
addOptions(modelSelect, [
|
332 |
+
{value: 'claude-3-opus-20240229', text: 'Claude 3 Opus'},
|
333 |
+
{value: 'claude-3-sonnet-20240229', text: 'Claude 3 Sonnet'},
|
334 |
+
{value: 'claude-3-haiku-20240307', text: 'Claude 3 Haiku'}
|
335 |
+
]);
|
336 |
+
break;
|
337 |
+
case 'llama':
|
338 |
+
addOptions(modelSelect, [
|
339 |
+
{value: 'llama-3-70b-chat', text: 'Llama 3 70B'},
|
340 |
+
{value: 'llama-3-8b-chat', text: 'Llama 3 8B'},
|
341 |
+
{value: 'llama-2-70b-chat', text: 'Llama 2 70B'}
|
342 |
+
]);
|
343 |
+
break;
|
344 |
+
case 'mistral':
|
345 |
+
addOptions(modelSelect, [
|
346 |
+
{value: 'mistral-large-latest', text: 'Mistral Large'},
|
347 |
+
{value: 'mistral-medium-latest', text: 'Mistral Medium'},
|
348 |
+
{value: 'mistral-small-latest', text: 'Mistral Small'}
|
349 |
+
]);
|
350 |
+
break;
|
351 |
+
case 'command':
|
352 |
+
addOptions(modelSelect, [
|
353 |
+
{value: 'command-r', text: 'Command R'},
|
354 |
+
{value: 'command-r-plus', text: 'Command R+'},
|
355 |
+
{value: 'command-light', text: 'Command Light'}
|
356 |
+
]);
|
357 |
+
break;
|
358 |
+
}
|
359 |
+
});
|
360 |
+
|
361 |
+
document.getElementById('image-generator').addEventListener('change', function() {
|
362 |
+
const modelSelect = document.getElementById('image-model');
|
363 |
+
modelSelect.innerHTML = '';
|
364 |
+
|
365 |
+
switch(this.value) {
|
366 |
+
case 'prodia':
|
367 |
+
addOptions(modelSelect, [
|
368 |
+
{value: 'sdxl', text: 'SDXL'},
|
369 |
+
{value: 'realvisxl', text: 'RealVisXL V4.0'},
|
370 |
+
{value: 'juggernaut', text: 'Juggernaut XL'},
|
371 |
+
{value: 'dreamshaper', text: 'DreamShaper 8'},
|
372 |
+
{value: 'portraitplus', text: 'Portrait+ V1'}
|
373 |
+
]);
|
374 |
+
break;
|
375 |
+
case 'hercai':
|
376 |
+
addOptions(modelSelect, [
|
377 |
+
{value: 'v1', text: 'Stable Diffusion v1'},
|
378 |
+
{value: 'v2', text: 'Stable Diffusion v2'},
|
379 |
+
{value: 'v3', text: 'Stable Diffusion v3'},
|
380 |
+
{value: 'lexica', text: 'Lexica Diffusion'}
|
381 |
+
]);
|
382 |
+
break;
|
383 |
+
case 'g4f':
|
384 |
+
addOptions(modelSelect, [
|
385 |
+
{value: 'dall-e-3', text: 'DALL-E 3'},
|
386 |
+
{value: 'dall-e-2', text: 'DALL-E 2'},
|
387 |
+
{value: 'imageapi', text: 'ImageAPI v1'}
|
388 |
+
]);
|
389 |
+
break;
|
390 |
+
case 'segmind':
|
391 |
+
addOptions(modelSelect, [
|
392 |
+
{value: 'sdxl-turbo', text: 'SDXL Turbo'},
|
393 |
+
{value: 'realistic-vision', text: 'Realistic Vision'},
|
394 |
+
{value: 'sd3', text: 'Stable Diffusion 3'}
|
395 |
+
]);
|
396 |
+
break;
|
397 |
+
case 'pollinations':
|
398 |
+
addOptions(modelSelect, [
|
399 |
+
{value: 'default', text: 'Default Model'}
|
400 |
+
]);
|
401 |
+
break;
|
402 |
+
}
|
403 |
+
});
|
404 |
+
|
405 |
+
document.getElementById('tts-engine').addEventListener('change', function() {
|
406 |
+
const voiceInput = document.getElementById('tts-voice');
|
407 |
+
|
408 |
+
switch(this.value) {
|
409 |
+
case 'elevenlabs':
|
410 |
+
voiceInput.placeholder = 'E.g., Sarah, Brian, Lily, Monika Sogam';
|
411 |
+
break;
|
412 |
+
case 'openai':
|
413 |
+
voiceInput.placeholder = 'E.g., alloy, echo, fable, onyx, nova, shimmer';
|
414 |
+
break;
|
415 |
+
case 'edge':
|
416 |
+
voiceInput.placeholder = 'E.g., en-US-JennyNeural, en-US-GuyNeural';
|
417 |
+
break;
|
418 |
+
case 'gtts':
|
419 |
+
voiceInput.placeholder = 'Language code (en, es, fr, etc.)';
|
420 |
+
break;
|
421 |
+
case 'xtts':
|
422 |
+
voiceInput.placeholder = 'Speaker name or reference audio path';
|
423 |
+
break;
|
424 |
+
default:
|
425 |
+
voiceInput.placeholder = 'Voice name or identifier';
|
426 |
+
}
|
427 |
+
});
|
428 |
+
|
429 |
+
function addOptions(selectElement, options) {
|
430 |
+
options.forEach(option => {
|
431 |
+
const optElement = document.createElement('option');
|
432 |
+
optElement.value = option.value;
|
433 |
+
optElement.textContent = option.text;
|
434 |
+
selectElement.appendChild(optElement);
|
435 |
+
});
|
436 |
+
}
|
437 |
+
|
438 |
+
// Initialize model selects
|
439 |
+
document.getElementById('text-generator').dispatchEvent(new Event('change'));
|
440 |
+
document.getElementById('image-generator').dispatchEvent(new Event('change'));
|
441 |
+
document.getElementById('tts-engine').dispatchEvent(new Event('change'));
|
442 |
+
|
443 |
// Handler for generating videos
|
444 |
document.getElementById('generate-btn').addEventListener('click', async function() {
|
445 |
// Get input values
|
446 |
const niche = document.getElementById('niche').value.trim();
|
447 |
const language = document.getElementById('language').value;
|
448 |
+
|
449 |
+
// Get advanced options
|
450 |
const textGenerator = document.getElementById('text-generator').value;
|
451 |
+
const textModel = document.getElementById('text-model').value;
|
452 |
const imageGenerator = document.getElementById('image-generator').value;
|
453 |
+
const imageModel = document.getElementById('image-model').value;
|
454 |
+
const ttsEngine = document.getElementById('tts-engine').value;
|
455 |
+
const ttsVoice = document.getElementById('tts-voice').value.trim();
|
456 |
+
const subtitleFont = document.getElementById('subtitle-font').value;
|
457 |
+
const subtitleColor = document.getElementById('subtitle-color').value;
|
458 |
+
const highlightColor = document.getElementById('highlight-color').value;
|
459 |
+
const promptCount = document.getElementById('prompt-count').value;
|
460 |
|
461 |
// Validation
|
462 |
if (!niche) {
|
|
|
470 |
document.getElementById('results-container').classList.add('hidden');
|
471 |
|
472 |
try {
|
473 |
+
// For the Poe environment, we'll create a simulated process that generates a video
|
474 |
+
// using text generation, image generation, and speech generation
|
|
|
475 |
|
476 |
// Step 1: Generate topic
|
477 |
+
updateProgress('Generating topic...');
|
478 |
+
const topic = await generateTopic(niche, language);
|
479 |
|
480 |
// Step 2: Generate script
|
481 |
updateProgress('Creating script...');
|
482 |
+
const script = await generateScript(topic, language);
|
483 |
|
484 |
// Step 3: Generate metadata
|
485 |
updateProgress('Creating title and description...');
|
486 |
+
const metadata = await generateMetadata(topic, script);
|
487 |
|
488 |
// Step 4: Generate image prompts
|
489 |
updateProgress('Creating image prompts...');
|
490 |
+
const imagePrompts = await generateImagePrompts(topic, script, promptCount);
|
491 |
|
492 |
// Step 5: Generate images
|
493 |
updateProgress('Generating images...');
|
494 |
+
const imageUrls = await generateImages(imagePrompts);
|
495 |
|
496 |
// Step 6: Generate speech
|
497 |
updateProgress('Creating voiceover...');
|
498 |
+
const audioData = await generateSpeech(script, language, ttsEngine, ttsVoice);
|
499 |
|
500 |
+
// Step 7: Generate subtitles (simulated)
|
501 |
+
updateProgress('Creating subtitles...');
|
502 |
+
const subtitles = generateSimulatedSubtitles(script);
|
503 |
|
504 |
+
// Step 8: Display the results
|
505 |
+
updateProgress('Finalizing video...');
|
506 |
+
displayResults({
|
507 |
+
topic: topic,
|
508 |
+
script: script,
|
509 |
+
metadata: metadata,
|
510 |
+
imagePrompts: imagePrompts,
|
511 |
+
imageUrls: imageUrls,
|
512 |
+
audioUrl: audioData.url,
|
513 |
+
audioDuration: audioData.duration,
|
514 |
+
subtitles: subtitles,
|
515 |
+
subtitleSettings: {
|
516 |
+
font: subtitleFont,
|
517 |
+
color: subtitleColor,
|
518 |
+
highlightColor: highlightColor
|
519 |
+
}
|
520 |
+
});
|
521 |
|
522 |
} catch (error) {
|
523 |
console.error('Error:', error);
|
|
|
531 |
}
|
532 |
|
533 |
// Function to generate topic based on niche
|
534 |
+
async function generateTopic(niche, language) {
|
535 |
try {
|
536 |
const prompt = `Please generate a specific video idea that takes about the following topic: ${niche}. Make it exactly one sentence. Only return the topic, nothing else.`;
|
537 |
|
538 |
+
// For this demonstration, we'll use Claude
|
539 |
const handlerId = 'topic-generation-handler';
|
540 |
let topicResult = '';
|
541 |
|
|
|
550 |
});
|
551 |
|
552 |
// Send request to generate topic
|
553 |
+
await window.Poe.sendUserMessage(`@Claude-3.7-Sonnet ${prompt}`, {
|
554 |
handler: handlerId,
|
555 |
stream: false,
|
556 |
openChat: false
|
|
|
569 |
}
|
570 |
|
571 |
// Function to generate script based on topic
|
572 |
+
async function generateScript(topic, language) {
|
573 |
try {
|
574 |
const prompt = `
|
575 |
Generate a script for youtube shorts video, depending on the subject of the video.
|
|
|
603 |
});
|
604 |
|
605 |
// Send request to generate script
|
606 |
+
await window.Poe.sendUserMessage(`@Claude-3.7-Sonnet ${prompt}`, {
|
607 |
handler: handlerId,
|
608 |
stream: false,
|
609 |
openChat: false
|
|
|
622 |
}
|
623 |
|
624 |
// Function to generate metadata (title and description)
|
625 |
+
async function generateMetadata(topic, script) {
|
626 |
try {
|
627 |
const titlePrompt = `Please generate a YouTube Video Title for the following subject, including hashtags: ${topic}. Only return the title, nothing else. Limit the title under 100 characters.`;
|
628 |
|
|
|
641 |
});
|
642 |
|
643 |
// Send request to generate title
|
644 |
+
await window.Poe.sendUserMessage(`@Claude-3.7-Sonnet ${titlePrompt}`, {
|
645 |
handler: titleHandlerId,
|
646 |
stream: false,
|
647 |
openChat: false
|
|
|
670 |
});
|
671 |
|
672 |
// Send request to generate description
|
673 |
+
await window.Poe.sendUserMessage(`@Claude-3.7-Sonnet ${descPrompt}`, {
|
674 |
handler: descHandlerId,
|
675 |
stream: false,
|
676 |
openChat: false
|
|
|
692 |
}
|
693 |
|
694 |
// Function to generate image prompts
|
695 |
+
async function generateImagePrompts(topic, script, count = 5) {
|
696 |
try {
|
697 |
const prompt = `
|
698 |
+
Generate ${count} Image Prompts for AI Image Generation,
|
699 |
depending on the subject of a video.
|
700 |
Subject: ${topic}
|
701 |
|
702 |
The image prompts are to be returned as
|
703 |
a JSON-Array of strings.
|
704 |
|
705 |
+
Each prompt should consist of a full sentence,
|
706 |
always add the main subject of the video.
|
707 |
|
708 |
Be emotional and use interesting adjectives to make the
|
|
|
730 |
});
|
731 |
|
732 |
// Send request to generate image prompts
|
733 |
+
await window.Poe.sendUserMessage(`@Claude-3.7-Sonnet ${prompt}`, {
|
734 |
handler: handlerId,
|
735 |
stream: false,
|
736 |
openChat: false
|
|
|
764 |
}
|
765 |
|
766 |
// Function to generate images based on prompts
|
767 |
+
async function generateImages(imagePrompts) {
|
768 |
try {
|
769 |
const imageUrls = [];
|
770 |
|
|
|
785 |
});
|
786 |
|
787 |
// Send request to generate image
|
788 |
+
await window.Poe.sendUserMessage(`@FLUX-pro-1.1 ${imagePrompts[i]}`, {
|
789 |
handler: handlerId,
|
790 |
stream: false,
|
791 |
openChat: false
|
|
|
808 |
}
|
809 |
|
810 |
// Function to generate speech from script
|
811 |
+
async function generateSpeech(script, language, ttsEngine = 'elevenlabs', ttsVoice = '') {
|
812 |
try {
|
813 |
// Use Poe API to send user message
|
814 |
const handlerId = 'speech-generation-handler';
|
|
|
826 |
|
827 |
// Prepare the prompt
|
828 |
let prompt = script;
|
829 |
+
if (ttsVoice) {
|
830 |
+
prompt += ` --voice ${ttsVoice}`;
|
831 |
}
|
832 |
|
833 |
// Send request to generate speech
|
834 |
+
await window.Poe.sendUserMessage(`@ElevenLabs ${prompt}`, {
|
835 |
handler: handlerId,
|
836 |
stream: false,
|
837 |
openChat: false
|
|
|
848 |
throw new Error('Failed to generate speech audio');
|
849 |
}
|
850 |
|
851 |
+
// Create an audio element to get the duration
|
852 |
+
const audio = new Audio();
|
853 |
+
audio.src = audioUrl;
|
854 |
+
|
855 |
+
// Wait for the audio to load to get its duration
|
856 |
+
const audioDuration = await new Promise((resolve) => {
|
857 |
+
audio.addEventListener('loadedmetadata', () => {
|
858 |
+
resolve(audio.duration);
|
859 |
+
});
|
860 |
+
|
861 |
+
// Fallback in case loadedmetadata doesn't fire
|
862 |
+
setTimeout(() => resolve(60), 5000); // Default to 60 seconds
|
863 |
+
});
|
864 |
+
|
865 |
+
return {
|
866 |
+
url: audioUrl,
|
867 |
+
duration: audioDuration
|
868 |
+
};
|
869 |
} catch (error) {
|
870 |
console.error('Error generating speech:', error);
|
871 |
throw new Error('Failed to generate speech');
|
872 |
}
|
873 |
}
|
874 |
|
875 |
+
// Function to generate simulated subtitles (word-level timing)
|
876 |
+
function generateSimulatedSubtitles(script) {
|
877 |
+
// Split script into words
|
878 |
+
const words = script.split(/\s+/);
|
879 |
+
const subtitles = [];
|
880 |
|
881 |
+
// Simulate timing for each word (we'd normally get this from AssemblyAI)
|
882 |
+
let currentTime = 0;
|
883 |
+
for (let i = 0; i < words.length; i++) {
|
884 |
+
const word = words[i];
|
885 |
+
// Simulate word duration based on length
|
886 |
+
const duration = 0.2 + (word.length * 0.05);
|
887 |
|
888 |
+
subtitles.push({
|
889 |
+
word: word,
|
890 |
+
start: currentTime,
|
891 |
+
end: currentTime + duration
|
892 |
+
});
|
|
|
|
|
|
|
893 |
|
894 |
+
currentTime += duration;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
895 |
}
|
896 |
+
|
897 |
+
return subtitles;
|
898 |
}
|
899 |
|
900 |
// Function to display results
|
901 |
+
function displayResults(data) {
|
902 |
// Hide loading container
|
903 |
document.getElementById('loading-container').classList.add('hidden');
|
904 |
document.getElementById('loading-container').classList.remove('flex');
|
|
|
907 |
document.getElementById('results-container').classList.remove('hidden');
|
908 |
|
909 |
// Set title and description
|
910 |
+
document.getElementById('video-title').textContent = data.metadata.title;
|
911 |
+
document.getElementById('video-description').textContent = data.metadata.description;
|
912 |
+
|
913 |
+
// Set script
|
914 |
+
document.getElementById('video-script').textContent = data.script;
|
915 |
+
|
916 |
+
// Set image prompts
|
917 |
+
const imagePromptsElement = document.getElementById('image-prompts');
|
918 |
+
imagePromptsElement.innerHTML = '';
|
919 |
+
data.imagePrompts.forEach((prompt, index) => {
|
920 |
+
const promptEl = document.createElement('div');
|
921 |
+
promptEl.className = 'mb-2';
|
922 |
+
promptEl.textContent = `${index + 1}. ${prompt}`;
|
923 |
+
imagePromptsElement.appendChild(promptEl);
|
924 |
+
});
|
925 |
|
926 |
+
// Set up image slideshow
|
927 |
+
const imageSlideshow = document.getElementById('image-slideshow');
|
928 |
+
imageSlideshow.innerHTML = '';
|
929 |
+
data.imageUrls.forEach((url, index) => {
|
930 |
+
const img = document.createElement('img');
|
931 |
+
img.src = url;
|
932 |
+
img.className = 'absolute top-0 left-0 w-full h-full object-cover transition-opacity duration-1000';
|
933 |
+
img.style.opacity = index === 0 ? '1' : '0';
|
934 |
+
img.dataset.index = index;
|
935 |
+
imageSlideshow.appendChild(img);
|
936 |
+
});
|
937 |
|
938 |
+
// Set up audio player
|
939 |
+
const audioPlayer = document.getElementById('audio-player');
|
940 |
+
audioPlayer.src = data.audioUrl;
|
941 |
+
audioPlayer.preload = 'auto';
|
942 |
+
|
943 |
+
// Set up subtitle container
|
944 |
+
const subtitleContainer = document.getElementById('subtitle-container');
|
945 |
+
subtitleContainer.innerHTML = '';
|
946 |
+
|
947 |
+
// Create elements for each word
|
948 |
+
data.subtitles.forEach(subtitle => {
|
949 |
+
const wordEl = document.createElement('span');
|
950 |
+
wordEl.className = 'subtitle-word';
|
951 |
+
wordEl.textContent = subtitle.word;
|
952 |
+
wordEl.dataset.start = subtitle.start;
|
953 |
+
wordEl.dataset.end = subtitle.end;
|
954 |
+
wordEl.style.color = data.subtitleSettings.color;
|
955 |
+
subtitleContainer.appendChild(wordEl);
|
956 |
+
});
|
957 |
+
|
958 |
+
// Set up video player controls
|
959 |
+
const playBtn = document.getElementById('play-btn');
|
960 |
+
const progressBar = document.getElementById('progress-bar');
|
961 |
+
|
962 |
+
let isPlaying = false;
|
963 |
+
let currentImageIndex = 0;
|
964 |
+
let slideInterval;
|
965 |
+
|
966 |
+
// Function to handle play/pause
|
967 |
+
function togglePlayPause() {
|
968 |
+
if (isPlaying) {
|
969 |
+
audioPlayer.pause();
|
970 |
+
clearInterval(slideInterval);
|
971 |
+
playBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
972 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
973 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
974 |
+
</svg>`;
|
975 |
+
} else {
|
976 |
+
audioPlayer.play();
|
977 |
+
playBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
978 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
979 |
+
</svg>`;
|
980 |
+
|
981 |
+
// Set up image slideshow interval
|
982 |
+
const slideDuration = data.audioDuration / data.imageUrls.length;
|
983 |
+
slideInterval = setInterval(() => {
|
984 |
+
const images = imageSlideshow.querySelectorAll('img');
|
985 |
+
images[currentImageIndex].style.opacity = '0';
|
986 |
+
currentImageIndex = (currentImageIndex + 1) % images.length;
|
987 |
+
images[currentImageIndex].style.opacity = '1';
|
988 |
+
}, slideDuration * 1000);
|
989 |
+
}
|
990 |
+
isPlaying = !isPlaying;
|
991 |
}
|
992 |
|
993 |
+
// Play button click handler
|
994 |
+
playBtn.addEventListener('click', togglePlayPause);
|
|
|
|
|
|
|
|
|
995 |
|
996 |
+
// Update progress bar
|
997 |
+
audioPlayer.addEventListener('timeupdate', () => {
|
998 |
+
const percent = (audioPlayer.currentTime / data.audioDuration) * 100;
|
999 |
+
progressBar.style.width = `${percent}%`;
|
1000 |
+
|
1001 |
+
// Update subtitle highlighting
|
1002 |
+
const currentTime = audioPlayer.currentTime;
|
1003 |
+
const subtitleWords = subtitleContainer.querySelectorAll('.subtitle-word');
|
1004 |
+
|
1005 |
+
subtitleWords.forEach(word => {
|
1006 |
+
const start = parseFloat(word.dataset.start);
|
1007 |
+
const end = parseFloat(word.dataset.end);
|
1008 |
+
|
1009 |
+
if (currentTime >= start && currentTime <= end) {
|
1010 |
+
word.classList.add('highlighted');
|
1011 |
+
word.style.backgroundColor = data.subtitleSettings.highlightColor;
|
1012 |
+
} else {
|
1013 |
+
word.classList.remove('highlighted');
|
1014 |
+
word.style.backgroundColor = 'transparent';
|
1015 |
+
}
|
1016 |
+
});
|
1017 |
+
});
|
1018 |
|
1019 |
+
// Reset when audio ends
|
1020 |
+
audioPlayer.addEventListener('ended', () => {
|
1021 |
+
isPlaying = false;
|
1022 |
+
clearInterval(slideInterval);
|
1023 |
+
playBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
1024 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
1025 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
1026 |
+
</svg>`;
|
1027 |
+
|
1028 |
+
// Reset images
|
1029 |
+
const images = imageSlideshow.querySelectorAll('img');
|
1030 |
+
images.forEach((img, i) => {
|
1031 |
+
img.style.opacity = i === 0 ? '1' : '0';
|
1032 |
+
});
|
1033 |
+
currentImageIndex = 0;
|
1034 |
+
|
1035 |
+
// Reset subtitles
|
1036 |
+
const subtitleWords = subtitleContainer.querySelectorAll('.subtitle-word');
|
1037 |
+
subtitleWords.forEach(word => {
|
1038 |
+
word.classList.remove('highlighted');
|
1039 |
+
word.style.backgroundColor = 'transparent';
|
1040 |
+
});
|
1041 |
+
});
|
1042 |
}
|
1043 |
</script>
|
1044 |
</body>
|