aliSaac510 commited on
Commit
8c64cc3
·
1 Parent(s): 2e6074b

fix defaults

Browse files
Files changed (5) hide show
  1. init_defaults.py +42 -11
  2. main.py +4 -0
  3. routers/presets.py +15 -1
  4. routers/subtitle_generator.py +65 -27
  5. schemas.py +7 -0
init_defaults.py CHANGED
@@ -22,8 +22,14 @@ defaults = {
22
  "pop_up_scale": 1.2,
23
  "highlight_mode": "karaoke",
24
  "back_box_enabled": True,
25
- "display_mode": "word",
26
- "max_words_per_line": 3
 
 
 
 
 
 
27
  },
28
  "gaming": {
29
  "name": "gaming",
@@ -40,8 +46,14 @@ defaults = {
40
  "pop_up_scale": 1.5,
41
  "highlight_mode": "karaoke",
42
  "back_box_enabled": False,
43
- "display_mode": "word",
44
- "max_words_per_line": 2
 
 
 
 
 
 
45
  },
46
  "professional": {
47
  "name": "professional",
@@ -59,7 +71,13 @@ defaults = {
59
  "highlight_mode": "instant",
60
  "back_box_enabled": True,
61
  "display_mode": "sentence",
62
- "max_words_per_line": 5
 
 
 
 
 
 
63
  },
64
  "storyteller": {
65
  "name": "storyteller",
@@ -77,12 +95,25 @@ defaults = {
77
  "highlight_mode": "karaoke",
78
  "back_box_enabled": True,
79
  "display_mode": "sentence",
80
- "max_words_per_line": 4
 
 
 
 
 
 
81
  }
82
  }
83
 
84
- for name, data in defaults.items():
85
- file_path = os.path.join(PRESETS_DIR, f"{name}.json")
86
- with open(file_path, "w", encoding="utf-8") as f:
87
- json.dump(data, f, indent=4)
88
- print(f"Created default preset: {name}")
 
 
 
 
 
 
 
 
22
  "pop_up_scale": 1.2,
23
  "highlight_mode": "karaoke",
24
  "back_box_enabled": True,
25
+ "display_mode": "sentence",
26
+ "max_words_per_line": 3,
27
+ "uppercase": False,
28
+ "letter_spacing": 0.0,
29
+ "background_opacity": 1.0,
30
+ "glow_intensity": 0,
31
+ "rotation_angle": 0.0,
32
+ "margin_h": 20
33
  },
34
  "gaming": {
35
  "name": "gaming",
 
46
  "pop_up_scale": 1.5,
47
  "highlight_mode": "karaoke",
48
  "back_box_enabled": False,
49
+ "display_mode": "sentence",
50
+ "max_words_per_line": 2,
51
+ "uppercase": True,
52
+ "letter_spacing": 1.0,
53
+ "background_opacity": 0.8,
54
+ "glow_intensity": 2,
55
+ "rotation_angle": -3.0,
56
+ "margin_h": 20
57
  },
58
  "professional": {
59
  "name": "professional",
 
71
  "highlight_mode": "instant",
72
  "back_box_enabled": True,
73
  "display_mode": "sentence",
74
+ "max_words_per_line": 5,
75
+ "uppercase": False,
76
+ "letter_spacing": 0.5,
77
+ "background_opacity": 0.9,
78
+ "glow_intensity": 0,
79
+ "rotation_angle": 0.0,
80
+ "margin_h": 40
81
  },
82
  "storyteller": {
83
  "name": "storyteller",
 
95
  "highlight_mode": "karaoke",
96
  "back_box_enabled": True,
97
  "display_mode": "sentence",
98
+ "max_words_per_line": 4,
99
+ "uppercase": True,
100
+ "letter_spacing": 0.0,
101
+ "background_opacity": 1.0,
102
+ "glow_intensity": 1,
103
+ "rotation_angle": 2.0,
104
+ "margin_h": 30
105
  }
106
  }
107
 
