diff options
Diffstat (limited to 'highlight.py')
| -rw-r--r-- | highlight.py | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/highlight.py b/highlight.py new file mode 100644 index 0000000..361a0d2 --- /dev/null +++ b/highlight.py | |||
| @@ -0,0 +1,118 @@ | |||
| 1 | import sys | ||
| 2 | import json | ||
| 3 | import re | ||
| 4 | from datetime import timedelta | ||
| 5 | |||
| 6 | # ANSI escape codes for terminal colors | ||
| 7 | class Colors: | ||
| 8 | GREEN = '\033[92m' # Highlight for valid ad text | ||
| 9 | RED = '\033[91m' # Highlight for invalid target ranges | ||
| 10 | BLUE = '\033[94m' # Header color | ||
| 11 | RESET = '\033[0m' # Reset to default color | ||
| 12 | |||
| 13 | def timestamp_to_seconds(ts_str: str) -> float: | ||
| 14 | """Converts HH:MM:SS.mmm string to total seconds. Returns -1.0 on failure.""" | ||
| 15 | try: | ||
| 16 | parts = ts_str.split(':') | ||
| 17 | h, m = int(parts[0]), int(parts[1]) | ||
| 18 | s_ms = parts[2].split('.') | ||
| 19 | s, ms = int(s_ms[0]), int(s_ms[1]) | ||
| 20 | return h * 3600 + m * 60 + s + ms / 1000.0 | ||
| 21 | except (ValueError, IndexError): | ||
| 22 | return -1.0 | ||
| 23 | |||
| 24 | def visualize_entry(entry: dict): | ||
| 25 | """Parses and prints a single entry from the jsonl file with highlighting.""" | ||
| 26 | entry_id = entry.get("id", "N/A") | ||
| 27 | full_text = entry.get("text", "") | ||
| 28 | target_str = entry.get("target", "") | ||
| 29 | |||
| 30 | print(f"{Colors.BLUE}{'='*80}\nID: {entry_id}{Colors.RESET}") | ||
| 31 | |||
| 32 | # --- 1. Parse the transcript from the 'text' field --- | ||
| 33 | transcript_lines = [] | ||
| 34 | # Regex to find the start of the actual transcript text | ||
| 35 | transcript_match = re.search(r"Transcript:\n(.*)", full_text, re.DOTALL) | ||
| 36 | if not transcript_match: | ||
| 37 | print(f"{Colors.RED}Could not find transcript text in entry.{Colors.RESET}") | ||
| 38 | return | ||
| 39 | |||
| 40 | raw_transcript = transcript_match.group(1).strip() | ||
| 41 | line_pattern = re.compile(r"\[(\d{2}:\d{2}:\d{2}\.\d{3}) --> (\d{2}:\d{2}:\d{2}\.\d{3})\](.*)") | ||
| 42 | |||
| 43 | min_transcript_time, max_transcript_time = float('inf'), float('-inf') | ||
| 44 | |||
| 45 | for line in raw_transcript.split('\n'): | ||
| 46 | match = line_pattern.match(line) | ||
| 47 | if match: | ||
| 48 | start_str, end_str, text = match.groups() | ||
| 49 | start_sec = timestamp_to_seconds(start_str) | ||
| 50 | end_sec = timestamp_to_seconds(end_str) | ||
| 51 | transcript_lines.append({"start": start_sec, "end": end_sec, "line": line}) | ||
| 52 | min_transcript_time = min(min_transcript_time, start_sec) | ||
| 53 | max_transcript_time = max(max_transcript_time, end_sec) | ||
| 54 | |||
| 55 | # --- 2. Parse and validate the target ranges --- | ||
| 56 | valid_target_ranges = [] | ||
| 57 | has_invalid_target = False | ||
| 58 | |||
| 59 | if target_str: | ||
| 60 | for target_line in target_str.strip().split('\n'): | ||
| 61 | if '-' not in target_line: | ||
| 62 | has_invalid_target = True | ||
| 63 | continue | ||
| 64 | |||
| 65 | start_target_str, end_target_str = target_line.split('-', 1) | ||
| 66 | start_target_sec = timestamp_to_seconds(start_target_str.strip()) | ||
| 67 | end_target_sec = timestamp_to_seconds(end_target_str.strip()) | ||
| 68 | |||
| 69 | # Validation checks | ||
| 70 | is_valid_format = start_target_sec != -1.0 and end_target_sec != -1.0 | ||
| 71 | is_valid_range = is_valid_format and start_target_sec < end_target_sec | ||
| 72 | # Check if the target overlaps with the transcript's time frame at all | ||
| 73 | is_overlapping = is_valid_range and start_target_sec < max_transcript_time and end_target_sec > min_transcript_time | ||
| 74 | |||
| 75 | if is_overlapping: | ||
| 76 | valid_target_ranges.append({"start": start_target_sec, "end": end_target_sec}) | ||
| 77 | else: | ||
| 78 | has_invalid_target = True | ||
| 79 | |||
| 80 | # --- 3. Print results with highlighting --- | ||
| 81 | print(f"TARGET: ", end="") | ||
| 82 | if has_invalid_target: | ||
| 83 | print(f"{Colors.RED}{target_str or 'EMPTY'}{Colors.RESET}") | ||
| 84 | else: | ||
| 85 | print(f"{Colors.GREEN}{target_str or 'EMPTY'}{Colors.RESET}") | ||
| 86 | print("-" * 30) | ||
| 87 | |||
| 88 | for line_info in transcript_lines: | ||
| 89 | is_ad_line = False | ||
| 90 | for target_range in valid_target_ranges: | ||
| 91 | # Check for overlap between the line's time and the target's time | ||
| 92 | if line_info['start'] < target_range['end'] and line_info['end'] > target_range['start']: | ||
| 93 | is_ad_line = True | ||
| 94 | break | ||
| 95 | |||
| 96 | if is_ad_line: | ||
| 97 | print(f"{Colors.GREEN}{line_info['line']}{Colors.RESET}") | ||
| 98 | else: | ||
| 99 | print(line_info['line']) | ||
| 100 | |||
| 101 | def main(filepath: str): | ||
| 102 | try: | ||
| 103 | with open(filepath, 'r', encoding='utf-8') as f: | ||
| 104 | for line in f: | ||
| 105 | try: | ||
| 106 | entry = json.loads(line) | ||
| 107 | visualize_entry(entry) | ||
| 108 | except json.JSONDecodeError: | ||
| 109 | print(f"{Colors.RED}Error: Could not decode JSON line: {line.strip()}{Colors.RESET}") | ||
| 110 | except FileNotFoundError: | ||
| 111 | print(f"{Colors.RED}Error: File not found at {filepath}{Colors.RESET}") | ||
| 112 | |||
| 113 | if __name__ == "__main__": | ||
| 114 | if len(sys.argv) < 2: | ||
| 115 | print("Usage: python highlight.py <path_to_labels.jsonl>") | ||
| 116 | sys.exit(1) | ||
| 117 | |||
| 118 | main(sys.argv[1]) | ||
