Meeting Detection - Python Library
Python implementation of the meeting detection library for detecting active video meetings on macOS.
This is a pure Python port of the original Rust/Node.js implementation, maintaining 100% accuracy parity while being more accessible for command-line tools and Python scripts.
Features
- ✅ Two-tier detection algorithm for accurate meeting detection
- ✅ Event-based API with callbacks (
on_meeting_start,on_meeting_end) - ✅ Simple Python API - no native compilation required
- ✅ Background polling every 2 seconds
- ✅ 100% accuracy parity with Rust implementation
Supported Platforms & Services
| Service | Native App | Browser | Detection Method |
|---|---|---|---|
| Zoom | ✅ | ✅ | Tier 1: Network connections (UDP port 8801) Tier 2: URL patterns ( zoom.us/j/, zoom.us/s/) |
| Google Meet | ❌ | ✅ | Tier 2: URL patterns with meeting code validation (meet.google.com/xxx-yyyy-zzz) |
| Microsoft Teams | ✅ | ✅ | Tier 1: Network connections (STUN/TURN ports) Tier 2: URL patterns ( teams.live.com/v2/, teams.microsoft.com/_#/meet/) |
| Webex | ✅ | ✅ | Tier 1: Network connections (video ports) Tier 2: URL patterns ( *.webex.com/webapp/, *.webex.com/meet/) |
Supported Operating Systems:
- macOS ✅ (Intel and Apple Silicon)
Installation
pip install -e .
Or for development:
pip install -e ".[dev]"
Requirements
- Python 3.8+
- macOS (uses
lsof,osascript,mdls) - Dependencies:
psutil,typing-extensions
Usage
Basic Example
from meeting_detection import init, is_meeting_active, get_last_detection_details
# Initialize the engine (starts background polling every 2 seconds)
init()
# Check current status
active = is_meeting_active()
print(f"Meeting active: {active}")
# Get detailed detection information
details = get_last_detection_details()
if details:
print(f"App: {details.app_name}")
print(f"Reason: {details.reason}")
if details.meeting_url:
print(f"URL: {details.meeting_url}")
Event-Based Monitoring
import time
from meeting_detection import init, on_meeting_start, on_meeting_end
# Initialize the engine
init()
# Register callbacks
def handle_meeting_start(details):
print(f"🎥 Meeting started!")
print(f" App: {details.app_name}")
print(f" Reason: {details.reason}")
if details.meeting_url:
print(f" URL: {details.meeting_url}")
def handle_meeting_end(details):
print(f"👋 Meeting ended!")
print(f" App: {details.app_name}")
on_meeting_start(handle_meeting_start)
on_meeting_end(handle_meeting_end)
# Keep the script running
print("Monitoring for meetings... (Press Ctrl+C to stop)")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Stopped monitoring.")
Command-Line Tool Example
#!/usr/bin/env python3
"""Simple CLI tool to check meeting status."""
import sys
from meeting_detection import init, is_meeting_active
def main():
init()
# Wait for first detection cycle
import time
time.sleep(3)
if is_meeting_active():
print("In a meeting")
sys.exit(0)
else:
print("Not in a meeting")
sys.exit(1)
if __name__ == '__main__':
main()
API Reference
Functions
init()
Initialize and start the meeting detection engine. Must be called before using other functions.
init()
is_meeting_active() -> bool
Check if a meeting is currently active.
active = is_meeting_active()
Returns: True if meeting is active, False otherwise
on_meeting_start(callback: Callable[[DetectionDetails], None])
Register a callback for when a meeting starts.
def handle_start(details):
print(f"Meeting started: {details.app_name}")
on_meeting_start(handle_start)
Parameters:
callback: Function that receivesDetectionDetailswhen a meeting starts
on_meeting_end(callback: Callable[[DetectionDetails], None])
Register a callback for when a meeting ends.
def handle_end(details):
print(f"Meeting ended: {details.app_name}")
on_meeting_end(handle_end)
Parameters:
callback: Function that receivesDetectionDetailswhen a meeting ends
get_last_detection_details() -> Optional[DetectionDetails]
Get details about the last detection cycle.
details = get_last_detection_details()
if details:
print(f"App: {details.app_name}")
print(f"Active: {details.active}")
Returns: DetectionDetails or None if no detection has occurred yet
Data Classes
DetectionDetails
Contains detailed information about meeting detection.
Attributes:
active(bool): Whether a meeting is currently activescore(int): Detection score (for backward compatibility)app_name(str | None): Name of the meeting applicationreason(str): Detection reason (e.g., "NativeAppWithNetwork(Zoom)")meeting_url(str | None): Meeting URL (for browser-based meetings)signals(SignalsBreakdown): Breakdown of detection signals
SignalsBreakdown
Breakdown of all detection signals.
Attributes:
meeting_app(SignalDetails): Meeting app detection signalmeeting_window(SignalDetails): Meeting window detection signalmicrophone(SignalDetails): Microphone active signalcamera(SignalDetails): Camera active signal
SignalDetails
Information about a specific detection signal.
Attributes:
active(bool): Whether this signal is activeweight(int): Weight of this signal in the overall score
How It Works
Two-Tier Detection Algorithm
The library uses a two-tier detection algorithm to accurately identify active meetings:
Tier 1: Native Meeting Apps
For native apps (Zoom desktop, Teams desktop, Webex desktop), network connections are the primary signal:
- Zoom: UDP port 8801 is a strong indicator (works with IP addresses)
- Teams/Webex: STUN ports (3478-3481) or meeting domains with ESTABLISHED connections
- Decision: If network connections active → MEETING ACTIVE
Tier 2: Browser-Based Meetings
For browser-based meetings (Google Meet, Teams web, Webex web), browser tab URLs are definitive:
- Google Meet: Validates meeting code format (xxx-yyyy-zzz), excludes landing pages
- Teams/Webex web: Pattern matching on meeting URLs
- Decision: If meeting URL detected → MEETING ACTIVE
Google Meet Code Validation
Google Meet URLs require special validation to avoid false positives:
- Format:
xxx-yyyy-zzz(3 segments separated by hyphens) - Each segment: 2-5 lowercase letters only
- Total: 8-15 characters (excluding hyphens)
- Excludes:
/landing,/new,/join, empty paths
Example valid codes:
abc-def-ghi✅cih-fjjf-pfd✅
Example invalid:
abc-def❌ (only 2 segments)ABC-def-ghi❌ (uppercase)abc-d3f-ghi❌ (contains digit)
Development
Running Tests
# Run all tests
pytest
# Run with coverage
pytest --cov=meeting_detection --cov-report=html
# Run specific test file
pytest tests/test_config.py
# Run manual validation
python tests/manual_test.py
Running Examples
# Basic usage
python examples/basic_usage.py
# Event-based monitoring
python examples/monitor_meetings.py
# Manual validation
python tests/manual_test.py
Project Structure
meeting_detection/
├── meeting_detection/ # Main package
│ ├── __init__.py # Public API
│ ├── models.py # Data models
│ ├── config.py # Configuration and patterns
│ ├── network.py # Network detection (lsof)
│ ├── detector.py # Two-tier detection algorithm
│ ├── engine.py # Background polling engine
│ └── platform/ # Platform-specific code
│ ├── base.py # Abstract interface
│ └── macos.py # macOS implementation
├── tests/ # Unit tests
│ ├── test_config.py # Config and validation tests
│ ├── test_network.py # Network detection tests
│ └── test_detector.py # Detection algorithm tests
├── examples/ # Usage examples
│ ├── basic_usage.py
│ └── monitor_meetings.py
└── requirements.txt # Dependencies
Accuracy Parity with Rust Implementation
This Python implementation is designed to maintain 100% accuracy parity with the original Rust implementation. Critical components that ensure this:
-
Google Meet Validation (
config.py:is_valid_google_meet_code)- Exact port of Rust logic for meeting code validation
- Prevents false positives from landing pages
-
Network Detection (
network.py:detect_meeting_network_activity)- Zoom UDP port 8801 detection (works without domain matching)
- Teams STUN ports (3478-3481)
- Proper handling of connection states (ESTABLISHED, CLOSED, UNKNOWN)
-
Two-Tier Algorithm (
detector.py:detect)- Native apps checked before browsers
- Browsers skipped in Tier 1, handled in Tier 2
- State transitions tracked correctly
Troubleshooting
Permissions
The library requires certain macOS permissions:
- Terminal/Python: May need accessibility permissions for
osascript - Network Access: Uses
lsofto check network connections (no special permissions required)
Debugging
Enable debug logging:
import logging
logging.basicConfig(level=logging.DEBUG)
Get detailed detection information:
details = get_last_detection_details()
if details:
print(f"Active: {details.active}")
print(f"Reason: {details.reason}")
print(f"Score: {details.score}")
print(f"Signals: {details.signals}")
License
MIT
Credits
Python port based on the original Rust/Node.js implementation with 100% accuracy parity.
Metadata
Release files for meeting-detection 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| meeting_detection-1.0.0.tar.gz | 21.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| meeting_detection-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.0 kB
Release files / meeting_detection-1.0.0.tar.gz
| Download URL | meeting_detection-1.0.0.tar.gz |
|---|---|
| Size | 21.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b713e74cdfe27013eb8ceae5533c98af6feeffb449d7ee2612d0ac01651f6765
|
|
BLAKE2b-256 checksum How to use checksums |
2b156006db00c462de683dfc34f2ca5d8d16cbe020c2404d339cda1a4433d1e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|
Release files / meeting_detection-1.0.0-py3-none-any.whl
| Download URL | meeting_detection-1.0.0-py3-none-any.whl |
|---|---|
| Size | 22.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c7cb3337b4d098121d52570c5a93d9a8b101a711a423d4272ba3608e6b38118e
|
|
BLAKE2b-256 checksum How to use checksums |
fb598a19523afb6b3ad7534ef43daceaedb00e0ba77df95eecd59954abdc4e41
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|