import streamlit as st
import pandas as pd
import numpy as np
from annoy import AnnoyIndex
from functions.find_artist_data import *
from functions.reorder_artist_data import *
st.set_page_config(
page_title="Demo app",
page_icon="",
layout="wide",
initial_sidebar_state="expanded",
menu_items={
'About': "Contact: rain.bomberg@theorchard.com"
}
)
@st.cache # make it more efficient to load pickled files into memory
def preparing_environment():
# Dataframes for this app can be recreated using project related notebooks.
# Inside these notebooks you will find cells that will produce required parquet files.
# load objects for just genre model
main_artist_df = pd.read_parquet('algo_input_df.parquet')
main_artist_df['search_string'] = main_artist_df['C_ARTIST_NAME'].str.lower()
# load objects for showing related artists
related_artist_df = pd.read_parquet('related_artists_df.parquet')
return related_artist_df, main_artist_df
# https://docs.streamlit.io/knowledge-base/deploy/authentication-without-sso
def check_password():
"""Returns `True` if the user had the correct password."""
def password_entered():
"""Checks whether a password entered by the user is correct."""
if st.session_state["password"] == st.secrets["password"]:
st.session_state["password_correct"] = True
del st.session_state["password"] # don't store password
else:
st.session_state["password_correct"] = False
if "password_correct" not in st.session_state:
# First run, show input for password.
st.text_input(
"Password", type="password", on_change=password_entered, key="password"
)
return False
elif not st.session_state["password_correct"]:
# Password not correct, show input + error.
st.text_input(
"Password", type="password", on_change=password_entered, key="password"
)
st.error("😕 Password incorrect")
return False
else:
# Password correct.
return True
def make_clickable(link):
# target _blank to open new window
# extract clickable text to display for your link
return f'Open Spotify'
if check_password():
# START OF ACTUAL APP CONTENT AFTER VALID PASSWORD HAS BEEN PROVIDED
# Before starting script make sure that *.py file folder also contains required *.ann files.
# These files can be created using project related notebooks.
# Depending on what kind of similarities have been created, fill dictionaries accordingly.
approaches = {
'a': ['C_GENRES'],
'b': ['C_GENRES', 'C_FAN_COUNTRY_CODE'],
'c': ['C_GENRES', 'C_FAN_COUNTRY_CODE', 'C_BAND', 'C_PRONOUN', 'C_GENDER'],
}
index_sizes = {
'a': 300,
'b': 200,
'c': 200,
}
descriptors = {
'a': 'This approach is based just on genre',
'b': 'This approach is based on genre and top 3 streaming countries',
'c': 'This approaches is based on genre, top 3 streaming locations and band, pronoun and gender indicators'
}
filenames = {
'a': 'a_test.ann',
'b': 'b_test.ann',
'c': 'c_test.ann',
}
for apr in approaches:
globals()[f"approach_{apr}"] = AnnoyIndex(index_sizes.get(apr), 'angular')
# using prefault should load file into memory but seems for MAC this feature is not usable
globals()[f"approach_{apr}"].load(filenames.get(apr), prefault=True)
nr_of_artists = 0
related_artists_df_for_pickle, algo_input_df = preparing_environment()
st.markdown("
Similar artists demo
", unsafe_allow_html=True)
st.markdown("STEP 1: Using left side menu, select approach to use. Name of the approach shows what data "
"features are used for finding similarity between artists.", unsafe_allow_html=True)
st.markdown("STEP 2: Then search for artist of interest. You can also use partial name. "
"Click on the select button. After that main screen will be filled with similar artists.",
unsafe_allow_html=True)
st.markdown("STEP 3: Results. These are sorted by descending order. Meaning that most similar artists "
" are at the top.", unsafe_allow_html=True)
st.markdown("STEP 4: Filters. You can narrow down inital results by applying additional filters."
"These filters can be found at the bottom of left side menu.", unsafe_allow_html=True)
st.markdown("STEP 5: Listening. By clicking on the link under in the Spotify ID column you will be"
" redirected to the Spotify website where you can listen for that artist.
", unsafe_allow_html=True)
with st.sidebar: # define filters inside left side of app window
st.markdown("Specify inputs", unsafe_allow_html=True)
algo_list = ['genre', 'genre, streaming location', 'genre, streaming location, band, gender, pronoun']
algo_selection = st.selectbox('Select algorithm:', algo_list)
artist = st.text_input('Type part of artist name to search for:', '')
artist = artist.lower()
if artist:
st.write("Search string used: ", artist,
". Top 5 results sorted by popularity:", unsafe_allow_html=True)
df_result_search = algo_input_df[algo_input_df['search_string'].str.contains(artist, na=False)][
['SPOTIFY_ARTIST_ID', 'C_ARTIST_NAME', 'C_POPULARITY']].sort_values(by=['C_POPULARITY'],
ascending=False).head()
for header, column in zip(['Artist', 'Popularity', ''], st.columns((2, 1, 1))):
column.subheader(header)
for i, row in df_result_search.iterrows():
col1, col2, col3 = st.columns((2, 1, 1))
col1.write(row['C_ARTIST_NAME'])
col2.write(row['C_POPULARITY'])
if col3.button("Select", key=row['SPOTIFY_ARTIST_ID']):
st.session_state['spotify_id'] = row['SPOTIFY_ARTIST_ID']
spotify_id = st.session_state.get('spotify_id')
# spotify_id = st.text_input('Copy here the spotify id:', '')
# nr_of_artists = st.slider('How many similar artist to show?(max 50)', 5, 50, 10, 5)
nr_of_artists = 30
# apply additional filters to first similarity results
options_gender = algo_input_df['C_GENDER'].unique().tolist()
multi_options_gender = st.multiselect(
'Choose artist gender',
options_gender)
options_pronoun = algo_input_df['C_PRONOUN'].unique().tolist()
multi_options_pronoun = st.multiselect(
'Choose artist pronoun',
options_pronoun)
options_band = algo_input_df['C_BAND'].unique().tolist()
multi_options_band = st.multiselect(
'Is artist band',
options_band)
country_filter = st.text_input('Type 2 letter country code:', '')
country_filter = country_filter.upper()
if artist and spotify_id:
st.write("Current similarity is based on ", algo_selection, ".")
# select what data to use
if algo_selection == 'genre':
selected_algo = 'a'
elif algo_selection == 'genre, streaming location':
selected_algo = 'b'
else:
selected_algo = 'c'
try:
artist_desc, artist_index = get_artist_data(algo_input_df, spotify_id)
if selected_algo == 'a':
nearest_artists, distances = approach_a.get_nns_by_item(artist_index, nr_of_artists,
include_distances=True)
if selected_algo == 'b':
nearest_artists, distances = approach_b.get_nns_by_item(artist_index, nr_of_artists,
include_distances=True)
else:
nearest_artists, distances = approach_c.get_nns_by_item(artist_index, nr_of_artists,
include_distances=True)
response_tag = algo_input_df.loc[nearest_artists][
['C_ARTIST_NAME', 'SPOTIFY_ARTIST_ID', 'C_POPULARITY', 'C_FAN_COUNTRY_CODE',
'C_GENRES', 'C_PRONOUN', 'C_GENDER', 'C_BAND']]
response_tag.columns = ['ARTIST NAME', 'SPOTIFY ID', 'POPULARITY', 'TOP COUNTRIES',
'GENRES', 'PRONOUN', 'GENDER', 'BAND']
st.json(artist_desc)
st.write("Showing most similar artists.")
# create hyperlink using Spotify id
response_tag['SPOTIFY ID'] = response_tag['SPOTIFY ID'].apply(make_clickable)
artists_from_algo = response_tag['ARTIST NAME'].tolist()
# check for filters
if multi_options_gender:
response_tag = response_tag[response_tag["GENDER"].isin(multi_options_gender)]
if multi_options_pronoun:
response_tag = response_tag[response_tag["PRONOUN"].isin(multi_options_pronoun)]
if multi_options_band:
response_tag = response_tag[response_tag["BAND"].isin(multi_options_band)]
if country_filter:
response_tag = response_tag[response_tag["TOP COUNTRIES"].str.contains(country_filter, na=True)]
st.write(country_filter)
# Move 10 most popular artists at the top based on popularity from artist itself
response_tag = reorder_artists(response_tag, artist_desc.get('popularity', 50))
response_tag = response_tag.to_html(escape=False)
st.write(response_tag, unsafe_allow_html=True)
except KeyError:
st.markdown("Did not find matching Spotify artist.", unsafe_allow_html=True)
st.markdown("
", unsafe_allow_html=True)
st.write("Showing most similar artists according to Spotify:")
try:
rel_artists = related_artists_df_for_pickle[['ARTIST_NAME', 'FOLLOWERS_LATEST', 'RELATED_ARTIST_ID']][
related_artists_df_for_pickle['MAIN_ARTIST'] == spotify_id]
# create hyperlink using Spotify id
rel_artists['RELATED_ARTIST_ID'] = rel_artists['RELATED_ARTIST_ID'].apply(make_clickable)
artist_from_spotify = rel_artists['ARTIST_NAME'].tolist()
rel_artists = rel_artists.style.set_precision(0)
rel_artists = rel_artists.to_html(escape=False)
st.write(rel_artists, unsafe_allow_html=True)
# st.dataframe(rel_artists.style.set_precision(0))
common_artists = set(artists_from_algo).intersection(artist_from_spotify)
st.write(f"Number of artist matching from both sources: {common_artists}")
except NameError:
st.markdown("Did not find matching related artists from Spotify.",
unsafe_allow_html=True)