108
+ def init_all_defaults():
109
+ """الدالة التي سنستدعيها عند بدء تشغيل التطبيق"""
110
+ os.makedirs(PRESETS_DIR, exist_ok=True)
111
+ for name, data in defaults.items():
112
+ file_path = os.path.join(PRESETS_DIR, f"{name}.json")
113
+ # لا نعيد الكتابة إذا كان الملف موجوداً بالفعل إلا إذا أردت تحديثه
114
+ with open(file_path, "w", encoding="utf-8") as f:
115
+ json.dump(data, f, indent=4)
116
+ print(f"✅ Initialized preset: {name}")
117
+
118
+ if __name__ == "__main__":
119
+ init_all_defaults()
main.py CHANGED
@@ -3,8 +3,12 @@ from fastapi.middleware.cors import CORSMiddleware
3
  import uvicorn
4
  from datetime import datetime
5
  from routers import video, files, presets
 
6
  import os
7
 
 
 
 
8
  # إعداد المسارات لتكون دائماً داخل مجلد Clipping
9
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
10
  UPLOAD_BASE_DIR = os.path.join(BASE_DIR, "temp_videos")
 
3
  import uvicorn
4
  from datetime import datetime
5
  from routers import video, files, presets
6
+ from init_defaults import init_all_defaults
7
  import os
8
 
9
+ # تهيئة الإعدادات الافتراضية عند بدء التشغيل
10
+ init_all_defaults()
11
+
12
  # إعداد المسارات لتكون دائماً داخل مجلد Clipping
13
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
14
  UPLOAD_BASE_DIR = os.path.join(BASE_DIR, "temp_videos")
routers/presets.py CHANGED
@@ -33,6 +33,13 @@ async def save_preset(
33
  back_box_enabled: bool = Form(True),
34
  display_mode: str = Form("word", description="word or sentence"),
35
  max_words_per_line: int = Form(3, description="Max words per line in sentence mode"),
 
 
 
 
 
 
 
36
  custom_font_file: Optional[UploadFile] = File(None, description="Optional: Upload a .ttf or .otf font file")
37
  ):
38
  """
@@ -71,7 +78,14 @@ async def save_preset(
71
  "highlight_mode": highlight_mode,
72
  "back_box_enabled": back_box_enabled,
73
  "display_mode": display_mode,
74
- "max_words_per_line": max_words_per_line
 
 
 
 
 
 
 
75
  }
76
 
77
  # 3. حفظ ملف الـ JSON
 
33
  back_box_enabled: bool = Form(True),
34
  display_mode: str = Form("word", description="word or sentence"),
35
  max_words_per_line: int = Form(3, description="Max words per line in sentence mode"),
36
+ uppercase: bool = Form(False, description="Convert English text to ALL CAPS"),
37
+ letter_spacing: float = Form(0.0, description="Spacing between characters"),
38
+ line_spacing: int = Form(0, description="Vertical spacing between lines"),
39
+ background_opacity: float = Form(1.0, description="Opacity of background box (0.0 to 1.0)"),
40
+ glow_intensity: int = Form(0, description="Intensity of glow effect (0 to 10)"),
41
+ rotation_angle: float = Form(0.0, description="Rotation angle in degrees"),
42
+ margin_h: int = Form(20, description="Horizontal margin"),
43
  custom_font_file: Optional[UploadFile] = File(None, description="Optional: Upload a .ttf or .otf font file")
44
  ):
45
  """
 
78
  "highlight_mode": highlight_mode,
79
  "back_box_enabled": back_box_enabled,
80
  "display_mode": display_mode,
81
+ "max_words_per_line": max_words_per_line,
82
+ "uppercase": uppercase,
83
+ "letter_spacing": letter_spacing,
84
+ "line_spacing": line_spacing,
85
+ "background_opacity": background_opacity,
86
+ "glow_intensity": glow_intensity,
87
+ "rotation_angle": rotation_angle,
88
+ "margin_h": margin_h
89
  }
90
 
91
  # 3. حفظ ملف الـ JSON
routers/subtitle_generator.py CHANGED
@@ -27,6 +27,15 @@ def generate_pro_ass(transcription, preset, output_path):
27
  secondary = rgb_to_ass(preset.secondary_color)
28
  outline = rgb_to_ass(preset.outline_color)
