Update app.py
Browse files
app.py
CHANGED
|
@@ -2,8 +2,8 @@ import pandas as pd
|
|
| 2 |
import numpy as np
|
| 3 |
import gradio as gr
|
| 4 |
import os
|
| 5 |
-
from sentence_transformers import SentenceTransformer, util
|
| 6 |
-
import torch
|
| 7 |
import re
|
| 8 |
|
| 9 |
print("--- Film Öneri Sistemi Başlatılıyor (Popülerlik Sıralaması ve Yeniden Tasarlanmış Arayüz ile) ---")
|
|
@@ -11,56 +11,30 @@ print("--- Film Öneri Sistemi Başlatılıyor (Popülerlik Sıralaması ve Yeni
|
|
| 11 |
# --- ADIM 1: Veri Seti Yükleniyor ve Keşfediliyor ---
|
| 12 |
print("ADIM 1: Veri Seti Yükleniyor ve Keşfediliyor...")
|
| 13 |
|
| 14 |
-
csv_file_name = "imdb-top-rated-movies-user-rated.csv"
|
| 15 |
-
file_path = os.path.join(".", csv_file_name)
|
| 16 |
|
| 17 |
if not os.path.exists(file_path):
|
| 18 |
print(f"HATA: '{csv_file_name}' dosyası '{file_path}' yolunda bulunamadı. Lütfen CSV dosyasını Space'e yüklediğinizden emin olun.")
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
'Votes': ["1,800,000", "1,500,000", "2,100,000", "2,300,000", "2,800,000", "2,200,000", "2,000,000", "1,700,000", "1,100,000", "900,000"],
|
| 28 |
-
'Description': [
|
| 29 |
-
"With the help of a German bounty hunter, a freed slave sets out to rescue his wife from a brutal Mississippi plantation owner.",
|
| 30 |
-
"An undercover state cop and a mole in the police force try to identify each other.",
|
| 31 |
-
"The lives of two mob hitmen, a boxer, a gangster's wife, and a pair of diner bandits intertwine in four tales of violence and redemption.",
|
| 32 |
-
"A thief who steals corporate secrets through the use of dream-sharing technology is given the inverse task of planting an idea into the mind of a C.E.O.",
|
| 33 |
-
"When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, Batman must accept one of the greatest psychological and physical tests of his ability to fight injustice.",
|
| 34 |
-
"The presidencies of Kennedy and Johnson, the Vietnam War, the Watergate scandal and other historical events unfold from the perspective of an Alabama man with an IQ of 75.",
|
| 35 |
-
"A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers.",
|
| 36 |
-
"A team of explorers travel through a wormhole in space in an attempt to ensure humanity's survival.",
|
| 37 |
-
"During her family's move to the suburbs, a sullen 10-year-old girl wanders into a world ruled by gods, witches, and spirits, and where humans are changed into beasts.",
|
| 38 |
-
"A promising young drummer enrolls at a cut-throat music conservatory where his unorthodox instructor pushes him to the breaking point."
|
| 39 |
-
],
|
| 40 |
-
'Poster URL': ["", "", "", "", "", "", "", "", "", ""] # Demo için boş bırakıldı, gerçek poster URL'leri buraya gelebilir
|
| 41 |
-
}
|
| 42 |
-
df = pd.DataFrame(dummy_data)
|
| 43 |
-
# Sahte CSV'yi kaydedelim ki sonraki çalıştırmalarda tekrar oluşturmasın (isteğe bağlı)
|
| 44 |
-
df.to_csv(file_path, index=False)
|
| 45 |
-
print(f"Sahte CSV '{csv_file_name}' oluşturuldu ve yüklendi. Toplam {len(df)} film bulundu.")
|
| 46 |
-
else:
|
| 47 |
-
try:
|
| 48 |
-
df = pd.read_csv(file_path)
|
| 49 |
-
print(f"'{csv_file_name}' başarıyla yüklendi. Toplam {len(df)} film bulundu.")
|
| 50 |
-
except Exception as e:
|
| 51 |
-
print(f"HATA: CSV dosyası yüklenirken hata oluştu: {e}")
|
| 52 |
-
exit(1)
|
| 53 |
print("ADIM 1: Veri Seti Keşfi Tamamlandı.")
|
| 54 |
|
| 55 |
|
| 56 |
# --- ADIM 2: Veri Temizliği ve Ön İşleme ---
|
| 57 |
print("\nADIM 2: Veri Temizliği ve Ön İşleme Başlıyor...")
|
| 58 |
|
| 59 |
-
df_filtered = df[['Title', 'IMDb Rating', 'Tags', 'Director', 'Stars', 'Votes', 'Description', 'Poster URL']].copy()
|
| 60 |
|
| 61 |
-
df_filtered['Stars'].fillna('', inplace=True)
|
| 62 |
-
df_filtered['Description'].fillna('', inplace=True)
|
| 63 |
-
df_filtered['Poster URL'].fillna('', inplace=True)
|
| 64 |
|
| 65 |
genre_mapping = {
|
| 66 |
'action': ['action', 'action epic', 'gun fu', 'one-person army action', 'car action', 'kung fu', 'martial arts', 'martial-arts'],
|
|
@@ -92,15 +66,17 @@ def map_to_main_genres(tag_list):
|
|
| 92 |
main_genres = set()
|
| 93 |
for tag in tag_list:
|
| 94 |
if tag in reverse_genre_map:
|
| 95 |
-
main_genres.add(reverse_genre_map[tag])
|
| 96 |
return list(main_genres)
|
| 97 |
|
| 98 |
def clean_and_split(text_series):
|
| 99 |
if pd.isna(text_series):
|
| 100 |
return []
|
|
|
|
| 101 |
item = str(text_series)
|
| 102 |
item = item.replace('"', '').replace("'", '').strip()
|
| 103 |
-
item = item.replace('sci, fi', 'sci-fi')
|
|
|
|
| 104 |
split_items = [s.strip().lower() for s in item.split(',') if s.strip()]
|
| 105 |
return split_items
|
| 106 |
|
|
@@ -114,19 +90,19 @@ df_filtered['Tags_cleaned'] = df_filtered['Tags_cleaned_raw'].apply(map_to_main_
|
|
| 114 |
|
| 115 |
def convert_votes_to_numeric(votes_str):
|
| 116 |
if isinstance(votes_str, str):
|
| 117 |
-
votes_str = votes_str.replace(",", "")
|
| 118 |
if 'K' in votes_str:
|
| 119 |
return float(votes_str.replace('K', '')) * 1000
|
| 120 |
elif 'M' in votes_str:
|
| 121 |
return float(votes_str.replace('M', '')) * 1_000_000
|
| 122 |
-
try:
|
| 123 |
return float(votes_str)
|
| 124 |
except ValueError:
|
| 125 |
-
return np.nan
|
| 126 |
|
| 127 |
df_filtered['Votes_numeric'] = df_filtered['Votes'].apply(convert_votes_to_numeric)
|
| 128 |
df_filtered.drop('Votes', axis=1, inplace=True)
|
| 129 |
-
df_filtered.dropna(subset=['Votes_numeric'], inplace=True)
|
| 130 |
|
| 131 |
df_filtered['Combined_Text'] = df_filtered['Title'] + ". " + \
|
| 132 |
df_filtered['Description'] + ". " + \
|
|
@@ -141,30 +117,24 @@ print("\nADIM 2: Veri Temizliği ve Ön İşleme Tamamlandı.")
|
|
| 141 |
print("\nADIM 3: NLP Modelini Yükleniyor ve Önceden Oluşturulmuş Embedding'ler Yükleniyor...")
|
| 142 |
|
| 143 |
model_name = 'sentence-transformers/all-MiniLM-L6-v2'
|
| 144 |
-
sentence_model = None # Modeli başlangıçta None olarak ayarla
|
| 145 |
try:
|
| 146 |
sentence_model = SentenceTransformer(model_name)
|
| 147 |
print(f"'{model_name}' modeli başarıyla yüklendi.")
|
| 148 |
except Exception as e:
|
| 149 |
print(f"HATA: Sentence Transformer modeli yüklenirken hata oluştu: {e}")
|
| 150 |
-
|
| 151 |
|
| 152 |
embeddings_file_path = os.path.join(".", "film_embeddings.npy")
|
| 153 |
if not os.path.exists(embeddings_file_path):
|
| 154 |
print(f"HATA: '{embeddings_file_path}' dosyası bulunamadı. Lütfen Space'e yüklediğinizden emin olun.")
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
film_embeddings = torch.from_numpy(
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
film_embeddings = torch.from_numpy(np.load(embeddings_file_path)).float() # Float tipini sağla
|
| 164 |
-
print("Film embedding'leri başarıyla 'film_embeddings.npy' dosyasından yüklendi.")
|
| 165 |
-
except Exception as e:
|
| 166 |
-
print(f"HATA: film_embeddings.npy yüklenirken hata oluştu: {e}")
|
| 167 |
-
exit(1)
|
| 168 |
|
| 169 |
print("ADIM 3: NLP Modelini Yükleme ve Film Embedding'lerini Oluşturma Tamamlandı.")
|
| 170 |
|
|
@@ -187,46 +157,14 @@ for index, row in df_filtered.iterrows():
|
|
| 187 |
all_tags = sorted(list(set([tag for sublist in df_filtered['Tags_cleaned'] for tag in sublist if tag in genre_mapping])))
|
| 188 |
|
| 189 |
# Popülerliğe göre sıralanmış yönetmen ve oyuncu listeleri
|
|
|
|
| 190 |
all_directors = sorted(list(director_popularity.keys()), key=lambda d: director_popularity[d], reverse=True)
|
| 191 |
all_stars = sorted(list(star_popularity.keys()), key=lambda s: star_popularity[s], reverse=True)
|
| 192 |
# --- YENİ EKLENTİ SONU ---
|
| 193 |
|
| 194 |
-
# --- YENİ EKLENTİ: Türlere göre renk ve ikon eşleme ---
|
| 195 |
-
genre_visual_map = {
|
| 196 |
-
'action': {'color': '#E74C3C', 'icon': '💥'}, # Kırmızı
|
| 197 |
-
'adventure': {'color': '#F39C12', 'icon': '🗺️'}, # Turuncu
|
| 198 |
-
'comedy': {'color': '#F1C40F', 'icon': '😂'}, # Sarı
|
| 199 |
-
'drama': {'color': '#C0392B', 'icon': '🎭'}, # Koyu Kırmızı
|
| 200 |
-
'thriller': {'color': '#34495E', 'icon': '😨'}, # Koyu Mavi
|
| 201 |
-
'sci-fi': {'color': '#2980B9', 'icon': '🚀'}, # Mavi
|
| 202 |
-
'fantasy': {'color': '#9B59B6', 'icon': '✨'}, # Açık Mor
|
| 203 |
-
'horror': {'color': '#2C3E50', 'icon': '👻'}, # Çok Koyu Mavi
|
| 204 |
-
'mystery': {'color': '#7F8C8D', 'icon': '❓'}, # Gri
|
| 205 |
-
'crime': {'color': '#8E44AD', 'icon': '🔪'}, # Mor
|
| 206 |
-
'romance': {'color': '#E84393', 'icon': '❤️'}, # Pembe
|
| 207 |
-
'animation': {'color': '#2ECC71', 'icon': '🎬'}, # Yeşil
|
| 208 |
-
'family': {'color': '#1ABC9C', 'icon': '👨👩👧👦'}, # Turkuaz
|
| 209 |
-
'western': {'color': '#D35400', 'icon': '🤠'}, # Turuncu-kahverengi
|
| 210 |
-
'war': {'color': '#BDC3C7', 'icon': '⚔️'}, # Açık Gri
|
| 211 |
-
'history': {'color': '#95A5A6', 'icon': '📜'}, # Orta Gri
|
| 212 |
-
'music': {'color': '#E67E22', 'icon': '🎵'}, # Koyu Turuncu
|
| 213 |
-
'documentary': {'color': '#3498DB', 'icon': '🎥'} # Açık Mavi
|
| 214 |
-
}
|
| 215 |
-
|
| 216 |
-
def get_genre_color(tags):
|
| 217 |
-
if tags:
|
| 218 |
-
first_genre = tags[0] # İlk ana türü al
|
| 219 |
-
return genre_visual_map.get(first_genre, {}).get('color', '#444444') # Varsayılan koyu gri
|
| 220 |
-
return '#444444' # Varsayılan koyu gri
|
| 221 |
-
|
| 222 |
-
def get_genre_icon(tags):
|
| 223 |
-
if tags:
|
| 224 |
-
first_genre = tags[0] # İlk ana türü al
|
| 225 |
-
return genre_visual_map.get(first_genre, {}).get('icon', '🎞️') # Varsayılan film şeridi
|
| 226 |
-
return '🎞️' # Varsayılan film şeridi
|
| 227 |
|
| 228 |
def get_movie_recommendations(selected_tags, selected_directors, selected_stars, min_imdb_rating_slider, num_recommendations_slider, search_text=""):
|
| 229 |
-
|
| 230 |
print(f"\n--- Öneri İsteği ---")
|
| 231 |
print(f"Seçilen Türler: {selected_tags}")
|
| 232 |
print(f"Seçilen Yönetmenler: {selected_directors}")
|
|
@@ -241,124 +179,100 @@ def get_movie_recommendations(selected_tags, selected_directors, selected_stars,
|
|
| 241 |
selected_stars_list = list(selected_stars) if selected_stars else []
|
| 242 |
|
| 243 |
recommendations_df = df_filtered.copy()
|
| 244 |
-
|
| 245 |
recommendations_df = recommendations_df[recommendations_df['IMDb Rating'] >= min_imdb_rating_slider]
|
| 246 |
print(f"IMDb Puanı filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 247 |
-
|
| 248 |
if selected_tags_list:
|
| 249 |
recommendations_df = recommendations_df[
|
| 250 |
recommendations_df['Tags_cleaned'].apply(lambda x: any(tag in x for tag in selected_tags_list))
|
| 251 |
]
|
| 252 |
print(f"Tür filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 253 |
-
|
| 254 |
if selected_directors_list:
|
| 255 |
recommendations_df = recommendations_df[
|
| 256 |
recommendations_df['Director_cleaned'].apply(lambda x: any(director in x for director in selected_directors_list))
|
| 257 |
]
|
| 258 |
print(f"Yönetmen filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 259 |
-
|
| 260 |
if selected_stars_list:
|
| 261 |
recommendations_df = recommendations_df[
|
| 262 |
recommendations_df['Stars_cleaned'].apply(lambda x: any(star in x for star in selected_stars_list))
|
| 263 |
]
|
| 264 |
print(f"Oyuncu filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 265 |
-
|
| 266 |
-
if search_text and len(recommendations_df) > 0
|
| 267 |
print(f"'{search_text}' için NLP benzerlik araması yapılıyor...")
|
| 268 |
-
|
| 269 |
try:
|
| 270 |
query_embedding = sentence_model.encode(search_text, convert_to_tensor=True)
|
| 271 |
except Exception as e:
|
| 272 |
print(f"HATA: Arama metni embedding'i oluşturulurken hata oluştu: {e}")
|
| 273 |
return "Arama metni işlenirken bir hata oluştu. Lütfen tekrar deneyin."
|
| 274 |
-
|
| 275 |
filtered_indices = recommendations_df.index.tolist()
|
| 276 |
if not filtered_indices or len(film_embeddings) == 0:
|
| 277 |
print("HATA: Filtrelenmiş film indeksi bulunamadı veya embedding'ler boş.")
|
| 278 |
return "Filtreleme sonrası film bulunamadı."
|
| 279 |
|
| 280 |
-
try:
|
| 281 |
-
# film_embeddings dizin aralığını kontrol et
|
| 282 |
-
if filtered_indices and (max(filtered_indices) >= film_embeddings.shape[0] or min(filtered_indices) < 0):
|
| 283 |
-
print(f"HATA: film_embeddings dizin aralığı dışında bir indeks var. Max index: {max(filtered_indices)}, Embedding boyutu: {film_embeddings.shape[0]}")
|
| 284 |
-
return "Benzerlik hesaplanırken bir hata oluştu (dizin hatası). Lütfen tekrar deneyin."
|
| 285 |
|
|
|
|
| 286 |
current_film_embeddings = film_embeddings[filtered_indices]
|
| 287 |
cosine_scores = util.cos_sim(query_embedding, current_film_embeddings)[0]
|
| 288 |
-
recommendations_df['Similarity_Score'] = cosine_scores.cpu().numpy()
|
| 289 |
recommendations_df = recommendations_df.sort_values(
|
| 290 |
-
by=['Similarity_Score', 'IMDb Rating', 'Votes_numeric'],
|
| 291 |
-
ascending=[False, False, False]
|
| 292 |
).reset_index(drop=True)
|
| 293 |
print(f"NLP benzerlik filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 294 |
except Exception as e:
|
| 295 |
print(f"HATA: NLP benzerlik hesaplanırken hata oluştu: {e}")
|
| 296 |
return "Benzerlik hesaplanırken bir hata oluştu. Lütfen tekrar deneyin."
|
| 297 |
-
elif search_text and sentence_model is None:
|
| 298 |
-
print("NLP modeli yüklenemediği için arama metni filtrelemesi atlandı.")
|
| 299 |
|
| 300 |
-
|
| 301 |
-
if not search_text or sentence_model is None:
|
| 302 |
recommendations_df = recommendations_df.sort_values(
|
| 303 |
-
by=['IMDb Rating', 'Votes_numeric'],
|
| 304 |
ascending=[False, False]
|
| 305 |
).reset_index(drop=True)
|
| 306 |
-
|
| 307 |
top_recommendations = recommendations_df.head(num_recommendations_slider)
|
| 308 |
-
|
| 309 |
if top_recommendations.empty:
|
| 310 |
print("Kriterlere uygun film bulunamadı.")
|
| 311 |
-
return "
|
| 312 |
else:
|
| 313 |
print(f"Toplam {len(top_recommendations)} öneri bulundu.")
|
| 314 |
-
html_output = "
|
| 315 |
for idx, row in top_recommendations.iterrows():
|
| 316 |
directors_str = ", ".join([d.title() for d in row['Director_cleaned']])
|
| 317 |
stars_str = ", ".join([s.title() for s in row['Stars_cleaned']])
|
| 318 |
tags_str = ", ".join([t.title() for t in row['Tags_cleaned']])
|
| 319 |
|
| 320 |
similarity_info = ""
|
| 321 |
-
if 'Similarity_Score' in row and search_text
|
| 322 |
-
similarity_info = f"
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
<
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
"""
|
| 331 |
-
else:
|
| 332 |
-
genre_color = get_genre_color(row['Tags_cleaned'])
|
| 333 |
-
genre_icon = get_genre_icon(row['Tags_cleaned'])
|
| 334 |
-
title_initial = row['Title'][0].upper() if row['Title'] else '?'
|
| 335 |
-
poster_div = f"""
|
| 336 |
-
<div class="movie-poster no-poster" style="background-color: {genre_color};">
|
| 337 |
-
<span class="genre-icon">{genre_icon}</span>
|
| 338 |
-
<span class="title-initial">{title_initial}</span>
|
| 339 |
-
<span class="no-poster-text">POSTER YOK</span>
|
| 340 |
-
</div>
|
| 341 |
-
"""
|
| 342 |
|
| 343 |
html_output += f"""
|
| 344 |
-
<div
|
| 345 |
-
<div
|
| 346 |
-
{
|
| 347 |
</div>
|
| 348 |
-
<div
|
| 349 |
-
<h3
|
| 350 |
-
<p
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
</p>
|
| 355 |
-
<p class="movie-detail">Yönetmen: <span class="detail-value">{directors_str if directors_str else 'Bilinmiyor'}</span></p>
|
| 356 |
-
<p class="movie-detail">Oyuncular: <span class="detail-value">{stars_str if stars_str else 'Bilinmiyor'}</span></p>
|
| 357 |
-
<p class="movie-detail">Türler: <span class="detail-value">{tags_str if tags_str else 'Bilinmiyor'}</span></p>
|
| 358 |
</div>
|
| 359 |
</div>
|
| 360 |
"""
|
| 361 |
-
html_output += "</div>" # recommendations-grid'i kapat
|
| 362 |
return html_output
|
| 363 |
|
| 364 |
print("\nADIM 4: Film Öneri Sistemi Mantığı Oluşturuldu.")
|
|
@@ -368,234 +282,37 @@ print("\nADIM 4: Film Öneri Sistemi Mantığı Oluşturuldu.")
|
|
| 368 |
print("\nADIM 5: Gradio Web Arayüzü Oluşturuluyor (Yeniden Tasarlanmış Arayüz ve Popülerlik Sıralaması)...")
|
| 369 |
|
| 370 |
with gr.Blocks(theme=gr.themes.Soft(), css="""
|
| 371 |
-
/*
|
| 372 |
-
.gradio-container {
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
}
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
padding-bottom: 10px;
|
| 382 |
-
border-bottom: 2px solid #333; /* Alt çizgi */
|
| 383 |
-
font-size: 2.5em;
|
| 384 |
-
margin-bottom: 20px;
|
| 385 |
-
}
|
| 386 |
-
h3 {
|
| 387 |
-
color: #f39c12; /* Turuncu alt başlıklar */
|
| 388 |
-
margin-top: 15px;
|
| 389 |
-
margin-bottom: 10px;
|
| 390 |
-
font-size: 1.5em;
|
| 391 |
-
}
|
| 392 |
-
/* Birincil düğme stili */
|
| 393 |
-
.gr-button.gr-button-primary {
|
| 394 |
-
background-color: #e67e22 !important; /* Koyu Turuncu */
|
| 395 |
-
border-color: #e67e22 !important;
|
| 396 |
-
color: #fff !important;
|
| 397 |
-
font-weight: bold;
|
| 398 |
-
padding: 12px 20px;
|
| 399 |
-
border-radius: 8px;
|
| 400 |
-
transition: background-color 0.3s ease, transform 0.2s ease;
|
| 401 |
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
| 402 |
-
}
|
| 403 |
-
.gr-button.gr-button-primary:hover {
|
| 404 |
-
background-color: #d35400 !important; /* Daha koyu turuncu */
|
| 405 |
-
border-color: #d35400 !important;
|
| 406 |
-
transform: translateY(-2px); /* Hafif yukarı kayma efekti */
|
| 407 |
-
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.4);
|
| 408 |
-
}
|
| 409 |
-
/* CheckboxGroup etiketleri */
|
| 410 |
-
.gr-checkbox-group label {
|
| 411 |
-
color: #ccc;
|
| 412 |
-
background-color: #333; /* Checkbox arka planı */
|
| 413 |
-
border: 1px solid #555;
|
| 414 |
-
border-radius: 5px;
|
| 415 |
-
padding: 8px 12px;
|
| 416 |
-
margin: 4px;
|
| 417 |
-
transition: all 0.2s ease;
|
| 418 |
-
cursor: pointer;
|
| 419 |
-
}
|
| 420 |
.gr-checkbox-group input[type='checkbox']:checked + label {
|
| 421 |
-
background-color: #f39c12 !important;
|
| 422 |
border-color: #f39c12 !important;
|
| 423 |
color: #1a1a1a !important; /* Koyu metin rengi */
|
| 424 |
-
font-weight: bold;
|
| 425 |
}
|
| 426 |
-
.gr-checkbox-group input[type='checkbox'] + label
|
| 427 |
-
background-color: #
|
| 428 |
-
border-color: #f39c12; /* Hover'da turuncu çerçeve */
|
| 429 |
-
}
|
| 430 |
-
/* Dropdown, Slider, Textbox genel stili */
|
| 431 |
-
.gr-dropdown, .gr-slider, .gr-textbox {
|
| 432 |
-
background-color: #2c2c2c;
|
| 433 |
color: #eee;
|
| 434 |
-
border
|
| 435 |
-
border-radius: 5px;
|
| 436 |
-
padding: 5px; /* İç boşluk */
|
| 437 |
}
|
| 438 |
-
.gr-
|
| 439 |
-
color: #
|
| 440 |
}
|
| 441 |
.gr-dropdown-item.selected {
|
| 442 |
-
background-color: #f39c12 !important;
|
| 443 |
color: #1a1a1a !important;
|
| 444 |
}
|
| 445 |
.gr-dropdown-item:hover {
|
| 446 |
-
background-color: #e67e22 !important;
|
| 447 |
color: #1a1a1a !important;
|
| 448 |
}
|
| 449 |
-
/* Slider dolgu ve tutucu */
|
| 450 |
-
.gr-slider-fill {
|
| 451 |
-
background-color: #e67e22 !important; /* Koyu Turuncu */
|
| 452 |
-
}
|
| 453 |
-
.gr-slider-handle {
|
| 454 |
-
border-color: #f39c12 !important; /* Turuncu çerçeve */
|
| 455 |
-
background-color: #1a1a1a !important; /* Koyu arka plan */
|
| 456 |
-
}
|
| 457 |
-
/* Textbox input metni */
|
| 458 |
-
.gr-textbox input {
|
| 459 |
-
color: #eee;
|
| 460 |
-
}
|
| 461 |
-
|
| 462 |
-
/* Film Kartları Tasarımı - Resimdeki yapıya uygun */
|
| 463 |
-
.recommendations-grid {
|
| 464 |
-
display: grid;
|
| 465 |
-
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); /* Duyarlı grid */
|
| 466 |
-
gap: 20px; /* Kartlar arası boşluk */
|
| 467 |
-
padding: 20px 0;
|
| 468 |
-
}
|
| 469 |
-
.movie-card {
|
| 470 |
-
display: flex;
|
| 471 |
-
background-color: #2a2a2a; /* Kart arka planı */
|
| 472 |
-
border-radius: 10px;
|
| 473 |
-
overflow: hidden;
|
| 474 |
-
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
|
| 475 |
-
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out, border-color 0.2s ease-in-out;
|
| 476 |
-
border: 1px solid #333; /* Hafif kenarlık */
|
| 477 |
-
}
|
| 478 |
-
.movie-card:hover {
|
| 479 |
-
transform: translateY(-5px);
|
| 480 |
-
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.6);
|
| 481 |
-
border-color: #f39c12; /* Hover'da turuncu kenarlık */
|
| 482 |
-
}
|
| 483 |
-
.movie-card-poster-section {
|
| 484 |
-
flex-shrink: 0;
|
| 485 |
-
width: 120px; /* Poster genişliği */
|
| 486 |
-
height: 180px; /* Poster yüksekliği */
|
| 487 |
-
background-color: #333; /* Varsayılan arka plan */
|
| 488 |
-
display: flex;
|
| 489 |
-
align-items: center;
|
| 490 |
-
justify-content: center;
|
| 491 |
-
}
|
| 492 |
-
.movie-poster {
|
| 493 |
-
width: 100%;
|
| 494 |
-
height: 100%;
|
| 495 |
-
display: flex;
|
| 496 |
-
flex-direction: column;
|
| 497 |
-
align-items: center;
|
| 498 |
-
justify-content: center;
|
| 499 |
-
text-align: center;
|
| 500 |
-
color: #fff;
|
| 501 |
-
font-size: 0.8em;
|
| 502 |
-
line-height: 1.2;
|
| 503 |
-
}
|
| 504 |
-
.movie-poster img {
|
| 505 |
-
display: block;
|
| 506 |
-
width: 100%;
|
| 507 |
-
height: 100%;
|
| 508 |
-
object-fit: cover; /* Resmi kutuya sığdır */
|
| 509 |
-
border-radius: 4px; /* Hafif yuvarlak köşeler */
|
| 510 |
-
}
|
| 511 |
-
.movie-poster.no-poster {
|
| 512 |
-
font-size: 1em;
|
| 513 |
-
text-transform: uppercase;
|
| 514 |
-
position: relative; /* Çocuk öğeleri konumlandırmak için */
|
| 515 |
-
border-radius: 4px; /* Resimdeki gibi yuvarlak köşeler */
|
| 516 |
-
}
|
| 517 |
-
.movie-poster .genre-icon {
|
| 518 |
-
font-size: 3em; /* İkon boyutu */
|
| 519 |
-
margin-bottom: 5px;
|
| 520 |
-
opacity: 0.7; /* Hafif şeffaf */
|
| 521 |
-
color: #fff; /* İkon rengi */
|
| 522 |
-
}
|
| 523 |
-
.movie-poster .title-initial {
|
| 524 |
-
font-size: 2.5em; /* Baş harf boyutu */
|
| 525 |
-
font-weight: bold;
|
| 526 |
-
position: absolute;
|
| 527 |
-
top: 50%;
|
| 528 |
-
left: 50%;
|
| 529 |
-
transform: translate(-50%, -50%); /* Tam ortala */
|
| 530 |
-
color: rgba(255, 255, 255, 0.2); /* Yarı şeffaf beyaz */
|
| 531 |
-
pointer-events: none; /* Metnin tıklanmasını engeller */
|
| 532 |
-
}
|
| 533 |
-
.movie-poster .no-poster-text {
|
| 534 |
-
font-size: 0.7em;
|
| 535 |
-
position: absolute;
|
| 536 |
-
bottom: 10px;
|
| 537 |
-
color: rgba(255, 255, 255, 0.5); /* Yarı şeffaf metin */
|
| 538 |
-
pointer-events: none;
|
| 539 |
-
}
|
| 540 |
-
.movie-card-info {
|
| 541 |
-
flex-grow: 1;
|
| 542 |
-
padding: 15px;
|
| 543 |
-
display: flex; /* İçeriği dikeyde düzenlemek için */
|
| 544 |
-
flex-direction: column;
|
| 545 |
-
justify-content: center; /* Dikey ortala */
|
| 546 |
-
}
|
| 547 |
-
.movie-title {
|
| 548 |
-
margin-top: 0;
|
| 549 |
-
margin-bottom: 8px;
|
| 550 |
-
color: #f39c12; /* Turuncu başlık */
|
| 551 |
-
font-size: 1.4em;
|
| 552 |
-
line-height: 1.2;
|
| 553 |
-
}
|
| 554 |
-
.movie-rating {
|
| 555 |
-
margin-bottom: 10px;
|
| 556 |
-
color: #bbb;
|
| 557 |
-
font-size: 0.95em;
|
| 558 |
-
display: flex;
|
| 559 |
-
align-items: center;
|
| 560 |
-
flex-wrap: wrap;
|
| 561 |
-
}
|
| 562 |
-
.movie-rating b {
|
| 563 |
-
color: #eee; /* Kalın metin rengi */
|
| 564 |
-
}
|
| 565 |
-
.movie-rating .movie-votes {
|
| 566 |
-
margin-left: 10px;
|
| 567 |
-
white-space: nowrap; /* Oy sayısını tek satırda tut */
|
| 568 |
-
}
|
| 569 |
-
.movie-detail {
|
| 570 |
-
margin-bottom: 4px;
|
| 571 |
-
color: #ccc;
|
| 572 |
-
font-size: 0.9em;
|
| 573 |
-
}
|
| 574 |
-
.movie-detail .detail-value {
|
| 575 |
-
color: #eee;
|
| 576 |
-
}
|
| 577 |
-
/* Alt bilgi bölümü */
|
| 578 |
-
.gr-markdown p {
|
| 579 |
-
color: #ccc;
|
| 580 |
-
}
|
| 581 |
-
.gr-examples {
|
| 582 |
-
background-color: #2c2c2c;
|
| 583 |
-
border-radius: 8px;
|
| 584 |
-
padding: 15px;
|
| 585 |
-
margin-top: 20px;
|
| 586 |
-
border: 1px solid #333;
|
| 587 |
-
}
|
| 588 |
-
.gr-examples-label {
|
| 589 |
-
color: #f39c12 !important;
|
| 590 |
-
font-weight: bold;
|
| 591 |
-
}
|
| 592 |
-
/* Gradio Panel geçersiz kılmaları */
|
| 593 |
-
.gr-panel {
|
| 594 |
-
background-color: #2c2c2c;
|
| 595 |
-
border-color: #333;
|
| 596 |
-
border-radius: 10px;
|
| 597 |
-
box-shadow: none;
|
| 598 |
-
}
|
| 599 |
""") as demo:
|
| 600 |
gr.Markdown(
|
| 601 |
"""
|
|
@@ -604,26 +321,26 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 604 |
İstediğiniz bir film veya konu hakkında yazın, benzerlerini de bulalım.
|
| 605 |
"""
|
| 606 |
)
|
| 607 |
-
|
| 608 |
# Yeni yerleşim düzeni: Öneriler üstte, girişler altta
|
| 609 |
with gr.Row():
|
| 610 |
with gr.Column(scale=2): # Öneriler sütununu daha geniş yaptık
|
| 611 |
gr.Markdown("### Önerilen Filmler:")
|
| 612 |
-
output_html = gr.HTML(label="Önerileriniz burada listelenecektir.", value="<p style='text-align: center; color: #bbb;
|
| 613 |
-
|
| 614 |
with gr.Row():
|
| 615 |
with gr.Column(scale=1):
|
| 616 |
gr.Markdown("### Film Özelliklerini Seçin:")
|
| 617 |
-
|
| 618 |
tags_input = gr.CheckboxGroup(
|
| 619 |
-
label="Film Türleri",
|
| 620 |
-
choices=all_tags,
|
| 621 |
value=['action', 'drama'], # Varsayılan değerler
|
| 622 |
interactive=True
|
| 623 |
)
|
| 624 |
-
|
| 625 |
directors_input = gr.Dropdown(
|
| 626 |
-
label="Yönetmenler",
|
| 627 |
choices=all_directors, # Popülerliğe göre sıralanmış liste
|
| 628 |
multiselect=True,
|
| 629 |
allow_custom_value=False,
|
|
@@ -631,26 +348,26 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 631 |
)
|
| 632 |
|
| 633 |
stars_input = gr.Dropdown(
|
| 634 |
-
label="Oyuncular",
|
| 635 |
choices=all_stars, # Popülerliğe göre sıralanmış liste
|
| 636 |
multiselect=True,
|
| 637 |
allow_custom_value=False,
|
| 638 |
interactive=True
|
| 639 |
)
|
| 640 |
-
|
| 641 |
min_imdb_rating_slider = gr.Slider(
|
| 642 |
-
minimum=
|
| 643 |
-
maximum=
|
| 644 |
-
step=0.1,
|
| 645 |
-
value=7.6,
|
| 646 |
label="Minimum IMDb Puanı"
|
| 647 |
)
|
| 648 |
|
| 649 |
num_recommendations_slider = gr.Slider(
|
| 650 |
-
minimum=1,
|
| 651 |
-
maximum=20,
|
| 652 |
-
step=1,
|
| 653 |
-
value=10,
|
| 654 |
label="Öneri Sayısı"
|
| 655 |
)
|
| 656 |
|
|
@@ -658,9 +375,9 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 658 |
label="Film Adı veya Konu Hakkında Ara (NLP Tabanlı Benzerlik)",
|
| 659 |
placeholder="Örneğin: Batman, uzay filmi, zamanda yolculuk..."
|
| 660 |
)
|
| 661 |
-
|
| 662 |
recommend_btn = gr.Button("🚀 Film Önerilerini Getir", variant="primary", size="lg")
|
| 663 |
-
|
| 664 |
recommend_btn.click(
|
| 665 |
fn=get_movie_recommendations,
|
| 666 |
inputs=[tags_input, directors_input, stars_input, min_imdb_rating_slider, num_recommendations_slider, search_text_input],
|
|
@@ -669,19 +386,19 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 669 |
|
| 670 |
gr.Examples(
|
| 671 |
examples=[
|
| 672 |
-
[['action'], [], [], 7.6, 5, ""],
|
| 673 |
-
[['comedy', 'drama'], [], [], 7.8, 3, ""],
|
| 674 |
-
[[], ['christopher nolan'], [], 8.0, 5, ""],
|
| 675 |
-
[[], [], ['leonardo dicaprio'], 7.8, 3, ""],
|
| 676 |
-
[[], [], [], 8.0, 5, "kahramanlık ve bilim kurgu"],
|
| 677 |
-
[['action', 'sci-fi'], [], [], 7.8, 5, "uzaylı istilası ve kaçış"],
|
| 678 |
],
|
| 679 |
inputs=[tags_input, directors_input, stars_input, min_imdb_rating_slider, num_recommendations_slider, search_text_input],
|
| 680 |
-
outputs=output_html,
|
| 681 |
fn=get_movie_recommendations,
|
| 682 |
label="Örnek Önerileri Deneyin"
|
| 683 |
)
|
| 684 |
-
|
| 685 |
gr.Markdown(
|
| 686 |
"""
|
| 687 |
---
|
|
@@ -695,4 +412,4 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 695 |
)
|
| 696 |
|
| 697 |
demo.launch(share=True)
|
| 698 |
-
print("\nADIM 5: Gradio Web Arayüzü Başlatıldı.")
|
|
|
|
| 2 |
import numpy as np
|
| 3 |
import gradio as gr
|
| 4 |
import os
|
| 5 |
+
from sentence_transformers import SentenceTransformer, util
|
| 6 |
+
import torch
|
| 7 |
import re
|
| 8 |
|
| 9 |
print("--- Film Öneri Sistemi Başlatılıyor (Popülerlik Sıralaması ve Yeniden Tasarlanmış Arayüz ile) ---")
|
|
|
|
| 11 |
# --- ADIM 1: Veri Seti Yükleniyor ve Keşfediliyor ---
|
| 12 |
print("ADIM 1: Veri Seti Yükleniyor ve Keşfediliyor...")
|
| 13 |
|
| 14 |
+
csv_file_name = "imdb-top-rated-movies-user-rated.csv"
|
| 15 |
+
file_path = os.path.join(".", csv_file_name)
|
| 16 |
|
| 17 |
if not os.path.exists(file_path):
|
| 18 |
print(f"HATA: '{csv_file_name}' dosyası '{file_path}' yolunda bulunamadı. Lütfen CSV dosyasını Space'e yüklediğinizden emin olun.")
|
| 19 |
+
exit(1)
|
| 20 |
+
|
| 21 |
+
try:
|
| 22 |
+
df = pd.read_csv(file_path)
|
| 23 |
+
print(f"'{csv_file_name}' başarıyla yüklendi. Toplam {len(df)} film bulundu.")
|
| 24 |
+
except Exception as e:
|
| 25 |
+
print(f"HATA: CSV dosyası yüklenirken hata oluştu: {e}")
|
| 26 |
+
exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
print("ADIM 1: Veri Seti Keşfi Tamamlandı.")
|
| 28 |
|
| 29 |
|
| 30 |
# --- ADIM 2: Veri Temizliği ve Ön İşleme ---
|
| 31 |
print("\nADIM 2: Veri Temizliği ve Ön İşleme Başlıyor...")
|
| 32 |
|
| 33 |
+
df_filtered = df[['Title', 'IMDb Rating', 'Tags', 'Director', 'Stars', 'Votes', 'Description', 'Poster URL']].copy()
|
| 34 |
|
| 35 |
+
df_filtered['Stars'].fillna('', inplace=True)
|
| 36 |
+
df_filtered['Description'].fillna('', inplace=True)
|
| 37 |
+
df_filtered['Poster URL'].fillna('', inplace=True)
|
| 38 |
|
| 39 |
genre_mapping = {
|
| 40 |
'action': ['action', 'action epic', 'gun fu', 'one-person army action', 'car action', 'kung fu', 'martial arts', 'martial-arts'],
|
|
|
|
| 66 |
main_genres = set()
|
| 67 |
for tag in tag_list:
|
| 68 |
if tag in reverse_genre_map:
|
| 69 |
+
main_genres.add(reverse_genre_map[tag])
|
| 70 |
return list(main_genres)
|
| 71 |
|
| 72 |
def clean_and_split(text_series):
|
| 73 |
if pd.isna(text_series):
|
| 74 |
return []
|
| 75 |
+
|
| 76 |
item = str(text_series)
|
| 77 |
item = item.replace('"', '').replace("'", '').strip()
|
| 78 |
+
item = item.replace('sci, fi', 'sci-fi')
|
| 79 |
+
|
| 80 |
split_items = [s.strip().lower() for s in item.split(',') if s.strip()]
|
| 81 |
return split_items
|
| 82 |
|
|
|
|
| 90 |
|
| 91 |
def convert_votes_to_numeric(votes_str):
|
| 92 |
if isinstance(votes_str, str):
|
| 93 |
+
votes_str = votes_str.replace(",", "")
|
| 94 |
if 'K' in votes_str:
|
| 95 |
return float(votes_str.replace('K', '')) * 1000
|
| 96 |
elif 'M' in votes_str:
|
| 97 |
return float(votes_str.replace('M', '')) * 1_000_000
|
| 98 |
+
try:
|
| 99 |
return float(votes_str)
|
| 100 |
except ValueError:
|
| 101 |
+
return np.nan
|
| 102 |
|
| 103 |
df_filtered['Votes_numeric'] = df_filtered['Votes'].apply(convert_votes_to_numeric)
|
| 104 |
df_filtered.drop('Votes', axis=1, inplace=True)
|
| 105 |
+
df_filtered.dropna(subset=['Votes_numeric'], inplace=True)
|
| 106 |
|
| 107 |
df_filtered['Combined_Text'] = df_filtered['Title'] + ". " + \
|
| 108 |
df_filtered['Description'] + ". " + \
|
|
|
|
| 117 |
print("\nADIM 3: NLP Modelini Yükleniyor ve Önceden Oluşturulmuş Embedding'ler Yükleniyor...")
|
| 118 |
|
| 119 |
model_name = 'sentence-transformers/all-MiniLM-L6-v2'
|
|
|
|
| 120 |
try:
|
| 121 |
sentence_model = SentenceTransformer(model_name)
|
| 122 |
print(f"'{model_name}' modeli başarıyla yüklendi.")
|
| 123 |
except Exception as e:
|
| 124 |
print(f"HATA: Sentence Transformer modeli yüklenirken hata oluştu: {e}")
|
| 125 |
+
exit(1)
|
| 126 |
|
| 127 |
embeddings_file_path = os.path.join(".", "film_embeddings.npy")
|
| 128 |
if not os.path.exists(embeddings_file_path):
|
| 129 |
print(f"HATA: '{embeddings_file_path}' dosyası bulunamadı. Lütfen Space'e yüklediğinizden emin olun.")
|
| 130 |
+
exit(1)
|
| 131 |
+
|
| 132 |
+
try:
|
| 133 |
+
film_embeddings = torch.from_numpy(np.load(embeddings_file_path))
|
| 134 |
+
print("Film embedding'leri başarıyla 'film_embeddings.npy' dosyasından yüklendi.")
|
| 135 |
+
except Exception as e:
|
| 136 |
+
print(f"HATA: film_embeddings.npy yüklenirken hata oluştu: {e}")
|
| 137 |
+
exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
print("ADIM 3: NLP Modelini Yükleme ve Film Embedding'lerini Oluşturma Tamamlandı.")
|
| 140 |
|
|
|
|
| 157 |
all_tags = sorted(list(set([tag for sublist in df_filtered['Tags_cleaned'] for tag in sublist if tag in genre_mapping])))
|
| 158 |
|
| 159 |
# Popülerliğe göre sıralanmış yönetmen ve oyuncu listeleri
|
| 160 |
+
# Popülerlik skoruna (Votes_numeric toplamı) göre azalan sırada sırala
|
| 161 |
all_directors = sorted(list(director_popularity.keys()), key=lambda d: director_popularity[d], reverse=True)
|
| 162 |
all_stars = sorted(list(star_popularity.keys()), key=lambda s: star_popularity[s], reverse=True)
|
| 163 |
# --- YENİ EKLENTİ SONU ---
|
| 164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
|
| 166 |
def get_movie_recommendations(selected_tags, selected_directors, selected_stars, min_imdb_rating_slider, num_recommendations_slider, search_text=""):
|
| 167 |
+
|
| 168 |
print(f"\n--- Öneri İsteği ---")
|
| 169 |
print(f"Seçilen Türler: {selected_tags}")
|
| 170 |
print(f"Seçilen Yönetmenler: {selected_directors}")
|
|
|
|
| 179 |
selected_stars_list = list(selected_stars) if selected_stars else []
|
| 180 |
|
| 181 |
recommendations_df = df_filtered.copy()
|
| 182 |
+
|
| 183 |
recommendations_df = recommendations_df[recommendations_df['IMDb Rating'] >= min_imdb_rating_slider]
|
| 184 |
print(f"IMDb Puanı filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 185 |
+
|
| 186 |
if selected_tags_list:
|
| 187 |
recommendations_df = recommendations_df[
|
| 188 |
recommendations_df['Tags_cleaned'].apply(lambda x: any(tag in x for tag in selected_tags_list))
|
| 189 |
]
|
| 190 |
print(f"Tür filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 191 |
+
|
| 192 |
if selected_directors_list:
|
| 193 |
recommendations_df = recommendations_df[
|
| 194 |
recommendations_df['Director_cleaned'].apply(lambda x: any(director in x for director in selected_directors_list))
|
| 195 |
]
|
| 196 |
print(f"Yönetmen filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 197 |
+
|
| 198 |
if selected_stars_list:
|
| 199 |
recommendations_df = recommendations_df[
|
| 200 |
recommendations_df['Stars_cleaned'].apply(lambda x: any(star in x for star in selected_stars_list))
|
| 201 |
]
|
| 202 |
print(f"Oyuncu filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 203 |
+
|
| 204 |
+
if search_text and len(recommendations_df) > 0:
|
| 205 |
print(f"'{search_text}' için NLP benzerlik araması yapılıyor...")
|
| 206 |
+
|
| 207 |
try:
|
| 208 |
query_embedding = sentence_model.encode(search_text, convert_to_tensor=True)
|
| 209 |
except Exception as e:
|
| 210 |
print(f"HATA: Arama metni embedding'i oluşturulurken hata oluştu: {e}")
|
| 211 |
return "Arama metni işlenirken bir hata oluştu. Lütfen tekrar deneyin."
|
| 212 |
+
|
| 213 |
filtered_indices = recommendations_df.index.tolist()
|
| 214 |
if not filtered_indices or len(film_embeddings) == 0:
|
| 215 |
print("HATA: Filtrelenmiş film indeksi bulunamadı veya embedding'ler boş.")
|
| 216 |
return "Filtreleme sonrası film bulunamadı."
|
| 217 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
|
| 219 |
+
try:
|
| 220 |
current_film_embeddings = film_embeddings[filtered_indices]
|
| 221 |
cosine_scores = util.cos_sim(query_embedding, current_film_embeddings)[0]
|
| 222 |
+
recommendations_df['Similarity_Score'] = cosine_scores.cpu().numpy()
|
| 223 |
recommendations_df = recommendations_df.sort_values(
|
| 224 |
+
by=['Similarity_Score', 'IMDb Rating', 'Votes_numeric'],
|
| 225 |
+
ascending=[False, False, False]
|
| 226 |
).reset_index(drop=True)
|
| 227 |
print(f"NLP benzerlik filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 228 |
except Exception as e:
|
| 229 |
print(f"HATA: NLP benzerlik hesaplanırken hata oluştu: {e}")
|
| 230 |
return "Benzerlik hesaplanırken bir hata oluştu. Lütfen tekrar deneyin."
|
|
|
|
|
|
|
| 231 |
|
| 232 |
+
if not search_text:
|
|
|
|
| 233 |
recommendations_df = recommendations_df.sort_values(
|
| 234 |
+
by=['IMDb Rating', 'Votes_numeric'],
|
| 235 |
ascending=[False, False]
|
| 236 |
).reset_index(drop=True)
|
| 237 |
+
|
| 238 |
top_recommendations = recommendations_df.head(num_recommendations_slider)
|
| 239 |
+
|
| 240 |
if top_recommendations.empty:
|
| 241 |
print("Kriterlere uygun film bulunamadı.")
|
| 242 |
+
return "Üzgünüz, seçtiğiniz kriterlere uygun film bulunamadı."
|
| 243 |
else:
|
| 244 |
print(f"Toplam {len(top_recommendations)} öneri bulundu.")
|
| 245 |
+
html_output = ""
|
| 246 |
for idx, row in top_recommendations.iterrows():
|
| 247 |
directors_str = ", ".join([d.title() for d in row['Director_cleaned']])
|
| 248 |
stars_str = ", ".join([s.title() for s in row['Stars_cleaned']])
|
| 249 |
tags_str = ", ".join([t.title() for t in row['Tags_cleaned']])
|
| 250 |
|
| 251 |
similarity_info = ""
|
| 252 |
+
if 'Similarity_Score' in row and search_text:
|
| 253 |
+
similarity_info = f", Benzerlik: {row['Similarity_Score']:.2f}"
|
| 254 |
+
|
| 255 |
+
poster_html = f"""
|
| 256 |
+
<div style="width: 120px; height: 180px; background-color: #2a2a2a; border-radius: 4px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; color: #888; font-size: 0.8em; line-height: 1.2; padding: 5px;">
|
| 257 |
+
<span style="font-weight: bold; margin-bottom: 5px;">{row['Title']}</span>
|
| 258 |
+
<span>Poster Yok</span>
|
| 259 |
+
</div>
|
| 260 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
html_output += f"""
|
| 263 |
+
<div style="display: flex; margin-bottom: 20px; border: 1px solid #333; padding: 10px; border-radius: 8px; background-color: #1a1a1a;">
|
| 264 |
+
<div style="flex-shrink: 0; margin-right: 15px;">
|
| 265 |
+
{poster_html}
|
| 266 |
</div>
|
| 267 |
+
<div style="flex-grow: 1;">
|
| 268 |
+
<h3 style="margin-top: 0px; margin-bottom: 5px; color: #f39c12;">{row['Title']}</h3>
|
| 269 |
+
<p style="margin-bottom: 5px; color: #eee;">IMDb: <b>{row['IMDb Rating']:.1f}</b> ⭐, Oylar: <b>{int(row['Votes_numeric']):,}</b> 🗳️{similarity_info}</p>
|
| 270 |
+
<p style="margin-bottom: 5px; color: #bbb;">Yönetmen: {directors_str if directors_str else 'Bilinmiyor'}</p>
|
| 271 |
+
<p style="margin-bottom: 5px; color: #bbb;">Oyuncular: {stars_str if stars_str else 'Bilinmiyor'}</p>
|
| 272 |
+
<p style="margin-bottom: 0px; color: #bbb;">Türler: {tags_str if tags_str else 'Bilinmiyor'}</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
</div>
|
| 274 |
</div>
|
| 275 |
"""
|
|
|
|
| 276 |
return html_output
|
| 277 |
|
| 278 |
print("\nADIM 4: Film Öneri Sistemi Mantığı Oluşturuldu.")
|
|
|
|
| 282 |
print("\nADIM 5: Gradio Web Arayüzü Oluşturuluyor (Yeniden Tasarlanmış Arayüz ve Popülerlik Sıralaması)...")
|
| 283 |
|
| 284 |
with gr.Blocks(theme=gr.themes.Soft(), css="""
|
| 285 |
+
/* Custom CSS */
|
| 286 |
+
.gradio-container { max-width: 1200px !important; font-family: 'Segoe UI', sans-serif; }
|
| 287 |
+
h1 { color: #f39c12; text-align: center; }
|
| 288 |
+
h3 { color: #eee; }
|
| 289 |
+
.gr-button.gr-button-primary { background-color: #f39c12 !important; border-color: #f39c12 !important; }
|
| 290 |
+
.gr-button.gr-button-primary:hover { background-color: #e67e22 !important; border-color: #e67e22 !important; }
|
| 291 |
+
.gr-checkbox-group label { color: #ccc; }
|
| 292 |
+
.gr-dropdown, .gr-slider, .gr-textbox { background-color: #2c2c2c; color: #eee; border-color: #555; }
|
| 293 |
+
.gr-dropdown-item { color: #eee; }
|
| 294 |
+
/* CheckboxGroup/Dropdown için sarı vurgu rengi */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
.gr-checkbox-group input[type='checkbox']:checked + label {
|
| 296 |
+
background-color: #f39c12 !important;
|
| 297 |
border-color: #f39c12 !important;
|
| 298 |
color: #1a1a1a !important; /* Koyu metin rengi */
|
|
|
|
| 299 |
}
|
| 300 |
+
.gr-checkbox-group input[type='checkbox'] + label {
|
| 301 |
+
background-color: #333;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
color: #eee;
|
| 303 |
+
border: 1px solid #555;
|
|
|
|
|
|
|
| 304 |
}
|
| 305 |
+
.gr-checkbox-group input[type='checkbox'] + label:hover {
|
| 306 |
+
background-color: #444;
|
| 307 |
}
|
| 308 |
.gr-dropdown-item.selected {
|
| 309 |
+
background-color: #f39c12 !important;
|
| 310 |
color: #1a1a1a !important;
|
| 311 |
}
|
| 312 |
.gr-dropdown-item:hover {
|
| 313 |
+
background-color: #e67e22 !important;
|
| 314 |
color: #1a1a1a !important;
|
| 315 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
""") as demo:
|
| 317 |
gr.Markdown(
|
| 318 |
"""
|
|
|
|
| 321 |
İstediğiniz bir film veya konu hakkında yazın, benzerlerini de bulalım.
|
| 322 |
"""
|
| 323 |
)
|
| 324 |
+
|
| 325 |
# Yeni yerleşim düzeni: Öneriler üstte, girişler altta
|
| 326 |
with gr.Row():
|
| 327 |
with gr.Column(scale=2): # Öneriler sütununu daha geniş yaptık
|
| 328 |
gr.Markdown("### Önerilen Filmler:")
|
| 329 |
+
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>")
|
| 330 |
+
|
| 331 |
with gr.Row():
|
| 332 |
with gr.Column(scale=1):
|
| 333 |
gr.Markdown("### Film Özelliklerini Seçin:")
|
| 334 |
+
|
| 335 |
tags_input = gr.CheckboxGroup(
|
| 336 |
+
label="Film Türleri",
|
| 337 |
+
choices=all_tags,
|
| 338 |
value=['action', 'drama'], # Varsayılan değerler
|
| 339 |
interactive=True
|
| 340 |
)
|
| 341 |
+
|
| 342 |
directors_input = gr.Dropdown(
|
| 343 |
+
label="Yönetmenler",
|
| 344 |
choices=all_directors, # Popülerliğe göre sıralanmış liste
|
| 345 |
multiselect=True,
|
| 346 |
allow_custom_value=False,
|
|
|
|
| 348 |
)
|
| 349 |
|
| 350 |
stars_input = gr.Dropdown(
|
| 351 |
+
label="Oyuncular",
|
| 352 |
choices=all_stars, # Popülerliğe göre sıralanmış liste
|
| 353 |
multiselect=True,
|
| 354 |
allow_custom_value=False,
|
| 355 |
interactive=True
|
| 356 |
)
|
| 357 |
+
|
| 358 |
min_imdb_rating_slider = gr.Slider(
|
| 359 |
+
minimum=df_filtered['IMDb Rating'].min(),
|
| 360 |
+
maximum=df_filtered['IMDb Rating'].max(),
|
| 361 |
+
step=0.1,
|
| 362 |
+
value=7.6,
|
| 363 |
label="Minimum IMDb Puanı"
|
| 364 |
)
|
| 365 |
|
| 366 |
num_recommendations_slider = gr.Slider(
|
| 367 |
+
minimum=1,
|
| 368 |
+
maximum=20,
|
| 369 |
+
step=1,
|
| 370 |
+
value=10,
|
| 371 |
label="Öneri Sayısı"
|
| 372 |
)
|
| 373 |
|
|
|
|
| 375 |
label="Film Adı veya Konu Hakkında Ara (NLP Tabanlı Benzerlik)",
|
| 376 |
placeholder="Örneğin: Batman, uzay filmi, zamanda yolculuk..."
|
| 377 |
)
|
| 378 |
+
|
| 379 |
recommend_btn = gr.Button("🚀 Film Önerilerini Getir", variant="primary", size="lg")
|
| 380 |
+
|
| 381 |
recommend_btn.click(
|
| 382 |
fn=get_movie_recommendations,
|
| 383 |
inputs=[tags_input, directors_input, stars_input, min_imdb_rating_slider, num_recommendations_slider, search_text_input],
|
|
|
|
| 386 |
|
| 387 |
gr.Examples(
|
| 388 |
examples=[
|
| 389 |
+
[['action'], [], [], 7.6, 5, ""],
|
| 390 |
+
[['comedy', 'drama'], [], [], 7.8, 3, ""],
|
| 391 |
+
[[], ['christopher nolan'], [], 8.0, 5, ""],
|
| 392 |
+
[[], [], ['leonardo dicaprio'], 7.8, 3, ""],
|
| 393 |
+
[[], [], [], 8.0, 5, "kahramanlık ve bilim kurgu"],
|
| 394 |
+
[['action', 'sci-fi'], [], [], 7.8, 5, "uzaylı istilası ve kaçış"],
|
| 395 |
],
|
| 396 |
inputs=[tags_input, directors_input, stars_input, min_imdb_rating_slider, num_recommendations_slider, search_text_input],
|
| 397 |
+
outputs=output_html,
|
| 398 |
fn=get_movie_recommendations,
|
| 399 |
label="Örnek Önerileri Deneyin"
|
| 400 |
)
|
| 401 |
+
|
| 402 |
gr.Markdown(
|
| 403 |
"""
|
| 404 |
---
|
|
|
|
| 412 |
)
|
| 413 |
|
| 414 |
demo.launch(share=True)
|
| 415 |
+
print("\nADIM 5: Gradio Web Arayüzü Başlatıldı.")
|