Critical fixes for video streaming: - Fix range request handling by using HEAD request first - Get total file size before processing range requests - Properly calculate Content-Range header with accurate boundaries - Prevent issues where object.size wasn't available on range requests - Add proper CORS headers for video streaming - Expose Content-Length, Content-Range, Accept-Ranges headers - Allow Range header in requests - Enables video seeking and progressive loading in browsers - Improve range request logic - Ensure end byte doesn't exceed file size - Calculate correct Content-Length for partial responses - Always return accurate byte ranges in 206 responses These fixes resolve issues where videos wouldn't play or seek properly due to incorrect Content-Range headers and missing CORS headers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> |
||
|---|---|---|
| obs_scripts | ||
| obs_uploader | ||
| scripts | ||
| streaming | ||
| worker | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| ADMIN.md | ||
| ADMIN_QUICKSTART.md | ||
| DEPLOY_UPDATES.md | ||
| Dockerfile | ||
| LICENSE | ||
| OBS_SETUP.md | ||
| QUICK_SETUP.md | ||
| README.md | ||
| SETUP.md | ||
| UPLOAD_FEATURE.md | ||
| cors-config.json | ||
| docker-compose.yml | ||
| package.json | ||
| requirements.txt | ||
| start-obs-watcher.sh | ||
README.md
OBS to Cloudflare R2 Direct Upload System
Upload OBS Studio recordings directly to Cloudflare R2 storage with public video sharing capabilities. No YouTube required!
Features
- Direct Upload: Upload video files to Cloudflare R2 with a single command
- Progress Tracking: Real-time upload progress with visual feedback
- Auto-Upload: File watcher automatically uploads new OBS recordings
- Public Sharing: Instantly shareable video URLs via videos.jeffemmett.com
- Video Gallery: Beautiful web gallery to browse all your videos
- Admin Panel: Web-based admin interface to manage video visibility
- Privacy Controls: Set videos as Private, Shareable, or Clip-Shareable
- Clip Generation: Create shareable time-based clips from longer videos
- Clipboard Integration: Public URLs automatically copied to clipboard
- Large File Support: Multipart uploads for videos over 100MB
- Cross-Platform: Works on Windows, macOS, and Linux
Quick Start
1. Installation
# Run the setup script
./scripts/setup.sh
# Authenticate with Cloudflare
wrangler login
2. Configuration
Edit .env file with your Cloudflare R2 credentials:
R2_ACCOUNT_ID=your_account_id
R2_ACCESS_KEY_ID=your_access_key
R2_SECRET_ACCESS_KEY=your_secret_key
R2_BUCKET_NAME=obs-videos
PUBLIC_DOMAIN=videos.jeffemmett.com
3. Deploy
# Create R2 bucket and deploy worker
./scripts/deploy.sh
4. Upload Your First Video
# Upload a video
./scripts/upload.sh /path/to/video.mp4
# The public URL will be displayed and copied to your clipboard
Usage
Manual Upload
Upload a single video file:
./scripts/upload.sh /path/to/video.mp4
Upload with custom name:
./scripts/upload.sh /path/to/video.mp4 my-awesome-video.mp4
Automatic Upload (File Watcher)
Watch a directory and auto-upload new recordings:
# Watch a specific directory
./scripts/start-watcher.sh /path/to/obs/recordings
# Or set OBS_RECORDING_DIR in .env and run:
./scripts/start-watcher.sh
The file watcher will:
- Monitor the directory for new video files
- Wait until recording is complete (file size stable)
- Automatically upload the video
- Copy the public URL to clipboard
- Optionally delete the local file (if
AUTO_DELETE_AFTER_UPLOAD=true)
View Your Videos
- Direct Access:
https://videos.jeffemmett.com/video-name.mp4 - Gallery View:
https://videos.jeffemmett.com/gallery - JSON API:
https://videos.jeffemmett.com/orhttps://videos.jeffemmett.com/api/list - Admin Panel:
https://videos.jeffemmett.com/admin(requires authentication)
Admin Panel
Manage your video library with a powerful web-based admin interface.
Features
-
📤 Manual Upload: Drag-and-drop video files directly in the browser
-
Video Visibility Control
- 🔒 Private: Only you can view
- 🔗 Shareable: Anyone with link can view
- ✂️ Clip Shareable: Only time-based clips are shareable
-
Clip Generation: Create shareable clips with start/end times
-
Bulk Management: Search, filter, and manage multiple videos
-
Delete Videos: Remove videos directly from the admin panel
-
Statistics Dashboard: See breakdown of video visibility
-
Upload Progress: Real-time progress tracking for uploads
-
Empty State: Helpful UI when no videos exist
Setup Admin Panel
# Setup KV namespace and admin password
./scripts/setup-admin.sh
# Build worker with embedded admin interface
python3 scripts/build-worker.py
# Deploy
cd worker && wrangler deploy
Access admin panel at: https://videos.jeffemmett.com/admin
See ADMIN.md for complete admin documentation.
Project Structure
obs-r2-uploader/
├── obs_uploader/ # Python upload package
│ ├── upload.py # Core upload script
│ ├── file_watcher.py # Auto-upload daemon
│ └── config.py # Configuration management
├── worker/ # Cloudflare Worker
│ ├── video-server.js # Video serving logic
│ └── wrangler.toml # Worker configuration
├── scripts/ # Convenience scripts
│ ├── setup.sh # One-command setup
│ ├── deploy.sh # Deploy worker
│ ├── upload.sh # Upload wrapper
│ └── start-watcher.sh # Start file watcher
├── .env # Your configuration (not in git)
├── .env.example # Configuration template
└── requirements.txt # Python dependencies
Configuration Options
All configuration is done via .env file:
| Variable | Required | Description |
|---|---|---|
R2_ACCOUNT_ID |
Yes | Your Cloudflare account ID |
R2_ACCESS_KEY_ID |
Yes | R2 API access key |
R2_SECRET_ACCESS_KEY |
Yes | R2 API secret key |
R2_BUCKET_NAME |
Yes | Name of R2 bucket (default: obs-videos) |
R2_ENDPOINT |
Auto | R2 endpoint URL (auto-generated) |
PUBLIC_DOMAIN |
Yes | Your public domain (videos.jeffemmett.com) |
OBS_RECORDING_DIR |
No | Default directory for file watcher |
AUTO_DELETE_AFTER_UPLOAD |
No | Delete local file after upload (default: false) |
Supported Video Formats
- MP4 (
.mp4) - MKV (
.mkv) - MOV (
.mov) - AVI (
.avi) - WebM (
.webm) - FLV (
.flv) - WMV (
.wmv)
Advanced Usage
Python Module
Use as a Python module in your own scripts:
from obs_uploader.config import Config
from obs_uploader.upload import R2Uploader
# Load configuration
config = Config()
# Create uploader
uploader = R2Uploader(config)
# Upload a file
public_url = uploader.upload_file(Path("/path/to/video.mp4"))
print(f"Uploaded: {public_url}")
Custom Worker Routes
Edit worker/wrangler.toml to customize routes and domains:
[env.production]
routes = [
{ pattern = "videos.jeffemmett.com/*", custom_domain = true }
]
Gallery Customization
Edit worker/video-server.js to customize the gallery appearance and functionality.
Troubleshooting
Upload fails with "Invalid credentials"
- Verify your R2 API credentials in
.env - Ensure the API token has read/write permissions for R2
- Check that the bucket name matches
Videos not accessible via public URL
- Verify CORS is configured (see SETUP.md)
- Check that custom domain is set up in Cloudflare dashboard
- Ensure worker is deployed:
cd worker && wrangler deploy
File watcher not detecting new files
- Verify the watch directory path is correct
- Check file permissions on the directory
- Ensure video file format is supported
Clipboard not working on Linux
Install xclip or xsel:
sudo apt-get install xclip
# or
sudo apt-get install xsel
Performance
- Small files (<100MB): Simple upload with progress bar
- Large files (>100MB): Automatic multipart upload with 10MB chunks
- Network resilience: Automatic retries with exponential backoff
- CDN caching: Cloudflare automatically caches videos globally
Security
- All R2 credentials stored in
.env(gitignored) - Videos are publicly accessible by URL (no authentication)
- Consider signed URLs for sensitive content (not implemented yet)
- CORS configured for browser access
Cost Considerations
Cloudflare R2 pricing (as of 2024):
- Storage: $0.015/GB per month
- Class A Operations (writes): $4.50 per million requests
- Class B Operations (reads): $0.36 per million requests
- Egress: FREE (no bandwidth charges!)
Example costs for 100GB of videos:
- Storage: $1.50/month
- 1000 uploads: ~$0.005
- 10,000 views: ~$0.004
Contributing
This is a personal project, but feel free to fork and adapt for your own use!
License
MIT License - See LICENSE file for details
Acknowledgments
Built with:
- Cloudflare R2 - Object storage
- Cloudflare Workers - Edge computing
- boto3 - AWS SDK for Python
- watchdog - File system monitoring
Made with ❤️ by Jeff Emmett