29
 
 
 
 
 
 
 
 
 
 
30
  header = f"""[Script Info]
31
  ScriptType: v4.00+
32
  PlayResX: 1080
@@ -35,8 +44,8 @@ ScaledBorderAndShadow: yes
35
 
36
  [V4+ Styles]
37
  Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
38
- Style: Main,{preset.font_name},{preset.font_size},{primary},{secondary},{outline},&H00000000,-1,0,0,0,100,100,0,0,1,3,2,{preset.alignment},10,10,{preset.margin_v},1
39
- Style: BackBox,{preset.font_name},{preset.font_size},{secondary},{secondary},&H00000000,&H00000000,-1,0,0,0,100,100,0,0,3,10,0,{preset.alignment},10,10,{preset.margin_v},1
40
  """
41
 
42
  events = "\n[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n"
@@ -74,52 +83,81 @@ Style: BackBox,{preset.font_name},{preset.font_size},{secondary},{secondary},&H0
74
 
75
  if preset.display_mode == "word":
76
  # الوضع التقليدي: كلمة بكلمة
77
- for word_data in sentence.words:
 
78
  start_t = format_ass_time(word_data.start)
79
  end_t = format_ass_time(word_data.end)
80
  duration = word_data.end - word_data.start
81
 
82
- t1 = min(50, int(duration * 200))
83
- t2 = t1 + 100
84
  scale = int(preset.pop_up_scale * 100)
85
- anim = f"{{\\fscx0\\fscy0\\t(0,{t1},0.3,\\fscx{scale}\\fscy{scale})\\t({t1},{t2},1.5,\\fscx100\\fscy100)}}"
 
 
 
 
 
 
 
 
 
 
 
86
 
87
  if preset.back_box_enabled:
88
- events += f"Dialogue: 0,{start_t},{end_t},BackBox,,0,0,0,,{{\\bord5\\1a&H40&}}{anim}{word_data.word}\n"
89
- events += f"Dialogue: 1,{start_t},{end_t},Main,,0,0,0,,{anim}{word_data.word}\n"
90
 
91
  else:
92
  # وضع الجملة: تظهر الجملة كاملة والكلمة النشطة تبرز
93
- # 1. طبقة الجملة الأساسية (تظهر طوال مدة الجملة بلون خافت)
94
- full_text = sentence.text
95
- # جعل النص الأساسي شفاف قليلاً (dimmed) ليبرز الـ Highlight
96
- dim_alpha = "\\1a&H80&"
97
-
98
- if preset.back_box_enabled:
99
- events += f"Dialogue: 0,{s_start},{s_end},BackBox,,0,0,0,,{{{dim_alpha}}}{full_text}\n"
100
- else:
101
- events += f"Dialogue: 0,{s_start},{s_end},Main,,0,0,0,,{{{dim_alpha}}}{full_text}\n"
102
 
103
- # 2. طبقة الكلمات النشطة (Highlight)
104
  for i, target_word in enumerate(sentence.words):
105
  w_start = format_ass_time(target_word.start)
106
  w_end = format_ass_time(target_word.end)
107
  duration = target_word.end - target_word.start
108
 
109
- # بناء النص: كلمات ما قبل (مخفية) + كلمة حالية (ملونة ومتحركة) + كلمات ما بعد (مخفية)
110
- prefix = " ".join([w.word for w in sentence.words[:i]]) + (" " if i > 0 else "")
111
- suffix = (" " if i < len(sentence.words)-1 else "") + " ".join([w.word for w in sentence.words[i+1:]])
 
 
 
 
 
 
112
 
113
- t1 = min(50, int(duration * 200))
114
- t2 = t1 + 100
 
 
 
 
 
 
 
 
 
 
115
  scale = int(preset.pop_up_scale * 100)
116
- # الـ Highlight يكون بلون الـ secondary وبدون شفافية
117
- anim = f"{{\\1c{secondary}\\1a&H00&\\fscx0\\fscy0\\t(0,{t1},0.3,\\fscx{scale}\\fscy{scale})\\t({t1},{t2},1.5,\\fscx100\\fscy100)}}"
 
 
 
 
 
 
 
 
 
 
118
 
119
  # استخدام \alpha&HFF& لإخفاء الكلمات غير النشطة في هذه الطبقة فقط
