Update app.py
Browse files
app.py
CHANGED
|
@@ -6,60 +6,26 @@ 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...")
|
| 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ı
|
| 19 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
-
df_filtered['Stars'
|
| 61 |
-
df_filtered['
|
| 62 |
-
df_filtered['
|
|
|
|
| 63 |
|
| 64 |
genre_mapping = {
|
| 65 |
'action': ['action', 'action epic', 'gun fu', 'one-person army action', 'car action', 'kung fu', 'martial arts', 'martial-arts'],
|
|
@@ -91,7 +57,7 @@ def map_to_main_genres(tag_list):
|
|
| 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):
|
|
@@ -99,35 +65,30 @@ def clean_and_split(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 |
|
| 106 |
-
|
| 107 |
df_filtered['Tags_cleaned_raw'] = df_filtered['Tags'].apply(clean_and_split)
|
| 108 |
df_filtered['Director_cleaned'] = df_filtered['Director'].apply(clean_and_split)
|
| 109 |
df_filtered['Stars_cleaned'] = df_filtered['Stars'].apply(clean_and_split)
|
| 110 |
-
|
| 111 |
df_filtered['Tags_cleaned'] = df_filtered['Tags_cleaned_raw'].apply(map_to_main_genres)
|
| 112 |
|
| 113 |
-
|
| 114 |
def convert_votes_to_numeric(votes_str):
|
| 115 |
if isinstance(votes_str, str):
|
| 116 |
-
|
| 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 |
-
|
| 129 |
-
|
| 130 |
-
df_filtered.dropna(subset=['Votes_numeric'], inplace=True)
|
| 131 |
|
| 132 |
df_filtered['Combined_Text'] = df_filtered['Title'] + ". " + \
|
| 133 |
df_filtered['Description'] + ". " + \
|
|
@@ -135,45 +96,26 @@ df_filtered['Combined_Text'] = df_filtered['Title'] + ". " + \
|
|
| 135 |
df_filtered['Director_cleaned'].apply(lambda x: ", ".join(x)) + ". " + \
|
| 136 |
df_filtered['Stars_cleaned'].apply(lambda x: ", ".join(x))
|
| 137 |
|
| 138 |
-
print("\nADIM 2: Veri Temizliği ve Ön İşleme Tamamlandı.")
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
# --- ADIM 3: NLP Modelini Yükleme ve ÖNCEDEN OLUŞTURULMUŞ Embedding'leri Yükleme ---
|
| 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 |
-
|
| 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ı.
|
| 156 |
-
|
| 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 |
-
|
| 172 |
|
| 173 |
-
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
-
# --- YENİ EKLENTİ: Popüler Yönetmen/Oyuncu Listelerini Oluşturma ---
|
| 177 |
director_popularity = {}
|
| 178 |
for index, row in df_filtered.iterrows():
|
| 179 |
for director in row['Director_cleaned']:
|
|
@@ -184,30 +126,17 @@ for index, row in df_filtered.iterrows():
|
|
| 184 |
for star in row['Stars_cleaned']:
|
| 185 |
star_popularity[star] = star_popularity.get(star, 0) + row['Votes_numeric']
|
| 186 |
|
| 187 |
-
# all_tags listesini, sadece eşlenmiş ana türlerden oluşturalım
|
| 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}")
|
| 200 |
-
print(f"Seçilen Oyuncular: {selected_stars}")
|
| 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:
|
|
@@ -225,21 +154,10 @@ def get_movie_recommendations(selected_tags, selected_directors, selected_stars,
|
|
| 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
|
| 229 |
try:
|
| 230 |
query_embedding = sentence_model.encode(search_text, convert_to_tensor=True)
|
| 231 |
-
|
| 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()
|
|
@@ -248,12 +166,9 @@ def get_movie_recommendations(selected_tags, selected_directors, selected_stars,
|
|
| 248 |
ascending=[False, False, False]
|
| 249 |
).reset_index(drop=True)
|
| 250 |
except Exception as e:
|
| 251 |
-
|
| 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
|
| 257 |
recommendations_df = recommendations_df.sort_values(
|
| 258 |
by=['IMDb Rating', 'Votes_numeric'],
|
| 259 |
ascending=[False, False]
|
|
@@ -263,625 +178,141 @@ def get_movie_recommendations(selected_tags, selected_directors, selected_stars,
|
|
| 263 |
|
| 264 |
if top_recommendations.empty:
|
| 265 |
return """
|
| 266 |
-
<div
|
| 267 |
-
<div
|
| 268 |
-
<h2
|
| 269 |
-
<p
|
| 270 |
</div>
|
| 271 |
"""
|
| 272 |
else:
|
| 273 |
-
html_output = "
|
| 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 |
-
|
| 278 |
|
| 279 |
similarity_info = ""
|
| 280 |
-
if 'Similarity_Score' in row and search_text
|
| 281 |
sim_percentage = int(row['Similarity_Score'] * 100)
|
| 282 |
similarity_info = f"""
|
| 283 |
-
<
|
| 284 |
-
|
| 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
|
| 315 |
-
<div
|
| 316 |
-
|
|
|
|
| 317 |
</div>
|
| 318 |
-
<div
|
| 319 |
-
<
|
| 320 |
-
|
| 321 |
-
<
|
| 322 |
-
|
| 323 |
-
<
|
| 324 |
-
<span>{row['Votes']}</span>
|
| 325 |
-
{similarity_info}
|
| 326 |
</div>
|
|
|
|
| 327 |
</div>
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
<
|
|
|
|
|
|
|
| 331 |
</div>
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
<span
|
|
|
|
| 335 |
</div>
|
| 336 |
-
|
| 337 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
</div>
|
| 339 |
</div>
|
| 340 |
</div>
|
| 341 |
"""
|
| 342 |
-
html_output += "</div>"
|
| 343 |
return html_output
|
| 344 |
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 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", # BU SATIR KALDIRILDI!
|
| 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 |
-
|
| 510 |
}
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 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-
|
| 520 |
-
color: #
|
| 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:
|
| 530 |
-
font-weight: 600 !important;
|
| 531 |
}
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 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 |
-
|
| 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 |
-
# 🎬
|
| 853 |
-
|
| 854 |
-
|
| 855 |
"""
|
| 856 |
)
|
| 857 |
|
| 858 |
with gr.Row():
|
| 859 |
-
with gr.Column(scale=
|
| 860 |
-
gr.Markdown("###
|
| 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("###
|
| 875 |
|
| 876 |
tags_input = gr.CheckboxGroup(
|
| 877 |
-
label="
|
| 878 |
choices=all_tags,
|
| 879 |
value=['action', 'drama'],
|
| 880 |
interactive=True
|
| 881 |
)
|
| 882 |
|
| 883 |
directors_input = gr.Dropdown(
|
| 884 |
-
label="
|
| 885 |
choices=all_directors,
|
| 886 |
multiselect=True,
|
| 887 |
allow_custom_value=False,
|
|
@@ -889,7 +320,7 @@ with gr.Blocks(theme=custom_theme, css="""
|
|
| 889 |
)
|
| 890 |
|
| 891 |
stars_input = gr.Dropdown(
|
| 892 |
-
label="
|
| 893 |
choices=all_stars,
|
| 894 |
multiselect=True,
|
| 895 |
allow_custom_value=False,
|
|
@@ -901,7 +332,7 @@ with gr.Blocks(theme=custom_theme, css="""
|
|
| 901 |
maximum=df_filtered['IMDb Rating'].max(),
|
| 902 |
step=0.1,
|
| 903 |
value=7.6,
|
| 904 |
-
label="
|
| 905 |
)
|
| 906 |
|
| 907 |
num_recommendations_slider = gr.Slider(
|
|
@@ -909,20 +340,15 @@ with gr.Blocks(theme=custom_theme, css="""
|
|
| 909 |
maximum=20,
|
| 910 |
step=1,
|
| 911 |
value=10,
|
| 912 |
-
label="
|
| 913 |
)
|
| 914 |
|
| 915 |
search_text_input = gr.Textbox(
|
| 916 |
-
label="
|
| 917 |
-
placeholder="
|
| 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,7 +356,6 @@ with gr.Blocks(theme=custom_theme, css="""
|
|
| 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,55 +368,20 @@ with gr.Blocks(theme=custom_theme, css="""
|
|
| 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="
|
| 947 |
)
|
| 948 |
|
| 949 |
gr.Markdown(
|
| 950 |
"""
|
| 951 |
---
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
|
| 955 |
-
|
| 956 |
-
|
| 957 |
-
|
| 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("
|
|
|
|
| 6 |
import torch
|
| 7 |
import re
|
| 8 |
|
| 9 |
+
print("--- Film Öneri Sistemi Başlatılıyor ---")
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
csv_file_name = "imdb-top-rated-movies-user-rated.csv"
|
| 12 |
file_path = os.path.join(".", csv_file_name)
|
| 13 |
|
| 14 |
if not os.path.exists(file_path):
|
| 15 |
+
print(f"HATA: '{csv_file_name}' dosyası bulunamadı.")
|
| 16 |
+
exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
+
try:
|
| 19 |
+
df = pd.read_csv(file_path)
|
| 20 |
+
print(f"'{csv_file_name}' başarıyla yüklendi. Toplam {len(df)} film bulundu.")
|
| 21 |
+
except Exception as e:
|
| 22 |
+
print(f"HATA: CSV dosyası yüklenirken hata oluştu: {e}")
|
| 23 |
+
exit(1)
|
| 24 |
|
| 25 |
+
df_filtered = df[['Title', 'IMDb Rating', 'Tags', 'Director', 'Stars', 'Votes', 'Description', 'Poster URL']].copy()
|
| 26 |
+
df_filtered['Stars'].fillna('', inplace=True)
|
| 27 |
+
df_filtered['Description'].fillna('', inplace=True)
|
| 28 |
+
df_filtered['Poster URL'].fillna('', inplace=True)
|
| 29 |
|
| 30 |
genre_mapping = {
|
| 31 |
'action': ['action', 'action epic', 'gun fu', 'one-person army action', 'car action', 'kung fu', 'martial arts', 'martial-arts'],
|
|
|
|
| 57 |
main_genres = set()
|
| 58 |
for tag in tag_list:
|
| 59 |
if tag in reverse_genre_map:
|
| 60 |
+
main_genres.add(reverse_genre_map[tag])
|
| 61 |
return list(main_genres)
|
| 62 |
|
| 63 |
def clean_and_split(text_series):
|
|
|
|
| 65 |
return []
|
| 66 |
item = str(text_series)
|
| 67 |
item = item.replace('"', '').replace("'", '').strip()
|
| 68 |
+
item = item.replace('sci, fi', 'sci-fi')
|
| 69 |
split_items = [s.strip().lower() for s in item.split(',') if s.strip()]
|
| 70 |
return split_items
|
| 71 |
|
|
|
|
| 72 |
df_filtered['Tags_cleaned_raw'] = df_filtered['Tags'].apply(clean_and_split)
|
| 73 |
df_filtered['Director_cleaned'] = df_filtered['Director'].apply(clean_and_split)
|
| 74 |
df_filtered['Stars_cleaned'] = df_filtered['Stars'].apply(clean_and_split)
|
|
|
|
| 75 |
df_filtered['Tags_cleaned'] = df_filtered['Tags_cleaned_raw'].apply(map_to_main_genres)
|
| 76 |
|
|
|
|
| 77 |
def convert_votes_to_numeric(votes_str):
|
| 78 |
if isinstance(votes_str, str):
|
| 79 |
+
votes_str = votes_str.replace(",", "")
|
|
|
|
| 80 |
if 'K' in votes_str:
|
| 81 |
return float(votes_str.replace('K', '')) * 1000
|
| 82 |
elif 'M' in votes_str:
|
| 83 |
return float(votes_str.replace('M', '')) * 1_000_000
|
| 84 |
+
try:
|
| 85 |
return float(votes_str)
|
| 86 |
except ValueError:
|
| 87 |
+
return np.nan
|
| 88 |
|
| 89 |
df_filtered['Votes_numeric'] = df_filtered['Votes'].apply(convert_votes_to_numeric)
|
| 90 |
+
df_filtered.drop('Votes', axis=1, inplace=True)
|
| 91 |
+
df_filtered.dropna(subset=['Votes_numeric'], inplace=True)
|
|
|
|
| 92 |
|
| 93 |
df_filtered['Combined_Text'] = df_filtered['Title'] + ". " + \
|
| 94 |
df_filtered['Description'] + ". " + \
|
|
|
|
| 96 |
df_filtered['Director_cleaned'].apply(lambda x: ", ".join(x)) + ". " + \
|
| 97 |
df_filtered['Stars_cleaned'].apply(lambda x: ", ".join(x))
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
model_name = 'sentence-transformers/all-MiniLM-L6-v2'
|
|
|
|
| 100 |
try:
|
| 101 |
sentence_model = SentenceTransformer(model_name)
|
| 102 |
print(f"'{model_name}' modeli başarıyla yüklendi.")
|
| 103 |
except Exception as e:
|
| 104 |
print(f"HATA: Sentence Transformer modeli yüklenirken hata oluştu: {e}")
|
| 105 |
+
exit(1)
|
| 106 |
|
| 107 |
embeddings_file_path = os.path.join(".", "film_embeddings.npy")
|
| 108 |
if not os.path.exists(embeddings_file_path):
|
| 109 |
+
print(f"HATA: '{embeddings_file_path}' dosyası bulunamadı.")
|
| 110 |
+
exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
+
try:
|
| 113 |
+
film_embeddings = torch.from_numpy(np.load(embeddings_file_path))
|
| 114 |
+
print("Film embedding'leri başarıyla yüklendi.")
|
| 115 |
+
except Exception as e:
|
| 116 |
+
print(f"HATA: film_embeddings.npy yüklenirken hata oluştu: {e}")
|
| 117 |
+
exit(1)
|
| 118 |
|
|
|
|
| 119 |
director_popularity = {}
|
| 120 |
for index, row in df_filtered.iterrows():
|
| 121 |
for director in row['Director_cleaned']:
|
|
|
|
| 126 |
for star in row['Stars_cleaned']:
|
| 127 |
star_popularity[star] = star_popularity.get(star, 0) + row['Votes_numeric']
|
| 128 |
|
|
|
|
| 129 |
all_tags = sorted(list(set([tag for sublist in df_filtered['Tags_cleaned'] for tag in sublist if tag in genre_mapping])))
|
|
|
|
|
|
|
| 130 |
all_directors = sorted(list(director_popularity.keys()), key=lambda d: director_popularity[d], reverse=True)
|
| 131 |
all_stars = sorted(list(star_popularity.keys()), key=lambda s: star_popularity[s], reverse=True)
|
|
|
|
| 132 |
|
| 133 |
def get_movie_recommendations(selected_tags, selected_directors, selected_stars, min_imdb_rating_slider, num_recommendations_slider, search_text=""):
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
selected_tags_list = list(selected_tags) if selected_tags else []
|
| 136 |
selected_directors_list = list(selected_directors) if selected_directors else []
|
| 137 |
selected_stars_list = list(selected_stars) if selected_stars else []
|
| 138 |
|
| 139 |
recommendations_df = df_filtered.copy()
|
|
|
|
| 140 |
recommendations_df = recommendations_df[recommendations_df['IMDb Rating'] >= min_imdb_rating_slider]
|
| 141 |
|
| 142 |
if selected_tags_list:
|
|
|
|
| 154 |
recommendations_df['Stars_cleaned'].apply(lambda x: any(star in x for star in selected_stars_list))
|
| 155 |
]
|
| 156 |
|
| 157 |
+
if search_text and len(recommendations_df) > 0:
|
| 158 |
try:
|
| 159 |
query_embedding = sentence_model.encode(search_text, convert_to_tensor=True)
|
| 160 |
+
filtered_indices = recommendations_df.index.tolist()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
current_film_embeddings = film_embeddings[filtered_indices]
|
| 162 |
cosine_scores = util.cos_sim(query_embedding, current_film_embeddings)[0]
|
| 163 |
recommendations_df['Similarity_Score'] = cosine_scores.cpu().numpy()
|
|
|
|
| 166 |
ascending=[False, False, False]
|
| 167 |
).reset_index(drop=True)
|
| 168 |
except Exception as e:
|
| 169 |
+
return "Benzerlik hesaplanırken bir hata oluştu."
|
|
|
|
|
|
|
|
|
|
| 170 |
|
| 171 |
+
if not search_text:
|
| 172 |
recommendations_df = recommendations_df.sort_values(
|
| 173 |
by=['IMDb Rating', 'Votes_numeric'],
|
| 174 |
ascending=[False, False]
|
|
|
|
| 178 |
|
| 179 |
if top_recommendations.empty:
|
| 180 |
return """
|
| 181 |
+
<div style="text-align: center; padding: 60px 20px; background: linear-gradient(135deg, #1a1a1a 0%, #2d1810 100%); border-radius: 16px; border: 2px solid #ff6b35;">
|
| 182 |
+
<div style="font-size: 64px; margin-bottom: 20px;">🎬</div>
|
| 183 |
+
<h2 style="color: #ff6b35; margin-bottom: 10px; font-size: 28px;">Sonuç Bulunamadı</h2>
|
| 184 |
+
<p style="color: #d4d4d4; font-size: 16px;">Seçtiğiniz kriterlere uygun film bulunamadı. Filtreleri değiştirerek tekrar deneyin.</p>
|
| 185 |
</div>
|
| 186 |
"""
|
| 187 |
else:
|
| 188 |
+
html_output = ""
|
| 189 |
for idx, row in top_recommendations.iterrows():
|
| 190 |
directors_str = ", ".join([d.title() for d in row['Director_cleaned']])
|
| 191 |
stars_str = ", ".join([s.title() for s in row['Stars_cleaned']])
|
| 192 |
+
tags_str = ", ".join([t.title() for t in row['Tags_cleaned']])
|
| 193 |
|
| 194 |
similarity_info = ""
|
| 195 |
+
if 'Similarity_Score' in row and search_text:
|
| 196 |
sim_percentage = int(row['Similarity_Score'] * 100)
|
| 197 |
similarity_info = f"""
|
| 198 |
+
<div style="display: inline-block; margin-left: 12px; padding: 6px 12px; background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%); border-radius: 8px;">
|
| 199 |
+
<span style="color: white; font-weight: 700; font-size: 13px;">🎯 Eşleşme: %{sim_percentage}</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
</div>
|
| 201 |
"""
|
| 202 |
+
|
| 203 |
+
rating_color = "#4ade80" if row['IMDb Rating'] >= 8.0 else "#fbbf24" if row['IMDb Rating'] >= 7.5 else "#fb923c"
|
| 204 |
+
|
| 205 |
+
poster_html = f"""
|
| 206 |
+
<div style="width: 160px; height: 240px; background: linear-gradient(135deg, #2a2a2a 0%, #1a1a1a 100%); border-radius: 12px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; color: #888; font-size: 0.9em; line-height: 1.4; padding: 15px; box-shadow: 0 4px 12px rgba(0,0,0,0.4); border: 2px solid #3a3a3a;">
|
| 207 |
+
<div style="font-size: 48px; margin-bottom: 15px;">🎬</div>
|
| 208 |
+
<span style="font-weight: 600; color: #ddd; margin-bottom: 8px;">{row['Title'][:40]}...</span>
|
| 209 |
+
<span style="color: #999; font-size: 0.85em;">Poster Yok</span>
|
| 210 |
+
</div>
|
| 211 |
+
"""
|
| 212 |
|
| 213 |
html_output += f"""
|
| 214 |
+
<div style="display: flex; margin-bottom: 24px; border: 2px solid #3a3a3a; padding: 20px; border-radius: 16px; background: linear-gradient(135deg, #1a1a1a 0%, #252525 100%); box-shadow: 0 8px 24px rgba(0,0,0,0.3); transition: all 0.3s ease; position: relative; overflow: hidden;">
|
| 215 |
+
<div style="position: absolute; top: 0; left: 0; width: 6px; height: 100%; background: linear-gradient(180deg, #ff6b35 0%, #ff8c42 100%);"></div>
|
| 216 |
+
<div style="flex-shrink: 0; margin-right: 24px; margin-left: 6px;">
|
| 217 |
+
{poster_html}
|
| 218 |
</div>
|
| 219 |
+
<div style="flex-grow: 1;">
|
| 220 |
+
<div style="margin-bottom: 12px;">
|
| 221 |
+
<h3 style="margin: 0; color: #ff8c42; font-size: 26px; font-weight: 700; display: inline-block;">{row['Title']}</h3>
|
| 222 |
+
<div style="display: inline-block; margin-left: 12px; background: {rating_color}; padding: 6px 14px; border-radius: 8px;">
|
| 223 |
+
<span style="font-size: 16px;">⭐</span>
|
| 224 |
+
<span style="color: #1a1a1a; font-weight: 700; font-size: 16px;">{row['IMDb Rating']:.1f}</span>
|
|
|
|
|
|
|
| 225 |
</div>
|
| 226 |
+
{similarity_info}
|
| 227 |
</div>
|
| 228 |
+
|
| 229 |
+
<div style="margin-bottom: 14px;">
|
| 230 |
+
<div style="display: inline-block; background: rgba(255, 107, 53, 0.15); padding: 8px 14px; border-radius: 8px; border: 1px solid rgba(255, 107, 53, 0.3);">
|
| 231 |
+
<span style="color: #ff8c42; font-weight: 600;">🗳️ {int(row['Votes_numeric']):,} Oy</span>
|
| 232 |
+
</div>
|
| 233 |
</div>
|
| 234 |
+
|
| 235 |
+
<div style="margin-bottom: 12px;">
|
| 236 |
+
<span style="color: #ff8c42; font-weight: 600; font-size: 15px;">🎬 Yönetmen:</span>
|
| 237 |
+
<span style="color: #d4d4d4; font-size: 15px; margin-left: 8px;">{directors_str if directors_str else 'Bilinmiyor'}</span>
|
| 238 |
</div>
|
| 239 |
+
|
| 240 |
+
<div style="margin-bottom: 14px;">
|
| 241 |
+
<span style="color: #ff8c42; font-weight: 600; font-size: 15px;">⭐ Oyuncular:</span>
|
| 242 |
+
<span style="color: #d4d4d4; font-size: 15px; margin-left: 8px;">{stars_str if stars_str else 'Bilinmiyor'}</span>
|
| 243 |
+
</div>
|
| 244 |
+
|
| 245 |
+
<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px;">
|
| 246 |
+
{''.join([f'<span style="background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%); color: white; padding: 6px 14px; border-radius: 20px; font-size: 13px; font-weight: 600; box-shadow: 0 2px 8px rgba(255, 107, 53, 0.3);">{tag.title()}</span>' for tag in row['Tags_cleaned']])}
|
| 247 |
</div>
|
| 248 |
</div>
|
| 249 |
</div>
|
| 250 |
"""
|
|
|
|
| 251 |
return html_output
|
| 252 |
|
| 253 |
+
with gr.Blocks(theme=gr.themes.Soft(), css="""
|
| 254 |
+
.gradio-container { max-width: 1200px !important; font-family: 'Segoe UI', sans-serif; }
|
| 255 |
+
h1 { color: #f39c12; text-align: center; }
|
| 256 |
+
h3 { color: #eee; }
|
| 257 |
+
.gr-button.gr-button-primary { background-color: #f39c12 !important; border-color: #f39c12 !important; }
|
| 258 |
+
.gr-button.gr-button-primary:hover { background-color: #e67e22 !important; border-color: #e67e22 !important; }
|
| 259 |
+
.gr-checkbox-group label { color: #ccc; }
|
| 260 |
+
.gr-dropdown, .gr-slider, .gr-textbox { background-color: #2c2c2c; color: #eee; border-color: #555; }
|
| 261 |
+
.gr-dropdown-item { color: #eee; }
|
| 262 |
+
.gr-checkbox-group input[type='checkbox']:checked + label {
|
| 263 |
+
background-color: #f39c12 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
border-color: #f39c12 !important;
|
| 265 |
+
color: #1a1a1a !important;
|
| 266 |
}
|
| 267 |
+
.gr-checkbox-group input[type='checkbox'] + label {
|
| 268 |
+
background-color: #333;
|
| 269 |
+
color: #eee;
|
| 270 |
+
border: 1px solid #555;
|
|
|
|
|
|
|
|
|
|
| 271 |
}
|
| 272 |
+
.gr-checkbox-group input[type='checkbox'] + label:hover {
|
| 273 |
+
background-color: #444;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
}
|
| 275 |
.gr-dropdown-item.selected {
|
| 276 |
background-color: #f39c12 !important;
|
| 277 |
+
color: #1a1a1a !important;
|
|
|
|
| 278 |
}
|
| 279 |
+
.gr-dropdown-item:hover {
|
| 280 |
+
background-color: #e67e22 !important;
|
| 281 |
+
color: #1a1a1a !important;
|
|
|
|
|
|
|
|
|
|
| 282 |
}
|
| 283 |
input[type="range"]::-webkit-slider-thumb {
|
| 284 |
background-color: #f39c12 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
}
|
| 286 |
+
input[type="range"]::-moz-range-thumb {
|
| 287 |
+
background-color: #f39c12 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
}
|
|
|
|
| 289 |
""") as demo:
|
|
|
|
| 290 |
gr.Markdown(
|
| 291 |
"""
|
| 292 |
+
# 🎬 Film Öneri Sistemi
|
| 293 |
+
Favori film özelliklerinizi seçin, yüksek IMDb puanına sahip filmleri keşfedin!
|
| 294 |
+
İstediğiniz bir film veya konu hakkında yazın, benzerlerini de bulalım.
|
| 295 |
"""
|
| 296 |
)
|
| 297 |
|
| 298 |
with gr.Row():
|
| 299 |
+
with gr.Column(scale=2):
|
| 300 |
+
gr.Markdown("### Önerilen Filmler:")
|
| 301 |
+
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>")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
|
| 303 |
with gr.Row():
|
| 304 |
with gr.Column(scale=1):
|
| 305 |
+
gr.Markdown("### Film Özelliklerini Seçin:")
|
| 306 |
|
| 307 |
tags_input = gr.CheckboxGroup(
|
| 308 |
+
label="Film Türleri",
|
| 309 |
choices=all_tags,
|
| 310 |
value=['action', 'drama'],
|
| 311 |
interactive=True
|
| 312 |
)
|
| 313 |
|
| 314 |
directors_input = gr.Dropdown(
|
| 315 |
+
label="Yönetmenler",
|
| 316 |
choices=all_directors,
|
| 317 |
multiselect=True,
|
| 318 |
allow_custom_value=False,
|
|
|
|
| 320 |
)
|
| 321 |
|
| 322 |
stars_input = gr.Dropdown(
|
| 323 |
+
label="Oyuncular",
|
| 324 |
choices=all_stars,
|
| 325 |
multiselect=True,
|
| 326 |
allow_custom_value=False,
|
|
|
|
| 332 |
maximum=df_filtered['IMDb Rating'].max(),
|
| 333 |
step=0.1,
|
| 334 |
value=7.6,
|
| 335 |
+
label="Minimum IMDb Puanı"
|
| 336 |
)
|
| 337 |
|
| 338 |
num_recommendations_slider = gr.Slider(
|
|
|
|
| 340 |
maximum=20,
|
| 341 |
step=1,
|
| 342 |
value=10,
|
| 343 |
+
label="Öneri Sayısı"
|
| 344 |
)
|
| 345 |
|
| 346 |
search_text_input = gr.Textbox(
|
| 347 |
+
label="Film Adı veya Konu Hakkında Ara (NLP Tabanlı Benzerlik)",
|
| 348 |
+
placeholder="Örneğin: Batman, uzay filmi, zamanda yolculuk..."
|
|
|
|
| 349 |
)
|
| 350 |
|
| 351 |
+
recommend_btn = gr.Button("🚀 Film Önerilerini Getir", variant="primary", size="lg")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
|
| 353 |
recommend_btn.click(
|
| 354 |
fn=get_movie_recommendations,
|
|
|
|
| 356 |
outputs=output_html
|
| 357 |
)
|
| 358 |
|
|
|
|
| 359 |
gr.Examples(
|
| 360 |
examples=[
|
| 361 |
[['action'], [], [], 7.6, 5, ""],
|
|
|
|
| 368 |
inputs=[tags_input, directors_input, stars_input, min_imdb_rating_slider, num_recommendations_slider, search_text_input],
|
| 369 |
outputs=output_html,
|
| 370 |
fn=get_movie_recommendations,
|
| 371 |
+
label="Örnek Önerileri Deneyin"
|
| 372 |
)
|
| 373 |
|
| 374 |
gr.Markdown(
|
| 375 |
"""
|
| 376 |
---
|
| 377 |
+
### ℹ️ Nasıl Kullanılır?
|
| 378 |
+
1. **Film Türleri, Yönetmenler ve Oyuncular** bölümlerinden istediğiniz filtreleri seçin (birden fazla seçim yapabilirsiniz).
|
| 379 |
+
2. **Minimum IMDb Puanı** ve **Öneri Sayısı** çubuklarını ayarlayın.
|
| 380 |
+
3. İsterseniz **"Film Adı veya Konu Hakkında Ara"** kutucuğuna bir film adı, konu veya anahtar kelime yazın.
|
| 381 |
+
4. **"🚀 Film Önerilerini Getir"** butonuna tıklayın.
|
| 382 |
+
5. Öneriler üst panelde görünecektir!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
"""
|
| 384 |
)
|
| 385 |
|
| 386 |
demo.launch(share=True)
|
| 387 |
+
print("Gradio Web Arayüzü Başlatıldı.")
|