Execute application with Gitea Actions (#4)
Closes Broadcast/video-summary-bot#1 Closes Broadcast/video-summary-bot#3 Reviewed-on: #4
This commit is contained in:
parent
d55b346b81
commit
46cb748bc6
12
.env_example
12
.env_example
@ -1,5 +1,7 @@
|
|||||||
OPENAI_API_KEY =
|
INPUT_VIDEO_URL=
|
||||||
OPENAI_BASE_URL =
|
OPENAI_API_KEY=
|
||||||
OPENAI_RESPONSES_PROMPT =
|
OPENAI_BASE_URL=
|
||||||
SEGMENT_DURATION =
|
OPENAI_TRANSCRIPTION_MODEL=
|
||||||
TMP_AUDIO_PATH =
|
OPENAI_CHAT_SYSTEM_PROMPT=
|
||||||
|
OPENAI_CHAT_MODEL=
|
||||||
|
OPENAI_CHAT_N=
|
||||||
|
29
.gitea/workflows/execute.yaml
Normal file
29
.gitea/workflows/execute.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: execute
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
video-url:
|
||||||
|
description: "URL for the video to be analyzed"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Python:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
OPENAI_API_KEY: "nokey"
|
||||||
|
OPENAI_BASE_URL: "http://192.168.1.168/v1"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.10"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Run applicaiton
|
||||||
|
run: python app.py
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,4 @@
|
|||||||
|
__pycache__/*
|
||||||
|
tmp/*
|
||||||
venv/*
|
venv/*
|
||||||
.env
|
.env
|
||||||
|
84
app.py
84
app.py
@ -1,49 +1,79 @@
|
|||||||
import argparse
|
|
||||||
import os
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from moviepy import VideoFileClip
|
from moviepy import VideoFileClip
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
from pydub import AudioSegment
|
from pydub import AudioSegment
|
||||||
|
|
||||||
DEFAULT_PROMPT = "You will be provided a video transcription for which you are to generate a blog post in Markdown format summarizing the video contents."
|
from prompt import DEFAULT_PROMPT
|
||||||
|
|
||||||
def main(args):
|
VIDEO_URL = os.getenv('INPUT_VIDEO_URL', None)
|
||||||
openai_client = OpenAI()
|
OUTPUT_PATH = os.getenv('OUTPUT_PATH', 'tmp')
|
||||||
|
AUDIO_SEGMENT_DURATION = 30000
|
||||||
|
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY', None)
|
||||||
|
OPENAI_BASE_URL = os.getenv('OPENAI_BASE_URL', 'https://api.openai.com/v1')
|
||||||
|
OPENAI_TRANSCRIPTION_MODEL = os.getenv('OPENAI_TRANSCRIPTION_MODEL', 'whisper-1')
|
||||||
|
OPENAI_CHAT_SYSTEM_PROMPT = os.getenv('OPENAI_CHAT_SYSTEM_PROMPT', DEFAULT_PROMPT)
|
||||||
|
OPENAI_CHAT_MODEL = os.getenv('OPENAI_CHAT_MODEL', 'whisper-1')
|
||||||
|
OPENAI_CHAT_N = int(os.getenv('OPENAI_CHAT_N', '3'))
|
||||||
|
|
||||||
|
def main():
|
||||||
|
openai_client = OpenAI(
|
||||||
|
base_url = OPENAI_BASE_URL,
|
||||||
|
api_key = OPENAI_API_KEY
|
||||||
|
)
|
||||||
return summarize_transcription(
|
return summarize_transcription(
|
||||||
openai_client,
|
openai_client,
|
||||||
transcribe_audio(
|
transcribe_audio(
|
||||||
openai_client,
|
openai_client,
|
||||||
get_audio_from_video(args.video_file_path)
|
get_audio_from_video(
|
||||||
|
get_video_from_url()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_audio_from_video(video_file_path):
|
def get_video_from_url():
|
||||||
tmp_audio_path = os.getenv('TMP_AUDIO_PATH', '/tmp/video_summary_bot_tmp_audio.wav')
|
filename = VIDEO_URL.split('/')[-1]
|
||||||
VideoFileClip(video_file_path).audio.write_audiofile(tmp_audio_path)
|
with open(f"{OUTPUT_PATH}/{filename}", 'wb') as f:
|
||||||
return AudioSegment.from_wav(tmp_audio_path)
|
for chunk in requests.get(VIDEO_URL).iter_content(chunk_size=255):
|
||||||
|
if chunk:
|
||||||
|
f.write(chunk)
|
||||||
|
return filename
|
||||||
|
|
||||||
def transcribe_audio(openai_client, audio):
|
def get_audio_from_video(video_filename):
|
||||||
segment_duration = int(os.getenv('SEGMENT_DURATION', 30000)),
|
VideoFileClip(f"{OUTPUT_PATH}/{video_filename}").audio.write_audiofile(f"{OUTPUT_PATH}/{video_filename}.wav")
|
||||||
transcription_model = os.getenv('OPENAI_TRANSCRIPTION_MODEL', 'whisper-1')
|
audio = AudioSegment.from_wav(f"{OUTPUT_PATH}/{video_filename}.wav")
|
||||||
|
segments = []
|
||||||
|
for i in range(0, len(audio), AUDIO_SEGMENT_DURATION):
|
||||||
|
segment = audio[i:i + AUDIO_SEGMENT_DURATION]
|
||||||
|
path = f"{OUTPUT_PATH}/audio_segment_{i // AUDIO_SEGMENT_DURATION}.wav"
|
||||||
|
segments.append(path)
|
||||||
|
segment.export(path, format='wav')
|
||||||
|
return segments
|
||||||
|
|
||||||
|
def transcribe_audio(openai_client, audio_segments):
|
||||||
return ' '.join([
|
return ' '.join([
|
||||||
openai_client.audio.transcriptions.create(
|
openai_client.audio.transcriptions.create(
|
||||||
model=transcription_model,
|
model=OPENAI_TRANSCRIPTION_MODEL,
|
||||||
file=each
|
file=open(each, 'rb')
|
||||||
).text for each in [audio[i:i + segment_duration] for i in range(0, len(audio), segment_duration)]
|
).text for each in audio_segments
|
||||||
])
|
])
|
||||||
|
|
||||||
def summarize_transcription(openai_client, transcription):
|
def summarize_transcription(openai_client, transcription):
|
||||||
prompt = os.getenv('OPENAI_RESPONSES_PROMPT', DEFAULT_PROMPT)
|
return openai_client.completions.create(
|
||||||
responses_model = os.getenv('OPENAI_RESPONSES_MODEL', 'whisper-1')
|
model=OPENAI_CHAT_MODEL,
|
||||||
return client.responses.create(
|
prompt=OPENAI_CHAT_SYSTEM_PROMPT.format(transcription)
|
||||||
model=responses_model,
|
).choices
|
||||||
instructions=prompt,
|
|
||||||
input=transcription
|
def setup():
|
||||||
)
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
def cleanup():
|
||||||
|
os.rmdir(OUTPUT_PATH)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
load_dotenv()
|
setup()
|
||||||
parser = argparse.ArgumentParser(description="Use AI models to summarize videos")
|
for each in main():
|
||||||
parser.add_argument('--video-file-path', type=str, help="Path to the video to be summarized")
|
print("========")
|
||||||
main(parser.parse_args())
|
print(each.text)
|
||||||
|
19
prompt.py
Normal file
19
prompt.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
DEFAULT_PROMPT = """
|
||||||
|
You are a professional blog writer and SEO expert. You will be given the transcript of a live stream for which you are to generate
|
||||||
|
a blog post.
|
||||||
|
|
||||||
|
Instructions:
|
||||||
|
- The blog post title should be SEO optimized.
|
||||||
|
- The blog post should be properly and beautifully formatted using markdown.
|
||||||
|
- Each blog post should have around 5 sections with 3 sub-sections each.
|
||||||
|
- Each sub section should have about 3 paragraphs.
|
||||||
|
- Sub-section headings should be clearly marked.
|
||||||
|
- Ensure that the content flows logically from one section to another, maintaining coherence and readability.
|
||||||
|
- In the final section, provide a forward-looking perspective on the topic and a conclusion.
|
||||||
|
- Make the blog post sound as human and as engaging as possible, add real world examples and make it as informative as possible.
|
||||||
|
- Please ensure proper and standard markdown formatting always.
|
||||||
|
|
||||||
|
Transcription: {}
|
||||||
|
|
||||||
|
Blog Post:
|
||||||
|
"""
|
@ -1,2 +1,5 @@
|
|||||||
moviepy
|
moviepy
|
||||||
|
openai
|
||||||
pydub
|
pydub
|
||||||
|
python-dotenv
|
||||||
|
requests
|
||||||
|
Loading…
Reference in New Issue
Block a user