File size: 19,168 Bytes
e2119ad
ede6adc
 
23dfb62
f94b6a2
 
23dfb62
 
2f1a64f
23dfb62
f94b6a2
 
23dfb62
 
2f1a64f
 
23dfb62
2f1a64f
 
 
 
 
 
77e28c5
2f1a64f
 
 
 
23dfb62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2f1a64f
23dfb62
 
 
 
 
 
 
2f1a64f
23dfb62
 
 
 
 
 
 
 
 
 
2f1a64f
23dfb62
 
 
 
2f1a64f
23dfb62
 
2f1a64f
23dfb62
 
2f1a64f
 
23dfb62
 
 
 
 
 
 
 
 
77e28c5
23dfb62
 
 
2f1a64f
23dfb62
 
 
2f1a64f
 
77e28c5
2f1a64f
 
 
 
 
 
77e28c5
23dfb62
 
 
 
 
 
 
 
 
 
 
 
 
77e28c5
23dfb62
92b5f25
23dfb62
 
 
 
 
 
92b5f25
23dfb62
 
 
 
92b5f25
23dfb62
 
 
 
92b5f25
23dfb62
 
 
 
92b5f25
2f1a64f
77e28c5
 
2f1a64f
77e28c5
 
92b5f25
77e28c5
92b5f25
 
77e28c5
 
2f1a64f
23dfb62
2f1a64f
23dfb62
92b5f25
23dfb62
 
92b5f25
23dfb62
92b5f25
23dfb62
1a6868a
2f1a64f
 
 
 
1a6868a
 
23dfb62
2f1a64f
23dfb62
 
 
2f1a64f
23dfb62
 
2f1a64f
1a6868a
 
2f1a64f
 
1a6868a
 
2f1a64f
 
 
 
 
 
 
 
 
 
23dfb62
 
2f1a64f
 
 
 
23dfb62
2f1a64f
 
 
 
 
 
1a6868a
2f1a64f
1a6868a
2f1a64f
 
 
 
 
1a6868a
2f1a64f
 
 
 
1a6868a
2f1a64f
 
 
 
 
 
 
 
1a6868a
23dfb62
 
 
 
 
2f1a64f
 
 
 
 
 
 
 
 
 
 
1a6868a
2f1a64f
1a6868a
2f1a64f
 
 
 
1a6868a
2f1a64f
 
23dfb62
77e28c5
f94b6a2
2f1a64f
23dfb62
2f1a64f
 
 
1a6868a
 
 
 
2f1a64f
 
1a6868a
23dfb62
 
 
2f1a64f
 
 
23dfb62
 
f94b6a2
23dfb62
2f1a64f
 
 
f94b6a2
23dfb62
 
2f1a64f
f94b6a2
23dfb62
2f1a64f
f94b6a2
1a6868a
23dfb62
 
f94b6a2
23dfb62
2f1a64f
1a6868a
23dfb62
 
 
 
 
 
2f1a64f
1a6868a
23dfb62
 
 
 
f94b6a2
23dfb62
f94b6a2
 
 
 
2f1a64f
23dfb62
 
 
f94b6a2
 
 
 
2f1a64f
23dfb62
 
 
2f1a64f
 
23dfb62
f94b6a2
2f1a64f
f94b6a2
23dfb62
 
 
 
 
 
 
 
f94b6a2
 
 
 
 
 
23dfb62
 
f94b6a2
23dfb62
2f1a64f
23dfb62
f94b6a2
23dfb62
 
 
2f1a64f
 
 
 
 
 
23dfb62
 
 
77e28c5
2f1a64f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
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ı.")