The full pipeline
Let's snag some content from a meeting
Transcription isn't perfect, and summaries aren't either. But if you plug one into the other... it's definitely tempting. Let's try it with a city council meeting.
It's 2.5 hours long, which is far too long for us to sit through ourselves (right?). Instead, we'll see if we can get to do all the work.
recipes/meeting-minutes.py — Download a public meeting, transcribe it, summarize with Gemini.
from pathlib import Path
import yt_dlp
from pydantic import BaseModel, Field
from pydantic_ai import Agent
DATA = Path("data")
URL = "https://www.youtube.com/watch?v=buEGUxrz8ho"
VIDEO_ID = "buEGUxrz8ho"
MODEL = "google-gla:gemini-2.5-flash"
We'll start by downloading the audio.
audio_path = DATA / f"{VIDEO_ID}.mp3"
if not audio_path.exists():
ydl_opts = {
"outtmpl": str(DATA / "%(id)s.%(ext)s"),
"format": "bestaudio/best",
"postprocessors": [{"key": "FFmpegExtractAudio", "preferredcodec": "mp3"}],
"quiet": True,
"no_warnings": True,
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([URL])
print(f"Audio ready: {audio_path.name}")
Audio ready: buEGUxrz8ho.mp3
Now we'll transcribe. I know we talked about Whisper this and Whisper that, but there's a new model from NVIDIA called Parakeet, and the Parakeet MLX implementation is super super fast on macOS. I transcribed this entire 2.5 hour meeting in about a minute. In the code below it first tries Parakeet, but if you don't have it (or are on Colab or Windows) it falls back to WhisperX.
try:
from parakeet_mlx import from_pretrained
print("Loading parakeet-mlx...")
parakeet = from_pretrained("mlx-community/parakeet-tdt-0.6b-v3")
print("Transcribing (chunked for long audio)...")
result = parakeet.transcribe(str(audio_path), chunk_duration=600, overlap_duration=15)
segments = [{"start": s.start, "text": s.text} for s in result.sentences]
print(f"Transcribed {len(segments)} segments (parakeet-mlx)")
except ImportError:
import os
os.environ["TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD"] = "1"
import torch
import whisperx
device = "cuda" if torch.cuda.is_available() else "cpu"
compute_type = "float16" if device == "cuda" else "int8"
print("Loading whisperx turbo...")
model = whisperx.load_model("turbo", device, compute_type=compute_type)
audio = whisperx.load_audio(str(audio_path))
print("Transcribing...")
result = model.transcribe(audio, batch_size=16)
segments = [{"start": s["start"], "text": s["text"].strip()} for s in result["segments"]]
print(f"Transcribed {len(segments)} segments (whisperx)")
Loading parakeet-mlx...
Transcribing (chunked for long audio)...
Transcribed 2039 segments (parakeet-mlx)
What do we want to know from the meeting? Maybe agenda items, votes, public comments, and some vague general details.
class AgendaItem(BaseModel):
summary: str = Field(description="One-sentence summary of what was discussed")
timestamp: str = Field(description="Approximate timestamp, MM:SS")
class Vote(BaseModel):
item: str = Field(description="What was voted on")
result: str = Field(description="Passed/failed/tabled and vote count if stated")
timestamp: str = Field(description="MM:SS")
class PublicComment(BaseModel):
speaker: str = Field(description="Name of commenter, if stated")
topic: str = Field(description="What they spoke about")
stance: str = Field(description="For/against/neutral")
timestamp: str = Field(description="MM:SS")
class MeetingMinutes(BaseModel):
title: str = Field(description="Short title for the meeting")
overall_summary: str = Field(description="2-3 sentence summary of the meeting")
agenda_items: list[AgendaItem] = Field(description="Major agenda items discussed")
votes: list[Vote] = Field(description="Votes taken and their outcomes")
public_comments: list[PublicComment] = Field(description="Public comments from residents")
notable_quotes: list[str] = Field(description="Direct quotes that stood out, with speaker name")
Now we can send the transcript to Gemini for all the details.
transcript_with_timestamps = "\n".join(
f"[{int(seg['start']//60):02d}:{int(seg['start']%60):02d}] {seg['text']}"
for seg in segments
)
print("Sending transcript to Gemini for summarization...")
agent = Agent(MODEL, output_type=MeetingMinutes)
summary = agent.run_sync(
f"Summarize this city council meeting transcript. Extract agenda items, "
f"votes and outcomes, public comments, and notable quotes. "
f"Use timestamps from the transcript.\n\n"
f"{transcript_with_timestamps}"
)
Sending transcript to Gemini for summarization...
And here's our structured meeting minutes.
minutes = summary.output
print(f"# {minutes.title}\n")
print(f"{minutes.overall_summary}\n")
print("## Agenda Items")
for item in minutes.agenda_items:
print(f" [{item.timestamp}] {item.summary}")
print("\n## Votes")
for v in minutes.votes:
print(f" [{v.timestamp}] {v.item} — {v.result}")
print("\n## Public Comments")
for pc in minutes.public_comments:
print(f" [{pc.timestamp}] {pc.speaker}: {pc.topic} ({pc.stance})")
print("\n## Notable Quotes")
for q in minutes.notable_quotes:
print(f" \"{q}\"")
# Lewiston City Council Meeting: AI Data Center Proposal, Housing Initiatives, and Public Concerns The Lewiston City Council addressed several key issues, most notably unanimously rejecting a controversial proposal for an AI data and technology center in Bates Mill 3 due to overwhelming public opposition and unanswered questions. The council also approved a major housing development project on Lowell Street, moved forward with condemning a dangerous building on Pierce Street, and extended a purchase agreement for a community market. Additionally, a resolution was passed to request the next council review the qualifications of Councilor-elect Iman Osman, which sparked a heated public discussion. ## Agenda Items [10:05] Acceptance of the minutes from the December 2nd council meeting. [10:27] Presentations were given to Councilors Sol and Gallant in appreciation of their dedicated service. [13:00] A proclamation was issued recognizing the City Council Ad Hoc Committee on Establishing a Homeless Shelter for its work. [30:23] The council passed all six items on the consent agenda, including fund transfers for various city projects and property dispositions. [31:42] A joint development agreement with MIL Compute LLC to develop an AI data and technology center in Bates Mill 3 was discussed and voted upon. [76:15] An amendment to the city business license fee schedule policy was approved to add a license for syringe service programs. [79:25] A public hearing was held and an order adopted to approve the first amendment to the Lowell Street Omnibus Tax Increment Financing District and Development Program. [100:17] A condemnation hearing was conducted for the building located at 102 Pierce Street to determine its dangerousness and necessary actions. [139:52] An order was approved authorizing a six-month extension to a purchase and sale agreement with the Lewiston Auburn Community Market Incorporated. [144:13] A resolve was approved to request a review of the qualifications of Councilor-elect Iman Osman to hold the Ward 5 city council seat. [167:29] The Lewiston Housing Authority provided an update on ongoing housing development projects and their older adult home modification program. [191:50] Reports and updates included discussions on the Public Health Committee's work and the relocation of a bus kiosk. [194:21] New business included farewell remarks from outgoing councilors Sol and Gallant. ## Votes [10:25] Acceptance of the minutes of the December 2nd council meeting. — Passed by vote of seven to zero. [31:38] Passage of all six items on the consent agenda. — Passed by vote of seven to zero. [76:01] Adoption of the order approving the joint development agreement with MIL Compute LLC to develop an AI data and technology center in Bates Mill 3. — Failed by a vote of zero to seven. [79:23] Approval of the proposed amendment to the city business licensing fee scheduled policy to add the syringe service program license. — Passed by vote of seven to zero. [100:12] Adoption of the order approving the first amendment to the Lowell Street Omnibus Tax Increment Financing District and Development Program. — Passed by vote of seven to zero. [131:08] Adoption of the findings of fact, conclusions of law, and order for the building at 102 Pierce Street, establishing corrective action or demolition. — Passed by vote of six to one. [144:01] Approval of the order authorizing a six-month extension to a purchase and sale agreement with the Lewiston Auburn Community Market Incorporated. — Passed by vote of seven to zero. [167:16] Approval of the resolve to request a review of the qualifications of counselor elect Iman Osman. — Passed by vote of seven to zero. [202:06] To enter into an executive session to discuss labor negotiations. — Passed by vote of seven to zero. ## Public Comments [18:05] Lisa Jones: Misappropriation of mass shooting victim funds, alleged nonprofit fraud, and increased gun violence. (Against the handling of funds.) [21:11] Catherine Ryder: Gratitude for the council's work on syringe service and harm reduction programs ordinance and commitment to community cleanup. (For the council's work and harm reduction.) [23:26] Melissa Dunn: Concerns about public information requests leading to stalking and threats, particularly from white nationalists, and requests a disclaimer for public comment emails. (Concerned about safety and transparency regarding public records.) [38:26] Matt Rory: Criticism of the council's historical lack of listening, comparison of AI data center process to syringe ordinance, and questions about Mill Compute LLC's legitimacy. (Against the AI data center proposal and the council's governance structure.) [41:25] Kyle Matevier: Economic implications of the AI data center, low tax capture for Lewiston, lack of local benefits, and questioning the developer's business plan. (Against the AI data center proposal.) [44:37] Hunter Bouchard: Environmental concerns (water usage, pollution) and negative impact on Lewiston's art culture due to the AI data center. (Against the AI data center proposal.) [47:38] Stephen Lwell: Concerns about increased electricity rates, natural gas plant emissions, and lack of local job creation from the AI data center. (Against the AI data center proposal.) [50:08] Jay Zimmerman: AI data center as a poor use of Mill 3, making downtown less livable, contradicting master plans, and suggesting alternative uses. (Against the AI data center proposal.) [52:09] Zoe Lidstrom: Appreciation for the council's 'no' vote on the AI data center and advocacy for public process in future mill development. (Against the AI data center, for public input on mill development.) [53:18] Sean O'Connell: Massive water usage of the AI data center, stressing the wastewater system and undoing river cleanup efforts. (Against the AI data center proposal.) [54:56] Unknown: Lack of water and housing for homeless people, criticism of the mayor and council for not addressing these issues. (Concerned about homelessness and council inaction.) [58:10] Kieran Maduris Collins: Advocacy for a moratorium on building any AI data centers in Lewiston, citing environmental and economic destruction. (Against any AI data center in Lewiston.) [58:47] Ciri Cressy: Negative health and water quality impacts from AI data centers, historical pollution, and (Against the AI data center proposal.) [62:23] Abby Roman: Increased local air pollution and health impacts from the combined heat and power (CHP) plant for the AI data center, advocating for zero-emission energy. (Against the AI data center, particularly the CHP plant.) [64:26] Gus Goodwin: Thanks the council for listening and raises concern about PFAS chemicals being used for cooling data centers. (Supportive of council's decision on AI data center, warns about PFAS.) [65:28] Marty Young: Skepticism about tech companies coming to Mill 3 with the AI data center and suggesting filling mills with residents and businesses that benefit the community. (Against the AI data center, for community-benefiting development.) [66:55] Luke Jensen: Lack of transparency regarding the AI data center project, the developer's lack of track record, massive tax breaks, insufficient public input, and negative impact on small businesses; suggests putting the issue to voters. (Strongly against the AI data center proposal and the process.) [181:37] Louise Sampson: Thanks the Housing Authority for tree street development, requests an open house, and urges completion of the Martel School Project due to personal age. (Supportive of housing projects, eager for Martel completion.) [183:03] Matthew Agrin: Asks about accessibility for seniors without computer access to get on housing waiting lists. (Seeking clarification on accessibility for seniors.) [186:00] Marcella Claire: Asks for consideration for a school committee nomination for Ward 5, and thanks Councilor Gallant and Councillor Sowell's wife. (Personal request and thanks.) [186:27] Matthew Agrin: Calls for community unity, suggests improvements for local policing, public health, small businesses, transparency in government, and criticizes flock cameras. (Advocates for community improvement and transparency.) [189:41] Matt Rory: Discusses the need for a charter commission and potential recall provisions for elected officials, highlighting logistical issues for implementation. (Advocates for a charter commission and recall provisions.) [150:02] Kira Kiefer: Supports investigation into Councilor-elect Osman's qualifications for transparency and proper representation. (For investigation of Councilor-elect Osman.) [150:40] Shukri Abdrahman: Opposes the resolve to investigate Councilor-elect Osman, characterizing it as a targeted harassment campaign, racist, and intended to intimidate underrepresented communities, and emphasizing Osman's leadership. (Against the investigation of Councilor-elect Osman.) [153:55] Unknown: Highlights the severity of theft charges against Osman, emphasizes due diligence, and potential negative impact in Ward 5. (For investigation of Councilor-elect Osman.) [155:01] Bob McCarthy: States Osman's claimed residence was never a legal apartment, making his residency claim fictitious, and criticizes the timing of the investigation. (For investigation of Councilor-elect Osman.) [156:21] Jay Zimmerman: Reminds the council not to assess the guilt or innocence of Osman but to allow due process. (Neutral on the investigation itself, but emphasizes due process.) [156:46] Marcella Clerk: Urges a unanimous vote for the investigation into Osman, believing it will show he has no place in government. (For investigation of Councilor-elect Osman.) [157:34] Melissa Dunn: Characterizes the investigation as "white nationalist, unnecessary terrorizing" and a "manufactured public lynching," alleging threats, harassment, and election intimidation; demands investigation into three councilors. (Strongly against the investigation of Councilor-elect Osman, alleges racism.) ## Notable Quotes ""Your voice was heard loud and clear. Facebook, social media, um, emails. We are listening."" ""I counted about 110 emails um with about half of them coming from Lewiston residents. And um it's it's very clear that the public is not in favor of this."" ""If what Mill Compute LLC's business plan is saying is that they cannot make this project work without massive multi-decade tax breaks, then what I'm saying is that you don't have a business plan yet. You have a GoFundMe that you're asking the residents of Lewiston to contribute to."" ""Our greatest threat in our community is white people."" ""This thing should be out in the woods somewhere. It doesn't need to be here."" ""This plan affirms my belief that the downtown should be for people, not machines."" ""I appreciate the council's unanimous decision tonight to reject this uh data center, but I think the broader truth is uh we shouldn't just reject this AI data center. We should reject any AI data center in Lewis and I hope the council will adopt an ordinance placing a moratorium on building these things, they contribute nothing to the community, they destroy our environment, they're destroying our economy, and they don't belong in our city."" ""This alarming pattern is particularly concerning given that the councillor-elect Osman represents Ward 5, the only area with a significant population of people of color, immigrant residents. Such targeted abuse not only undermines the principles of democracy, but also seeks to deter participation from underrepresented communities to be able to participate in this democracy."" ""Lewiston belongs to every single person that lives here. Lewiston is our home."" ""Iman Osman should resign."" ""This is not about an issue of racism. It's not about the color of a man's skin or the mosque or church or cathedral he worships in. It's not about any of those things, it's about whether or not an individual lives in the ward he is elected to represent."" ""Our charter is archaic. Our charter does not cover the needs of the basic items. We can't recall the mayor. You can't recall a counselor. You have no power as a citizen once someone gets voted in. And that's not what democracy is all about."" ""When I first sat in this seat, I was terrified. I had a rough start, but fast forward two years, and it's still terrifying because Lewis and you are a very tough crowd."" ""In my opinion, hiring you has been this council's best decision. And in my opinion, your best decision has been hiring Chief Conley."" ""I've enjoyed working with everyone. And um, and I just hope that going forward, if you have a question or concern or want to use somebody for a signing board, feel free to reach out. You know, um I I always have an opinion. May not be one you like. But I would give you an opinion.""
We could have done this other ways: pass the entire thing to Gemini, use local models for the summarization/extraction, all kinds of options. But it's a good starting point!
I also tried to do the same thing with Google Opal, and it just hallucinated every single aspect of the meeting. I don't even think the end result was working from a transcript, despite the fact that Google owns YouTube. Weird!
Trust but verify!
Cost
Before you run a big batch: how much will it cost? Images × tokens × price = receipt. I would not trust the below, but it'll give you a general idea of what processing 500 images might look like, and the variation across different models.
recipes/cost.py — Estimate API cost for a batch of images. No API key needed.
# USD per million tokens (Feb 2026)
PRICE_TABLE = {
"gpt-4o-mini": (0.15, 0.60),
"gpt-4o": (2.50, 10.00),
"gpt-5-nano": (0.05, 0.20),
"gemini-2.5-flash": (0.10, 0.40),
"gemini-2.5-pro": (1.25, 5.00),
"claude-3-5-haiku": (0.80, 4.00),
"claude-3-5-sonnet": (3.00, 15.00),
"ollama": (0.00, 0.00),
}
NUM_IMAGES = 500
INPUT_TOKENS_PER_IMAGE = 1000
OUTPUT_TOKENS_PER_IMAGE = 200
print(f"Images: {NUM_IMAGES:,}")
for model in PRICE_TABLE.keys():
input_price, output_price = PRICE_TABLE[model]
# --- Calculate ---
total_input = NUM_IMAGES * INPUT_TOKENS_PER_IMAGE
total_output = NUM_IMAGES * OUTPUT_TOKENS_PER_IMAGE
input_cost = (total_input / 1_000_000) * input_price
output_cost = (total_output / 1_000_000) * output_price
total_cost = input_cost + output_cost
print(f"Model: {model}")
print(f"Input cost: ${input_cost:.4f} ({total_input:,} tokens @ ${input_price}/M)")
print(f"Output cost: ${output_cost:.4f} ({total_output:,} tokens @ ${output_price}/M)")
print(f"TOTAL: ${total_cost:.4f}")
print("-" * 40)
Images: 500 Model: gpt-4o-mini Input cost: $0.0750 (500,000 tokens @ $0.15/M) Output cost: $0.0600 (100,000 tokens @ $0.6/M) TOTAL: $0.1350 ---------------------------------------- Model: gpt-4o Input cost: $1.2500 (500,000 tokens @ $2.5/M) Output cost: $1.0000 (100,000 tokens @ $10.0/M) TOTAL: $2.2500 ---------------------------------------- Model: gpt-5-nano Input cost: $0.0250 (500,000 tokens @ $0.05/M) Output cost: $0.0200 (100,000 tokens @ $0.2/M) TOTAL: $0.0450 ---------------------------------------- Model: gemini-2.5-flash Input cost: $0.0500 (500,000 tokens @ $0.1/M) Output cost: $0.0400 (100,000 tokens @ $0.4/M) TOTAL: $0.0900 ---------------------------------------- Model: gemini-2.5-pro Input cost: $0.6250 (500,000 tokens @ $1.25/M) Output cost: $0.5000 (100,000 tokens @ $5.0/M) TOTAL: $1.1250 ---------------------------------------- Model: claude-3-5-haiku Input cost: $0.4000 (500,000 tokens @ $0.8/M) Output cost: $0.4000 (100,000 tokens @ $4.0/M) TOTAL: $0.8000 ---------------------------------------- Model: claude-3-5-sonnet Input cost: $1.5000 (500,000 tokens @ $3.0/M) Output cost: $1.5000 (100,000 tokens @ $15.0/M) TOTAL: $3.0000 ---------------------------------------- Model: ollama Input cost: $0.0000 (500,000 tokens @ $0.0/M) Output cost: $0.0000 (100,000 tokens @ $0.0/M) TOTAL: $0.0000 ----------------------------------------