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