|
|
|
from stable_whisper import modify_model,results_to_word_srt, results_to_sentence_srt |
|
import whisper |
|
import pysrt |
|
import re |
|
from pytube import YouTube |
|
import os |
|
from copy import deepcopy |
|
from typing import List |
|
import os |
|
from langchain import HuggingFaceHub, PromptTemplate, LLMChain |
|
from langchain_community.document_loaders import UnstructuredPDFLoader |
|
from nltk.corpus import stopwords |
|
from nltk.tokenize import word_tokenize |
|
from nltk import FreqDist |
|
from nltk.metrics import jaccard_distance |
|
from nltk.corpus import stopwords |
|
from nltk.tokenize import word_tokenize |
|
from nltk.stem import PorterStemmer |
|
from sklearn.feature_extraction.text import TfidfVectorizer |
|
from sklearn.metrics.pairwise import cosine_similarity |
|
import nltk |
|
nltk.download('stopwords') |
|
nltk.download('punkt') |
|
from moviepy.video.io.VideoFileClip import VideoFileClip |
|
from moviepy.video.VideoClip import ImageClip |
|
from datetime import datetime |
|
import moviepy.editor as mpy |
|
import gradio as gr |
|
|
|
huggingfacehub_api_token = os.getenv("HF_TOKEN") |
|
class VideoEditor(): |
|
|
|
def __init__(self): |
|
repo_id = "mistralai/Mistral-7B-Instruct-v0.2" |
|
self.llm = HuggingFaceHub( |
|
huggingfacehub_api_token=huggingfacehub_api_token, |
|
repo_id=repo_id, |
|
model_kwargs={"temperature": 0.2,"max_new_tokens":1000} |
|
) |
|
|
|
|
|
|
|
def load_model(self,model_selected): |
|
""" |
|
Load a pre-trained machine learning model specified by the `model_selected` parameter |
|
using the `whisper` library and modify it to output word timestamps. |
|
|
|
Parameters: |
|
----------- |
|
model_selected : str |
|
A string specifying the name of the pre-trained machine learning model to load. |
|
|
|
Returns: |
|
-------- |
|
model : object |
|
A modified version of the loaded pre-trained machine learning model that outputs |
|
timestamps for individual words. |
|
|
|
""" |
|
model = whisper.load_model(model_selected) |
|
modify_model(model) |
|
return model |
|
|
|
def whisper_result_to_srt(self,result): |
|
""" |
|
Convert the output of the Whisper speech recognition model into SubRip subtitle format. |
|
|
|
Parameters: |
|
----------- |
|
result : dict |
|
A dictionary containing the output of the Whisper speech recognition model, including word-level |
|
timestamps. |
|
|
|
Returns: |
|
-------- |
|
srt : str |
|
A string in SubRip subtitle format, containing the word-level transcriptions and timing information |
|
from the Whisper output. |
|
|
|
Notes: |
|
------ |
|
This function takes the output of the Whisper speech recognition model, which includes word-level timestamps |
|
for each segment of the input audio file, and converts it into SubRip subtitle format. The resulting subtitle |
|
file can be used to display captions or transcripts alongside a video recording of the original audio. |
|
""" |
|
text = [] |
|
for i,s in enumerate(result['segments']): |
|
text.append(str(i+1)) |
|
time_start = s['start'] |
|
hours, minutes, seconds = int(time_start/3600), (time_start/60) % 60, (time_start) % 60 |
|
timestamp_start = "%02d:%02d:%06.3f" % (hours, minutes, seconds) |
|
timestamp_start = timestamp_start.replace('.',',') |
|
time_end = s['end'] |
|
hours, minutes, seconds = int(time_end/3600), (time_end/60) % 60, (time_end) % 60 |
|
timestamp_end = "%02d:%02d:%06.3f" % (hours, minutes, seconds) |
|
timestamp_end = timestamp_end.replace('.',',') |
|
text.append(timestamp_start + " --> " + timestamp_end) |
|
text.append(s['text'].strip() + "\n") |
|
return "\n".join(text) |
|
|
|
|
|
def transcribe_video(self,vid, model_selected): |
|
""" |
|
Transcribe the audio in a video file using a pre-trained machine learning model and return the transcription |
|
and its corresponding timestamps in a subtitle format. |
|
|
|
Parameters: |
|
----------- |
|
vid : str |
|
A string specifying the path to the video file to be transcribed. |
|
model_selected : str |
|
A string specifying the name of the pre-trained machine learning model to use for transcription. |
|
|
|
Returns: |
|
-------- |
|
result : dict |
|
A dictionary containing the transcription and its corresponding timestamps in a subtitle format. |
|
|
|
""" |
|
model = self.load_model(model_selected) |
|
options = whisper.DecodingOptions(fp16=False) |
|
result = model.transcribe(vid, **options.__dict__) |
|
result['srt'] = self.whisper_result_to_srt(result) |
|
return result |
|
|
|
def to_srt(self,lines: List[dict], strip=False) -> str: |
|
""" |
|
lines: List[dict] |
|
[{start:<start-timestamp-of-text>, end:<end-timestamp-of-text>, text:<str-of-text>}, ...] |
|
""" |
|
|
|
def secs_to_hhmmss(secs): |
|
mm, ss = divmod(secs, 60) |
|
hh, mm = divmod(mm, 60) |
|
return f'{hh:0>2.0f}:{mm:0>2.0f}:{ss:0>6.3f}'.replace(".", ",") |
|
|
|
srt_str = '\n'.join( |
|
f'{i}\n' |
|
f'{secs_to_hhmmss(sub["start"])} --> {secs_to_hhmmss(sub["end"])}\n' |
|
f'{sub["text"].strip() if strip else sub["text"]}\n' |
|
for i, sub in enumerate(lines, 1)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
return srt_str |
|
|
|
def tighten_timestamps(self,res: dict, end_at_last_word=True, end_before_period=False, start_at_first_word=False) -> dict: |
|
res = deepcopy(res) |
|
for i in range(len(res['segments'])): |
|
if start_at_first_word: |
|
res['segments'][i]['start'] = res['segments'][i]['word_timestamps'][0]['timestamp'] |
|
if end_before_period and \ |
|
res['segments'][i]['word_timestamps'][-1] == '.' and \ |
|
len(res['segments'][i]['word_timestamps']) > 1: |
|
res['segments'][i]['end'] = res['segments'][i]['word_timestamps'][-2]['timestamp'] |
|
elif end_at_last_word: |
|
res['segments'][i]['end'] = res['segments'][i]['word_timestamps'][-1]['timestamp'] |
|
|
|
return res |
|
|
|
def results_to_sentence_srt(self,res: dict, |
|
end_at_last_word=False, |
|
end_before_period=False, |
|
start_at_first_word=False, |
|
strip=False): |
|
""" |
|
|
|
Parameters |
|
---------- |
|
res: dict |
|
results from modified model |
|
srt_path: str |
|
output path of srt |
|
end_at_last_word: bool |
|
set end-of-sentence to timestamp-of-last-token |
|
end_before_period: bool |
|
set end-of-sentence to timestamp-of-last-non-period-token |
|
start_at_first_word: bool |
|
set start-of-sentence to timestamp-of-first-token |
|
strip: bool |
|
perform strip() on each sentence |
|
|
|
""" |
|
strict = any((end_at_last_word, end_before_period, start_at_first_word)) |
|
segs = self.tighten_timestamps(res, |
|
end_at_last_word=end_at_last_word, |
|
end_before_period=end_before_period, |
|
start_at_first_word=start_at_first_word)['segments'] \ |
|
if strict else res['segments'] |
|
|
|
max_idx = len(segs) - 1 |
|
i = 1 |
|
while i <= max_idx: |
|
if not (segs[i]['end'] - segs[i]['start']): |
|
if segs[i - 1]['end'] == segs[i]['end']: |
|
segs[i - 1]['text'] += (' ' + segs[i]['text'].strip()) |
|
del segs[i] |
|
max_idx -= 1 |
|
continue |
|
else: |
|
segs[i]['start'] = segs[i - 1]['end'] |
|
i += 1 |
|
|
|
srt = self.to_srt(segs, strip=strip) |
|
return srt |
|
|
|
def extract_timestamps_and_text(self,input_text): |
|
timestamp_pattern = re.compile(r'(\d{2}:\d{2}:\d{2}.\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2}.\d{3})\n(.+)') |
|
|
|
matches = timestamp_pattern.findall(input_text) |
|
|
|
data = [] |
|
|
|
for match in matches: |
|
start_timestamp, end_timestamp, text = match |
|
data.append({ |
|
'start_timestamp': start_timestamp, |
|
'end_timestamp': end_timestamp, |
|
'text': text.strip() |
|
}) |
|
|
|
return data |
|
|
|
def sentence_timestamp(self,data): |
|
result_sentences = [] |
|
|
|
current_sentence = "" |
|
current_start_timestamp = "" |
|
|
|
for entry in data: |
|
text = entry['text'] |
|
start_timestamp = entry['start_timestamp'] |
|
end_timestamp = entry['end_timestamp'] |
|
|
|
|
|
if not current_sentence: |
|
current_start_timestamp = start_timestamp |
|
|
|
|
|
current_sentence += " " + text |
|
if text.endswith('.'): |
|
result_sentences.append({ |
|
'start_timestamp': current_start_timestamp, |
|
'end_timestamp': end_timestamp, |
|
'text': current_sentence.strip() |
|
}) |
|
current_sentence = "" |
|
return result_sentences |
|
|
|
def timestamp_text_to_list(self,result_sentences): |
|
text_list = [item['text'] for item in result_sentences] |
|
|
|
return text_list |
|
|
|
def list_to_json(self,text_list): |
|
jsonfile = { |
|
"sentences": text_list |
|
} |
|
json_text = str(jsonfile) |
|
return json_text |
|
|
|
|
|
def video_qa_generate_contract(self,text,question): |
|
|
|
|
|
template = """you are the german language and universal language expert .your task is analyze the given text and user ask any question about given text answer to the user question.your returning answer must in user's language.otherwise reply i don't know. |
|
extracted_text:{text} |
|
user_question:{question}""" |
|
|
|
prompt = PromptTemplate(template=template, input_variables=["text","question"]) |
|
llm_chain = LLMChain(prompt=prompt, verbose=True, llm=self.llm) |
|
|
|
result = llm_chain.run({"text":text,"question":question}) |
|
print() |
|
print() |
|
print("this is answer:",result) |
|
return result |
|
|
|
|
|
def topic_generate_contract(self,json_text,subrip): |
|
|
|
|
|
template = """your first task is extract all topics discussed in the given content. |
|
|
|
second task is analyze the given paragraph and extract answer for the first task's extracted topics. |
|
don't genarate answer yourself just extract related answer from the given paragraph. |
|
|
|
returing answer format: |
|
Topic:Topic |
|
Sentence:*Topic* Sentence |
|
|
|
```content:{content}``` |
|
```paragraph:{paragraph}``` |
|
""" |
|
|
|
prompt = PromptTemplate(template=template, input_variables=["content","paragraph"]) |
|
llm_chain = LLMChain(prompt=prompt, verbose=True, llm=self.llm) |
|
|
|
result = llm_chain.run({"content":json_text,"paragraph":subrip['text']}) |
|
return result |
|
|
|
|
|
def video_qa_preprocess_sentence(self,sentence): |
|
stop_words = set(stopwords.words('english')) |
|
words = word_tokenize(sentence.lower()) |
|
filtered_words = [word for word in words if word.isalnum() and word not in stop_words] |
|
return filtered_words |
|
|
|
def compute_similarity(self,sentence1, sentence2): |
|
words1 = self.video_qa_preprocess_sentence(sentence1) |
|
words2 = self.video_qa_preprocess_sentence(sentence2) |
|
|
|
freq_dist1 = FreqDist(words1) |
|
freq_dist2 = FreqDist(words2) |
|
|
|
jaccard = 1 - jaccard_distance(set(freq_dist1), set(freq_dist2)) |
|
|
|
return jaccard |
|
|
|
def video_qa_find_most_similar(self,sentence_list, target_sentence): |
|
similarities = [self.compute_similarity(target_sentence, sentence) for sentence in sentence_list] |
|
|
|
|
|
most_similar_index = similarities.index(max(similarities)) |
|
|
|
|
|
return sentence_list[most_similar_index] |
|
|
|
|
|
def video_qa_start_end_timestamp(self,result,answer): |
|
appended_text = [] |
|
|
|
for item in result: |
|
appended_text.append(item['text']) |
|
|
|
|
|
matched_sentence = self.video_qa_find_most_similar(appended_text, answer) |
|
start_time="" |
|
end_time="" |
|
for entry in result: |
|
if matched_sentence in entry['text']: |
|
start_time = entry['start_timestamp'] |
|
end_time = entry['end_timestamp'] |
|
print(start_time+"\n"+end_time) |
|
return start_time,end_time |
|
|
|
|
|
def preprocess_sentence(self,sentence): |
|
stop_words = set(stopwords.words('english')) |
|
ps = PorterStemmer() |
|
|
|
|
|
words = word_tokenize(sentence) |
|
words = [ps.stem(word.lower()) for word in words if word.isalnum() and word.lower() not in stop_words] |
|
|
|
return ' '.join(words) |
|
|
|
|
|
def topic_find_most_similar(self,sentence, list1): |
|
similarities = [] |
|
processed_sentence = self.preprocess_sentence(sentence) |
|
|
|
for candidate_sentence in list1: |
|
similarity = self.calculate_cosine_similarity(processed_sentence, candidate_sentence) |
|
similarities.append(similarity) |
|
|
|
|
|
max_similarity_index = similarities.index(max(similarities)) |
|
|
|
return list1[max_similarity_index] |
|
|
|
|
|
def calculate_cosine_similarity(self,sentence1, sentence2): |
|
|
|
vectorizer = TfidfVectorizer() |
|
tfidf_matrix = vectorizer.fit_transform([sentence1, sentence2]) |
|
|
|
|
|
cosine_sim = cosine_similarity(tfidf_matrix[0], tfidf_matrix[1])[0][0] |
|
|
|
return cosine_sim |
|
|
|
def timestamp_to_seconds(self,timestamp): |
|
time_format = "%H:%M:%S,%f" |
|
dt = datetime.strptime(timestamp, time_format) |
|
return dt.hour * 3600 + dt.minute * 60 + dt.second + dt.microsecond / 1e6 |
|
|
|
def cut_video(self,input_file, output_file, start_timestamp, end_timestamp): |
|
|
|
start_time = self.timestamp_to_seconds(start_timestamp) |
|
end_time = self.timestamp_to_seconds(end_timestamp) |
|
|
|
|
|
video_clip = VideoFileClip(input_file).subclip(start_time, end_time) |
|
video_clip.write_videofile(output_file, codec='libx264', audio_codec='aac', temp_audiofile='temp-audio.m4a', remove_temp=True) |
|
|
|
def start_end_timestamp(self,result,matched_sentence): |
|
start_time="" |
|
end_time="" |
|
for entry in result: |
|
if matched_sentence in entry['text']: |
|
start_time = entry['start_timestamp'] |
|
end_time = entry['end_timestamp'] |
|
|
|
return start_time,end_time |
|
|
|
def video_write_funcion(self,vid,answer,text_list,result_sentences): |
|
video = mpy.VideoFileClip(vid) |
|
topics = {} |
|
topics_list = answer.strip().split("\n\n") |
|
for topic in topics_list: |
|
lines = topic.split("\n") |
|
if len(lines) > 0: |
|
topic = lines[0].split(":")[1].strip() |
|
sentence = "".join([line.split(":")[1].strip() for line in lines[1:]]) |
|
|
|
sentence_list = sentence.split(".") |
|
unique_similar_sentences = [] |
|
list1 = text_list |
|
|
|
for sentence2 in sentence_list: |
|
most_similar_sentence = self.topic_find_most_similar(sentence2, list1) |
|
|
|
|
|
if most_similar_sentence not in unique_similar_sentences: |
|
unique_similar_sentences.append(most_similar_sentence) |
|
|
|
|
|
clips = [] |
|
for sentence in unique_similar_sentences: |
|
|
|
start_time,end_time = self.start_end_timestamp(result_sentences,sentence) |
|
clip = video.subclip(start_time, end_time) |
|
clips.append(clip) |
|
concatenated_clip = mpy.concatenate_videoclips(clips) |
|
topics[topic] = concatenated_clip |
|
|
|
for topic, clip in topics.items(): |
|
clip.write_videofile(f"{topic}.mp4") |
|
|
|
|
|
def video_qa_main(self,input_path,video,question): |
|
if input_path: |
|
input_path = self.Download(input_path) |
|
subtitle = self.transcribe_video(input_path,'medium') |
|
elif video: |
|
subtitle = self.transcribe_video(video,'medium') |
|
input_path = video |
|
print(subtitle['text']) |
|
text = subtitle['text'] |
|
answer = self.video_qa_generate_contract(text,question) |
|
print("video_qa_generate_contract") |
|
|
|
subrip_text = self.results_to_sentence_srt(subtitle) |
|
result = self.extract_timestamps_and_text(subrip_text) |
|
sent = self.sentence_timestamp(result) |
|
start_time,end_time = self.video_qa_start_end_timestamp(sent,answer) |
|
output_video_path = 'output_video.mp4' |
|
|
|
self.cut_video(input_path, output_video_path, start_time, end_time) |
|
return output_video_path |
|
|
|
def Download(self,link): |
|
youtubeObject = YouTube(link) |
|
youtubeObject = youtubeObject.streams.get_highest_resolution() |
|
try: |
|
file_name = youtubeObject.download() |
|
return file_name |
|
except: |
|
print("An error has occurred") |
|
print("Download is completed successfully") |
|
|
|
def topic_main(self,input_path,video): |
|
|
|
if input_path: |
|
input_path = self.Download(input_path) |
|
subrip = self.transcribe_video(input_path,'medium') |
|
elif video: |
|
subrip = self.transcribe_video(video,'medium') |
|
input_path = video |
|
print(subrip['text']) |
|
text = self.results_to_sentence_srt(subrip) |
|
print("results_to_sentence_srt") |
|
data = self.extract_timestamps_and_text(text) |
|
print("extract_timestamps_and_text") |
|
result_sentences = self.sentence_timestamp(data) |
|
text_list = self.timestamp_text_to_list(result_sentences) |
|
|
|
json_text = self.list_to_json(text_list) |
|
|
|
print("list_to_json") |
|
answer = self.topic_generate_contract(json_text,subrip) |
|
|
|
print("topic_generate_contract") |
|
self.video_write_funcion(input_path,answer,text_list,result_sentences) |
|
return "Topic Video Writted Successfully." |
|
|
|
|
|
with gr.Blocks(css="style.css",theme="freddyaboulton/test-blue") as demo: |
|
video_editor = VideoEditor() |
|
gr.HTML("""<center><h1>Video Question Answering & Topic Extracter</h1></center>""") |
|
with gr.Tab("Video QA"): |
|
with gr.Row(): |
|
youtube_link = gr.Textbox(label= "Youtube Link",placeholder="https://www.youtube.com/watch?v=") |
|
with gr.Row(): |
|
video = gr.Video(sources="upload",height=200,width=300) |
|
with gr.Row(): |
|
query = gr.Textbox(label="Query") |
|
|
|
with gr.Row(): |
|
output_video = gr.Video(height=200,width=300) |
|
|
|
|
|
|
|
|
|
query.submit(video_editor.video_qa_main,[youtube_link,video,query],output_video) |
|
with gr.Tab("Topic Extract"): |
|
with gr.Row(): |
|
yt_link = gr.Textbox(label= "Youtube Link",placeholder="https://www.youtube.com/watch?v=") |
|
with gr.Row(): |
|
video = gr.Video(sources="upload",height=200,width=300) |
|
with gr.Row(): |
|
submit_btn = gr.Button(value="Submit") |
|
with gr.Row(): |
|
textbox = gr.Textbox(label = "Status") |
|
|
|
submit_btn.click(video_editor.topic_main,[yt_link,video],textbox) |
|
demo.launch() |