containerize & enhance filtering
All checks were successful
Build & Push Docker Image / build-and-publish (push) Successful in 13s
All checks were successful
Build & Push Docker Image / build-and-publish (push) Successful in 13s
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
|
||||
40
.env.example
Normal file
40
.env.example
Normal file
@@ -0,0 +1,40 @@
|
||||
# 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=
|
||||
|
||||
# List all available tags and exit
|
||||
LIST_TAGS=false
|
||||
|
||||
# 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 ---
|
||||
# List tags: LIST_TAGS=true RUN_ONCE=true
|
||||
# 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"]
|
||||
217
README.md
217
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
|
||||
- **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
|
||||
|
||||
- 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
|
||||
- API key from Emby Server
|
||||
|
||||
@@ -19,7 +52,161 @@ Automatically copy or clear Live TV channel logos in Emby Server.
|
||||
2. Go to Settings → Advanced → API Keys
|
||||
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`) |
|
||||
| `LIST_TAGS` | `false` | `true` = list all available tags and exit |
|
||||
| `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
|
||||
|
||||
#### Discover Available Tags
|
||||
```bash
|
||||
# In .env:
|
||||
LIST_TAGS=true
|
||||
RUN_ONCE=true
|
||||
|
||||
# Run:
|
||||
docker compose up
|
||||
```
|
||||
|
||||
#### 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)
|
||||
|
||||
@@ -76,35 +263,49 @@ python update_channel_logos.py \
|
||||
| `--first-only` | Only process the first channel (useful for testing) |
|
||||
| `--clear` | Clear all logos instead of copying them |
|
||||
| `--force` | Overwrite existing LogoLight/LogoLightColor (copy mode only) |
|
||||
| `--tags` | Comma-separated list of tags to filter channels (e.g., `sports,news`) |
|
||||
| `--list-tags` | List all available tags and exit |
|
||||
| `--non-interactive` | Skip confirmation prompts (for automated execution) |
|
||||
|
||||
## Examples
|
||||
|
||||
**Recommended workflow:**
|
||||
|
||||
1. Test on first channel with dry run:
|
||||
1. Discover available tags:
|
||||
```bash
|
||||
python update_channel_logos.py --server URL --api-key KEY --list-tags
|
||||
```
|
||||
|
||||
2. Test on first channel with dry run:
|
||||
```bash
|
||||
python update_channel_logos.py --server URL --api-key KEY --first-only
|
||||
```
|
||||
|
||||
2. Execute on first channel:
|
||||
3. Execute on first channel:
|
||||
```bash
|
||||
python update_channel_logos.py --server URL --api-key KEY --first-only --execute
|
||||
```
|
||||
|
||||
3. If successful, run on all channels:
|
||||
4. If successful, run on all channels:
|
||||
```bash
|
||||
python update_channel_logos.py --server URL --api-key KEY --execute
|
||||
```
|
||||
|
||||
5. Process only channels with specific tags:
|
||||
```bash
|
||||
python update_channel_logos.py --server URL --api-key KEY --tags sports,news --execute
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Connects to your Emby server
|
||||
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
|
||||
- **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. Shows a summary of results
|
||||
5. Skips channels that already have the logos (copy mode, unless `--force` is used) or have no logos (clear mode)
|
||||
6. Shows a summary of results
|
||||
|
||||
## Safety Features
|
||||
|
||||
|
||||
20
docker-compose.yml
Normal file
20
docker-compose.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
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:-}
|
||||
- LIST_TAGS=${LIST_TAGS:-false}
|
||||
- CRON_SCHEDULE=${CRON_SCHEDULE:-0 3 * * *}
|
||||
- RUN_ONCE=${RUN_ONCE:-false}
|
||||
- TZ=${TZ:-UTC}
|
||||
142
entrypoint.sh
Normal file
142
entrypoint.sh
Normal file
@@ -0,0 +1,142 @@
|
||||
#!/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\""
|
||||
|
||||
# Handle --list-tags mode (takes precedence)
|
||||
if [ "${LIST_TAGS:-false}" = "true" ]; then
|
||||
cmd="$cmd --list-tags"
|
||||
echo "$cmd"
|
||||
return
|
||||
fi
|
||||
|
||||
# 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,29 @@ class EmbyClient:
|
||||
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 get_all_unique_tags(channels: List[Dict]) -> List[str]:
|
||||
"""Get all unique tags from channels."""
|
||||
tags = set()
|
||||
for channel in channels:
|
||||
channel_tags = channel.get('Tags', [])
|
||||
tags.update(channel_tags)
|
||||
return sorted(list(tags))
|
||||
|
||||
|
||||
def copy_primary_to_light_logos(client: EmbyClient, channel_id: str, channel_name: str, dry_run: bool = True, force: bool = False) -> bool:
|
||||
try:
|
||||
channel_data = client.get_channel_by_id(channel_id)
|
||||
@@ -172,8 +195,26 @@ def clear_channel_logos(client: EmbyClient, channel_id: str, channel_name: str,
|
||||
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()
|
||||
|
||||
# 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")
|
||||
all_tags = get_all_unique_tags(client.get_live_tv_channels())
|
||||
if all_tags:
|
||||
print(f"\nAvailable tags in your channels ({len(all_tags)}):")
|
||||
for tag in all_tags[:20]: # Show first 20 tags
|
||||
print(f" - {tag}")
|
||||
if len(all_tags) > 20:
|
||||
print(f" ... and {len(all_tags) - 20} more")
|
||||
else:
|
||||
print("No tags found in any channels")
|
||||
return
|
||||
|
||||
channels_to_process = [channels[0]] if first_only else channels
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
@@ -208,8 +249,26 @@ 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.")
|
||||
|
||||
|
||||
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()
|
||||
|
||||
# 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")
|
||||
all_tags = get_all_unique_tags(client.get_live_tv_channels())
|
||||
if all_tags:
|
||||
print(f"\nAvailable tags in your channels ({len(all_tags)}):")
|
||||
for tag in all_tags[:20]: # Show first 20 tags
|
||||
print(f" - {tag}")
|
||||
if len(all_tags) > 20:
|
||||
print(f" ... and {len(all_tags) - 20} more")
|
||||
else:
|
||||
print("No tags found in any channels")
|
||||
return
|
||||
|
||||
channels_to_process = [channels[0]] if first_only else channels
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
@@ -250,10 +309,21 @@ def main():
|
||||
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('--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")')
|
||||
parser.add_argument('--list-tags', action='store_true', help='List all available tags and exit')
|
||||
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)
|
||||
|
||||
|
||||
try:
|
||||
print(f"Connecting to {args.server}...")
|
||||
channels = client.get_live_tv_channels()
|
||||
@@ -261,26 +331,39 @@ def main():
|
||||
except Exception as e:
|
||||
print(f"✗ Connection failed: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Handle --list-tags
|
||||
if args.list_tags:
|
||||
all_tags = get_all_unique_tags(channels)
|
||||
if all_tags:
|
||||
print(f"\nFound {len(all_tags)} unique tags:")
|
||||
for tag in all_tags:
|
||||
print(f" - {tag}")
|
||||
else:
|
||||
print("\nNo tags found in any channels")
|
||||
sys.exit(0)
|
||||
|
||||
if args.clear:
|
||||
if args.execute:
|
||||
target = "first channel" if args.first_only else "all channels"
|
||||
if input(f"\nCLEAR ALL LOGOS from {target}? (yes/no): ").lower() == 'yes':
|
||||
clear_logos(client, dry_run=False, first_only=args.first_only)
|
||||
tag_info = f" with tags [{', '.join(tags)}]" if tags else ""
|
||||
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:
|
||||
print("Cancelled.")
|
||||
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:
|
||||
if args.execute:
|
||||
target = "first channel" if args.first_only else "all channels"
|
||||
action = "overwrite logos in" if args.force else "modify"
|
||||
if input(f"\n{action.capitalize()} {target}? (yes/no): ").lower() == 'yes':
|
||||
update_channel_logos(client, dry_run=False, first_only=args.first_only, force=args.force)
|
||||
tag_info = f" with tags [{', '.join(tags)}]" if tags else ""
|
||||
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:
|
||||
print("Cancelled.")
|
||||
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__':
|
||||
|
||||
Reference in New Issue
Block a user