Initial commit: TVH2Jellyfin application
A Docker-based Python application that transfers completed recordings from TVHeadend to Jellyfin media storage via SCP. Features: - Monitors TVHeadend for completed recordings via JSON API - Transfers files via SCP with SSH key authentication - Organizes files by show name - Deletes recordings from TVHeadend after successful transfer - Tracks transferred files to avoid duplicates - Dry run mode for testing without making changes
This commit is contained in:
211
README.md
Normal file
211
README.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# TVH2Jellyfin
|
||||
|
||||
Transfer completed recordings from TVHeadend to Jellyfin media storage.
|
||||
|
||||
## Features
|
||||
|
||||
- Monitors TVHeadend for completed recordings via JSON API
|
||||
- Pulls files from TVHeadend server via SCP
|
||||
- Writes to local Jellyfin media directory (Docker volume)
|
||||
- Organizes files by show name: `{dest_path}/{show_name}/{episode}.ts`
|
||||
- Deletes recordings from TVHeadend after successful transfer
|
||||
- Tracks transferred files to avoid duplicates
|
||||
- Runs as a Docker container
|
||||
- Dry run mode for testing without making changes
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. SSH key access from the Docker host to the TVHeadend server
|
||||
2. TVHeadend API access (username/password)
|
||||
3. Docker and Docker Compose
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Generate SSH Key (if needed)
|
||||
|
||||
```bash
|
||||
ssh-keygen -t rsa -b 4096 -f ~/.ssh/tvh2jellyfin
|
||||
ssh-copy-id -i ~/.ssh/tvh2jellyfin.pub user@tvheadend-server
|
||||
```
|
||||
|
||||
### 2. Configure Environment
|
||||
|
||||
Copy the example environment file and edit with your settings:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env` with your configuration:
|
||||
|
||||
```bash
|
||||
# TVHeadend server
|
||||
TVH_HOST=192.168.1.100
|
||||
TVH_PORT=9981
|
||||
TVH_USER=admin
|
||||
TVH_PASS=your_password
|
||||
|
||||
# SSH user on TVHeadend server (defaults to TVH_USER)
|
||||
TVH_SSH_USER=admin
|
||||
|
||||
# SSH key path on Docker host
|
||||
SSH_KEY_PATH=~/.ssh/tvh2jellyfin
|
||||
|
||||
# Jellyfin media library path on Docker host
|
||||
JELLYFIN_MEDIA_PATH=/mnt/jellyfin/tv
|
||||
|
||||
# Poll interval in seconds (default: 300 = 5 minutes)
|
||||
POLL_INTERVAL=300
|
||||
```
|
||||
|
||||
### 3. Build and Run
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 4. View Logs
|
||||
|
||||
```bash
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `TVH_HOST` | TVHeadend server hostname/IP | `localhost` |
|
||||
| `TVH_PORT` | TVHeadend HTTP port | `9981` |
|
||||
| `TVH_USER` | TVHeadend API username | (required) |
|
||||
| `TVH_PASS` | TVHeadend API password | (required) |
|
||||
| `TVH_SSH_USER` | SSH username for TVHeadend server | `TVH_USER` |
|
||||
| `SSH_KEY_PATH` | Path to SSH private key on Docker host | `~/.ssh/id_rsa` |
|
||||
| `JELLYFIN_MEDIA_PATH` | Jellyfin media path on Docker host | `/mnt/jellyfin/tv` |
|
||||
| `POLL_INTERVAL` | Seconds between checks | `300` |
|
||||
| `LOG_LEVEL` | Logging level | `INFO` |
|
||||
| `DRY_RUN` | Test mode (no transfers/deletions) | `false` |
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ TVHeadend Server │ │ Docker Host │
|
||||
│ │ │ │
|
||||
│ ┌───────────────┐ │ HTTP API │ ┌───────────────┐ │
|
||||
│ │ TVHeadend │◄─┼────────────────────┼──│ tvh2jellyfin │ │
|
||||
│ │ API (:9981) │ │ │ │ container │ │
|
||||
│ └───────────────┘ │ │ └───────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌───────────────┐ │ SCP (SSH) │ ▼ │
|
||||
│ │ Recordings │──┼────────────────────┼──►┌─────────────┐ │
|
||||
│ │ /recordings │ │ │ │ /media/tv │ │
|
||||
│ └───────────────┘ │ │ │ (Jellyfin) │ │
|
||||
│ │ │ └─────────────┘ │
|
||||
└─────────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
1. Container queries TVHeadend API for completed recordings
|
||||
2. Downloads files via SCP from TVHeadend server
|
||||
3. Writes to local Jellyfin media volume
|
||||
4. Removes recording from TVHeadend via API
|
||||
|
||||
## File Organization
|
||||
|
||||
Recordings are organized by show name:
|
||||
|
||||
```
|
||||
/mnt/jellyfin/tv/
|
||||
├── Show Name/
|
||||
│ ├── Show Name - Episode Title.ts
|
||||
│ └── Show Name - 2024-01-15 2000.ts (if no episode title)
|
||||
└── Another Show/
|
||||
└── Another Show - Episode.ts
|
||||
```
|
||||
|
||||
## Dry Run Mode
|
||||
|
||||
Test what would happen without actually transferring or deleting anything:
|
||||
|
||||
```bash
|
||||
DRY_RUN=true docker compose up
|
||||
```
|
||||
|
||||
In dry run mode:
|
||||
- Connects to TVHeadend API and lists completed recordings
|
||||
- Shows what files would be transferred and where
|
||||
- Shows which recordings have already been transferred
|
||||
- Does NOT connect via SSH
|
||||
- Does NOT transfer any files
|
||||
- Does NOT delete recordings from TVHeadend
|
||||
- Does NOT update the state file
|
||||
- Exits after a single check (no polling loop)
|
||||
|
||||
Example output:
|
||||
```
|
||||
[DRY RUN] Starting sync cycle...
|
||||
[DRY RUN] Found 2 new recording(s) to transfer
|
||||
[DRY RUN] Would process: The News - Evening Edition
|
||||
[DRY RUN] Source: /recordings/The News - Evening Edition.ts
|
||||
[DRY RUN] Destination: /media/tv/The News/The News - Evening Edition.ts
|
||||
[DRY RUN] Size: 1250.5 MB
|
||||
[DRY RUN] Would delete from TVHeadend after transfer
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
|
||||
### Run Locally
|
||||
|
||||
```bash
|
||||
export TVH_HOST=...
|
||||
export TVH_USER=...
|
||||
export TVH_PASS=...
|
||||
export TVH_SSH_USER=...
|
||||
export DEST_PATH=/path/to/destination
|
||||
python -m tvh2jellyfin.main
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### SSH Connection Issues
|
||||
|
||||
Ensure the SSH key has correct permissions:
|
||||
```bash
|
||||
chmod 600 ~/.ssh/tvh2jellyfin
|
||||
```
|
||||
|
||||
Test SSH connection manually:
|
||||
```bash
|
||||
ssh -i ~/.ssh/tvh2jellyfin user@tvheadend-server
|
||||
```
|
||||
|
||||
### TVHeadend API Issues
|
||||
|
||||
Test API access:
|
||||
```bash
|
||||
curl -u admin:password http://tvheadend:9981/api/serverinfo
|
||||
```
|
||||
|
||||
### Permission Issues
|
||||
|
||||
Ensure the Jellyfin media directory is writable:
|
||||
```bash
|
||||
touch /mnt/jellyfin/tv/.test && rm /mnt/jellyfin/tv/.test
|
||||
```
|
||||
|
||||
### View Transfer History
|
||||
|
||||
The state file (`data/state.json`) contains all transfer records.
|
||||
Reference in New Issue
Block a user