Thank you for your interest in contributing to EXStreamTV! This document provides guidelines and information for contributors.
- Code of Conduct
- Getting Started
- Development Setup
- Making Changes
- Pull Request Process
- Coding Standards
- Testing
- Documentation
- Release Process
This project follows a simple code of conduct:
- Be respectful and inclusive
- Welcome newcomers and help them learn
- Accept constructive criticism gracefully
- Focus on what's best for the community
- Show empathy towards others
- Check the GitHub Issues for open tasks
- Look for issues labeled
good first issueif you're new help wantedlabels indicate issues where contributions are especially welcome- Comment on an issue before starting work to avoid duplicate efforts
We welcome:
- Bug fixes - Fix issues and improve stability
- Features - Implement new functionality
- Documentation - Improve guides and API docs
- Tests - Add or improve test coverage
- Performance - Optimize code and reduce resource usage
- UI/UX - Enhance the web interface
- Python 3.10 or higher
- FFmpeg 5.0+
- Git
- Node.js (for frontend development, optional)
git clone https://github.com/roto31/EXStreamTV.git
cd EXStreamTV# Create virtual environment
python3 -m venv venv
source venv/bin/activate # or .\venv\Scripts\Activate.ps1 on Windows
# Install development dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt
# Install in editable mode
pip install -e ".[dev]"# Run tests
pytest
# Start development server
python -m exstreamtv --debugVS Code (Recommended):
// .vscode/settings.json
{
"python.defaultInterpreterPath": "./venv/bin/python",
"python.formatting.provider": "black",
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"editor.formatOnSave": true
}PyCharm:
- Open the project folder
- Configure interpreter: Settings → Project → Python Interpreter → Select
venv - Enable Black formatter: Settings → Tools → Black
# Create a feature branch from main
git checkout main
git pull origin main
git checkout -b feature/your-feature-name
# For bug fixes
git checkout -b fix/issue-description
# For documentation
git checkout -b docs/topic-nameFollow the Conventional Commits specification:
type(scope): description
[optional body]
[optional footer]
Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style (formatting, missing semicolons)refactor: Code refactoringperf: Performance improvementtest: Adding or updating testschore: Maintenance tasks
Examples:
git commit -m "feat(channels): add bulk import from M3U"
git commit -m "fix(streaming): resolve buffer overflow on long streams"
git commit -m "docs(api): add examples for library endpoints"# Regularly sync with main
git fetch origin
git rebase origin/main-
Run all tests
pytest
-
Check code formatting
black --check . flake8 -
Update documentation if you've changed APIs or behavior
-
Add tests for new functionality
-
Push your branch:
git push origin feature/your-feature-name
-
Open a Pull Request on GitHub
-
Fill out the PR template:
- Description of changes
- Related issue(s)
- Type of change
- Testing performed
- Screenshots (for UI changes)
- At least one maintainer must approve
- All CI checks must pass
- Discussions should be resolved
- Branch must be up to date with main
Your changes will be included in the next release. Thank you for contributing!
We follow PEP 8 with some modifications:
- Line length: 100 characters maximum
- Imports: Sorted with
isort - Formatting: Handled by
black - Type hints: Required for public APIs
# Good
from typing import Optional, List
async def get_channels(
group: Optional[str] = None,
limit: int = 100,
) -> List[Channel]:
"""
Retrieve channels with optional filtering.
Args:
group: Filter by channel group name
limit: Maximum number of results
Returns:
List of Channel objects
"""
...exstreamtv/
├── api/ # FastAPI routes
├── database/ # SQLAlchemy models and repositories
├── media/ # Media handling (libraries, scanning)
├── streaming/ # Stream management
├── transcoding/ # FFmpeg integration
├── templates/ # Jinja2 HTML templates
├── static/ # CSS, JS assets
└── utils/ # Shared utilities
- Files:
snake_case.py - Classes:
PascalCase - Functions/Variables:
snake_case - Constants:
UPPER_SNAKE_CASE - Private members:
_leading_underscore
# Use specific exceptions
from exstreamtv.exceptions import ChannelNotFoundError, StreamingError
async def get_channel(channel_id: int) -> Channel:
channel = await repository.get(channel_id)
if not channel:
raise ChannelNotFoundError(f"Channel {channel_id} not found")
return channel- Use
async/awaitfor I/O operations - Avoid blocking calls in async functions
- Use
asyncio.gather()for concurrent operations
# Good - concurrent execution
results = await asyncio.gather(
fetch_channel_info(channel_id),
fetch_channel_schedule(channel_id),
fetch_channel_stats(channel_id),
)
# Avoid - sequential when concurrent is possible
info = await fetch_channel_info(channel_id)
schedule = await fetch_channel_schedule(channel_id)
stats = await fetch_channel_stats(channel_id)tests/
├── unit/ # Fast, isolated tests
├── integration/ # API and database tests
├── e2e/ # End-to-end workflow tests
└── fixtures/ # Shared test data
# Run all tests
pytest
# Run specific test file
pytest tests/unit/test_channels.py
# Run with coverage
pytest --cov=exstreamtv --cov-report=html
# Run only fast unit tests
pytest tests/unit/ -x
# Run integration tests
pytest tests/integration/import pytest
from exstreamtv.api.channels import create_channel
class TestChannels:
"""Tests for channel operations."""
@pytest.fixture
def sample_channel(self):
return {
"number": 1,
"name": "Test Channel",
"group": "Test"
}
async def test_create_channel_success(self, client, sample_channel):
"""Creating a channel should return 201 with channel data."""
response = await client.post("/api/channels", json=sample_channel)
assert response.status_code == 201
data = response.json()
assert data["name"] == sample_channel["name"]
assert "id" in data
async def test_create_channel_duplicate_number(self, client, sample_channel):
"""Creating a channel with duplicate number should fail."""
await client.post("/api/channels", json=sample_channel)
response = await client.post("/api/channels", json=sample_channel)
assert response.status_code == 409We aim for 80%+ test coverage. Check coverage locally:
pytest --cov=exstreamtv --cov-report=term-missingdocs/
├── api/ # API reference
├── architecture/ # System design docs
└── guides/ # User guides
- Use clear, concise language
- Include code examples
- Add screenshots for UI features
- Keep docs up to date with code changes
# Install mkdocs
pip install mkdocs mkdocs-material
# Serve locally
mkdocs serve
# Build static site
mkdocs buildUse Google-style docstrings:
def scan_library(
library_id: int,
full_scan: bool = False,
) -> ScanResult:
"""
Scan a media library for new and updated content.
Args:
library_id: The ID of the library to scan.
full_scan: If True, rescan all files. If False, only scan
new or modified files.
Returns:
ScanResult containing counts and any errors.
Raises:
LibraryNotFoundError: If the library doesn't exist.
ScanInProgressError: If a scan is already running.
Example:
>>> result = await scan_library(1, full_scan=True)
>>> print(f"Found {result.new_items} new items")
"""Releases are managed by maintainers using semantic versioning.
- MAJOR.MINOR.PATCH (e.g., 1.6.0)
- MAJOR: Breaking changes
- MINOR: New features (backwards compatible)
- PATCH: Bug fixes
All notable changes are documented in CHANGELOG.md:
## [1.6.0] - 2024-01-20
### Added
- Local media library support (#123)
- Hardware transcoding profiles (#145)
### Fixed
- Stream buffer overflow on long playback (#156)
- EPG timezone handling (#160)
### Changed
- Improved scanning performance by 40%- Questions: Open a Discussion
- Bugs: Open an Issue
- Chat: Join our Discord
By contributing, you agree that your contributions will be licensed under the MIT License.
Thank you for contributing to EXStreamTV! 🎬