Google Meet bot for meeting transcription, recording, and speech-to-text. Join meetings automatically, capture audio, and transcribe in real time with speaker attribution.
Project description
meeto
Open-source Google Meet bot. Join meetings, capture audio, and transcribe in real time using Playwright and pluggable STT providers.
Installation
pip install meeto
playwright install chromium
Quick Start
The simplest way to use meeto is guest mode — no Google account needed. The bot joins the meeting as a guest and waits for the host to admit it.
import asyncio
from meeto import run_meeting_worker
from meeto.config import WorkerConfig, JoinConfig, SttConfig
config = WorkerConfig(
meeting_id="my-meeting-001",
meet_url="https://meet.google.com/abc-defg-hij",
join=JoinConfig(
bot_name="Meeto Bot",
),
stt=SttConfig(
provider="deepgram",
api_key="YOUR_DEEPGRAM_API_KEY",
),
)
asyncio.run(run_meeting_worker(config))
Or via the CLI example:
PYTHONPATH=src python scripts/example.py https://meet.google.com/abc-defg-hij --bot-name "Meeto Bot" --no-headless
Audio dumps are saved to ./audio/ and transcripts to ./transcripts/ by default.
Note: Guest mode requires a display — see Deployment for running on servers and containers.
Authenticated Mode
To join meetings as a Google account (no waiting room), generate a browser session first:
meeto-auth --output storage_state.json
This opens a Chromium window. Log in to Google, then press Enter in the terminal.
config = WorkerConfig(
meeting_id="my-meeting-001",
meet_url="https://meet.google.com/abc-defg-hij",
duration_seconds=3600,
join=JoinConfig(
headless=True,
storage_state_path="storage_state.json",
),
stt=SttConfig(
provider="deepgram",
api_key="YOUR_DEEPGRAM_API_KEY",
),
)
asyncio.run(run_meeting_worker(config))
Deployment
Guest Mode on Servers / Containers
Google blocks headless browsers from joining meetings via server-side bot detection. To run guest mode in a headless environment, use Xvfb to provide a virtual display. meeto automatically detects the DISPLAY environment variable and switches to headed mode.
Quick option — wrap your process with xvfb-run:
apt-get install -y xvfb
xvfb-run python your_bot_script.py
Docker — start Xvfb in your entrypoint:
RUN apt-get update && apt-get install -y xvfb
ENV DISPLAY=:99
#!/bin/bash
Xvfb :99 -screen 0 1920x1080x24 &
sleep 1
exec python your_bot_script.py
Authenticated Mode on Servers / Containers
Authenticated mode uses a saved Google session (storage_state.json), which Google trusts as a real user. This means it works with headless=True out of the box — no Xvfb, no virtual display, no extra system dependencies.
config = WorkerConfig(
meeting_id="my-meeting-001",
meet_url="https://meet.google.com/abc-defg-hij",
join=JoinConfig(
headless=True,
storage_state_path="storage_state.json",
),
)
Generate storage_state.json once on a machine with a display (your laptop), then copy it to the server or bake it into your deployment secrets.
Environment Variable Config
For container/job deployments, build config from env vars:
from meeto.config import worker_config_from_env
config = worker_config_from_env()
Required: MEETING_ID, MEET_URL. See meeto/config/env_config.py for the full list.
Configuration
| Field | Type | Default | Description |
|---|---|---|---|
meeting_id |
str |
required | Unique identifier for the meeting |
meet_url |
str |
required | Google Meet URL |
duration_seconds |
int |
3600 |
Max recording duration |
audio |
AudioConfig |
defaults | Audio capture settings |
stt |
SttConfig |
defaults | Speech-to-text settings |
join |
JoinConfig |
defaults | Browser join settings |
Extending
Custom Storage Adapter
By default, artifacts stay local. To upload to cloud storage (S3, GCS, etc.), implement ArtifactStorageAdapter:
from meeto.storage import ArtifactStorageAdapter
class S3StorageAdapter(ArtifactStorageAdapter):
def upload(self, local_path, content_type="application/octet-stream"):
return f"s3://my-bucket/{local_path}"
asyncio.run(run_meeting_worker(config, storage_adapter=S3StorageAdapter()))
Custom Meeting State Store
By default, meeting lifecycle state is kept in memory. To persist state (e.g. to a database), implement MeetingLifecycleStore:
from meeto.state_store import MeetingLifecycleStore
class PostgresMeetingStore(MeetingLifecycleStore):
def update_status(self, meeting_id, *, status, ended_at=None, transcription_path=None):
...
def heartbeat(self, meeting_id):
...
asyncio.run(run_meeting_worker(config, state_store=PostgresMeetingStore()))
Custom STT Provider
Meeto ships with Deepgram support. To add your own STT provider, implement STTStreamingAdapter:
from meeto.stt import STTStreamingAdapter, register_stt
class MySTTAdapter(STTStreamingAdapter):
async def connect(self): ...
async def send_audio(self, pcm_bytes): ...
async def start(self, on_segment): ...
async def close(self): ...
register_stt("my_provider", MySTTAdapter)
Then set SttConfig(provider="my_provider").
Logging
meeto uses Python's standard logging module and does not configure any handlers itself. To see logs, configure logging in your application:
import logging
logging.basicConfig(level=logging.INFO)
For finer control:
logging.getLogger("meeto").setLevel(logging.DEBUG) # all meeto logs
logging.getLogger("meeto.stt").setLevel(logging.WARNING) # quieter STT logs
Architecture
meeto/
├── runtime.py # Main entrypoint: run_meeting_worker()
├── pipeline.py # Wires audio capture, STT, and transcript writing
├── storage.py # ArtifactStorageAdapter ABC + LocalStorageAdapter
├── audio_writer.py # PCM audio dump writer
├── transcript_writer.py # JSONL transcript writer
├── auth/ # Google login session generator
├── config/ # Typed config models + env var parser
├── meet/ # Playwright-based meeting join, end detection, speaker tracking
├── state_store/ # Meeting lifecycle state management
└── stt/ # STT adapter interface + Deepgram implementation
Contributing
See CONTRIBUTING.md for setup, code style, and PR guidelines.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file meeto-0.3.2.tar.gz.
File metadata
- Download URL: meeto-0.3.2.tar.gz
- Upload date:
- Size: 31.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd5f2608add926f0574ac72a5e5ce1c5a498dd295ff749e0f28b3a184f2628a6
|
|
| MD5 |
dc074046c50a05460e1776ea21ccaee0
|
|
| BLAKE2b-256 |
956977117ab7da2bbad8811b66be417c7262f7f6669ca6b828cc3f3cf3a88e04
|
Provenance
The following attestation bundles were made for meeto-0.3.2.tar.gz:
Publisher:
release.yml on ResearchifyLabs/meeto
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
meeto-0.3.2.tar.gz -
Subject digest:
dd5f2608add926f0574ac72a5e5ce1c5a498dd295ff749e0f28b3a184f2628a6 - Sigstore transparency entry: 1160960925
- Sigstore integration time:
-
Permalink:
ResearchifyLabs/meeto@29044316927442be332de8765302311867870e61 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/ResearchifyLabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@29044316927442be332de8765302311867870e61 -
Trigger Event:
push
-
Statement type:
File details
Details for the file meeto-0.3.2-py3-none-any.whl.
File metadata
- Download URL: meeto-0.3.2-py3-none-any.whl
- Upload date:
- Size: 35.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c1d6afa1c5d07c7b7b00d6fae2f8c1fed71d73a2071d48cf83b834563a9bb19
|
|
| MD5 |
2448b249a5bd2ef325637d8bc8fc3201
|
|
| BLAKE2b-256 |
6f02133d59db4b28d9a59494165023e1432437b9177d3db878fb3ebdd99d4db5
|
Provenance
The following attestation bundles were made for meeto-0.3.2-py3-none-any.whl:
Publisher:
release.yml on ResearchifyLabs/meeto
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
meeto-0.3.2-py3-none-any.whl -
Subject digest:
6c1d6afa1c5d07c7b7b00d6fae2f8c1fed71d73a2071d48cf83b834563a9bb19 - Sigstore transparency entry: 1160960989
- Sigstore integration time:
-
Permalink:
ResearchifyLabs/meeto@29044316927442be332de8765302311867870e61 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/ResearchifyLabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@29044316927442be332de8765302311867870e61 -
Trigger Event:
push
-
Statement type: