File size: 13,121 Bytes
6f0bbbb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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

def plot_sentiment_pie(results, title="Reels Sentiment Analysis"):
    """
    Creates a pie chart from sentiment analysis results and returns the matplotlib figure.

    Args:
        results: Counter object or dict with 'positive', 'neutral', 'negative' keys
        title: Chart title

    Returns:
        Matplotlib Figure object, or None if no data.
    """
    labels = ['Positive', 'Neutral', 'Negative']
    sizes = [results.get('positive', 0), results.get('neutral', 0), results.get('negative', 0)]

    if sum(sizes) == 0:
        return None

    colors = ['#4CAF50', '#FFC107', '#F44336']
    explode = (0.05, 0, 0.05)

    fig, ax = plt.subplots(figsize=(8, 6))

    filtered_labels = [label for i, label in enumerate(labels) if sizes[i] > 0]
    filtered_sizes = [size for size in sizes if size > 0]
    filtered_colors = [colors[i] for i, size in enumerate(sizes) if size > 0]
    explode_map = {'Positive': 0.05, 'Neutral': 0, 'Negative': 0.05}
    filtered_explode = [explode_map.get(label, 0) for label in filtered_labels]

    ax.pie(filtered_sizes, explode=filtered_explode, labels=filtered_labels, colors=filtered_colors,
           autopct='%1.1f%%', shadow=True, startangle=140,
           textprops={'fontsize': 12, 'color': 'black'})

    ax.axis('equal')
    plt.title(title, fontsize=16, pad=20)
    plt.tight_layout()

    # Return the figure object instead of saving to bytes
    return fig

def plot_category_distribution(counter, title="Reels Content Distribution"):
    """
    Generate pie chart from category counts and returns the matplotlib figure.

    Args:
        counter: Counter object with category counts.
        title: Chart title.

    Returns:
        Matplotlib Figure object, or None if no data.
    """
    labels = []
    sizes = []

    total = sum(counter.values())
    if total == 0:
        return None

    threshold = total * 0.02
    other_count = 0

    sorted_categories = counter.most_common()

    for category, count in sorted_categories:
        if count >= threshold and category != "other":
            labels.append(category.replace('_', ' ').title())
            sizes.append(count)
        elif category == "other":
             other_count += count
        else:
            other_count += count

    if other_count > 0:
        labels.append("Other")
        sizes.append(other_count)

    if not sizes:
         return None

    fig, ax = plt.subplots(figsize=(10, 8))
    colors = plt.cm.viridis(np.linspace(0, 1, len(sizes)))

    ax.pie(
        sizes,
        labels=labels,
        autopct='%1.1f%%',
        startangle=140,
        colors=colors,
        wedgeprops={'edgecolor': 'white', 'linewidth': 1},
        textprops={'fontsize': 11, 'color': 'black'}
    )

    plt.title(title, pad=20, fontsize=15)
    plt.axis('equal')
    plt.tight_layout()

    # Return the figure object instead of saving to bytes
    return fig

# The rest of the Gradio Blocks interface definition and function linking
# should remain the same, as the analyze_reels_gradio function already
# calls these plotting functions and is intended to return the figure objects now.

# Global variables to maintain state across Gradio calls
global cl
global explore_reels_list
global sentiment_analyzer_instance
global content_classifier_pipeline

# Initialize sentiment analyzer if not already done (can be done here or lazily in analyze_reels_gradio)
# Doing it here ensures the model is loaded when this cell runs, potentially reducing latency on first analyze click.
try:
    sentiment_analyzer_instance = ReelSentimentAnalyzer()
    print("Sentiment Analyzer initialized.")
    # Optional: Train Hindi model if needed and data is available
    # sample_train_data = [...] # Define your training data
    # sentiment_analyzer_instance.train_hindi_model(sample_train_data)
except Exception as e:
    print(f"Error initializing Sentiment Analyzer globally: {e}")
    sentiment_analyzer_instance = None


