File size: 48,951 Bytes
afeb9fc
758c54f
347cda9
 
758c54f
afeb9fc
31020d6
afeb9fc
 
347cda9
 
afeb9fc
347cda9
 
afeb9fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52a4a68
afeb9fc
52a4a68
afeb9fc
347cda9
 
 
afeb9fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52a4a68
347cda9
afeb9fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347cda9
afeb9fc
 
 
 
 
 
 
 
 
347cda9
 
52a4a68
afeb9fc
 
 
 
 
 
 
31020d6
afeb9fc
 
 
 
 
 
 
 
347cda9
afeb9fc
 
347cda9
 
afeb9fc
347cda9
afeb9fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31020d6
afeb9fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347cda9
d02f7ee
347cda9
afeb9fc
 
 
 
52a4a68
347cda9
afeb9fc
 
 
52a4a68
 
347cda9
52a4a68
347cda9
 
 
afeb9fc
347cda9
 
 
 
afeb9fc
 
 
347cda9
52a4a68
 
afeb9fc
 
 
 
 
 
347cda9
afeb9fc
 
347cda9
52a4a68
 
 
347cda9
afeb9fc
347cda9
 
afeb9fc
 
347cda9
 
afeb9fc
 
 
52a4a68
347cda9
52a4a68
347cda9
afeb9fc
 
 
 
 
 
 
 
347cda9
 
52a4a68
347cda9
afeb9fc
 
 
52a4a68
afeb9fc
 
 
 
 
 
 
 
 
 
 
347cda9
52a4a68
347cda9
 
afeb9fc
 
52a4a68
afeb9fc
 
52a4a68
afeb9fc
 
 
 
 
 
347cda9
 
 
 
afeb9fc
347cda9
afeb9fc
 
347cda9
 
 
afeb9fc
 
347cda9
 
 
afeb9fc
 
347cda9
 
 
afeb9fc
347cda9
 
afeb9fc
347cda9
afeb9fc
347cda9
 
 
 
 
afeb9fc
 
347cda9
 
 
afeb9fc
347cda9
afeb9fc
 
 
 
 
 
 
347cda9
 
afeb9fc
347cda9
 
 
 
52a4a68
afeb9fc
 
 
 
 
 
 
 
 
 
 
52a4a68
afeb9fc
 
 
 
 
 
 
 
347cda9
254a789
afeb9fc
 
 
 
 
347cda9
31020d6
347cda9
bbbb9a3
afeb9fc
 
 
347cda9
afeb9fc
 
 
347cda9
afeb9fc
 
 
347cda9
31020d6
afeb9fc
 
 
 
 
 
 
 
347cda9
 
afeb9fc
52a4a68
afeb9fc
 
 
 
 
52a4a68
afeb9fc
 
 
 
 
 
 
 
 
 
 
 
347cda9
afeb9fc
 
 
 
 
 
 
 
 
347cda9
52a4a68
347cda9
 
afeb9fc
52a4a68
afeb9fc
 
52a4a68
afeb9fc
347cda9
afeb9fc
 
 
 
 
 
 
347cda9
afeb9fc
 
347cda9
afeb9fc
 
347cda9
afeb9fc
 
 
347cda9
afeb9fc
347cda9
afeb9fc
 
52a4a68
afeb9fc
 
 
cbbafef
afeb9fc
 
 
 
 
 
cbbafef
afeb9fc
52a4a68
afeb9fc
 
 
52a4a68
afeb9fc
 
 
 
347cda9
afeb9fc
347cda9
afeb9fc
 
 
 
347cda9
afeb9fc
 
 
 
 
 
 
 
347cda9
afeb9fc
 
 
 
 
 
 
 
 
 
 
 
 
347cda9
afeb9fc
 
 
 
 
 
 
 
 
347cda9
afeb9fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347cda9
afeb9fc
 
 
 
347cda9
afeb9fc
347cda9
afeb9fc
 
 
347cda9
afeb9fc
 
347cda9
afeb9fc
347cda9
 
afeb9fc
347cda9
 
afeb9fc
 
347cda9
afeb9fc
 
 
 
 
347cda9
 
cbbafef
afeb9fc
 
 
 
 
347cda9
afeb9fc
 
 
347cda9
c379d0e
52a4a68
347cda9
afeb9fc
 
 
c379d0e
347cda9
afeb9fc
 
347cda9
52a4a68
347cda9
 
afeb9fc
 
 
 
 
347cda9
afeb9fc
 
 
 
 
 
347cda9
afeb9fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347cda9
afeb9fc
 
 
 
 
 
 
 
 
 
 
 
 
 
347cda9
afeb9fc
 
 
 
 
347cda9
afeb9fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347cda9
 
afeb9fc
 
 
 
 
 
 
 
 
347cda9
afeb9fc
347cda9
 
afeb9fc
 
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
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
import gradio as gr
import moviepy.editor as mp
from moviepy.video.fx.all import crop # For zoom effect if needed for crop
from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageOps
import numpy as np
import os
import gdown
import requests
from urllib.parse import urlparse # Used in user's download_from_google_drive
import tempfile
import re
import time
import traceback
INSTA_ONEAPI_KEY=os.environ.get('INSTAONEAPIKEY')
# --- Helper Functions (Prioritizing from your pasted-text.txt) ---

