Buckets:

cam-kai-ml/ML-Lip-Reader / code /src /lrs_alignment.py
KaisResearch's picture
download
raw
3.67 kB
"""Parse LRS2/LRS3 per-word alignment text files and count word frequencies.
Format (pretrain splits of both datasets — this is what makes word-level
clipping possible at all; the alignment is provided, not computed by us):
Text: ABOUT TWO THIRDS OF THE COST
Conf: 1
WORD START END ASDSCORE
ABOUT 0.00 0.34 1.00000
TWO 0.34 0.54 1.00000
...
Only files with a WORD/START/END table carry per-word timing. LRS2's "main"
split and LRS3's trainval/test splits typically have just a Text: line and
are skipped — there's no way to isolate one word from a whole-sentence clip
without timing, so those entries simply yield no word rows.
"""
from __future__ import annotations
import re
from collections import Counter
from typing import Iterable, List, Optional, Tuple
from src.lrs_source import LRSSource
_WORD_ROW = re.compile(r"^([A-Za-z']+)\s+([\d.]+)\s+([\d.]+)(?:\s+([\d.]+))?\s*$")
# Pure function words — deliberately minimal. Content words like ABOUT or
# PEOPLE are good lip-reading classes (LRW's own 500-word vocabulary keeps
# them), so we only strip words carrying no visual/lexical content.
DEFAULT_STOPWORDS = {
"THE", "A", "AN", "TO", "OF", "IS", "IN", "AND", "THAT", "IT", "ARE",
"WAS", "FOR", "ON", "AS", "WITH", "BE", "AT", "HAVE", "THIS", "FROM",
"OR", "BY", "BUT", "WE", "CAN", "YOU", "I", "HE", "SHE", "THEY", "THEM",
"HIS", "HER", "ITS", "OUR", "YOUR", "THEIR", "NOT", "DO", "DOES", "DID",
"WILL", "WOULD", "SHOULD", "COULD", "HAS", "HAD",
}
def parse_word_timings(text: str) -> Optional[List[Tuple[str, float, float, float]]]:
"""Return [(WORD, start_sec, end_sec, conf)] or None if untimed."""
lines = text.splitlines()
header_idx = None
for i, line in enumerate(lines):
upper = line.strip().upper()
if upper.startswith("WORD") and "START" in upper:
header_idx = i
break
if header_idx is None:
return None
rows = []
for line in lines[header_idx + 1:]:
line = line.strip()
if not line:
continue
m = _WORD_ROW.match(line)
if not m:
continue
word = m.group(1).upper()
start, end = float(m.group(2)), float(m.group(3))
conf = float(m.group(4)) if m.group(4) is not None else 1.0
rows.append((word, start, end, conf))
return rows or None
def iter_word_rows(sources: Iterable[LRSSource], min_duration: float = 0.0,
max_duration: float = 10.0, min_conf: float = 0.0):
"""Yield (source, entry, word, start, end) for every qualifying word
occurrence across all given sources. Reads only .txt members — cheap
even across LRS3's 100 shards."""
for source in sources:
with source:
for entry in source.entries():
text = source.read_text(entry.txt_member)
rows = parse_word_timings(text)
if not rows:
continue
for word, start, end, conf in rows:
dur = end - start
if dur < min_duration or dur > max_duration:
continue
if conf < min_conf:
continue
yield source, entry, word, start, end
def count_word_frequencies(sources: Iterable[LRSSource], min_duration: float = 0.0,
max_duration: float = 10.0, min_conf: float = 0.0) -> Counter:
counts: Counter = Counter()
for _, _, word, _, _ in iter_word_rows(sources, min_duration,
max_duration, min_conf):
counts[word] += 1
return counts

Xet Storage Details

Size:
3.67 kB
·
Xet hash:
2f6e5ee6d36816f215c6a59517d685c566ff0b0720a80e8e3ec862d958c1a52a

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.