120
- display_text = f"{{\\alpha&HFF&}}{prefix}{anim}{target_word.word}{{\\alpha&HFF&}}{suffix}"
121
 
122
- events += f"Dialogue: 1,{w_start},{w_end},Main,,0,0,0,,{display_text}\n"
123
 
124
  with open(output_path, "w", encoding="utf-8-sig") as f:
125
  f.write(header + events)
 
27
  secondary = rgb_to_ass(preset.secondary_color)
28
  outline = rgb_to_ass(preset.outline_color)
29
 
30
+ # تحويل الشفافية (0.0 - 1.0) إلى صيغة ASS (00 - FF)
31
+ # ملاحظة: في ASS، الـ 00 هو معتم تماماً و FF هو شفاف تماماً
32
+ back_alpha = int((1.0 - getattr(preset, 'background_opacity', 1.0)) * 255)
33
+ back_color = f"&H{back_alpha:02X}000000" # لون خلفية أسود مع شفافية متغيرة
34
+
35
+ spacing = getattr(preset, 'letter_spacing', 0.0)
36
+ angle = getattr(preset, 'rotation_angle', 0.0)
37
+ margin_h = getattr(preset, 'margin_h', 20)
38
+
39
  header = f"""[Script Info]
40
  ScriptType: v4.00+
41
  PlayResX: 1080
 
44
 
45
  [V4+ Styles]
46
  Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
47
+ Style: Main,{preset.font_name},{preset.font_size},{primary},{secondary},{outline},&H00000000,-1,0,0,0,100,100,{spacing},{angle},1,{preset.outline_width},{preset.shadow_depth},{preset.alignment},{margin_h},{margin_h},{preset.margin_v},1
48
+ Style: BackBox,{preset.font_name},{preset.font_size},{secondary},{secondary},&H00000000,{back_color},-1,0,0,0,100,100,{spacing},{angle},3,10,0,{preset.alignment},{margin_h},{margin_h},{preset.margin_v},1
49
  """
50
 
51
  events = "\n[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n"
 
83
 
84
  if preset.display_mode == "word":
85
  # الوضع التقليدي: كلمة بكلمة
86
+ for i, word_data in enumerate(sentence.words):
87
+ word_text = word_data.word.upper() if preset.uppercase else word_data.word
88
  start_t = format_ass_time(word_data.start)
89
  end_t = format_ass_time(word_data.end)
90
  duration = word_data.end - word_data.start
91
 
92
+ t1 = min(60, int(duration * 150))
93
+ t2 = t1 + 80
94
  scale = int(preset.pop_up_scale * 100)
95
+
96
+ # إضافة تأثير التوهج (Blur) إذا كان مفعلاً
97
+ glow_val = getattr(preset, 'glow_intensity', 0)
98
+ blur_tag = f"\\be{glow_val}" if glow_val > 0 else ""
99
+
100
+ # انيميشن احترافي مع دوران ديناميكي أو ثابت
101
+ if angle != 0:
102
+ rotation = f"\\frz{angle}"
103
+ else:
104
+ rotation = "\\frz-2" if i % 2 == 0 else "\\frz2"
105
+
106
+ anim = f"{{\\fscx80\\fscy80{rotation}{blur_tag}\\t(0,{t1},0.3,\\fscx{scale}\\fscy{scale})\\t({t1},{t2},1.5,\\fscx100\\fscy100)}}"
107
 
108
  if preset.back_box_enabled:
109
+ events += f"Dialogue: 0,{start_t},{end_t},BackBox,,0,0,0,,{{\\bord5\\1a&H40&}}{anim}{word_text}\n"
110
+ events += f"Dialogue: 1,{start_t},{end_t},Main,,0,0,0,,{anim}{word_text}\n"
111
 
112
  else:
113
  # وضع الجملة: تظهر الجملة كاملة والكلمة النشطة تبرز
114
+ # قمنا بتعديل المنطق لإخفاء الكلمة الأصلية في الخلفية أثناء ظهور الـ Popup
 
 
 
 
 
 
 
 
115
 
 
116
  for i, target_word in enumerate(sentence.words):
117
  w_start = format_ass_time(target_word.start)
