leicam commited on
Commit
476a98a
·
verified ·
1 Parent(s): a0c80f5

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -240
app.py DELETED
@@ -1,240 +0,0 @@
1
-
2
- import re
3
- import os
4
- import xml.etree.ElementTree as ET
5
- from dataclasses import dataclass
6
- from typing import List
7
- import gradio as gr
8
-
9
- # Configuration
10
- FPS = 24
11
- OUTPUT_DIR = "./Output"
12
- os.makedirs(OUTPUT_DIR, exist_ok=True)
13
-
14
- def parse_timecode_to_frames(tc: str, fps: int = FPS) -> int:
15
- m = re.match(r"^\s*(\d{2}):(\d{2}):(\d{2})[:;](\d{2})\s*$", tc)
16
- if not m:
17
- raise ValueError(f"Invalid timecode: {tc}")
18
- hh, mm, ss, ff = map(int, m.groups())
19
- return hh*3600*fps + mm*60*fps + ss*fps + ff
20
-
21
- def frames_to_timecode(frames: int, fps: int = FPS) -> str:
22
- hh = frames // (3600*fps)
23
- rem = frames % (3600*fps)
24
- mm = rem // (60*fps)
25
- rem = rem % (60*fps)
26
- ss = rem // fps
27
- ff = rem % fps
28
- return f"{hh:02d}:{mm:02d}:{ss:02d}:{ff:02d}"
29
-
30
- @dataclass
31
- class Segment:
32
- start_tc: str
33
- end_tc: str
34
- start_f: int
35
- end_f: int
36
- text: str
37
- score: float
38
-
39
- def parse_transcript(txt: str) -> List[Segment]:
40
- lines = [l.strip() for l in txt.splitlines() if l.strip()]
41
- results = []
42
- pat_range = re.compile(r"^\[?\s*(\d{2}:\d{2}:\d{2}[:;]\d{2})\s*[-–]\s*(\d{2}:\d{2}:\d{2}[:;]\d{2})\s*\]?\s+(.*)$")
43
- pat_point = re.compile(r"^(\d{2}:\d{2}:\d{2}[:;]\d{2})\s+(.*)$")
44
-
45
- for l in lines:
46
- m = pat_range.match(l)
47
- if m:
48
- s, e, text = m.groups()
49
- try:
50
- s_f = parse_timecode_to_frames(s)
51
- e_f = parse_timecode_to_frames(e)
52
- if e_f > s_f:
53
- results.append(Segment(s, e, s_f, e_f, text, 0.0))
54
- except Exception:
55
- continue
56
- continue
57
- m = pat_point.match(l)
58
- if m:
59
- s, text = m.groups()
60
- try:
61
- s_f = parse_timecode_to_frames(s)
62
- e_f = s_f + 4*FPS
63
- e = frames_to_timecode(e_f)
64
- results.append(Segment(s, e, s_f, e_f, text, 0.0))
65
- except Exception:
66
- continue
67
- return results
68
-
69
- def keyword_score(text: str) -> float:
70
- t = text.lower()
71
- kw_emotion = ["medo", "coragem", "raiva", "chorei", "feliz", "triste", "emocion", "culpa", "vergonha", "orgulho"]
72
- kw_break = ["nunca", "de repente", "contraintuitivo", "ninguém te conta", "parei", "decidi", "quebrei", "virada"]
73
- kw_learn = ["aprendi", "descobri", "lição", "entendi", "percebi", "insight", "melhorou", "piorou"]
74
- kw_viral = ["segredo", "verdade", "por trás", "3 passos", "passo a passo", "como eu", "ninguém fala"]
75
-
76
- score = 0.0
77
- for kw in kw_emotion: score += 2.0 if kw in t else 0.0
78
- for kw in kw_break: score += 1.5 if kw in t else 0.0
79
- for kw in kw_learn: score += 1.2 if kw in t else 0.0
80
- for kw in kw_viral: score += 1.0 if kw in t else 0.0
81
- score += 0.2 * text.count("!")
82
- score += 0.0005 * len(text)
83
- return score
84
-
85
- def select_segments(transcript_txt: str) -> List[Segment]:
86
- segs = parse_transcript(transcript_txt)
87
- if not segs:
88
- raise ValueError("Nenhum trecho válido encontrado na transcrição.")
89
- for s in segs:
90
- s.score = keyword_score(s.text)
91
- segs.sort(key=lambda x: x.score, reverse=True)
92
- k = min(5, max(2, len(segs)))
93
- return segs[:k]
94
-
95
- def get_sequence(root: ET.Element) -> ET.Element:
96
- seq = root.find(".//sequence")
97
- if seq is None:
98
- raise ValueError("Nenhuma <sequence> encontrada no XML.")
99
- return seq
100
-
101
- def ensure_rate_24fps(element: ET.Element):
102
- rate = element.find("./rate")
103
- if rate is None:
104
- rate = ET.SubElement(element, "rate")
105
- tb = rate.find("timebase")
106
- if tb is None:
107
- tb = ET.SubElement(rate, "timebase")
108
- tb.text = str(FPS)
109
- ntsc = rate.find("ntsc")
110
- if ntsc is None:
111
- ntsc = ET.SubElement(rate, "ntsc")
112
- ntsc.text = "FALSE"
113
-
114
- def deep_copy(elem: ET.Element) -> ET.Element:
115
- new = ET.Element(elem.tag, attrib=elem.attrib)
116
- new.text = elem.text
117
- new.tail = elem.tail
118
- for child in list(elem):
119
- new.append(deep_copy(child))
120
- return new
121
-
122
- def clear_clipitems(track_elem: ET.Element):
123
- for ci in list(track_elem.findall("./clipitem")):
124
- track_elem.remove(ci)
125
-
126
- def first_clipitem_ref(track_elem: ET.Element):
127
- return track_elem.find("./clipitem")
128
-
129
- def copy_file_ref(from_clip: ET.Element, to_clip: ET.Element):
130
- src_file = from_clip.find("./file")
131
- if src_file is not None:
132
- old = to_clip.find("./file")
133
- if old is not None:
134
- to_clip.remove(old)
135
- to_clip.append(deep_copy(src_file))
136
-
137
- def build_clipitem(template_ci: ET.Element, cid: str, start_f: int, end_f: int, in_f: int, out_f: int, linked_ids):
138
- ci = ET.Element("clipitem", {"id": cid})
139
- name = template_ci.find("name")
140
- ci_name = ET.SubElement(ci, "name")
141
- ci_name.text = name.text if name is not None else cid
142
-
143
- rate = template_ci.find("rate")
144
- ci.append(deep_copy(rate) if rate is not None else ET.Element("rate"))
145
- ensure_rate_24fps(ci)
146
-
147
- for tag, val in [("start", start_f), ("end", end_f), ("in", in_f), ("out", out_f)]:
148
- t = ET.SubElement(ci, tag)
149
- t.text = str(val)
150
-
151
- copy_file_ref(template_ci, ci)
152
-
153
- for lid in linked_ids:
154
- link = ET.SubElement(ci, "link")
155
- linkclipref = ET.SubElement(link, "linkclipref")
156
- linkclipref.text = lid
157
- mediatype = ET.SubElement(link, "mediatype")
158
- mediatype.text = "video" if "-v" in lid else "audio"
159
- return ci
160
-
161
- def edit_sequence_with_segments(tree: ET.ElementTree, segs: List[Segment]) -> ET.ElementTree:
162
- root = tree.getroot()
163
- seq = get_sequence(root)
164
- ensure_rate_24fps(seq)
165
-
166
- video_track = seq.find("./media/video/track")
167
- audio_track = seq.find("./media/audio/track")
168
-
169
- if video_track is None or audio_track is None:
170
- raise ValueError("Estrutura de trilhas não encontrada (esperado media/video/track e media/audio/track).")
171
-
172
- v_tpl = first_clipitem_ref(video_track)
173
- a_tpl = first_clipitem_ref(audio_track)
174
- if v_tpl is None or a_tpl is None:
175
- raise ValueError("Não há clipitem de referência em V1 e/ou A1.")
176
-
177
- clear_clipitems(video_track)
178
- clear_clipitems(audio_track)
179
-
180
- cursor = 0
181
- for idx, s in enumerate(segs, start=1):
182
- dur = s.end_f - s.start_f
183
- start = cursor
184
- end = cursor + dur
185
-
186
- v_id = f"clipitem-v-cut{idx}"
187
- a_id = f"clipitem-a-cut{idx}"
188
-
189
- v_ci = build_clipitem(v_tpl, v_id, start, end, s.start_f, s.end_f, [a_id])
190
- a_ci = build_clipitem(a_tpl, a_id, start, end, s.start_f, s.end_f, [v_id])
191
-
192
- video_track.append(v_ci)
193
- audio_track.append(a_ci)
194
-
195
- cursor = end
196
-
197
- return tree
198
-
199
- def process_xml_and_transcript(premiere_xml_file, transcript_txt_file):
200
- if premiere_xml_file is None or transcript_txt_file is None:
201
- return "Envie o XML do Premiere e a transcrição em .txt.", None
202
-
203
- with open(transcript_txt_file.name, "r", encoding="utf-8") as f:
204
- transcript = f.read()
205
-
206
- segments = select_segments(transcript)
207
-
208
- tree = ET.parse(premiere_xml_file.name)
209
- tree = edit_sequence_with_segments(tree, segments)
210
-
211
- base = os.path.splitext(os.path.basename(premiere_xml_file.name))[0]
212
- out_path = os.path.join(OUTPUT_DIR, f"{base}_EDITADO.xml")
213
- tree.write(out_path, encoding="utf-8", xml_declaration=True)
214
-
215
- resumo = "Cortes aplicados (24 fps):\n"
216
- for i, s in enumerate(segments, 1):
217
- resumo += f"{i}. {s.start_tc} -> {s.end_tc} | {s.end_f - s.start_f} frames | {s.text[:120]}\n"
218
-
219
- return resumo, out_path
220
-
221
- demo = gr.Interface(
222
- fn=process_xml_and_transcript,
223
- inputs=[
224
- gr.File(label="XML da sequência do Premiere (FCP XML)", file_types=[".xml"]),
225
- gr.File(label="Transcrição (.txt) com timecodes hh:mm:ss:ff", file_types=[".txt"]),
226
- ],
227
- outputs=[
228
- gr.Textbox(label="Resumo dos cortes aplicados"),
229
- gr.File(label="Download do XML Editado"),
230
- ],
231
- title="Agente de Edição XML para Premiere (24 fps)",
232
- description=(
233
- "Lê a transcrição com timecodes e recria a timeline na mesma sequência, com 2–5 trechos fortes. "
234
- "Não cria nova sequência, não adiciona mídias externas e mantém trilhas originais."
235
- ),
236
- allow_flagging="never",
237
- )
238
-
239
- if __name__ == "__main__":
240
- demo.launch()