summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvin <git@vineetk.net>2025-08-23 01:33:21 -0400
committervin <git@vineetk.net>2025-08-23 10:58:54 -0400
commit78decc2ddd05519780153a8d09f68884e8d11fc6 (patch)
tree049dce292cb72cbcdda5e923d2833f9568c5ea58
parent3b4865a270888643446d9092ba55d69b79117e5e (diff)
add highlight/visualizer script and update system prompt
-rw-r--r--highlight.py118
-rw-r--r--main.py67
2 files changed, 155 insertions, 30 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 @@
1import sys
2import json
3import re
4from datetime import timedelta
5
6# ANSI escape codes for terminal colors
7class 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
13def 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
24def 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
101def 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
113if __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])
diff --git a/main.py b/main.py
index c7a1ff5..a58e02f 100644
--- a/main.py
+++ b/main.py
@@ -13,12 +13,8 @@ client = openai.OpenAI(
13 api_key=os.getenv("OPENROUTER_API_KEY") 13 api_key=os.getenv("OPENROUTER_API_KEY")
14) 14)
15 15
16#MODEL = "deepseek/deepseek-r1-0528:free" 16# smaller models than this (besides dense llama 3.1 405b) really struggle
17#MODEL = "deepseek/deepseek-chat-v3-0324" # good with some minor editing of output ranges 17MODEL = "deepseek/deepseek-chat-v3.1"
18MODEL = "deepseek/deepseek-chat-v3-0324:free" # twice as slow as paid
19#MODEL = "google/gemini-2.5-flash-lite-preview-06-17" # too many false-positives
20#MODEL = "google/gemini-2.0-flash-exp:free" # fine
21#MODEL = "qwen/qwen3-30b-a3b" # sort of works, but slow
22 18
23SYSTEM_PROMPT = """ 19SYSTEM_PROMPT = """
24You are an expert podcast content analyzer. Your task is to identify and extract pre-recorded dynamic advertising segments from podcast transcripts. These ads typically have a distinct tone shift, often using more direct, persuasive language, and frequently include calls to action, product mentions, or specific brand names. 20You are an expert podcast content analyzer. Your task is to identify and extract pre-recorded dynamic advertising segments from podcast transcripts. These ads typically have a distinct tone shift, often using more direct, persuasive language, and frequently include calls to action, product mentions, or specific brand names.
@@ -29,6 +25,7 @@ You will be provided with segments of a podcast transcript, formatted with times
29- Only identify segments that are clearly pre-recorded dynamic ads with a noticeable tone shift. 25- Only identify segments that are clearly pre-recorded dynamic ads with a noticeable tone shift.
30- Do NOT include host-read sponsorships that blend naturally with the content. Focus only on the "distracting" pre-recorded elements. 26- Do NOT include host-read sponsorships that blend naturally with the content. Focus only on the "distracting" pre-recorded elements.
31- The end of an ad segment is marked by the return to the original podcast discussion, a clear transition to new content, or the end of the ad's persuasive language and calls to action. 27- The end of an ad segment is marked by the return to the original podcast discussion, a clear transition to new content, or the end of the ad's persuasive language and calls to action.
28- A segment should only be classified as an ad if it contains an abrupt topic change AND specific marketing language (e.g., product names, calls to action, special offers). A topic change alone is NOT an ad.
32- Ensure the start and end times correspond precisely to the ad segment in the transcript. 29- Ensure the start and end times correspond precisely to the ad segment in the transcript.
33- Output ONLY the timestamp range(s) in the specified format. No other text, no JSON, no explanations. 30- Output ONLY the timestamp range(s) in the specified format. No other text, no JSON, no explanations.
34- If no ad segments are found, your output MUST be completely empty. Generating any text, including explanations like '[No output...]', is a failure to follow instructions. The only valid output in this case is a blank string. 31- If no ad segments are found, your output MUST be completely empty. Generating any text, including explanations like '[No output...]', is a failure to follow instructions. The only valid output in this case is a blank string.
@@ -107,8 +104,7 @@ You will be provided with segments of a podcast transcript, formatted with times
107**Example 4 Input:** 104**Example 4 Input:**
108[01:46:23.940 --> 01:46:32.220] It has so rapidly become one of the most critical apps on my whole system, I cannot even believe it. 105[01:46:23.940 --> 01:46:32.220] It has so rapidly become one of the most critical apps on my whole system, I cannot even believe it.
109[01:46:32.220 --> 01:46:36.320] So if you don't use shortcuts, this is completely worthless to you. 106[01:46:32.220 --> 01:46:36.320] So if you don't use shortcuts, this is completely worthless to you.
110[01:46:36.320 --> 01:46:54.380] But if you make even mildly complicated shortcuts, you have to be using Logger and it will just completely unlock your ability to be able to make much more complicated shortcuts because you can always know exac 107[01:46:36.320 --> 01:46:54.380] But if you make even mildly complicated shortcuts, you have to be using Logger and it will just completely unlock your ability to be able to make much more complicated shortcuts because you can always know exactly what's happening and exactly where things are going wrong.
111tly what's happening and exactly where things are going wrong.
112[01:46:54.380 --> 01:46:57.460] So Logger is my app of the year. 108[01:46:54.380 --> 01:46:57.460] So Logger is my app of the year.
113[01:46:59.320 --> 01:47:02.180] This episode of Cortex is brought to you by FitBod. 109[01:46:59.320 --> 01:47:02.180] This episode of Cortex is brought to you by FitBod.
114[01:47:02.180 --> 01:47:06.040] When you're looking to change your fitness level, it's hard to know where to get started. 110[01:47:02.180 --> 01:47:06.040] When you're looking to change your fitness level, it's hard to know where to get started.
@@ -120,26 +116,25 @@ tly what's happening and exactly where things are going wrong.
120[01:47:40.940 --> 01:47:44.500] And it builds your best possible workout with the use of exercise science. 116[01:47:40.940 --> 01:47:44.500] And it builds your best possible workout with the use of exercise science.
121[01:47:44.500 --> 01:47:50.260] They have analyzed billions of data points that have been fine-tuned by FitBod certified personal trainers. 117[01:47:44.500 --> 01:47:50.260] They have analyzed billions of data points that have been fine-tuned by FitBod certified personal trainers.
122[01:47:50.260 --> 01:47:57.180] And you can make sure you're going to be learning new movements the right way thanks to the more than 1,000 demonstration videos that are all in the FitBod app. 118[01:47:50.260 --> 01:47:57.180] And you can make sure you're going to be learning new movements the right way thanks to the more than 1,000 demonstration videos that are all in the FitBod app.
123[01:47:57.180 --> 01:48:07.380] This is my favorite feature of FitBod that when I get shown a new exercise, I have a perfect way to be able to understand how to do it because of all the videos that they can show me from different angles along 119[01:47:57.180 --> 01:48:07.380] This is my favorite feature of FitBod that when I get shown a new exercise, I have a perfect way to be able to understand how to do it because of all the videos that they can show me from different angles along with the instructions.
124 with the instructions. 120[01:48:07.380 --> 01:48:10.100] And it makes me feel confident about the work that I'm going to be doing.
125 [01:48:07.380 --> 01:48:10.100] And it makes me feel confident about the work that I'm going to be doing. 121[01:48:10.100 --> 01:48:14.340] Muscles improve when they're working in concert with the entire musculoskeletal system.
126 [01:48:10.100 --> 01:48:14.340] Muscles improve when they're working in concert with the entire musculoskeletal system. 122[01:48:14.340 --> 01:48:19.360] So overworking some muscles while underworking others can negatively impact your overall results.
127 [01:48:14.340 --> 01:48:19.360] So overworking some muscles while underworking others can negatively impact your overall results. 123[01:48:19.360 --> 01:48:24.280] This is why FitBod tracks muscle fatigue and recovery to design a well-balanced workout routine,
128 [01:48:19.360 --> 01:48:24.280] This is why FitBod tracks muscle fatigue and recovery to design a well-balanced workout routine, 124[01:48:24.280 --> 01:48:30.960] which also means you'll never get bored as the app mixes up your workouts with new exercises, rep schemes, supersets, and circuits.
129 [01:48:24.280 --> 01:48:30.960] which also means you'll never get bored as the app mixes up your workouts with new exercises, rep schemes, supersets, and circuits. 125[01:48:30.960 --> 01:48:37.760] The FitBod app is super easy to use and it integrates with your Apple Watch, WearOS smartwatch, and apps like Strava, Fitbit, and Apple Health.
130 [01:48:30.960 --> 01:48:37.760] The FitBod app is super easy to use and it integrates with your Apple Watch, WearOS smartwatch, and apps like Strava, Fitbit, and Apple Health. 126[01:48:37.760 --> 01:48:44.020] You've already heard in this episode why Gray loves FitBod, and Gray Loving FitBod is the reason why they became a sponsor of the show.
131 [01:48:37.760 --> 01:48:44.020] You've already heard in this episode why Gray loves FitBod, and Gray Loving FitBod is the reason why they became a sponsor of the show. 127[01:48:44.020 --> 01:48:45.960] So you should go and check it out for yourself.
132 [01:48:44.020 --> 01:48:45.960] So you should go and check it out for yourself. 128[01:48:45.960 --> 01:48:51.740] You'll be able to benefit from all of the incredible work from FitBod to help you in your fitness journey.
133 [01:48:45.960 --> 01:48:51.740] You'll be able to benefit from all of the incredible work from FitBod to help you in your fitness journey. 129[01:48:51.740 --> 01:48:58.440] Personalized training of this quality can be expensive, but FitBod is just $12.99 a month or $79.99 a year.
134 [01:48:51.740 --> 01:48:58.440] Personalized training of this quality can be expensive, but FitBod is just $12.99 a month or $79.99 a year. 130[01:48:58.440 --> 01:49:04.380] But you can get 25% off your membership by signing up today at FitBod.me slash Cortex.
135 [01:48:58.440 --> 01:49:04.380] But you can get 25% off your membership by signing up today at FitBod.me slash Cortex. 131[01:49:04.380 --> 01:49:09.840] So go now and get your customized fitness plan at FitBod.me slash Cortex.
136 [01:49:04.380 --> 01:49:09.840] So go now and get your customized fitness plan at FitBod.me slash Cortex. 132[01:49:09.980 --> 01:49:14.180] Once again, that is FitBod.me slash Cortex, and you'll get 25% off your membership.
137 [01:49:09.980 --> 01:49:14.180] Once again, that is FitBod.me slash Cortex, and you'll get 25% off your membership. 133[01:49:14.180 --> 01:49:17.980] A thanks to FitBod for their continued support of this show and Relay.
138 [01:49:14.180 --> 01:49:17.980] A thanks to FitBod for their continued support of this show and Relay. 134[01:49:17.980 --> 01:49:20.120] We've made it to home screens.
139 [01:49:17.980 --> 01:49:20.120] We've made it to home screens. 135[01:49:20.120 --> 01:49:22.360] Oh, home screens. Right, right.
140 [01:49:20.120 --> 01:49:22.360] Oh, home screens. Right, right. 136[01:49:22.360 --> 01:49:23.820] How complicated can it be?
141 [01:49:22.360 --> 01:49:23.820] How complicated can it be? 137[01:49:23.920 --> 01:49:29.820] So as like last time, my home screens are broken down into a selection of focus modes too.
142 [01:49:23.920 --> 01:49:29.820] So as like last time, my home screens are broken down into a selection of focus modes too.
143 138
144**Example 4 Output:** 139**Example 4 Output:**
14501:46:59.320-01:49:17.980 14001:46:59.320-01:49:17.980
@@ -253,6 +248,17 @@ tly what's happening and exactly where things are going wrong.
253 248
254**Example 7 Output:** 249**Example 7 Output:**
25500:14:17.500-00:17:05.040 25000:14:17.500-00:17:05.040
251
252---
253**Example 8 Input:**
254[00:27:11.039 --> 00:27:14.480] Why aren't stadium seats just toilets?
255[00:27:14.720 --> 00:27:19.839] Like the Romans of old, like the ancient Greek bathhouse.
256[00:27:20.480 --> 00:27:29.920] Everything was cool and you could chit sit next to your bro while you talked out some philosophy or watched a little sporting event, maybe a little wrestling.
257[00:27:30.160 --> 00:27:37.359] Why are we because in the stadium, one of the biggest problems is you oh like, oh, it's between innings, oh, is it's halftime.
258[00:27:37.440 --> 00:27:38.480] Oh, I gotta go to the bathroom.
259
260**Example 8 Output:**
261
256""" 262"""
257 263
258# --- Helper Functions --- 264# --- Helper Functions ---
@@ -293,7 +299,7 @@ def segment_by_punctuation(asr_results: list, min_words_per_segment: int = 3) ->
293 for i, token in enumerate(tokens): 299 for i, token in enumerate(tokens):
294 is_end_of_sentence = any(p in token.strip() for p in punctuations) 300 is_end_of_sentence = any(p in token.strip() for p in punctuations)
295 301
296 if is_end_of_sentence or (i == len(tokens) - 1): 302 if (is_end_of_sentence or (i == len(tokens) - 1)) and len(tokens) > current_segment_start_idx:
297 segment_tokens = tokens[current_segment_start_idx : i + 1] 303 segment_tokens = tokens[current_segment_start_idx : i + 1]
298 304
299 if len(segment_tokens) >= min_words_per_segment: 305 if len(segment_tokens) >= min_words_per_segment:
@@ -306,7 +312,8 @@ def segment_by_punctuation(asr_results: list, min_words_per_segment: int = 3) ->
306 "end_time": end_time, 312 "end_time": end_time,
307 "text": text 313 "text": text
308 }) 314 })
309 current_segment_tokens = [] 315
316 current_segment_start_idx = i + 1
310 return segments 317 return segments
311 318
312def chunk_segments(segments: list[dict], chunk_duration_seconds: int = 240, overlap_seconds: int = 60) -> list[list[dict]]: 319def chunk_segments(segments: list[dict], chunk_duration_seconds: int = 240, overlap_seconds: int = 60) -> list[list[dict]]: