File size: 7,324 Bytes
3cf77dc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import os
import streamlit as st
import tempfile
import whisper
from transformers import pipeline
import plotly.express as px
import torch
import logging
import warnings
import shutil

# Suppress warnings for a clean console
logging.getLogger("torch").setLevel(logging.CRITICAL)
logging.getLogger("transformers").setLevel(logging.CRITICAL)
warnings.filterwarnings("ignore")
os.environ["TOKENIZERS_PARALLELISM"] = "false"
torch.device("cpu")

# Set Streamlit app layout
st.set_page_config(layout="wide", page_title="Voice Based Sentiment Analysis")

# Interface design
st.title("πŸŽ™οΈ Voice Based Sentiment Analysis")
st.write("Detect emotions, sentiment, and sarcasm from your voice with high accuracy.")

# Sidebar for file upload
st.sidebar.title("Audio Input")
st.sidebar.write("Upload a WAV file for transcription and detailed analysis.")
audio_file = st.sidebar.file_uploader("Choose an audio file", type=["wav"], help="Supports WAV format only.")
upload_button = st.sidebar.button("Analyze", help="Click to process the uploaded audio.")

# Check if FFmpeg is available
def check_ffmpeg():
    return shutil.which("ffmpeg") is not None

# Emotion Detection Function
@st.cache_resource
def get_emotion_classifier():
    emotion_model = "bhadresh-savani/distilbert-base-uncased-emotion"
    return pipeline("text-classification", model=emotion_model, top_k=None, device=-1)

def perform_emotion_detection(text):
    try:
        emotion_classifier = get_emotion_classifier()
        emotion_results = emotion_classifier(text)[0]
        emotion_map = {"anger": "😑", "fear": "😨", "joy": "😊", "love": "❀️", "sadness": "😒", "surprise": "😲"}
        emotions_dict = {result['label']: result['score'] for result in emotion_results}
        top_emotion = max(emotions_dict, key=emotions_dict.get)
        sentiment_map = {"joy": "POSITIVE", "love": "POSITIVE", "anger": "NEGATIVE", "fear": "NEGATIVE", "sadness": "NEGATIVE", "surprise": "NEUTRAL"}
        sentiment = sentiment_map.get(top_emotion, "NEUTRAL")
        return emotions_dict, top_emotion, emotion_map, sentiment
    except Exception as e:
        st.error(f"Emotion detection failed: {str(e)}")
        return {}, "unknown", {}, "UNKNOWN"

# Sarcasm Detection Function
@st.cache_resource
def get_sarcasm_classifier():
    sarcasm_model = "cardiffnlp/twitter-roberta-base-irony"
    return pipeline("text-classification", model=sarcasm_model, device=-1)

def perform_sarcasm_detection(text):
    try:
        sarcasm_classifier = get_sarcasm_classifier()
        result = sarcasm_classifier(text)[0]
        is_sarcastic = result['label'] == "LABEL_1"
        sarcasm_score = result['score'] if is_sarcastic else 1 - result['score']
        return is_sarcastic, sarcasm_score
    except Exception as e:
        st.error(f"Sarcasm detection failed: {str(e)}")
        return False, 0.0

# Transcription Function with Whisper
@st.cache_resource
def get_whisper_model():
    return whisper.load_model("base")

def transcribe_audio(audio_file):
    if not check_ffmpeg():
        st.error("FFmpeg is not installed or not found in PATH. Please install FFmpeg and add it to your system PATH.")
        st.markdown("**Instructions to install FFmpeg on Windows:**\n"
                    "1. Download FFmpeg from [https://www.gyan.dev/ffmpeg/builds/](https://www.gyan.dev/ffmpeg/builds/) (e.g., `ffmpeg-release-essentials.zip`).\n"
                    "2. Extract the ZIP to a folder (e.g., `C:\\ffmpeg`).\n"
                    "3. Add `C:\\ffmpeg\\bin` to your system PATH:\n"
                    "   - Right-click 'This PC' > 'Properties' > 'Advanced system settings' > 'Environment Variables'.\n"
                    "   - Under 'System variables', edit 'Path' and add the new path.\n"
                    "4. Restart your terminal and rerun the app.")
        return ""

    try:
        model = get_whisper_model()
        # Save uploaded file to a temporary location
        temp_dir = tempfile.gettempdir()
        temp_file_path = os.path.join(temp_dir, "temp_audio.wav")
        with open(temp_file_path, "wb") as f:
            f.write(audio_file.getvalue())
        
        # Verify file exists
        if not os.path.exists(temp_file_path):
            st.error(f"Temporary file not created at {temp_file_path}. Check write permissions.")
            return ""

        # Transcribe using Whisper
        result = model.transcribe(temp_file_path)
        
        # Clean up temporary file
        if os.path.exists(temp_file_path):
            os.remove(temp_file_path)
        return result["text"]
    except Exception as e:
        st.error(f"Transcription failed: {str(e)}")
        return ""

# Main App Logic
def main():
    if audio_file and upload_button:
        st.audio(audio_file.getvalue(), format='audio/wav')
        st.caption("🎧 Uploaded Audio Playback")

        with st.spinner('Analyzing audio with advanced precision...'):
            transcribed_text = transcribe_audio(audio_file)
            if not transcribed_text:
                return

            emotions_dict, top_emotion, emotion_map, sentiment = perform_emotion_detection(transcribed_text)
            is_sarcastic, sarcasm_score = perform_sarcasm_detection(transcribed_text)

        st.header("Transcribed Text")
        st.text_area("Text", transcribed_text, height=150, disabled=True, help="The audio converted to text.")

        st.header("Analysis Results")
        col1, col2 = st.columns([1, 2])

        with col1:
            st.subheader("Sentiment")
            sentiment_icon = "πŸ‘" if sentiment == "POSITIVE" else "πŸ‘Ž" if sentiment == "NEGATIVE" else "😐"
            st.markdown(f"**{sentiment_icon} {sentiment.capitalize()}** (Based on {top_emotion})")
            st.info("Sentiment reflects the dominant emotion’s tone.")

            st.subheader("Sarcasm")
            sarcasm_icon = "😏" if is_sarcastic else "😐"
            sarcasm_text = "Detected" if is_sarcastic else "Not Detected"
            st.markdown(f"**{sarcasm_icon} {sarcasm_text}** (Score: {sarcasm_score:.3f})")
            st.info("Score indicates sarcasm confidence (0 to 1).")

        with col2:
            st.subheader("Emotions")
            if emotions_dict:
                st.markdown(f"**Dominant:** {emotion_map.get(top_emotion, '❓')} {top_emotion.capitalize()} (Score: {emotions_dict[top_emotion]:.3f})")
                sorted_emotions = sorted(emotions_dict.items(), key=lambda x: x[1], reverse=True)
                emotions = [e[0] for e in sorted_emotions]
                scores = [e[1] for e in sorted_emotions]
                fig = px.bar(x=emotions, y=scores, labels={'x': 'Emotion', 'y': 'Score'}, 
                             title="Emotion Distribution", color=emotions, 
                             color_discrete_sequence=px.colors.qualitative.Pastel1)
                fig.update_layout(yaxis_range=[0, 1], showlegend=False, title_font_size=14)
                st.plotly_chart(fig, use_container_width=True)
            else:
                st.write("No emotions detected.")

        st.info("Emotions drive sentiment here. Sarcasm is analyzed separately for accuracy.")

    elif upload_button and not audio_file:
        st.sidebar.error("Please upload an audio file first!")

if __name__ == "__main__":
    main()