# Initialize content classifier pipeline if not already done (can be done here or lazily)
try:
    print("Initializing Content Classifier Pipeline globally...")
    content_classifier_pipeline = pipeline(
        "zero-shot-classification",
        model="facebook/bart-large-mnli",
        device=0 if torch.cuda.is_available() else -1 # Use GPU if available
    )
    print("Content Classifier Pipeline Initialized.")
except Exception as e:
    print(f"Error initializing Content Classifier globally: {e}")
    content_classifier_pipeline = None


def login_gradio(username):
    """Gradio-compatible login function."""
    global cl
    try:
         PASSWORD = userdata.get('password')
    except Exception as e:
         return f"Error accessing password secret: {e}"


    if not PASSWORD:
        return "Error: Instagram password not found in Colab secrets."

    cl = Client()

    try:
        cl.login(username, PASSWORD)
        return f"Successfully logged in as {username}"
    except Exception as e:
        cl = None # Ensure cl is None on failure
        return f"Error during login: {e}"

def fetch_reels_gradio():
    """Gradio-compatible function to fetch explore reels."""
    global cl
    global explore_reels_list

    if cl is None:
        explore_reels_list = [] # Ensure list is empty on failure
        return "Error: Not logged in. Please log in first."

    try:
        # Fetch a limited number of reels for demonstration purposes
        # You might want to make this number configurable later
        fetched_reels = cl.explore_reels()[:100] # Fetch up to 100 for analysis
        explore_reels_list = fetched_reels
        if explore_reels_list:
            return f"Successfully fetched {len(explore_reels_list)} explore reels."
        else:
            explore_reels_list = [] # Ensure it's an empty list
            return "Fetched 0 explore reels."
    except Exception as e:
        explore_reels_list = [] # Ensure it's an empty list on error
        return f"Error fetching explore reels: {e}"


def analyze_reels_gradio(max_to_analyze):
    """Gradio-compatible function to analyze fetched reels and generate plots."""
    global explore_reels_list
    global sentiment_analyzer_instance
    global content_classifier_pipeline

    if not explore_reels_list:
        # Return None for plots if no reels
        return "Error: No reels fetched yet. Please fetch reels first.", None, None

    # Ensure max_to_analyze does not exceed the number of fetched reels
    num_reels_to_process = min(max_to_analyze, len(explore_reels_list))
    reels_to_analyze = explore_reels_list[:num_reels_to_process]

    if not reels_to_analyze:
         return "Error: No reels available to analyze.", None, None


    # Check if analyzers are initialized
    if sentiment_analyzer_instance is None:
         return "Error: Sentiment Analyzer not initialized.", None, None
    if content_classifier_pipeline is None:
         return "Error: Content Classifier not initialized.", None, None


    analysis_status_messages = []
    sentiment_plot_figure = None # Changed to figure
    content_plot_figure = None   # Changed to figure

    # Perform Sentiment Analysis
    try:
        analysis_status_messages.append(f"Starting Sentiment Analysis for {len(reels_to_analyze)} reels...")
        sentiment_results, detailed_sentiment_results = sentiment_analyzer_instance.analyze_reels(
            reels_to_analyze,
            max_to_analyze=len(reels_to_analyze) # Pass the actual number being processed
        )
        # Call the updated plotting function that returns a figure
        sentiment_plot_figure = plot_sentiment_pie(sentiment_results, title=f"Sentiment of {len(reels_to_analyze)} Instagram Reels")
        analysis_status_messages.append("Sentiment Analysis Complete.")
    except Exception as e:
        analysis_status_messages.append(f"Error during Sentiment Analysis: {e}")
        sentiment_plot_figure = None # Ensure plot is None on error


    # Perform Content Categorization
    try:
        analysis_status_messages.append(f"Starting Content Categorization for {len(reels_to_analyze)} reels...")
        category_counts = Counter()
        # Re-implement content analysis slightly to fit this flow using the global pipeline
        print(f"\n⏳ Analyzing content for {len(reels_to_analyze)} reels...")
        for i, reel in enumerate(reels_to_analyze, 1):
            caption = getattr(reel, 'caption_text', '') or getattr(reel, 'caption', '') or ''
            # Use the global classifier pipeline
            category, details = classify_reel_content(caption)
            category_counts[category] += 1

        print("\n✅ Content Analysis complete!")
        print("\n📊 Category Counts:")
        for category, count in category_counts.most_common():
            print(f"- {category.replace('_', ' ').title()}: {count}")

        # Call the updated plotting function that returns a figure
        content_plot_figure = plot_category_distribution(category_counts)
        analysis_status_messages.append("Content Categorization Complete.")

    except Exception as e:
        analysis_status_messages.append(f"Error during Content Analysis: {e}")
        content_plot_figure = None # Ensure plot is None on error


    final_status_message = "\n".join(analysis_status_messages)
    # Return the figure objects
    return final_status_message, sentiment_plot_figure, content_plot_figure

