re-orged the repo
Browse files- app.py +133 -165
- {source β data}/Timestamps.csv +0 -0
- {source β data}/Titles & Thumbnails.csv +0 -0
- {source β data}/Viral Episode Descriptions.csv +0 -0
- {source β data}/Viral Twitter Clips.csv +0 -0
- prompts/clips.txt +5 -0
- prompts/description.txt +4 -0
- prompt.txt β prompts/enhance.txt +0 -0
- prompts/timestamps.txt +11 -0
- prompts/titles_and_thumbnails.txt +23 -0
- transcript.py β scripts/transcript.py +2 -2
- source/.DS_Store +0 -0
- test.txt +0 -53
app.py
CHANGED
@@ -1,181 +1,150 @@
|
|
1 |
import gradio as gr
|
2 |
import anthropic
|
3 |
import pandas as pd
|
4 |
-
from typing import Tuple, Dict
|
5 |
from youtube_transcript_api import YouTubeTranscriptApi
|
6 |
import re
|
|
|
|
|
|
|
|
|
|
|
7 |
|
8 |
# Initialize Anthropic client
|
9 |
client = anthropic.Anthropic()
|
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 |
-
Thumbnail: "LAST HUMANS STANDING"
|
41 |
-
|
42 |
-
The combination should create intellectual curiosity without clickbait.
|
43 |
-
|
44 |
-
Previous examples:
|
45 |
-
{titles_and_thumbnails_examples}""",
|
46 |
-
}
|
47 |
-
|
48 |
-
# Current prompts used in the session
|
49 |
-
current_prompts = DEFAULT_PROMPTS.copy()
|
50 |
-
|
51 |
-
|
52 |
-
def load_examples(filename: str, columns: list) -> str:
|
53 |
-
"""Load examples from CSV file."""
|
54 |
-
try:
|
55 |
-
df = pd.read_csv(f"source/{filename}")
|
56 |
-
if len(columns) == 1:
|
57 |
-
examples = df[columns[0]].dropna().tolist()
|
58 |
return "\n\n".join(examples)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
|
60 |
-
|
61 |
-
|
62 |
-
if all(pd.notna(row[col]) for col in columns):
|
63 |
-
example = "\n".join(f"{col}: {row[col]}" for col in columns)
|
64 |
-
examples.append(example)
|
65 |
-
return "\n\n".join(examples)
|
66 |
-
except Exception as e:
|
67 |
-
print(f"Error loading {filename}: {str(e)}")
|
68 |
-
return ""
|
69 |
-
|
70 |
-
|
71 |
-
def generate_content(
|
72 |
-
prompt_key: str, transcript: str, max_tokens: int = 1000, temp: float = 0.6
|
73 |
-
) -> str:
|
74 |
-
"""Generate content using Claude."""
|
75 |
-
examples = {
|
76 |
-
"clips": load_examples(
|
77 |
-
"Viral Twitter Clips.csv", ["Tweet Text", "Clip Transcript"]
|
78 |
-
),
|
79 |
-
"description": load_examples("Viral Episode Descriptions.csv", ["Tweet Text"]),
|
80 |
-
"timestamps": load_examples("Timestamps.csv", ["Timestamps"]),
|
81 |
-
"titles_and_thumbnails": load_examples(
|
82 |
-
"Titles & Thumbnails.csv", ["Titles", "Thumbnail"]
|
83 |
-
),
|
84 |
-
}
|
85 |
-
|
86 |
-
message = client.messages.create(
|
87 |
-
model="claude-3-5-sonnet-20241022",
|
88 |
-
max_tokens=max_tokens,
|
89 |
-
temperature=temp,
|
90 |
-
system=current_prompts[prompt_key].format(
|
91 |
-
**{f"{prompt_key}_examples": examples[prompt_key]}
|
92 |
-
),
|
93 |
-
messages=[
|
94 |
-
{
|
95 |
-
"role": "user",
|
96 |
-
"content": [
|
97 |
-
{
|
98 |
-
"type": "text",
|
99 |
-
"text": f"Process this transcript:\n\n{transcript}",
|
100 |
-
}
|
101 |
-
],
|
102 |
-
}
|
103 |
-
],
|
104 |
-
)
|
105 |
-
return message.content[0].text
|
106 |
-
|
107 |
-
|
108 |
-
def get_youtube_transcript(url: str) -> str:
|
109 |
-
"""Get transcript from YouTube URL."""
|
110 |
-
try:
|
111 |
-
video_id = re.search(
|
112 |
-
r"(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/)([A-Za-z0-9_-]+)",
|
113 |
-
url,
|
114 |
-
).group(1)
|
115 |
-
transcript = YouTubeTranscriptApi.list_transcripts(video_id).find_transcript(
|
116 |
-
["en"]
|
117 |
-
)
|
118 |
-
return " ".join(entry["text"] for entry in transcript.fetch())
|
119 |
-
except Exception as e:
|
120 |
-
raise Exception(f"Error fetching YouTube transcript: {str(e)}")
|
121 |
-
|
122 |
-
|
123 |
-
def process_transcript(input_text: str) -> Tuple[str, str, str, str]:
|
124 |
-
"""Process input and generate all content."""
|
125 |
-
try:
|
126 |
-
# Get transcript from URL or use direct input
|
127 |
-
transcript = (
|
128 |
-
get_youtube_transcript(input_text)
|
129 |
-
if any(x in input_text for x in ["youtube.com", "youtu.be"])
|
130 |
-
else input_text
|
131 |
-
)
|
132 |
-
|
133 |
-
# Generate all content types
|
134 |
-
return (
|
135 |
-
generate_content("clips", transcript, max_tokens=8192),
|
136 |
-
generate_content("description", transcript),
|
137 |
-
generate_content("timestamps", transcript, temp=0.4),
|
138 |
-
generate_content("titles_and_thumbnails", transcript, temp=0.7),
|
139 |
-
)
|
140 |
-
except Exception as e:
|
141 |
-
error_msg = f"Error processing input: {str(e)}"
|
142 |
-
return (error_msg,) * 4
|
143 |
-
|
144 |
-
|
145 |
-
def update_prompts(*values) -> str:
|
146 |
-
"""Update the current session's prompts."""
|
147 |
-
global current_prompts
|
148 |
-
current_prompts = {
|
149 |
-
"clips": values[0],
|
150 |
-
"description": values[1],
|
151 |
-
"timestamps": values[2],
|
152 |
-
"titles_and_thumbnails": values[3],
|
153 |
-
}
|
154 |
-
return (
|
155 |
-
"Prompts updated for this session! Changes will reset when you reload the page."
|
156 |
-
)
|
157 |
|
|
|
|
|
|
|
|
|
|
|
158 |
|
159 |
def create_interface():
|
160 |
"""Create the Gradio interface."""
|
|
|
|
|
161 |
with gr.Blocks(title="Podcast Transcript Analyzer") as app:
|
162 |
with gr.Tab("Generate Content"):
|
163 |
gr.Markdown("# Podcast Content Generator")
|
164 |
-
input_text = gr.Textbox(
|
165 |
-
label="Input", placeholder="YouTube URL or transcript...", lines=10
|
166 |
-
)
|
167 |
submit_btn = gr.Button("Generate Content")
|
168 |
outputs = [
|
169 |
-
gr.Textbox(label=
|
170 |
-
|
171 |
-
gr.Textbox(label="Timestamps", lines=10, interactive=False),
|
172 |
-
gr.Textbox(
|
173 |
-
label="Title & Thumbnail Suggestions", lines=10, interactive=False
|
174 |
-
),
|
175 |
]
|
176 |
-
|
177 |
-
|
178 |
-
|
|
|
|
|
179 |
|
180 |
with gr.Tab("Experiment with Prompts"):
|
181 |
gr.Markdown("# Experiment with Prompts")
|
@@ -190,42 +159,41 @@ def create_interface():
|
|
190 |
|
191 |
prompt_inputs = [
|
192 |
gr.Textbox(
|
193 |
-
label="Clips Prompt", lines=10, value=
|
194 |
),
|
195 |
gr.Textbox(
|
196 |
label="Description Prompt",
|
197 |
lines=10,
|
198 |
-
value=
|
199 |
),
|
200 |
gr.Textbox(
|
201 |
label="Timestamps Prompt",
|
202 |
lines=10,
|
203 |
-
value=
|
204 |
),
|
205 |
gr.Textbox(
|
206 |
label="Titles & Thumbnails Prompt",
|
207 |
lines=10,
|
208 |
-
value=
|
209 |
),
|
210 |
]
|
211 |
status = gr.Textbox(label="Status", interactive=False)
|
212 |
|
213 |
# Update prompts when they change
|
214 |
for prompt in prompt_inputs:
|
215 |
-
prompt.change(fn=update_prompts, inputs=prompt_inputs, outputs=[status])
|
216 |
|
217 |
# Reset button
|
218 |
reset_btn = gr.Button("Reset to Default Prompts")
|
219 |
reset_btn.click(
|
220 |
fn=lambda: (
|
221 |
-
update_prompts(*
|
222 |
-
*
|
223 |
),
|
224 |
outputs=[status] + prompt_inputs,
|
225 |
)
|
226 |
|
227 |
return app
|
228 |
|
229 |
-
|
230 |
if __name__ == "__main__":
|
231 |
create_interface().launch()
|
|
|
1 |
import gradio as gr
|
2 |
import anthropic
|
3 |
import pandas as pd
|
4 |
+
from typing import Tuple, Dict, List
|
5 |
from youtube_transcript_api import YouTubeTranscriptApi
|
6 |
import re
|
7 |
+
from pathlib import Path
|
8 |
+
import asyncio
|
9 |
+
import concurrent.futures
|
10 |
+
from dataclasses import dataclass
|
11 |
+
import time
|
12 |
|
13 |
# Initialize Anthropic client
|
14 |
client = anthropic.Anthropic()
|
15 |
|
16 |
+
@dataclass
|
17 |
+
class ContentRequest:
|
18 |
+
prompt_key: str
|
19 |
+
max_tokens: int = 2000
|
20 |
+
temperature: float = 0.6
|
21 |
+
|
22 |
+
class TranscriptProcessor:
|
23 |
+
def __init__(self):
|
24 |
+
self.current_prompts = self._load_default_prompts()
|
25 |
+
|
26 |
+
def _load_default_prompts(self) -> Dict[str, str]:
|
27 |
+
"""Load default prompts from files."""
|
28 |
+
return {
|
29 |
+
key: Path(f"prompts/{key}.txt").read_text()
|
30 |
+
for key in ["clips", "description", "timestamps", "titles_and_thumbnails"]
|
31 |
+
}
|
32 |
+
|
33 |
+
def _load_examples(self, filename: str, columns: List[str]) -> str:
|
34 |
+
"""Load examples from CSV file."""
|
35 |
+
try:
|
36 |
+
df = pd.read_csv(f"data/{filename}")
|
37 |
+
if len(columns) == 1:
|
38 |
+
return "\n\n".join(df[columns[0]].dropna().tolist())
|
39 |
+
|
40 |
+
examples = []
|
41 |
+
for _, row in df.iterrows():
|
42 |
+
if all(pd.notna(row[col]) for col in columns):
|
43 |
+
example = "\n".join(f"{col}: {row[col]}" for col in columns)
|
44 |
+
examples.append(example)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
return "\n\n".join(examples)
|
46 |
+
except Exception as e:
|
47 |
+
print(f"Error loading {filename}: {str(e)}")
|
48 |
+
return ""
|
49 |
+
|
50 |
+
async def _generate_content(self, request: ContentRequest, transcript: str) -> str:
|
51 |
+
"""Generate content using Claude asynchronously."""
|
52 |
+
print(f"Starting {request.prompt_key} generation...")
|
53 |
+
start_time = time.time()
|
54 |
+
|
55 |
+
example_configs = {
|
56 |
+
"clips": ("Viral Twitter Clips.csv", ["Tweet Text", "Clip Transcript"]),
|
57 |
+
"description": ("Viral Episode Descriptions.csv", ["Tweet Text"]),
|
58 |
+
"timestamps": ("Timestamps.csv", ["Timestamps"]),
|
59 |
+
"titles_and_thumbnails": ("Titles & Thumbnails.csv", ["Titles", "Thumbnail"]),
|
60 |
+
}
|
61 |
+
|
62 |
+
# Build prompt with examples
|
63 |
+
full_prompt = self.current_prompts[request.prompt_key]
|
64 |
+
if config := example_configs.get(request.prompt_key):
|
65 |
+
if examples := self._load_examples(*config):
|
66 |
+
full_prompt += f"\n\nPrevious examples:\n{examples}"
|
67 |
+
|
68 |
+
# Run API call in thread pool
|
69 |
+
loop = asyncio.get_event_loop()
|
70 |
+
with concurrent.futures.ThreadPoolExecutor() as pool:
|
71 |
+
message = await loop.run_in_executor(
|
72 |
+
pool,
|
73 |
+
lambda: client.messages.create(
|
74 |
+
model="claude-3-5-sonnet-20241022",
|
75 |
+
max_tokens=request.max_tokens,
|
76 |
+
temperature=request.temperature,
|
77 |
+
system=full_prompt,
|
78 |
+
messages=[{"role": "user", "content": [{"type": "text", "text": f"Process this transcript:\n\n{transcript}"}]}]
|
79 |
+
)
|
80 |
+
)
|
81 |
+
result = message.content[0].text
|
82 |
+
print(f"Finished {request.prompt_key} in {time.time() - start_time:.2f} seconds")
|
83 |
+
return result
|
84 |
+
|
85 |
+
def _get_youtube_transcript(self, url: str) -> str:
|
86 |
+
"""Get transcript from YouTube URL."""
|
87 |
+
try:
|
88 |
+
video_id = re.search(
|
89 |
+
r"(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/)([A-Za-z0-9_-]+)",
|
90 |
+
url
|
91 |
+
).group(1)
|
92 |
+
transcript = YouTubeTranscriptApi.list_transcripts(video_id).find_transcript(["en"])
|
93 |
+
return " ".join(entry["text"] for entry in transcript.fetch())
|
94 |
+
except Exception as e:
|
95 |
+
raise Exception(f"Error fetching YouTube transcript: {str(e)}")
|
96 |
+
|
97 |
+
async def process_transcript(self, input_text: str) -> Tuple[str, str, str, str]:
|
98 |
+
"""Process input and generate all content."""
|
99 |
+
try:
|
100 |
+
# Get transcript from URL or use direct input
|
101 |
+
transcript = (
|
102 |
+
self._get_youtube_transcript(input_text)
|
103 |
+
if any(x in input_text for x in ["youtube.com", "youtu.be"])
|
104 |
+
else input_text
|
105 |
+
)
|
106 |
+
|
107 |
+
# Define content generation requests
|
108 |
+
requests = [
|
109 |
+
ContentRequest("clips", max_tokens=8192),
|
110 |
+
ContentRequest("description"),
|
111 |
+
ContentRequest("timestamps", temperature=0.4),
|
112 |
+
ContentRequest("titles_and_thumbnails", temperature=0.7),
|
113 |
+
]
|
114 |
+
|
115 |
+
# Generate all content concurrently
|
116 |
+
results = await asyncio.gather(
|
117 |
+
*[self._generate_content(req, transcript) for req in requests]
|
118 |
+
)
|
119 |
+
return tuple(results)
|
120 |
|
121 |
+
except Exception as e:
|
122 |
+
return (f"Error processing input: {str(e)}",) * 4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
|
124 |
+
def update_prompts(self, *values) -> str:
|
125 |
+
"""Update the current session's prompts."""
|
126 |
+
keys = ["clips", "description", "timestamps", "titles_and_thumbnails"]
|
127 |
+
self.current_prompts = dict(zip(keys, values))
|
128 |
+
return "Prompts updated for this session! Changes will reset when you reload the page."
|
129 |
|
130 |
def create_interface():
|
131 |
"""Create the Gradio interface."""
|
132 |
+
processor = TranscriptProcessor()
|
133 |
+
|
134 |
with gr.Blocks(title="Podcast Transcript Analyzer") as app:
|
135 |
with gr.Tab("Generate Content"):
|
136 |
gr.Markdown("# Podcast Content Generator")
|
137 |
+
input_text = gr.Textbox(label="Input", placeholder="YouTube URL or transcript...", lines=10)
|
|
|
|
|
138 |
submit_btn = gr.Button("Generate Content")
|
139 |
outputs = [
|
140 |
+
gr.Textbox(label=label, lines=10, interactive=False)
|
141 |
+
for label in ["Twitter Clips", "Twitter Description", "Timestamps", "Title & Thumbnail Suggestions"]
|
|
|
|
|
|
|
|
|
142 |
]
|
143 |
+
|
144 |
+
async def process_wrapper(text):
|
145 |
+
return await processor.process_transcript(text)
|
146 |
+
|
147 |
+
submit_btn.click(fn=process_wrapper, inputs=[input_text], outputs=outputs)
|
148 |
|
149 |
with gr.Tab("Experiment with Prompts"):
|
150 |
gr.Markdown("# Experiment with Prompts")
|
|
|
159 |
|
160 |
prompt_inputs = [
|
161 |
gr.Textbox(
|
162 |
+
label="Clips Prompt", lines=10, value=processor.current_prompts["clips"]
|
163 |
),
|
164 |
gr.Textbox(
|
165 |
label="Description Prompt",
|
166 |
lines=10,
|
167 |
+
value=processor.current_prompts["description"],
|
168 |
),
|
169 |
gr.Textbox(
|
170 |
label="Timestamps Prompt",
|
171 |
lines=10,
|
172 |
+
value=processor.current_prompts["timestamps"],
|
173 |
),
|
174 |
gr.Textbox(
|
175 |
label="Titles & Thumbnails Prompt",
|
176 |
lines=10,
|
177 |
+
value=processor.current_prompts["titles_and_thumbnails"],
|
178 |
),
|
179 |
]
|
180 |
status = gr.Textbox(label="Status", interactive=False)
|
181 |
|
182 |
# Update prompts when they change
|
183 |
for prompt in prompt_inputs:
|
184 |
+
prompt.change(fn=processor.update_prompts, inputs=prompt_inputs, outputs=[status])
|
185 |
|
186 |
# Reset button
|
187 |
reset_btn = gr.Button("Reset to Default Prompts")
|
188 |
reset_btn.click(
|
189 |
fn=lambda: (
|
190 |
+
processor.update_prompts(*processor.current_prompts.values()),
|
191 |
+
*processor.current_prompts.values(),
|
192 |
),
|
193 |
outputs=[status] + prompt_inputs,
|
194 |
)
|
195 |
|
196 |
return app
|
197 |
|
|
|
198 |
if __name__ == "__main__":
|
199 |
create_interface().launch()
|
{source β data}/Timestamps.csv
RENAMED
File without changes
|
{source β data}/Titles & Thumbnails.csv
RENAMED
File without changes
|
{source β data}/Viral Episode Descriptions.csv
RENAMED
File without changes
|
{source β data}/Viral Twitter Clips.csv
RENAMED
File without changes
|
prompts/clips.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
You are a social media expert for the Dwarkesh Podcast. Generate 10 viral-worthy clips from the transcript.
|
2 |
+
Format as:
|
3 |
+
Tweet 1
|
4 |
+
Tweet Text: [text]
|
5 |
+
Clip Transcript: [45-120 seconds of transcript]
|
prompts/description.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Create an engaging episode description tweet (280 chars max) that:
|
2 |
+
1. Highlights compelling aspects
|
3 |
+
2. Includes topic areas and handles
|
4 |
+
3. Ends with "Links below" or "Enjoy!"
|
prompt.txt β prompts/enhance.txt
RENAMED
File without changes
|
prompts/timestamps.txt
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
You are a podcast timestamp generator. Create 5-7 timestamps for this episode, following these rules:
|
2 |
+
- Space timestamps roughly 10 minutes apart
|
3 |
+
- Use only 1-3 words per timestamp
|
4 |
+
- Focus on the most important discussion points
|
5 |
+
- Use this format exactly: "0:00 First Topic"
|
6 |
+
- Skip minor tangents or small talk
|
7 |
+
|
8 |
+
Output the timestamps in chronological order, one per line.
|
9 |
+
|
10 |
+
Previous examples:
|
11 |
+
{timestamps_examples}
|
prompts/titles_and_thumbnails.txt
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Create 3-5 compelling title-thumbnail combinations that tell an intellectually fascinating story.
|
2 |
+
|
3 |
+
The goal is to capture the most mind-blowing insight or narrative from the episode that would make curious people think "I HAVE to hear this story."
|
4 |
+
|
5 |
+
Title Format: "Guest Name β [The Most Intriguing Story/Insight from the Episode]"
|
6 |
+
- Focus on one powerful story/insight rather than a list of topics
|
7 |
+
- The title should make people wonder "How is that possible?" or "I need to know more"
|
8 |
+
- Avoid generic listicles like "Guest - Topic 1, Topic 2, & Topic 3"
|
9 |
+
- Never use clickbait or culture war bait
|
10 |
+
|
11 |
+
Thumbnail: 2-4 ALL CAPS words that amplify the intrigue
|
12 |
+
- Should work together with the title to tell a story
|
13 |
+
- Create intellectual curiosity without sensationalism
|
14 |
+
- Make the viewer wonder "What's the story here?"
|
15 |
+
|
16 |
+
Example:
|
17 |
+
Title: "David Reich β How One Small Tribe Conquered the World 70,000 Years Ago"
|
18 |
+
Thumbnail: "LAST HUMANS STANDING"
|
19 |
+
Why it works: Creates genuine curiosity about an epic historical story. The thumbnail adds mystery - which tribe? why did they survive when others didn't?
|
20 |
+
|
21 |
+
Bad Example:
|
22 |
+
Title: "David Reich - Human Evolution, Neanderthals, & The Yamnaya"
|
23 |
+
Why it's weak: Generic list of topics, doesn't tell a story or create intrigue
|
transcript.py β scripts/transcript.py
RENAMED
@@ -102,8 +102,8 @@ class Enhancer:
|
|
102 |
generativeai.configure(api_key=api_key)
|
103 |
self.model = generativeai.GenerativeModel("gemini-exp-1206")
|
104 |
|
105 |
-
#
|
106 |
-
prompt_path = Path(
|
107 |
self.prompt = prompt_path.read_text()
|
108 |
|
109 |
async def enhance_chunks(self, chunks: List[tuple[str, io.BytesIO]]) -> List[str]:
|
|
|
102 |
generativeai.configure(api_key=api_key)
|
103 |
self.model = generativeai.GenerativeModel("gemini-exp-1206")
|
104 |
|
105 |
+
# Update prompt path
|
106 |
+
prompt_path = Path("prompts/enhance.txt")
|
107 |
self.prompt = prompt_path.read_text()
|
108 |
|
109 |
async def enhance_chunks(self, chunks: List[tuple[str, io.BytesIO]]) -> List[str]:
|
source/.DS_Store
DELETED
Binary file (6.15 kB)
|
|
test.txt
DELETED
@@ -1,53 +0,0 @@
|
|
1 |
-
Speaker A 00:00:00
|
2 |
-
|
3 |
-
Today, I'm chatting with Adam Brown, a founder and lead of the Blueshift team at Google DeepMind, which is cracking maths and reasoning, and a theoretical physicist at Stanford. Adam, welcome.
|
4 |
-
|
5 |
-
Speaker B 00:00:11
|
6 |
-
|
7 |
-
Super excited to be here. Let's do this.
|
8 |
-
|
9 |
-
Speaker A 00:00:13
|
10 |
-
|
11 |
-
First question. What is going to be the ultimate fate of the universe? And how much confidence should we have?
|
12 |
-
|
13 |
-
Speaker B 00:00:19
|
14 |
-
|
15 |
-
I think it depends on physics we don't yet fully understand because the ultimate fate is a long time away. That extends a long way out into the future. It also probably depends on us. It's probably in our hands, depending on how the unknown physics breaks out.
|
16 |
-
|
17 |
-
Our idea of the answer to that question has changed quite a lot over the last century. In the 1930s, when they turned on the big telescopes, they discovered that the universe was expanding, which they were not previously aware of. The question is, how fast is it expanding?
|
18 |
-
|
19 |
-
Then in the 1990s, we discovered something that really surprised us. There had been a learned debate up to that point about whether it was expanding so slowly that it would just expand and then recollapse in a big crunch or whether it was expanding sufficiently fast that it would just keep going forever, maybe slowing down in its expansion but not growing forever.
|
20 |
-
|
21 |
-
Then, in possibly the worst day in human history in terms of expected value, in the 90s, we discovered something that had not been anticipated: not only is it expanding, but the rate at which it's expanding is accelerating. It's getting faster and faster as it expands. This is what's called a cosmological constant or dark energy.
|
22 |
-
|
23 |
-
That completely changes the answer to the question, "What is the ultimate fate?" if it's really there. Because it means that distant galaxies, galaxies that are more than maybe 20 billion light-years away from us right now, are being dragged away from us by the expansion of the universe. We'll never reach them. We'll never get to them because even if we headed towards them at the speed of light, the expansion of the universe is dragging them away faster than we'll be able to catch up with them.
|
24 |
-
|
25 |
-
That's really bad news because we have plans for those galaxies. Maybe we could go get them and turn them into tropical Edos or computronium or whatever we had a plan for. We can't if the cosmological constant is really there because they're being dragged away from us by the expansion of the universe.
|
26 |
-
|
27 |
-
So how confident of that picture should we be? In answer to your question, according to that picture, eventually, the ultimate fate will just be that these universes get dragged away. Only the galaxies that are currently within a dozen billion light-years of us will we be able to reach.
|
28 |
-
|
29 |
-
Speaker A 00:02:57
|
30 |
-
|
31 |
-
Wait, a dozen light-years?
|
32 |
-
|
33 |
-
Speaker B 00:02:58
|
34 |
-
|
35 |
-
Sorry, a dozen billion light-years. A dozen light-years is not many other galaxies.
|
36 |
-
|
37 |
-
Maybe a dozen billion light-years, those ones we'll be able to run out and grab. But anything beyond that is just going to be dragged away from us by the cosmological constant. So that's just a finite number of galaxies and a finite amount of resources.
|
38 |
-
|
39 |
-
But then you ask, how confident should we be? On first principles grounds, you should not be particularly confident in that answer at all. We've had a number of radical reimaginings of what the expansion and fate of the universe is in the last century, including in my lifetime.
|
40 |
-
|
41 |
-
So just on first principles grounds, you might imagine that you shouldn't be very confident, and indeed you shouldn't. We're not totally confident that the dark energy that currently seems to be pushing the universe apart is indeed going to be a feature of our universe forever. Things could change a lot.
|
42 |
-
|
43 |
-
Including, you could imagine that a future civilization could manipulate the cosmological constant and bleed it away or manipulate it in some way in order to avoid the heat death.
|
44 |
-
|
45 |
-
Speaker A 00:04:10
|
46 |
-
|
47 |
-
Can you say more about that? How would one do this, and how far would it apply? How much would it expand the cosmic horizon?
|
48 |
-
|
49 |
-
Speaker B 00:04:18
|
50 |
-
|
51 |
-
Now we're getting to more speculative levels, but it does seem to be a feature of our best theories, a completely untested feature, but a feature nevertheless, of our best theories that combine quantum mechanics and gravity that the cosmological constant isn't just some fixed value.
|
52 |
-
|
53 |
-
In fact, it can take different values, the amount of dark energy, the energy density, and dark energy in what's called different vacuums. For example, string theory has this property that there are many, many vacuums, if string theory is correct, in which the cosmological constant can take very different values. And that perhaps provides some hope.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|