containerize & enhance filtering
All checks were successful
Build & Push Docker Image / build-and-publish (push) Successful in 11s
All checks were successful
Build & Push Docker Image / build-and-publish (push) Successful in 11s
This commit is contained in:
31
.dockerignore
Normal file
31
.dockerignore
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Git files
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.gitea
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Python cache
|
||||||
|
__pycache__
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
36
.env.example
Normal file
36
.env.example
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Copy to .env: cp .env.example .env
|
||||||
|
|
||||||
|
# Required - Emby Server Configuration
|
||||||
|
EMBY_SERVER_URL=https://your-emby-server.com
|
||||||
|
EMBY_API_KEY=your_api_key_here
|
||||||
|
|
||||||
|
# Mode: copy (default) or clear
|
||||||
|
MODE=copy
|
||||||
|
|
||||||
|
# Safety: true = dry run, false = execute
|
||||||
|
DRY_RUN=true
|
||||||
|
|
||||||
|
# Test on first channel only
|
||||||
|
FIRST_ONLY=false
|
||||||
|
|
||||||
|
# Overwrite existing logos (copy mode only)
|
||||||
|
FORCE=false
|
||||||
|
|
||||||
|
# Filter by tags (comma-separated, e.g. sports,news)
|
||||||
|
TAGS=
|
||||||
|
|
||||||
|
# Cron schedule (minute hour day month weekday)
|
||||||
|
# Examples: 0 3 * * * (daily 3am), 0 */6 * * * (every 6hrs)
|
||||||
|
CRON_SCHEDULE=0 3 * * *
|
||||||
|
|
||||||
|
# Run once and exit (ignores cron)
|
||||||
|
RUN_ONCE=false
|
||||||
|
|
||||||
|
# Timezone (e.g. America/New_York, Europe/London)
|
||||||
|
TZ=UTC
|
||||||
|
|
||||||
|
# --- Example Configurations ---
|
||||||
|
# Test: DRY_RUN=true FIRST_ONLY=true
|
||||||
|
# Production: DRY_RUN=false MODE=copy
|
||||||
|
# By tags: TAGS=sports,news DRY_RUN=false
|
||||||
|
# Run now: RUN_ONCE=true DRY_RUN=false
|
||||||
32
.gitea/workflows/build-image.yml
Normal file
32
.gitea/workflows/build-image.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Build & Push Docker Image
|
||||||
|
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Output Repository Information
|
||||||
|
run: |
|
||||||
|
echo This workflow will build and push a Docker image to:
|
||||||
|
echo ${{ vars.REPO_ADDRESS }}/${{ vars.REPO_USER }}/${{ vars.REPO_NAME }}:latest
|
||||||
|
echo https://${{ vars.REPO_ADDRESS }}/${{ vars.REPO_USER }}/${{ vars.REPO_NAME }}
|
||||||
|
|
||||||
|
- name: Build Docker Image
|
||||||
|
run: |
|
||||||
|
docker build -t ${{ vars.REPO_ADDRESS }}/${{ vars.REPO_USER }}/${{ vars.REPO_NAME }}:latest --output type=docker --platform linux/amd64 .
|
||||||
|
|
||||||
|
- name: Log in to Container Repo
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ vars.REPO_USER }}
|
||||||
|
registry: ${{ vars.REPO_ADDRESS }}
|
||||||
|
password: ${{ secrets.REPO_TOKEN }}
|
||||||
|
|
||||||
|
- name: Push Docker Image
|
||||||
|
run: |
|
||||||
|
echo "Pushing Docker image with tag: latest to ${{ vars.REPO_ADDRESS }}"
|
||||||
|
docker push ${{ vars.REPO_ADDRESS }}/${{ vars.REPO_USER }}/${{ vars.REPO_NAME }}:latest
|
||||||
|
docker rmi ${{ vars.REPO_ADDRESS }}/${{ vars.REPO_USER }}/${{ vars.REPO_NAME }}:latest || true
|
||||||
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Install cron and timezone handling
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y cron tzdata && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create app directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the Python script
|
||||||
|
COPY update_channel_logos.py /app/
|
||||||
|
|
||||||
|
# Copy the entrypoint script
|
||||||
|
COPY entrypoint.sh /app/
|
||||||
|
|
||||||
|
# Make the entrypoint executable
|
||||||
|
RUN chmod +x /app/entrypoint.sh
|
||||||
|
|
||||||
|
# Set timezone (can be overridden)
|
||||||
|
ENV TZ=UTC
|
||||||
|
|
||||||
|
# Run the entrypoint
|
||||||
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
194
README.md
194
README.md
@@ -7,9 +7,42 @@ Automatically copy or clear Live TV channel logos in Emby Server.
|
|||||||
- **Copy Mode** (default): Copies the Primary logo (Logo Dark Version) to LogoLight (Logo Light Version) and LogoLightColor (Logo Light with Colour) for all TV channels
|
- **Copy Mode** (default): Copies the Primary logo (Logo Dark Version) to LogoLight (Logo Light Version) and LogoLightColor (Logo Light with Colour) for all TV channels
|
||||||
- **Clear Mode** (`--clear`): Removes all logos (Primary, LogoLight, LogoLightColor) from channels
|
- **Clear Mode** (`--clear`): Removes all logos (Primary, LogoLight, LogoLightColor) from channels
|
||||||
|
|
||||||
|
## Quick Start (Docker - Recommended)
|
||||||
|
|
||||||
|
1. **Copy the environment template:**
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Edit `.env` and set your Emby credentials:**
|
||||||
|
```bash
|
||||||
|
EMBY_SERVER_URL=https://your-emby-server.com
|
||||||
|
EMBY_API_KEY=your_api_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Test with dry run on first channel:**
|
||||||
|
```bash
|
||||||
|
docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **When ready, enable execution:**
|
||||||
|
Edit `.env` and set:
|
||||||
|
```bash
|
||||||
|
DRY_RUN=false
|
||||||
|
FIRST_ONLY=false
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Run in the background:**
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [Docker Usage](#docker-usage-recommended) section for detailed configuration.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Python 3.6+
|
- **Docker** (recommended): Docker and Docker Compose - uses pre-built image from GHCR
|
||||||
|
- **Python** (alternative): Python 3.6+
|
||||||
- Emby Server with API access
|
- Emby Server with API access
|
||||||
- API key from Emby Server
|
- API key from Emby Server
|
||||||
|
|
||||||
@@ -19,7 +52,150 @@ Automatically copy or clear Live TV channel logos in Emby Server.
|
|||||||
2. Go to Settings → Advanced → API Keys
|
2. Go to Settings → Advanced → API Keys
|
||||||
3. Create a new API key or use an existing one
|
3. Create a new API key or use an existing one
|
||||||
|
|
||||||
## Usage
|
## Docker Usage (Recommended)
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
All configuration is done via the `.env` file. Copy `.env.example` to `.env` and customize:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Required Settings
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `EMBY_SERVER_URL` | Your Emby server URL (e.g., `https://emby.example.com`) |
|
||||||
|
| `EMBY_API_KEY` | Your Emby API key |
|
||||||
|
|
||||||
|
#### Optional Settings
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `MODE` | `copy` | Operation mode: `copy` (copy logos) or `clear` (delete logos) |
|
||||||
|
| `DRY_RUN` | `true` | `true` = simulate only, `false` = execute changes |
|
||||||
|
| `FIRST_ONLY` | `false` | `true` = process first channel only (for testing) |
|
||||||
|
| `FORCE` | `false` | `true` = overwrite existing logos (copy mode only) |
|
||||||
|
| `TAGS` | _(empty)_ | Comma-separated list of tags to filter channels (e.g., `sports,news`) |
|
||||||
|
| `CRON_SCHEDULE` | `0 3 * * *` | Cron schedule (default: daily at 3 AM) |
|
||||||
|
| `RUN_ONCE` | `false` | `true` = run once and exit, `false` = run on schedule |
|
||||||
|
| `TZ` | `UTC` | Timezone for cron (e.g., `America/New_York`) |
|
||||||
|
|
||||||
|
### Common Scenarios
|
||||||
|
|
||||||
|
#### Safe Testing (Recommended First Run)
|
||||||
|
```bash
|
||||||
|
# In .env:
|
||||||
|
MODE=copy
|
||||||
|
DRY_RUN=true
|
||||||
|
FIRST_ONLY=true
|
||||||
|
|
||||||
|
# Run:
|
||||||
|
docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Production - Daily Updates
|
||||||
|
```bash
|
||||||
|
# In .env:
|
||||||
|
MODE=copy
|
||||||
|
DRY_RUN=false
|
||||||
|
CRON_SCHEDULE=0 3 * * *
|
||||||
|
|
||||||
|
# Run in background:
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### One-Time Update Now
|
||||||
|
```bash
|
||||||
|
# In .env:
|
||||||
|
MODE=copy
|
||||||
|
DRY_RUN=false
|
||||||
|
RUN_ONCE=true
|
||||||
|
|
||||||
|
# Run:
|
||||||
|
docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Force Overwrite Existing Logos
|
||||||
|
```bash
|
||||||
|
# In .env:
|
||||||
|
MODE=copy
|
||||||
|
DRY_RUN=false
|
||||||
|
FORCE=true
|
||||||
|
|
||||||
|
# Run:
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Clear All Logos (Use with Caution!)
|
||||||
|
```bash
|
||||||
|
# In .env:
|
||||||
|
MODE=clear
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
# Run:
|
||||||
|
docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Process Only Channels with Specific Tags
|
||||||
|
```bash
|
||||||
|
# In .env:
|
||||||
|
MODE=copy
|
||||||
|
DRY_RUN=false
|
||||||
|
TAGS=sports,news
|
||||||
|
|
||||||
|
# Run:
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Force Update Sports Channels Only
|
||||||
|
```bash
|
||||||
|
# In .env:
|
||||||
|
MODE=copy
|
||||||
|
DRY_RUN=false
|
||||||
|
FORCE=true
|
||||||
|
TAGS=sports
|
||||||
|
|
||||||
|
# Run:
|
||||||
|
docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build and start
|
||||||
|
docker compose up
|
||||||
|
|
||||||
|
# Start in background
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker compose logs -f
|
||||||
|
|
||||||
|
# Stop
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# Build locally (if modifying source)
|
||||||
|
# Uncomment "build: ." in docker-compose.yml, then:
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cron Schedule Examples
|
||||||
|
|
||||||
|
Format: `minute hour day month weekday`
|
||||||
|
|
||||||
|
| Schedule | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `0 3 * * *` | Daily at 3 AM (default) |
|
||||||
|
| `0 */6 * * *` | Every 6 hours |
|
||||||
|
| `0 0 * * 0` | Weekly on Sunday at midnight |
|
||||||
|
| `0 2 1 * *` | Monthly on the 1st at 2 AM |
|
||||||
|
| `*/30 * * * *` | Every 30 minutes |
|
||||||
|
|
||||||
|
## Python Usage (Alternative)
|
||||||
|
|
||||||
|
If you prefer to run the script directly with Python instead of Docker:
|
||||||
|
|
||||||
### Copy Logos (Default Mode)
|
### Copy Logos (Default Mode)
|
||||||
|
|
||||||
@@ -76,6 +252,8 @@ python update_channel_logos.py \
|
|||||||
| `--first-only` | Only process the first channel (useful for testing) |
|
| `--first-only` | Only process the first channel (useful for testing) |
|
||||||
| `--clear` | Clear all logos instead of copying them |
|
| `--clear` | Clear all logos instead of copying them |
|
||||||
| `--force` | Overwrite existing LogoLight/LogoLightColor (copy mode only) |
|
| `--force` | Overwrite existing LogoLight/LogoLightColor (copy mode only) |
|
||||||
|
| `--tags` | Comma-separated list of tags to filter channels (e.g., `sports,news`) |
|
||||||
|
| `--non-interactive` | Skip confirmation prompts (for automated execution) |
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -96,15 +274,21 @@ python update_channel_logos.py \
|
|||||||
python update_channel_logos.py --server URL --api-key KEY --execute
|
python update_channel_logos.py --server URL --api-key KEY --execute
|
||||||
```
|
```
|
||||||
|
|
||||||
|
4. Process only channels with specific tags:
|
||||||
|
```bash
|
||||||
|
python update_channel_logos.py --server URL --api-key KEY --tags sports,news --execute
|
||||||
|
```
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
1. Connects to your Emby server
|
1. Connects to your Emby server
|
||||||
2. Fetches all Live TV channels
|
2. Fetches all Live TV channels
|
||||||
3. For each channel:
|
3. Filters channels by tags (if specified)
|
||||||
|
4. For each channel:
|
||||||
- **Copy mode**: Downloads the Primary logo and uploads it as LogoLight and LogoLightColor
|
- **Copy mode**: Downloads the Primary logo and uploads it as LogoLight and LogoLightColor
|
||||||
- **Clear mode**: Deletes Primary, LogoLight, and LogoLightColor
|
- **Clear mode**: Deletes Primary, LogoLight, and LogoLightColor
|
||||||
4. Skips channels that already have the logos (copy mode, unless `--force` is used) or have no logos (clear mode)
|
5. Skips channels that already have the logos (copy mode, unless `--force` is used) or have no logos (clear mode)
|
||||||
5. Shows a summary of results
|
6. Shows a summary of results
|
||||||
|
|
||||||
## Safety Features
|
## Safety Features
|
||||||
|
|
||||||
|
|||||||
19
docker-compose.yml
Normal file
19
docker-compose.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
emby-logo-tools:
|
||||||
|
image: ghcr.io/sethwv/emby-logo-tools:latest
|
||||||
|
# build: . # Uncomment to build locally instead
|
||||||
|
container_name: emby-logo-tools
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- EMBY_SERVER_URL=${EMBY_SERVER_URL}
|
||||||
|
- EMBY_API_KEY=${EMBY_API_KEY}
|
||||||
|
- MODE=${MODE:-copy}
|
||||||
|
- DRY_RUN=${DRY_RUN:-true}
|
||||||
|
- FIRST_ONLY=${FIRST_ONLY:-false}
|
||||||
|
- FORCE=${FORCE:-false}
|
||||||
|
- TAGS=${TAGS:-}
|
||||||
|
- CRON_SCHEDULE=${CRON_SCHEDULE:-0 3 * * *}
|
||||||
|
- RUN_ONCE=${RUN_ONCE:-false}
|
||||||
|
- TZ=${TZ:-UTC}
|
||||||
135
entrypoint.sh
Normal file
135
entrypoint.sh
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Function to build the command based on environment variables
|
||||||
|
build_command() {
|
||||||
|
local cmd="python3 /app/update_channel_logos.py"
|
||||||
|
|
||||||
|
# Required parameters
|
||||||
|
cmd="$cmd --server \"$EMBY_SERVER_URL\""
|
||||||
|
cmd="$cmd --api-key \"$EMBY_API_KEY\""
|
||||||
|
|
||||||
|
# Optional parameters
|
||||||
|
if [ "${MODE:-copy}" = "clear" ]; then
|
||||||
|
cmd="$cmd --clear"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${DRY_RUN:-true}" != "true" ]; then
|
||||||
|
cmd="$cmd --execute"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${FIRST_ONLY:-false}" = "true" ]; then
|
||||||
|
cmd="$cmd --first-only"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${FORCE:-false}" = "true" ] && [ "${MODE:-copy}" = "copy" ]; then
|
||||||
|
cmd="$cmd --force"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add tags if specified
|
||||||
|
if [ -n "${TAGS:-}" ]; then
|
||||||
|
cmd="$cmd --tags \"$TAGS\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add non-interactive flag
|
||||||
|
cmd="$cmd --non-interactive"
|
||||||
|
|
||||||
|
echo "$cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate required environment variables
|
||||||
|
if [ -z "$EMBY_SERVER_URL" ]; then
|
||||||
|
echo "ERROR: EMBY_SERVER_URL environment variable is required"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$EMBY_API_KEY" ]; then
|
||||||
|
echo "ERROR: EMBY_API_KEY environment variable is required"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the cron schedule (default: daily at 3 AM)
|
||||||
|
CRON_SCHEDULE="${CRON_SCHEDULE:-0 3 * * *}"
|
||||||
|
|
||||||
|
# Build the command
|
||||||
|
COMMAND=$(build_command)
|
||||||
|
|
||||||
|
# Log the configuration
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Emby TV Logo Tools - Docker Container"
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Emby Server: $EMBY_SERVER_URL"
|
||||||
|
echo "Mode: ${MODE:-copy}"
|
||||||
|
echo "Dry Run: ${DRY_RUN:-true}"
|
||||||
|
echo "First Only: ${FIRST_ONLY:-false}"
|
||||||
|
echo "Force: ${FORCE:-false}"
|
||||||
|
echo "Tags: ${TAGS:-(none)}"
|
||||||
|
echo "Cron Schedule: $CRON_SCHEDULE"
|
||||||
|
echo "Timezone: ${TZ:-UTC}"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# If RUN_ONCE is set, just run the command once and exit
|
||||||
|
if [ "${RUN_ONCE:-false}" = "true" ]; then
|
||||||
|
echo "Running once (RUN_ONCE=true)..."
|
||||||
|
eval "$COMMAND"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create cron job
|
||||||
|
echo "Setting up cron job..."
|
||||||
|
echo "$CRON_SCHEDULE $COMMAND >> /var/log/cron.log 2>&1" > /etc/cron.d/emby-logo-update
|
||||||
|
|
||||||
|
# Give execution rights on the cron job
|
||||||
|
chmod 0644 /etc/cron.d/emby-logo-update
|
||||||
|
|
||||||
|
# Create the log file
|
||||||
|
touch /var/log/cron.log
|
||||||
|
|
||||||
|
# Apply cron job
|
||||||
|
crontab /etc/cron.d/emby-logo-update
|
||||||
|
|
||||||
|
# Print next scheduled run times
|
||||||
|
echo ""
|
||||||
|
echo "Cron job installed. Next 5 scheduled runs:"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# Calculate next run times (this is approximate)
|
||||||
|
python3 - <<EOF
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import sys
|
||||||
|
|
||||||
|
schedule = "${CRON_SCHEDULE}"
|
||||||
|
parts = schedule.split()
|
||||||
|
if len(parts) != 5:
|
||||||
|
print("Invalid cron schedule format")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
minute, hour = parts[0], parts[1]
|
||||||
|
|
||||||
|
# Simple calculation for daily cron jobs
|
||||||
|
if parts[2] == '*' and parts[3] == '*' and parts[4] == '*':
|
||||||
|
now = datetime.now()
|
||||||
|
target_hour = int(hour) if hour != '*' else now.hour
|
||||||
|
target_minute = int(minute) if minute != '*' else 0
|
||||||
|
|
||||||
|
next_run = now.replace(hour=target_hour, minute=target_minute, second=0, microsecond=0)
|
||||||
|
if next_run <= now:
|
||||||
|
next_run += timedelta(days=1)
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
print(next_run.strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
|
next_run += timedelta(days=1)
|
||||||
|
else:
|
||||||
|
print("(Schedule calculation only supports simple daily jobs)")
|
||||||
|
print("Your schedule: $CRON_SCHEDULE")
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Starting cron daemon..."
|
||||||
|
echo "Container is running. Logs will appear below."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Start cron in foreground and tail the log
|
||||||
|
cron && tail -f /var/log/cron.log
|
||||||
@@ -92,6 +92,20 @@ class EmbyClient:
|
|||||||
raise Exception(f"Failed to delete {image_type}: HTTP {e.code} - {error_body}")
|
raise Exception(f"Failed to delete {image_type}: HTTP {e.code} - {error_body}")
|
||||||
|
|
||||||
|
|
||||||
|
def filter_channels_by_tags(channels: List[Dict], tags: Optional[List[str]]) -> List[Dict]:
|
||||||
|
"""Filter channels by tags. Returns all channels if tags is None or empty."""
|
||||||
|
if not tags:
|
||||||
|
return channels
|
||||||
|
|
||||||
|
filtered = []
|
||||||
|
for channel in channels:
|
||||||
|
channel_tags = channel.get('Tags', [])
|
||||||
|
if any(tag in channel_tags for tag in tags):
|
||||||
|
filtered.append(channel)
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
def copy_primary_to_light_logos(client: EmbyClient, channel_id: str, channel_name: str, dry_run: bool = True, force: bool = False) -> bool:
|
def copy_primary_to_light_logos(client: EmbyClient, channel_id: str, channel_name: str, dry_run: bool = True, force: bool = False) -> bool:
|
||||||
try:
|
try:
|
||||||
channel_data = client.get_channel_by_id(channel_id)
|
channel_data = client.get_channel_by_id(channel_id)
|
||||||
@@ -172,8 +186,17 @@ def clear_channel_logos(client: EmbyClient, channel_id: str, channel_name: str,
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def update_channel_logos(client: EmbyClient, dry_run: bool = True, first_only: bool = False, force: bool = False) -> None:
|
def update_channel_logos(client: EmbyClient, dry_run: bool = True, first_only: bool = False, force: bool = False, tags: Optional[List[str]] = None) -> None:
|
||||||
channels = client.get_live_tv_channels()
|
channels = client.get_live_tv_channels()
|
||||||
|
|
||||||
|
# Filter by tags if specified
|
||||||
|
if tags:
|
||||||
|
print(f"Filtering channels by tags: {', '.join(tags)}")
|
||||||
|
channels = filter_channels_by_tags(channels, tags)
|
||||||
|
if not channels:
|
||||||
|
print(f"\n⚠️ No channels found with the specified tags")
|
||||||
|
return
|
||||||
|
|
||||||
channels_to_process = [channels[0]] if first_only else channels
|
channels_to_process = [channels[0]] if first_only else channels
|
||||||
|
|
||||||
print("\n" + "=" * 80)
|
print("\n" + "=" * 80)
|
||||||
@@ -208,8 +231,17 @@ def update_channel_logos(client: EmbyClient, dry_run: bool = True, first_only: b
|
|||||||
print("\n💡 Processed first channel only. Remove --first-only to process all.")
|
print("\n💡 Processed first channel only. Remove --first-only to process all.")
|
||||||
|
|
||||||
|
|
||||||
def clear_logos(client: EmbyClient, dry_run: bool = True, first_only: bool = False) -> None:
|
def clear_logos(client: EmbyClient, dry_run: bool = True, first_only: bool = False, tags: Optional[List[str]] = None) -> None:
|
||||||
channels = client.get_live_tv_channels()
|
channels = client.get_live_tv_channels()
|
||||||
|
|
||||||
|
# Filter by tags if specified
|
||||||
|
if tags:
|
||||||
|
print(f"Filtering channels by tags: {', '.join(tags)}")
|
||||||
|
channels = filter_channels_by_tags(channels, tags)
|
||||||
|
if not channels:
|
||||||
|
print(f"\n⚠️ No channels found with the specified tags")
|
||||||
|
return
|
||||||
|
|
||||||
channels_to_process = [channels[0]] if first_only else channels
|
channels_to_process = [channels[0]] if first_only else channels
|
||||||
|
|
||||||
print("\n" + "=" * 80)
|
print("\n" + "=" * 80)
|
||||||
@@ -250,8 +282,18 @@ def main():
|
|||||||
parser.add_argument('--first-only', action='store_true', help='Only process first channel')
|
parser.add_argument('--first-only', action='store_true', help='Only process first channel')
|
||||||
parser.add_argument('--clear', action='store_true', help='Clear all logos (Primary, LogoLight, LogoLightColor)')
|
parser.add_argument('--clear', action='store_true', help='Clear all logos (Primary, LogoLight, LogoLightColor)')
|
||||||
parser.add_argument('--force', action='store_true', help='Overwrite existing logos (only applies to copy mode)')
|
parser.add_argument('--force', action='store_true', help='Overwrite existing logos (only applies to copy mode)')
|
||||||
|
parser.add_argument('--non-interactive', action='store_true', help='Skip confirmation prompts (for automated execution)')
|
||||||
|
parser.add_argument('--tags', help='Comma-separated list of tags to filter channels (e.g., "sports,news")')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Parse tags if provided
|
||||||
|
tags = None
|
||||||
|
if args.tags:
|
||||||
|
tags = [tag.strip() for tag in args.tags.split(',') if tag.strip()]
|
||||||
|
if not tags:
|
||||||
|
print("Warning: --tags specified but no valid tags found", file=sys.stderr)
|
||||||
|
tags = None
|
||||||
|
|
||||||
client = EmbyClient(args.server, args.api_key)
|
client = EmbyClient(args.server, args.api_key)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -265,22 +307,24 @@ def main():
|
|||||||
if args.clear:
|
if args.clear:
|
||||||
if args.execute:
|
if args.execute:
|
||||||
target = "first channel" if args.first_only else "all channels"
|
target = "first channel" if args.first_only else "all channels"
|
||||||
if input(f"\nCLEAR ALL LOGOS from {target}? (yes/no): ").lower() == 'yes':
|
tag_info = f" with tags [{', '.join(tags)}]" if tags else ""
|
||||||
clear_logos(client, dry_run=False, first_only=args.first_only)
|
if args.non_interactive or input(f"\nCLEAR ALL LOGOS from {target}{tag_info}? (yes/no): ").lower() == 'yes':
|
||||||
|
clear_logos(client, dry_run=False, first_only=args.first_only, tags=tags)
|
||||||
else:
|
else:
|
||||||
print("Cancelled.")
|
print("Cancelled.")
|
||||||
else:
|
else:
|
||||||
clear_logos(client, dry_run=True, first_only=args.first_only)
|
clear_logos(client, dry_run=True, first_only=args.first_only, tags=tags)
|
||||||
else:
|
else:
|
||||||
if args.execute:
|
if args.execute:
|
||||||
target = "first channel" if args.first_only else "all channels"
|
target = "first channel" if args.first_only else "all channels"
|
||||||
action = "overwrite logos in" if args.force else "modify"
|
action = "overwrite logos in" if args.force else "modify"
|
||||||
if input(f"\n{action.capitalize()} {target}? (yes/no): ").lower() == 'yes':
|
tag_info = f" with tags [{', '.join(tags)}]" if tags else ""
|
||||||
update_channel_logos(client, dry_run=False, first_only=args.first_only, force=args.force)
|
if args.non_interactive or input(f"\n{action.capitalize()} {target}{tag_info}? (yes/no): ").lower() == 'yes':
|
||||||
|
update_channel_logos(client, dry_run=False, first_only=args.first_only, force=args.force, tags=tags)
|
||||||
else:
|
else:
|
||||||
print("Cancelled.")
|
print("Cancelled.")
|
||||||
else:
|
else:
|
||||||
update_channel_logos(client, dry_run=True, first_only=args.first_only, force=args.force)
|
update_channel_logos(client, dry_run=True, first_only=args.first_only, force=args.force, tags=tags)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
Reference in New Issue
Block a user