# --- Gradio Blocks Interface ---
with gr.Blocks() as demo:
    gr.Markdown("# Instagram Reels Analysis")
    with gr.Row():
        username_input = gr.Textbox(label="Instagram Username")
        login_button = gr.Button("Login")
    login_status_output = gr.Label(label="Login Status")

    with gr.Row():
        fetch_button = gr.Button("Fetch Reels")
    fetch_status_output = gr.Label(label="Fetch Status")

    with gr.Row():
        max_reels_input = gr.Slider(minimum=1, maximum=100, value=10, step=1, label="Number of Reels to Analyze")
        analyze_button = gr.Button("Analyze Reels")

    analyze_status_output = gr.Label(label="Analysis Status")

    with gr.Row():
        # Sentiment Analysis Outputs
        with gr.Column():
            gr.Markdown("## Sentiment Analysis")
            sentiment_plot_output = gr.Plot(label="Sentiment Distribution")

        # Content Analysis Outputs
        with gr.Column():
            gr.Markdown("## Content Analysis")
            content_plot_output = gr.Plot(label="Content Distribution")


    # Link login and fetch buttons (assuming login_gradio and fetch_reels_gradio are defined)
    # Redefine login_gradio and fetch_reels_gradio here within the Blocks context
    # to ensure they are linked correctly, even though they were defined above.
    # This is a common pattern in Gradio Blocks.

    def login_gradio_blocks(username):
        """Gradio-compatible login function for Blocks."""
        global cl
        try:
             PASSWORD = userdata.get('password')
        except Exception as e:
             return f"Error accessing password secret: {e}"


        if not PASSWORD:
            return "Error: Instagram password not found in Colab secrets."

        cl = Client()

        try:
            cl.login(username, PASSWORD)
            return f"Successfully logged in as {username}"
        except Exception as e:
            cl = None # Ensure cl is None on failure
            return f"Error during login: {e}"

    def fetch_reels_gradio_blocks():
        """Gradio-compatible function to fetch explore reels for Blocks."""
        global cl
        global explore_reels_list

        if cl is None:
            explore_reels_list = [] # Ensure list is empty on failure
            return "Error: Not logged in. Please log in first."

        try:
            # Fetch a limited number of reels for demonstration purposes
            # You might want to make this number configurable later
            fetched_reels = cl.explore_reels()[:100] # Fetch up to 100 for analysis
            explore_reels_list = fetched_reels
            if explore_reels_list:
                return f"Successfully fetched {len(explore_reels_list)} explore reels."
            else:
                explore_reels_list = [] # Ensure it's an empty list
                return "Fetched 0 explore reels."
        except Exception as e:
            explore_reels_list = [] # Ensure it's an empty list on error
            return f"Error fetching explore reels: {e}"


    login_button.click(
        fn=login_gradio_blocks,
        inputs=username_input,
        outputs=login_status_output
    )

    fetch_button.click(
        fn=fetch_reels_gradio_blocks,
        inputs=None, # No direct inputs needed for fetching
        outputs=fetch_status_output
    )

    # Link the Analyze button to the analysis function
    analyze_button.click(
        fn=analyze_reels_gradio,
        inputs=max_reels_input, # Input is the slider value
        outputs=[analyze_status_output, sentiment_plot_output, content_plot_output] # Outputs are status and the two plots
    )

# The demo is now fully defined. It can be launched in the next step.
# demo.launch()