Skip to content

Commit ded9b27

Browse files
committed
Update the generation
- Let user select videos and search
1 parent fc3ca8d commit ded9b27

File tree

11 files changed

+325
-23
lines changed

11 files changed

+325
-23
lines changed

Backend/classes/Shorts.py

+27-18
Original file line numberDiff line numberDiff line change
@@ -177,27 +177,36 @@ def GenerateSearchTerms(self):
177177
return self.search_terms
178178

179179
#Download the videos base on the search terms from pexel api
180-
def DownloadVideos(self):
180+
def DownloadVideos(self, selectedVideoUrls):
181181
global GENERATING
182+
182183
# Search for videos
183-
for search_term in self.search_terms:
184-
global GENERATING
185-
if not GENERATING:
186-
return jsonify(
187-
{
188-
"status": "error",
189-
"message": "Video generation was cancelled.",
190-
"data": [],
191-
}
184+
# Check if the selectedVideoUrls is empty
185+
if selectedVideoUrls and len(selectedVideoUrls) > 0:
186+
print(colored(f"Selected videos: {selectedVideoUrls}", "green"))
187+
# filter the selectedVideoUrls is a Array of objects with videoUrl object that has a link key with a value we use the value of the link key
188+
self.video_urls = [video_url["videoUrl"]["link"] for video_url in selectedVideoUrls]
189+
# log the selectedVideoUrls
190+
print(colored(f"Selected video urls: {self.video_urls}", "green"))
191+
else:
192+
for search_term in self.search_terms:
193+
global GENERATING
194+
if not GENERATING:
195+
return jsonify(
196+
{
197+
"status": "error",
198+
"message": "Video generation was cancelled.",
199+
"data": [],
200+
}
201+
)
202+
found_urls = search_for_stock_videos(
203+
search_term, os.getenv("PEXELS_API_KEY"), self.videos_quantity_search, self.min_duration_search
192204
)
193-
found_urls = search_for_stock_videos(
194-
search_term, os.getenv("PEXELS_API_KEY"), self.videos_quantity_search, self.min_duration_search
195-
)
196-
# Check for duplicates
197-
for url in found_urls:
198-
if url not in self.video_urls:
199-
self.video_urls.append(url)
200-
break
205+
# Check for duplicates
206+
for url in found_urls:
207+
if url not in self.video_urls:
208+
self.video_urls.append(url)
209+
break
201210

202211
# Check if video_urls is empty
203212
if not self.video_urls:

Backend/main.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ def search_and_download():
256256
script = data["script"]
257257
ai_model = data["aiModel"]
258258
voice = data["voice"]
259+
selectedVideoUrls = data.get("selectedVideoUrls",[])
259260

260261
# Extra options:
261262
custom_video = data.get("videoUrls",[])
@@ -273,7 +274,7 @@ def search_and_download():
273274
videoClass.final_script = script
274275
videoClass.subtitles_position = subtitles_position
275276

276-
videoClass.DownloadVideos()
277+
videoClass.DownloadVideos(selectedVideoUrls)
277278

278279
videoClass.GenerateVoice(voice)
279280

UI/components/VideoSearch.vue

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<script lang="ts" setup>
2+
/**
3+
*
4+
* Component to preview and select videos base on the user related search terms
5+
*
6+
* @author Ismael García <[email protected]>
7+
* @version 0.0.1
8+
*
9+
* @todo [ ] Test the component
10+
* @todo [ ] Integration test.
11+
* @todo [✔] Update the typescript.
12+
*/
13+
interface VideoResult {
14+
url: string;
15+
image: string;
16+
video_files: {
17+
fileType: string;
18+
link: string;
19+
quality: string;
20+
}[];
21+
}
22+
23+
const { video } = useVideoSettings();
24+
25+
const searchResults = ref<VideoResultFormat[]>([]);
26+
27+
const $URL_SEARCH = `https://api.pexels.com/videos/search`;
28+
29+
const {
30+
public: { pexelsApiKey },
31+
} = useRuntimeConfig();
32+
const HandleSearch = async () => {
33+
const termsToSearch: string[] = video.value.search.split(",");
34+
termsToSearch.forEach(async (term) => {
35+
// Fetch result from pexels
36+
console.log("Key", pexelsApiKey);
37+
38+
const { data } = await useFetch<{ videos: VideoResult[] }>(
39+
`${$URL_SEARCH}?query=${term}&per_page=20`,
40+
{
41+
headers: {
42+
Authorization: `${pexelsApiKey}`,
43+
},
44+
}
45+
);
46+
47+
//Get the video of the results
48+
if (!data.value?.videos) return;
49+
const formattedVideos = data.value.videos.map((video) => {
50+
return {
51+
url: video.url,
52+
image: video.image,
53+
videoUrl: video.video_files.find(
54+
(videoFile) => videoFile.link && videoFile.quality === "hd"
55+
),
56+
};
57+
});
58+
searchResults.value = [...searchResults.value, ...formattedVideos];
59+
});
60+
};
61+
62+
const HandleSelectVideo = (v: VideoResultFormat) => {
63+
if (selectedUrls.value.includes(v.url)) {
64+
video.value.selectedVideoUrls = video.value.selectedVideoUrls.filter(
65+
(video) => video.url !== v.url
66+
);
67+
} else {
68+
if (!video.value.selectedVideoUrls) {
69+
video.value.selectedVideoUrls = [];
70+
}
71+
video.value.selectedVideoUrls.push(v);
72+
}
73+
};
74+
75+
const selectedUrls = computed(() => {
76+
return video.value.selectedVideoUrls?.map((video) => video.url) || [];
77+
});
78+
</script>
79+
80+
<template>
81+
<div>
82+
<div class="max-w-5xl mx-auto">
83+
<n-input-group>
84+
<n-input v-model:value="video.search" />
85+
<n-button type="success" ghost @click="HandleSearch"> Search </n-button>
86+
</n-input-group>
87+
</div>
88+
<div class="max-w-5xl mx-auto mt-10">
89+
<section class="grid grid-cols-3 gap-10">
90+
<div
91+
v-for="result in searchResults"
92+
:key="result.url"
93+
:value="result.url"
94+
class="relative"
95+
>
96+
<video
97+
v-if="result.videoUrl"
98+
:src="result.videoUrl?.link"
99+
controls
100+
:poster="result.image"
101+
></video>
102+
<n-button
103+
:type="selectedUrls.includes(result.url) ? 'success' : 'primary'"
104+
@click="HandleSelectVideo(result)"
105+
circle
106+
size="small"
107+
class="absolute top-2 right-2"
108+
>
109+
<template #icon>
110+
<Icon
111+
:name="
112+
selectedUrls.includes(result.url) ? 'mdi:check' : 'mdi:plus'
113+
"
114+
/>
115+
</template>
116+
</n-button>
117+
</div>
118+
</section>
119+
</div>
120+
</div>
121+
</template>
122+
<style scoped></style>

UI/components/VideoSelected.vue

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<script lang="ts" setup>
2+
/**
3+
*
4+
* Selected videos
5+
*
6+
* @author Reflect-Media <reflect.media GmbH>
7+
* @version 0.0.1
8+
*
9+
* @todo [ ] Test the component
10+
* @todo [ ] Integration test.
11+
* @todo [✔] Update the typescript.
12+
*/
13+
14+
const { video } = useVideoSettings();
15+
16+
const HandleSelectVideo = (v: VideoResultFormat) => {
17+
if (selectedUrls.value.includes(v.url)) {
18+
video.value.selectedVideoUrls = video.value.selectedVideoUrls.filter(
19+
(video) => video.url !== v.url
20+
);
21+
} else {
22+
video.value.selectedVideoUrls.push(v);
23+
}
24+
};
25+
26+
const selectedUrls = computed(() => {
27+
return video.value.selectedVideoUrls?.map((video) => video.url) || [];
28+
});
29+
</script>
30+
31+
<template>
32+
<div class="max-w-5xl mx-auto mt-10">
33+
<section class="grid grid-cols-3 gap-10">
34+
<div
35+
v-for="result in video.selectedVideoUrls"
36+
:key="result.url"
37+
:value="result.url"
38+
class="relative"
39+
>
40+
<video
41+
v-if="result.videoUrl"
42+
:src="result.videoUrl?.link"
43+
controls
44+
:poster="result.image"
45+
></video>
46+
<n-button
47+
:type="
48+
video.selectedVideoUrls.includes(result) ? 'success' : 'primary'
49+
"
50+
@click="HandleSelectVideo(result)"
51+
circle
52+
size="small"
53+
class="absolute top-2 right-2"
54+
>
55+
<template #icon>
56+
<Icon
57+
:name="
58+
video.selectedVideoUrls.includes(result)
59+
? 'mdi:check'
60+
: 'mdi:plus'
61+
"
62+
/>
63+
</template>
64+
</n-button>
65+
</div>
66+
</section>
67+
</div>
68+
</template>
69+
<style scoped></style>

UI/composables/useVideoSetings.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
1+
export interface VideoResultFormat {
2+
url: string;
3+
image: string;
4+
videoUrl?: {
5+
fileType: string;
6+
link: string;
7+
quality: string;
8+
};
9+
}
10+
111
export const useVideoSettings = () => {
2-
const video = useLocalStorage('VideoSettings',{
12+
const video = useLocalStorage<{
13+
script: string;
14+
voice: string;
15+
videoSubject: string;
16+
extraPrompt: string;
17+
search: string;
18+
aiModel: string;
19+
finalVideoUrl: string;
20+
selectedAudio: string;
21+
selectedVideoUrls: VideoResultFormat[];
22+
}>('VideoSettings',{
323
script: "",
424
voice: "en_us_001",
525
videoSubject: "",
@@ -11,7 +31,8 @@ export const useVideoSettings = () => {
1131
finalVideoUrl: "",
1232
// Audio related
1333

14-
selectedAudio: ""
34+
selectedAudio: "",
35+
selectedVideoUrls: [],
1536
});
1637

1738

UI/content/docs/how-to-use.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
title: 'Short generator how to use'
3+
description: 'Small tutorial on how to use the short generator'
4+
---
5+
6+
7+
8+
# How to use the Short Generator
9+
10+
11+
1. Click on the "Generate" button to start the process of generating a new short
12+
1. Enter a topic of what the short will be about
13+
2. Add extra prompt information if needed
14+
3. Review the script
15+
4. Select a specific voice to use or set a global default voice for all generations
16+
5. Update the search terms if needed
17+
1. Or can search manually and select the videos that you like by clicking on them
18+
2, view all the selected vieos -> Click on the "Search and select videos" button and then click on the tab "Selected Videos" to see all the videos that you have selected
19+
6. Click on the "Generate" button
20+
7. You can add your own music to the video by selecting a music track then click on "Add music"

UI/content/docs/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ description: 'MoneyPrinter Documentation'
66

77
# Documentation related to the Money Printer UI
88

9-
[Project roadmap](/docs/road-map)
9+
[Project roadmap](/docs/road-map) | [How to use](/docs/how-to-use)
1010

1111
## Getting started
1212

UI/nuxt.config.ts

+5
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,9 @@ export default defineNuxtConfig({
3535
langDir: "locales",
3636
defaultLocale: "en",
3737
},
38+
runtimeConfig: {
39+
public: {
40+
pexelsApiKey: process.env.PEXELS_API_KEY,
41+
},
42+
},
3843
});

UI/pages/docs/[...slug].vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<main class="prose mx-auto py-20 max-w-5xl">
2+
<main class="prose mx-auto py-20 max-w-5xl dark:prose-invert">
33
<ContentDoc />
44
</main>
55
</template>

0 commit comments

Comments
 (0)