PierreBrunelle
commited on
Commit
•
b2f0dbe
1
Parent(s):
37b78fd
Simplified the app
Browse filesRelied only on Pixeltable and removed loops
app.py
CHANGED
@@ -1,239 +1,127 @@
|
|
1 |
-
|
2 |
-
import gradio as gr
|
3 |
import pixeltable as pxt
|
4 |
import os
|
|
|
|
|
5 |
import getpass
|
6 |
-
from pixeltable.functions.video import extract_audio
|
7 |
-
from pixeltable.functions import openai as pxop
|
8 |
from pixeltable.iterators import FrameIterator
|
9 |
-
import
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
-
# Define constants
|
19 |
MAX_VIDEO_SIZE_MB = 35
|
20 |
-
GPT_MODEL = "gpt-4o-mini-2024-07-18"
|
21 |
-
MAX_TOKENS = 500
|
22 |
-
WHISPER_MODEL = "whisper-1"
|
23 |
-
|
24 |
-
# Set OpenAI API key
|
25 |
-
if "OPENAI_API_KEY" not in os.environ:
|
26 |
-
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:")
|
27 |
-
|
28 |
-
# Clean up existing database and table if they exist
|
29 |
-
pxt.drop_dir("video_db", force=True)
|
30 |
-
if table_name in pxt.list_tables():
|
31 |
-
pxt.drop_table("video_db.video_table")
|
32 |
-
|
33 |
-
# Create or use existing directory and table
|
34 |
-
if db_directory not in pxt.list_dirs():
|
35 |
-
pxt.create_dir(db_directory)
|
36 |
-
else:
|
37 |
-
print(f"Directory {db_directory} already exists. Using the existing directory.")
|
38 |
-
|
39 |
-
if table_name not in pxt.list_tables():
|
40 |
-
t = pxt.create_table(
|
41 |
-
f"{db_directory}.{table_name}",
|
42 |
-
{
|
43 |
-
"video": pxt.VideoType(),
|
44 |
-
"video_filename": pxt.StringType(),
|
45 |
-
"sm_type": pxt.StringType(),
|
46 |
-
"sm_post": pxt.StringType(),
|
47 |
-
},
|
48 |
-
)
|
49 |
-
else:
|
50 |
-
t = pxt.get_table(f"{db_directory}.{table_name}")
|
51 |
-
print(f"Table {table_name} already exists. Using the existing table.")
|
52 |
-
|
53 |
-
# Function to generate social media post using OpenAI GPT-4 API
|
54 |
-
def generate_social_media_post(transcript_text, social_media_type):
|
55 |
-
response = openai.chat.completions.create(
|
56 |
-
model=GPT_MODEL,
|
57 |
-
messages=[
|
58 |
-
{
|
59 |
-
"role": "system",
|
60 |
-
"content": f"You are an expert in creating social media content for {social_media_type}.",
|
61 |
-
},
|
62 |
-
{
|
63 |
-
"role": "user",
|
64 |
-
"content": f"Generate an effective and casual social media post based on this video transcript below. Make it a viral and suitable post for {social_media_type}. Transcript:\n{transcript_text}.",
|
65 |
-
},
|
66 |
-
],
|
67 |
-
max_tokens=MAX_TOKENS,
|
68 |
-
)
|
69 |
-
return response.choices[0].message.content
|
70 |
-
|
71 |
-
# Function to process the uploaded video and generate the post
|
72 |
-
def process_and_generate_post(video_file, social_media_type):
|
73 |
-
if video_file:
|
74 |
-
try:
|
75 |
-
# Check video file size
|
76 |
-
video_size = os.path.getsize(video_file) / (1024 * 1024) # Convert to MB
|
77 |
-
if video_size > MAX_VIDEO_SIZE_MB:
|
78 |
-
return f"The video file is larger than {MAX_VIDEO_SIZE_MB} MB. Please upload a smaller file."
|
79 |
-
|
80 |
-
video_filename = os.path.basename(video_file)
|
81 |
-
tr_audio_gen_flag = True
|
82 |
-
sm_gen_flag = True
|
83 |
-
|
84 |
-
# Check if video already exists in the table
|
85 |
-
video_df = t.where(t.video_filename == video_filename).tail(1)
|
86 |
-
if t.select().where(t.video_filename == video_filename).count() >= 1:
|
87 |
-
tr_audio_gen_flag = False
|
88 |
-
|
89 |
-
# Check if video and social media type combination exists
|
90 |
-
video_type_df = t.where(
|
91 |
-
(t.video_filename == video_filename) & (t.sm_type == social_media_type)
|
92 |
-
).tail(1)
|
93 |
-
if video_type_df:
|
94 |
-
sm_gen_flag = False
|
95 |
-
|
96 |
-
# Insert video into PixelTable if it doesn't exist or if it's a new social media type
|
97 |
-
if (
|
98 |
-
(t.count() < 1)
|
99 |
-
or not (
|
100 |
-
t.select().where(t.video_filename == video_filename).count() >= 1
|
101 |
-
)
|
102 |
-
or (video_df and not video_type_df)
|
103 |
-
):
|
104 |
-
t.insert(
|
105 |
-
[
|
106 |
-
{
|
107 |
-
"video": video_file,
|
108 |
-
"video_filename": video_filename,
|
109 |
-
"sm_type": social_media_type,
|
110 |
-
"sm_post": "",
|
111 |
-
}
|
112 |
-
]
|
113 |
-
)
|
114 |
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
t["audio"] = extract_audio(t.video, format="mp3")
|
119 |
-
else:
|
120 |
-
t.audio = extract_audio(t.video, format="mp3")
|
121 |
-
|
122 |
-
print("########### processing transcription #############")
|
123 |
-
|
124 |
-
if not t.get_column(name="transcription"):
|
125 |
-
t["transcription"] = pxop.transcriptions(
|
126 |
-
t.audio, model=WHISPER_MODEL
|
127 |
-
)
|
128 |
-
else:
|
129 |
-
t.transcription = pxop.transcriptions(t.audio, model=WHISPER_MODEL)
|
130 |
-
|
131 |
-
# Get the current video data
|
132 |
-
filtered_df = t.where(
|
133 |
-
(t.video_filename == video_filename) & (t.sm_type == social_media_type)
|
134 |
-
).tail(1)
|
135 |
-
|
136 |
-
if len(filtered_df) == 0:
|
137 |
-
return "No matching video found in the table. Please ensure the video is uploaded correctly and try again."
|
138 |
-
|
139 |
-
cur_video_df = filtered_df[0]
|
140 |
-
plain_text = cur_video_df["transcription"]["text"]
|
141 |
-
|
142 |
-
# Generate or retrieve social media post
|
143 |
-
if (
|
144 |
-
t.select()
|
145 |
-
.where(
|
146 |
-
(t.video_filename == video_filename)
|
147 |
-
& (t.sm_type == social_media_type)
|
148 |
-
& (t.sm_post != "")
|
149 |
-
)
|
150 |
-
.count()
|
151 |
-
>= 1
|
152 |
-
):
|
153 |
-
print("retrieving existing social media post")
|
154 |
-
social_media_post = (
|
155 |
-
t.select(t.sm_post)
|
156 |
-
.where(
|
157 |
-
(t.sm_type == social_media_type)
|
158 |
-
& (t.video_filename == video_filename)
|
159 |
-
)
|
160 |
-
.collect()["sm_post"]
|
161 |
-
)
|
162 |
-
else:
|
163 |
-
print("generating new social media post")
|
164 |
-
social_media_post = generate_social_media_post(
|
165 |
-
plain_text, social_media_type
|
166 |
-
)
|
167 |
-
if sm_gen_flag:
|
168 |
-
cur_video_df.update({"sm_post": social_media_post})
|
169 |
-
social_media_post = cur_video_df["sm_post"]
|
170 |
|
|
|
|
|
|
|
|
|
|
|
171 |
|
172 |
-
|
|
|
|
|
|
|
|
|
173 |
|
174 |
-
|
175 |
-
|
176 |
-
# images.append(frame['frame']) # frame['frame'] is a PIL image
|
177 |
-
|
178 |
-
if f"{db_directory}.{view_name}" in pxt.list_tables():
|
179 |
-
pxt.drop_table(f"{db_directory}.{view_name}")
|
180 |
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
iterator=FrameIterator.create(video=t.video, num_frames = 4)
|
185 |
-
)
|
186 |
|
187 |
-
|
188 |
-
|
189 |
-
return social_media_post, [frame['frame'] for frame in frames]
|
190 |
|
191 |
-
|
192 |
-
|
193 |
-
else:
|
194 |
-
return "Please upload a video file."
|
195 |
|
196 |
# Gradio Interface
|
|
|
|
|
197 |
def gradio_interface():
|
198 |
with gr.Blocks(theme=gr.themes.Glass()) as demo:
|
199 |
-
# Set up the UI components
|
200 |
-
gr.Markdown(
|
201 |
-
"""<center><font size=12>Video to Social Media Post Generator</center>"""
|
202 |
-
)
|
203 |
-
gr.Markdown(
|
204 |
-
"""<div align="center">
|
205 |
-
<img src="https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/source/data/pixeltable-logo-large.png" alt="Pixeltable" width="20%" />
|
206 |
-
"""
|
207 |
-
)
|
208 |
gr.Markdown(
|
209 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
210 |
)
|
211 |
-
gr.Markdown(
|
212 |
-
"""<center>Pixeltable is a Python library providing a declarative interface for multimodal data (text, images, audio, video). It features built-in versioning, lineage tracking, and incremental updates, enabling users to store, transform, index, and iterate on data for their ML workflows. Data transformations, model inference, and custom logic are embedded as computed columns.
|
213 |
-
</center>"""
|
214 |
-
)
|
215 |
-
video_input = gr.Video(label=f"Upload Video File (max {MAX_VIDEO_SIZE_MB} MB):",
|
216 |
-
include_audio = True,
|
217 |
-
max_length= 300,
|
218 |
-
height='400px',
|
219 |
-
autoplay= True)
|
220 |
-
social_media_type = gr.Dropdown(
|
221 |
-
choices=["X (Twitter)", "Facebook", "LinkedIn"],
|
222 |
-
label="Select Social Media Platform:",
|
223 |
-
value="X (Twitter)",
|
224 |
-
)
|
225 |
-
generate_btn = gr.Button("Generate Post", interactive= True)
|
226 |
-
|
227 |
-
output = gr.Textbox(label="Generated Social Media Post", show_copy_button=True)
|
228 |
|
229 |
-
|
230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
231 |
|
232 |
-
|
233 |
-
[["example1.mp4"], ["example2.mp4"],["example3.mp4"]],
|
|
|
234 |
)
|
235 |
|
236 |
-
# Connect the generate button to the processing function
|
237 |
generate_btn.click(
|
238 |
fn=process_and_generate_post,
|
239 |
inputs=[video_input, social_media_type],
|
@@ -243,4 +131,5 @@ def gradio_interface():
|
|
243 |
return demo
|
244 |
|
245 |
# Launch the Gradio interface
|
246 |
-
|
|
|
|
1 |
+
|
|
|
2 |
import pixeltable as pxt
|
3 |
import os
|
4 |
+
import openai
|
5 |
+
import gradio as gr
|
6 |
import getpass
|
|
|
|
|
7 |
from pixeltable.iterators import FrameIterator
|
8 |
+
from pixeltable.functions.video import extract_audio
|
9 |
+
from pixeltable.functions.audio import get_metadata
|
10 |
+
from pixeltable.functions import openai
|
11 |
+
|
12 |
+
if 'OPENAI_API_KEY' not in os.environ:
|
13 |
+
os.environ['OPENAI_API_KEY'] = getpass.getpass('Enter your OpenAI API key:')
|
14 |
+
|
15 |
+
pxt.drop_dir('directory', force=True)
|
16 |
+
pxt.create_dir('directory')
|
17 |
+
|
18 |
+
t = pxt.create_table(
|
19 |
+
'directory.video_table', {
|
20 |
+
"video": pxt.VideoType(nullable=True),
|
21 |
+
"sm_type": pxt.StringType(nullable=True),
|
22 |
+
}
|
23 |
+
)
|
24 |
+
|
25 |
+
frames_view = pxt.create_view(
|
26 |
+
"directory.frames",
|
27 |
+
t,
|
28 |
+
iterator=FrameIterator.create(video=t.video, num_frames=20)
|
29 |
+
)
|
30 |
+
|
31 |
+
# Create computed columns to store transformations and persist outputs
|
32 |
+
t['audio'] = extract_audio(t.video, format='mp3')
|
33 |
+
t['metadata'] = get_metadata(t.audio)
|
34 |
+
t['transcription'] = openai.transcriptions(audio=t.audio, model='whisper-1')
|
35 |
+
t['transcription_text'] = t.transcription.text
|
36 |
+
|
37 |
+
@pxt.udf
|
38 |
+
def prompt(A: str, B: str) -> list[dict]:
|
39 |
+
return [
|
40 |
+
{'role': 'system', 'content': 'You are an expert in creating social media content and you generate effective post, based on the video transcript and the type of social media asked for. Please respect the limitations in terms of characters and size of each social media platform'},
|
41 |
+
{'role': 'user', 'content': f'A: "{A}" \n B: "{B}"'}
|
42 |
+
]
|
43 |
+
|
44 |
+
t['message'] = prompt(t.sm_type, t.transcription_text)
|
45 |
+
|
46 |
+
t['response'] = openai.chat_completions(messages=t.message, model='gpt-4o-mini-2024-07-18', max_tokens=500)
|
47 |
+
t['answer'] = t.response.choices[0].message.content
|
48 |
|
|
|
49 |
MAX_VIDEO_SIZE_MB = 35
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
|
51 |
+
def process_and_generate_post(video_file, social_media_type):
|
52 |
+
if not video_file:
|
53 |
+
return "Please upload a video file.", None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
|
55 |
+
try:
|
56 |
+
# Check video file size
|
57 |
+
video_size = os.path.getsize(video_file) / (1024 * 1024) # Convert to MB
|
58 |
+
if video_size > MAX_VIDEO_SIZE_MB:
|
59 |
+
return f"The video file is larger than {MAX_VIDEO_SIZE_MB} MB. Please upload a smaller file.", None
|
60 |
|
61 |
+
# Insert video in PixelTable
|
62 |
+
t.insert([{
|
63 |
+
"video": video_file,
|
64 |
+
"sm_type": social_media_type
|
65 |
+
}])
|
66 |
|
67 |
+
# Retrieve Social media posts
|
68 |
+
social_media_post = t.select(t.answer).tail(1)['answer'][0]
|
|
|
|
|
|
|
|
|
69 |
|
70 |
+
# Retrieve thumbnails
|
71 |
+
frames = frames_view.select(frames_view.frame).tail(4)
|
72 |
+
thumbnails = [frame['frame'] for frame in frames]
|
|
|
|
|
73 |
|
74 |
+
#Display content
|
75 |
+
return social_media_post, thumbnails
|
|
|
76 |
|
77 |
+
except Exception as e:
|
78 |
+
return f"An error occurred: {str(e)}", None
|
|
|
|
|
79 |
|
80 |
# Gradio Interface
|
81 |
+
import gradio as gr
|
82 |
+
|
83 |
def gradio_interface():
|
84 |
with gr.Blocks(theme=gr.themes.Glass()) as demo:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
gr.Markdown(
|
86 |
+
"""
|
87 |
+
<center>
|
88 |
+
<h1>Video to Social Media Post Generator</h1>
|
89 |
+
<img src="https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/source/data/pixeltable-logo-large.png" alt="Pixeltable" width="20%" />
|
90 |
+
<p>Pixeltable is a declarative interface for working with text, images, embeddings, and even video, enabling you to store, transform, index, and iterate on data.</p>
|
91 |
+
</center>
|
92 |
+
"""
|
93 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
|
95 |
+
with gr.Row():
|
96 |
+
with gr.Column():
|
97 |
+
video_input = gr.Video(
|
98 |
+
label=f"Upload Video File (max {MAX_VIDEO_SIZE_MB} MB):",
|
99 |
+
include_audio=True,
|
100 |
+
max_length=300,
|
101 |
+
height='400px',
|
102 |
+
autoplay=True
|
103 |
+
)
|
104 |
+
social_media_type = gr.Dropdown(
|
105 |
+
choices=["X (Twitter)", "Facebook", "LinkedIn", "Instagram"],
|
106 |
+
label="Select Social Media Platform:",
|
107 |
+
value="X (Twitter)",
|
108 |
+
)
|
109 |
+
generate_btn = gr.Button("Generate Post")
|
110 |
+
|
111 |
+
with gr.Column():
|
112 |
+
output = gr.Textbox(label="Generated Social Media Post", show_copy_button=True)
|
113 |
+
thumbnail = gr.Gallery(
|
114 |
+
label="Pick your favorite Post Thumbnail",
|
115 |
+
show_download_button=True,
|
116 |
+
show_fullscreen_button=True,
|
117 |
+
height='400px'
|
118 |
+
)
|
119 |
|
120 |
+
gr.Examples(
|
121 |
+
examples=[["example1.mp4"], ["example2.mp4"], ["example3.mp4"]],
|
122 |
+
inputs=[video_input]
|
123 |
)
|
124 |
|
|
|
125 |
generate_btn.click(
|
126 |
fn=process_and_generate_post,
|
127 |
inputs=[video_input, social_media_type],
|
|
|
131 |
return demo
|
132 |
|
133 |
# Launch the Gradio interface
|
134 |
+
if __name__ == "__main__":
|
135 |
+
gradio_interface().launch(show_api=False)
|