mirror of
https://github.com/FakeTV/pseudo-channel.git
synced 2026-01-02 00:03:15 +00:00
Merge branch 'develop'
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -11,3 +11,6 @@ pseudo-channel.db
|
||||
env/
|
||||
*.log
|
||||
*.pid
|
||||
*.json
|
||||
.cache/
|
||||
.prevplaying
|
||||
|
||||
750
PseudoChannel.py
750
PseudoChannel.py
File diff suppressed because it is too large
Load Diff
44
README.md
44
README.md
@@ -1,12 +1,11 @@
|
||||
# PseudoChannel.py - a Plex Controller for Home-Brewed TV Channels
|
||||
# PseudoChannel.py - Your Home-Brewed TV Channels
|
||||
|
||||
Joined by the author of [Fake TV](https://medium.com/@Fake.TV), this project aims at tackling one issue: creating a fake tv channel experience with your own media library (movies, tv shows, commercials, etc.). The idea is super simple... when you turn on your TV, rather than hopping straight to Netflix, you can choose to watch your own channel of curated media like a real channel, with randomized movie time blocks, weekend morning cartoons, 90's commercials to fill up gaps and more. We aim to add a ton of neat features but the basic idea is to have something that feels like a real TV channel. That being said it isn't supposed to "pause" nor are you supposed to intervene too much. Just like a real channel you are presented with a channel that you define once and let it go as it advances in series episodes, playing random movies where specified (defined by various parameters like genre, "Kevin Bacon", etc.). Think: weekday movie nights @ 8:00 PM. Or perhaps you want to further specify your weekly Wednesday evening movie be a movie in your Plex library that stars "Will Smith". PseudoChannel is built to interface with the Plex media server. So if you want to have your very own PseudoChannel, you first need to set up your home server using [Plex](https://www.plex.tv/). After that you can come back here to learn how to setup everything else. Although this app runs using Python and the command line, we aim to make all of it as easy as possible to understand for those who are intimidated by this sort of technology. If you have a question, are troubleshooting or have feature ideas, just leave an 'issue' here in this repository.
|
||||
|
||||
Joined by the author of [Fake TV](https://medium.com/@Fake.TV), this project aims at tackling one issue: creating a fake tv channel experience with your own media library (movies, tv shows, commercials, etc.). The idea is super simple... when you turn on your TV, rather than hopping straight to Netflix, you can choose to watch your own channel of curated media like a real channel, with randomized movie time blocks, weekend morning cartoons, 90's commercials to fill up gaps and more. We aim to add a ton of neat features but the basic idea is to have something that feels like a real TV channel. That being said it isn't supposed to "pause" nor are you supposed to intervene too much. Just like a real channel you are presented with a channel that you define once and let it go as it advances in series episodes, playing random movies where specified (defined by various parameters like genre, "Kevin Bacon", etc.). Think: weekday movie nights @ 8:00 PM. Or perhaps you want to further specify your weekly Wednesday evening movie be a movie in your Plex library that stars "Will Smith". Currently the latter feature among many others are being developed but this is the goal. PseudoChannel is built to interface with the Plex media server. So if you want to have your very own PseudoChannel, you first need to set up your home server using [Plex](https://www.plex.tv/). After that you can come back here to learn how to setup everything else. Please note that we just started this project so everything is evolving rapidly. Check back often. We aim to have a decent working "alpha" version within a week or so. This readme / the how-to guide will all be very user friendly. Although this app runs using Python and the command line, we aim to make all of it as easy as possible to understand for those who are intimidated by this sort of technology.
|
||||
|
||||

|
||||
|
||||
If interested in this project, check back very soon when the alpha is up. It's close and a tiny bit more user friendly. :)
|
||||
|
||||
## How to Use (in the case someone stumbles across this and wants to try it before its polished):
|
||||
## How to Use:
|
||||
|
||||
- The instructions below are all for configuring the **"controller"** device (i.e. a laptop or raspberry pi running linux). This is the device this app runs on to control the Plex client. The **"client"** device should be a Raspberry Pi running Rasplex hooked up to your TV via HDMI - although I'm sure other devices work great too (never tried).
|
||||
|
||||
@@ -25,21 +24,22 @@ baseurl = 'http://192.168.1.28:32400'
|
||||
```
|
||||
*This file is important as it tells PseudoChannel.py how/where to connect to your Plex server. It should sit just outside of this /pseudo-channel/ directory.*
|
||||
|
||||
3. Edit the `pseudo_config.py` / `the pseudo_schedule.xml` to your liking. You can specify your plex media library names within the `pseudo_config.py` file... the default assumes that you have these libraries in your Plex server named like so: "TV Shows", "Movies" & "Commercials". If you do not intend on using commercials just set the `useCommercialInjection` flag to `False`. There are a few other experimental options like using Google Calendar rather than an XML. It is an arduous process to initially set up and I've found the XML method to be the easiest method for organizing your schedule - so stick with that for now.
|
||||
3. Edit the `pseudo_config.py` / `the pseudo_schedule.xml` to your liking. You can specify your plex media library names within the `pseudo_config.py` file... If you do not intend on using commercials just set the `useCommercialInjection` flag to `False`. There are a few other experimental options like using Google Calendar rather than an XML. It is an arduous process to initially set up and I've found the XML method to be the easiest method for organizing your schedule - so stick with that for now. Finally, setup your schedule in the xml file. There are some detailed instructions commented at the top of that file.
|
||||
|
||||
4. Run the `PseudoChannel.py` file with the following flags:
|
||||
|
||||
```bash
|
||||
% python PseudoChannel.py -u -xml -g -r
|
||||
% python PseudoChannel.py -u -xml -g -m -r
|
||||
```
|
||||
*You can also run `-h` to view all the options. Keep in mind not all options are operational & some are experimental. Stick with the ones above and use `-c` to find the name(s) of your Plex client(s).*
|
||||
*You can also run `-h` to view all the options. Use `-c` to find the name(s) of your Plex client(s) to add to the config.*
|
||||
|
||||
- The `-u` flag will prepare & update (& create if not exists) the local `pseudo-channel.db`, you only need to run this once in the beginning or later when you have added new media to your Plex libraries.
|
||||
- The `-xml` flag will update the newly created local db with your schedule from the xml file - you should run this everytime you make changes to the xml.
|
||||
- The `-g` file will generate the daily schedule (for today) based on the xml. This is useful for the first run or testing (or manually advancing the daily queue forward). Running this flag say, 15 times will advance the play queue forward 15 days. It is automatically run every night at midnight to generate the daily schedule.
|
||||
- Finally, the `-r` flag will run the app, checking the time / triggering the playstate of any media that is scheduled. It will also update the daily schedule when the clock hits 11.59 (or whatever time you've configured in the config file). The xml schedule is a bit tempermental at the moment so if you see errors, check your entries there first. Make sure all of your movie names / TV Series names are correct.
|
||||
- The `-m` flag makes both the .html/.xml files and starts a simple html web server in the `./schedules` directory.
|
||||
- Finally, the `-r` flag will run the app, checking the time / triggering the playstate of any media that is scheduled. It will also update the daily schedule when the clock hits 11.59 (or whatever time you've configured in the config file). If you see errors, check your entries in the xml first. Check your times, check for overlaps & make sure your are using ascii characters to replace foreign characters like umlauts and '&' characters, etc. Make sure all of your movie names / TV Series names are correct.
|
||||
|
||||
You can run `% python PseudoChannel.py` with the following options. The order is important (i.e. `% python PseudoChannel.py -u -xml -g -m -r`):
|
||||
You can run `% python PseudoChannel.py` with the following options. The order is important depending on what you are doing (i.e. `% python PseudoChannel.py -u -xml -g -m -r`):
|
||||
|
||||
| Flag | Description |
|
||||
| ------------------------|--------------|
|
||||
@@ -50,16 +50,22 @@ You can run `% python PseudoChannel.py` with the following options. The order is
|
||||
| -c, --show_clients | Show connected Plex clients. |
|
||||
| -s, --show_schedule | Output the generated "Daily Schedule" to your terminal. |
|
||||
| -m, --make_html | Manually generate both html / xml docs based on the "Daily Schedule". |
|
||||
| -e, --export | Export the current queue of your "TV Shows" episodes. Useful when redoing your local DB. |
|
||||
| -i, --import | Import the previously exported queue of your "TV Shows" episodes. |
|
||||
|
||||
## `startstop.sh` - Alternative Way of Running the Application:
|
||||
|
||||
Within the app directory, there is a file named, `startstop.sh`. This bash script is a convenient way to start/stop the application. When run, it will start the application and save the "pid" of the process to the application directory. When run again, it will check to see if that process is running, if so it will stop it. All you have to do is run:
|
||||
|
||||
```bash
|
||||
% ./startstop.sh
|
||||
```
|
||||
|
||||
When you start the application with this bash script, you can close your terminal as it will keep running in the background. Later, when you come back and want to stop it... you can just execute that file once more and it will stop the running process. Please note: It's good to test the application and your configurations using the manual process above before running this bash script. Although there is a `pseudo-channel.log` that is created within the application directory, it is easier to just view the output in your terminal window - something that won't happen when using the bash script.
|
||||
|
||||
## Futher Info:
|
||||
|
||||
Features are being added to the xml but as of now there are a few. Within the XML `<time>` entry you are able to pass in various attributes to set certain values. As of now, aside from "title" and "type" which are mandatory, you can take advantage of "time-shift". This parameter accepts values in minutes and can be no lower than "1". If the attribute, "strict-time" is set to "false", then this `<time>` entry will be shifted to a new time based on the previous time with a smaller gap calculated according to the value in "time-shift". Basically, if you do not want any gaps in your daily generated schedule you would leave "strict-time" false and set "time-shift" to "1" for all `<time>` entries. However, this will create a schedule with weird start times like, "1:57 PM". Taking advantage of the "time-shift" perameter will correct this. If you set it to a value of "5", all media is shifted and hooked on to a "pretty" time that is a multiple of 5. So if used, rather then having a "Seinfeld" episode being set to "1:57 PM" it may be recalculated and scheduled for "2:00 PM". However, if you would like to make sure that "The Simpsons" will always start every weekday at "6:00 PM" then you can simply set that `<time>` entry to `srtict-time="true"`. This will ensure that despite other non-strict times shifting around, "The Simpsons" will air every weekday at the desired "6:00 PM" as scheduled (be sure that you haven't accidentally made two time entries "strict-time" for the same day/time - this sort of thing will cause weird scheduling errors). When using "strict-time" or having the "time-shift" value > than 1 (minute), this will result in empty gaps in the schedule. Currently I have a flag in the config for "commercial injection" to fill up the gaps as much as possible with commercials from commercials in your Plex server "Commercials" library. If you do not want to use this feature or if you don't have any commercials in your Plex server, just open up that `pseudo_config.py` file and set `useCommercialInjection` to `False`.
|
||||
|
||||
## Power Saving / Using an External Controller:
|
||||
|
||||
*Warning: this section is useful only when getting the app working well manually. I highly recommend sticking with the manual methods of running PseudoChannel.py before attempting these features. It also helps to fully understand how the app works before implementing an external controller. You can better understand this app by tinkering with the methods above before attempting this part. When in doubt, just reach out to me here by creating an issue in this repo.*
|
||||
|
||||
If you'd like to use your PseudoChannel but do not want your media playing when you're not watching it, you have a handful of options to make the channel even cooler. When you run the app using `python PseudoChannel.py -r` it checks the `daily_schedule` that was generated for the day to see where the playhead is when you execute the app... if it happens to be `1:17 PM` when you run the app and "Seinfeld" started at `1:10 PM`, then the app will start playing the scheduled "Seinfeld" episode exactly 7 minutes in as it is scheduled for the day (down to the second). This is nice but what if you want to start / stop the app dynamically using a button hooked up to your Raspberry Pi? Or perhaps you'd like to setup a voice command using your Amazon Echo. All you have to do is trigger the `startstop.sh` bash file. So let's say you decided to use an IR Led reciever to catch commands sent by your TV remote. After you configure the IR circuit on your Raspberry Pi, you would set it to execute the `startstop.sh` file - this will then either start PseudoChannel.py & resume the daily schedule if it isn't already running, or stop the currently running PseudoChannel.py instance if it is running. Doing this will pick up the playhead exactly where it would be as if the app never stopped. However, if you decided to use PseudoChannel in this way you need to manually setup a crontab to trigger the daily update every night at `12:00 AM`. Since the app isn't running unless you trigger it, it needs to know to update the daily schedule and advance forward in the queue. To do this, there is another bash file named, `generate_daily_sched.sh`. This is the file that should be setup with crontab to be triggered every evening at midnight. Essentially, this file will check to see if the app has been started via, `startstop.sh` - if so it will do nothing as the running app will know to update when the clock hits midnight; if however, it finds that the `startstop.sh` script did not spawn an instance of the app, it will trigger, `python PseudoChannel.py -g` which will generate the daily schedule. It's important to note that you should only use the two bash scripts, `startstop.sh` & `generate_daily_sched.sh` with each other. If you have previously started the app using the command, `python PseudoChannel.py -r`, you should kill it before using the, `startstop.sh` or the `generate_daily_sched.sh`. So basically, if you decide to use this neat power-saving / dynamic controlling feature using the bash scripts, then stick with that. If you accidentally have an instance of the app running using `PseudoChannel.py` with the `-r` flag and also have the bash file setup with a crontab, you will unwittingly trigger the app to generate the daily schedule twice at the same time when the clock hits midnight. Before attempting any of this I recommend keeping it simple and just manually running the app. This will also help you easily view debug info incase you run into XML issues or other unforseen errs. Once everything seems to be running great, then move to this method.
|
||||
Features are being added to the xml but as of now there are a few. Within the XML `<time>` entry you are able to pass in various attributes to set certain values. As of now, aside from "title" and "type" which are mandatory, you can take advantage of "time-shift". This parameter accepts values in minutes and can be no lower than "1". If the attribute, "strict-time" is set to "false", then this `<time>` entry will be shifted to a new time based on the previous time with a smaller gap calculated according to the value in "time-shift". Basically, if you do not want any gaps in your daily generated schedule you would leave "strict-time" false and set "time-shift" to "1" for all `<time>` entries. However, this will create a schedule with weird start times like, "1:57 PM". Taking advantage of the "time-shift" perameter will correct this. If you set it to a value of "5", all media is shifted and hooked on to a "pretty" time that is a multiple of 5. So if used, rather then having a "Seinfeld" episode being set to "1:57 PM" it may be recalculated and scheduled for "2:00 PM". However, if you would like to make sure that "The Simpsons" will always start every weekday at "6:00 PM" then you can simply set that `<time>` entry to `srtict-time="true"`. This will ensure that despite other non-strict times shifting around, "The Simpsons" will air every weekday at the desired "6:00 PM" as scheduled (be sure that you haven't accidentally made two time entries "strict-time" for the same day/time - this sort of thing will cause weird scheduling errors). When using "strict-time" or having the "time-shift" value > than 1 (minute), this will result in empty gaps in the schedule. Currently I have a flag in the config for "commercial injection" to fill up the gaps as much as possible with commercials from commercials in your Plex server "Commercials" library. If you do not want to use this feature or if you don't have any commercials in your Plex server, just open up that `pseudo_config.py` file and set `useCommercialInjection` to `False`. Please check the `pseudo-channel.xml` for more information.
|
||||
|
||||
## The Automatically Generated .HTML Daily Schedule / Server
|
||||
|
||||
@@ -74,6 +80,4 @@ http://192.168.1.28:8000
|
||||
|
||||
Stay tuned for a polished version / bug fixes. I've also started a user friendly web version that hopefully will be working soon.
|
||||
|
||||
|
||||
|
||||
|
||||
Special thanks to Mark @ [Fake TV](https://medium.com/@Fake.TV). Without his creative ideas and love for TV, this "PseudoChannel" wouldn't be as cool as it is. I look forward to tinkering with this project and seeing others "unplugging" and creating their own home network. Mark has some excellent ideas in regard to making this thing much more usable as a "pseudo-cable" network - I think this will be in the next version as it is the 'icing on the cake' sort of feature. Anyway, enjoy!
|
||||
128
bash-scripts/channeldown.sh
Normal file
128
bash-scripts/channeldown.sh
Normal file
@@ -0,0 +1,128 @@
|
||||
#!/bin/bash
|
||||
|
||||
# file: channeldown.sh
|
||||
|
||||
#----
|
||||
# Simple script to cycle through multiple pseudo-channel instances - triggering start / stop.
|
||||
#----
|
||||
|
||||
#----
|
||||
# To Use:
|
||||
# Configure something (a tv remote or alexa) to trigger this script. Make sure you move this script just
|
||||
# outside of the pseudo-channel directories:
|
||||
# -------------------
|
||||
# -channels/
|
||||
# --pseudo-channel_1/
|
||||
# ---startstop.sh
|
||||
# --pseudo-channel_2/
|
||||
# ---startstop.sh
|
||||
# --pseudo-channel_3/
|
||||
# ---startstop.sh
|
||||
# --channeldown.sh <--- on the same level as the 3 channels.
|
||||
#----
|
||||
|
||||
# Make sure that each channel dir ends with a "_" + an incrementing number as seen above.
|
||||
|
||||
#----BEGIN EDITABLE VARS----
|
||||
|
||||
SCRIPT_TO_EXECUTE='startstop.sh'
|
||||
|
||||
OUTPUT_PREV_CHANNEL_PATH=.
|
||||
|
||||
OUTPUT_PREV_CHANNEL_FILE=".prevplaying"
|
||||
|
||||
CHANNEL_DIR_INCREMENT_SYMBOL="_"
|
||||
|
||||
#----END EDITABLE VARS-------
|
||||
|
||||
FIRST_RUN=false
|
||||
|
||||
# Scan the dir to see how many channels there are, store them in an arr.
|
||||
CHANNEL_DIR_ARR=( $(find . -maxdepth 1 -type d -name '*'"$CHANNEL_DIR_INCREMENT_SYMBOL"'[[:digit:]]' -printf "%P\n") )
|
||||
|
||||
# If the previous channel txt file doesn't exist already create it (first run?)
|
||||
if [ ! -e "$OUTPUT_PREV_CHANNEL_PATH/$OUTPUT_PREV_CHANNEL_FILE" ]; then
|
||||
|
||||
#FIRST_RUN_NUM=$((${#CHANNEL_DIR_ARR[@]}))
|
||||
echo 1 > "$OUTPUT_PREV_CHANNEL_PATH/$OUTPUT_PREV_CHANNEL_FILE"
|
||||
|
||||
echo "First run: $FIRST_RUN_NUM"
|
||||
|
||||
FIRST_RUN=true
|
||||
|
||||
fi
|
||||
|
||||
# If this script see's there are multiple channels,
|
||||
# then read file, get prevchannel, increment, and trigger next channel:
|
||||
if [ "${#CHANNEL_DIR_ARR[@]}" -gt 1 ]; then
|
||||
|
||||
NEXT_CHANNEL=""
|
||||
|
||||
NEXT_CHANNEL_NUM=1
|
||||
|
||||
PREV_CHANNEL_FOUND=false
|
||||
|
||||
PREV_CHANNEL_DIR=""
|
||||
|
||||
echo "+++++ There are ${#CHANNEL_DIR_ARR[@]} channels detected."
|
||||
|
||||
PREV_CHANNEL=$(<$OUTPUT_PREV_CHANNEL_PATH/$OUTPUT_PREV_CHANNEL_FILE)
|
||||
|
||||
echo "+++++ It looks like the previous channel was: $PREV_CHANNEL"
|
||||
|
||||
# Now that the prevchannel is stored in a var, loop through channels and find prev channel & increment
|
||||
for ((i = ${#CHANNEL_DIR_ARR[@]};i >= 1;i--)); do
|
||||
|
||||
NEXT_CHANNEL_NUM=$i
|
||||
|
||||
if [[ ${i} == *"$PREV_CHANNEL"* ]]; then
|
||||
echo "+++++ Found previous channel, incrementing by 1."
|
||||
PREV_CHANNEL_FOUND=true
|
||||
PREV_CHANNEL_DIR=${CHANNEL_DIR_ARR[i-1]}
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$PREV_CHANNEL_FOUND" = true ] ; then
|
||||
|
||||
NEXT_CHANNEL=${CHANNEL_DIR_ARR[i-1]}
|
||||
|
||||
break
|
||||
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
# If the next channel is an empty string, then we need to start the cycle over.
|
||||
if [ -z "$NEXT_CHANNEL" ]; then
|
||||
|
||||
ARR_LENGTH=(${#CHANNEL_DIR_ARR[@]})
|
||||
NEXT_CHANNEL=${CHANNEL_DIR_ARR[$ARR_LENGTH-1]}
|
||||
echo "Starting cycle over."
|
||||
echo "$PREV_CHANNEL_DIR"
|
||||
echo "$NEXT_CHANNEL"
|
||||
|
||||
fi
|
||||
|
||||
echo "+++++ The next channel is: $NEXT_CHANNEL"
|
||||
|
||||
# Write next channel to previous channel file to reference later
|
||||
echo "$NEXT_CHANNEL" | cut -d "_" -f2 > "$OUTPUT_PREV_CHANNEL_PATH/$OUTPUT_PREV_CHANNEL_FILE"
|
||||
|
||||
# Finally let's trigger the startstop script in both the previous channel and the next channel dirs.
|
||||
# This will stop the previous channels playback & trigger the next channels playback
|
||||
|
||||
if [ "$FIRST_RUN" = false ]; then
|
||||
cd "$OUTPUT_PREV_CHANNEL_PATH"/"$PREV_CHANNEL_DIR" && ./"$SCRIPT_TO_EXECUTE"
|
||||
cd ../"$NEXT_CHANNEL" && ./"$SCRIPT_TO_EXECUTE"
|
||||
else
|
||||
|
||||
cd "$OUTPUT_PREV_CHANNEL_PATH"/"$NEXT_CHANNEL" && ./"$SCRIPT_TO_EXECUTE"
|
||||
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
|
||||
|
||||
fi
|
||||
|
||||
exit 0
|
||||
119
bash-scripts/channelup.sh
Normal file
119
bash-scripts/channelup.sh
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
|
||||
# file: channelup.sh
|
||||
|
||||
#----
|
||||
# Simple script to cycle through multiple pseudo-channel instances - triggering start / stop.
|
||||
#----
|
||||
|
||||
#----
|
||||
# To Use:
|
||||
# Configure something (a tv remote or alexa) to trigger this script. Make sure you move this script just
|
||||
# outside of the pseudo-channel directories:
|
||||
# -------------------
|
||||
# -channels/
|
||||
# --pseudo-channel_1/
|
||||
# ---startstop.sh
|
||||
# --pseudo-channel_2/
|
||||
# ---startstop.sh
|
||||
# --pseudo-channel_3/
|
||||
# ---startstop.sh
|
||||
# --channelup.sh <--- on the same level as the 3 channels.
|
||||
#----
|
||||
|
||||
# Make sure that each channel dir ends with a "_" + an incrementing number as seen above.
|
||||
|
||||
#----BEGIN EDITABLE VARS----
|
||||
|
||||
SCRIPT_TO_EXECUTE='startstop.sh'
|
||||
|
||||
OUTPUT_PREV_CHANNEL_PATH=.
|
||||
|
||||
OUTPUT_PREV_CHANNEL_FILE=".prevplaying"
|
||||
|
||||
CHANNEL_DIR_INCREMENT_SYMBOL="_"
|
||||
|
||||
#----END EDITABLE VARS-------
|
||||
|
||||
FIRST_RUN=false
|
||||
|
||||
# If the previous channel txt file doesn't exist already create it (first run?)
|
||||
if [ ! -e "$OUTPUT_PREV_CHANNEL_PATH/$OUTPUT_PREV_CHANNEL_FILE" ]; then
|
||||
|
||||
echo 1 > "$OUTPUT_PREV_CHANNEL_PATH/$OUTPUT_PREV_CHANNEL_FILE"
|
||||
|
||||
FIRST_RUN=true
|
||||
|
||||
fi
|
||||
|
||||
# If the file exists b
|
||||
|
||||
# Scan the dir to see how many channels there are, store them in an arr.
|
||||
CHANNEL_DIR_ARR=( $(find . -maxdepth 1 -type d -name '*'"$CHANNEL_DIR_INCREMENT_SYMBOL"'[[:digit:]]' -printf "%P\n") )
|
||||
|
||||
# If this script see's there are multiple channels,
|
||||
# then read file, get prevchannel, increment, and trigger next channel:
|
||||
if [ "${#CHANNEL_DIR_ARR[@]}" -gt 1 ]; then
|
||||
|
||||
NEXT_CHANNEL=""
|
||||
|
||||
PREV_CHANNEL_FOUND=false
|
||||
|
||||
PREV_CHANNEL_DIR=""
|
||||
|
||||
echo "+++++ There are ${#CHANNEL_DIR_ARR[@]} channels detected."
|
||||
|
||||
PREV_CHANNEL=$(<$OUTPUT_PREV_CHANNEL_PATH/$OUTPUT_PREV_CHANNEL_FILE)
|
||||
|
||||
echo "+++++ It looks like the previous channel was: $PREV_CHANNEL"
|
||||
|
||||
# Now that the prevchannel is stored in a var, loop through channels and find prev channel & increment
|
||||
for channel in "${CHANNEL_DIR_ARR[@]}"
|
||||
do
|
||||
if [[ $channel == *"$PREV_CHANNEL"* ]]; then
|
||||
echo "+++++ Found previous channel, incrementing by 1."
|
||||
PREV_CHANNEL_FOUND=true
|
||||
PREV_CHANNEL_DIR=$channel
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$PREV_CHANNEL_FOUND" = true ] ; then
|
||||
|
||||
NEXT_CHANNEL=$channel
|
||||
|
||||
break
|
||||
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
# If the next channel is an empty string, then we need to start the cycle over.
|
||||
if [ -z "$NEXT_CHANNEL" ]; then
|
||||
|
||||
NEXT_CHANNEL=${CHANNEL_DIR_ARR[0]}
|
||||
|
||||
fi
|
||||
|
||||
echo "+++++ The next channel is: $NEXT_CHANNEL"
|
||||
|
||||
# Write next channel to previous channel file to reference later
|
||||
echo "$NEXT_CHANNEL" | cut -d "_" -f2 > "$OUTPUT_PREV_CHANNEL_PATH/$OUTPUT_PREV_CHANNEL_FILE"
|
||||
|
||||
# Finally let's trigger the startstop script in both the previous channel and the next channel dirs.
|
||||
# This will stop the previous channels playback & trigger the next channels playback
|
||||
|
||||
if [ "$FIRST_RUN" = false ]; then
|
||||
cd "$OUTPUT_PREV_CHANNEL_PATH"/"$PREV_CHANNEL_DIR" && ./"$SCRIPT_TO_EXECUTE"
|
||||
cd ../"$NEXT_CHANNEL" && ./"$SCRIPT_TO_EXECUTE"
|
||||
else
|
||||
|
||||
cd "$OUTPUT_PREV_CHANNEL_PATH"/"$NEXT_CHANNEL" && ./"$SCRIPT_TO_EXECUTE"
|
||||
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
|
||||
|
||||
fi
|
||||
|
||||
exit 0
|
||||
13
bash-scripts/generate-channels-daily-schedules.sh
Normal file
13
bash-scripts/generate-channels-daily-schedules.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
# file: generate-channels-daily-schedules.sh
|
||||
|
||||
#----
|
||||
# Simple script to generate the daily schedule for each individual channel.
|
||||
#
|
||||
#----
|
||||
|
||||
#----
|
||||
# To Use:
|
||||
# This script needs to be setup with a crontab entry that runs everyday at midnight.
|
||||
#----
|
||||
70
bash-scripts/updatechannels.sh
Normal file
70
bash-scripts/updatechannels.sh
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
# file: updatechannels.sh
|
||||
|
||||
#----
|
||||
# Simple script to updates each channels local db with new Plex lib items / xml.
|
||||
#----
|
||||
|
||||
#----
|
||||
# To Use:
|
||||
# If you added new content to your Plex Library, just make this file executable move it
|
||||
# to where the plex_token.py file is and run ./updatechannels.sh
|
||||
#----
|
||||
|
||||
# Make sure that each channel dir ends with a "_" + an incrementing number as seen above.
|
||||
|
||||
#----BEGIN EDITABLE VARS----
|
||||
|
||||
SCRIPT_TO_EXECUTE_PLUS_ARGS='PseudoChannel.py -u -xml'
|
||||
|
||||
OUTPUT_PREV_CHANNEL_PATH=.
|
||||
|
||||
CHANNEL_DIR_INCREMENT_SYMBOL="_"
|
||||
|
||||
PYTHON_TO_USE="$(which python)"
|
||||
|
||||
# If using 'virtualenv' with python, specify the local virtualenv dir.
|
||||
VIRTUAL_ENV_DIR="env"
|
||||
|
||||
#----END EDITABLE VARS-------
|
||||
|
||||
# If virtualenv specified & exists, using that version of python instead.
|
||||
if [ -d "$VIRTUAL_ENV_DIR" ]; then
|
||||
|
||||
PYTHON_TO_USE="$VIRTUAL_ENV_DIR/bin/python"
|
||||
|
||||
fi
|
||||
|
||||
# If the file exists b
|
||||
|
||||
# Scan the dir to see how many channels there are, store them in an arr.
|
||||
CHANNEL_DIR_ARR=( $(find . -maxdepth 1 -type d -name '*'"$CHANNEL_DIR_INCREMENT_SYMBOL"'[[:digit:]]' -printf "%P\n") )
|
||||
|
||||
# If this script see's there are multiple channels,
|
||||
# then loop through each channel and run the updates
|
||||
if [ "${#CHANNEL_DIR_ARR[@]}" -gt 1 ]; then
|
||||
|
||||
# If virtualenv specified & exists, using that version of python instead.
|
||||
if [ -d ./"$channel"/"$VIRTUAL_ENV_DIR" ]; then
|
||||
|
||||
PYTHON_TO_USE=./"$channel"/"$VIRTUAL_ENV_DIR/bin/python"
|
||||
|
||||
fi
|
||||
|
||||
echo "+++++ There are ${#CHANNEL_DIR_ARR[@]} channels detected."
|
||||
|
||||
for channel in "${CHANNEL_DIR_ARR[@]}"
|
||||
do
|
||||
|
||||
echo "+++++ Trying to update: ""$PYTHON_TO_USE" ./"$channel"/$SCRIPT_TO_EXECUTE_PLUS_ARGS
|
||||
# If the running.pid file doesn't exists, create it, start PseudoChannel.py and add the PID to it.
|
||||
"$PYTHON_TO_USE" ./"$channel"/$SCRIPT_TO_EXECUTE_PLUS_ARGS
|
||||
|
||||
sleep 1
|
||||
|
||||
done
|
||||
|
||||
fi
|
||||
|
||||
exit 0
|
||||
4
plex_token-example.py
Normal file
4
plex_token-example.py
Normal file
@@ -0,0 +1,4 @@
|
||||
'''Change the values below, rename this file to "plex_token.py", and move this file just
|
||||
outside of this directory ("../pseudo-channel")'''
|
||||
token = "<your token>"
|
||||
baseurl = 'http://media.home:32400'
|
||||
102
pseudo_config.py
102
pseudo_config.py
@@ -5,7 +5,7 @@
|
||||
|
||||
touch ../plex_token.py
|
||||
|
||||
2) add this line to the newly created file:
|
||||
2) add these lines to the newly created file:
|
||||
|
||||
baseurl = 'the url to your server'
|
||||
token = 'your plex token'
|
||||
@@ -20,7 +20,9 @@
|
||||
|
||||
"Movies" : ["Films"],
|
||||
|
||||
6) For Google Calendar integration add your "gkey" to the "plex_token.py" file
|
||||
6) *Skip this feature for now*
|
||||
|
||||
For Google Calendar integration add your "gkey" to the "plex_token.py" file
|
||||
...(https://docs.simplecalendar.io/find-google-calendar-id/):
|
||||
|
||||
gkey = "the key"
|
||||
@@ -31,6 +33,65 @@
|
||||
|
||||
"""
|
||||
|
||||
'''
|
||||
*
|
||||
* List of plex clients to use (add multiple clients to control multiple TV's)
|
||||
*
|
||||
'''
|
||||
plexClients = ['RasPlex']
|
||||
|
||||
plexLibraries = {
|
||||
"TV Shows" : ["TV Shows"],
|
||||
"Movies" : ["Movies"],
|
||||
"Commercials" : ["Commercials"],
|
||||
}
|
||||
|
||||
useCommercialInjection = True
|
||||
|
||||
"""How many seconds to pad commercials between each other / other media"""
|
||||
commercialPadding = 5
|
||||
|
||||
"""
|
||||
Specify the path to this controller on the network (i.e. 'http://192.168.1.28' - no trailing slash).
|
||||
Also specify the desired port to run the simple http webserver. The daily generated
|
||||
schedule will be served at "http://<your-ip>:<your-port>/" (i.e. "http://192.168.1.28:8000/").
|
||||
|
||||
You can also leave the below controllerServerPath empty if you'd like to run your own webserver.
|
||||
"""
|
||||
controllerServerPath = "http://192.168.1.28"
|
||||
controllerServerPort = "8000"
|
||||
|
||||
"""
|
||||
When the schedule updates every 24 hours, it's possible that it will interrupt any shows / movies that were
|
||||
playing from the previous day. To fix this, the app saves a "cached" schedule from the previous day to
|
||||
override any media that is trying to play while the previous day is finishing.
|
||||
"""
|
||||
useDailyOverlapCache = True
|
||||
|
||||
dailyUpdateTime = "12:00 AM"
|
||||
|
||||
"""---"""
|
||||
useGoogleCalendar = False
|
||||
|
||||
"""When to delete / remake the pseudo-channel.log - right at midnight, (i.e. 'friday') """
|
||||
rotateLog = "friday"
|
||||
|
||||
"""Debug mode will give you more output in your terminal to help problem solve issues."""
|
||||
debug_mode = True
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
##### Do not edit below this line---------------------------------------------------------------
|
||||
|
||||
Below is logic to grab your Plex 'token' & Plex 'baseurl'. If you are following along and have created a 'plex_token.py'
|
||||
file as instructed, you do not need to edit below this line.
|
||||
|
||||
"""
|
||||
|
||||
import os, sys
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
# import ../plex_token.py
|
||||
@@ -43,39 +104,4 @@ except ImportError as e:
|
||||
|
||||
baseurl = plex_token.baseurl
|
||||
token = plex_token.token
|
||||
gkey = plex_token.gkey
|
||||
|
||||
'''
|
||||
*
|
||||
* List of plex clients to use (add multiple clients to control multiple TV's)
|
||||
*
|
||||
'''
|
||||
plexClients = ['RasPlex']
|
||||
|
||||
plexLibraries = {
|
||||
"TV Shows" : ["TV Shows"],
|
||||
"Movies" : ["Movies"],
|
||||
"Music" : ["Music"],
|
||||
"Commercials" : ["Commercials"],
|
||||
}
|
||||
|
||||
useGoogleCalendar = False
|
||||
|
||||
useCommercialInjection = True
|
||||
|
||||
# How many seconds to pad commercials between each other / other media
|
||||
commercialPadding = 5
|
||||
|
||||
"""
|
||||
Specify the path to this controller on the network (i.e. 'http://192.168.1.28' - no trailing slash).
|
||||
Also specify the desired port to run the simple http webserver. The daily generated
|
||||
schedule will be served at "http://<your-ip>:<your-port>/" (i.e. "http://192.168.1.28:8000/").
|
||||
|
||||
You can also leave the below controllerServerPath empty if you'd like to run your own webserver.
|
||||
"""
|
||||
controllerServerPath = "http://192.168.1.28"
|
||||
controllerServerPort = "8000"
|
||||
|
||||
dailyUpdateTime = "12:00 AM"
|
||||
|
||||
debug_mode = True
|
||||
gkey = '' #plex_token.gkey
|
||||
@@ -1,97 +1,279 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- Welcome to PseudoChannel!
|
||||
|
||||
This will be the most difficult part in the process of setting up your PseudoChannel (but it is not hard,
|
||||
just be sure to read this and you will be set).
|
||||
|
||||
There are a few things to keep in mind when setting up this XML.
|
||||
|
||||
1) What exactly is this file for?
|
||||
|
||||
The whole idea behind "PseudoChannel.py" is to create your own channel(s) to mimick real TV,
|
||||
using your own "TV Shows", "Movies" and "Commercials". You are not supposed to intervene too often,
|
||||
rather you are supposed to set it up (here) and just let it go as it advances in series episodes, playing
|
||||
commercials to fill up gaps between scheduled content and playing movies where specified. That being said,
|
||||
this particular file is where you map out what your channel looks like. I have my own schedule
|
||||
below so you can reference it when building your own channel.
|
||||
|
||||
2) How do I make sense of these '<time></time>' blocks / '<weekdays></weekdays' blocks?
|
||||
|
||||
Since the whole concept of the channel is to have repeating blocks of content that changes throughout the
|
||||
week depending on the day / time of day (like a real channel) you specify your "TV Shows" and "Movies" schedule
|
||||
by adding a '<time></time>' block within the part of the week you want it to be scheduled for. For instance,
|
||||
below I have "Looney Tunes" scheduled to play "everyday" starting at "6:00 AM", whereas I have "Garfield & Friends"
|
||||
playing only on "weekday" mornings scheduled for after Looney Tunes starting at "8:00 AM". Also notice that
|
||||
"Garfield & Friends" below is actually written as, "Garfield & Friends". This is especially important
|
||||
to those new to editing XML. In XML, "UTF-8", you are forbidden from using certain characters like the and
|
||||
character. It is important to encode your titles to XML friendly text (this is also important for non-english characters).
|
||||
You can convert titles that have forbidden characters by using this online too: http://coderstoolbox.net/string/#!encoding=xml&action=encode&charset=us_ascii
|
||||
|
||||
3) Setting the available parameters: "title=", "type=", "strict-time=", "time-shift=", "xtra="
|
||||
|
||||
There are two required parameters: "title" and "type". The "title" value should be either the title of your series
|
||||
(i.e. "Friends") or if you are scheduling a movie it should only be set to, "random". The "type" parameter should be set
|
||||
to either "series" or "movie". The attribute "strict-time" can either be "true" or "false" and refers to
|
||||
whether or not the particular "<time>" block will be scheduled for the exact time you specify or if it will
|
||||
shift around to fill up gaps. This is useful as sometimes episodes are as short as 5 minutes (cartoons) while
|
||||
other episodes that are normally ~25 minutes are an hour or so long. Setting "strict-time" to "false" will
|
||||
tell the app to shift that time block closer to the previous episode. The corresponding, "time-shift" attribute
|
||||
tells the app how to shift the item. Its value can be "1" or more and will help the scheduler determine when to schedule the
|
||||
shifting time according to that value. So for instance, if you'd like no gaps between your content, then you want
|
||||
to set "strict-time='false'" and "time-shift='1'". However if you want your content to shift but would rather
|
||||
it 'hook' on to a pretty time, like "2:45 PM" versus "2:41 PM" then you would set "time-shift" to a value like "5".
|
||||
This will shift content around and schedule it within 5 minutes of the previous item but hook it on to the
|
||||
nearest multiple of "5". You could use "15" or "30" too for even prettier times. Experiment.
|
||||
|
||||
4) Movies. How do I schedule "Billy Madison" to play on Saturday afternoon?
|
||||
|
||||
Well, since the app is supposed to work like a real TV Channel, you aren't supposed to have that kind of
|
||||
control. If you want to watch "Billy Madison" then why not just turn on your Plex TV app and play it? Instead
|
||||
here you want to always use "random" tor the "title=" value of movie content. But let's say you have a ton of Adam Sandler
|
||||
movies and want to schedule a "random" Adam Sandler movie on Saturday afternoon? That makes more sense, that way
|
||||
you aren't playing the same movie every Saturday afternoon! For movies specifically, you have a new
|
||||
attribute called "xtra". There you can add various parameters to narrow in on the random movie type you
|
||||
want scheduled every Saturday afternoon. So if for some reason you are set on playing an Adam Sandler comedy
|
||||
every Saturday, then you might have a <time> block that looks like this:
|
||||
|
||||
<time title="random" type="movie" strict-time="true" xtra='actor:adam sandler genre:comedy contentRating:PG'>12:45 PM</time>
|
||||
|
||||
The available "xtra" paramters are as follows (http://python-plexapi.readthedocs.io/en/latest/_modules/plexapi/library.html#LibrarySection.search):
|
||||
|
||||
* unwatched: Display or hide unwatched content (True, False). [all]
|
||||
* duplicate: Display or hide duplicate items (True, False). [movie]
|
||||
* actor: List of actors to search ([actor_or_id, ...]). [movie]
|
||||
* collection: List of collections to search within ([collection_or_id, ...]). [all]
|
||||
* contentRating: List of content ratings to search within ([rating_or_key, ...]). [movie]
|
||||
* country: List of countries to search within ([country_or_key, ...]). [movie,music]
|
||||
* decade: List of decades to search within ([yyy0, ...]). [movie]
|
||||
* director: List of directors to search ([director_or_id, ...]). [movie]
|
||||
* genre: List Genres to search within ([genere_or_id, ...]). [all]
|
||||
* resolution: List of video resolutions to search within ([resolution_or_key, ...]). [movie]
|
||||
* studio: List of studios to search within ([studio_or_key, ...]). [music]
|
||||
* year: List of years to search within ([yyyy, ...]). [all]
|
||||
|
||||
Currently the "xtra" attribute is only available to be used with movies.
|
||||
|
||||
5) Commercials?
|
||||
|
||||
If you are planning on using "time-shift" with a value greater than "1", then you will have empty gaps
|
||||
in between your scheduled content. A neat feature is to fill those gaps with commercials, music videos,
|
||||
or whatever you can come up with. All you have to do is set the commercial flag in the "pseudo_config.py"
|
||||
file to tell the app to use "commercial injection" and make sure you have a "Commercials" library in your
|
||||
plex media library. In that library, fill it with as many commercials or short videos as you can. The more
|
||||
the better! I have close to a thousand commercials in mine - this helps the app fill up the gaps with a
|
||||
wide variety of video content of varied durations. (hint: use a tool like 'youtube-dl' to download full
|
||||
playlists from yourtube. You can fill up your "Commercials" library quick). Once you have your commercials library
|
||||
setup, make sure to run, "python PseudoChannel.py -u" once more to update your local db with your new commercials
|
||||
library. Commercials will now be "injected" to fill up gaps upon the next days schedule (or you can manually
|
||||
generate the schedule using the "-g" flag).
|
||||
|
||||
Ok, that should be it. I've made it sound much more complicated than it actually is. Just make sure that you aren't
|
||||
accidentally overlapping times, aren't accidentally trying to use forbidden XML characters, etc. Once you have
|
||||
everything set, it should be hands off form there on out. Just go back to the cli and run "python PseudoChannel.py -xml"
|
||||
to tell the app that you have updated the XML.
|
||||
|
||||
Oh and lastly, make sure that your "series" title's are written exactly as they are in your Plex Library. So if you
|
||||
have "The Office (us)" in your Plex library, you need to have it written exactly like that here (not case sensitive) or it won't work. In
|
||||
my previous "garfield" example you might be tempted to write it as "Garfield and Friends" instead of the hassle of
|
||||
using the XML ascii character "&". Well you cannot do that. I usually like to have my Plex Server web page
|
||||
open in a tab while making my XML. That way for each "series" title I can double check the library to make sure I
|
||||
am using the series title exactly as Plex is.
|
||||
|
||||
Ok, that is it. If you have questions feel free to e-mail me at justin@pseudochannel.tv or open an 'issue' on the
|
||||
github repository. Have fun!
|
||||
|
||||
Cheers!
|
||||
|
||||
-->
|
||||
<schedule>
|
||||
<everyday>
|
||||
<time title="Looney Tunes" type="series" strict-time="true" time-shift="1" overlap-max="">6:00 AM</time>
|
||||
<time title="Looney Tunes" type="series" strict-time="false" time-shift="1" overlap-max="">6:30 AM</time>
|
||||
<time title="Looney Tunes" type="series" strict-time="false" time-shift="1" overlap-max="">7:00 AM</time>
|
||||
<time title="Looney Tunes" type="series" strict-time="false" time-shift="1" overlap-max="">7:30 AM</time>
|
||||
<!-- The first item is set to: strict-time=true - this isn't necessary but helps organize the schedule -->
|
||||
<!--
|
||||
The following is a good example of content that is extremely short. Looney Tunes cartoons are around
|
||||
5 minutes in length per episode. Althrough sometimes they are as much as 25 minutes long.
|
||||
Since strict-time is set to false, the app will adjust accordingly. I am however setting a *rough
|
||||
intended time of 6:00 AM and incrementing by 10 minutes. The app will figure out the real
|
||||
start times but this will help me conceptualize my schedule as I keep adding entries.
|
||||
-->
|
||||
<time title="Looney Tunes" type="series" strict-time="false" time-shift="1" >6:00 AM</time>
|
||||
<time title="Looney Tunes" type="series" strict-time="false" time-shift="1" >6:10 AM</time>
|
||||
<time title="Looney Tunes" type="series" strict-time="false" time-shift="1" >6:20 AM</time>
|
||||
<time title="Looney Tunes" type="series" strict-time="false" time-shift="1" >6:40 AM</time>
|
||||
<time title="Looney Tunes" type="series" strict-time="false" time-shift="1" >6:50 AM</time>
|
||||
</everyday>
|
||||
<mondays></mondays>
|
||||
<tuesdays></tuesdays>
|
||||
<wednesdays></wednesdays>
|
||||
<thursdays></thursdays>
|
||||
<fridays>
|
||||
<!--
|
||||
Here is an example of a day specific block. TGIF 90's style. Again, I am keeping strict-time set to false
|
||||
to maximize the content in my schedule and to avoid overlap. If you wanted to make sure your TGIF block
|
||||
started at 8 PM on the dot, you could make room for it...
|
||||
-->
|
||||
<time title="Sabrina The Teenage Witch" type="series" strict-time="false" time-shift="1" >8:00 PM</time>
|
||||
<time title="Boy Meets World" type="series" strict-time="false" time-shift="1" >8:20 PM</time>
|
||||
<time title="Family Matters" type="series" strict-time="false" time-shift="1" >8:40 PM</time>
|
||||
<time title="Family Matters" type="series" strict-time="false" time-shift="1" >9:00 PM</time>
|
||||
</fridays>
|
||||
<saturdays></saturdays>
|
||||
<sundays></sundays>
|
||||
<saturdays>
|
||||
<!--
|
||||
Here is an example of using the xtra attr to specify a saturday late-afternoon 80's movie block.
|
||||
-->
|
||||
<time title="random" type="movie" strict-time="false" time-shift="5" xtra="decade:1980">5:00 PM</time>
|
||||
|
||||
<time title="Elementary" type="show" strict-time="false" time-shift="5" >7:00 PM</time>
|
||||
|
||||
</saturdays>
|
||||
<sundays>
|
||||
<!--
|
||||
Here is an example of using the xtra attr to specify a sunday late-afternoon romantic comedy movie block.
|
||||
-->
|
||||
<time title="random" type="movie" strict-time="false" time-shift="5" xtra="genre:comedy,romance">5:00 PM</time>
|
||||
|
||||
<time title="Sherlock" type="show" strict-time="false" time-shift="5" >7:00 PM</time>
|
||||
|
||||
</sundays>
|
||||
<weekends>
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" overlap-max="">8:00 AM</time>
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" overlap-max="">8:30 AM</time>
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" overlap-max="">9:00 AM</time>
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" overlap-max="">9:30 AM</time>
|
||||
<!--
|
||||
I am incrementing the start times by about 30 minutes. Since times will shift with a
|
||||
5 minute "time-shift", the app will adjust the schedule according to the episode
|
||||
duration / time-shift value.
|
||||
-->
|
||||
<time title="The Smurfs" type="series" strict-time="false" time-shift="5" >7:00 AM</time>
|
||||
|
||||
<time title="Batman" type="series" strict-time="false" time-shift="5" overlap-max="">10:00 AM</time>
|
||||
<time title="Batman" type="series" strict-time="false" time-shift="5" overlap-max="">10:30 AM</time>
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" >7:30 AM</time>
|
||||
|
||||
<time title="random" type="movie" strict-time="false" time-shift="5" overlap-max="">11:00 AM</time>
|
||||
<time title="Batman" type="series" strict-time="false" time-shift="5" >8:00 AM</time>
|
||||
|
||||
<time title="The Adventures of Pete & Pete" type="series" strict-time="false" time-shift="5" overlap-max="">12:30 PM</time>
|
||||
<time title="The Adventures of Pete & Pete" type="series" strict-time="false" time-shift="5" overlap-max="">1:00 PM</time>
|
||||
<time title="Gilligan's Island" type="series" strict-time="false" time-shift="5" overlap-max="">1:30 PM</time>
|
||||
<time title="Gilligan's Island" type="series" strict-time="false" time-shift="5" overlap-max="">1:40 PM</time>
|
||||
<time title="The Jetsons" type="series" strict-time="false" time-shift="5" overlap-max="">2:00 PM</time>
|
||||
<time title="random" type="movie" strict-time="false" time-shift="5" xtra="genre:comedy">8:30 AM</time>
|
||||
|
||||
<time title="random" type="movie" strict-time="true" time-shift="5" overlap-max="">2:30 PM</time>
|
||||
<time title="The Adventures of Pete & Pete" type="series" strict-time="false" time-shift="5" >10:00 AM</time>
|
||||
|
||||
<time title="The Wonder Years" type="series" strict-time="false" time-shift="5" overlap-max="">4:30 PM</time>
|
||||
<time title="The Wonder Years" type="series" strict-time="false" time-shift="5" overlap-max="">5:00 PM</time>
|
||||
<time title="Gilligan's Island" type="series" strict-time="false" time-shift="5" >10:30 AM</time>
|
||||
|
||||
<time title="The Simpsons" type="series" strict-time="true" time-shift="5" overlap-max="">5:30 PM</time>
|
||||
<time title="The Simpsons" type="series" strict-time="false" time-shift="5" overlap-max="">6:00 PM</time>
|
||||
<time title="The Jetsons" type="series" strict-time="false" time-shift="5" >11:00 AM</time>
|
||||
|
||||
<time title="Sherlock" type="series" strict-time="false" time-shift="5" overlap-max="">6:30 PM</time>
|
||||
<time title="Blossom" type="series" strict-time="false" time-shift="5" >11:30 AM</time>
|
||||
|
||||
<time title="Arrested Development" type="series" strict-time="false" time-shift="5" overlap-max="">7:00 PM</time>
|
||||
<time title="Arrested Development" type="series" strict-time="false" time-shift="5" overlap-max="">7:30 PM</time>
|
||||
<time title="Clarissa Explains It All" type="series" strict-time="false" time-shift="5" >12:00 AM</time>
|
||||
|
||||
<time title="random" type="movie" strict-time="false" time-shift="5" overlap-max="">8:00 PM</time>
|
||||
<time title="random" type="movie" strict-time="false" time-shift="5" xtra="genre:action decade:1990,1980,1970,1960">12:30 PM</time>
|
||||
|
||||
<time title="Band of Brothers" type="show" strict-time="false" time-shift="5" overlap-max="">10:00 PM</time>
|
||||
<time title="Beverly Hills, 90210" type="show" strict-time="false" time-shift="5" overlap-max="">10:30 PM</time>
|
||||
<time title="The Wonder Years" type="series" strict-time="false" time-shift="5" >2:00 PM</time>
|
||||
|
||||
<time title="The Simpsons" type="series" strict-time="false" time-shift="5" >2:30 PM</time>
|
||||
|
||||
<time title="Monk" type="series" strict-time="false" time-shift="5" >3:00 PM</time>
|
||||
|
||||
<time title="Married... with Children" type="series" strict-time="false" time-shift="5" >3:30 PM</time>
|
||||
|
||||
<time title="The Fresh Prince of Bel-Air" type="series" strict-time="false" time-shift="5" >4:00 PM</time>
|
||||
|
||||
<time title="Arrested Development" type="series" strict-time="false" time-shift="5" >4:30 PM</time>
|
||||
|
||||
<!--
|
||||
Here I am leaving some room for saturdays/sundays specific content. I will keep strict-time false for all my
|
||||
times so I can squeeze in as much content into my schedule without cutting anything off. If you choose to
|
||||
use strict-time, then it's important to make sure you have left a good amount of room so scheduled content
|
||||
doesn't get cutoff.
|
||||
-->
|
||||
|
||||
<time title="Beverly Hills, 90210" type="show" strict-time="false" time-shift="5" >8:00 PM</time>
|
||||
|
||||
<time title="Daria" type="show" strict-time="false" time-shift="5" >9:00 PM</time>
|
||||
|
||||
<time title="The Scooby-Doo Show" type="show" strict-time="false" time-shift="5" >9:30 PM</time>
|
||||
|
||||
<time title="The Flintstones" type="show" strict-time="false" time-shift="5" >10:00 PM</time>
|
||||
|
||||
<time title="Happy Days" type="show" strict-time="false" time-shift="5" >10:30 PM</time>
|
||||
|
||||
<time title="The Flintstones" type="show" strict-time="false" time-shift="5" overlap-max="">11:00 PM</time>
|
||||
<time title="The Flintstones" type="show" strict-time="false" time-shift="5" overlap-max="">11:30 PM</time>
|
||||
</weekends>
|
||||
<weekdays>
|
||||
<default title="Seinfeld" type="series" ></default>
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" >8:00 AM</time>
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" >8:30 AM</time>
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" >9:00 AM</time>
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" >9:30 AM</time>
|
||||
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" overlap-max="">8:00 AM</time>
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" overlap-max="">8:30 AM</time>
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" overlap-max="">9:00 AM</time>
|
||||
<time title="Garfield & Friends" type="series" strict-time="false" time-shift="5" overlap-max="">9:30 AM</time>
|
||||
<time title="talespin" type="series" strict-time="false" time-shift="5" >10:00 AM</time>
|
||||
<time title="talespin" type="series" strict-time="false" time-shift="5" >10:30 AM</time>
|
||||
|
||||
<time title="talespin" type="series" strict-time="false" time-shift="5" overlap-max="">10:00 AM</time>
|
||||
<time title="talespin" type="series" strict-time="false" time-shift="5" overlap-max="">10:30 AM</time>
|
||||
<time title="macgyver" type="series" strict-time="false" time-shift="5" >11:00 AM</time>
|
||||
|
||||
<time title="macgyver" type="series" strict-time="false" time-shift="5" overlap-max="">11:00 AM</time>
|
||||
<time title="boy meets world" type="series" strict-time="false" time-shift="5" >12:00 PM</time>
|
||||
|
||||
<time title="boy meets world" type="series" strict-time="false" time-shift="5" overlap-max="">12:00 PM</time>
|
||||
<time title="full house" type="series" strict-time="false" time-shift="5" >12:30 PM</time>
|
||||
<time title="full house" type="series" strict-time="false" time-shift="5" >1:00 PM</time>
|
||||
|
||||
<time title="full house" type="series" strict-time="false" time-shift="5" overlap-max="">12:30 PM</time>
|
||||
<time title="full house" type="series" strict-time="false" time-shift="5" overlap-max="">1:00 PM</time>
|
||||
<time title="The Wonder Years" type="series" strict-time="false" time-shift="5" >1:30 PM</time>
|
||||
|
||||
<time title="the it crowd" type="series" strict-time="false" time-shift="5" overlap-max="">1:30 PM</time>
|
||||
<time title="the it crowd" type="series" strict-time="false" time-shift="5" overlap-max="">2:00 PM</time>
|
||||
<time title="The Simpsons" type="series" strict-time="false" time-shift="5" >2:00 PM</time>
|
||||
|
||||
<time title="the office (us)" type="series" strict-time="false" time-shift="5" overlap-max="">2:30 PM</time>
|
||||
<time title="the office (us)" type="series" strict-time="false" time-shift="5" overlap-max="">3:00 PM</time>
|
||||
<time title="Monk" type="series" strict-time="false" time-shift="5" >2:30 PM</time>
|
||||
|
||||
<time title="friends" type="series" strict-time="false" time-shift="5" overlap-max="">3:30 PM</time>
|
||||
<time title="friends" type="series" strict-time="false" time-shift="5" overlap-max="">4:00 PM</time>
|
||||
<time title="Married... with Children" type="series" strict-time="false" time-shift="5" >3:00 PM</time>
|
||||
|
||||
<time title="seinfeld" type="series" strict-time="false" time-shift="5" overlap-max="">4:30 PM</time>
|
||||
<time title="seinfeld" type="series" strict-time="false" time-shift="5" overlap-max="">5:00 PM</time>
|
||||
<time title="The Fresh Prince of Bel-Air" type="series" strict-time="false" time-shift="5" >3:30 PM</time>
|
||||
|
||||
<time title="Futurama" type="series" strict-time="false" time-shift="5" overlap-max="">5:30 PM</time>
|
||||
<time title="the office (us)" type="series" strict-time="false" time-shift="5" >4:00 PM</time>
|
||||
<time title="the office (us)" type="series" strict-time="false" time-shift="5" >4:30 PM</time>
|
||||
|
||||
<time title="Saved by the Bell" type="series" strict-time="false" time-shift="5" overlap-max="">6:00 PM</time>
|
||||
<time title="Saved by the Bell" type="series" strict-time="false" time-shift="5" overlap-max="">6:30 PM</time>
|
||||
<time title="Roseanne" type="series" strict-time="false" time-shift="5" >5:00 PM</time>
|
||||
<time title="Roseanne" type="series" strict-time="false" time-shift="5" >5:30 PM</time>
|
||||
|
||||
<time title="new girl" type="series" strict-time="false" time-shift="5" overlap-max="">7:00 PM</time>
|
||||
<time title="new girl" type="series" strict-time="false" time-shift="5" overlap-max="">7:30 PM</time>
|
||||
<time title="seinfeld" type="series" strict-time="false" time-shift="5" >6:00 PM</time>
|
||||
|
||||
<time title="Parker Lewis Can't Lose" type="series" strict-time="false" time-shift="5" >6:30 PM</time>
|
||||
|
||||
<time title="Futurama" type="series" strict-time="false" time-shift="5" >7:00 PM</time>
|
||||
|
||||
<time title="Saved by the Bell" type="series" strict-time="false" time-shift="5" >7:30 PM</time>
|
||||
<time title="Saved by the Bell" type="series" strict-time="false" time-shift="5" >8:00 PM</time>
|
||||
|
||||
<time title="new girl" type="series" strict-time="false" time-shift="5" >8:30 PM</time>
|
||||
|
||||
<time title="random" type="movie" strict-time="false" time-shift="5" xtra='actor:mike myers genre:comedy contentRating:PG-13'>9:00 PM</time>
|
||||
|
||||
|
||||
<time title="the trip" type="series" strict-time="false" time-shift="5" overlap-max="">8:30 PM</time>
|
||||
<time title="the trip" type="series" strict-time="false" time-shift="5" overlap-max="">9:00 PM</time>
|
||||
<time title="Married... with Children" type="series" strict-time="false" time-shift="5" >9:30 PM</time>
|
||||
<time title="Married... with Children" type="series" strict-time="false" time-shift="5" >9:40 PM</time>
|
||||
<time title="Blossom" type="series" strict-time="false" time-shift="5" >10:00 PM</time>
|
||||
|
||||
<time title="Frasier" type="series" strict-time="false" time-shift="1" >10:20 PM</time>
|
||||
|
||||
<time title="Mad About You" type="series" strict-time="false" time-shift="5" >10:30 PM</time>
|
||||
<time title="Mad About You" type="series" strict-time="false" time-shift="5" >10:40 PM</time>
|
||||
|
||||
<time title="M*A*S*H" type="series" strict-time="false" time-shift="5" >10:50 PM</time>
|
||||
|
||||
<time title="Beverly Hills, 90210" type="series" strict-time="false" time-shift="5" >11:00 PM</time>
|
||||
|
||||
<time title="Cheers" type="series" strict-time="false" time-shift="5" >11:20 PM</time>
|
||||
|
||||
<time title="Three's Company" type="series" strict-time="false" time-shift="5" >11:30 PM</time>
|
||||
|
||||
<time title="The Brady Bunch" type="series" strict-time="false" time-shift="5" >11:40 PM</time>
|
||||
</weekdays>
|
||||
</schedule>
|
||||
|
||||
@@ -8,8 +8,10 @@ idna==2.5
|
||||
kombu==4.1.0
|
||||
oauth2client==4.1.2
|
||||
PlexAPI==2.0.2
|
||||
py==1.4.34
|
||||
pyasn1==0.3.2
|
||||
pyasn1-modules==0.0.11
|
||||
pytest==3.2.1
|
||||
pytz==2017.2
|
||||
requests==2.18.3
|
||||
rsa==3.4.2
|
||||
|
||||
43
src/Helpers.py
Normal file
43
src/Helpers.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import os
|
||||
|
||||
import logging
|
||||
|
||||
class Helpers():
|
||||
"""Class for consolidating helper methods"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
pass
|
||||
|
||||
def save_file(self, data, filename, path, overwrite=True):
|
||||
|
||||
fileName = filename
|
||||
|
||||
writepath = path
|
||||
|
||||
if not os.path.exists(writepath):
|
||||
|
||||
os.makedirs(writepath)
|
||||
|
||||
if os.path.exists(writepath+fileName) and overwrite:
|
||||
|
||||
os.remove(writepath+fileName)
|
||||
|
||||
mode = 'a' if os.path.exists(writepath) else 'w'
|
||||
|
||||
with open(writepath+fileName, mode) as f:
|
||||
|
||||
f.write(data)
|
||||
|
||||
def get_file(self, filename, path):
|
||||
|
||||
if not os.path.exists(writepath):
|
||||
|
||||
raise IOError("{}, doesn't exist").format(writepath)
|
||||
|
||||
if not os.path.exists(writepath+fileName):
|
||||
|
||||
raise IOError("{}, doesn't exist").format(fileName)
|
||||
|
||||
return None
|
||||
|
||||
@@ -16,147 +16,86 @@ class PseudoChannelCommercial():
|
||||
def __init__(self, commercials, commercialPadding):
|
||||
|
||||
self.commercials = commercials
|
||||
|
||||
self.COMMERCIAL_PADDING_IN_SECONDS = commercialPadding
|
||||
|
||||
def get_commercials_to_inject(self):
|
||||
|
||||
self.go()
|
||||
|
||||
return None
|
||||
|
||||
def get_random_commercial(self):
|
||||
|
||||
random_commercial = random.choice(self.commercials)
|
||||
|
||||
random_commercial_dur_seconds = (int(random_commercial[4])/1000)%60
|
||||
|
||||
while random_commercial_dur_seconds < self.MIN_DURATION_FOR_COMMERCIAL:
|
||||
|
||||
random_commercial = random.choice(self.commercials)
|
||||
|
||||
random_commercial_dur_seconds = (int(random_commercial[4])/1000)%60
|
||||
|
||||
return random_commercial
|
||||
|
||||
def go(self):
|
||||
|
||||
shuffled_commercial_list = copy.deepcopy(self.commercials)
|
||||
|
||||
random.shuffle(self.commercials, random.random)
|
||||
|
||||
#print shuffled_commercial_list
|
||||
|
||||
prev_item = None
|
||||
|
||||
for entry in self.daily_schedule:
|
||||
|
||||
"""First Episode"""
|
||||
if prev_item == None:
|
||||
|
||||
prev_item = entry
|
||||
|
||||
else:
|
||||
|
||||
prev_item_end_time = datetime.datetime.strptime(prev_item[9], '%Y-%m-%d %H:%M:%S.%f')
|
||||
|
||||
curr_item_start_time = datetime.datetime.strptime(entry[8], '%I:%M:%S %p')
|
||||
|
||||
time_diff = (curr_item_start_time - prev_item_end_time)
|
||||
|
||||
days, hours, minutes = time_diff.days, time_diff.seconds // 3600, time_diff.seconds // 60 % 60
|
||||
|
||||
count = 0
|
||||
|
||||
commercial_list = []
|
||||
|
||||
commercial_dur_sum = 0
|
||||
|
||||
while int(time_diff.total_seconds()) >= commercial_dur_sum and count < len(self.commercials):
|
||||
|
||||
|
||||
random_commercial = self.get_random_commercial()
|
||||
|
||||
commercial_list.append(random_commercial)
|
||||
|
||||
commercial_dur_sum += int(random_commercial[4])
|
||||
|
||||
print commercial_list
|
||||
|
||||
prev_item = entry
|
||||
|
||||
def timedelta_milliseconds(self, td):
|
||||
|
||||
return td.days*86400000 + td.seconds*1000 + td.microseconds/1000
|
||||
|
||||
def pad_the_commercial_dur(self, commercial):
|
||||
|
||||
commercial_as_list = list(commercial)
|
||||
|
||||
commercial_as_list[4] = int(commercial_as_list[4]) + (self.COMMERCIAL_PADDING_IN_SECONDS * 1000)
|
||||
|
||||
commercial = tuple(commercial_as_list)
|
||||
|
||||
return commercial
|
||||
|
||||
def get_commercials_to_place_between_media(self, last_ep, now_ep):
|
||||
|
||||
#print last_ep.end_time, now_ep.start_time
|
||||
|
||||
prev_item_end_time = datetime.strptime(last_ep.end_time.strftime('%Y-%m-%d %H:%M:%S.%f'), '%Y-%m-%d %H:%M:%S.%f')
|
||||
|
||||
curr_item_start_time = datetime.strptime(now_ep.start_time, '%I:%M:%S %p')
|
||||
|
||||
time_diff = (curr_item_start_time - prev_item_end_time)
|
||||
|
||||
count = 0
|
||||
|
||||
commercial_list = []
|
||||
|
||||
commercial_dur_sum = 0
|
||||
|
||||
time_diff_milli = self.timedelta_milliseconds(time_diff)
|
||||
|
||||
last_commercial = None
|
||||
|
||||
time_watch = prev_item_end_time
|
||||
|
||||
new_commercial_start_time = prev_item_end_time
|
||||
|
||||
#print "here", time_diff.seconds
|
||||
|
||||
while curr_item_start_time > new_commercial_start_time and (count) < len(self.commercials)*100:
|
||||
|
||||
random_commercial_without_pad = self.get_random_commercial()
|
||||
|
||||
"""
|
||||
Padding the duration of commercials as per user specified padding.
|
||||
"""
|
||||
random_commercial = self.pad_the_commercial_dur(random_commercial_without_pad)
|
||||
|
||||
#new_commercial_seconds = (int(random_commercial[4])/1000)%60
|
||||
|
||||
new_commercial_milli = int(random_commercial[4])
|
||||
|
||||
if last_commercial != None:
|
||||
|
||||
#print last_commercial[3]
|
||||
|
||||
new_commercial_start_time = last_commercial.end_time
|
||||
|
||||
new_commercial_end_time = new_commercial_start_time + \
|
||||
timedelta(milliseconds=int(new_commercial_milli))
|
||||
|
||||
else:
|
||||
|
||||
new_commercial_start_time = prev_item_end_time
|
||||
|
||||
new_commercial_end_time = new_commercial_start_time + \
|
||||
timedelta(milliseconds=int(new_commercial_milli))
|
||||
|
||||
commercial_dur_sum += new_commercial_milli
|
||||
|
||||
formatted_time_for_new_commercial = new_commercial_start_time.strftime('%I:%M:%S %p')
|
||||
|
||||
new_commercial = Commercial(
|
||||
"Commercials",
|
||||
random_commercial[3],
|
||||
@@ -169,15 +108,8 @@ class PseudoChannelCommercial():
|
||||
"0", # overlap_max
|
||||
"", # plex_media_id
|
||||
)
|
||||
|
||||
last_commercial = new_commercial
|
||||
|
||||
if new_commercial_end_time > curr_item_start_time:
|
||||
|
||||
break
|
||||
|
||||
commercial_list.append(new_commercial)
|
||||
|
||||
#print "here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||
|
||||
return commercial_list
|
||||
@@ -9,83 +9,61 @@ class PseudoChannelDatabase():
|
||||
def __init__(self, db):
|
||||
|
||||
self.db = db
|
||||
|
||||
self.conn = sqlite3.connect(self.db, check_same_thread=False)
|
||||
|
||||
self.cursor = self.conn.cursor()
|
||||
|
||||
"""Database functions.
|
||||
|
||||
Utilities, etc.
|
||||
"""
|
||||
|
||||
def create_tables(self):
|
||||
|
||||
self.cursor.execute('CREATE TABLE IF NOT EXISTS '
|
||||
'movies(id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||
'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, '
|
||||
'lastPlayedDate TEXT, plexMediaID TEXT)')
|
||||
|
||||
self.cursor.execute('CREATE TABLE IF NOT EXISTS '
|
||||
'videos(id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||
'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, plexMediaID TEXT)')
|
||||
|
||||
self.cursor.execute('CREATE TABLE IF NOT EXISTS '
|
||||
'music(id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||
'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, plexMediaID TEXT)')
|
||||
|
||||
self.cursor.execute('CREATE TABLE IF NOT EXISTS '
|
||||
'shows(id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||
'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, '
|
||||
'lastEpisodeTitle TEXT, fullImageURL TEXT, plexMediaID TEXT)')
|
||||
|
||||
self.cursor.execute('CREATE TABLE IF NOT EXISTS '
|
||||
'episodes(id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||
'unix INTEGER, mediaID INTEGER, title TEXT, duration INTEGER, '
|
||||
'episodeNumber INTEGER, seasonNumber INTEGER, showTitle TEXT, plexMediaID TEXT)')
|
||||
|
||||
self.cursor.execute('CREATE TABLE IF NOT EXISTS '
|
||||
'commercials(id INTEGER PRIMARY KEY AUTOINCREMENT, unix INTEGER, '
|
||||
'mediaID INTEGER, title TEXT, duration INTEGER, plexMediaID TEXT)')
|
||||
|
||||
self.cursor.execute('CREATE TABLE IF NOT EXISTS '
|
||||
'schedule(id INTEGER PRIMARY KEY AUTOINCREMENT, unix INTEGER, '
|
||||
'mediaID INTEGER, title TEXT, duration INTEGER, startTime INTEGER, '
|
||||
'endTime INTEGER, dayOfWeek TEXT, startTimeUnix INTEGER, section TEXT, '
|
||||
'strictTime TEXT, timeShift TEXT, overlapMax TEXT)')
|
||||
|
||||
'strictTime TEXT, timeShift TEXT, overlapMax TEXT, xtra TEXT)')
|
||||
self.cursor.execute('CREATE TABLE IF NOT EXISTS '
|
||||
'daily_schedule(id INTEGER PRIMARY KEY AUTOINCREMENT, unix INTEGER, '
|
||||
'mediaID INTEGER, title TEXT, episodeNumber INTEGER, seasonNumber INTEGER, '
|
||||
'showTitle TEXT, duration INTEGER, startTime INTEGER, endTime INTEGER, '
|
||||
'dayOfWeek TEXT, sectionType TEXT, plexMediaID TEXT)')
|
||||
|
||||
self.cursor.execute('CREATE TABLE IF NOT EXISTS '
|
||||
'app_settings(id INTEGER PRIMARY KEY AUTOINCREMENT, version TEXT)')
|
||||
|
||||
#index
|
||||
self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_episode_title ON episodes (title);')
|
||||
|
||||
self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_movie_title ON movies (title);')
|
||||
|
||||
self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_shows_title ON shows (title);')
|
||||
|
||||
self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_video_title ON videos (title);')
|
||||
|
||||
self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_music_title ON music (title);')
|
||||
|
||||
self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_commercial_title ON commercials (title);')
|
||||
|
||||
self.cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_settings_version ON app_settings (version);')
|
||||
|
||||
"""Setting Basic Settings
|
||||
|
||||
"""
|
||||
try:
|
||||
self.cursor.execute("INSERT OR REPLACE INTO app_settings "
|
||||
"(version) VALUES (?)",
|
||||
("0.1",))
|
||||
|
||||
self.conn.commit()
|
||||
# Catch the exception
|
||||
except Exception as e:
|
||||
@@ -101,38 +79,49 @@ class PseudoChannelDatabase():
|
||||
|
||||
pass
|
||||
|
||||
def drop_daily_schedule(self):
|
||||
def drop_daily_schedule_table(self):
|
||||
|
||||
pass
|
||||
sql = "DROP TABLE IF EXISTS daily_schedule"
|
||||
self.cursor.execute(sql)
|
||||
self.conn.commit()
|
||||
|
||||
def create_daily_schedule_table(self):
|
||||
|
||||
self.cursor.execute('CREATE TABLE IF NOT EXISTS '
|
||||
'daily_schedule(id INTEGER PRIMARY KEY AUTOINCREMENT, unix INTEGER, '
|
||||
'mediaID INTEGER, title TEXT, episodeNumber INTEGER, seasonNumber INTEGER, '
|
||||
'showTitle TEXT, duration INTEGER, startTime INTEGER, endTime INTEGER, '
|
||||
'dayOfWeek TEXT, sectionType TEXT, plexMediaID TEXT)')
|
||||
self.conn.commit()
|
||||
|
||||
def remove_all_scheduled_items(self):
|
||||
|
||||
sql = "DELETE FROM schedule WHERE id > -1"
|
||||
|
||||
self.cursor.execute(sql)
|
||||
|
||||
self.conn.commit()
|
||||
|
||||
def remove_all_daily_scheduled_items(self):
|
||||
|
||||
sql = "DELETE FROM daily_schedule"
|
||||
|
||||
self.cursor.execute(sql)
|
||||
self.conn.commit()
|
||||
|
||||
def clear_shows_table(self):
|
||||
|
||||
sql = "DELETE FROM shows"
|
||||
self.cursor.execute(sql)
|
||||
self.conn.commit()
|
||||
|
||||
"""Database functions.
|
||||
|
||||
Setters, etc.
|
||||
"""
|
||||
|
||||
def add_movies_to_db(self, mediaID, title, duration, plexMediaID):
|
||||
|
||||
unix = int(time.time())
|
||||
try:
|
||||
self.cursor.execute("REPLACE INTO movies "
|
||||
"(unix, mediaID, title, duration, plexMediaID) VALUES (?, ?, ?, ?, ?)",
|
||||
(unix, mediaID, title, duration, plexMediaID))
|
||||
|
||||
self.conn.commit()
|
||||
# Catch the exception
|
||||
except Exception as e:
|
||||
@@ -141,6 +130,7 @@ class PseudoChannelDatabase():
|
||||
raise e
|
||||
|
||||
def add_videos_to_db(self, mediaID, title, duration, plexMediaID):
|
||||
|
||||
unix = int(time.time())
|
||||
try:
|
||||
self.cursor.execute("REPLACE INTO videos "
|
||||
@@ -155,6 +145,7 @@ class PseudoChannelDatabase():
|
||||
raise e
|
||||
|
||||
def add_shows_to_db(self, mediaID, title, duration, lastEpisodeTitle, fullImageURL, plexMediaID):
|
||||
|
||||
unix = int(time.time())
|
||||
try:
|
||||
self.cursor.execute("INSERT OR IGNORE INTO shows "
|
||||
@@ -168,6 +159,7 @@ class PseudoChannelDatabase():
|
||||
raise e
|
||||
|
||||
def add_episodes_to_db(self, mediaID, title, duration, episodeNumber, seasonNumber, showTitle, plexMediaID):
|
||||
|
||||
unix = int(time.time())
|
||||
try:
|
||||
self.cursor.execute("REPLACE INTO episodes "
|
||||
@@ -181,6 +173,7 @@ class PseudoChannelDatabase():
|
||||
raise e
|
||||
|
||||
def add_commercials_to_db(self, mediaID, title, duration, plexMediaID):
|
||||
|
||||
unix = int(time.time())
|
||||
try:
|
||||
self.cursor.execute("REPLACE INTO commercials "
|
||||
@@ -194,13 +187,25 @@ class PseudoChannelDatabase():
|
||||
self.conn.rollback()
|
||||
raise e
|
||||
|
||||
def add_schedule_to_db(self, mediaID, title, duration, startTime, endTime, dayOfWeek, startTimeUnix, section, strictTime, timeShift, overlapMax):
|
||||
def add_schedule_to_db(self,
|
||||
mediaID,
|
||||
title,
|
||||
duration,
|
||||
startTime,
|
||||
endTime,
|
||||
dayOfWeek,
|
||||
startTimeUnix,
|
||||
section,
|
||||
strictTime,
|
||||
timeShift,
|
||||
overlapMax,
|
||||
xtra):
|
||||
unix = int(time.time())
|
||||
try:
|
||||
self.cursor.execute("REPLACE INTO schedule "
|
||||
"(unix, mediaID, title, duration, startTime, endTime, dayOfWeek, startTimeUnix, section, strictTime, timeShift, overlapMax) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(unix, mediaID, title, duration, startTime, endTime, dayOfWeek, startTimeUnix, section, strictTime, timeShift, overlapMax))
|
||||
"(unix, mediaID, title, duration, startTime, endTime, dayOfWeek, startTimeUnix, section, strictTime, timeShift, overlapMax, xtra) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(unix, mediaID, title, duration, startTime, endTime, dayOfWeek, startTimeUnix, section, strictTime, timeShift, overlapMax, xtra))
|
||||
self.conn.commit()
|
||||
# Catch the exception
|
||||
except Exception as e:
|
||||
@@ -224,9 +229,7 @@ class PseudoChannelDatabase():
|
||||
):
|
||||
|
||||
unix = int(time.time())
|
||||
|
||||
try:
|
||||
|
||||
self.cursor.execute("INSERT OR REPLACE INTO daily_schedule "
|
||||
"(unix, mediaID, title, episodeNumber, seasonNumber, "
|
||||
"showTitle, duration, startTime, endTime, dayOfWeek, sectionType, plexMediaID) "
|
||||
@@ -245,16 +248,11 @@ class PseudoChannelDatabase():
|
||||
sectionType,
|
||||
plexMediaID
|
||||
))
|
||||
|
||||
self.conn.commit()
|
||||
|
||||
# Catch the exception
|
||||
except Exception as e:
|
||||
|
||||
# Roll back any change if something goes wrong
|
||||
|
||||
self.conn.rollback()
|
||||
|
||||
raise e
|
||||
|
||||
def add_media_to_daily_schedule(self, media):
|
||||
@@ -263,7 +261,6 @@ class PseudoChannelDatabase():
|
||||
print str("#### Adding media to db: {} {}".format(media.title, media.start_time)).encode('UTF-8')
|
||||
except:
|
||||
print "----- Not outputting media info due to ascii code issues."
|
||||
|
||||
self.add_daily_schedule_to_db(
|
||||
0,
|
||||
media.title,
|
||||
@@ -278,95 +275,94 @@ class PseudoChannelDatabase():
|
||||
media.plex_media_id
|
||||
)
|
||||
|
||||
"""Database functions.
|
||||
def import_shows_table_by_row(self, mediaID, title, duration, lastEpisodeTitle, fullImageURL, plexMediaID):
|
||||
|
||||
unix = int(time.time())
|
||||
try:
|
||||
self.cursor.execute("REPLACE INTO shows "
|
||||
"(unix, mediaID, title, duration, lastEpisodeTitle, fullImageURL, plexMediaID) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
(unix, mediaID, title, duration, lastEpisodeTitle, fullImageURL, plexMediaID))
|
||||
self.conn.commit()
|
||||
# Catch the exception
|
||||
except Exception as e:
|
||||
# Roll back any change if something goes wrong
|
||||
self.conn.rollback()
|
||||
raise e
|
||||
|
||||
"""Database functions.
|
||||
Getters, etc.
|
||||
"""
|
||||
def get_shows_table(self):
|
||||
|
||||
sql = "SELECT * FROM shows"
|
||||
self.cursor.execute(sql)
|
||||
return self.cursor.fetchall()
|
||||
|
||||
def get_media(self, title, mediaType):
|
||||
|
||||
media = mediaType
|
||||
|
||||
sql = "SELECT * FROM "+media+" WHERE (title LIKE ?) COLLATE NOCASE"
|
||||
self.cursor.execute(sql, ("%"+title+"%", ))
|
||||
media_item = self.cursor.fetchone()
|
||||
|
||||
return media_item
|
||||
|
||||
def get_schedule(self):
|
||||
|
||||
self.cursor.execute("SELECT * FROM schedule ORDER BY datetime(startTimeUnix) ASC")
|
||||
|
||||
datalist = list(self.cursor.fetchall())
|
||||
|
||||
return datalist
|
||||
|
||||
def get_daily_schedule(self):
|
||||
|
||||
print "##### Getting Daily Schedule from DB."
|
||||
|
||||
self.cursor.execute("SELECT * FROM daily_schedule ORDER BY datetime(startTime) ASC")
|
||||
|
||||
datalist = list(self.cursor.fetchall())
|
||||
|
||||
print "+++++ Done."
|
||||
|
||||
return datalist
|
||||
|
||||
def get_movie(self, title):
|
||||
|
||||
media = "movies"
|
||||
|
||||
return self.get_media(title, media)
|
||||
|
||||
def get_shows(self, title):
|
||||
|
||||
media = "shows"
|
||||
|
||||
return self.get_media(title, media)
|
||||
|
||||
def get_music(self, title):
|
||||
|
||||
media = "music"
|
||||
|
||||
return self.get_media(title, media)
|
||||
|
||||
def get_video(self, title):
|
||||
|
||||
media = "videos"
|
||||
|
||||
return self.get_media(title, media)
|
||||
|
||||
def get_episodes(self, title):
|
||||
|
||||
media = "episodes"
|
||||
|
||||
return self.get_media(title, media)
|
||||
|
||||
def get_commercials(self):
|
||||
|
||||
self.cursor.execute("SELECT * FROM commercials ORDER BY duration ASC")
|
||||
|
||||
datalist = list(self.cursor.fetchall())
|
||||
|
||||
return datalist
|
||||
|
||||
def update_shows_table_with_last_episode(self, showTitle, lastEpisodeTitle):
|
||||
|
||||
sql1 = "UPDATE shows SET lastEpisodeTitle = ? WHERE title LIKE ? COLLATE NOCASE"
|
||||
|
||||
self.cursor.execute(sql1, (lastEpisodeTitle, showTitle, ))
|
||||
|
||||
self.conn.commit()
|
||||
|
||||
def get_first_episode(self, tvshow):
|
||||
|
||||
sql = ("SELECT id, unix, mediaID, title, duration, MIN(episodeNumber), MIN(seasonNumber), "
|
||||
"showTitle FROM episodes WHERE ( showTitle LIKE ?) COLLATE NOCASE")
|
||||
|
||||
self.cursor.execute(sql, (tvshow, ))
|
||||
|
||||
first_episode = self.cursor.fetchone()
|
||||
|
||||
return first_episode
|
||||
|
||||
'''
|
||||
@@ -377,32 +373,24 @@ class PseudoChannelDatabase():
|
||||
def get_episode_id(self, episodeTitle):
|
||||
|
||||
sql = "SELECT id FROM episodes WHERE ( title LIKE ?) COLLATE NOCASE"
|
||||
|
||||
self.cursor.execute(sql, (episodeTitle, ))
|
||||
|
||||
episode_id = self.cursor.fetchone()
|
||||
|
||||
return episode_id
|
||||
|
||||
def get_random_episode(self):
|
||||
|
||||
sql = "SELECT * FROM episodes WHERE id IN (SELECT id FROM episodes ORDER BY RANDOM() LIMIT 1)"
|
||||
|
||||
self.cursor.execute(sql)
|
||||
|
||||
return self.cursor.fetchone()
|
||||
|
||||
def get_random_movie(self):
|
||||
|
||||
sql = "SELECT * FROM movies WHERE id IN (SELECT id FROM movies ORDER BY RANDOM() LIMIT 1)"
|
||||
|
||||
self.cursor.execute(sql)
|
||||
|
||||
return self.cursor.fetchone()
|
||||
|
||||
def get_next_episode(self, series):
|
||||
|
||||
#print(series)
|
||||
'''
|
||||
*
|
||||
* As a way of storing a "queue", I am storing the *next episode title in the "shows" table so I can
|
||||
@@ -410,7 +398,6 @@ class PseudoChannelDatabase():
|
||||
*
|
||||
'''
|
||||
self.cursor.execute("SELECT lastEpisodeTitle FROM shows WHERE title LIKE ? COLLATE NOCASE", (series, ))
|
||||
|
||||
last_title_list = self.cursor.fetchone()
|
||||
'''
|
||||
*
|
||||
@@ -425,17 +412,13 @@ class PseudoChannelDatabase():
|
||||
*
|
||||
'''
|
||||
first_episode = self.get_first_episode(series)
|
||||
|
||||
first_episode_title = first_episode[3]
|
||||
|
||||
#print(first_episode_title)
|
||||
'''
|
||||
*
|
||||
* Add this episdoe title to the "shows" table for the queue functionality to work
|
||||
*
|
||||
'''
|
||||
self.update_shows_table_with_last_episode(series, first_episode_title)
|
||||
|
||||
return first_episode
|
||||
|
||||
elif last_title_list:
|
||||
@@ -444,10 +427,6 @@ class PseudoChannelDatabase():
|
||||
* The last episode stored in the "shows" table was not empty... get the next episode in the series
|
||||
*
|
||||
'''
|
||||
#print("First episode already set in shows, advancing episodes forward")
|
||||
|
||||
#print(str(self.get_episode_id(last_title_list[0])))
|
||||
|
||||
"""
|
||||
*
|
||||
* If this isn't a first run, then grabbing the next episode by incrementing id
|
||||
@@ -455,7 +434,6 @@ class PseudoChannelDatabase():
|
||||
"""
|
||||
sql = ("SELECT * FROM episodes WHERE ( id > "+str(self.get_episode_id(last_title_list[0])[0])+
|
||||
" AND showTitle LIKE ? ) ORDER BY seasonNumber LIMIT 1 COLLATE NOCASE")
|
||||
|
||||
self.cursor.execute(sql, (series, ))
|
||||
'''
|
||||
*
|
||||
@@ -463,38 +441,23 @@ class PseudoChannelDatabase():
|
||||
*
|
||||
'''
|
||||
next_episode = self.cursor.fetchone()
|
||||
|
||||
if next_episode != None:
|
||||
|
||||
#print(next_episode[3])
|
||||
|
||||
self.update_shows_table_with_last_episode(series, next_episode[3])
|
||||
|
||||
return next_episode
|
||||
|
||||
else:
|
||||
|
||||
print("Not grabbing next episode restarting series, series must be over. Restarting from episode 1.")
|
||||
|
||||
print("+++++ Not grabbing next episode restarting series, series must be over. Restarting from episode 1.")
|
||||
first_episode = self.get_first_episode(series)
|
||||
|
||||
self.update_shows_table_with_last_episode(series, first_episode[3])
|
||||
|
||||
return first_episode
|
||||
|
||||
def get_commercial(self, title):
|
||||
|
||||
media = "commercials"
|
||||
|
||||
sql = "SELECT * FROM "+media+" WHERE (title LIKE ?) COLLATE NOCASE"
|
||||
self.cursor.execute(sql, (title, ))
|
||||
datalist = list(self.cursor.fetchone())
|
||||
if datalist > 0:
|
||||
print(datalist)
|
||||
|
||||
return datalist
|
||||
|
||||
else:
|
||||
|
||||
return None
|
||||
|
||||
return None
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from plexapi.server import PlexServer
|
||||
from datetime import datetime
|
||||
import sqlite3
|
||||
|
||||
import thread,SocketServer,SimpleHTTPServer
|
||||
|
||||
from yattag import Doc
|
||||
from yattag import indent
|
||||
import os, sys
|
||||
|
||||
import socket
|
||||
import logging
|
||||
import logging.handlers
|
||||
from datetime import datetime
|
||||
import sqlite3
|
||||
import thread,SocketServer,SimpleHTTPServer
|
||||
from plexapi.server import PlexServer
|
||||
from yattag import Doc
|
||||
from yattag import indent
|
||||
|
||||
class PseudoDailyScheduleController():
|
||||
|
||||
@@ -25,32 +23,19 @@ class PseudoDailyScheduleController():
|
||||
):
|
||||
|
||||
self.PLEX = PlexServer(server, token)
|
||||
|
||||
self.BASE_URL = server
|
||||
|
||||
self.TOKEN = token
|
||||
|
||||
self.PLEX_CLIENTS = clients
|
||||
|
||||
self.CONTROLLER_SERVER_PATH = controllerServerPath
|
||||
|
||||
self.CONTROLLER_SERVER_PORT = controllerServerPort if controllerServerPort != '' else '80'
|
||||
|
||||
self.DEBUG = debugMode
|
||||
|
||||
self.webserverStarted = False
|
||||
|
||||
try:
|
||||
|
||||
self.my_logger = logging.getLogger('MyLogger')
|
||||
self.my_logger.setLevel(logging.DEBUG)
|
||||
|
||||
self.handler = logging.handlers.SysLogHandler(address = '/dev/log')
|
||||
|
||||
self.my_logger.addHandler(self.handler)
|
||||
|
||||
except:
|
||||
|
||||
pass
|
||||
|
||||
'''
|
||||
@@ -63,33 +48,22 @@ class PseudoDailyScheduleController():
|
||||
def get_show_photo(self, section, title):
|
||||
|
||||
backgroundImagePath = None
|
||||
|
||||
backgroundImgURL = ''
|
||||
|
||||
try:
|
||||
|
||||
backgroundImagePath = self.PLEX.library.section(section).get(title)
|
||||
|
||||
except:
|
||||
|
||||
return backgroundImgURL
|
||||
|
||||
if backgroundImagePath != None and isinstance(backgroundImagePath.art, str):
|
||||
|
||||
backgroundImgURL = self.BASE_URL+backgroundImagePath.art+"?X-Plex-Token="+self.TOKEN
|
||||
|
||||
return backgroundImgURL
|
||||
|
||||
def start_server(self):
|
||||
|
||||
if self.webserverStarted == False and self.CONTROLLER_SERVER_PATH != '':
|
||||
|
||||
"""Changing dir to the schedules dir."""
|
||||
web_dir = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'schedules'))
|
||||
os.chdir(web_dir)
|
||||
|
||||
PORT = int(self.CONTROLLER_SERVER_PORT)
|
||||
|
||||
class MyHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
|
||||
def log_message(self, format, *args):
|
||||
@@ -103,46 +77,34 @@ class PseudoDailyScheduleController():
|
||||
class ReusableTCPServer(SocketServer.TCPServer): allow_reuse_address=True
|
||||
# specify the httpd service on 0.0.0.0 (all interfaces) on port 80
|
||||
httpd = ReusableTCPServer(("0.0.0.0", PORT),MyHandler)
|
||||
|
||||
# thread this mofo
|
||||
thread.start_new_thread(httpd.serve_forever,())
|
||||
|
||||
# handle keyboard interrupts
|
||||
except KeyboardInterrupt:
|
||||
core.print_info("Exiting the SET web server...")
|
||||
httpd.socket.close()
|
||||
|
||||
except socket.error, exc:
|
||||
print "Caught exception socket.error : %s" % exc
|
||||
# handle the rest
|
||||
#except Exception:
|
||||
# print "[*] Exiting the SET web server...\n"
|
||||
# httpd.socket.close()
|
||||
|
||||
self.webserverStarted = True
|
||||
|
||||
def get_xml_from_daily_schedule(self, currentTime, bgImageURL, datalist):
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
time = now.strftime("%B %d, %Y")
|
||||
|
||||
doc, tag, text, line = Doc(
|
||||
|
||||
).ttl()
|
||||
|
||||
doc.asis('<?xml version="1.0" encoding="UTF-8"?>')
|
||||
|
||||
with tag('schedule', currently_playing_bg_image=bgImageURL if bgImageURL != None else ''):
|
||||
|
||||
for row in datalist:
|
||||
|
||||
if str(row[11]) == "Commercials" and self.DEBUG == False:
|
||||
|
||||
continue
|
||||
|
||||
timeB = datetime.strptime(row[8], '%I:%M:%S %p')
|
||||
|
||||
if currentTime == None:
|
||||
|
||||
with tag('time',
|
||||
('data-key', str(row[12])),
|
||||
('data-current', 'false'),
|
||||
@@ -150,11 +112,8 @@ class PseudoDailyScheduleController():
|
||||
('data-title', str(row[3])),
|
||||
('data-start-time', str(row[8])),
|
||||
):
|
||||
|
||||
text(row[8])
|
||||
|
||||
elif currentTime.hour == timeB.hour and currentTime.minute == timeB.minute:
|
||||
|
||||
with tag('time',
|
||||
('data-key', str(row[12])),
|
||||
('data-current', 'true'),
|
||||
@@ -162,11 +121,8 @@ class PseudoDailyScheduleController():
|
||||
('data-title', str(row[3])),
|
||||
('data-start-time', str(row[8])),
|
||||
):
|
||||
|
||||
text(row[8])
|
||||
|
||||
else:
|
||||
|
||||
with tag('time',
|
||||
('data-key', str(row[12])),
|
||||
('data-current', 'false'),
|
||||
@@ -174,12 +130,9 @@ class PseudoDailyScheduleController():
|
||||
('data-title', str(row[3])),
|
||||
('data-start-time', str(row[8])),
|
||||
):
|
||||
|
||||
text(row[8])
|
||||
|
||||
return indent(doc.getvalue())
|
||||
|
||||
|
||||
'''
|
||||
*
|
||||
* Get the generated html for the .html file that is the schedule.
|
||||
@@ -192,27 +145,18 @@ class PseudoDailyScheduleController():
|
||||
def get_html_from_daily_schedule(self, currentTime, bgImageURL, datalist):
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
time = now.strftime("%B %d, %Y")
|
||||
|
||||
doc, tag, text, line = Doc(
|
||||
|
||||
).ttl()
|
||||
|
||||
doc.asis('<!DOCTYPE html>')
|
||||
|
||||
with tag('html'):
|
||||
|
||||
with tag('head'):
|
||||
|
||||
with tag('title'):
|
||||
|
||||
text(time + " - Daily Pseudo Schedule")
|
||||
|
||||
doc.asis('<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet">')
|
||||
doc.asis('<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet">')
|
||||
doc.asis('<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>')
|
||||
|
||||
doc.asis("""
|
||||
<script>
|
||||
$(function(){
|
||||
@@ -267,28 +211,17 @@ class PseudoDailyScheduleController():
|
||||
});
|
||||
</script>
|
||||
""")
|
||||
|
||||
if bgImageURL != None:
|
||||
doc.asis('<style>body{ background:transparent!important; } html { background: url('+bgImageURL+') no-repeat center center fixed; -webkit-background-size: cover;-moz-background-size: cover;-o-background-size: cover;background-size: cover;}.make-white { padding: 24px; background:rgba(255,255,255, 0.9); }</style>')
|
||||
|
||||
with tag('body'):
|
||||
|
||||
with tag('div', klass='container mt-3'):
|
||||
|
||||
with tag('div', klass='row make-white'):
|
||||
|
||||
with tag('div'):
|
||||
|
||||
with tag('div'):
|
||||
|
||||
line('h1', "Daily Pseudo Schedule", klass='col-12 pl-0')
|
||||
|
||||
with tag('div'):
|
||||
|
||||
line('h3', time, klass='col-12 pl-1')
|
||||
|
||||
with tag('table', klass='col-12 table table-bordered table-hover'):
|
||||
|
||||
with tag('thead', klass='table-info'):
|
||||
with tag('tr'):
|
||||
with tag('th'):
|
||||
@@ -301,23 +234,14 @@ class PseudoDailyScheduleController():
|
||||
text('Title')
|
||||
with tag('th'):
|
||||
text('Start Time')
|
||||
|
||||
numberIncrease = 0
|
||||
|
||||
for row in datalist:
|
||||
|
||||
if str(row[11]) == "Commercials" and self.DEBUG == False:
|
||||
|
||||
continue
|
||||
|
||||
numberIncrease += 1
|
||||
|
||||
with tag('tbody'):
|
||||
|
||||
timeB = datetime.strptime(row[8], '%I:%M:%S %p')
|
||||
|
||||
if currentTime == None:
|
||||
|
||||
with tag('tr'):
|
||||
with tag('th', scope='row'):
|
||||
text(numberIncrease)
|
||||
@@ -329,11 +253,8 @@ class PseudoDailyScheduleController():
|
||||
text(row[3])
|
||||
with tag('td'):
|
||||
text(row[8])
|
||||
|
||||
elif currentTime.hour == timeB.hour and currentTime.minute == timeB.minute:
|
||||
|
||||
with tag('tr', klass='bg-info'):
|
||||
|
||||
with tag('th', scope='row'):
|
||||
text(numberIncrease)
|
||||
with tag('td'):
|
||||
@@ -344,9 +265,7 @@ class PseudoDailyScheduleController():
|
||||
text(row[3])
|
||||
with tag('td'):
|
||||
text(row[8])
|
||||
|
||||
else:
|
||||
|
||||
with tag('tr'):
|
||||
with tag('th', scope='row'):
|
||||
text(numberIncrease)
|
||||
@@ -358,8 +277,6 @@ class PseudoDailyScheduleController():
|
||||
text(row[3])
|
||||
with tag('td'):
|
||||
text(row[8])
|
||||
|
||||
|
||||
return indent(doc.getvalue())
|
||||
|
||||
'''
|
||||
@@ -372,25 +289,15 @@ class PseudoDailyScheduleController():
|
||||
def write_schedule_to_file(self, data):
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
fileName = "index.html"
|
||||
|
||||
writepath = './' if os.path.basename(os.getcwd()) == "schedules" else "./schedules/"
|
||||
|
||||
if not os.path.exists(writepath):
|
||||
|
||||
os.makedirs(writepath)
|
||||
|
||||
if os.path.exists(writepath+fileName):
|
||||
|
||||
os.remove(writepath+fileName)
|
||||
|
||||
mode = 'a' if os.path.exists(writepath) else 'w'
|
||||
|
||||
with open(writepath+fileName, mode) as f:
|
||||
|
||||
f.write(data)
|
||||
|
||||
self.start_server()
|
||||
|
||||
'''
|
||||
@@ -403,26 +310,16 @@ class PseudoDailyScheduleController():
|
||||
def write_xml_to_file(self, data):
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
fileName = "pseudo_schedule.xml"
|
||||
|
||||
writepath = './' if os.path.basename(os.getcwd()) == "schedules" else "./schedules/"
|
||||
|
||||
if not os.path.exists(writepath):
|
||||
|
||||
os.makedirs(writepath)
|
||||
|
||||
if os.path.exists(writepath+fileName):
|
||||
|
||||
os.remove(writepath+fileName)
|
||||
|
||||
mode = 'a' if os.path.exists(writepath) else 'w'
|
||||
|
||||
with open(writepath+fileName, mode) as f:
|
||||
|
||||
f.write(data)
|
||||
|
||||
|
||||
'''
|
||||
*
|
||||
* Write 0 or 1 to file for the ajax in the schedule.html to know when to refresh
|
||||
@@ -433,44 +330,27 @@ class PseudoDailyScheduleController():
|
||||
def write_refresh_bool_to_file(self):
|
||||
|
||||
fileName = "pseudo_refresh.txt"
|
||||
|
||||
writepath = './' if os.path.basename(os.getcwd()) == "schedules" else "./schedules/"
|
||||
|
||||
first_line = ''
|
||||
|
||||
if not os.path.exists(writepath):
|
||||
|
||||
os.makedirs(writepath)
|
||||
|
||||
if not os.path.exists(writepath+fileName):
|
||||
|
||||
file(writepath+fileName, 'w').close()
|
||||
|
||||
mode = 'r+'
|
||||
|
||||
with open(writepath+fileName, mode) as f:
|
||||
|
||||
f.seek(0)
|
||||
|
||||
first_line = f.read()
|
||||
|
||||
if self.DEBUG:
|
||||
print "+++++ Html refresh flag: {}".format(first_line)
|
||||
|
||||
if first_line == '' or first_line == "0":
|
||||
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write("1")
|
||||
|
||||
else:
|
||||
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write("0")
|
||||
|
||||
#f.close()
|
||||
|
||||
'''
|
||||
*
|
||||
* Trigger "playMedia()" on the Python Plex API for specified media.
|
||||
@@ -482,63 +362,45 @@ class PseudoDailyScheduleController():
|
||||
'''
|
||||
def play_media(self, mediaType, mediaParentTitle, mediaTitle, offset):
|
||||
|
||||
|
||||
try:
|
||||
|
||||
if mediaType == "TV Shows":
|
||||
|
||||
mediaItems = self.PLEX.library.section(mediaType).get(mediaParentTitle).episodes()
|
||||
|
||||
for item in mediaItems:
|
||||
|
||||
# print(part.title)
|
||||
|
||||
if item.title == mediaTitle:
|
||||
|
||||
for client in self.PLEX_CLIENTS:
|
||||
|
||||
clientItem = self.PLEX.client(client)
|
||||
|
||||
clientItem.playMedia(item, offset=offset)
|
||||
|
||||
break
|
||||
|
||||
elif mediaType == "Movies":
|
||||
|
||||
movie = self.PLEX.library.section(mediaType).get(mediaTitle)
|
||||
|
||||
for client in self.PLEX_CLIENTS:
|
||||
|
||||
clientItem = self.PLEX.client(client)
|
||||
|
||||
clientItem.playMedia(movie, offset=offset)
|
||||
|
||||
elif mediaType == "Commercials":
|
||||
|
||||
movie = self.PLEX.library.section(mediaType).get(mediaTitle)
|
||||
|
||||
for client in self.PLEX_CLIENTS:
|
||||
|
||||
clientItem = self.PLEX.client(client)
|
||||
|
||||
clientItem.playMedia(movie, offset=offset)
|
||||
|
||||
else:
|
||||
|
||||
print("##### Not sure how to play {}".format(mediaType))
|
||||
|
||||
print "+++++ Done."
|
||||
|
||||
except Exception as e:
|
||||
|
||||
print e.__doc__
|
||||
|
||||
print e.message
|
||||
|
||||
print "##### There was an error trying to play the media."
|
||||
|
||||
pass
|
||||
|
||||
def stop_media(self):
|
||||
|
||||
try:
|
||||
self.my_logger.debug('Trying to stop media.')
|
||||
for client in self.PLEX_CLIENTS:
|
||||
clientItem = self.PLEX.client(client)
|
||||
clientItem.stop(mtype='video')
|
||||
self.my_logger.debug('Done.')
|
||||
except Exception as e:
|
||||
self.my_logger.debug('stop_media - except.', e)
|
||||
pass
|
||||
'''
|
||||
*
|
||||
* If tv_controller() does not find a "startTime" for scheduled media, search for an "endTime" match for now time.
|
||||
@@ -550,49 +412,32 @@ class PseudoDailyScheduleController():
|
||||
def check_for_end_time(self, datalist):
|
||||
|
||||
currentTime = datetime.now()
|
||||
|
||||
"""c.execute("SELECT * FROM daily_schedule")
|
||||
|
||||
datalist = list(c.fetchall())
|
||||
"""
|
||||
for row in datalist:
|
||||
|
||||
try:
|
||||
|
||||
endTime = datetime.strptime(row[9], '%Y-%m-%d %H:%M:%S.%f')
|
||||
|
||||
except ValueError:
|
||||
|
||||
endTime = datetime.strptime(row[9], '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
if currentTime.hour == endTime.hour:
|
||||
|
||||
if currentTime.minute == endTime.minute:
|
||||
|
||||
if currentTime.second == endTime.second:
|
||||
|
||||
if self.DEBUG:
|
||||
print("Ok end time found")
|
||||
|
||||
self.write_schedule_to_file(self.get_html_from_daily_schedule(None, None, datalist))
|
||||
self.write_xml_to_file(self.get_xml_from_daily_schedule(None, None, datalist))
|
||||
|
||||
self.write_refresh_bool_to_file()
|
||||
|
||||
break
|
||||
|
||||
def play(self, row, datalist, offset=0):
|
||||
|
||||
print str("##### Starting Media: '{}'".format(row[3])).encode('UTF-8')
|
||||
print str("##### Media Offset: '{}' seconds.".format(int(offset / 1000))).encode('UTF-8')
|
||||
|
||||
if self.DEBUG:
|
||||
print str(row).encode('UTF-8')
|
||||
|
||||
timeB = datetime.strptime(row[8], '%I:%M:%S %p')
|
||||
|
||||
self.play_media(row[11], row[6], row[3], offset)
|
||||
|
||||
self.write_schedule_to_file(
|
||||
self.get_html_from_daily_schedule(
|
||||
timeB,
|
||||
@@ -603,9 +448,7 @@ class PseudoDailyScheduleController():
|
||||
datalist
|
||||
)
|
||||
)
|
||||
|
||||
self.write_refresh_bool_to_file()
|
||||
|
||||
"""Generate / write XML to file
|
||||
"""
|
||||
self.write_xml_to_file(
|
||||
@@ -618,15 +461,11 @@ class PseudoDailyScheduleController():
|
||||
datalist
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
self.my_logger.debug('Trying to play: ' + row[3])
|
||||
|
||||
except:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
'''
|
||||
*
|
||||
* Check DB / current time. If that matches a scheduled shows startTime then trigger play via Plex API
|
||||
@@ -637,36 +476,21 @@ class PseudoDailyScheduleController():
|
||||
def tv_controller(self, datalist):
|
||||
|
||||
datalistLengthMonitor = 0;
|
||||
|
||||
currentTime = datetime.now()
|
||||
|
||||
"""c.execute("SELECT * FROM daily_schedule ORDER BY datetime(startTimeUnix) ASC")
|
||||
|
||||
datalist = list(c.fetchall())"""
|
||||
|
||||
try:
|
||||
|
||||
self.my_logger.debug('TV Controller')
|
||||
|
||||
except:
|
||||
|
||||
pass
|
||||
|
||||
for row in datalist:
|
||||
|
||||
timeB = datetime.strptime(row[8], '%I:%M:%S %p')
|
||||
|
||||
if currentTime.hour == timeB.hour:
|
||||
|
||||
if currentTime.minute == timeB.minute:
|
||||
|
||||
if currentTime.second == timeB.second:
|
||||
|
||||
print("Starting Media: " + row[3])
|
||||
print(row)
|
||||
|
||||
self.play_media(row[11], row[6], row[3])
|
||||
|
||||
self.write_schedule_to_file(
|
||||
self.get_html_from_daily_schedule(
|
||||
timeB,
|
||||
@@ -677,9 +501,7 @@ class PseudoDailyScheduleController():
|
||||
datalist
|
||||
)
|
||||
)
|
||||
|
||||
self.write_refresh_bool_to_file()
|
||||
|
||||
"""Generate / write XML to file
|
||||
"""
|
||||
self.write_xml_to_file(
|
||||
@@ -692,26 +514,18 @@ class PseudoDailyScheduleController():
|
||||
datalist
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
self.my_logger.debug('Trying to play: ' + row[3])
|
||||
|
||||
except:
|
||||
|
||||
pass
|
||||
|
||||
break
|
||||
|
||||
datalistLengthMonitor += 1
|
||||
|
||||
if datalistLengthMonitor >= len(datalist):
|
||||
|
||||
self.check_for_end_time(datalist)
|
||||
|
||||
def make_xml_schedule(self, datalist):
|
||||
|
||||
print "+++++ ", "Writing XML / HTML to file."
|
||||
|
||||
self.write_refresh_bool_to_file()
|
||||
self.write_schedule_to_file(self.get_html_from_daily_schedule(None, None, datalist))
|
||||
self.write_xml_to_file(self.get_xml_from_daily_schedule(None, None, datalist))
|
||||
83
startstop.sh
83
startstop.sh
@@ -3,7 +3,7 @@
|
||||
# file: startstop.sh
|
||||
|
||||
#----
|
||||
# Simple script to start / stop PseudoChannel.py
|
||||
# Simple script to start / stop a python script in the background.
|
||||
#----
|
||||
|
||||
#----
|
||||
@@ -13,34 +13,79 @@
|
||||
|
||||
#----BEGIN EDITABLE VARS----
|
||||
|
||||
pid_file=running.pid
|
||||
SCRIPT_TO_EXECUTE_PLUS_ARGS='PseudoChannel.py -m -r'
|
||||
|
||||
output_pid_path=.
|
||||
OUTPUT_PID_FILE=running.pid
|
||||
|
||||
python_to_use="$(which python)"
|
||||
OUTPUT_PID_PATH=.
|
||||
|
||||
PYTHON_TO_USE="$(which python)"
|
||||
|
||||
# If using 'virtualenv' with python, specify the local virtualenv dir.
|
||||
VIRTUAL_ENV_DIR="env"
|
||||
|
||||
#----END EDITABLE VARS-------
|
||||
|
||||
if [ ! -e $output_pid_path/$pid_file ]; then
|
||||
# If virtualenv specified & exists, using that version of python instead.
|
||||
if [ -d "$VIRTUAL_ENV_DIR" ]; then
|
||||
|
||||
PYTHON_TO_USE="$VIRTUAL_ENV_DIR/bin/python"
|
||||
|
||||
fi
|
||||
|
||||
# If the .pid file doesn't exist (let's assume no processes are running)...
|
||||
if [ ! -e "$OUTPUT_PID_PATH/$OUTPUT_PID_FILE" ]; then
|
||||
|
||||
# If the running.pid file doesn't exists, create it, start PseudoChannel.py and add the PID to it.
|
||||
nohup $python_to_use ./PseudoChannel.py -m -r > /dev/null 2>&1 & echo $! > $output_pid_path/$pid_file
|
||||
"$PYTHON_TO_USE" ./$SCRIPT_TO_EXECUTE_PLUS_ARGS > /dev/null 2>&1 & echo $! > "$OUTPUT_PID_PATH/$OUTPUT_PID_FILE"
|
||||
|
||||
echo "Started PseudoChannel.py @ Process: $!"
|
||||
echo "Created $pid_file file in $output_pid_path dir"
|
||||
echo "Started $SCRIPT_TO_EXECUTE_PLUS_ARGS @ Process: $!"
|
||||
|
||||
sleep .7
|
||||
|
||||
echo "Created $OUTPUT_PID_FILE file in $OUTPUT_PID_PATH dir"
|
||||
|
||||
else
|
||||
|
||||
# If the running.pid exists, read it & try to kill the process if it exists, then delete it.
|
||||
the_pid=$(<$output_pid_path/$pid_file)
|
||||
rm $output_pid_path/$pid_file
|
||||
echo "Deleted $pid_file file in $output_pid_path dir"
|
||||
kill $the_pid
|
||||
while [ -e /proc/$the_pid ]
|
||||
do
|
||||
echo "PseudoChannel.py @: $the_pid is still running"
|
||||
sleep .6
|
||||
done
|
||||
echo "PseudoChannel.py @: $the_pid has finished"
|
||||
the_pid=$(<$OUTPUT_PID_PATH/$OUTPUT_PID_FILE)
|
||||
|
||||
fi
|
||||
rm "$OUTPUT_PID_PATH/$OUTPUT_PID_FILE"
|
||||
|
||||
echo "Deleted $OUTPUT_PID_FILE file in $OUTPUT_PID_PATH dir"
|
||||
|
||||
echo "kill: $the_pid"
|
||||
|
||||
kill "$the_pid"
|
||||
|
||||
COUNTER=1
|
||||
|
||||
while [ -e /proc/$the_pid ]
|
||||
|
||||
do
|
||||
|
||||
echo "$SCRIPT_TO_EXECUTE_PLUS_ARGS @: $the_pid is still running"
|
||||
|
||||
sleep .7
|
||||
|
||||
COUNTER=$[$COUNTER +1]
|
||||
|
||||
if [ $COUNTER -eq 20 ]; then
|
||||
|
||||
kill -9 "$the_pid"
|
||||
|
||||
fi
|
||||
|
||||
if [ $COUNTER -eq 40 ]; then
|
||||
|
||||
exit 1
|
||||
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
echo "$SCRIPT_TO_EXECUTE_PLUS_ARGS @: $the_pid has finished"
|
||||
|
||||
fi
|
||||
|
||||
exit 0
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
17
tests/test_commercial_injection.py
Normal file
17
tests/test_commercial_injection.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import pytest
|
||||
import datetime
|
||||
|
||||
@pytest.mark.parametrize("commercial, expected", [
|
||||
(["1", "1501900754", "3", "001 - Kit_Kat_Commercial_-_Give_Me_A_Break_1988", "30890", "/library/metadata/3854"], 35890)
|
||||
])
|
||||
def test_pad_the_commercial_dur(commercial, expected):
|
||||
|
||||
commercial_as_list = list(commercial)
|
||||
|
||||
commercial_as_list[4] = int(commercial_as_list[4]) + (5 * 1000)
|
||||
|
||||
assert int(commercial_as_list[4]) == expected
|
||||
|
||||
def test_inject_commercials():
|
||||
|
||||
pass
|
||||
25
tests/test_update_daily_schedule.py
Normal file
25
tests/test_update_daily_schedule.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import pytest
|
||||
import datetime
|
||||
|
||||
@pytest.mark.parametrize("prevstartime, prevendtime, nowtime, expected", [
|
||||
("04:55:00 PM", "1900-01-01 17:23:42.304000", "1900-01-01 17:10:42.304000", True),
|
||||
("04:55:00 PM", "1900-01-01 17:23:42.304000", "1900-01-01 16:56:42.304000", True),
|
||||
("04:55:00 PM", "1900-01-01 17:23:42.304000", "1900-01-01 16:59:42.304000", True),
|
||||
("04:55:00 PM", "1900-01-01 17:23:42.304000", "1900-01-01 17:02:42.304000", True),
|
||||
("04:55:00 PM", "1900-01-01 17:23:42.304000", "1900-01-01 17:15:42.304000", True),
|
||||
("04:55:00 PM", "1900-01-01 17:23:42.304000", "1900-01-01 17:23:43.304000", False),
|
||||
("04:55:00 PM", "1900-01-01 17:23:42.304000", "1900-01-01 17:25:00.304000", False),
|
||||
])
|
||||
def test_prev_day_media_still_playing_on_update(prevendtime, prevstartime, nowtime, expected):
|
||||
|
||||
prev_end_time_to_watch_for = None
|
||||
|
||||
now = datetime.datetime.strptime(nowtime, '%Y-%m-%d %H:%M:%S.%f')
|
||||
|
||||
prev_start_time = datetime.datetime.strptime(prevstartime, "%I:%M:%S %p")
|
||||
|
||||
prev_end_time_format = '%Y-%m-%d %H:%M:%S.%f' if '.' in prevendtime else '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
prev_end_time = datetime.datetime.strptime(prevendtime, prev_end_time_format)
|
||||
|
||||
assert (prev_start_time < now and prev_end_time > now) == expected
|
||||
Reference in New Issue
Block a user