118
  w_end = format_ass_time(target_word.end)
119
  duration = target_word.end - target_word.start
120
 
121
+ # 1. طبقة الخلفية لجملة كاملة مع إخفاء الكلمة النشطة حالياً)
122
+ words_list_base = [w.word.upper() if preset.uppercase else w.word for w in sentence.words]
123
+ prefix_base = " ".join(words_list_base[:i]) + (" " if i > 0 else "")
124
+ suffix_base = (" " if i < len(words_list_base)-1 else "") + " ".join(words_list_base[i+1:])
125
+ target_word_base = words_list_base[i]
126
+
127
+ # جعل النص الأساسي شفاف قليلاً (dimmed) وإخفاء الكلمة النشطة بـ \alpha&HFF&
128
+ dim_alpha = "\\1a&H80&"
129
+ base_display_text = f"{{{dim_alpha}}}{prefix_base}{{\\alpha&HFF&}}{target_word_base}{{\\alpha&H00&}}{{{dim_alpha}}}{suffix_base}"
130
 
131
+ if preset.back_box_enabled:
132
+ events += f"Dialogue: 0,{w_start},{w_end},BackBox,,0,0,0,,{base_display_text}\n"
133
+ else:
134
+ events += f"Dialogue: 0,{w_start},{w_end},Main,,0,0,0,,{base_display_text}\n"
135
+
136
+ # 2. طبقة الكلمات النشطة (Highlight / Popup)
137
+ prefix_hl = " ".join(words_list_base[:i]) + (" " if i > 0 else "")
138
+ suffix_hl = (" " if i < len(words_list_base)-1 else "") + " ".join(words_list_base[i+1:])
139
+ target_word_hl = words_list_base[i]
140
+
141
+ t1 = min(60, int(duration * 150))
142
+ t2 = t1 + 80
143
  scale = int(preset.pop_up_scale * 100)
144
+
145
+ # إضافة تأثير التوهج (Blur)
146
+ glow_val = getattr(preset, 'glow_intensity', 0)
147
+ blur_tag = f"\\be{glow_val}" if glow_val > 0 else ""
148
+
149
+ # الـ Highlight يكون بلون الـ secondary وبدون شفافية مع تأثير Pop وميلان
150
+ if angle != 0:
151
+ rotation = f"\\frz{angle}"
152
+ else:
153
+ rotation = "\\frz-2" if i % 2 == 0 else "\\frz2"
154
+
155
+ anim = f"{{\\1c{secondary}\\1a&H00&\\fscx80\\fscy80{rotation}{blur_tag}\\t(0,{t1},0.3,\\fscx{scale}\\fscy{scale})\\t({t1},{t2},1.5,\\fscx100\\fscy100)}}"
156
 
157
  # استخدام \alpha&HFF& لإخفاء الكلمات غير النشطة في هذه الطبقة فقط
158
+ display_text_hl = f"{{\\alpha&HFF&}}{prefix_hl}{anim}{target_word_hl}{{\\alpha&HFF&}}{suffix_hl}"
159
 
160
+ events += f"Dialogue: 1,{w_start},{w_end},Main,,0,0,0,,{display_text_hl}\n"
161
 
162
  with open(output_path, "w", encoding="utf-8-sig") as f:
163
  f.write(header + events)
schemas.py CHANGED
@@ -99,6 +99,13 @@ class SubtitlePreset(BaseModel):
99
  back_box_enabled: bool = True
100
  display_mode: str = "word" # "word" or "sentence"
101
  max_words_per_line: int = 3
 
 
 
 
 
 
 
102
 
103
  class ClipRequest(BaseModel):
104
  video_url: Optional[str] = None
 
99
  back_box_enabled: bool = True
100
  display_mode: str = "word" # "word" or "sentence"
101
  max_words_per_line: int = 3
102
+ uppercase: bool = False
103
+ letter_spacing: float = 0.0
104
+ line_spacing: int = 0
105
+ background_opacity: float = 1.0
106
+ glow_intensity: int = 0
107
+ rotation_angle: float = 0.0
108
+ margin_h: int = 20
109
 
110
  class ClipRequest(BaseModel):
111
  video_url: Optional[str] = None