VeuReu commited on
Commit
cf4287a
·
verified ·
1 Parent(s): b90479c

Delete tts_ad.py

Browse files
Files changed (1) hide show
  1. tts_ad.py +0 -121
tts_ad.py DELETED
@@ -1,121 +0,0 @@
1
- import os, re, tempfile, subprocess
2
- from dataclasses import dataclass
3
- from typing import List, Optional, Tuple
4
- import numpy as np
5
- import soundfile as sf
6
-
7
- from ovos_tts_plugin_matxa_multispeaker_cat import MatxaCatalanTTSPlugin
8
- from pydub import AudioSegment
9
-
10
- @dataclass
11
- class Segment:
12
- idx: int
13
- start_s: float
14
- end_s: float
15
- text: str
16
-
17
- SRT_TS = re.compile(
18
- r"(?P<h1>\d{2}):(?P<m1>\d{2}):(?P<s1>\d{2}),(?P<ms1>\d{3})\s*-->\s*"
19
- r"(?P<h2>\d{2}):(?P<m2>\d{2}):(?P<s2>\d{2}),(?P<ms2>\d{3})"
20
- )
21
-
22
- def _ts_to_seconds(h: str, m: str, s: str, ms: str) -> float:
23
- return int(h)*3600 + int(m)*60 + int(s) + int(ms)/1000.0
24
-
25
- def _is_empty_ad_text(t: str) -> bool:
26
- cleaned = re.sub(r"[^\wÀ-ÿ]", "", t, flags=re.UNICODE)
27
- return len(cleaned.strip()) == 0
28
-
29
- def parse_srt_ad_only(srt_text: str) -> List[Segment]:
30
- content = srt_text.replace("\r\n","\n").replace("\r","\n")
31
- blocks = [b.strip() for b in re.split(r"\n\s*\n", content) if b.strip()]
32
- segs: List[Segment] = []
33
- for block in blocks:
34
- lines = block.split("\n")
35
- if len(lines) < 2: continue
36
- try:
37
- idx = int(lines[0].strip())
38
- m = SRT_TS.match(lines[1].strip())
39
- if not m: continue
40
- start_s = _ts_to_seconds(m["h1"],m["m1"],m["s1"],m["ms1"])
41
- end_s = _ts_to_seconds(m["h2"],m["m2"],m["s2"],m["ms2"])
42
- ad_texts=[]
43
- for t in lines[2:]:
44
- t=t.strip()
45
- if t.startswith("(AD):"):
46
- t=t[len("(AD):"):].lstrip()
47
- if t and not _is_empty_ad_text(t):
48
- ad_texts.append(t)
49
- if not ad_texts: continue
50
- segs.append(Segment(idx=idx, start_s=start_s, end_s=end_s, text=" ".join(ad_texts)))
51
- except:
52
- continue
53
- segs.sort(key=lambda s:(s.start_s, s.idx))
54
- return segs
55
-
56
- def _resample_np(x: np.ndarray, sr_from: int, sr_to: int) -> np.ndarray:
57
- if sr_from == sr_to: return x
58
- ratio = sr_to / sr_from
59
- new_len = int(round(len(x)*ratio))
60
- xp = np.linspace(0,1,num=len(x),endpoint=False)
61
- xq = np.linspace(0,1,num=new_len,endpoint=False)
62
- return np.interp(xq, xp, x).astype(np.float32)
63
-
64
- def trim_or_pad(data: np.ndarray, sr: int, target_sec: float) -> np.ndarray:
65
- target_len = int(round(target_sec * sr))
66
- if len(data) > target_len: return data[:target_len]
67
- if len(data) < target_len:
68
- pad = np.zeros(target_len-len(data), dtype=data.dtype)
69
- return np.concatenate([data,pad])
70
- return data
71
-
72
- def synth_text_to_wav(text: str, out_path: str, voice="central/grau", tts=None) -> Tuple[int, np.ndarray]:
73
- created = False
74
- if tts is None:
75
- tts = MatxaCatalanTTSPlugin()
76
- created = True
77
- # asegura carpeta
78
- os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
79
- tts.get_tts(text, out_path, voice=voice)
80
- data, sr = sf.read(out_path, dtype="float32", always_2d=False)
81
- if data.ndim == 2: data = data.mean(axis=1)
82
- if created: del tts
83
- return sr, data
84
-
85
- def build_ad_track_from_srt_text(srt_text: str, out_audio_path: str, voice="central/grau") -> str:
86
- segs = parse_srt_ad_only(srt_text)
87
- if not segs:
88
- raise ValueError("No se encontraron bloques (AD) con contenido en el SRT.")
89
- # generar cada clip y montar timeline
90
- tts = MatxaCatalanTTSPlugin()
91
- tmpdir = tempfile.mkdtemp(prefix="matxa_ad_")
92
- clips=[]
93
- for seg in segs:
94
- seg_wav = os.path.join(tmpdir, f"ad_{seg.idx}.wav")
95
- sr, data = synth_text_to_wav(seg.text, seg_wav, voice=voice, tts=tts)
96
- data = trim_or_pad(data, sr, seg.end_s - seg.start_s)
97
- clips.append((sr, data, seg.start_s, seg.end_s))
98
- master_sr = clips[0][0]
99
- total_dur = max(s.end_s for s in segs)
100
- master = np.zeros(int(round(total_dur*master_sr)), dtype=np.float32)
101
- for sr, data, start_s, _ in clips:
102
- d = _resample_np(data, sr, master_sr)
103
- i = int(round(start_s*master_sr))
104
- j = min(i+len(d), len(master))
105
- master[i:j] += d[:j-i]
106
- # normalización suave
107
- peak = float(np.max(np.abs(master))) if master.size else 0.0
108
- if peak > 0.999: master = (master/peak*0.98).astype(np.float32)
109
- # escribe mp3 o wav
110
- base, ext = os.path.splitext(out_audio_path)
111
- os.makedirs(os.path.dirname(out_audio_path) or ".", exist_ok=True)
112
- if ext.lower()==".mp3":
113
- tmp_wav = base + ".__tmp__.wav"
114
- sf.write(tmp_wav, master, master_sr, subtype="PCM_16")
115
- AudioSegment.from_wav(tmp_wav).export(out_audio_path, format="mp3")
116
- os.remove(tmp_wav)
117
- else:
118
- if ext.lower()!=".wav":
119
- out_audio_path = base + ".wav"
120
- sf.write(out_audio_path, master, master_sr, subtype="PCM_16")
121
- return out_audio_path