def download_from_google_drive(url):
    """Download file from Google Drive URL with improved parsing (from user's file)"""
    try:
        if not url or not isinstance(url, str):
            raise ValueError("Invalid URL provided")
            
        print(f"Processing Google Drive URL: {url}")
        
        url = url.strip()
        
        if 'drive.google.com' in url:
            patterns = [
                r'/file/d/([a-zA-Z0-9-_]+)',
                r'id=([a-zA-Z0-9-_]+)',
                r'/d/([a-zA-Z0-9-_]+)',
                r'file/d/([a-zA-Z0-9-_]+)/view',
                r'open\?id=([a-zA-Z0-9-_]+)'
            ]
            
            file_id = None
            for pattern in patterns:
                match = re.search(pattern, url)
                if match:
                    file_id = match.group(1)
                    break
            
            if not file_id:
                if '/file/d/' in url:
                    try:
                        file_id = url.split('/file/d/')[1].split('/')[0]
                    except IndexError: pass
                elif 'id=' in url:
                    try:
                        file_id = url.split('id=')[1].split('&')[0]
                    except IndexError: pass
            
            if not file_id:
                raise ValueError("Could not extract file ID from Google Drive URL")
            
            print(f"Extracted file ID: {file_id}")
            
            download_url = f"https://drive.google.com/uc?export=download&id={file_id}"
            
            # Using NamedTemporaryFile for safer temp file creation
            temp_file = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False)
            output_path = temp_file.name
            temp_file.close() # Close it so gdown can write to it

            try:
                gdown.download(download_url, output_path, quiet=False)
                if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
                    print(f"Successfully downloaded from Google Drive to {output_path}")
                    return output_path
                else:
                    # Try alternative download method
                    print("Initial gdown failed or file empty, trying alternative.")
                    if os.path.exists(output_path): os.remove(output_path) # Clean up before retry
                    gdown.download(f"https://drive.google.com/file/d/{file_id}/view?usp=sharing", 
                                 output_path, quiet=False, fuzzy=True)
                    if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
                        print(f"Successfully downloaded (alternative) from Google Drive to {output_path}")
                        return output_path
                    else:
                         raise Exception("Alternative gdown download also failed or resulted in empty file.")
            except Exception as e:
                print(f"gdown failed for {url}: {e}")
                # Fallback to direct requests
                try:
                    print(f"Attempting direct requests fallback for {download_url}")
                    response = requests.get(download_url, stream=True, timeout=60)
                    response.raise_for_status()
                    with open(output_path, 'wb') as f:
                        for chunk in response.iter_content(chunk_size=8192):
                            f.write(chunk)
                    if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
                        print(f"Successfully downloaded (direct requests) to {output_path}")
                        return output_path
                    else:
                        raise Exception("Direct requests download failed or resulted in empty file.")
                except Exception as e2:
                    print(f"Direct download also failed for {url}: {e2}")
                    if os.path.exists(output_path): os.remove(output_path)
                    raise e2 # Re-raise the error from direct download
        else: # Assumed direct URL if not Google Drive
            print(f"Processing as direct URL: {url}")
            temp_file = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False)
            output_path = temp_file.name
            temp_file.close()
            response = requests.get(url, stream=True, timeout=60)
            response.raise_for_status()
            with open(output_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    f.write(chunk)
            if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
                print(f"Successfully downloaded (direct URL) to {output_path}")
                return output_path
            else:
                if os.path.exists(output_path): os.remove(output_path)
                raise Exception("Direct URL download failed or resulted in empty file.")
            
    except Exception as e:
        print(f"Error downloading file from URL '{url}': {e}")
        traceback.print_exc()
        # Clean up temp file if it exists and error occurred
        if 'output_path' in locals() and os.path.exists(output_path):
            try: os.remove(output_path)
            except: pass
        return None


def download_audio_file(url):
    """Download audio file from URL (from user's file)"""
    try:
        if not url or not url.strip():
            return None
            
        url = url.strip()
        print(f"Downloading audio from: {url}")
        
        # Determine file extension
        parsed_url = urlparse(url)
        path = parsed_url.path
        base, ext = os.path.splitext(path)
        
        if ext.lower() in ['.mp3', '.wav', '.m4a', '.aac']:
            suffix = ext
        else:
            # Check Content-Type header if no obvious extension
            try:
                head_resp = requests.head(url, timeout=10, allow_redirects=True)
                content_type = head_resp.headers.get('content-type', '').lower()
                if 'audio/mpeg' in content_type: suffix = '.mp3'
                elif 'audio/wav' in content_type: suffix = '.wav'
                elif 'audio/aac' in content_type: suffix = '.aac'
                elif 'audio/mp4' in content_type: suffix = '.m4a' # Often for AAC in MP4 container
                else: suffix = '.mp3' # Default
            except Exception:
                suffix = '.mp3' # Default on error

        temp_audio_file = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
        output_path = temp_audio_file.name
        temp_audio_file.close()
        
        response = requests.get(url, stream=True, timeout=60)
        response.raise_for_status()
        
        with open(output_path, 'wb') as f:
            for chunk in response.iter_content(chunk_size=8192):
                f.write(chunk)
        
        if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
            print(f"Successfully downloaded audio to {output_path}")
            return output_path
        else:
            if os.path.exists(output_path): os.remove(output_path)
            print(f"Failed to download audio or file is empty: {url}")
            return None

    except Exception as e:
        print(f"Error downloading audio from '{url}': {e}")
        traceback.print_exc()
        if 'output_path' in locals() and os.path.exists(output_path):
            try: os.remove(output_path)
            except: pass
        return None


def hex_to_rgb(hex_color):
    """Convert hex color to RGB tuple (from user's file)"""
    hex_color = hex_color.lstrip('#')
    if len(hex_color) == 3: # Handle short hex like #RGB
        hex_color = "".join([c*2 for c in hex_color])
    if len(hex_color) == 6:
        return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
    print(f"Warning: Invalid hex color format '{hex_color}'. Defaulting to white.")
    return (255, 255, 255) # Default to white


def calculate_font_size(text, max_width, max_height, custom_size=None):
    """Calculate optimal font size based on text length and video dimensions (from user's file)"""
    if custom_size and custom_size > 0:
        return int(custom_size) # Ensure integer
        
    if not text: return 24 # Default for empty text
    char_count = len(text)
    
    if char_count < 50:
        base_size = max_width // 15
    elif char_count < 100:
        base_size = max_width // 20
    elif char_count < 200:
        base_size = max_width // 25
    else:
        base_size = max_width // 30
    
    font_size = max(24, min(base_size, max_height // 8)) # Ensure min 24, max relative to height
    return int(font_size)


def detect_rtl_text(text):
    """Detect if text contains RTL characters (Arabic, Persian, Hebrew, etc.) (from user's file)"""
    if not text: return False
    rtl_chars_specific = { # Specific common Persian/Arabic letters for quick check
        'ا', 'ب', 'پ', 'ت', 'ث', 'ج', 'چ', 'ح', 'خ', 'د', 'ذ', 'ر', 'ز', 'ژ', 'س', 'ش',
        'ص', 'ض', 'ط', 'ظ', 'ع', 'غ', 'ف', 'ق', 'ک', 'گ', 'ل', 'م', 'ن', 'و', 'ه', 'ی',
        'ء', 'آ', 'اً', 'ة', 'ی'
    }
    # General Unicode ranges for RTL scripts
    # Arabic: U+0600 to U+06FF
    # Hebrew: U+0590 to U+05FF
    # Syriac: U+0700 to U+074F
    # Thaana: U+0780 to U+07BF
    # N'Ko: U+07C0 to U+07FF
    # Arabic Supplement: U+0750 to U+077F
    # Arabic Extended-A: U+08A0 to U+08FF
    for char in text:
        if char in rtl_chars_specific:
            return True
        if '\u0600' <= char <= '\u06FF' or \
           '\u0590' <= char <= '\u05FF' or \
           '\u0700' <= char <= '\u074F' or \
           '\u0780' <= char <= '\u07BF' or \
           '\u07C0' <= char <= '\u07FF' or \
           '\u0750' <= char <= '\u077F' or \
           '\u08A0' <= char <= '\u08FF':
            return True
    return False


def create_glow_effect(img, glow_radius=5, glow_color=(255, 255, 255, 100)):
    """Create glow effect for text (from user's file, img is PIL RGBA)"""
    # Create glow layer. glow_color is expected to be RGBA.
    base_for_glow = Image.new('RGBA', img.size, (0,0,0,0))
    # Create a mask from the text's alpha channel
    alpha_mask = img.getchannel('A')
    
    # Apply the glow color to the areas defined by the alpha mask
    colored_text_shape = Image.new('RGBA', img.size, glow_color)
    base_for_glow.paste(colored_text_shape, mask=alpha_mask)

    # Blur this colored shape multiple times
    glow_effect_layer = base_for_glow
    for i in range(max(1, glow_radius // 2)): # Apply blur progressively or multiple times
        glow_effect_layer = glow_effect_layer.filter(ImageFilter.GaussianBlur(radius=2)) # Smaller radius, more iterations
    
    if glow_radius > 1: # Apply a final larger blur
         glow_effect_layer = glow_effect_layer.filter(ImageFilter.GaussianBlur(radius=glow_radius / 2.0))


    # Composite the blurred glow behind the original image
    # Image.alpha_composite expects background first, then foreground
    final_image_with_glow = Image.alpha_composite(glow_effect_layer, img)
    return final_image_with_glow


def insta_oneapi(url, api_key):
    """Downloads video from Instagram using one-api.ir"""
    shortcode_match = re.search(r"(?:instagram\.com|instagr\.am)\/(?:p|reel|reels|tv)\/([a-zA-Z0-9_-]+)", url)
    if shortcode_match:
        shortcode = shortcode_match.group(1)
    else:
        parts = url.strip("/").split("/")
        shortcode = parts[-1] if parts and parts[-1] not in ["", "feed"] else "" # handle cases like "username/feed/"
        if not shortcode and len(parts) > 1 and parts[-2] in ["p", "reel", "reels", "tv"]: # a bit more robust for just ID
            shortcode = parts[-1]


    print(f"Extracted shortcode: '{shortcode}' from URL: {url}")

    if not shortcode:
        print("Error: Could not extract shortcode from Instagram URL.")
        return None

    api_url = f"https://api.one-api.ir/instagram/v1/post/?shortcode={shortcode}"
    headers = {"one-api-token": api_key, "Content-Type": "application/json"}
    
    print(f"Requesting Instagram post info from: {api_url}")
    try:
        response = requests.get(api_url, headers=headers, timeout=20)
        print(f"Instagram API Response Status: {response.status_code}")
        response.raise_for_status()
        result = response.json()
        
        if result.get("status") != 200 or "result" not in result:
             print(f"API Error: Status {result.get('status')} - {result.get('message', 'Unknown API error')}")
             print(f"Full API response: {result}")
             return None

        media_items = result.get("result", {}).get("media", [])
        if not media_items:
            print("Error: 'media' not found in API response or is empty.")
            print(f"Full API response: {result}")
            return None

        video_download_url = None
        for item in media_items:
            if item.get("type") != "image" and item.get("url"):
                video_download_url = item["url"]
                break
        if not video_download_url and media_items and media_items[0].get("url"):
             video_download_url = media_items[0]["url"] # Fallback to first item's URL

        if not video_download_url:
            print("Error: No suitable media URL found in API response.")
            print(f"Full API response: {result}")
            return None
            
        print(f"Video download URL obtained: {video_download_url}")
        print(f"Waiting for 2 seconds before downloading video...") # Reduced wait time
        time.sleep(2) 
        
        response_video = requests.get(video_download_url, stream=True, timeout=60)
        response_video.raise_for_status()
        
        temp_vid_file = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
        output_filename = temp_vid_file.name
        temp_vid_file.close()

        with open(output_filename, 'wb') as file:
            for chunk in response_video.iter_content(chunk_size=8192*4):
                if chunk: file.write(chunk)
        
        if os.path.exists(output_filename) and os.path.getsize(output_filename) > 0:
            print(f"Downloaded Instagram video successfully to {output_filename}")
            return output_filename
        else:
            if os.path.exists(output_filename): os.remove(output_filename)
            print(f"Failed to download Instagram video or file is empty: {video_download_url}")
            return None

    except requests.exceptions.Timeout:
        print(f"Timeout error communicating with Instagram API or downloading video for URL: {url}")
        return None
    except requests.exceptions.RequestException as e:
        print(f"RequestException with Instagram API or download for {url}: {e}")
        return None
    except KeyError as e:
        print(f"Error: Could not find expected key '{e}' in API response for {url}.")
        if 'result' in locals(): print(f"Full API response: {result}")
        return None
    except Exception as e:
        print(f"An unexpected error occurred in insta_oneapi for {url}: {e}")
        traceback.print_exc()
        return None


def create_advanced_text_clip(text, font_size, video_size, position_key, duration, start_time=1,
                            font_path="Dima Shekasteh.ttf", text_color_hex="#FFFFFF",
                            effect_type="fade_in", apply_glow_effect=False, custom_position=None,
                            line_spacing_ratio=0.3, text_align="center", glow_color_hex=None, glow_radius_ratio=0.1):
    """Create advanced animated text clip using user's create_glow_effect if enabled"""
    width, height = video_size

    # Ensure font_size is an integer
    font_size = int(font_size)

    img = Image.new('RGBA', (width, height), (0, 0, 0, 0))
    draw = ImageDraw.Draw(img)

    try:
        font = ImageFont.truetype(font_path, font_size)
    except IOError:
        print(f"Warning: Font '{font_path}' not found. Trying system fallbacks.")
        fallback_fonts = ["Arial.ttf", "DejaVuSans.ttf", "tahoma.ttf", "B Nazanin.ttf", "XB Niloofar.ttf", "/System/Library/Fonts/Supplemental/GeezaPro.ttf", "/usr/share/fonts/truetype/noto/NotoNaskhArabic-Regular.ttf"]
        font_loaded = False
        for f_path in fallback_fonts:
            try:
                font = ImageFont.truetype(f_path, font_size)
                font_loaded = True; print(f"Using fallback font: {f_path}"); break
            except IOError: continue
        if not font_loaded: print("Warning: No suitable fallback font found. Using default PIL font."); font = ImageFont.load_default()

    lines = text.split('\n')
    is_rtl = detect_rtl_text(text)
    
    # Determine effective alignment for RTL text
    effective_align = text_align
    if is_rtl:
        if text_align == "center": effective_align = "right" # Common preference for centered RTL
        elif text_align == "left": effective_align = "right" # Force right for RTL if left is chosen

    text_color_rgb_pil = hex_to_rgb(text_color_hex) 
    text_color_rgba_pil = text_color_rgb_pil + (255,) # Full opacity for text

    line_heights = []
    line_widths = []
    for line in lines:
        try:
            bbox = draw.textbbox((0,0), line, font=font, direction="rtl" if is_rtl else None, features=["raqm"] if is_rtl else None)
            line_w = bbox[2] - bbox[0]
            line_h = bbox[3] - bbox[1]
        except Exception: # Fallback for older PIL or issues
             (line_w, line_h) = draw.textsize(line, font=font)
        line_widths.append(line_w)
        line_heights.append(line_h)
    
    dynamic_line_spacing = int(font_size * line_spacing_ratio)
    total_text_height = sum(line_heights) + (len(lines) - 1) * dynamic_line_spacing
    max_line_width = max(line_widths) if line_widths else 0

    if custom_position:
        base_x, base_y = custom_position
    else: # Automatic positioning based on position_key
        margin_v_factor = 0.10; margin_h_factor = 0.05
        if position_key == 'center': base_x = (width - max_line_width) // 2; base_y = (height - total_text_height) // 2
        elif position_key == 'bottom': base_x = (width - max_line_width) // 2; base_y = height - total_text_height - int(height * margin_v_factor)
        elif position_key == 'top': base_x = (width - max_line_width) // 2; base_y = int(height * margin_v_factor)
        elif position_key == 'right': base_x = width - max_line_width - int(width*margin_h_factor); base_y = (height - total_text_height) // 2
        elif position_key == 'left': base_x = int(width*margin_h_factor); base_y = (height - total_text_height) // 2
        else: base_x = (width - max_line_width) // 2; base_y = (height - total_text_height) // 2 # Default center

    current_y = base_y
    for i, line in enumerate(lines):
        line_w_current = line_widths[i]
        if effective_align == "center": line_x = base_x + (max_line_width - line_w_current) // 2
        elif effective_align == "right": line_x = base_x + (max_line_width - line_w_current)
        else: line_x = base_x # Left align
        
        shadow_offset = max(1, font_size // 30)
        shadow_color = (0, 0, 0, 100) # Semi-transparent black shadow
        draw.text((line_x + shadow_offset, current_y + shadow_offset), line, font=font, fill=shadow_color, direction="rtl" if is_rtl else None, features=["raqm"] if is_rtl else None)
        draw.text((line_x, current_y), line, font=font, fill=text_color_rgba_pil, direction="rtl" if is_rtl else None, features=["raqm"] if is_rtl else None)
        current_y += line_heights[i] + dynamic_line_spacing

    if apply_glow_effect:
        glow_rgb = hex_to_rgb(glow_color_hex if glow_color_hex else text_color_hex)
        glow_rgba_for_effect = glow_rgb + (100,) # Use fixed alpha 100 for glow, as in user's example
        final_glow_radius = max(3, int(font_size * glow_radius_ratio))
        img = create_glow_effect(img, glow_radius=final_glow_radius, glow_color=glow_rgba_for_effect)

    img_array = np.array(img)
    img_clip = mp.ImageClip(img_array).set_duration(duration).set_start(start_time)

    if effect_type == "fade_in": img_clip = img_clip.fadein(min(1.0, duration / 3.0))
    elif effect_type == "fade_out": img_clip = img_clip.fadeout(min(1.0, duration / 3.0))
    elif effect_type == "fade_in_out":
        fade_dur = min(1.0, duration / 4.0)
        img_clip = img_clip.fadein(fade_dur).fadeout(fade_dur)
    elif effect_type == "slide_up":
        # Basic slide up: final_pos needs to be defined by how ImageClip positions itself.
        # This is tricky without knowing the clip's size relative to video_size if it's not full screen.
        # Assuming the `img` is the size of `video_size`, and text is drawn onto it.
        # The position set by set_position below acts on the whole `img_clip`.
        img_clip = img_clip.set_position(lambda t: ('center', height - (height - base_y + total_text_height/2) * (t / min(1.0, duration * 0.3))))
        img_clip = img_clip.fadein(0.5)

    return img_clip


def create_divider_line(width, line_type="simple", color_rgba=(255, 255, 255, 150), thickness=2, margin_ratio=0.1):
    """Creates a PIL Image of a divider line."""
    line_width_actual = int(width * (1 - 2 * margin_ratio))
    img_height = thickness + 10 # Padding
    img = Image.new('RGBA', (width, img_height), (0,0,0,0))
    draw = ImageDraw.Draw(img)
    
    start_x = int(width * margin_ratio)
    end_x = int(width * (1 - margin_ratio))
    y_pos = img_height // 2

    if line_type == "simple":
        draw.line([(start_x, y_pos), (end_x, y_pos)], fill=color_rgba, width=thickness)
    # Add dotted/dashed if needed, similar to previous versions
    return img


def create_decorative_frame(width, height, frame_type="simple", color_rgba=(255, 255, 255, 100), thickness=5, margin_percent=0.02):
    img = Image.new('RGBA', (width, height), (0,0,0,0))
    draw = ImageDraw.Draw(img)
    margin_px = int(min(width, height) * margin_percent)
    if frame_type == "simple":
        draw.rectangle([(margin_px, margin_px), (width - margin_px -1, height - margin_px-1)], outline=color_rgba, width=thickness)
    return img

def create_background_overlay(video_size, overlay_type="vignette", opacity=0.3, color_hex="#000000"):
    width, height = video_size
    img = Image.new('RGBA', (width, height), (0,0,0,0))
    overlay_rgb = hex_to_rgb(color_hex)
    alpha_int = int(opacity * 255)
    
    if overlay_type == "vignette":
        center_x, center_y = width / 2, height / 2
        max_dist = np.sqrt(center_x**2 + center_y**2) if center_x > 0 and center_y > 0 else 1
        pixels = img.load()
        for x_coord in range(width):
            for y_coord in range(height):
                dist = np.sqrt((x_coord - center_x)**2 + (y_coord - center_y)**2)
                vignette_alpha = int(alpha_int * (dist / max_dist)**1.5)
                vignette_alpha = min(alpha_int, vignette_alpha)
                vignette_base_color = (0,0,0) # Vignettes are typically blackish
                pixels[x_coord, y_coord] = vignette_base_color + (vignette_alpha,)
    elif overlay_type == "solid_color":
        draw = ImageDraw.Draw(img)
        draw.rectangle([(0,0), (width, height)], fill=overlay_rgb + (alpha_int,))
    return img


# --- Main Video Processing Function ---
def process_video(video_url, music_url, poem_verse, poet_name, username,
                 auto_font_size_enabled, poem_font_size_manual, poet_font_size_manual, username_font_size_manual,
                 poem_color, poet_color, username_color,
                 poem_effect, poet_effect, username_effect,
                 enable_glow_effect, glow_color_main, # Single glow color for all text if enabled
                 add_divider_line, divider_type_style,
                 add_frame_overlay, frame_type_style,
                 background_overlay_type, overlay_opacity_value, overlay_color_value,
                 poem_text_position, poet_original_position, username_text_position,
                 progress=gr.Progress(track_tqdm=True)):

    progress(0, desc="Initializing...")
    
    # Temp file storage
    downloaded_video_path = None
    downloaded_audio_path = None
    final_output_path = None # This will be the path returned

    # Lists for resource cleanup
    clips_to_close = []
    files_to_delete = []

    try:
        # --- Input Validation (Basic) ---
        if not video_url: return "Error: Video URL is required.", None
        if not poem_verse: return "Error: Poem verse is required.", None
        if not poet_name: return "Error: Poet name is required.", None
        if not username: return "Error: Username is required.", None

        progress(0.1, desc="Downloading video...")
        if "instagram.com" in video_url or "instagr.am" in video_url:
            INSTA_API_KEY =os.environ.get('INSTAONEAPIKEY')
            if not INSTA_API_KEY or INSTA_API_KEY == "YOUR_API_KEY_HERE": # Basic check
                return "Error: Instagram API Key (INSTA_ONEAPI_KEY) not configured in environment.", None
            downloaded_video_path = insta_oneapi(video_url, INSTA_API_KEY)
        elif "drive.google.com" in video_url:
            downloaded_video_path = download_from_google_drive(video_url)
        else: # Assume direct link
            downloaded_video_path = download_from_google_drive(video_url) # User's GDrive func also handles direct links

        if not downloaded_video_path or not os.path.exists(downloaded_video_path):
            return f"Error: Failed to download video from {video_url}. Check URL and logs.", None
        files_to_delete.append(downloaded_video_path)

        progress(0.2, desc="Loading video...")
        video = mp.VideoFileClip(downloaded_video_path)
        clips_to_close.append(video)

        video_duration = video.duration
        if video_duration is None or video_duration <= 0:
            return "Error: Video has invalid duration.", None
        
        video_w, video_h = video.w, video.h

        # --- Audio Processing ---
        processed_audio_clip = None
        if music_url and music_url.strip():
            progress(0.25, desc="Downloading audio...")
            downloaded_audio_path = download_audio_file(music_url)
            if downloaded_audio_path and os.path.exists(downloaded_audio_path):
                files_to_delete.append(downloaded_audio_path)
                progress(0.3, desc="Processing audio...")
                try:
                    audio_clip_temp = mp.AudioFileClip(downloaded_audio_path)
                    clips_to_close.append(audio_clip_temp)
                    if audio_clip_temp.duration > video_duration:
                        processed_audio_clip = audio_clip_temp.subclip(0, video_duration)
                    elif audio_clip_temp.duration < video_duration:
                        if audio_clip_temp.duration > 0:
                            num_loops = int(np.ceil(video_duration / audio_clip_temp.duration))
                            looped_clips = [audio_clip_temp] * num_loops
                            concatenated_audio = mp.concatenate_audioclips(looped_clips)
                            clips_to_close.append(concatenated_audio)
                            processed_audio_clip = concatenated_audio.subclip(0, video_duration)
                        else: processed_audio_clip = video.audio # Original if downloaded audio is silent
                    else:
                        processed_audio_clip = audio_clip_temp
                    
                    # If processed_audio_clip is a subclip or new clip, original audio_clip_temp might not be needed further by this var
                    if processed_audio_clip != audio_clip_temp and audio_clip_temp not in clips_to_close:
                         clips_to_close.append(audio_clip_temp) # Ensure it's closed if different
                    elif processed_audio_clip == audio_clip_temp and audio_clip_temp in clips_to_close:
                         clips_to_close.remove(audio_clip_temp) # Avoid double closing if assigned directly
                    clips_to_close.append(processed_audio_clip)


                except Exception as e:
                    print(f"Audio processing failed: {e}. Using video's original audio.")
                    traceback.print_exc()
                    processed_audio_clip = video.audio # Fallback
            else:
                print("Audio download failed or no audio URL. Using video's original audio.")
                processed_audio_clip = video.audio
        else:
            processed_audio_clip = video.audio

        progress(0.4, desc="Calculating font sizes...")
        # Use user's calculate_font_size function
        poem_fs = calculate_font_size(poem_verse, video_w, video_h, custom_size=None if auto_font_size_enabled else poem_font_size_manual)
        poet_fs = calculate_font_size(poet_name, video_w, video_h, custom_size=None if auto_font_size_enabled else poet_font_size_manual)
        username_text_for_calc = f"@{username}"
        username_fs = calculate_font_size(username_text_for_calc, video_w, video_h, custom_size=None if auto_font_size_enabled else username_font_size_manual)
        print(f"Font sizes: Poem={poem_fs}, Poet={poet_fs}, Username={username_fs}")

        # --- Create Text & Decorative Clips ---
        text_and_deco_clips = [] # To be composited over the main video

        progress(0.45, desc="Creating username overlay...")
        # Username typically at top or bottom, less dynamic start time
        username_clip = create_advanced_text_clip(
            username_text_for_calc, username_fs, (video_w, video_h), username_text_position, video_duration - 0.5, start_time=0.25,
            text_color_hex=username_color, effect_type=username_effect, apply_glow_effect=enable_glow_effect, glow_color_hex=glow_color_main,
            text_align="center" # Usernames usually centered or simple alignment
        )
        text_and_deco_clips.append(username_clip)

        progress(0.5, desc="Creating poem verse overlay...")
        poem_start_time = 0.5; poem_duration = video_duration - 1.0
        verse_clip = create_advanced_text_clip(
            poem_verse, poem_fs, (video_w, video_h), poem_text_position, poem_duration, start_time=poem_start_time,
            text_color_hex=poem_color, effect_type=poem_effect, apply_glow_effect=enable_glow_effect, glow_color_hex=glow_color_main,
            text_align="right" if detect_rtl_text(poem_verse) else "center"
        )
        text_and_deco_clips.append(verse_clip)

        # Create poet clip first, its position might be adjusted by divider
        poet_start_time = poem_start_time + 0.5 # Slightly after poem
        poet_duration = poem_duration - 0.5
        poet_text_content = f"- {poet_name}"
        
        # Create poet clip (initial position)
        poet_clip = create_advanced_text_clip(
            poet_text_content, poet_fs, (video_w, video_h), poet_original_position, poet_duration, start_time=poet_start_time,
            text_color_hex=poet_color, effect_type=poet_effect, apply_glow_effect=enable_glow_effect, glow_color_hex=glow_color_main,
            text_align="right" if detect_rtl_text(poet_name) else "center"
        )

        if add_divider_line:
            progress(0.55, desc="Adding divider line...")
            divider_color_rgba = hex_to_rgb(poet_color) + (200,) # Use poet color, semi-transparent
            divider_thickness = max(2, int(poet_fs * 0.08))
            divider_margin_ratio = 0.15
            
            divider_pil_img = create_divider_line(video_w, divider_type_style, divider_color_rgba, divider_thickness, divider_margin_ratio)
            divider_img_h_pil = divider_pil_img.height

            divider_clip = mp.ImageClip(np.array(divider_pil_img)).set_duration(poet_duration).set_start(poet_start_time).fadein(0.5)
            
            # Position divider: Let's place it a bit above the default "bottom" area or poet's original spot.
            # Example: 75% down the screen, or above poet's original position.
            # For this version, let's try a fixed relative position for simplicity if poet_original_position is bottom.
            if poet_original_position == 'bottom':
                 # Place divider above typical bottom text area
                 divider_y_center_target_abs = video_h * 0.78 
            else: # If poet is 'center', place divider below poem or fixed.
                 divider_y_center_target_abs = video_h * 0.70 # A general lower-middle position
            
            divider_y_top_for_moviepy = divider_y_center_target_abs - (divider_img_h_pil / 2.0)
            divider_clip = divider_clip.set_position(('center', divider_y_top_for_moviepy))
            text_and_deco_clips.append(divider_clip)

            # --- Reposition poet name to be centered horizontally, just below the divider ---
            # Get actual height of the poet text from a temporary render for precise placement
            temp_poet_draw_img = Image.new('RGBA', (1,1)) # Minimal image for textbbox
            temp_poet_font = ImageFont.truetype(poet_clip.text_options['font'], int(poet_fs)) # Use actual font and size
            poet_bbox = ImageDraw.Draw(temp_poet_draw_img).textbbox((0,0), poet_text_content, font=temp_poet_font, direction="rtl" if detect_rtl_text(poet_name) else None)
            actual_poet_text_height = poet_bbox[3] - poet_bbox[1] if poet_bbox else poet_fs

            poet_gap_below_divider = int(poet_fs * 0.2) # Small gap
            poet_y_top_target_abs = divider_y_top_for_moviepy + divider_img_h_pil + poet_gap_below_divider
            
            # Create new poet_clip with this specific custom_position (top-left of text block)
            # To center it horizontally, we need its width. Max line width is already calculated in create_advanced_text_clip.
            # We can pass custom_position = (x_centered, poet_y_top_target_abs)
            # This is tricky because create_advanced_text_clip calculates its own x.
            # Alternative: use set_position on the existing poet_clip.
            # poet_clip.w is the width of the *entire image clip*, not just text.
            # For 'center', Y means center of the clip. If clip height is full video height, this is not text center.
            # The easiest is to re-create poet_clip using custom_position focusing on Y, and let create_advanced_text_clip handle X centering.
            # Or, set position as ('center', Y_for_center_of_text_block)
            
            # Let's set the position of the *existing* poet_clip (which is full video canvas size with text drawn on it)
            # The 'y' in .set_position(('center', y)) refers to the y of the center of the clip.
            # If poet_clip is full video height, its center is video_h/2.
            # We want the center of the *text block* to be at a certain Y.
            # poet_y_center_of_text_target = poet_y_top_target_abs + actual_poet_text_height / 2
            # We need to find where to place the full-canvas clip so its internal text appears there.
            # This means adjusting the original base_y inside create_advanced_text_clip.
            # The current `poet_clip` was made with position_key. We just need to adjust its Y.
            poet_clip = poet_clip.set_position(('center', poet_y_top_target_abs + actual_poet_text_height / 2 - poet_clip.h / 2))
            # The above is complicated if poet_clip.h is video_h.
            # Simpler: create poet_clip with custom_position Y and let internal logic center X.
            # The poet_clip is already made. Let's just try setting its Y position.
            # We need the Y for the *top* of the poet_clip, assuming text is drawn near top if custom_pos.
            # Let's try setting the top of the poet_clip (which is an ImageClip of video_size)
            # such that the text *within it* appears where we want.
            # This requires knowing where create_advanced_text_clip placed text if custom_pos is used.

            # Re-create poet_clip for precise custom Y positioning, allowing its internal X centering to work.
            # We need to calculate the X for the text block if we give a custom_position=(x,y)
            # The current create_advanced_text_clip uses position_key OR custom_position.
            # For this case, we'll set the Y position using moviepy's set_position on the already created clip.
            # Assume poet_clip (the transparent canvas) will be positioned such that its drawn text is correct.
            # The (x,y) in set_position is top-left of the clip.
            # We want the text's top at `poet_y_top_target_abs`.
            # The poet_clip is an ImageClip, likely full video height. Its text is drawn at some `base_y` within it.
            # This is hard. Let's simplify: position the poet_clip (canvas) such that the center of its text block is at the desired Y.
            # We want the TOP of the poet TEXT to be at `poet_y_top_target_abs`.
            # The `poet_clip` already has the text rendered on it. Its `pos` can be set.
            # The text is at some `y_offset_within_clip` from the top of `poet_clip`.
            # We want `y_of_poet_clip_top + y_offset_within_clip = poet_y_top_target_abs`.
            # This internal y_offset is `base_y` from `create_advanced_text_clip`.
            # This is too complex. Let's use a simpler MoviePy positioning:
            # Position the poet_clip (which is a full canvas with text drawn) such that its *center*
            # is at `divider_y_top_for_moviepy + divider_img_h_pil + poet_gap_below_divider + actual_poet_text_height / 2`.
            # This sets the *center of the text block* correctly.
            desired_y_for_center_of_poet_text_block = divider_y_top_for_moviepy + divider_img_h_pil + poet_gap_below_divider + (actual_poet_text_height / 2.0)
            poet_clip = poet_clip.set_position(('center', desired_y_for_center_of_poet_text_block))

        text_and_deco_clips.append(poet_clip) # Add the (potentially re-positioned) poet_clip

        if add_frame_overlay:
            progress(0.6, desc="Adding frame...")
            frame_color_rgba = hex_to_rgb(poem_color) + (120,) # Use poem color, semi-transparent
            frame_thickness = max(3, video_w // 250)
            frame_img = create_decorative_frame(video_w, video_h, frame_type_style, frame_color_rgba, frame_thickness)
            frame_clip = mp.ImageClip(np.array(frame_img)).set_duration(video_duration).set_start(0).fadein(0.5)
            text_and_deco_clips.insert(0, frame_clip) # Frame at the bottom of overlays

        if background_overlay_type != "none" and background_overlay_type:
            progress(0.65, desc="Adding background overlay...")
            overlay_img = create_background_overlay((video_w, video_h), background_overlay_type, overlay_opacity_value, overlay_color_value)
            overlay_clip = mp.ImageClip(np.array(overlay_img)).set_duration(video_duration)
            insert_idx = 1 if add_frame_overlay else 0 # Behind text, above video (and frame if exists)
            text_and_deco_clips.insert(insert_idx, overlay_clip)


        progress(0.7, desc="Applying video effects...")
        # Enhanced Zoom/Pan Effect
        zoom_factor_max = 1.03 # Subtle zoom
        pan_amplitude = max(5, video_w // 200) # Subtle pan

        def zoom_pan_effect(get_frame, t):
            frame = get_frame(t)
            pil_img = Image.fromarray(frame)
            orig_w, orig_h = pil_img.size
            current_zoom = 1 + (zoom_factor_max - 1) * (0.5 * (1 - np.cos(np.pi * t / video_duration)))
            new_w, new_h = int(orig_w * current_zoom), int(orig_h * current_zoom)
            
            resized_pil = pil_img.resize((new_w, new_h), Image.Resampling.LANCZOS)
            
            angle = 2 * np.pi * t / (video_duration / 1.5) # Pan cycle 1.5 times
            pan_x = int(pan_amplitude * np.sin(angle * 0.6)) 
            pan_y = int(pan_amplitude * np.cos(angle))    
            
            crop_x = (new_w - orig_w) // 2 + pan_x; crop_y = (new_h - orig_h) // 2 + pan_y
            crop_x = max(0, min(crop_x, new_w - orig_w)); crop_y = max(0, min(crop_y, new_h - orig_h))
            
            cropped_pil = resized_pil.crop((crop_x, crop_y, crop_x + orig_w, crop_y + orig_h))
            return np.array(cropped_pil)

        video_with_anim_effects = video.fl(zoom_pan_effect)

        progress(0.8, desc="Compositing final video...")
        # Add all text and decor clips to the list of clips to close
        for tc in text_and_deco_clips: clips_to_close.append(tc)

        final_composite = mp.CompositeVideoClip([video_with_anim_effects] + text_and_deco_clips, size=(video_w, video_h))
        clips_to_close.append(final_composite)
        
        if processed_audio_clip:
            final_composite = final_composite.set_audio(processed_audio_clip)

        progress(0.9, desc="Rendering final video...")
        
        temp_final_output = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False)
        final_output_path = temp_final_output.name
        temp_final_output.close()
        # No need to add final_output_path to files_to_delete, it's the return value.

        output_fps = video.fps if video.fps and video.fps > 0 else 24
        output_fps = min(output_fps, 30) # Cap FPS

        final_composite.write_videofile(
            final_output_path,
            codec='libx264',
            audio_codec='aac',
            fps=output_fps,
            preset='medium', 
            threads=os.cpu_count(), # Use available cores
            ffmpeg_params=['-crf', '23', '-pix_fmt', 'yuv420p'] # CRF 23 good balance
        )

        progress(1.0, desc="Process complete!")
        return final_output_path, f"Video processed successfully: {os.path.basename(final_output_path)}"

    except Exception as e:
        print(f"An error occurred in process_video: {e}")
        traceback.print_exc()
        # Ensure temp output file is cleaned if error occurs before return
        if final_output_path and os.path.exists(final_output_path):
            try: os.remove(final_output_path)
            except: pass
        return None, f"Error: {str(e)}. Check console for details."
    finally:
        print("--- Cleaning up resources ---")
        for clip in clips_to_close:
            try:
                if clip: clip.close()
            except Exception as e_clean:
                print(f"Error closing a clip: {e_clean}")
        
        for file_path in files_to_delete:
            if file_path and os.path.exists(file_path):
                try:
                    os.remove(file_path)
                    print(f"Cleaned up temporary file: {file_path}")
                except Exception as e_clean:
                    print(f"Error cleaning up temp file {file_path}: {e_clean}")
        print("--- Cleanup finished ---")


# --- Gradio Interface Definition ---
with gr.Blocks(theme=gr.themes.Soft()) as iface:
    gr.Markdown("# Persian Poetry Video Creator ✨")
    gr.Markdown("Overlay Persian poetry onto videos with Instagram/Google Drive/Direct URL support. Ensure `INSTA_ONEAPI_KEY` is set for Instagram.")

    with gr.Row():
        with gr.Column(scale=2):
            video_url = gr.Textbox(label="Video URL (Instagram, Google Drive, Direct Link)", placeholder="e.g., https://www.instagram.com/p/...")
            music_url = gr.Textbox(label="Background Music URL (Direct Link, Optional)", placeholder="e.g., https://example.com/music.mp3")
            poem_verse = gr.TextArea(label="Poem Verse (use '\\n' for new lines)", lines=3, placeholder="مثال:\nاین قافله عمر عجب میگذرد\nدریاب دمی که با طرب میگذرد")
            poet_name = gr.Textbox(label="Poet Name", placeholder="e.g., خیام")
            username = gr.Textbox(label="Your Username (will be displayed with @)", placeholder="e.g., persian_poetry_lover")
        
        with gr.Column(scale=1):
            gr.Markdown("### Output")
            output_video = gr.Video(label="Processed Video")
            status_message = gr.Textbox(label="Status", interactive=False)
            process_button = gr.Button("Create Video", variant="primary")

    with gr.Accordion("⚙️ Customization Options", open=False):
        gr.Markdown("#### Font Settings")
        with gr.Row():
            auto_font_size_enabled = gr.Checkbox(label="Auto Adjust Font Sizes", value=True)
            poem_font_size_manual = gr.Number(label="Poem Font Size (if Auto off)", value=60, minimum=10, maximum=200, step=1)
        with gr.Row():
            poet_font_size_manual = gr.Number(label="Poet Font Size (if Auto off)", value=40, minimum=10, maximum=150, step=1)
            username_font_size_manual = gr.Number(label="Username Font Size (if Auto off)", value=30, minimum=10, maximum=100, step=1)
        
        gr.Markdown("#### Text Colors")
        with gr.Row():
            poem_color = gr.ColorPicker(label="Poem Color", value="#FFFFFF")
            poet_color = gr.ColorPicker(label="Poet Color", value="#E0E0E0") # Slightly off-white for poet
            username_color = gr.ColorPicker(label="Username Color", value="#B0B0B0") # Greyish for username
        
        gr.Markdown("#### Text Effects & Styling")
        with gr.Row():
            poem_effect = gr.Radio(["none", "fade_in", "fade_out", "fade_in_out", "slide_up"], label="Poem Animation", value="fade_in")
            poet_effect = gr.Radio(["none", "fade_in", "fade_out", "fade_in_out"], label="Poet Animation", value="fade_in") # Slide up for poet might conflict with divider
            username_effect = gr.Radio(["none", "fade_in", "fade_out", "fade_in_out"], label="Username Animation", value="fade_in")
        with gr.Row():
            enable_glow_effect = gr.Checkbox(label="Enable Text Glow Effect", value=True)
            glow_color_main = gr.ColorPicker(label="Glow Color (if enabled)", value="#FFFFFF", info="Applied to all text if glow is on")

        gr.Markdown("#### Decorative Elements")
        with gr.Row():
            add_divider_line = gr.Checkbox(label="Add Divider Line (below poem, above poet)", value=True)
            divider_type_style = gr.Radio(["simple"], label="Divider Type", value="simple", info="More types can be added.") # Kept simple for now
        with gr.Row():
            add_frame_overlay = gr.Checkbox(label="Add Decorative Frame", value=False)
            frame_type_style = gr.Radio(["simple"], label="Frame Type", value="simple", info="More types can be added.")

        gr.Markdown("#### Background Overlay")
        with gr.Row():
            background_overlay_type = gr.Radio(["none", "vignette", "solid_color"], label="Background Overlay Type", value="vignette")
            overlay_opacity_value = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label="Overlay Opacity", value=0.3)
            overlay_color_value = gr.ColorPicker(label="Solid Overlay Color", value="#000000", info="Used if 'solid_color' overlay is chosen")

        gr.Markdown("#### Text Positioning (Advanced)")
        with gr.Row():
            poem_text_position = gr.Radio(["center", "top", "bottom"], label="Poem Base Position", value="center")
            poet_original_position = gr.Radio(["bottom", "center"], label="Poet Original Anchor (influences divider if active)", value="bottom")
            username_text_position = gr.Radio(["top", "bottom"], label="Username Position", value="top")
    
    # Connect button to function
    process_button.click(
        fn=process_video,
        inputs=[
            video_url, music_url, poem_verse, poet_name, username,
            auto_font_size_enabled, poem_font_size_manual, poet_font_size_manual, username_font_size_manual,
            poem_color, poet_color, username_color,
            poem_effect, poet_effect, username_effect,
            enable_glow_effect, glow_color_main,
            add_divider_line, divider_type_style,
            add_frame_overlay, frame_type_style,
            background_overlay_type, overlay_opacity_value, overlay_color_value,
            poem_text_position, poet_original_position, username_text_position
        ],
        outputs=[output_video, status_message]
    )

if __name__ == "__main__":
    iface.launch(debug=True) # Launch the Gradio app