diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..8d27729
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,13 @@
+*.md
+LICENSE
+Dockerfile
+.dockerignore
+.gitignore
+.github
+.git
+.idea
+docker-compose.yml
+venv
+__pycache__
+documentation
+templates
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index f47941f..0000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: "[BUG]"
-labels: bug
-assignees: ''
-
----
-
-**Describe the bug**
-A clear and concise description of what the bug is.
-
-**To Reproduce**
-Steps to reproduce the behavior:
-1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
-4. See error
-
-**Expected behavior**
-A clear and concise description of what you expected to happen.
-
-**Screenshots**
-If applicable, add screenshots to help explain your problem.
-
-**Environment (please complete the following information):**
- - OS: [e.g. macOS, Ubuntu, Alpine]
- - Version [e.g. 22]
- - Python Version:
-
-**Setup (please complete the following information):**
- - Did you ensure all required modules are installed? yes / no
- - Does code/modules work outside the context of this script or program? yes / no
-
-**Additional context**
-Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 9cde18a..0000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-title: "[REQUEST]"
-labels: enhancement
-assignees: ''
-
----
-
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
-
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
-
-**Additional context**
-Add any other context or screenshots about the feature request here.
diff --git a/.github/release.yml b/.github/release.yml
deleted file mode 100644
index 7cb3a3f..0000000
--- a/.github/release.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-# .github/release.yml
-
-changelog:
- exclude:
- labels:
- - ignore-for-release
- authors:
- - octocat
- categories:
- - title: Breaking Changes ðŸ›
- labels:
- - Semver-Major
- - breaking-change
- - title: Exciting New Features 🎉
- labels:
- - Semver-Minor
- - enhancement
- - title: Other Changes
- labels:
- - "*"
\ No newline at end of file
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 0000000..1d7fccc
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -0,0 +1,85 @@
+name: Build & Publish Docker image
+on:
+ release:
+ types: [ created ]
+ secrets:
+ DOCKER_USERNAME:
+ required: true
+ DOCKER_TOKEN:
+ required: true
+ workflow_dispatch:
+ inputs:
+ version:
+ type: string
+ description: Version number
+ required: true
+jobs:
+ publish:
+ name: Build & Publish to DockerHub and GitHub Packages
+ runs-on: ubuntu-latest
+ if: contains(github.event.head_commit.message, '[no build]') == false
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Establish variables
+ id: vars
+ run: |
+ VERSION=${{ github.event.inputs.version || github.ref_name }}
+ echo ::set-output name=version::${VERSION}
+ echo ::set-output name=today::$(date +'%Y-%m-%d')
+ echo "::set-output name=year::$(date +'%Y')"
+
+ - name: Update version number
+ uses: jacobtomlinson/gha-find-replace@2.0.0
+ with:
+ find: "VERSIONADDEDBYGITHUB"
+ replace: "${{ steps.vars.outputs.version }}"
+ regex: false
+
+ - name: Update copyright year
+ uses: jacobtomlinson/gha-find-replace@2.0.0
+ with:
+ find: "YEARADDEDBYGITHUB"
+ replace: "${{ steps.vars.outputs.year }}"
+ regex: false
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v2
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v2
+ id: docker-buildx
+
+ - name: Login to DockerHub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v2
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build and push
+ uses: docker/build-push-action@v3
+ with:
+ builder: ${{ steps.docker-buildx.outputs.name }}
+ context: .
+ file: ./Dockerfile
+ push: true
+ platforms: linux/amd64,linux/armhf,linux/arm64
+ tags: |
+ nwithan8/plex_prerolls:latest
+ nwithan8/plex_prerolls:${{ steps.vars.outputs.version }}
+ ghcr.io/nwithan8/plex_prerolls:latest
+ ghcr.io/nwithan8/plex_prerolls:${{ steps.vars.outputs.version }}
+ labels: |
+ org.opencontainers.image.title=plex_prerolls
+ org.opencontainers.image.version=${{ steps.vars.outputs.version }}
+ org.opencontainers.image.created=${{ steps.vars.outputs.today }}
diff --git a/.github/workflows/publish-release-manual.yml b/.github/workflows/publish-release-manual.yml
deleted file mode 100644
index 8225b4a..0000000
--- a/.github/workflows/publish-release-manual.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-# .github/workflows/publish-release-manual.yml
-name: Publish release (manual)
-on:
- workflow_dispatch:
- inputs:
- tag:
- description: The tag to publish
- required: true
-jobs:
- publish:
- runs-on: ubuntu-latest
- name: Publish release
- steps:
- - name: Checkout
- uses: actions/checkout@v3
- with:
- ref: refs/tags/${{ github.event.inputs.tag }}
- - name: Publish release
- uses: eloquent/github-release-action@v3
\ No newline at end of file
diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml
deleted file mode 100644
index d5a3c21..0000000
--- a/.github/workflows/publish-release.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-# .github/workflows/publish-release.yml
-name: Publish release
-on:
- push:
- tags:
- - "v*"
-jobs:
- publish:
- runs-on: ubuntu-latest
- name: Publish release
- steps:
- - name: Checkout
- uses: actions/checkout@v3
- - name: Publish release
- uses: eloquent/github-release-action@v3
diff --git a/.github/workflows/upload_unraid_template.yml b/.github/workflows/upload_unraid_template.yml
new file mode 100644
index 0000000..279859b
--- /dev/null
+++ b/.github/workflows/upload_unraid_template.yml
@@ -0,0 +1,36 @@
+name: Copy Unraid Community Applications template(s) to templates repository
+
+on:
+ release:
+ types: [ created ]
+ workflow_dispatch: ~
+
+jobs:
+ copy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Establish variables
+ id: vars
+ run: |
+ VERSION=${{ github.event.inputs.version || github.ref_name }}
+ echo ::set-output name=version::${VERSION}
+ echo ::set-output name=today::$(date +'%Y-%m-%d')
+
+ - name: Open PR with template changes to unraid_templates
+ uses: nwithan8/action-pull-request-another-repo@v1.1.1
+ env:
+ API_TOKEN_GITHUB: ${{ secrets.PR_OPEN_GITHUB_TOKEN }}
+ with:
+ # Will mirror folder structure (copying "templates" folder to "templates" folder in destination repo)
+ source_folder: 'templates'
+ destination_repo: 'nwithan8/unraid_templates'
+ destination_base_branch: 'main'
+ destination_head_branch: prerolls-${{ steps.vars.outputs.version }}
+ user_email: 'nwithan8@users.noreply.github.com'
+ user_name: 'nwithan8'
+ pull_request_assignees: 'nwithan8'
diff --git a/.gitignore b/.gitignore
index 30ef07b..265a265 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
# Exclude local Config files
config.ini
-preroll_schedules.yaml
-preroll_schedules.yml
+schedules.yaml
#Dev environmet
.devcontainer
diff --git a/Dockerfile b/Dockerfile
new file mode 100755
index 0000000..bb6f77e
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,40 @@
+FROM python:3.11-alpine3.18
+WORKDIR /
+
+# Install Python and other utilities
+RUN apk add --no-cache --update alpine-sdk git wget python3 python3-dev ca-certificates musl-dev libc-dev gcc bash nano linux-headers && \
+ python3 -m ensurepip && \
+ pip3 install --no-cache-dir --upgrade pip setuptools
+
+# Copy requirements.txt from build machine to WORKDIR (/app) folder (important we do this BEFORE copying the rest of the files to avoid re-running pip install on every code change)
+COPY requirements.txt requirements.txt
+
+# Install Python requirements
+RUN pip3 install --no-cache-dir -r requirements.txt
+
+# Make Docker /config volume for optional config file
+VOLUME /config
+
+# Copy logging.conf file from build machine to Docker /config folder
+COPY logging.conf /config/
+
+# Copy example config file from build machine to Docker /config folder
+# Also copies any existing config.ini file from build machine to Docker /config folder, (will cause the bot to use the existing config file if it exists)
+COPY config.ini* /config/
+
+# Copy example schedule file from build machine to Docker /config folder
+# Also copies any existing schedules.yaml file from build machine to Docker /config folder, (will cause the bot to use the existing schedule file if it exists)
+COPY schedules.yaml* /config/
+
+# Make Docker /logs volume for log file
+VOLUME /logs
+
+# Copy source code from build machine to WORKDIR (/app) folder
+COPY . .
+
+# Delete unnecessary files in WORKDIR (/app) folder (not caught by .dockerignore)
+RUN echo "**** removing unneeded files ****"
+RUN rm -rf /requirements.txt
+
+# Run entrypoint.sh script
+ENTRYPOINT ["sh", "/entrypoint.sh"]
diff --git a/README.md b/README.md
index 88ed072..4ccd2fb 100644
--- a/README.md
+++ b/README.md
@@ -1,171 +1,51 @@
-# Schedule Plex server related Pre-roll intro videos
+# Plex Preroll Scheduler
-A helper script to automate management of Plex pre-rolls. \
-Define when you want different pre-rolls to play throughout the year.
+A script to automate management of Plex pre-rolls.
-Ideas include:
+Define when you want different pre-rolls to play throughout the year. For example:
- Holiday pre-roll rotations
- Special occasions
-- Summer/Winter/Seasonal rotations
+- Seasonal rotations
- Breaking up the monotony
- Keeping your family on their toes!
-Simple steps:
-
-> 1. Config the schedule
-> 2. Schedule script on server
-> 3. ...
-> 4. Profit!
-
-See [Installation & Setup](#install) section
-
---
-## Schedule Rules
+## Installation and Usage
-Schedule priority for a given Date:
+### Run Script Directly
-1. **misc** \
-always_use - always includes in listing (append)
+#### Requirements
-2. **date_range** \
-Include listing for the specified Start/End date range that include the given Date \
-Range can be specified as a Date or DateTime \
-Advanced features to have recurring timeframes \
-**overrides usage of *week/month/default* listings
+- Python 3.8+
-3. **weekly** \
-Include listing for the specified WEEK of the year for the given Date \
- **override usage of *month/default* listings
-
-4. **monthly** \
-Include listing for the specified MONTH of the year for the given Date \
-**overrides usage of *default* listings
-
-5. **default** \
-Default listing used of none of above apply to the given Date
-
-Note: Script tries to find the closest matching range if multiple overlap at same time
-
----
-
-## Installation & Setup
-
-Grab a copy of the code
+Clone the repo:
```sh
-cd /path/to/your/location
-git clone https://github.com/BrianLindner/plex-schedule-prerolls.git
+git clone https://github.com/nwithan8/plex-schedule-prerolls.git
```
-### Install Requirements
-
-Requires:
-
-- Python 3.8+ [may work on 3.6+ but not tested]
-- See `requirements.txt` for Python modules and versions [link](requirements.txt)
- - plexapi, configparser, pyyaml, etc.
-
-Install Python requirements \
-(highly recomend using Virtual Environments )
+Install Python requirements:
```sh
pip install -r requirements.txt
```
-### Create `config.ini` file with Plex connection information
+Copy `config.ini.sample` to `config.ini` and complete the `[auth]` section with your Plex server information.
-Script checks for:
+Copy `schedules.yaml.sample` to `schedules.yaml` and [edit your schedule](#schedule-rules).
-- local ./config.ini (See: [Sample](config.ini.sample))
-- PlexAPI global config.ini
-- Custom location config.ini (see [Arguments](#arguments))
-
-(See: plexapi.CONFIG for more info)
-
-Rename `config.ini.sample` -> `config.ini` and update to your environment
-
-Example `config.ini`
-
-```ini
-[auth]
-server_baseurl = http://127.0.0.1:32400 # your plex server url
-server_token = # access token
-```
-
-### Create `preroll_schedules.yaml` file with desired schedule
-
-#### Date Range Section Scheduling
-
-Use it for *Day* or *Ranges of Dates* needs \
-Now with Time support! (optional)
-
-Formatting Supported:
-
-- Dates: yyyy-mm-dd
-- DateTime: yyyy-mm-dd hh:mm:ss (24hr time format)
-
-Rename `preroll_schedules.yaml.sample` -> `preroll_schedules.yaml` and update for your environment
-
-Example YAML config layout (See: [Sample](preroll_schedules.yaml.sample) for more info)
-
-```yaml
----
-monthly:
- enabled: (yes/no)
- jan: /path/to/file.mp4;/path/to/file.mp4
- ...
- dec: /path/to/file.mp4;/path/to/file.mp4
-date_range:
- enabled: (yes/no)
- ranges:
- - start_date: 2020-01-01
- end_date: 2020-01-01
- path: /path/to/video.mp4
- - start_date: 2020-07-03
- end_date: 2020-07-05
- path: /path/to/video.mp4
- - start_date: 2020-12-19
- end_date: 2020-12-26
- path: /path/to/video.mp4
-weekly:
- enabled: (yes/no)
- "1": /path/to/file(s)
- ...
- "52": /path/to/file(s)
-misc:
- enabled: (yes/no)
- always_use: /path/to/file(s)
-default:
- enabled: (yes/no)
- path: /path/to/file.mp4;/path/to/file.mp4
-```
-
-See [Advancecd Date Ranges](#advanced_date) for additional features
-
-## Usage
-
-### Default Usage
+Run the script:
```sh
python schedule_preroll.py
```
-### Runtime Arguments
-
-- -v : version information
-- -h : help information
-- -c : config.ini (local or PlexAPI system central) for Connection Info (see [config.ini.sample](config.ini.sample))
-- -s : preroll_schedules.yaml for various scheduling information (see [spreroll_schedules.yaml.sample](preroll_schedules.yaml.sample))
-- -lc : location of custom logger.conf config file \
-See:
- - Sample [logger config](logging.conf)
- - Logger usage Examples
- - Logging Doc Info
+#### Advanced Usage
```sh
-python schedule_preroll.py -h
+$ python schedule_preroll.py -h
usage: schedule_preroll.py [-h] [-v] [-l LOG_CONFIG_FILE] [-c CONFIG_FILE] [-s SCHEDULE_FILE]
@@ -179,100 +59,134 @@ optional arguments:
-c CONFIG_FILE, --config-path CONFIG_FILE
Path to Config.ini to use for Plex Server info. [Default: ./config.ini]
-s SCHEDULE_FILE, --schedule-path SCHEDULE_FILE
- Path to pre-roll schedule file (YAML) to be use. [Default: ./preroll_schedules.yaml]
+ Path to pre-roll schedule file (YAML) to be use. [Default: ./schedules.yaml]
```
-### Runtime Arguments Example
+##### Example
```sh
python schedule_preroll.py \
-c path/to/custom/config.ini \
- -s path/to/custom/preroll_schedules.yaml \
+ -s path/to/custom/schedules.yaml \
-lc path/to/custom/logger.conf
```
+### Run as Docker Container
+
+#### Requirements
+
+- Docker
+
+#### Docker Compose
+
+Complete the provided `docker-compose.yml` file and run:
+
+```sh
+docker-compose up -d
+```
+
+#### Docker CLI
+
+```sh
+docker run -d \
+ --name=plex_prerolls \
+ -e PUID=1000 \
+ -e PGID=1000 \
+ -e TZ=Etc/UTC \
+ -e CRON_SCHEDULE="0 0 * * *" \
+ -v /path/to/config:/config \
+ -v /path/to/logs:/logs \
+ --restart unless-stopped \
+ nwithan8/plex_prerolls:latest
+```
+
+#### Paths and Environment Variables
+
+| Path | Description |
+|-----------|-------------------------------------------------------------------------------------|
+| `/config` | Path to config files (`config.ini` and `schedule.yaml` should be in this directory) |
+| `/logs` | Path to log files (`schedule_preroll.log` will be in this directory) |
+
+| Environment Variable | Description |
+|----------------------|-------------------------------------------------------------------|
+| `PUID` | UID of user to run as |
+| `PGID` | GID of user to run as |
+| `TZ` | Timezone to use for cron schedule |
+| `CRON_SCHEDULE` | Cron schedule to run script (see for help) |
+
---
-## Scheduling Script (Optional)
+## Schedule Rules
+
+Schedules follow the following priority:
+1. **misc**: Items listed in `always_use` will always be included (appended) to the preroll list
+
+2. **date_range**: Schedule based on a specific date/time range
+
+3. **weekly**: Schedule based on a specific week of the year
+
+4. **monthly**: Schedule based on a specific month of the year
+
+5. **default**: Default item to use if none of the above apply
+
+For any conflicting schedules, the script tries to find the closest matching range and highest priority.
+
+### Advanced Scheduling
+
+#### Date Range Section Scheduling
+
+`date_range` entries can accept both dates (`yyyy-mm-dd`) and datetimes (`yyyy-mm-dd hh:mm:ss`, 24-hour time).
+
+`date_range` entries can also accept wildcards for any of the date/time fields. This can be useful for scheduling recurring events, such as annual events, "first-of-the-month" events, or even hourly events.
+
+```yaml
+date_range:
+ enabled: true
+ ranges:
+ # Each entry requires start_date, end_date, path values
+ - start_date: 2020-01-01 # Jan 1st, 2020
+ end_date: 2020-01-02 # Jan 2nd, 2020
+ path: /path/to/video.mp4
+ - start_date: xxxx-07-04 # Every year on July 4th
+ end_date: xxxx-07-04 # Every year on July 4th
+ path: /path/to/video.mp4
+ - start_date: xxxx-xx-02 # Every year on the 2nd of every month
+ end_date: xxxx-xx-03 # Every year on the 3rd of every month
+ path: /path/to/video.mp4
+ - start_date: xxxx-xx-xx 08:00:00 # Every day at 8am
+ end_date: xxxx-xx-xx 09:30:00 # Every day at 9:30am
+ path: /path/to/holiday_video.mp4
+```
+
+You should [adjust your cron schedule](#scheduling-script) to run the script more frequently if you use this feature.
+
+---
+
+## Scheduling Script
+
+**NOTE:** Scheduling is handled automatically in the Docker version of this script via the `CRON_SCHEDULE` environment variable.
+
+### Linux
Add to system scheduler:
-Linux:
-
```sh
crontab -e
```
-Place desired schedule (example below for everyday at midnight)
+Place desired schedule (example below for every day at midnight)
```sh
0 0 * * * python /path/to/schedule_preroll.py >/dev/null 2>&1
```
-or \
-(Optional) Wrap in a shell script: \
-useful if running other scripts/commands, using venv encapsulation, customizing arguments
+You can also wrap the execution in a shell script (useful if running other scripts/commands, using venv encapsulation, customizing arguments, etc.)
```sh
0 0 * * * /path/to/schedule_preroll.sh >/dev/null 2>&1
```
-Schedule as frequently as needed for your environment and how specific and to your personal rotation schedule needs
-
----
-
-## Advanced Date Range Section Scheduling (Optional)
-
-Date Ranges with Recurring Timeframes \
-Useful for static dates or times where you want recurring preroll activity
-
-Examples:
-
-- Every Morning
-- Yearly holidays (Halloween, New Years, Independence)
-- Birthdays, Anniversaries
-
-For either Start and/or End date of range \
-Substitute "xx" for date/times to schedule for "any" \
-Substitute "xxxx" for recurring year
-
-- xxxx-xx-01 - Every first of month
-- xxxx-xx-xx - Every day
-- xxxx-xx-xx 08:00:00 - every day from 8am
-- xxxx-01-01 - Every year on Jan 1 (new years day)
-
-if using Time, still must have a full datetime pattern (ex: hour, minute, second hh:mm:ss)
-
-```yaml
-#every July 4
-- start_date: xxxx-07-04
- end_date: xxxx-07-04
- path: /path/to/video.mp4
-# every first of month, all day
-- start_date: xxxx-xx-01
- end_date: xxxx-xx-01
- path: /path/to/video.mp4
-# 8-9 am every day
-- start_date: xxxx-xx-xx 08:00:00
- end_date: xxxx-xx-xx 08:59:59
- path: /path/to/video.mp4
-
-```
-
-Note: Detailed time based schedules benefit from increased running of the Python script for frequently - ex: Hourly \
-(See: [Scheduling Script](#scheduling) section)
-
----
-
-## Config `logger.conf` to your needs (Optional)
-
-See:
-
----
-
-## Wrapping Up
-
-> Sit back and enjoy the Intros!
+Schedule as frequently as needed for your schedule (ex: hourly, daily, weekly, etc.)
---
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..7073dff
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,10 @@
+version: "3.9"
+services:
+ plex_prerolls:
+ image: nwithan8/plex_prerolls:latest
+ volumes:
+ - /path/to/config:/config
+ - /path/to/logs:/logs
+ environment:
+ CRON_SCHEDULE: "0 0 * * *" # Run every day at midnight, see https://crontab.guru/ for help
+ TZ: America/New_York
diff --git a/entrypoint.sh b/entrypoint.sh
new file mode 100644
index 0000000..7a1538f
--- /dev/null
+++ b/entrypoint.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# Create cron directory
+mkdir -p /etc/cron.d
+
+# Read cron schedule from environment variable
+CRON_SCHEDULE=${CRON_SCHEDULE:-"0 0 * * *"} # Default to midnight every day if not supplied
+
+# Schedule cron job with supplied cron schedule
+echo "$CRON_SCHEDULE python3 /schedule_preroll.py -c /config/config.ini -s /config/schedules.yaml -lc /config/logging.conf > /proc/1/fd/1 2>/proc/1/fd/2" > /etc/cron.d/schedule_preroll
+
+# Give execution rights on the cron job
+chmod 0644 /etc/cron.d/schedule_preroll
+
+# Apply cron job
+crontab /etc/cron.d/schedule_preroll
+
+# Create the log file to be able to run tail
+touch /var/log/cron.log
+
+# Run the command on container startup
+crond && tail -f /var/log/cron.log
diff --git a/preroll_schedules.yaml.sample b/preroll_schedules.yaml.sample
deleted file mode 100644
index eeabe3c..0000000
--- a/preroll_schedules.yaml.sample
+++ /dev/null
@@ -1,56 +0,0 @@
----
-# all Key items must be in lowercase
-# all PATH entries will be case sensitive based on your Environment (Linux, Windows)
-monthly:
- # If enabled, List out each month (lowercase;3-char abreviation) (optional)
- # dont have to have all the months here, script will skip but log a Warning
- enabled: Yes
- jan: /path/to/video.mp4
- feb:
- mar:
- apr:
- may: /path/to/video.mp4
- jun:
- jul:
- aug:
- sep: /path/to/video.mp4;/path/to/video.mp4
- oct:
- nov:
- dec:
-date_range:
- # If enabled, use a list of various date ranges/timeframes for different lists of pre-rolls
- # list must be under ranges:
- # each entry requires start_date, end_date, path values
- enabled: Yes
- ranges:
- - start_date: 2020-01-01
- end_date: 2020-01-01
- path: /path/to/video.mp4
- - start_date: 2020-07-03
- end_date: 2020-07-05
- path: /path/to/video.mp4
- - start_date: 2020-12-19
- end_date: 2020-12-26
- path: /path/to/video.mp4
- # every year on Dec 25
- - start_date: xxxx-12-25
- end_date: xxxx-12-25
- path: /path/to/holiday_video.mp4
-weekly:
- # If enabled, list any weeks of the year to have specific prerolls 1-52 (optional)
- # Dont need to have all the week numbers; the script skips over missing entries
- # each week number must be surrounded by quotes
- enabled: No
- "1":
- "2": /path/to/video.mkv
- "52":
-misc:
- # If enabled, additional items for preroll selecton processing
- # always_use: If enabled, always include these video files in pre-role listing
- enabled: Yes
- always_use: /path/to/video.mp4
- # TBD: Future Use - other features
-default:
- # If enabled, Default listing of prerolls to use if no Schedule (above) is specified for date
- enabled: Yes
- path: /path/to/video1.mp4;/path/to/video3.mp4;/path/to/video4.mp4
diff --git a/schedule_preroll.py b/schedule_preroll.py
index f5e70bd..73f6ec8 100644
--- a/schedule_preroll.py
+++ b/schedule_preroll.py
@@ -15,8 +15,8 @@ Optional Arguments:
Path to Config.ini to use for Plex Server info.
[Default: ./config.ini]
-s SCHEDULE_FILE, --schedule-path SCHEDULE_FILE
- Path to pre-roll schedule file (YAML) to be use.
- [Default: ./preroll_schedules.yaml]
+ Path to pre-roll schedule file (YAML) to be used.
+ [Default: ./schedules.yaml]
Requirements:
- See Requirements.txt for Python modules
@@ -24,7 +24,7 @@ Requirements:
Scheduling:
Add to system scheduler such as:
> crontab -e
- > 0 0 * * * python path/to/schedule_preroll.py >/dev/null 2>&1
+ > 0 0 * * * python path/to/schedule_pre_roll.py >/dev/null 2>&1
Raises:
FileNotFoundError: [description]
@@ -60,8 +60,8 @@ script_dir = os.path.dirname(__file__)
class ScheduleEntry(NamedTuple):
type: str
- startdate: datetime
- enddate: datetime
+ start_date: datetime
+ end_date: datetime
force: bool
path: str
@@ -81,7 +81,7 @@ def arguments() -> Namespace:
config_default = "./config.ini"
log_config_default = "./logging.conf"
- schedule_default = "./preroll_schedules.yaml"
+ schedule_default = "./schedules.yaml"
parser = ArgumentParser(description=f"{description}")
parser.add_argument(
"-v",
@@ -141,18 +141,18 @@ def schedule_types() -> ScheduleType:
return schema
-def week_range(year: int, weeknum: int) -> Tuple[datetime, datetime]:
+def week_range(year: int, week_num: int) -> Tuple[datetime, datetime]:
"""Return the starting/ending date range of a given year/week
Args:
year (int): Year to calc range for
- weeknum (int): Month of the year (1-12)
+ week_num (int): Month of the year (1-12)
Returns:
DateTime: Start date of the Year/Month
DateTime: End date of the Year/Month
"""
- start = datetime.strptime(f"{year}-W{int(weeknum) - 1}-0", "%Y-W%W-%w").date()
+ start = datetime.strptime(f"{year}-W{int(week_num) - 1}-0", "%Y-W%W-%w").date()
end = start + timedelta(days=6)
start = datetime.combine(start, datetime.min.time())
@@ -161,18 +161,18 @@ def week_range(year: int, weeknum: int) -> Tuple[datetime, datetime]:
return (start, end)
-def month_range(year: int, monthnum: int) -> Tuple[datetime, datetime]:
+def month_range(year: int, month_num: int) -> Tuple[datetime, datetime]:
"""Return the starting/ending date range of a given year/month
Args:
year (int): Year to calc range for
- monthnum (int): Month of the year (1-12)
+ month_num (int): Month of the year (1-12)
Returns:
DateTime: Start date of the Year/Month
DateTime: End date of the Year/Month
"""
- start = date(year, monthnum, 1)
+ start = date(year, month_num, 1)
next_month = start.replace(day=28) + timedelta(days=4)
end = next_month - timedelta(days=next_month.day)
@@ -247,23 +247,23 @@ def make_datetime(value: Union[str, date, datetime], lowtime: bool = True) -> da
year, month, day = today.year, today.month, today.day
hour, minute, second = time.hour, time.minute, time.second
- # start parsing the Time out, for later additional processing
- dateparts = value.lower().split("-")
+ # start parsing the timeout, for later additional processing
+ date_parts = value.lower().split("-")
- year = today.year if dateparts[0] == "xxxx" else int(dateparts[0])
- month = today.month if dateparts[1] == "xx" else int(dateparts[1])
+ year = today.year if date_parts[0] == "xxxx" else int(date_parts[0])
+ month = today.month if date_parts[1] == "xx" else int(date_parts[1])
- dateparts_day = dateparts[2].split(" ")
+ date_parts_day = date_parts[2].split(" ")
- day = today.day if dateparts_day[0] == "xx" else int(dateparts_day[0])
+ day = today.day if date_parts_day[0] == "xx" else int(date_parts_day[0])
# attempt to parse out Time components
- if len(dateparts_day) > 1:
- timeparts = dateparts_day[1].split(":")
- if len(timeparts) > 1:
- hour = now.hour if timeparts[0] == "xx" else int(timeparts[0])
- minute = now.minute if timeparts[1] == "xx" else int(timeparts[1])
- second = now.second + 1 if timeparts[2] == "xx" else int(timeparts[2])
+ if len(date_parts_day) > 1:
+ time_parts = date_parts_day[1].split(":")
+ if len(time_parts) > 1:
+ hour = now.hour if time_parts[0] == "xx" else int(time_parts[0])
+ minute = now.minute if time_parts[1] == "xx" else int(time_parts[1])
+ second = now.second + 1 if time_parts[2] == "xx" else int(time_parts[2])
dt_val = datetime(year, month, day, hour, minute, second)
logger.debug("Datetime-> '%s'", dt_val)
@@ -278,7 +278,7 @@ def make_datetime(value: Union[str, date, datetime], lowtime: bool = True) -> da
return dt_val
-def schedulefile_contents(schedule_filename: Optional[str]) -> dict[str, any]: # type: ignore
+def schedule_file_contents(schedule_filename: Optional[str]) -> dict[str, any]: # type: ignore
"""Returns a contents of the provided YAML file and validates
Args:
@@ -290,7 +290,7 @@ def schedulefile_contents(schedule_filename: Optional[str]) -> dict[str, any]:
Returns:
YAML Contents: YAML structure of Dict[str, Any]
"""
- default_files = ["preroll_schedules.yaml", "preroll_schedules.yml"]
+ default_files = ["schedules.yaml"]
filename = None
if schedule_filename not in ("", None):
@@ -312,11 +312,11 @@ def schedulefile_contents(schedule_filename: Optional[str]) -> dict[str, any]:
# if we still cant find a schedule file, we abort
if not filename:
- filestr = '" / "'.join(default_files)
- logger.error('Missing schedule file: "%s"', filestr)
- raise FileNotFoundError(filestr)
+ file_str = '" / "'.join(default_files)
+ logger.error('Missing schedule file: "%s"', file_str)
+ raise FileNotFoundError(file_str)
- schema_filename = os.path.join(script_dir, "util/schedulefile_schema.json")
+ schema_filename = os.path.join(script_dir, "util/schedule_file_schema.json")
logger.debug('using schema validation file "%s"', schema_filename)
@@ -354,17 +354,17 @@ def schedulefile_contents(schedule_filename: Optional[str]) -> dict[str, any]:
raise
if not v.validate(contents): # type: ignore
- logger.error("Preroll-Schedule YAML Validation Error: %s", v.errors) # type: ignore
- raise yaml.YAMLError(f"Preroll-Schedule YAML Validation Error: {v.errors}") # type: ignore
+ logger.error("Pre-roll Schedule YAML Validation Error: %s", v.errors) # type: ignore
+ raise yaml.YAMLError(f"Pre-roll Schedule YAML Validation Error: {v.errors}") # type: ignore
return contents
-def preroll_schedule(schedule_file: Optional[str] = None) -> List[ScheduleEntry]:
- """Return a listing of defined preroll schedules for searching/use
+def pre_roll_schedule(schedule_file: Optional[str] = None) -> List[ScheduleEntry]:
+ """Return a listing of defined pre_roll schedules for searching/use
Args:
- schedule_file (str): path/to/schedule_preroll.yaml style config file (YAML Format)
+ schedule_file (str): path/to/schedule_pre_roll.yaml style config file (YAML Format)
Raises:
FileNotFoundError: If no schedule config file exists
@@ -373,7 +373,7 @@ def preroll_schedule(schedule_file: Optional[str] = None) -> List[ScheduleEntry]
list: list of ScheduleEntries
"""
- contents = schedulefile_contents(schedule_file) # type: ignore
+ contents = schedule_file_contents(schedule_file) # type: ignore
today = date.today()
schedule: List[ScheduleEntry] = []
@@ -399,8 +399,8 @@ def preroll_schedule(schedule_file: Optional[str] = None) -> List[ScheduleEntry]
entry = ScheduleEntry(
type=schedule_section,
force=False,
- startdate=start,
- enddate=end,
+ start_date=start,
+ end_date=end,
path=path,
)
@@ -432,8 +432,8 @@ def preroll_schedule(schedule_file: Optional[str] = None) -> List[ScheduleEntry]
entry = ScheduleEntry(
type=schedule_section,
force=False,
- startdate=start,
- enddate=end,
+ start_date=start,
+ end_date=end,
path=path,
)
@@ -468,8 +468,8 @@ def preroll_schedule(schedule_file: Optional[str] = None) -> List[ScheduleEntry]
entry = ScheduleEntry(
type=schedule_section,
force=force, # type: ignore
- startdate=start,
- enddate=end,
+ start_date=start,
+ end_date=end,
path=path,
)
@@ -496,8 +496,8 @@ def preroll_schedule(schedule_file: Optional[str] = None) -> List[ScheduleEntry]
entry = ScheduleEntry(
type=schedule_section,
force=False,
- startdate=datetime(today.year, today.month, today.day, 0, 0, 0),
- enddate=datetime(today.year, today.month, today.day, 23, 59, 59),
+ start_date=datetime(today.year, today.month, today.day, 0, 0, 0),
+ end_date=datetime(today.year, today.month, today.day, 23, 59, 59),
path=path,
)
@@ -521,8 +521,8 @@ def preroll_schedule(schedule_file: Optional[str] = None) -> List[ScheduleEntry]
entry = ScheduleEntry(
type=schedule_section,
force=False,
- startdate=datetime(today.year, today.month, today.day, 0, 0, 0),
- enddate=datetime(today.year, today.month, today.day, 23, 59, 59),
+ start_date=datetime(today.year, today.month, today.day, 0, 0, 0),
+ end_date=datetime(today.year, today.month, today.day, 23, 59, 59),
path=path,
)
@@ -539,7 +539,7 @@ def preroll_schedule(schedule_file: Optional[str] = None) -> List[ScheduleEntry]
raise ValueError(msg)
# Sort list so most recent Ranges appear first
- schedule.sort(reverse=True, key=lambda x: x.startdate)
+ schedule.sort(reverse=True, key=lambda x: x.start_date)
logger.debug("***START Schedule Set to be used***")
logger.debug(schedule)
@@ -549,14 +549,14 @@ def preroll_schedule(schedule_file: Optional[str] = None) -> List[ScheduleEntry]
def build_listing_string(items: List[str], play_all: bool = False) -> str:
- """Build the Plex formatted string of preroll paths
+ """Build the Plex formatted string of pre_roll paths
Args:
- items (list): List of preroll video paths to place into a string listing
+ items (list): List of pre_roll video paths to place into a string listing
play_all (bool, optional): Play all videos. [Default: False (Random choice)]
Returns:
- string: CSV Listing (, or ;) based on play_all param of preroll video paths
+ string: CSV Listing (, or ;) based on play_all param of pre_roll video paths
"""
if len(items) == 0:
@@ -572,8 +572,8 @@ def build_listing_string(items: List[str], play_all: bool = False) -> str:
return listing
-def preroll_listing(schedule: List[ScheduleEntry], for_datetime: Optional[datetime] = None) -> str:
- """Return listing of preroll videos to be used by Plex
+def pre_roll_listing(schedule: List[ScheduleEntry], for_datetime: Optional[datetime] = None) -> str:
+ """Return listing of pre_roll videos to be used by Plex
Args:
schedule (List[ScheduleEntry]): List of schedule entries (See: getPrerollSchedule)
@@ -581,7 +581,7 @@ def preroll_listing(schedule: List[ScheduleEntry], for_datetime: Optional[dateti
Useful for simulating what different dates produce
Returns:
- string: listing of preroll video paths to be used for Extras. CSV style: (;|,)
+ string: listing of pre_roll video paths to be used for Extras. CSV style: (;|,)
"""
listing = ""
entries = schedule_types()
@@ -598,8 +598,8 @@ def preroll_listing(schedule: List[ScheduleEntry], for_datetime: Optional[dateti
# process the schedule for the given date
for entry in schedule:
try:
- entry_start = entry.startdate
- entry_end = entry.enddate
+ entry_start = entry.start_date
+ entry_end = entry.end_date
if not isinstance(entry_start, datetime): # type: ignore
entry_start = datetime.combine(entry_start, datetime.min.time())
if not isinstance(entry_end, datetime): # type: ignore
@@ -628,7 +628,7 @@ def preroll_listing(schedule: List[ScheduleEntry], for_datetime: Optional[dateti
# check new schedule item against exist list
for e in entries[entry_type]:
duration_new = duration_seconds(entry_start, entry_end)
- duration_curr = duration_seconds(e.startdate, e.enddate)
+ duration_curr = duration_seconds(e.start_date, e.end_date)
# only the narrowest timeframe should stay
# disregard if a force entry is there
@@ -655,10 +655,10 @@ def preroll_listing(schedule: List[ScheduleEntry], for_datetime: Optional[dateti
if entries["monthly"] and not entries["weekly"] and not entries["date_range"]:
merged_list.extend([p.path for p in entries["monthly"]]) # type: ignore
if (
- entries["default"]
- and not entries["monthly"]
- and not entries["weekly"]
- and not entries["date_range"]
+ entries["default"]
+ and not entries["monthly"]
+ and not entries["weekly"]
+ and not entries["date_range"]
):
merged_list.extend([p.path for p in entries["default"]]) # type: ignore
@@ -667,27 +667,27 @@ def preroll_listing(schedule: List[ScheduleEntry], for_datetime: Optional[dateti
return listing
-def save_preroll_listing(plex: PlexServer, preroll_listing: Union[str, List[str]]) -> None:
+def save_pre_roll_listing(plex: PlexServer, pre_roll_listing: Union[str, List[str]]) -> None:
"""Save Plex Preroll info to PlexServer settings
Args:
plex (PlexServer): Plex server to update
- preroll_listing (str, list[str]): csv listing or List of preroll paths to save
+ pre_roll_listing (str, list[str]): csv listing or List of pre_roll paths to save
"""
- # if happend to send in an Iterable List, merge to a string
- if isinstance(preroll_listing, list):
- preroll_listing = build_listing_string(list(preroll_listing))
+ # if happened to send in an Iterable List, merge to a string
+ if isinstance(pre_roll_listing, list):
+ pre_roll_listing = build_listing_string(list(pre_roll_listing))
- logger.debug('Attempting save of pre-rolls: "%s"', preroll_listing)
+ logger.debug('Attempting save of pre-rolls: "%s"', pre_roll_listing)
- plex.settings.get("cinemaTrailersPrerollID").set(preroll_listing) # type: ignore
+ plex.settings.get("cinemaTrailersPrerollID").set(pre_roll_listing) # type: ignore
try:
plex.settings.save() # type: ignore
except Exception as e:
logger.error('Unable to save Pre-Rolls to Server: "%s"', plex.friendlyName, exc_info=e) # type: ignore
raise e
- logger.info('Saved Pre-Rolls: Server: "%s" Pre-Rolls: "%s"', plex.friendlyName, preroll_listing) # type: ignore
+ logger.info('Saved Pre-Rolls: Server: "%s" Pre-Rolls: "%s"', plex.friendlyName, pre_roll_listing) # type: ignore
if __name__ == "__main__":
@@ -716,12 +716,12 @@ if __name__ == "__main__":
logger.error("Error connecting to Plex", exc_info=e)
raise e
- schedule = preroll_schedule(args.schedule_file)
- prerolls = preroll_listing(schedule)
+ schedule = pre_roll_schedule(args.schedule_file)
+ pre_rolls = pre_roll_listing(schedule)
if args.do_test_run:
- msg = f"Test Run of Plex Pre-Rolls: **Nothing being saved**\n{prerolls}\n"
+ msg = f"Test Run of Plex Pre-Rolls: **Nothing being saved**\n{pre_rolls}\n"
logger.debug(msg)
print(msg)
else:
- save_preroll_listing(plex, prerolls)
+ save_pre_roll_listing(plex, pre_rolls)
diff --git a/schedules.yaml.sample b/schedules.yaml.sample
new file mode 100644
index 0000000..02c01b9
--- /dev/null
+++ b/schedules.yaml.sample
@@ -0,0 +1,64 @@
+# All keys must be in lowercase
+# All paths will be case-sensitive based on your environment (Linux, Windows)
+
+# Order of precedence:
+# 1. Misc always_use
+# 2. Date range
+# 3. Weekly
+# 4. Monthly
+# 5. Default
+
+# Additional items for preroll selection processing
+misc:
+ enabled: true
+ always_use: /path/to/video.mp4 # If enabled, always include these video files in pre-role listing
+
+# Schedule prerolls by date and time frames
+date_range:
+ enabled: false
+ ranges:
+ # Each entry requires start_date, end_date, path values
+ - start_date: 2020-01-01 # Jan 1st, 2020
+ end_date: 2020-01-02 # Jan 2nd, 2020
+ path: /path/to/video.mp4
+ - start_date: xxxx-07-04 # Every year on July 4th
+ end_date: xxxx-07-04 # Every year on July 4th
+ path: /path/to/video.mp4
+ - start_date: xxxx-xx-02 # Every year on the 2nd of every month
+ end_date: xxxx-xx-03 # Every year on the 3rd of every month
+ path: /path/to/video.mp4
+ - start_date: xxxx-xx-xx 08:00:00 # Every day at 8am
+ end_date: xxxx-xx-xx 09:30:00 # Every day at 9:30am
+ path: /path/to/holiday_video.mp4
+
+# Schedule prerolls by week of the year
+weekly:
+ # No need to have all the week numbers; the script skips over missing entries
+ # Each week number must be surrounded by quotes
+ enabled: false
+ "1":
+ "2": /path/to/video.mkv
+ "52":
+
+# Schedule prerolls by month of the year
+monthly:
+ # No need to have all the months here, script will skip over missing entries (logs a warning)
+ # Keys must be lowercase, three-char month abbreviation
+ enabled: false
+ jan: /path/to/video.mp4
+ feb:
+ mar:
+ apr:
+ may: /path/to/video.mp4
+ jun:
+ jul:
+ aug:
+ sep: /path/to/video.mp4;/path/to/video.mp4
+ oct:
+ nov:
+ dec:
+
+# Use a default preroll if no other schedule is matched
+default:
+ enabled: true
+ path: /path/to/video.mp4
diff --git a/util/plexutil.py b/util/plexutil.py
index 775c7cb..133ec28 100644
--- a/util/plexutil.py
+++ b/util/plexutil.py
@@ -21,7 +21,7 @@ SCRIPT_NAME = os.path.splitext(filename)[0]
def plex_config(config_file: Optional[str] = "") -> Dict[str, str]:
- """Return Plex Config paramaters for connection info {PLEX_URL, PLEX_TOKEN}\n
+ """Return Plex Config parameters for connection info {PLEX_URL, PLEX_TOKEN}\n
Attempts to use one of either:\n
* supplier path/to/config file (INI Format)
* local config.ini (primary)
diff --git a/util/schedulefile_schema.json b/util/schedule_file_schema.json
similarity index 100%
rename from util/schedulefile_schema.json
rename to util/schedule_file_schema.json