Update app.py
Browse files
app.py
CHANGED
|
@@ -6,7 +6,7 @@ from sentence_transformers import SentenceTransformer, util
|
|
| 6 |
import torch
|
| 7 |
import re
|
| 8 |
|
| 9 |
-
print("--- Film Öneri Sistemi Başlatılıyor (
|
| 10 |
|
| 11 |
# --- ADIM 1: Veri Seti Yükleniyor ve Keşfediliyor ---
|
| 12 |
print("ADIM 1: Veri Seti Yükleniyor ve Keşfediliyor...")
|
|
@@ -16,25 +16,50 @@ 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 |
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,17 +91,15 @@ 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 |
|
|
@@ -90,19 +113,21 @@ df_filtered['Tags_cleaned'] = df_filtered['Tags_cleaned_raw'].apply(map_to_main_
|
|
| 90 |
|
| 91 |
def convert_votes_to_numeric(votes_str):
|
| 92 |
if isinstance(votes_str, str):
|
| 93 |
-
|
|
|
|
| 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 |
-
|
| 105 |
-
df_filtered.
|
|
|
|
| 106 |
|
| 107 |
df_filtered['Combined_Text'] = df_filtered['Title'] + ". " + \
|
| 108 |
df_filtered['Description'] + ". " + \
|
|
@@ -117,24 +142,30 @@ print("\nADIM 2: Veri Temizliği ve Ön İşleme Tamamlandı.")
|
|
| 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 |
-
|
| 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 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
film_embeddings = torch.from_numpy(
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
print("ADIM 3: NLP Modelini Yükleme ve Film Embedding'lerini Oluşturma Tamamlandı.")
|
| 140 |
|
|
@@ -157,14 +188,12 @@ for index, row in df_filtered.iterrows():
|
|
| 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}")
|
|
@@ -172,184 +201,696 @@ def get_movie_recommendations(selected_tags, selected_directors, selected_stars,
|
|
| 172 |
print(f"Minimum IMDb Puanı: {min_imdb_rating_slider}")
|
| 173 |
print(f"Öneri Sayısı: {num_recommendations_slider}")
|
| 174 |
print(f"Arama Metni: '{search_text}'")
|
| 175 |
-
print(f"Başlangıç DataFrame boyutu: {len(df_filtered)} (Poster URL'si dahil)")
|
| 176 |
|
| 177 |
selected_tags_list = list(selected_tags) if selected_tags else []
|
| 178 |
selected_directors_list = list(selected_directors) if selected_directors else []
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 242 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
else:
|
| 244 |
-
|
| 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 |
-
|
| 250 |
|
| 251 |
similarity_info = ""
|
| 252 |
-
if 'Similarity_Score' in row and search_text:
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
<
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
html_output += f"""
|
| 263 |
-
<div
|
| 264 |
-
<div
|
| 265 |
-
{
|
| 266 |
</div>
|
| 267 |
-
<div
|
| 268 |
-
<h3
|
| 269 |
-
<
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
</div>
|
| 274 |
</div>
|
| 275 |
"""
|
|
|
|
| 276 |
return html_output
|
| 277 |
|
| 278 |
print("\nADIM 4: Film Öneri Sistemi Mantığı Oluşturuldu.")
|
| 279 |
|
| 280 |
|
| 281 |
# --- ADIM 5: Gradio Web Arayüzü Oluşturma ---
|
| 282 |
-
print("\nADIM 5: Gradio Web Arayüzü Oluşturuluyor (
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
border-color: #f39c12 !important;
|
| 298 |
-
color: #1a1a1a !important; /* Koyu metin rengi */
|
| 299 |
}
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
}
|
| 305 |
-
.gr-
|
| 306 |
-
background-color: #
|
|
|
|
| 307 |
}
|
| 308 |
.gr-dropdown-item.selected {
|
| 309 |
background-color: #f39c12 !important;
|
| 310 |
-
color:
|
|
|
|
| 311 |
}
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
""") as demo:
|
|
|
|
| 317 |
gr.Markdown(
|
| 318 |
"""
|
| 319 |
-
# 🎬 Film Öneri Sistemi
|
| 320 |
-
|
| 321 |
-
|
| 322 |
"""
|
| 323 |
)
|
| 324 |
|
| 325 |
-
# Yeni yerleşim düzeni: Öneriler üstte, girişler altta
|
| 326 |
with gr.Row():
|
| 327 |
-
with gr.Column(scale=
|
| 328 |
-
gr.Markdown("###
|
| 329 |
-
output_html = gr.HTML(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
|
| 331 |
with gr.Row():
|
| 332 |
with gr.Column(scale=1):
|
| 333 |
-
gr.Markdown("### Film
|
| 334 |
|
| 335 |
tags_input = gr.CheckboxGroup(
|
| 336 |
-
label="Film Türleri",
|
| 337 |
choices=all_tags,
|
| 338 |
-
value=['action', 'drama'],
|
| 339 |
interactive=True
|
| 340 |
)
|
| 341 |
|
| 342 |
directors_input = gr.Dropdown(
|
| 343 |
-
label="Yönetmenler",
|
| 344 |
-
choices=all_directors,
|
| 345 |
multiselect=True,
|
| 346 |
allow_custom_value=False,
|
| 347 |
interactive=True
|
| 348 |
)
|
| 349 |
|
| 350 |
stars_input = gr.Dropdown(
|
| 351 |
-
label="Oyuncular",
|
| 352 |
-
choices=all_stars,
|
| 353 |
multiselect=True,
|
| 354 |
allow_custom_value=False,
|
| 355 |
interactive=True
|
|
@@ -360,7 +901,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 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(
|
|
@@ -368,15 +909,20 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 368 |
maximum=20,
|
| 369 |
step=1,
|
| 370 |
value=10,
|
| 371 |
-
label="Öneri Sayısı"
|
| 372 |
)
|
| 373 |
|
| 374 |
search_text_input = gr.Textbox(
|
| 375 |
-
label="
|
| 376 |
-
placeholder="
|
|
|
|
| 377 |
)
|
| 378 |
|
| 379 |
-
recommend_btn = gr.Button(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
|
| 381 |
recommend_btn.click(
|
| 382 |
fn=get_movie_recommendations,
|
|
@@ -384,6 +930,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 384 |
outputs=output_html
|
| 385 |
)
|
| 386 |
|
|
|
|
| 387 |
gr.Examples(
|
| 388 |
examples=[
|
| 389 |
[['action'], [], [], 7.6, 5, ""],
|
|
@@ -396,20 +943,55 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 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="
|
| 400 |
)
|
| 401 |
|
| 402 |
gr.Markdown(
|
| 403 |
"""
|
| 404 |
---
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
"""
|
| 412 |
)
|
| 413 |
|
| 414 |
demo.launch(share=True)
|
| 415 |
-
print("\nADIM 5: Gradio Web Arayüzü Başlatıldı.")
|
|
|
|
| 6 |
import torch
|
| 7 |
import re
|
| 8 |
|
| 9 |
+
print("--- Film Öneri Sistemi Başlatılıyor (Orijinal Görsel Tasarım Entegrasyonu) ---")
|
| 10 |
|
| 11 |
# --- ADIM 1: Veri Seti Yükleniyor ve Keşfediliyor ---
|
| 12 |
print("ADIM 1: Veri Seti Yükleniyor ve Keşfediliyor...")
|
|
|
|
| 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 |
+
print("DEMO AMAÇLI SAHTE CSV OLUŞTURULUYOR...")
|
| 20 |
+
# Test amaçlı minimal bir sahte DataFrame oluştur
|
| 21 |
+
dummy_data = {
|
| 22 |
+
'Title': ["Django Unchained", "The Departed", "Pulp Fiction", "Inception", "The Dark Knight", "Forrest Gump", "The Matrix", "Interstellar", "Spirited Away", "Whiplash"],
|
| 23 |
+
'IMDb Rating': [8.5, 8.5, 8.9, 8.8, 9.0, 8.8, 8.7, 8.6, 8.6, 8.5],
|
| 24 |
+
'Tags': ["Drama, Western, Comedy", "Crime, Drama, Thriller", "Crime, Drama", "Action, Adventure, Sci-Fi", "Action, Crime, Drama", "Drama, Romance", "Action, Sci-Fi", "Adventure, Drama, Sci-Fi", "Animation, Adventure, Family", "Drama, Music"],
|
| 25 |
+
'Director': ["Quentin Tarantino", "Martin Scorsese", "Quentin Tarantino", "Christopher Nolan", "Christopher Nolan", "Robert Zemeckis", "Lana Wachowski, Lilly Wachowski", "Christopher Nolan", "Hayao Miyazaki", "Damien Chazelle"],
|
| 26 |
+
'Stars': ["Jamie Foxx, Christoph Waltz, Leonardo DiCaprio", "Leonardo DiCaprio, Matt Damon, Jack Nicholson", "John Travolta, Uma Thurman, Samuel L. Jackson", "Leonardo DiCaprio, Joseph Gordon-Levitt, Elliot Page", "Christian Bale, Heath Ledger, Aaron Eckhart", "Tom Hanks, Robin Wright, Gary Sinise", "Keanu Reeves, Laurence Fishburne, Carrie-Anne Moss", "Matthew McConaughey, Anne Hathaway, Jessica Chastain", "Daveigh Chase, Suzanne Pleshette, Jason Marsden", "Miles Teller, J.K. Simmons, Melissa Benoist"],
|
| 27 |
+
'Votes': ["1,800,000 Oy", "1,500,000 Oy", "2,100,000 Oy", "2,300,000 Oy", "2,800,000 Oy", "2,200,000 Oy", "2,000,000 Oy", "1,700,000 Oy", "1,100,000 Oy", "900,000 Oy"],
|
| 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 |
+
df.to_csv(file_path, index=False)
|
| 44 |
+
print(f"Sahte CSV '{csv_file_name}' oluşturuldu ve yüklendi. Toplam {len(df)} film bulundu.")
|
| 45 |
+
else:
|
| 46 |
+
try:
|
| 47 |
+
df = pd.read_csv(file_path)
|
| 48 |
+
print(f"'{csv_file_name}' başarıyla yüklendi. Toplam {len(df)} film bulundu.")
|
| 49 |
+
except Exception as e:
|
| 50 |
+
print(f"HATA: CSV dosyası yüklenirken hata oluştu: {e}")
|
| 51 |
+
exit(1)
|
| 52 |
print("ADIM 1: Veri Seti Keşfi Tamamlandı.")
|
| 53 |
|
| 54 |
|
| 55 |
# --- ADIM 2: Veri Temizliği ve Ön İşleme ---
|
| 56 |
print("\nADIM 2: Veri Temizliği ve Ön İşleme Başlıyor...")
|
| 57 |
|
| 58 |
+
df_filtered = df[['Title', 'IMDb Rating', 'Tags', 'Director', 'Stars', 'Votes', 'Description', 'Poster URL']].copy()
|
| 59 |
|
| 60 |
+
df_filtered['Stars'].fillna('', inplace=True)
|
| 61 |
+
df_filtered['Description'].fillna('', inplace=True)
|
| 62 |
+
df_filtered['Poster URL'].fillna('', inplace=True)
|
| 63 |
|
| 64 |
genre_mapping = {
|
| 65 |
'action': ['action', 'action epic', 'gun fu', 'one-person army action', 'car action', 'kung fu', 'martial arts', 'martial-arts'],
|
|
|
|
| 91 |
main_genres = set()
|
| 92 |
for tag in tag_list:
|
| 93 |
if tag in reverse_genre_map:
|
| 94 |
+
main_genres.add(reverse_genre_map[tag])
|
| 95 |
return list(main_genres)
|
| 96 |
|
| 97 |
def clean_and_split(text_series):
|
| 98 |
if pd.isna(text_series):
|
| 99 |
return []
|
|
|
|
| 100 |
item = str(text_series)
|
| 101 |
item = item.replace('"', '').replace("'", '').strip()
|
| 102 |
+
item = item.replace('sci, fi', 'sci-fi')
|
|
|
|
| 103 |
split_items = [s.strip().lower() for s in item.split(',') if s.strip()]
|
| 104 |
return split_items
|
| 105 |
|
|
|
|
| 113 |
|
| 114 |
def convert_votes_to_numeric(votes_str):
|
| 115 |
if isinstance(votes_str, str):
|
| 116 |
+
# " Oy" son ekini kaldır
|
| 117 |
+
votes_str = votes_str.replace(" Oy", "").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 |
+
# Orijinal 'Votes' sütunu string olarak tutulsun, sayısal değer 'Votes_numeric'te.
|
| 129 |
+
# df_filtered.drop('Votes', axis=1, inplace=True) # Bu satırı kaldırın
|
| 130 |
+
df_filtered.dropna(subset=['Votes_numeric'], inplace=True)
|
| 131 |
|
| 132 |
df_filtered['Combined_Text'] = df_filtered['Title'] + ". " + \
|
| 133 |
df_filtered['Description'] + ". " + \
|
|
|
|
| 142 |
print("\nADIM 3: NLP Modelini Yükleniyor ve Önceden Oluşturulmuş Embedding'ler Yükleniyor...")
|
| 143 |
|
| 144 |
model_name = 'sentence-transformers/all-MiniLM-L6-v2'
|
| 145 |
+
sentence_model = None # Modeli başlangıçta None olarak ayarla
|
| 146 |
try:
|
| 147 |
sentence_model = SentenceTransformer(model_name)
|
| 148 |
print(f"'{model_name}' modeli başarıyla yüklendi.")
|
| 149 |
except Exception as e:
|
| 150 |
print(f"HATA: Sentence Transformer modeli yüklenirken hata oluştu: {e}")
|
| 151 |
+
print("NLP modeli yüklenemedi, arama metni özelliği devre dışı bırakılacak.")
|
| 152 |
|
| 153 |
embeddings_file_path = os.path.join(".", "film_embeddings.npy")
|
| 154 |
if not os.path.exists(embeddings_file_path):
|
| 155 |
print(f"HATA: '{embeddings_file_path}' dosyası bulunamadı. Lütfen Space'e yüklediğinizden emin olun.")
|
| 156 |
+
print("DEMO AMAÇLI SAHTE EMBEDDING'LER OLUŞTURULUYOR...")
|
| 157 |
+
# Sahte embedding'ler oluştur - her film için rastgele bir vektör
|
| 158 |
+
dummy_embeddings = np.random.rand(len(df_filtered), 384) # all-MiniLM-L6-v2 boyutu 384
|
| 159 |
+
film_embeddings = torch.from_numpy(dummy_embeddings).float()
|
| 160 |
+
np.save(embeddings_file_path, dummy_embeddings) # Sonraki çalıştırmalar için kaydet
|
| 161 |
+
print("Film embedding'leri başarıyla 'film_embeddings.npy' dosyasından oluşturuldu.")
|
| 162 |
+
else:
|
| 163 |
+
try:
|
| 164 |
+
film_embeddings = torch.from_numpy(np.load(embeddings_file_path)).float() # Float tipini sağla
|
| 165 |
+
print("Film embedding'leri başarıyla 'film_embeddings.npy' dosyasından yüklendi.")
|
| 166 |
+
except Exception as e:
|
| 167 |
+
print(f"HATA: film_embeddings.npy yüklenirken hata oluştu: {e}")
|
| 168 |
+
exit(1)
|
| 169 |
|
| 170 |
print("ADIM 3: NLP Modelini Yükleme ve Film Embedding'lerini Oluşturma Tamamlandı.")
|
| 171 |
|
|
|
|
| 188 |
all_tags = sorted(list(set([tag for sublist in df_filtered['Tags_cleaned'] for tag in sublist if tag in genre_mapping])))
|
| 189 |
|
| 190 |
# Popülerliğe göre sıralanmış yönetmen ve oyuncu listeleri
|
|
|
|
| 191 |
all_directors = sorted(list(director_popularity.keys()), key=lambda d: director_popularity[d], reverse=True)
|
| 192 |
all_stars = sorted(list(star_popularity.keys()), key=lambda s: star_popularity[s], reverse=True)
|
| 193 |
# --- YENİ EKLENTİ SONU ---
|
| 194 |
|
|
|
|
| 195 |
def get_movie_recommendations(selected_tags, selected_directors, selected_stars, min_imdb_rating_slider, num_recommendations_slider, search_text=""):
|
| 196 |
+
|
| 197 |
print(f"\n--- Öneri İsteği ---")
|
| 198 |
print(f"Seçilen Türler: {selected_tags}")
|
| 199 |
print(f"Seçilen Yönetmenler: {selected_directors}")
|
|
|
|
| 201 |
print(f"Minimum IMDb Puanı: {min_imdb_rating_slider}")
|
| 202 |
print(f"Öneri Sayısı: {num_recommendations_slider}")
|
| 203 |
print(f"Arama Metni: '{search_text}'")
|
|
|
|
| 204 |
|
| 205 |
selected_tags_list = list(selected_tags) if selected_tags else []
|
| 206 |
selected_directors_list = list(selected_directors) if selected_directors else []
|
| 207 |
selected_stars_list = list(selected_stars) if selected_stars else []
|
| 208 |
|
| 209 |
recommendations_df = df_filtered.copy()
|
| 210 |
+
|
| 211 |
recommendations_df = recommendations_df[recommendations_df['IMDb Rating'] >= min_imdb_rating_slider]
|
| 212 |
+
|
|
|
|
| 213 |
if selected_tags_list:
|
| 214 |
recommendations_df = recommendations_df[
|
| 215 |
recommendations_df['Tags_cleaned'].apply(lambda x: any(tag in x for tag in selected_tags_list))
|
| 216 |
]
|
| 217 |
+
|
|
|
|
| 218 |
if selected_directors_list:
|
| 219 |
recommendations_df = recommendations_df[
|
| 220 |
recommendations_df['Director_cleaned'].apply(lambda x: any(director in x for director in selected_directors_list))
|
| 221 |
]
|
| 222 |
+
|
|
|
|
| 223 |
if selected_stars_list:
|
| 224 |
recommendations_df = recommendations_df[
|
| 225 |
recommendations_df['Stars_cleaned'].apply(lambda x: any(star in x for star in selected_stars_list))
|
| 226 |
]
|
| 227 |
+
|
| 228 |
+
if search_text and len(recommendations_df) > 0 and sentence_model is not None:
|
|
|
|
|
|
|
|
|
|
| 229 |
try:
|
| 230 |
query_embedding = sentence_model.encode(search_text, convert_to_tensor=True)
|
| 231 |
except Exception as e:
|
| 232 |
print(f"HATA: Arama metni embedding'i oluşturulurken hata oluştu: {e}")
|
| 233 |
return "Arama metni işlenirken bir hata oluştu. Lütfen tekrar deneyin."
|
| 234 |
+
|
| 235 |
filtered_indices = recommendations_df.index.tolist()
|
| 236 |
if not filtered_indices or len(film_embeddings) == 0:
|
|
|
|
| 237 |
return "Filtreleme sonrası film bulunamadı."
|
| 238 |
|
|
|
|
| 239 |
try:
|
| 240 |
+
if filtered_indices and (max(filtered_indices) >= film_embeddings.shape[0] or min(filtered_indices) < 0):
|
| 241 |
+
return "Benzerlik hesaplanırken bir hata oluştu (dizin hatası). Lütfen tekrar deneyin."
|
| 242 |
+
|
| 243 |
current_film_embeddings = film_embeddings[filtered_indices]
|
| 244 |
cosine_scores = util.cos_sim(query_embedding, current_film_embeddings)[0]
|
| 245 |
+
recommendations_df['Similarity_Score'] = cosine_scores.cpu().numpy()
|
| 246 |
recommendations_df = recommendations_df.sort_values(
|
| 247 |
+
by=['Similarity_Score', 'IMDb Rating', 'Votes_numeric'],
|
| 248 |
+
ascending=[False, False, False]
|
| 249 |
).reset_index(drop=True)
|
|
|
|
| 250 |
except Exception as e:
|
| 251 |
print(f"HATA: NLP benzerlik hesaplanırken hata oluştu: {e}")
|
| 252 |
return "Benzerlik hesaplanırken bir hata oluştu. Lütfen tekrar deneyin."
|
| 253 |
+
elif search_text and sentence_model is None:
|
| 254 |
+
print("NLP modeli yüklenemediği için arama metni filtrelemesi atlandı.")
|
| 255 |
|
| 256 |
+
if not search_text or sentence_model is None:
|
| 257 |
recommendations_df = recommendations_df.sort_values(
|
| 258 |
+
by=['IMDb Rating', 'Votes_numeric'],
|
| 259 |
ascending=[False, False]
|
| 260 |
).reset_index(drop=True)
|
| 261 |
+
|
| 262 |
top_recommendations = recommendations_df.head(num_recommendations_slider)
|
| 263 |
+
|
| 264 |
if top_recommendations.empty:
|
| 265 |
+
return """
|
| 266 |
+
<div class="no-results-card">
|
| 267 |
+
<div class="no-results-icon">🎬</div>
|
| 268 |
+
<h2 class="no-results-title">Sonuç Bulunamadı</h2>
|
| 269 |
+
<p class="no-results-message">Seçtiğiniz kriterlere uygun film bulunamadı. Filtreleri değiştirerek tekrar deneyin.</p>
|
| 270 |
+
</div>
|
| 271 |
+
"""
|
| 272 |
else:
|
| 273 |
+
html_output = "<div class='recommendations-grid'>"
|
|
|
|
| 274 |
for idx, row in top_recommendations.iterrows():
|
| 275 |
directors_str = ", ".join([d.title() for d in row['Director_cleaned']])
|
| 276 |
stars_str = ", ".join([s.title() for s in row['Stars_cleaned']])
|
| 277 |
+
tags_str_list = [tag.title() for tag in row['Tags_cleaned']]
|
| 278 |
|
| 279 |
similarity_info = ""
|
| 280 |
+
if 'Similarity_Score' in row and search_text and sentence_model is not None:
|
| 281 |
+
sim_percentage = int(row['Similarity_Score'] * 100)
|
| 282 |
+
similarity_info = f"""
|
| 283 |
+
<span style="color: #bbb; font-size: 0.9em; margin-left: 10px;">
|
| 284 |
+
(Benzerlik: {sim_percentage}%)
|
| 285 |
+
</span>
|
| 286 |
+
"""
|
| 287 |
+
|
| 288 |
+
# Poster veya yer tutucu HTML'i
|
| 289 |
+
poster_content = ""
|
| 290 |
+
if row['Poster URL'] and row['Poster URL'].strip() != "":
|
| 291 |
+
poster_content = f"""
|
| 292 |
+
<img src="{row['Poster URL']}" alt="{row['Title']}" loading="lazy" style="width: 100%; height: 100%; object-fit: cover; border-radius: 8px;">
|
| 293 |
+
"""
|
| 294 |
+
else:
|
| 295 |
+
# Orijinal görseldeki gibi film şeridi simgesi ve başlığın kısaltması
|
| 296 |
+
short_title = row['Title'][:10] + "..." if len(row['Title']) > 13 else row['Title']
|
| 297 |
+
poster_content = f"""
|
| 298 |
+
<div class="poster-placeholder">
|
| 299 |
+
<svg viewBox="0 0 24 24" width="60" height="60" stroke="#a0a0a0" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round" class="clapboard-icon">
|
| 300 |
+
<path d="M4 14.899A2.121 2.121 0 0 1 4 12.02c5.688 0 8.007-4.135 12-4.135 1.052-.007 1.954.184 2.766.527M12.63 7.885c2.735-2.22 4.673-4.22 7.37-4.22C21.49 3.665 22 4.173 22 4.802v14.4c0 .629-.51 1.137-1.137 1.137-.996 0-1.898-.24-2.71-.62-3.83-1.742-6.533-2.612-9.255-2.612-1.07 0-2.002.26-2.825.75-.487.27-.887.59-1.21.95M4 14.899v4.321c0 .629-.51 1.137-1.137 1.137-.996 0-1.898-.24-2.71-.62-3.83-1.742-6.533-2.612-9.255-2.612-1.07 0-2.002.26-2.825.75-.487.27-.887.59-1.21.95M4 14.899c-2.735-2.22-4.673-4.22-7.37-4.22C2.51 3.665 2 4.173 2 4.802v14.4c0 .629.51 1.137 1.137 1.137.996 0 1.898-.24 2.71-.62 3.83-1.742 6.533-2.612 9.255-2.612 1.07 0 2.002.26 2.825.75.487.27.887.59 1.21.95" />
|
| 301 |
+
<rect x="3" y="1" width="18" height="2" rx="1" ry="1"></rect>
|
| 302 |
+
<rect x="3" y="1" width="4" height="2" rx="1" ry="1"></rect>
|
| 303 |
+
<rect x="7" y="1" width="4" height="2" rx="1" ry="1"></rect>
|
| 304 |
+
<rect x="11" y="1" width="4" height="2" rx="1" ry="1"></rect>
|
| 305 |
+
<rect x="15" y="1" width="4" height="2" rx="1" ry="1"></rect>
|
| 306 |
+
<path d="M5 21H19a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2z"></path>
|
| 307 |
+
</svg>
|
| 308 |
+
<span class="poster-title-placeholder">{short_title}</span>
|
| 309 |
+
<span class="no-poster-text">Poster Yok</span>
|
| 310 |
+
</div>
|
| 311 |
+
"""
|
| 312 |
|
| 313 |
html_output += f"""
|
| 314 |
+
<div class="movie-card">
|
| 315 |
+
<div class="movie-card-left-panel">
|
| 316 |
+
{poster_content}
|
| 317 |
</div>
|
| 318 |
+
<div class="movie-card-info-panel">
|
| 319 |
+
<h3 class="movie-title-display">{row['Title']}</h3>
|
| 320 |
+
<div class="rating-and-votes">
|
| 321 |
+
<span class="rating-badge">⭐ {row['IMDb Rating']:.1f}</span>
|
| 322 |
+
<div class="votes-info">
|
| 323 |
+
<svg viewBox="0 0 24 24" width="16" height="16" stroke="#9e9e9e" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="17" x2="16" y2="10"></line><line x1="8" y1="17" x2="8" y2="10"></line><line x1="12" y1="17" x2="12" y2="10"></line><line x1="12" y1="3" x2="12" y2="21"></line></svg>
|
| 324 |
+
<span>{row['Votes']}</span>
|
| 325 |
+
{similarity_info}
|
| 326 |
+
</div>
|
| 327 |
+
</div>
|
| 328 |
+
<div class="movie-details-row">
|
| 329 |
+
<span class="detail-label">Yönetmen:</span>
|
| 330 |
+
<span class="detail-value">{directors_str if directors_str else 'Bilinmiyor'}</span>
|
| 331 |
+
</div>
|
| 332 |
+
<div class="movie-details-row">
|
| 333 |
+
<span class="detail-label">Oyuncular:</span>
|
| 334 |
+
<span class="detail-value">{stars_str if stars_str else 'Bilinmiyor'}</span>
|
| 335 |
+
</div>
|
| 336 |
+
<div class="genre-tags-container">
|
| 337 |
+
{''.join([f'<span class="genre-tag">{tag}</span>' for tag in tags_str_list])}
|
| 338 |
+
</div>
|
| 339 |
</div>
|
| 340 |
</div>
|
| 341 |
"""
|
| 342 |
+
html_output += "</div>"
|
| 343 |
return html_output
|
| 344 |
|
| 345 |
print("\nADIM 4: Film Öneri Sistemi Mantığı Oluşturuldu.")
|
| 346 |
|
| 347 |
|
| 348 |
# --- ADIM 5: Gradio Web Arayüzü Oluşturma ---
|
| 349 |
+
print("\nADIM 5: Gradio Web Arayüzü Oluşturuluyor (Orijinal Görsel Entegrasyonu ile)...")
|
| 350 |
+
|
| 351 |
+
# Özel tema oluştur - tüm mor/mavi renkleri turuncu yap
|
| 352 |
+
# Bu tema Gradio bileşenlerinin genel renklerini etkiler.
|
| 353 |
+
custom_theme = gr.themes.Base(
|
| 354 |
+
primary_hue=gr.themes.colors.orange, # Ana renk turuncu tonları
|
| 355 |
+
secondary_hue=gr.themes.colors.orange, # İkincil renk turuncu tonları
|
| 356 |
+
neutral_hue=gr.themes.colors.slate, # Nötr renkler (gri tonları)
|
| 357 |
+
).set(
|
| 358 |
+
# Butonlar
|
| 359 |
+
button_primary_background_fill="hsl(24, 88%, 50%)", # Turuncuya yakın renk
|
| 360 |
+
button_primary_background_fill_hover="hsl(24, 88%, 40%)", # Daha koyu turuncu hover
|
| 361 |
+
button_primary_text_color="white",
|
| 362 |
+
# Checkbox'lar
|
| 363 |
+
checkbox_label_background_fill="hsl(220, 10%, 20%)", # Koyu gri
|
| 364 |
+
checkbox_label_background_fill_hover="hsl(220, 10%, 25%)",
|
| 365 |
+
checkbox_label_background_fill_selected="hsl(24, 88%, 50%)", # Turuncu seçili
|
| 366 |
+
checkbox_label_text_color="white",
|
| 367 |
+
checkbox_label_text_color_selected="black", # Seçili metin siyah
|
| 368 |
+
checkbox_label_border_color_selected="hsl(24, 88%, 50%)",
|
| 369 |
+
# Dropdown, Slider, Textbox
|
| 370 |
+
input_background_fill="hsl(220, 10%, 20%)", # Koyu gri
|
| 371 |
+
input_border_color="hsl(220, 10%, 30%)",
|
| 372 |
+
input_text_color="white",
|
| 373 |
+
slider_color="hsl(24, 88%, 50%)", # Slider dolgu rengi
|
| 374 |
+
# Genel Gradio panelleri
|
| 375 |
+
block_background_fill="hsl(220, 10%, 15%)", # Daha koyu panel arka planı
|
| 376 |
+
border_color_accent="hsl(24, 88%, 50%)", # Turuncu vurgu kenarlığı
|
| 377 |
+
)
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
with gr.Blocks(theme=custom_theme, css="""
|
| 381 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
| 382 |
+
|
| 383 |
+
body {
|
| 384 |
+
background-color: #0a0a0a !important; /* Genel sayfa arka planı daha koyu */
|
| 385 |
+
color: #e0e0e0;
|
| 386 |
+
font-family: 'Inter', sans-serif;
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
.gradio-container {
|
| 390 |
+
max-width: 1300px !important;
|
| 391 |
+
font-family: 'Inter', sans-serif !important;
|
| 392 |
+
background-color: #0a0a0a !important;
|
| 393 |
+
border-radius: 16px;
|
| 394 |
+
overflow: hidden; /* Kenar yuvarlaklığı için */
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
/* Ana Başlık */
|
| 398 |
+
h1 {
|
| 399 |
+
color: #f39c12 !important; /* Turuncu */
|
| 400 |
+
text-align: center;
|
| 401 |
+
font-size: 40px !important;
|
| 402 |
+
font-weight: 700 !important;
|
| 403 |
+
margin-bottom: 5px !important;
|
| 404 |
+
text-shadow: 0 0 10px rgba(243, 156, 18, 0.3);
|
| 405 |
+
}
|
| 406 |
+
.gr-markdown p {
|
| 407 |
+
color: #d0d0d0;
|
| 408 |
+
text-align: center;
|
| 409 |
+
margin-top: 5px;
|
| 410 |
+
font-size: 16px;
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
/* Bölüm Başlıkları (Önerilen Filmler, Film Tercihleri vb.) */
|
| 414 |
+
h3 {
|
| 415 |
+
color: #f39c12 !important;
|
| 416 |
+
font-weight: 600 !important;
|
| 417 |
+
font-size: 24px !important;
|
| 418 |
+
margin-bottom: 20px !important;
|
| 419 |
+
border-bottom: 2px solid #2a2a2a; /* Alt çizgi */
|
| 420 |
+
padding-bottom: 10px;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
/* Ana Düğme */
|
| 424 |
+
.gr-button.gr-button-primary {
|
| 425 |
+
background-color: #f39c12 !important; /* Turuncu */
|
| 426 |
+
border: none !important;
|
| 427 |
+
font-weight: 700 !important;
|
| 428 |
+
font-size: 16px !important;
|
| 429 |
+
padding: 14px 32px !important;
|
| 430 |
+
border-radius: 8px !important;
|
| 431 |
+
box-shadow: 0 4px 12px rgba(243, 156, 18, 0.4) !important;
|
| 432 |
+
transition: all 0.3s ease !important;
|
| 433 |
+
text-transform: uppercase !important;
|
| 434 |
+
letter-spacing: 0.5px !important;
|
| 435 |
+
color: white !important;
|
| 436 |
+
}
|
| 437 |
+
.gr-button.gr-button-primary:hover {
|
| 438 |
+
background-color: #e67e22 !important; /* Daha koyu turuncu */
|
| 439 |
+
transform: translateY(-2px) !important;
|
| 440 |
+
box-shadow: 0 6px 16px rgba(243, 156, 18, 0.6) !important;
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
/* Tüm label başlıkları için turuncu arka plan */
|
| 444 |
+
.gr-form label > span:first-child,
|
| 445 |
+
.gr-box label > span:first-child,
|
| 446 |
+
.gr-input-label > span,
|
| 447 |
+
fieldset > legend > span {
|
| 448 |
+
background-color: #f39c12 !important; /* Turuncu */
|
| 449 |
+
color: white !important;
|
| 450 |
+
padding: 6px 12px !important;
|
| 451 |
+
border-radius: 6px !important;
|
| 452 |
+
font-weight: 600 !important;
|
| 453 |
+
display: inline-block !important;
|
| 454 |
+
margin-bottom: 10px !important;
|
| 455 |
+
box-shadow: 0 2px 6px rgba(243, 156, 18, 0.2);
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
/* Gradio Panelleri - Orijinal görseldeki gibi koyu, yuvarlak kenarlı */
|
| 459 |
+
.gr-panel {
|
| 460 |
+
background-color: #1c1c1c !important; /* Koyu gri */
|
| 461 |
+
border: 1px solid #2a2a2a !important;
|
| 462 |
+
border-radius: 12px !important;
|
| 463 |
+
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
| 464 |
+
padding: 20px;
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
/* Giriş Alanları (Checkbox, Dropdown, Slider, Textbox) */
|
| 468 |
+
.gr-checkbox-group, .gr-dropdown, .gr-slider, .gr-textbox {
|
| 469 |
+
background-color: #242424 !important; /* Bir tık daha açık gri */
|
| 470 |
+
border: 1px solid #3a3a3a !important;
|
| 471 |
+
border-radius: 8px !important;
|
| 472 |
+
padding: 15px !important;
|
| 473 |
+
margin-bottom: 15px; /* Aralarında boşluk */
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
/* Checkbox Group Label'ları */
|
| 477 |
+
.gr-checkbox-group label {
|
| 478 |
+
background-color: #333333 !important; /* Normal checkbox arka planı */
|
| 479 |
+
color: #ccc !important;
|
| 480 |
+
border: 1px solid #444444 !important;
|
| 481 |
+
border-radius: 5px !important;
|
| 482 |
+
padding: 8px 12px !important;
|
| 483 |
+
margin: 4px !important;
|
| 484 |
+
transition: all 0.2s ease !important;
|
| 485 |
+
font-weight: 500 !important;
|
| 486 |
+
}
|
| 487 |
+
.gr-checkbox-group input:checked + label {
|
| 488 |
+
background-color: #f39c12 !important; /* Seçili turuncu */
|
| 489 |
+
border-color: #f39c12 !important;
|
| 490 |
+
color: white !important;
|
| 491 |
+
font-weight: 600 !important;
|
| 492 |
+
}
|
| 493 |
+
.gr-checkbox-group label:hover {
|
| 494 |
+
background-color: #444444 !important;
|
| 495 |
border-color: #f39c12 !important;
|
|
|
|
| 496 |
}
|
| 497 |
+
|
| 498 |
+
/* Dropdown ve Textbox Giriş Alanları */
|
| 499 |
+
.gr-input, .gr-textbox textarea, .gr-dropdown-container {
|
| 500 |
+
background-color: #333333 !important;
|
| 501 |
+
color: #e0e0e0 !important;
|
| 502 |
+
border: 1px solid #444444 !important;
|
| 503 |
+
border-radius: 6px !important;
|
| 504 |
+
font-size: 15px !important;
|
| 505 |
+
padding: 8px 12px !important;
|
| 506 |
+
}
|
| 507 |
+
.gr-input:focus, .gr-textbox textarea:focus, .gr-dropdown-container:focus-within {
|
| 508 |
+
border-color: #f39c12 !important;
|
| 509 |
+
box-shadow: 0 0 0 2px rgba(243, 156, 18, 0.3) !important;
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
/* Dropdown Menüleri */
|
| 513 |
+
.gr-dropdown-menu {
|
| 514 |
+
background-color: #2a2a2a !important;
|
| 515 |
+
border: 1px solid #f39c12 !important;
|
| 516 |
+
border-radius: 8px !important;
|
| 517 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
| 518 |
+
}
|
| 519 |
+
.gr-dropdown-item {
|
| 520 |
+
color: #d0d0d0 !important;
|
| 521 |
+
padding: 10px 15px !important;
|
| 522 |
}
|
| 523 |
+
.gr-dropdown-item:hover {
|
| 524 |
+
background-color: #3a3a3a !important;
|
| 525 |
+
color: #f39c12 !important;
|
| 526 |
}
|
| 527 |
.gr-dropdown-item.selected {
|
| 528 |
background-color: #f39c12 !important;
|
| 529 |
+
color: white !important;
|
| 530 |
+
font-weight: 600 !important;
|
| 531 |
}
|
| 532 |
+
|
| 533 |
+
/* Slider */
|
| 534 |
+
input[type="range"]::-webkit-slider-runnable-track {
|
| 535 |
+
background: linear-gradient(to right, #f39c12 0%, #f39c12 var(--slider-value, 50%), #444444 var(--slider-value, 50%)) !important;
|
| 536 |
+
height: 8px !important;
|
| 537 |
+
border-radius: 4px !important;
|
| 538 |
+
}
|
| 539 |
+
input[type="range"]::-webkit-slider-thumb {
|
| 540 |
+
background-color: #f39c12 !important;
|
| 541 |
+
border: 2px solid white !important;
|
| 542 |
+
width: 20px !important;
|
| 543 |
+
height: 20px !important;
|
| 544 |
+
margin-top: -6px !important;
|
| 545 |
+
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
|
| 546 |
+
}
|
| 547 |
+
|
| 548 |
+
/* No Results Card */
|
| 549 |
+
.no-results-card {
|
| 550 |
+
text-align: center;
|
| 551 |
+
padding: 60px 20px;
|
| 552 |
+
background-color: #242424;
|
| 553 |
+
border-radius: 12px;
|
| 554 |
+
border: 2px dashed #4a4a4a;
|
| 555 |
+
margin-top: 20px;
|
| 556 |
+
}
|
| 557 |
+
.no-results-icon {
|
| 558 |
+
font-size: 64px;
|
| 559 |
+
margin-bottom: 20px;
|
| 560 |
+
color: #6a6a6a;
|
| 561 |
+
}
|
| 562 |
+
.no-results-title {
|
| 563 |
+
color: #f39c12;
|
| 564 |
+
margin-bottom: 10px;
|
| 565 |
+
font-size: 28px;
|
| 566 |
+
font-weight: 700;
|
| 567 |
}
|
| 568 |
+
.no-results-message {
|
| 569 |
+
color: #a0a0a0;
|
| 570 |
+
font-size: 16px;
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
/* Film Kartları Grid Düzeni */
|
| 574 |
+
.recommendations-grid {
|
| 575 |
+
display: grid;
|
| 576 |
+
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); /* Daha geniş kartlar */
|
| 577 |
+
gap: 25px; /* Kartlar arası boşluk */
|
| 578 |
+
padding: 20px 0;
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
/* Tek Film Kartı - Orijinal Görsel Stil */
|
| 582 |
+
.movie-card {
|
| 583 |
+
display: flex;
|
| 584 |
+
background-color: #242424; /* Koyu gri kart arka planı */
|
| 585 |
+
border-radius: 12px;
|
| 586 |
+
overflow: hidden;
|
| 587 |
+
box-shadow: 0 6px 20px rgba(0,0,0,0.5); /* Daha belirgin gölge */
|
| 588 |
+
border: 2px solid transparent; /* Varsayılan şeffaf kenarlık */
|
| 589 |
+
transition: all 0.3s ease;
|
| 590 |
+
position: relative; /* Orange border için */
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
.movie-card::before {
|
| 594 |
+
content: '';
|
| 595 |
+
position: absolute;
|
| 596 |
+
top: 0;
|
| 597 |
+
left: 0;
|
| 598 |
+
right: 0;
|
| 599 |
+
bottom: 0;
|
| 600 |
+
border: 2px solid transparent;
|
| 601 |
+
border-radius: 12px;
|
| 602 |
+
pointer-events: none;
|
| 603 |
+
transition: border-color 0.3s ease;
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
.movie-card:hover::before {
|
| 607 |
+
border-color: #f39c12; /* Hover'da turuncu kenarlık */
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
.movie-card-left-panel {
|
| 611 |
+
flex-shrink: 0;
|
| 612 |
+
width: 120px; /* Poster genişliği */
|
| 613 |
+
height: auto; /* İçeriğe göre yükseklik */
|
| 614 |
+
background-color: #1a1a1a; /* Daha koyu panel */
|
| 615 |
+
border-right: 1px solid #333; /* Hafif ayırıcı */
|
| 616 |
+
border-top-left-radius: 10px;
|
| 617 |
+
border-bottom-left-radius: 10px;
|
| 618 |
+
overflow: hidden;
|
| 619 |
+
padding: 5px; /* İç boşluk */
|
| 620 |
+
display: flex;
|
| 621 |
+
align-items: center;
|
| 622 |
+
justify-content: center;
|
| 623 |
+
}
|
| 624 |
+
|
| 625 |
+
/* Poster Yer Tutucu */
|
| 626 |
+
.poster-placeholder {
|
| 627 |
+
width: 100%;
|
| 628 |
+
height: 100%; /* Parent'ı kadar yer kapla */
|
| 629 |
+
display: flex;
|
| 630 |
+
flex-direction: column;
|
| 631 |
+
align-items: center;
|
| 632 |
+
justify-content: center;
|
| 633 |
+
text-align: center;
|
| 634 |
+
color: #a0a0a0;
|
| 635 |
+
font-size: 0.8em;
|
| 636 |
+
line-height: 1.2;
|
| 637 |
+
padding: 5px;
|
| 638 |
+
background-color: #1a1a1a; /* Orijinal görseldeki gibi koyu arka plan */
|
| 639 |
+
border-radius: 8px; /* Hafif yuvarlak köşeler */
|
| 640 |
+
}
|
| 641 |
+
.poster-placeholder .clapboard-icon {
|
| 642 |
+
color: #a0a0a0; /* Gri ikon */
|
| 643 |
+
width: 50px; /* Daha büyük ikon */
|
| 644 |
+
height: 50px;
|
| 645 |
+
margin-bottom: 5px;
|
| 646 |
+
}
|
| 647 |
+
.poster-placeholder .poster-title-placeholder {
|
| 648 |
+
font-weight: 600;
|
| 649 |
+
color: #d0d0d0; /* Daha açık başlık */
|
| 650 |
+
font-size: 0.9em;
|
| 651 |
+
margin-bottom: 3px;
|
| 652 |
+
}
|
| 653 |
+
.poster-placeholder .no-poster-text {
|
| 654 |
+
font-size: 0.7em;
|
| 655 |
+
color: #707070; /* Daha koyu "Poster Yok" metni */
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
.movie-card-info-panel {
|
| 659 |
+
flex-grow: 1;
|
| 660 |
+
padding: 15px 20px;
|
| 661 |
+
display: flex;
|
| 662 |
+
flex-direction: column;
|
| 663 |
+
justify-content: flex-start; /* Üste hizala */
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
.movie-title-display {
|
| 667 |
+
margin-top: 0;
|
| 668 |
+
margin-bottom: 8px;
|
| 669 |
+
color: white; /* Beyaz başlık */
|
| 670 |
+
font-size: 20px; /* Daha büyük başlık */
|
| 671 |
+
font-weight: 600;
|
| 672 |
+
line-height: 1.3;
|
| 673 |
+
}
|
| 674 |
+
|
| 675 |
+
.rating-and-votes {
|
| 676 |
+
display: flex;
|
| 677 |
+
align-items: center;
|
| 678 |
+
margin-bottom: 12px;
|
| 679 |
+
gap: 15px; /* Puan ve Oy arasında boşluk */
|
| 680 |
+
}
|
| 681 |
+
|
| 682 |
+
.rating-badge {
|
| 683 |
+
background-color: #28a745; /* Yeşil */
|
| 684 |
+
color: white;
|
| 685 |
+
padding: 5px 10px;
|
| 686 |
+
border-radius: 6px; /* Yuvarlatılmış köşeler */
|
| 687 |
+
font-weight: 600;
|
| 688 |
+
font-size: 15px;
|
| 689 |
+
display: inline-flex;
|
| 690 |
+
align-items: center;
|
| 691 |
+
gap: 5px;
|
| 692 |
+
box-shadow: 0 2px 8px rgba(40, 167, 69, 0.3);
|
| 693 |
+
}
|
| 694 |
+
.rating-badge span { /* Yıldız ikonu için */
|
| 695 |
+
font-size: 1em;
|
| 696 |
+
}
|
| 697 |
+
|
| 698 |
+
.votes-info {
|
| 699 |
+
display: flex;
|
| 700 |
+
align-items: center;
|
| 701 |
+
color: #bbb;
|
| 702 |
+
font-size: 14px;
|
| 703 |
+
gap: 5px;
|
| 704 |
+
background-color: #333; /* Arka plan rengi */
|
| 705 |
+
padding: 5px 10px;
|
| 706 |
+
border-radius: 6px;
|
| 707 |
+
border: 1px solid #444;
|
| 708 |
+
}
|
| 709 |
+
.votes-info svg {
|
| 710 |
+
color: #9e9e9e; /* İkon rengi */
|
| 711 |
+
fill: #9e9e9e;
|
| 712 |
+
stroke: #9e9e9e;
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
.movie-details-row {
|
| 716 |
+
margin-bottom: 6px;
|
| 717 |
+
display: flex;
|
| 718 |
+
flex-wrap: wrap; /* Uzun isimlerde alt satıra geçiş */
|
| 719 |
+
}
|
| 720 |
+
.detail-label {
|
| 721 |
+
color: #f39c12; /* Turuncu etiket */
|
| 722 |
+
font-weight: 500;
|
| 723 |
+
font-size: 14px;
|
| 724 |
+
margin-right: 8px;
|
| 725 |
+
white-space: nowrap; /* Etiket tek satırda kalsın */
|
| 726 |
+
}
|
| 727 |
+
.detail-value {
|
| 728 |
+
color: #d0d0d0;
|
| 729 |
+
font-size: 14px;
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
.genre-tags-container {
|
| 733 |
+
display: flex;
|
| 734 |
+
flex-wrap: wrap;
|
| 735 |
+
gap: 8px;
|
| 736 |
+
margin-top: 15px;
|
| 737 |
+
}
|
| 738 |
+
.genre-tag {
|
| 739 |
+
background-color: #f39c12; /* Turuncu */
|
| 740 |
+
color: white;
|
| 741 |
+
padding: 6px 12px;
|
| 742 |
+
border-radius: 20px; /* Yuvarlak hap şekli */
|
| 743 |
+
font-size: 13px;
|
| 744 |
+
font-weight: 500;
|
| 745 |
+
white-space: nowrap;
|
| 746 |
+
box-shadow: 0 2px 6px rgba(243, 156, 18, 0.2);
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
/* Scroll bar */
|
| 750 |
+
::-webkit-scrollbar {
|
| 751 |
+
width: 10px;
|
| 752 |
+
background-color: #1a1a1a;
|
| 753 |
+
}
|
| 754 |
+
|
| 755 |
+
::-webkit-scrollbar-thumb {
|
| 756 |
+
background-color: #f39c12;
|
| 757 |
+
border-radius: 5px;
|
| 758 |
+
}
|
| 759 |
+
|
| 760 |
+
::-webkit-scrollbar-thumb:hover {
|
| 761 |
+
background-color: #e67e22;
|
| 762 |
+
}
|
| 763 |
+
|
| 764 |
+
/* Örnekler bölümü */
|
| 765 |
+
.gr-examples {
|
| 766 |
+
background-color: #1c1c1c !important;
|
| 767 |
+
border: 1px solid #2a2a2a !important;
|
| 768 |
+
border-radius: 12px !important;
|
| 769 |
+
padding: 15px !important;
|
| 770 |
+
}
|
| 771 |
+
|
| 772 |
+
.gr-examples .gr-button {
|
| 773 |
+
background-color: #333333 !important;
|
| 774 |
+
color: #d0d0d0 !important;
|
| 775 |
+
border: 1px solid #444444 !important;
|
| 776 |
+
border-radius: 8px !important;
|
| 777 |
+
font-weight: 500;
|
| 778 |
+
padding: 8px 12px;
|
| 779 |
+
}
|
| 780 |
+
|
| 781 |
+
.gr-examples .gr-button:hover {
|
| 782 |
+
background-color: #444444 !important;
|
| 783 |
+
border-color: #f39c12 !important;
|
| 784 |
+
color: #f39c12 !important;
|
| 785 |
+
}
|
| 786 |
+
|
| 787 |
+
/* Nasıl Kullanılır bölümü */
|
| 788 |
+
.how-to-use-section {
|
| 789 |
+
background-color: #1c1c1c;
|
| 790 |
+
padding: 30px;
|
| 791 |
+
border-radius: 16px;
|
| 792 |
+
border: 1px solid #2a2a2a;
|
| 793 |
+
margin-top: 30px;
|
| 794 |
+
}
|
| 795 |
+
.how-to-use-grid {
|
| 796 |
+
display: grid;
|
| 797 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| 798 |
+
gap: 20px;
|
| 799 |
+
margin-top: 20px;
|
| 800 |
+
}
|
| 801 |
+
.how-to-use-step {
|
| 802 |
+
background-color: #242424;
|
| 803 |
+
padding: 20px;
|
| 804 |
+
border-radius: 12px;
|
| 805 |
+
border-left: 4px solid #f39c12; /* Turuncu sol kenarlık */
|
| 806 |
+
}
|
| 807 |
+
.how-to-use-step h4 {
|
| 808 |
+
color: #f39c12;
|
| 809 |
+
margin-top: 0;
|
| 810 |
+
font-size: 18px;
|
| 811 |
+
font-weight: 600;
|
| 812 |
+
}
|
| 813 |
+
.how-to-use-step p {
|
| 814 |
+
color: #d0d0d0;
|
| 815 |
+
font-size: 14px;
|
| 816 |
+
line-height: 1.6;
|
| 817 |
+
text-align: left;
|
| 818 |
+
}
|
| 819 |
+
.pro-tip-box {
|
| 820 |
+
margin-top: 30px;
|
| 821 |
+
padding: 20px;
|
| 822 |
+
background-color: rgba(243, 156, 18, 0.1);
|
| 823 |
+
border-radius: 12px;
|
| 824 |
+
border: 1px solid rgba(243, 156, 18, 0.3);
|
| 825 |
+
}
|
| 826 |
+
.pro-tip-box h4 {
|
| 827 |
+
color: #f39c12;
|
| 828 |
+
margin-top: 0;
|
| 829 |
+
display: flex;
|
| 830 |
+
align-items: center;
|
| 831 |
+
gap: 10px;
|
| 832 |
+
}
|
| 833 |
+
.pro-tip-box p {
|
| 834 |
+
color: #d0d0d0;
|
| 835 |
+
font-size: 14px;
|
| 836 |
+
line-height: 1.6;
|
| 837 |
+
margin: 0;
|
| 838 |
+
text-align: left;
|
| 839 |
+
}
|
| 840 |
+
.footer-info {
|
| 841 |
+
text-align: center;
|
| 842 |
+
margin-top: 30px;
|
| 843 |
+
padding: 20px;
|
| 844 |
+
color: #707070;
|
| 845 |
+
font-size: 13px;
|
| 846 |
+
}
|
| 847 |
+
|
| 848 |
""") as demo:
|
| 849 |
+
|
| 850 |
gr.Markdown(
|
| 851 |
"""
|
| 852 |
+
# 🎬 CineMate Film Öneri Sistemi
|
| 853 |
+
|
| 854 |
+
<p>Yapay Zeka Destekli Kişiselleştirilmiş Film Keşfi</p>
|
| 855 |
"""
|
| 856 |
)
|
| 857 |
|
|
|
|
| 858 |
with gr.Row():
|
| 859 |
+
with gr.Column(scale=3):
|
| 860 |
+
gr.Markdown("### 🎯 Sizin İçin Seçtiklerimiz")
|
| 861 |
+
output_html = gr.HTML(
|
| 862 |
+
label="Film Önerileri",
|
| 863 |
+
value="""
|
| 864 |
+
<div class="no-results-card">
|
| 865 |
+
<div class="no-results-icon">🎬</div>
|
| 866 |
+
<h2 class="no-results-title">Filmler Yükleniyor...</h2>
|
| 867 |
+
<p class="no-results-message">Tercihlerinizi seçin ve keşfetmeye başlayın!</p>
|
| 868 |
+
</div>
|
| 869 |
+
"""
|
| 870 |
+
)
|
| 871 |
|
| 872 |
with gr.Row():
|
| 873 |
with gr.Column(scale=1):
|
| 874 |
+
gr.Markdown("### 🎨 Film Tercihleriniz")
|
| 875 |
|
| 876 |
tags_input = gr.CheckboxGroup(
|
| 877 |
+
label="🎭 Film Türleri",
|
| 878 |
choices=all_tags,
|
| 879 |
+
value=['action', 'drama'],
|
| 880 |
interactive=True
|
| 881 |
)
|
| 882 |
|
| 883 |
directors_input = gr.Dropdown(
|
| 884 |
+
label="🎬 Favori Yönetmenler",
|
| 885 |
+
choices=all_directors,
|
| 886 |
multiselect=True,
|
| 887 |
allow_custom_value=False,
|
| 888 |
interactive=True
|
| 889 |
)
|
| 890 |
|
| 891 |
stars_input = gr.Dropdown(
|
| 892 |
+
label="⭐ Favori Oyuncular",
|
| 893 |
+
choices=all_stars,
|
| 894 |
multiselect=True,
|
| 895 |
allow_custom_value=False,
|
| 896 |
interactive=True
|
|
|
|
| 901 |
maximum=df_filtered['IMDb Rating'].max(),
|
| 902 |
step=0.1,
|
| 903 |
value=7.6,
|
| 904 |
+
label="📊 Minimum IMDb Puanı"
|
| 905 |
)
|
| 906 |
|
| 907 |
num_recommendations_slider = gr.Slider(
|
|
|
|
| 909 |
maximum=20,
|
| 910 |
step=1,
|
| 911 |
value=10,
|
| 912 |
+
label="🎯 Öneri Sayısı"
|
| 913 |
)
|
| 914 |
|
| 915 |
search_text_input = gr.Textbox(
|
| 916 |
+
label="🔍 Akıllı Arama (Yapay Zeka Destekli)",
|
| 917 |
+
placeholder="Örn: Uzayda geçen, aksiyon dolu bir macera...",
|
| 918 |
+
lines=2
|
| 919 |
)
|
| 920 |
|
| 921 |
+
recommend_btn = gr.Button(
|
| 922 |
+
"🚀 FİLMLERİ KEŞFET",
|
| 923 |
+
variant="primary",
|
| 924 |
+
size="lg"
|
| 925 |
+
)
|
| 926 |
|
| 927 |
recommend_btn.click(
|
| 928 |
fn=get_movie_recommendations,
|
|
|
|
| 930 |
outputs=output_html
|
| 931 |
)
|
| 932 |
|
| 933 |
+
gr.Markdown("### 💡 Hızlı Başlangıç Örnekleri")
|
| 934 |
gr.Examples(
|
| 935 |
examples=[
|
| 936 |
[['action'], [], [], 7.6, 5, ""],
|
|
|
|
| 943 |
inputs=[tags_input, directors_input, stars_input, min_imdb_rating_slider, num_recommendations_slider, search_text_input],
|
| 944 |
outputs=output_html,
|
| 945 |
fn=get_movie_recommendations,
|
| 946 |
+
label="Popüler Aramalar"
|
| 947 |
)
|
| 948 |
|
| 949 |
gr.Markdown(
|
| 950 |
"""
|
| 951 |
---
|
| 952 |
+
|
| 953 |
+
<div class='how-to-use-section'>
|
| 954 |
+
|
| 955 |
+
<h3>📖 Nasıl Kullanılır?</h3>
|
| 956 |
+
|
| 957 |
+
<div class='how-to-use-grid'>
|
| 958 |
+
|
| 959 |
+
<div class='how-to-use-step'>
|
| 960 |
+
<h4>🎭 1. Tür Seçin</h4>
|
| 961 |
+
<p>İlginizi çeken film türlerini seçin. Birden fazla tür kombinleyebilirsiniz.</p>
|
| 962 |
+
</div>
|
| 963 |
+
|
| 964 |
+
<div class='how-to-use-step'>
|
| 965 |
+
<h4>🎬 2. Filtre Uygulayın</h4>
|
| 966 |
+
<p>Favori yönetmen ve oyuncularınızı, minimum IMDb puanını ayarlayın.</p>
|
| 967 |
+
</div>
|
| 968 |
+
|
| 969 |
+
<div class='how-to-use-step'>
|
| 970 |
+
<h4>🔍 3. Akıllı Arama</h4>
|
| 971 |
+
<p>Yapay zeka destekli arama ile konu, tema veya film adı arayın.</p>
|
| 972 |
+
</div>
|
| 973 |
+
|
| 974 |
+
<div class='how-to-use-step'>
|
| 975 |
+
<h4>🚀 4. Keşfedin</h4>
|
| 976 |
+
<p>Butona tıklayın ve size özel seçilmiş filmleri keşfedin!</p>
|
| 977 |
+
</div>
|
| 978 |
+
|
| 979 |
+
</div>
|
| 980 |
+
|
| 981 |
+
<div class='pro-tip-box'>
|
| 982 |
+
<h4><span style='font-size: 24px;'>💡</span> Pro İpucu</h4>
|
| 983 |
+
<p>
|
| 984 |
+
Daha spesifik sonuçlar için birden fazla filtreyi kombine edin. Örneğin: "Christopher Nolan yönetmenliğinde, bilim kurgu türünde, 8.0 ve üzeri puanlı filmler" gibi.
|
| 985 |
+
</p>
|
| 986 |
+
</div>
|
| 987 |
+
|
| 988 |
+
</div>
|
| 989 |
+
|
| 990 |
+
<div class='footer-info'>
|
| 991 |
+
<p>🤖 Yapay Zeka ile Güçlendirilmiş | ⚡ Anlık Sonuçlar | 🎯 Kişiselleştirilmiş Öneriler</p>
|
| 992 |
+
</div>
|
| 993 |
"""
|
| 994 |
)
|
| 995 |
|
| 996 |
demo.launch(share=True)
|
| 997 |
+
print("\nADIM 5: Gradio Web Arayüzü Başlatıldı.")
|