File size: 3,533 Bytes
2219798
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Reuse the old demo's word alignment and fades, with Small edge-cue protection."""
import logging
import re
import sys

import settings

if str(settings.PARENT) not in sys.path:
    sys.path.append(str(settings.PARENT))
import align_engine
import timed_script

log = logging.getLogger("small.alignment")
_TAG = re.compile(r"\([^)]*\)|\[[^]]*\]")


def edge_cues(tagged):
    """Only tags outside the spoken words protect the leading/trailing edges.

    Unlike the legacy heuristic, this works without duration tags and does not
    mistake a mid-sentence burst for a leading or closing sound. Explicit edge
    trailing silence is also intentional. Leading silence needs no exception:
    the legacy fade silences the lead without removing samples or its duration.
    """
    tagged = str(tagged or "")
    masked = _TAG.sub(lambda m: " " * len(m[0]), tagged)
    speech = list(re.finditer(r"\S+", masked))
    if not speech:
        return True, ""

    def protects(tag):
        if tag.startswith("["):
            return "pause" in tag.lower()
        body = tag[1:-1]
        return bool(re.search(r",\s*[0-9.]+\s*seconds?", body, re.I)
                    or timed_script._is_burst_label(body))

    prefix = tagged[:speech[0].start()]
    suffix = tagged[speech[-1].end():]
    allow_lead = not any(m[0].startswith("(") and protects(m[0]) for m in _TAG.finditer(prefix))
    closing = []
    for m in _TAG.finditer(suffix):
        tag = m[0]
        if not protects(tag):
            continue
        number = re.search(r"([0-9.]+)\s*seconds?", tag, re.I)
        seconds = float(number[1]) if number else (0.5 if tag.startswith("[") else 0.28)
        # Legacy trim's tail rule retains twice the requested length plus air.
        closing.append(f"(requested edge sound or silence, {seconds} seconds)")
    return allow_lead, "[0 seconds duration] speech " + " ".join(closing)


class AudioFinisher:
    def __init__(self):
        # Deliberately choose the same Apache-2.0 Qwen backend, not the old
        # optional non-commercial MMS fallback.
        self.aligner = align_engine.QwenAligner(device=settings.SCORE_DEVICE)

    @property
    def available(self):
        return self.aligner is not None and self.aligner.ok

    def finish(self, waves, spec, enabled=True):
        allow_lead, edges = edge_cues(spec.get("edge_script", spec.get("tagged", "")))
        output, reports = [], []
        for wave in waves:
            raw_seconds = len(wave) / 48000
            if not enabled:
                report = {"applied": False, "note": "disabled", "spans": []}
                edited = wave
            else:
                try:
                    edited, report = align_engine.trim(wave, 48000, edges,
                        spec["text"], self.aligner, allow_lead=allow_lead)
                except Exception:
                    log.exception("Alignment failed; leaving this take unchanged")
                    edited, report = wave, {"applied": False, "note": "alignment failed", "spans": []}
            report.update(enabled=enabled, available=self.available,
                          raw_seconds=round(raw_seconds, 3), seconds=round(len(edited) / 48000, 3),
                          lead_ramp_s=align_engine.config.ALIGN_LEAD_RAMP_S,
                          tail_ramp_s=align_engine.config.ALIGN_TAIL_RAMP_S,
                          score_kind="structural plausibility, not model confidence")
            output.append(edited)
            reports.append(report)
        return output, reports