├── Aura Pipeline.ipynb ├── app.py ├── backend_functions.py ├── requirements.txt └── song_dataset.csv /Aura Pipeline.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "8fd6dc57", 6 | "metadata": {}, 7 | "source": [ 8 | "### Step 1: Import Required Libraries" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "2f54d702", 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stderr", 19 | "output_type": "stream", 20 | "text": [ 21 | "f:\\BROTOTYPE WEEK TASKS\\WEEK 31\\Project 22 - AURA\\auraenv\\lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", 22 | " from .autonotebook import tqdm as notebook_tqdm\n" 23 | ] 24 | } 25 | ], 26 | "source": [ 27 | "import re\n", 28 | "import torch # for PyTorch model operations\n", 29 | "import pandas as pd\n", 30 | "import random\n", 31 | "from transformers import AutoTokenizer, AutoModelForSequenceClassification\n", 32 | "from sentence_transformers import SentenceTransformer\n", 33 | "from sklearn.metrics.pairwise import cosine_similarity\n", 34 | "import torch.nn.functional as F" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "id": "36a15bb7", 40 | "metadata": {}, 41 | "source": [ 42 | "### Step 2: Load the Models" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "id": "9066d23a", 49 | "metadata": {}, 50 | "outputs": [ 51 | { 52 | "name": "stderr", 53 | "output_type": "stream", 54 | "text": [ 55 | "f:\\BROTOTYPE WEEK TASKS\\WEEK 31\\Project 22 - AURA\\auraenv\\lib\\site-packages\\huggingface_hub\\file_download.py:896: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.\n", 56 | " warnings.warn(\n" 57 | ] 58 | } 59 | ], 60 | "source": [ 61 | "# Load DistilBert model and tokenizer from local path\n", 62 | "model_id = \"joeddav/distilbert-base-uncased-go-emotions-student\"\n", 63 | "\n", 64 | "# Load tokenizer from local path\n", 65 | "tokenizer = AutoTokenizer.from_pretrained(model_id)\n", 66 | "\n", 67 | "# Load model from local path\n", 68 | "model = AutoModelForSequenceClassification.from_pretrained(model_id)\n", 69 | "\n", 70 | "# Load embedding (vectorizing) model from HuggingFace (online)\n", 71 | "embedder = SentenceTransformer('all-mpnet-base-v2')\n", 72 | "\n", 73 | "# Get id2label mapping from model config\n", 74 | "id2label = model.config.id2label\n" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "id": "e3575b3c", 80 | "metadata": {}, 81 | "source": [ 82 | "### Step 3: Text Preprocessing [User Input]" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 3, 88 | "id": "0aa61462", 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "def preprocess_text(text):\n", 93 | " # Lowercase\n", 94 | " text = text.lower()\n", 95 | " # Keep only lowercase alphabets and spaces\n", 96 | " text = re.sub(r\"[^a-z\\s]\",'',text)\n", 97 | " # Remove extra spaces\n", 98 | " text = re.sub(r'\\s+',' ',text).strip()\n", 99 | " return text" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "id": "fe94e205", 105 | "metadata": {}, 106 | "source": [ 107 | "### Step 4: Predict Emotions from User Input" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "id": "e0e2ac62", 113 | "metadata": {}, 114 | "source": [ 115 | "Multi-label classification: sigmoid + thresholding" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "id": "8163b029", 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "def predict_emotions(text, threshold=0.7):\n", 126 | " # tokenize input\n", 127 | " # return_tensors='pt' : return pytorch tensor instead of python lists or dictionaries\n", 128 | " # truncation to cut off length of input length if it exeeds maximum length\n", 129 | " inputs = tokenizer(text,return_tensors='pt',truncation=True)\n", 130 | " # pass through model\n", 131 | " with torch.no_grad():\n", 132 | " outputs = model(**inputs)\n", 133 | " probs = F.softmax(outputs.logits, dim=1).squeeze()\n", 134 | " # take probabilities using sigmoid\n", 135 | " # outputs is the predictions (numbers) from the models and ouputs.logits contains the raw predictions\n", 136 | "\n", 137 | " # select emotions above threshold\n", 138 | " raw_emotions = [ id2label[i] for i , p in enumerate(probs) if p >= threshold ]\n", 139 | " print('\\nRaw Emotions:',raw_emotions)\n", 140 | " \n", 141 | " # Mapping model emotions to song emotion tags\n", 142 | " emotion_map = {\n", 143 | " \"sadness\": \"Sadness 😢\",\n", 144 | " \"disappointment\": \"Sadness 😢\",\n", 145 | " \"grief\": \"Sadness 😢\",\n", 146 | " \"remorse\": \"Sadness 😢\",\n", 147 | "\n", 148 | " \"optimism\": \"Hope ✨\",\n", 149 | " \"gratitude\": \"Hope ✨\",\n", 150 | "\n", 151 | " \"desire\": \"Motivation 🔥\",\n", 152 | " \n", 153 | " \"annoyance\": \"Stress 😣\",\n", 154 | " \"confusion\": \"Stress 😣\",\n", 155 | " \"disapproval\": \"Stress 😣\",\n", 156 | " \"embarrassment\": \"Stress 😣\",\n", 157 | "\n", 158 | " \"fear\": \"Anxiety 😰\",\n", 159 | "\n", 160 | " \"nervousness\": \"Nervousness 😬\",\n", 161 | "\n", 162 | " \"joy\": \"Joy 😄\",\n", 163 | " \"amusement\": \"Joy 😄\",\n", 164 | "\n", 165 | " \"excitement\": \"Excitement 🤩\",\n", 166 | " \"surprise\": \"Excitement 🤩\",\n", 167 | "\n", 168 | " \"love\": \"Love ❤️\",\n", 169 | "\n", 170 | " \"pride\": \"Pride 🏅\",\n", 171 | " \"admiration\": \"Pride 🏅\",\n", 172 | "\n", 173 | " \"relief\": \"Calm 😌\",\n", 174 | " \"neutral\": \"Calm 😌\",\n", 175 | "\n", 176 | " \"curiosity\": \"Curiosity 🤔\",\n", 177 | "\n", 178 | " \"anger\": \"Anger 😡\",\n", 179 | " \"disgust\": \"Anger 😡\",\n", 180 | "\n", 181 | " \"approval\": \"Confidence 💪\",\n", 182 | " \"realization\": \"Confidence 💪\",\n", 183 | "\n", 184 | " \"caring\": \"Caring 🤗\"\n", 185 | " } \n", 186 | "\n", 187 | " mapped_emotions = [emotion_map[emotion] for emotion in raw_emotions if emotion in emotion_map]\n", 188 | " return mapped_emotions\n", 189 | " " 190 | ] 191 | }, 192 | { 193 | "cell_type": "markdown", 194 | "id": "a02f1ad0", 195 | "metadata": {}, 196 | "source": [ 197 | "### Step 5: Load and Prepare Song Dataset" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 5, 203 | "id": "49b4c76c", 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "def load_song_dataset(file_path='song_dataset.csv'):\n", 208 | " songs_df = pd.read_csv(file_path)\n", 209 | " songs_df['Emotion_Tags'] = songs_df['Emotion_Tags'].apply(eval) # safely evaluate string to list\n", 210 | " return songs_df" 211 | ] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "id": "ce13888c", 216 | "metadata": {}, 217 | "source": [ 218 | "### Step 6: Match Predicted Emotions to Best Song (Cosine Similarity)" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": 6, 224 | "id": "90120e9c", 225 | "metadata": {}, 226 | "outputs": [], 227 | "source": [ 228 | "def find_best_song(predicted_emotions, songs_df):\n", 229 | " print(\"Predicted Emotions:\", predicted_emotions)\n", 230 | "\n", 231 | " # Encode all predicted emotions individually\n", 232 | " input_embs = embedder.encode(predicted_emotions)\n", 233 | "\n", 234 | " song_scores = [] # list to hold (row, score) pairs\n", 235 | " \n", 236 | " for _, row in songs_df.iterrows():\n", 237 | " song_emotions = row['Emotion_Tags'].split(',') \n", 238 | " song_embs = embedder.encode(song_emotions) #encode song emotions into embedding\n", 239 | " \n", 240 | " # Compute average similarity between all pairs of (predicted_emotion, song_emotion)\n", 241 | " total_score = 0\n", 242 | " count = 0\n", 243 | "\n", 244 | " for input_emb in input_embs:\n", 245 | " for song_emb in song_embs:\n", 246 | " # calculate similarity using cosine similarity\n", 247 | " score = cosine_similarity([input_emb], [song_emb])[0][0]\n", 248 | " total_score += score\n", 249 | " count += 1\n", 250 | "\n", 251 | " avg_score = total_score / count if count > 0 else 0\n", 252 | "\n", 253 | " song_scores.append((row, avg_score))\n", 254 | " print(f\"{row['Emotion_Tags']} → Score: {avg_score:.3f}\")\n", 255 | "\n", 256 | " # Sort songs by average similarity score\n", 257 | " song_scores.sort(key=lambda x: x[1], reverse=True)\n", 258 | "\n", 259 | " # Take top 2 songs\n", 260 | " top_songs = song_scores[:2]\n", 261 | "\n", 262 | " # Randomly select one of the top 2\n", 263 | " if top_songs:\n", 264 | " selected_row, selected_score = random.choice(top_songs)\n", 265 | " return selected_row\n", 266 | " else:\n", 267 | " return None" 268 | ] 269 | } 270 | ], 271 | "metadata": { 272 | "kernelspec": { 273 | "display_name": "auraenv", 274 | "language": "python", 275 | "name": "python3" 276 | }, 277 | "language_info": { 278 | "codemirror_mode": { 279 | "name": "ipython", 280 | "version": 3 281 | }, 282 | "file_extension": ".py", 283 | "mimetype": "text/x-python", 284 | "name": "python", 285 | "nbconvert_exporter": "python", 286 | "pygments_lexer": "ipython3", 287 | "version": "3.10.0" 288 | } 289 | }, 290 | "nbformat": 4, 291 | "nbformat_minor": 5 292 | } 293 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from backend_functions import * # import all functions from file 3 | import time # for typing animation 4 | 5 | 6 | # configure basic page settings 7 | st.set_page_config(page_title="AURA", page_icon="🌌") 8 | 9 | # Add the description to the sidebar 10 | st.sidebar.header("Welcome to AURA 🌌🤖") 11 | aura_description = """ 12 | **Meet AURA – Your Emotion-Responsive Music Assistant** 13 | 14 | AURA is here to understand how you're feeling, just by reading your messages. Whether you're anxious, happy, or feeling down. 15 | 16 | Once AURA knows how you're feeling, it suggests the perfect song 🎶 to match your mood. Whether you need something calming, motivating 💪, or uplifting, AURA instantly provides a song that fits your emotions. 17 | 18 | So if you need to relax, simply vibe to the right tune, AURA has you covered with the perfect soundtrack for your emotions. 19 | """ 20 | 21 | # Display the description in the sidebar 22 | st.sidebar.write(aura_description) 23 | 24 | # Inject custom CSS to remove the toggle button and center the content 25 | st.markdown( 26 | """ 27 | 51 | """, unsafe_allow_html=True 52 | ) 53 | 54 | 55 | # Create the centered container using custom CSS class 56 | with st.container(): 57 | st.markdown('
', unsafe_allow_html=True) 58 | 59 | # Text input from user 60 | user_input = st.text_input("👤 Hello, Aura here, You can talk to me.", placeholder="Type your message here...") 61 | 62 | # Button to trigger song recommendation 63 | if st.button('Get Your Aura Song'): 64 | if user_input: 65 | with st.spinner("Analyzing your emotions..."): 66 | preprocessed_text = preprocess_text(user_input) # clean input text 67 | emotions = predict_emotions(preprocessed_text) 68 | 69 | # Find the best matching song based on detected emotions 70 | songs_df = load_song_dataset() 71 | song = find_best_song(emotions,songs_df) 72 | if song is not None: 73 | st.success(f"🎧 Here's a song that matches your mood: **{song['Title']}**") 74 | st.write(f"Matched Emotions: {set(emotions)}") # display matched emotions 75 | 76 | # Play recommended song 77 | audio_file = open(song['Path'],'rb') # open the song file 78 | audio_bytes = audio_file.read() 79 | st.audio(audio_bytes,format='audio/mp3') # stream song 80 | 81 | else: 82 | st.warning("Oops! We couldn't find a song that fits your emotions. Please try again!") 83 | 84 | else: 85 | st.warning('Please enter your text in the input box!') 86 | 87 | st.markdown('
', unsafe_allow_html=True) # Close the centered div -------------------------------------------------------------------------------- /backend_functions.py: -------------------------------------------------------------------------------- 1 | import re 2 | import torch 3 | import pandas as pd 4 | import random 5 | from transformers import AutoTokenizer, AutoModelForSequenceClassification 6 | from sentence_transformers import SentenceTransformer 7 | from sklearn.metrics.pairwise import cosine_similarity 8 | import torch.nn.functional as F 9 | 10 | # Load the GoEmotions model and tokenizer from Hugging Face directly 11 | model_id = "joeddav/distilbert-base-uncased-go-emotions-student" 12 | tokenizer = AutoTokenizer.from_pretrained(model_id) 13 | model = AutoModelForSequenceClassification.from_pretrained(model_id) 14 | model.eval() # Set to evaluation mode 15 | 16 | # SentenceTransformer for emotion-tag similarity 17 | embedder = SentenceTransformer('sentence-transformers/all-mpnet-base-v2') 18 | 19 | # Get id2label mapping from model config 20 | id2label = model.config.id2label 21 | 22 | def preprocess_text(text): 23 | text = text.lower() 24 | text = re.sub(r"[^a-z\s']", '', text) 25 | text = re.sub(r'\s+', ' ', text).strip() 26 | return text 27 | 28 | def predict_emotions(text, threshold=0.1): 29 | inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True) 30 | with torch.no_grad(): 31 | outputs = model(**inputs) 32 | probs = F.softmax(outputs.logits, dim=1).squeeze() 33 | print(id2label) 34 | raw_emotions = [id2label[i] for i, p in enumerate(probs) if p >= threshold] 35 | for i, p in enumerate(probs): 36 | print(f'Raw Emotions:{id2label[i]} :{p}') 37 | emotion_map = { 38 | "sadness": "Sadness 😢", "disappointment": "Sadness 😢", "grief": "Sadness 😢", "remorse": "Sadness 😢", 39 | "optimism": "Hope ✨", "gratitude": "Hope ✨", 40 | "desire": "Motivation 🔥", 41 | "annoyance": "Stress 😣", "confusion": "Stress 😣", "disapproval": "Stress 😣", "embarrassment": "Stress 😣", 42 | "fear": "Anxiety 😰", 43 | "nervousness": "Nervousness 😬", 44 | "joy": "Joy 😄", "amusement": "Joy 😄", 45 | "excitement": "Excitement 🤩", "surprise": "Excitement 🤩", 46 | "love": "Love ❤️", 47 | "pride": "Pride 🏅", "admiration": "Pride 🏅", 48 | "relief": "Calm 😌", "neutral": "Calm 😌", 49 | "curiosity": "Curiosity 🤔", 50 | "anger": "Anger 😡", "disgust": "Anger 😡", 51 | "approval": "Confidence 💪", "realization": "Confidence 💪", 52 | "caring": "Caring 🤗" 53 | } 54 | 55 | mapped_emotions = [emotion_map[emotion] for emotion in raw_emotions if emotion in emotion_map] 56 | return mapped_emotions 57 | 58 | def load_song_dataset(file_path='song_dataset.csv'): 59 | songs_df = pd.read_csv(file_path) 60 | songs_df['Emotion_Tags'] = songs_df['Emotion_Tags'].apply(eval) 61 | return songs_df 62 | 63 | def find_best_song(predicted_emotions, songs_df): 64 | print("Predicted Emotions:", predicted_emotions) 65 | input_embs = embedder.encode(predicted_emotions) 66 | song_scores = [] 67 | 68 | for _, row in songs_df.iterrows(): 69 | song_emotions = row['Emotion_Tags'] 70 | song_embs = embedder.encode(song_emotions) 71 | 72 | total_score = 0 73 | count = 0 74 | for input_emb in input_embs: 75 | for song_emb in song_embs: 76 | score = cosine_similarity([input_emb], [song_emb])[0][0] 77 | total_score += score 78 | count += 1 79 | 80 | avg_score = total_score / count if count > 0 else 0 81 | song_scores.append((row, avg_score)) 82 | print(f"{row['Emotion_Tags']} → Score: {avg_score:.3f}") 83 | 84 | song_scores.sort(key=lambda x: x[1], reverse=True) 85 | top_songs = song_scores[:2] 86 | return random.choice(top_songs)[0] if top_songs else None 87 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muhdshahan/Aura-Emotion-Based-Song-Recommender/52ad213d379886ce89daa03e4ceed496b20262d9/requirements.txt -------------------------------------------------------------------------------- /song_dataset.csv: -------------------------------------------------------------------------------- 1 | Song_ID,Title,Path,Emotion_Tags 2 | 1,Let It Be,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\letitbe.mp3,"['Sadness', 'Hope']" 3 | 2,Lose Yourself,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\loseyourself.mp3,"['Motivation', 'Stress']" 4 | 3,Someone Like You,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\someonelikeyou.mp3,"['Sadness', 'Anxiety']" 5 | 4,Weightless,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\weightless.mp3,"['Stress', 'Nervousness', 'Anxiety']" 6 | 5,Happy,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\happy.mp3,"['Joy', 'Excitement']" 7 | 6,Fix You,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\fixyou.mp3,"['Sadness', 'Hope', 'Stress']" 8 | 7,Breathe Me,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\breatheme.mp3,"['Anxiety', 'Sadness', 'Nervousness']" 9 | 8,Don't Worry Child,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\dontworry.mp3,"['Anxiety', 'Hope']" 10 | 9,Chasing Cars,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\chasingcars.mp3,"['Sadness', 'Calm']" 11 | 10,Titanium,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\titanium.mp3,"['Motivation', 'Confidence']" 12 | 11,Rise Up,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\riseup.mp3,"['Motivation', 'Hope']" 13 | 12,Demons,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\demons.mp3,"['Sadness', 'Anxiety']" 14 | 13,Lovely,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\lovely.mp3,"['Sadness', 'Love']" 15 | 14,Believer,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\believer.mp3,"['Motivation', 'Pride']" 16 | 15,Counting Stars,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\countingstars.mp3,"['Hope', 'Excitement']" 17 | 16,Let Her Go,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\lethergo.mp3,"['Sadness', 'Love']" 18 | 17,Memories,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\memories.mp3,"['Calm', 'Love']" 19 | 18,Whatever it Takes,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\whateverittakes.mp3,"['Motivation', 'Excitement']" 20 | 19,Shallow,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\shallow.mp3,"['Love', 'Sadness']" 21 | 20,Faded,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\faded.mp3,"['Sadness', 'Anxiety']" 22 | 21,Perfect,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\perfect.mp3,"['Love', 'Calm']" 23 | 22,Bad Guy,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\badguy.mp3,"['Curiosity', 'Anger']" 24 | 23,Stronger,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\stronger.mp3,"['Motivation', 'Pride']" 25 | 24,Ocean Eyes,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\oceaneyes.mp3,"['Calm', 'Love', 'Curiosity']" 26 | 25,Thunder,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\thunder.mp3,"['Motivation', 'Excitement']" 27 | 26,Happier,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\happier.mp3,"['Sadness', 'Hope']" 28 | 27,Unstoppable,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\unstoppable.mp3,"['Pride', 'Motivation']" 29 | 28,Lovely Day,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\lovelyday.mp3,['Calm'] 30 | 29,Say You Won't Let Go,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\sayyouwontletgo.mp3,"['Love', 'Caring']" 31 | 30,Fight Song,F:\BROTOTYPE WEEK TASKS\WEEK 31\Project 22 - AURA\songs\fightsong.mp3,"['Motivation', 'Pride', 'Hope']" 32 | --------------------------------------------------------------------------------