Update app.py
Browse files
app.py
CHANGED
|
@@ -0,0 +1,383 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
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 (Hugging Face Deploy için) ---")
|
| 10 |
+
|
| 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'],
|
| 41 |
+
'adventure': ['adventure', 'adventure epic', 'desert adventure', 'animal adventure', 'space adventure', 'swashbuckler'],
|
| 42 |
+
'comedy': ['comedy', 'romantic comedy', 'buddy comedy', 'sitcom', 'black comedy', 'satire', 'spoof', 'parody', 'slapstick', 'screwball comedy', 'dark comedy', 'body swap comedy'],
|
| 43 |
+
'drama': ['drama', 'period drama', 'cop drama', 'legal drama', 'medical drama', 'teen drama', 'psychological drama', 'melodrama', 'historical drama', 'biography', 'romantic drama', 'showbiz drama', 'tragedy'],
|
| 44 |
+
'thriller': ['thriller', 'crime thriller', 'spy thriller', 'psychological thriller', 'mystery thriller', 'political thriller', 'conspiracy thriller', 'erotic thriller', 'cyber thriller', 'suspense'],
|
| 45 |
+
'sci-fi': ['sci-fi', 'space sci-fi', 'dystopian sci-fi', 'cyberpunk', 'alien invasion', 'mutant', 'robot', 'post-apocalyptic', 'time travel'],
|
| 46 |
+
'fantasy': ['fantasy', 'dark fantasy', 'sword & sorcery', 'fairy tale', 'epic fantasy'],
|
| 47 |
+
'horror': ['horror', 'slasher', 'supernatural horror', 'body horror', 'zombie', 'monster', 'vampire', 'werewolf', 'ghost'],
|
| 48 |
+
'mystery': ['mystery', 'suspense mystery', 'cozy mystery', 'whodunnit', 'detective', 'police procedural'],
|
| 49 |
+
'crime': ['crime', 'gangster', 'heist', 'mob', 'true crime'],
|
| 50 |
+
'romance': ['romance', 'romantic comedy', 'romantic drama'],
|
| 51 |
+
'animation': ['animation', 'adult animation', 'anime', 'computer animation', 'drawn animation', 'stop-motion animation'],
|
| 52 |
+
'family': ['family', 'kids'],
|
| 53 |
+
'western': ['western', 'classic western', 'neo-western'],
|
| 54 |
+
'war': ['war', 'war drama'],
|
| 55 |
+
'history': ['history', 'historical drama', 'biography'],
|
| 56 |
+
'music': ['music', 'musical', 'classic musical', 'concert'],
|
| 57 |
+
'documentary': ['documentary', 'docudrama', 'mockumentary']
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
reverse_genre_map = {}
|
| 61 |
+
for main_genre, sub_genres in genre_mapping.items():
|
| 62 |
+
for sub_genre in sub_genres:
|
| 63 |
+
reverse_genre_map[sub_genre] = main_genre
|
| 64 |
+
|
| 65 |
+
def map_to_main_genres(tag_list):
|
| 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 |
+
|
| 83 |
+
|
| 84 |
+
df_filtered['Tags_cleaned_raw'] = df_filtered['Tags'].apply(clean_and_split)
|
| 85 |
+
df_filtered['Director_cleaned'] = df_filtered['Director'].apply(clean_and_split)
|
| 86 |
+
df_filtered['Stars_cleaned'] = df_filtered['Stars'].apply(clean_and_split)
|
| 87 |
+
|
| 88 |
+
df_filtered['Tags_cleaned'] = df_filtered['Tags_cleaned_raw'].apply(map_to_main_genres)
|
| 89 |
+
|
| 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'] + ". " + \
|
| 109 |
+
df_filtered['Tags_cleaned'].apply(lambda x: ", ".join(x)) + ". " + \
|
| 110 |
+
df_filtered['Director_cleaned'].apply(lambda x: ", ".join(x)) + ". " + \
|
| 111 |
+
df_filtered['Stars_cleaned'].apply(lambda x: ", ".join(x))
|
| 112 |
+
|
| 113 |
+
print("\nADIM 2: Veri Temizliği ve Ön İşleme Tamamlandı.")
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
# --- ADIM 3: NLP Modelini Yükleme ve ÖNCEDEN OLUŞTURULMUŞ Embedding'leri Yükleme ---
|
| 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 |
+
|
| 141 |
+
|
| 142 |
+
# --- ADIM 4: Film Öneri Sistemi Mantığını Oluşturma ---
|
| 143 |
+
print("\nADIM 4: Film Öneri Sistemi Mantığı Oluşturuluyor...")
|
| 144 |
+
|
| 145 |
+
all_tags = sorted(list(set([tag for sublist in df_filtered['Tags_cleaned'] for tag in sublist if tag in genre_mapping])))
|
| 146 |
+
all_directors = sorted(list(set([director for sublist in df_filtered['Director_cleaned'] for director in sublist])))
|
| 147 |
+
all_stars = sorted(list(set([star for sublist in df_filtered['Stars_cleaned'] for star in sublist])))
|
| 148 |
+
|
| 149 |
+
def get_movie_recommendations(selected_tags, selected_directors, selected_stars, min_imdb_rating_slider, num_recommendations_slider, search_text=""):
|
| 150 |
+
|
| 151 |
+
print(f"\n--- Öneri İsteği ---")
|
| 152 |
+
print(f"Seçilen Türler: {selected_tags}")
|
| 153 |
+
print(f"Seçilen Yönetmenler: {selected_directors}")
|
| 154 |
+
print(f"Seçilen Oyuncular: {selected_stars}")
|
| 155 |
+
print(f"Minimum IMDb Puanı: {min_imdb_rating_slider}")
|
| 156 |
+
print(f"Öneri Sayısı: {num_recommendations_slider}")
|
| 157 |
+
print(f"Arama Metni: '{search_text}'")
|
| 158 |
+
print(f"Başlangıç DataFrame boyutu: {len(df_filtered)} (Poster URL'si dahil)")
|
| 159 |
+
|
| 160 |
+
selected_tags_list = list(selected_tags) if selected_tags else []
|
| 161 |
+
selected_directors_list = list(selected_directors) if selected_directors else []
|
| 162 |
+
selected_stars_list = list(selected_stars) if selected_stars else []
|
| 163 |
+
|
| 164 |
+
recommendations_df = df_filtered.copy()
|
| 165 |
+
|
| 166 |
+
recommendations_df = recommendations_df[recommendations_df['IMDb Rating'] >= min_imdb_rating_slider]
|
| 167 |
+
print(f"IMDb Puanı filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 168 |
+
|
| 169 |
+
if selected_tags_list:
|
| 170 |
+
recommendations_df = recommendations_df[
|
| 171 |
+
recommendations_df['Tags_cleaned'].apply(lambda x: any(tag in x for tag in selected_tags_list))
|
| 172 |
+
]
|
| 173 |
+
print(f"Tür filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 174 |
+
|
| 175 |
+
if selected_directors_list:
|
| 176 |
+
recommendations_df = recommendations_df[
|
| 177 |
+
recommendations_df['Director_cleaned'].apply(lambda x: any(director in x for director in selected_directors_list))
|
| 178 |
+
]
|
| 179 |
+
print(f"Yönetmen filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 180 |
+
|
| 181 |
+
if selected_stars_list:
|
| 182 |
+
recommendations_df = recommendations_df[
|
| 183 |
+
recommendations_df['Stars_cleaned'].apply(lambda x: any(star in x for star in selected_stars_list))
|
| 184 |
+
]
|
| 185 |
+
print(f"Oyuncu filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 186 |
+
|
| 187 |
+
if search_text and len(recommendations_df) > 0:
|
| 188 |
+
print(f"'{search_text}' için NLP benzerlik araması yapılıyor...")
|
| 189 |
+
|
| 190 |
+
try:
|
| 191 |
+
query_embedding = sentence_model.encode(search_text, convert_to_tensor=True)
|
| 192 |
+
except Exception as e:
|
| 193 |
+
print(f"HATA: Arama metni embedding'i oluşturulurken hata oluştu: {e}")
|
| 194 |
+
return "Arama metni işlenirken bir hata oluştu. Lütfen tekrar deneyin."
|
| 195 |
+
|
| 196 |
+
filtered_indices = recommendations_df.index.tolist()
|
| 197 |
+
if not filtered_indices or len(film_embeddings) == 0:
|
| 198 |
+
print("HATA: Filtrelenmiş film indeksi bulunamadı veya embedding'ler boş.")
|
| 199 |
+
return "Filtreleme sonrası film bulunamadı."
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
try:
|
| 203 |
+
current_film_embeddings = film_embeddings[filtered_indices]
|
| 204 |
+
cosine_scores = util.cos_sim(query_embedding, current_film_embeddings)[0]
|
| 205 |
+
recommendations_df['Similarity_Score'] = cosine_scores.cpu().numpy()
|
| 206 |
+
recommendations_df = recommendations_df.sort_values(
|
| 207 |
+
by=['Similarity_Score', 'IMDb Rating', 'Votes_numeric'],
|
| 208 |
+
ascending=[False, False, False]
|
| 209 |
+
).reset_index(drop=True)
|
| 210 |
+
print(f"NLP benzerlik filtrelemesi sonrası: {len(recommendations_df)} film")
|
| 211 |
+
except Exception as e:
|
| 212 |
+
print(f"HATA: NLP benzerlik hesaplanırken hata oluştu: {e}")
|
| 213 |
+
return "Benzerlik hesaplanırken bir hata oluştu. Lütfen tekrar deneyin."
|
| 214 |
+
|
| 215 |
+
if not search_text:
|
| 216 |
+
recommendations_df = recommendations_df.sort_values(
|
| 217 |
+
by=['IMDb Rating', 'Votes_numeric'],
|
| 218 |
+
ascending=[False, False]
|
| 219 |
+
).reset_index(drop=True)
|
| 220 |
+
|
| 221 |
+
top_recommendations = recommendations_df.head(num_recommendations_slider)
|
| 222 |
+
|
| 223 |
+
if top_recommendations.empty:
|
| 224 |
+
print("Kriterlere uygun film bulunamadı.")
|
| 225 |
+
return "Üzgünüz, seçtiğiniz kriterlere uygun film bulunamadı."
|
| 226 |
+
else:
|
| 227 |
+
print(f"Toplam {len(top_recommendations)} öneri bulundu.")
|
| 228 |
+
html_output = ""
|
| 229 |
+
for idx, row in top_recommendations.iterrows():
|
| 230 |
+
directors_str = ", ".join([d.title() for d in row['Director_cleaned']])
|
| 231 |
+
stars_str = ", ".join([s.title() for s in row['Stars_cleaned']])
|
| 232 |
+
tags_str = ", ".join([t.title() for t in row['Tags_cleaned']])
|
| 233 |
+
|
| 234 |
+
similarity_info = ""
|
| 235 |
+
if 'Similarity_Score' in row and search_text:
|
| 236 |
+
similarity_info = f", Benzerlik: {row['Similarity_Score']:.2f}"
|
| 237 |
+
|
| 238 |
+
poster_url = row['Poster URL']
|
| 239 |
+
is_valid_image_url = False
|
| 240 |
+
if poster_url and pd.notna(poster_url):
|
| 241 |
+
if re.match(r".*\.(jpg|jpeg|png|gif|bmp|webp)$", poster_url.lower()):
|
| 242 |
+
is_valid_image_url = True
|
| 243 |
+
|
| 244 |
+
poster_html = ""
|
| 245 |
+
if is_valid_image_url:
|
| 246 |
+
poster_html = f'<img src="{poster_url}" alt="{row["Title"]} Poster" style="width: 120px; height: 180px; object-fit: cover; border-radius: 4px;">'
|
| 247 |
+
else:
|
| 248 |
+
poster_html = f"""
|
| 249 |
+
<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;">
|
| 250 |
+
<span style="font-weight: bold; margin-bottom: 5px;">{row['Title']}</span>
|
| 251 |
+
<span>Poster Yok</span>
|
| 252 |
+
</div>
|
| 253 |
+
"""
|
| 254 |
+
|
| 255 |
+
html_output += f"""
|
| 256 |
+
<div style="display: flex; margin-bottom: 20px; border: 1px solid #333; padding: 10px; border-radius: 8px; background-color: #1a1a1a;">
|
| 257 |
+
<div style="flex-shrink: 0; margin-right: 15px;">
|
| 258 |
+
{poster_html}
|
| 259 |
+
</div>
|
| 260 |
+
<div style="flex-grow: 1;">
|
| 261 |
+
<h3 style="margin-top: 0px; margin-bottom: 5px; color: #f39c12;">{row['Title']}</h3>
|
| 262 |
+
<p style="margin-bottom: 5px; color: #eee;">IMDb: <b>{row['IMDb Rating']:.1f}</b> ⭐, Oylar: <b>{int(row['Votes_numeric']):,}</b> 🗳️{similarity_info}</p>
|
| 263 |
+
<p style="margin-bottom: 5px; color: #bbb;">Yönetmen: {directors_str if directors_str else 'Bilinmiyor'}</p>
|
| 264 |
+
<p style="margin-bottom: 5px; color: #bbb;">Oyuncular: {stars_str if stars_str else 'Bilinmiyor'}</p>
|
| 265 |
+
<p style="margin-bottom: 0px; color: #bbb;">Türler: {tags_str if tags_str else 'Bilinmiyor'}</p>
|
| 266 |
+
</div>
|
| 267 |
+
</div>
|
| 268 |
+
"""
|
| 269 |
+
return html_output
|
| 270 |
+
|
| 271 |
+
print("\nADIM 4: Film Öneri Sistemi Mantığı Oluşturuldu.")
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
# --- ADIM 5: Gradio Web Arayüzü Oluşturma ---
|
| 275 |
+
print("\nADIM 5: Gradio Web Arayüzü Oluşturuluyor...")
|
| 276 |
+
|
| 277 |
+
with gr.Blocks(theme=gr.themes.Soft(), css="""
|
| 278 |
+
.gradio-container { max-width: 1200px !important; font-family: 'Segoe UI', sans-serif; }
|
| 279 |
+
h1 { color: #f39c12; text-align: center; }
|
| 280 |
+
h3 { color: #eee; }
|
| 281 |
+
.gr-button.gr-button-primary { background-color: #f39c12 !important; border-color: #f39c12 !important; }
|
| 282 |
+
.gr-button.gr-button-primary:hover { background-color: #e67e22 !important; border-color: #e67e22 !important; }
|
| 283 |
+
.gr-checkbox-group label { color: #ccc; }
|
| 284 |
+
.gr-dropdown, .gr-slider, .gr-textbox { background-color: #2c2c2c; color: #eee; border-color: #555; }
|
| 285 |
+
.gr-dropdown-item { color: #eee; }
|
| 286 |
+
""") as demo:
|
| 287 |
+
gr.Markdown(
|
| 288 |
+
"""
|
| 289 |
+
# 🎬 Film Öneri Sistemi
|
| 290 |
+
Favori film özelliklerinizi seçin, yüksek IMDb puanına sahip filmleri keşfedin!
|
| 291 |
+
İstediğiniz bir film veya konu hakkında yazın, benzerlerini de bulalım.
|
| 292 |
+
"""
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
with gr.Row():
|
| 296 |
+
with gr.Column(scale=1):
|
| 297 |
+
gr.Markdown("### Film Özelliklerini Seçin:")
|
| 298 |
+
|
| 299 |
+
tags_input = gr.CheckboxGroup(
|
| 300 |
+
label="Film Türleri",
|
| 301 |
+
choices=all_tags,
|
| 302 |
+
value=['action', 'drama'],
|
| 303 |
+
interactive=True
|
| 304 |
+
)
|
| 305 |
+
|
| 306 |
+
directors_input = gr.Dropdown(
|
| 307 |
+
label="Yönetmenler",
|
| 308 |
+
choices=all_directors,
|
| 309 |
+
multiselect=True,
|
| 310 |
+
allow_custom_value=False,
|
| 311 |
+
interactive=True
|
| 312 |
+
)
|
| 313 |
+
|
| 314 |
+
stars_input = gr.Dropdown(
|
| 315 |
+
label="Oyuncular",
|
| 316 |
+
choices=all_stars,
|
| 317 |
+
multiselect=True,
|
| 318 |
+
allow_custom_value=False,
|
| 319 |
+
interactive=True
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
min_imdb_rating_slider = gr.Slider(
|
| 323 |
+
minimum=df_filtered['IMDb Rating'].min(),
|
| 324 |
+
maximum=df_filtered['IMDb Rating'].max(),
|
| 325 |
+
step=0.1,
|
| 326 |
+
value=7.6,
|
| 327 |
+
label="Minimum IMDb Puanı"
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
num_recommendations_slider = gr.Slider(
|
| 331 |
+
minimum=1,
|
| 332 |
+
maximum=20,
|
| 333 |
+
step=1,
|
| 334 |
+
value=10,
|
| 335 |
+
label="Öneri Sayısı"
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
search_text_input = gr.Textbox(
|
| 339 |
+
label="Film Adı veya Konu Hakkında Ara (NLP Tabanlı Benzerlik)",
|
| 340 |
+
placeholder="Örneğin: Batman, uzay filmi, zamanda yolculuk..."
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
recommend_btn = gr.Button("🚀 Film Önerilerini Getir", variant="primary", size="lg")
|
| 344 |
+
|
| 345 |
+
with gr.Column(scale=2):
|
| 346 |
+
gr.Markdown("### Önerilen Filmler:")
|
| 347 |
+
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>")
|
| 348 |
+
|
| 349 |
+
recommend_btn.click(
|
| 350 |
+
fn=get_movie_recommendations,
|
| 351 |
+
inputs=[tags_input, directors_input, stars_input, min_imdb_rating_slider, num_recommendations_slider, search_text_input],
|
| 352 |
+
outputs=output_html
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
gr.Examples(
|
| 356 |
+
examples=[
|
| 357 |
+
[['action'], [], [], 7.6, 5, ""],
|
| 358 |
+
[['comedy', 'drama'], [], [], 7.8, 3, ""],
|
| 359 |
+
[[], ['christopher nolan'], [], 8.0, 5, ""],
|
| 360 |
+
[[], [], ['leonardo dicaprio'], 7.8, 3, ""],
|
| 361 |
+
[[], [], [], 8.0, 5, "kahramanlık ve bilim kurgu"],
|
| 362 |
+
[['action', 'sci-fi'], [], [], 7.8, 5, "uzaylı istilası ve kaçış"],
|
| 363 |
+
],
|
| 364 |
+
inputs=[tags_input, directors_input, stars_input, min_imdb_rating_slider, num_recommendations_slider, search_text_input],
|
| 365 |
+
outputs=output_html,
|
| 366 |
+
fn=get_movie_recommendations,
|
| 367 |
+
label="Örnek Önerileri Deneyin"
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
gr.Markdown(
|
| 371 |
+
"""
|
| 372 |
+
---
|
| 373 |
+
### ℹ️ Nasıl Kullanılır?
|
| 374 |
+
1. **Film Türleri, Yönetmenler ve Oyuncular** bölümlerinden istediğiniz filtreleri seçin (birden fazla seçim yapabilirsiniz).
|
| 375 |
+
2. **Minimum IMDb Puanı** ve **Öneri Sayısı** çubuklarını ayarlayın.
|
| 376 |
+
3. İsterseniz **"Film Adı veya Konu Hakkında Ara"** kutucuğuna bir film adı, konu veya anahtar kelime yazın.
|
| 377 |
+
4. **"🚀 Film Önerilerini Getir"** butonuna tıklayın.
|
| 378 |
+
5. Öneriler sağ panelde görünecektir!
|
| 379 |
+
"""
|
| 380 |
+
)
|
| 381 |
+
|
| 382 |
+
demo.launch(share=True)
|
| 383 |
+
print("\nADIM 5: Gradio Web Arayüzü Başlatıldı.")
|