imdb / app.py
ssenaay's picture
Update app.py
2f1a64f verified
Raw
History Blame Contribute Delete
19.2 kB
import pandas as pd
import numpy as np
import gradio as gr
import os
from sentence_transformers import SentenceTransformer, util
import torch
import re
print("--- Film Öneri Sistemi Başlatılıyor ---")
csv_file_name = "imdb-top-rated-movies-user-rated.csv"
file_path = os.path.join(".", csv_file_name)
if not os.path.exists(file_path):
print(f"HATA: '{csv_file_name}' dosyası bulunamadı.")
exit(1)
try:
df = pd.read_csv(file_path)
print(f"'{csv_file_name}' başarıyla yüklendi. Toplam {len(df)} film bulundu.")
except Exception as e:
print(f"HATA: CSV dosyası yüklenirken hata oluştu: {e}")
exit(1)
df_filtered = df[['Title', 'IMDb Rating', 'Tags', 'Director', 'Stars', 'Votes', 'Description', 'Poster URL']].copy()
df_filtered['Stars'].fillna('', inplace=True)
df_filtered['Description'].fillna('', inplace=True)
df_filtered['Poster URL'].fillna('', inplace=True)
genre_mapping = {
'action': ['action', 'action epic', 'gun fu', 'one-person army action', 'car action', 'kung fu', 'martial arts', 'martial-arts'],
'adventure': ['adventure', 'adventure epic', 'desert adventure', 'animal adventure', 'space adventure', 'swashbuckler'],
'comedy': ['comedy', 'romantic comedy', 'buddy comedy', 'sitcom', 'black comedy', 'satire', 'spoof', 'parody', 'slapstick', 'screwball comedy', 'dark comedy', 'body swap comedy'],
'drama': ['drama', 'period drama', 'cop drama', 'legal drama', 'medical drama', 'teen drama', 'psychological drama', 'melodrama', 'historical drama', 'biography', 'romantic drama', 'showbiz drama', 'tragedy'],
'thriller': ['thriller', 'crime thriller', 'spy thriller', 'psychological thriller', 'mystery thriller', 'political thriller', 'conspiracy thriller', 'erotic thriller', 'cyber thriller', 'suspense'],
'sci-fi': ['sci-fi', 'space sci-fi', 'dystopian sci-fi', 'cyberpunk', 'alien invasion', 'mutant', 'robot', 'post-apocalyptic', 'time travel'],
'fantasy': ['fantasy', 'dark fantasy', 'sword & sorcery', 'fairy tale', 'epic fantasy'],
'horror': ['horror', 'slasher', 'supernatural horror', 'body horror', 'zombie', 'monster', 'vampire', 'werewolf', 'ghost'],
'mystery': ['mystery', 'suspense mystery', 'cozy mystery', 'whodunnit', 'detective', 'police procedural'],
'crime': ['crime', 'gangster', 'heist', 'mob', 'true crime'],
'romance': ['romance', 'romantic comedy', 'romantic drama'],
'animation': ['animation', 'adult animation', 'anime', 'computer animation', 'drawn animation', 'stop-motion animation'],
'family': ['family', 'kids'],
'western': ['western', 'classic western', 'neo-western'],
'war': ['war', 'war drama'],
'history': ['history', 'historical drama', 'biography'],
'music': ['music', 'musical', 'classic musical', 'concert'],
'documentary': ['documentary', 'docudrama', 'mockumentary']
}
reverse_genre_map = {}
for main_genre, sub_genres in genre_mapping.items():
for sub_genre in sub_genres:
reverse_genre_map[sub_genre] = main_genre
def map_to_main_genres(tag_list):
main_genres = set()
for tag in tag_list:
if tag in reverse_genre_map:
main_genres.add(reverse_genre_map[tag])
return list(main_genres)
def clean_and_split(text_series):
if pd.isna(text_series):
return []
item = str(text_series)
item = item.replace('"', '').replace("'", '').strip()
item = item.replace('sci, fi', 'sci-fi')
split_items = [s.strip().lower() for s in item.split(',') if s.strip()]
return split_items
df_filtered['Tags_cleaned_raw'] = df_filtered['Tags'].apply(clean_and_split)
df_filtered['Director_cleaned'] = df_filtered['Director'].apply(clean_and_split)
df_filtered['Stars_cleaned'] = df_filtered['Stars'].apply(clean_and_split)
df_filtered['Tags_cleaned'] = df_filtered['Tags_cleaned_raw'].apply(map_to_main_genres)
def convert_votes_to_numeric(votes_str):
if isinstance(votes_str, str):
votes_str = votes_str.replace(",", "")
if 'K' in votes_str:
return float(votes_str.replace('K', '')) * 1000
elif 'M' in votes_str:
return float(votes_str.replace('M', '')) * 1_000_000
try:
return float(votes_str)
except ValueError:
return np.nan
df_filtered['Votes_numeric'] = df_filtered['Votes'].apply(convert_votes_to_numeric)
df_filtered.drop('Votes', axis=1, inplace=True)
df_filtered.dropna(subset=['Votes_numeric'], inplace=True)
df_filtered['Combined_Text'] = df_filtered['Title'] + ". " + \
df_filtered['Description'] + ". " + \
df_filtered['Tags_cleaned'].apply(lambda x: ", ".join(x)) + ". " + \
df_filtered['Director_cleaned'].apply(lambda x: ", ".join(x)) + ". " + \
df_filtered['Stars_cleaned'].apply(lambda x: ", ".join(x))
model_name = 'sentence-transformers/all-MiniLM-L6-v2'
try:
sentence_model = SentenceTransformer(model_name)
print(f"'{model_name}' modeli başarıyla yüklendi.")
except Exception as e:
print(f"HATA: Sentence Transformer modeli yüklenirken hata oluştu: {e}")
exit(1)
embeddings_file_path = os.path.join(".", "film_embeddings.npy")
if not os.path.exists(embeddings_file_path):
print(f"HATA: '{embeddings_file_path}' dosyası bulunamadı.")
exit(1)
try:
film_embeddings = torch.from_numpy(np.load(embeddings_file_path))
print("Film embedding'leri başarıyla yüklendi.")
except Exception as e:
print(f"HATA: film_embeddings.npy yüklenirken hata oluştu: {e}")
exit(1)
director_popularity = {}
for index, row in df_filtered.iterrows():
for director in row['Director_cleaned']:
director_popularity[director] = director_popularity.get(director, 0) + row['Votes_numeric']
star_popularity = {}
for index, row in df_filtered.iterrows():
for star in row['Stars_cleaned']:
star_popularity[star] = star_popularity.get(star, 0) + row['Votes_numeric']
all_tags = sorted(list(set([tag for sublist in df_filtered['Tags_cleaned'] for tag in sublist if tag in genre_mapping])))
all_directors = sorted(list(director_popularity.keys()), key=lambda d: director_popularity[d], reverse=True)
all_stars = sorted(list(star_popularity.keys()), key=lambda s: star_popularity[s], reverse=True)
def get_movie_recommendations(selected_tags, selected_directors, selected_stars, min_imdb_rating_slider, num_recommendations_slider, search_text=""):
selected_tags_list = list(selected_tags) if selected_tags else []
selected_directors_list = list(selected_directors) if selected_directors else []
selected_stars_list = list(selected_stars) if selected_stars else []
recommendations_df = df_filtered.copy()
recommendations_df = recommendations_df[recommendations_df['IMDb Rating'] >= min_imdb_rating_slider]
if selected_tags_list:
recommendations_df = recommendations_df[
recommendations_df['Tags_cleaned'].apply(lambda x: any(tag in x for tag in selected_tags_list))
]
if selected_directors_list:
recommendations_df = recommendations_df[
recommendations_df['Director_cleaned'].apply(lambda x: any(director in x for director in selected_directors_list))
]
if selected_stars_list:
recommendations_df = recommendations_df[
recommendations_df['Stars_cleaned'].apply(lambda x: any(star in x for star in selected_stars_list))
]
if search_text and len(recommendations_df) > 0:
try:
query_embedding = sentence_model.encode(search_text, convert_to_tensor=True)
filtered_indices = recommendations_df.index.tolist()
current_film_embeddings = film_embeddings[filtered_indices]
cosine_scores = util.cos_sim(query_embedding, current_film_embeddings)[0]
recommendations_df['Similarity_Score'] = cosine_scores.cpu().numpy()
recommendations_df = recommendations_df.sort_values(
by=['Similarity_Score', 'IMDb Rating', 'Votes_numeric'],
ascending=[False, False, False]
).reset_index(drop=True)
except Exception as e:
return "Benzerlik hesaplanırken bir hata oluştu."
if not search_text:
recommendations_df = recommendations_df.sort_values(
by=['IMDb Rating', 'Votes_numeric'],
ascending=[False, False]
).reset_index(drop=True)
top_recommendations = recommendations_df.head(num_recommendations_slider)
if top_recommendations.empty:
return """
<div style="text-align: center; padding: 60px 20px; background: linear-gradient(135deg, #1a1a1a 0%, #2d1810 100%); border-radius: 16px; border: 2px solid #ff6b35;">
<div style="font-size: 64px; margin-bottom: 20px;">🎬</div>
<h2 style="color: #ff6b35; margin-bottom: 10px; font-size: 28px;">Sonuç Bulunamadı</h2>
<p style="color: #d4d4d4; font-size: 16px;">Seçtiğiniz kriterlere uygun film bulunamadı. Filtreleri değiştirerek tekrar deneyin.</p>
</div>
"""
else:
html_output = ""
for idx, row in top_recommendations.iterrows():
directors_str = ", ".join([d.title() for d in row['Director_cleaned']])
stars_str = ", ".join([s.title() for s in row['Stars_cleaned']])
tags_str = ", ".join([t.title() for t in row['Tags_cleaned']])
similarity_info = ""
if 'Similarity_Score' in row and search_text:
sim_percentage = int(row['Similarity_Score'] * 100)
similarity_info = f"""
<div style="display: inline-block; margin-left: 12px; padding: 6px 12px; background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%); border-radius: 8px;">
<span style="color: white; font-weight: 700; font-size: 13px;">🎯 Eşleşme: %{sim_percentage}</span>
</div>
"""
rating_color = "#4ade80" if row['IMDb Rating'] >= 8.0 else "#fbbf24" if row['IMDb Rating'] >= 7.5 else "#fb923c"
poster_html = f"""
<div style="width: 160px; height: 240px; background: linear-gradient(135deg, #2a2a2a 0%, #1a1a1a 100%); border-radius: 12px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; color: #888; font-size: 0.9em; line-height: 1.4; padding: 15px; box-shadow: 0 4px 12px rgba(0,0,0,0.4); border: 2px solid #3a3a3a;">
<div style="font-size: 48px; margin-bottom: 15px;">🎬</div>
<span style="font-weight: 600; color: #ddd; margin-bottom: 8px;">{row['Title'][:40]}...</span>
<span style="color: #999; font-size: 0.85em;">Poster Yok</span>
</div>
"""
html_output += f"""
<div style="display: flex; margin-bottom: 24px; border: 2px solid #3a3a3a; padding: 20px; border-radius: 16px; background: linear-gradient(135deg, #1a1a1a 0%, #252525 100%); box-shadow: 0 8px 24px rgba(0,0,0,0.3); transition: all 0.3s ease; position: relative; overflow: hidden;">
<div style="position: absolute; top: 0; left: 0; width: 6px; height: 100%; background: linear-gradient(180deg, #ff6b35 0%, #ff8c42 100%);"></div>
<div style="flex-shrink: 0; margin-right: 24px; margin-left: 6px;">
{poster_html}
</div>
<div style="flex-grow: 1;">
<div style="margin-bottom: 12px;">
<h3 style="margin: 0; color: #ff8c42; font-size: 26px; font-weight: 700; display: inline-block;">{row['Title']}</h3>
<div style="display: inline-block; margin-left: 12px; background: {rating_color}; padding: 6px 14px; border-radius: 8px;">
<span style="font-size: 16px;">⭐</span>
<span style="color: #1a1a1a; font-weight: 700; font-size: 16px;">{row['IMDb Rating']:.1f}</span>
</div>
{similarity_info}
</div>
<div style="margin-bottom: 14px;">
<div style="display: inline-block; background: rgba(255, 107, 53, 0.15); padding: 8px 14px; border-radius: 8px; border: 1px solid rgba(255, 107, 53, 0.3);">
<span style="color: #ff8c42; font-weight: 600;">🗳️ {int(row['Votes_numeric']):,} Oy</span>
</div>
</div>
<div style="margin-bottom: 12px;">
<span style="color: #ff8c42; font-weight: 600; font-size: 15px;">🎬 Yönetmen:</span>
<span style="color: #d4d4d4; font-size: 15px; margin-left: 8px;">{directors_str if directors_str else 'Bilinmiyor'}</span>
</div>
<div style="margin-bottom: 14px;">
<span style="color: #ff8c42; font-weight: 600; font-size: 15px;">⭐ Oyuncular:</span>
<span style="color: #d4d4d4; font-size: 15px; margin-left: 8px;">{stars_str if stars_str else 'Bilinmiyor'}</span>
</div>
<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px;">
{''.join([f'<span style="background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%); color: white; padding: 6px 14px; border-radius: 20px; font-size: 13px; font-weight: 600; box-shadow: 0 2px 8px rgba(255, 107, 53, 0.3);">{tag.title()}</span>' for tag in row['Tags_cleaned']])}
</div>
</div>
</div>
"""
return html_output
with gr.Blocks(theme=gr.themes.Soft(), css="""
.gradio-container { max-width: 1200px !important; font-family: 'Segoe UI', sans-serif; }
h1 { color: #f39c12; text-align: center; }
h3 { color: #eee; }
.gr-button.gr-button-primary { background-color: #f39c12 !important; border-color: #f39c12 !important; }
.gr-button.gr-button-primary:hover { background-color: #e67e22 !important; border-color: #e67e22 !important; }
.gr-checkbox-group label { color: #ccc; }
.gr-dropdown, .gr-slider, .gr-textbox { background-color: #2c2c2c; color: #eee; border-color: #555; }
.gr-dropdown-item { color: #eee; }
.gr-checkbox-group input[type='checkbox']:checked + label {
background-color: #f39c12 !important;
border-color: #f39c12 !important;
color: #1a1a1a !important;
}
.gr-checkbox-group input[type='checkbox'] + label {
background-color: #333;
color: #eee;
border: 1px solid #555;
}
.gr-checkbox-group input[type='checkbox'] + label:hover {
background-color: #444;
}
.gr-dropdown-item.selected {
background-color: #f39c12 !important;
color: #1a1a1a !important;
}
.gr-dropdown-item:hover {
background-color: #e67e22 !important;
color: #1a1a1a !important;
}
input[type="range"]::-webkit-slider-thumb {
background-color: #f39c12 !important;
}
input[type="range"]::-moz-range-thumb {
background-color: #f39c12 !important;
}
""") as demo:
gr.Markdown(
"""
# 🎬 Film Öneri Sistemi
Favori film özelliklerinizi seçin, yüksek IMDb puanına sahip filmleri keşfedin!
İstediğiniz bir film veya konu hakkında yazın, benzerlerini de bulalım.
"""
)
with gr.Row():
with gr.Column(scale=2):
gr.Markdown("### Önerilen Filmler:")
output_html = gr.HTML(label="Önerileriniz burada listelenecektir.", value="<p style='text-align: center; color: #bbb;'>Henüz bir öneri yapılmadı. Özellikleri seçip butona tıklayın!</p>")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Film Özelliklerini Seçin:")
tags_input = gr.CheckboxGroup(
label="Film Türleri",
choices=all_tags,
value=['action', 'drama'],
interactive=True
)
directors_input = gr.Dropdown(
label="Yönetmenler",
choices=all_directors,
multiselect=True,
allow_custom_value=False,
interactive=True
)
stars_input = gr.Dropdown(
label="Oyuncular",
choices=all_stars,
multiselect=True,
allow_custom_value=False,
interactive=True
)
min_imdb_rating_slider = gr.Slider(
minimum=df_filtered['IMDb Rating'].min(),
maximum=df_filtered['IMDb Rating'].max(),
step=0.1,
value=7.6,
label="Minimum IMDb Puanı"
)
num_recommendations_slider = gr.Slider(
minimum=1,
maximum=20,
step=1,
value=10,
label="Öneri Sayısı"
)
search_text_input = gr.Textbox(
label="Film Adı veya Konu Hakkında Ara (NLP Tabanlı Benzerlik)",
placeholder="Örneğin: Batman, uzay filmi, zamanda yolculuk..."
)
recommend_btn = gr.Button("🚀 Film Önerilerini Getir", variant="primary", size="lg")
recommend_btn.click(
fn=get_movie_recommendations,
inputs=[tags_input, directors_input, stars_input, min_imdb_rating_slider, num_recommendations_slider, search_text_input],
outputs=output_html
)
gr.Examples(
examples=[
[['action'], [], [], 7.6, 5, ""],
[['comedy', 'drama'], [], [], 7.8, 3, ""],
[[], ['christopher nolan'], [], 8.0, 5, ""],
[[], [], ['leonardo dicaprio'], 7.8, 3, ""],
[[], [], [], 8.0, 5, "kahramanlık ve bilim kurgu"],
[['action', 'sci-fi'], [], [], 7.8, 5, "uzaylı istilası ve kaçış"],
],
inputs=[tags_input, directors_input, stars_input, min_imdb_rating_slider, num_recommendations_slider, search_text_input],
outputs=output_html,
fn=get_movie_recommendations,
label="Örnek Önerileri Deneyin"
)
gr.Markdown(
"""
---
### ℹ️ Nasıl Kullanılır?
1. **Film Türleri, Yönetmenler ve Oyuncular** bölümlerinden istediğiniz filtreleri seçin (birden fazla seçim yapabilirsiniz).
2. **Minimum IMDb Puanı** ve **Öneri Sayısı** çubuklarını ayarlayın.
3. İsterseniz **"Film Adı veya Konu Hakkında Ara"** kutucuğuna bir film adı, konu veya anahtar kelime yazın.
4. **"🚀 Film Önerilerini Getir"** butonuna tıklayın.
5. Öneriler üst panelde görünecektir!
"""
)
demo.launch(share=True)
print("Gradio Web Arayüzü Başlatıldı.")