Python library for detecting active video meetings on macOS
Project description
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.
Project details
Release history Release notifications | RSS feed
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 meeting_detection-1.0.0.tar.gz.
File metadata
- Download URL: meeting_detection-1.0.0.tar.gz
- Upload date:
- Size: 21.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b713e74cdfe27013eb8ceae5533c98af6feeffb449d7ee2612d0ac01651f6765
|
|
| MD5 |
879bc9c0f53e07a107b91dace096ecf1
|
|
| BLAKE2b-256 |
2b156006db00c462de683dfc34f2ca5d8d16cbe020c2404d339cda1a4433d1e1
|
File details
Details for the file meeting_detection-1.0.0-py3-none-any.whl.
File metadata
- Download URL: meeting_detection-1.0.0-py3-none-any.whl
- Upload date:
- Size: 22.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c7cb3337b4d098121d52570c5a93d9a8b101a711a423d4272ba3608e6b38118e
|
|
| MD5 |
e9afa586df2702313434cd7efc7d23e0
|
|
| BLAKE2b-256 |
fb598a19523afb6b3ad7534ef43daceaedb00e0ba77df95eecd59954abdc4e41
|