Updated with code
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.env
|
||||||
|
.DS_Store
|
||||||
25
README.md
Normal file
25
README.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# OBS Recording Transcriber
|
||||||
|
|
||||||
|
Process OBS recordings with AI-based transcription and summarization.
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- AI transcription using Whisper.
|
||||||
|
- Summarization using Hugging Face Transformers.
|
||||||
|
- File selection, resource validation, and error handling.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
1. Clone the repo.
|
||||||
|
git clone https://github.com/yourusername/OBS_Recording_Transcriber.git
|
||||||
|
cd OBS_Recording_Transcriber
|
||||||
|
2. Install dependencies:
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Ensure that the versions align with the features you use and your system compatibility.
|
||||||
|
torch version should match the capabilities of your hardware (e.g., CUDA support for GPUs).
|
||||||
|
whisper might need to be installed from source or a GitHub repository if it's not available on PyPI.
|
||||||
|
If you encounter any issues regarding compatibility, versions may need adjustments.
|
||||||
|
|
||||||
|
3. streamlit run app.py
|
||||||
58
app.py
Normal file
58
app.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import streamlit as st
|
||||||
|
from utils.audio_processing import extract_audio
|
||||||
|
from utils.transcription import transcribe_audio
|
||||||
|
from utils.summarization import summarize_text
|
||||||
|
from utils.validation import validate_environment
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def main():
|
||||||
|
st.title("🎥 OBS Recording Transcriber")
|
||||||
|
st.caption("Process your OBS recordings with AI transcription and summarization")
|
||||||
|
|
||||||
|
# Allow the user to select a base folder
|
||||||
|
st.sidebar.header("Folder Selection")
|
||||||
|
base_folder = st.sidebar.text_input(
|
||||||
|
"Enter the base folder path:",
|
||||||
|
value=str(Path.home())
|
||||||
|
)
|
||||||
|
|
||||||
|
base_path = Path(base_folder)
|
||||||
|
|
||||||
|
# Validate environment
|
||||||
|
env_errors = validate_environment(base_path)
|
||||||
|
if env_errors:
|
||||||
|
st.error("## Environment Issues")
|
||||||
|
for error in env_errors:
|
||||||
|
st.markdown(f"- {error}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# File selection
|
||||||
|
recordings = list(base_path.glob("*.mp4"))
|
||||||
|
if not recordings:
|
||||||
|
st.warning(f"📂 No recordings found in the folder: {base_folder}!")
|
||||||
|
return
|
||||||
|
|
||||||
|
selected_file = st.selectbox("Choose a recording", recordings)
|
||||||
|
|
||||||
|
if st.button("🚀 Start Processing"):
|
||||||
|
try:
|
||||||
|
transcript, summary = transcribe_audio(selected_file)
|
||||||
|
if transcript:
|
||||||
|
st.subheader("🖍 Summary")
|
||||||
|
st.write(summary)
|
||||||
|
st.subheader("📜 Full Transcript")
|
||||||
|
with st.expander("View transcript content"):
|
||||||
|
st.text(transcript)
|
||||||
|
st.download_button(
|
||||||
|
label="💾 Download Transcript",
|
||||||
|
data=transcript,
|
||||||
|
file_name=f"{Path(selected_file).stem}_transcript.txt",
|
||||||
|
mime="text/plain"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
st.error("❌ Failed to process recording")
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"An error occurred: {e}")
|
||||||
|
st.write(e) # This will show the traceback in the Streamlit app
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
streamlit==1.26.0
|
||||||
|
moviepy==1.0.3
|
||||||
|
whisper
|
||||||
|
transformers==4.21.1
|
||||||
|
torch>=1.7.0
|
||||||
12
utils/audio_processing.py
Normal file
12
utils/audio_processing.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from moviepy.editor import AudioFileClip
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def extract_audio(video_path: Path):
|
||||||
|
"""Extract audio from a video file."""
|
||||||
|
try:
|
||||||
|
audio = AudioFileClip(str(video_path))
|
||||||
|
audio_path = video_path.parent / f"{video_path.stem}_audio.wav"
|
||||||
|
audio.write_audiofile(str(audio_path), verbose=False, logger=None)
|
||||||
|
return audio_path
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Audio extraction failed: {e}")
|
||||||
8
utils/summarization.py
Normal file
8
utils/summarization.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from transformers import pipeline
|
||||||
|
|
||||||
|
SUMMARY_MODEL = "Falconsai/text_summarization"
|
||||||
|
|
||||||
|
def summarize_text(text):
|
||||||
|
"""Summarize text using a Hugging Face pipeline."""
|
||||||
|
summarizer = pipeline("summarization", model=SUMMARY_MODEL)
|
||||||
|
return summarizer(text, max_length=150, min_length=30, do_sample=False)[0]["summary_text"]
|
||||||
60
utils/transcription.py
Normal file
60
utils/transcription.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import whisper
|
||||||
|
from pathlib import Path
|
||||||
|
from transformers import pipeline, AutoTokenizer
|
||||||
|
|
||||||
|
WHISPER_MODEL = "base"
|
||||||
|
SUMMARIZATION_MODEL = "t5-base"
|
||||||
|
|
||||||
|
def transcribe_audio(audio_path: Path):
|
||||||
|
"""Transcribe audio using Whisper."""
|
||||||
|
model = whisper.load_model(WHISPER_MODEL)
|
||||||
|
result = model.transcribe(str(audio_path))
|
||||||
|
transcript = result["text"]
|
||||||
|
summary = summarize_text(transcript)
|
||||||
|
return transcript, summary
|
||||||
|
|
||||||
|
def summarize_text(text):
|
||||||
|
"""Summarize text using a pre-trained T5 transformer model with chunking."""
|
||||||
|
summarization_pipeline = pipeline("summarization", model=SUMMARIZATION_MODEL)
|
||||||
|
tokenizer = AutoTokenizer.from_pretrained(SUMMARIZATION_MODEL)
|
||||||
|
|
||||||
|
max_tokens = 512
|
||||||
|
|
||||||
|
tokens = tokenizer(text, return_tensors='pt')
|
||||||
|
num_tokens = len(tokens['input_ids'][0])
|
||||||
|
|
||||||
|
if num_tokens > max_tokens:
|
||||||
|
chunks = chunk_text(text, max_tokens)
|
||||||
|
summaries = []
|
||||||
|
for chunk in chunks:
|
||||||
|
summary_output = summarization_pipeline("summarize: " + chunk, max_length=150, min_length=30, do_sample=False)
|
||||||
|
summaries.append(summary_output[0]['summary_text'])
|
||||||
|
overall_summary = " ".join(summaries)
|
||||||
|
else:
|
||||||
|
overall_summary = summarization_pipeline("summarize: " + text, max_length=150, min_length=30, do_sample=False)[0]['summary_text']
|
||||||
|
|
||||||
|
return overall_summary
|
||||||
|
|
||||||
|
def chunk_text(text, max_tokens):
|
||||||
|
"""Splits the text into a list of chunks based on token limits."""
|
||||||
|
tokenizer = AutoTokenizer.from_pretrained(SUMMARIZATION_MODEL)
|
||||||
|
words = text.split()
|
||||||
|
|
||||||
|
chunks = []
|
||||||
|
current_chunk = []
|
||||||
|
current_length = 0
|
||||||
|
|
||||||
|
for word in words:
|
||||||
|
hypothetical_length = current_length + len(tokenizer(word, return_tensors='pt')['input_ids'][0]) - 2
|
||||||
|
if hypothetical_length <= max_tokens:
|
||||||
|
current_chunk.append(word)
|
||||||
|
current_length = hypothetical_length
|
||||||
|
else:
|
||||||
|
chunks.append(' '.join(current_chunk))
|
||||||
|
current_chunk = [word]
|
||||||
|
current_length = len(tokenizer(word, return_tensors='pt')['input_ids'][0]) - 2
|
||||||
|
|
||||||
|
if current_chunk:
|
||||||
|
chunks.append(' '.join(current_chunk))
|
||||||
|
|
||||||
|
return chunks
|
||||||
8
utils/validation.py
Normal file
8
utils/validation.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def validate_environment(obs_path: Path):
|
||||||
|
"""Validate environment and prerequisites."""
|
||||||
|
errors = []
|
||||||
|
if not obs_path.exists():
|
||||||
|
errors.append(f"OBS directory not found: {obs_path}")
|
||||||
|
return errors
|
||||||
Reference in New Issue
Block a user