├── tvStart.sh ├── generateXMLTV.py ├── tvContinue.sh ├── README.md └── generatePlaylist.py /tvStart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Generating playlist for Todderang" 3 | 4 | python3 /path/to/tv/generatePlaylist.py no 5 | 6 | sleep 5 7 | 8 | python3 /path/to/tv/generateXMLTV.py 9 | 10 | sleep 2 11 | 12 | curl -X POST "https://:32400/livetv/dvrs//reloadGuide?X-Plex-Token=" 13 | 14 | sleep 2 15 | 16 | /usr/bin/cvlc /path/to/tv/playlist.m3u --sout-keep --sout '#transcode{vcodec=h264, acodec=aac, vb=800, ab=128} :standard{access=http, mux=ts, dst=}' --sout-mux-caching=5000 & 17 | 18 | sleep 5 19 | 20 | echo -ne '\n' 21 | 22 | sleep 2 23 | 24 | python3 /path/to/tv/generatePlaylist.py yes 25 | -------------------------------------------------------------------------------- /generateXMLTV.py: -------------------------------------------------------------------------------- 1 | import time 2 | from datetime import datetime, timedelta 3 | import temp_variables 4 | 5 | def generateGuideData(myTvDirectory, myBackup, myShowDurations): 6 | showCounter = 0 7 | print("Generating Guide Data...This will take a few seconds") 8 | 9 | # Replace time in xmltv files 10 | ## Need to do this after execution b/c script takes long to execute 11 | f1 = open(myTvDirectory + 'temp_xmltv.xml', 'r') 12 | if not myBackup: 13 | f2 = open(myTvDirectory + 'xmltv.xml', 'w') 14 | else: 15 | f2 = open(myTvDirectory + 'xmltv1.xml', 'w') 16 | 17 | # Wait for script to run on the minute (0 seconds) 18 | timeObject = datetime.now() 19 | myCurrentTime = int(timeObject.strftime("%S")) 20 | while (myCurrentTime / 60) != 0: 21 | timeObject = datetime.now() 22 | myCurrentTime = int(timeObject.strftime("%S")) 23 | continue 24 | 25 | for line in f1: 26 | # Replace & with correct escape 27 | line = line.replace('&', '&') 28 | 29 | if '{tempStartTime}' in line: 30 | showLength = myShowDurations[showCounter] 31 | print ("Show length: " + str(showLength)) 32 | currentTime = timeObject.strftime("%Y%m%d%H%M%S") 33 | line = line.replace('{tempStartTime}', str(currentTime)) 34 | timeObject += timedelta(seconds=showLength) 35 | currentTime = timeObject.strftime("%Y%m%d%H%M%S") 36 | line = line.replace('{tempEndTime}', str(currentTime)) 37 | 38 | # Increase show counter 39 | showCounter += 1 40 | 41 | f2.write(line) 42 | 43 | # Close xmltv 44 | f1.close() 45 | f2.close() 46 | 47 | # Call the function 48 | generateGuideData(temp_variables.tvDirectory, temp_variables.backup, list(temp_variables.showDurations)) 49 | -------------------------------------------------------------------------------- /tvContinue.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | LOCKFILE=/tmp/tvLockFile.lock 3 | 4 | #Do not run script if characters less than 10 (if uptime is less than 10 minutes) 5 | uptime=`uptime -p | cut -d " " -f2-` 6 | if [ "${#uptime}" -lt 10 ] 7 | then 8 | echo "Uptime has not reached 10 minutes!" 9 | exit 1 10 | fi 11 | 12 | if pgrep -x "tvStart.sh" > /dev/null 13 | then 14 | echo "tvStart.sh running...exiting" 15 | else 16 | # If lockfile exists 17 | if [ -f "$LOCKFILE" ] 18 | then 19 | echo "tvContinue.sh running...exiting" 20 | exit 1 21 | else 22 | if pgrep -x "vlc" > /dev/null 23 | then 24 | echo "VLC running...exiting" 25 | else 26 | # Create Lock File 27 | touch "$LOCKFILE" 28 | 29 | #Generate Guide Data 30 | python3 /path/to/tv/generateXMLTV.py 31 | 32 | sleep 2 33 | 34 | #Move backup files to current files 35 | mv -f /path/to/tv/showList1.txt /path/to/tv/showList.txt 36 | mv -f /path/to/tv/playlist1.m3u /path/to/tv/playlist.m3u 37 | mv -f /path/to/tv/xmltv1.xml /path/to/tv/xmltv.xml 38 | 39 | sleep 2 40 | 41 | echo "VLC stopped...starting it" 42 | /usr/bin/cvlc /path/to/tv/playlist.m3u --sout-keep --sout '#transcode{vcodec=h264, acodec=aac, vb=800, ab=128} :standard{access=http, mux=ts, dst=}' --sout-mux-caching=5000 & 43 | 44 | sleep 5 45 | 46 | echo -ne '\n' 47 | 48 | sleep 2 49 | 50 | #Generate a new playlist 51 | python3 /path/to/tv/generatePlaylist.py yes 52 | 53 | sleep 2 54 | 55 | # Remove Lock File 56 | rm -f "$LOCKFILE" 57 | 58 | curl -X POST "https://:32400/livetv/dvrs//reloadGuide?X-Plex-Token=" 59 | 60 | fi 61 | fi 62 | fi 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DIY TV Channel for Plex 2 | 3 | **Brief Description:** generatePlaylist.py will output blocks of episodes ranging from 40mins-1hr (blocks do not exceed 1hr) when provided a list of tv show directories. generateXMLTV.py will output guide data to import into plex. The two other bash script included are for linux users who want to setup total automation. These are run as a cron job and will take care of everything for you. 4 | 5 | --- 6 | 7 | **Software Needed**: 8 | 9 | - Plex (Plex Pass Required) 10 | 11 | - [xTeVe] (https://xteve.de) 12 | 13 | - VLC 14 | 15 | - Python3 (if you want to generate a playlist and guide) 16 | 17 | - moviepy (pip3 install moviepy) 18 | - tvdb_api (pip3 install tvdb_api) 19 | 20 | - FFmpeg [linux guide] (https://linoxide.com/linux-how-to/install-ffmpeg-centos-7/) 21 | 22 | --- 23 | 24 | **A few notes:** I am using a *Linux* Server. You may need to adjust steps to make this work for your individual setup. This works by using vlc to start an http stream and then have xTeVe start capturing it. Then you add xTeVe into the Plex DVR section as a new device. It is pretty straight forward. To get VLC working, you need to generate a M3U playlist so it plays your list of videos in order, continuously. I wrote my Python script to generate this M3U file and generate a XMLTV guide to import into Plex. 25 | 26 | **Before you run the python script, locate the following variables inside the script:** 27 | 28 | ``` 29 | cartoons = ["show1", "show2", "show3"] 30 | 31 | dir = "" # Directory to grab shows from (make sure there is a "/" at the end!) 32 | tvDirectory = "" # Directory where files will be generated (make sure there is a "/" at the end!) 33 | commercialsDirectory = "" # Directory where commercials will be pulled from (make sure there is a "/" at the end!) 34 | timezone = "" # Enter Timezone (Example: "-0400" for EST) 35 | showPoster = "" 36 | channelName = "" 37 | ``` 38 | 39 | It is imperative that you populate the variables before you run the script! 40 | 41 | --- 42 | 43 | *Step 1:* Generating your M3U playlist for VLC and XMLTV for Plex. Run the Python script by executing **python3 generatePlaylist.py **. To generate the XMLTV Guide, run **python3 generateXMLTV.py**. The script will recognize if you selected backup or not. 44 | 45 | --- 46 | 47 | *Step 2:* Launch VLC and test stream. 48 | Launch VLC by using the command making sure to input the ip address of your machine and any port you would like to broadcast your http stream on: 49 | 50 | ``` 51 | vlc -vvv /path/to/tv/playlist.m3u --sout-keep --sout '#transcode{vcodec=h264, acodec=aac, vb=800, ab=128} :standard{access=http, mux=ts, dst=}’ --sout-mux-caching=5000 52 | ``` 53 | **Note**: If you have a **headless** install of a server, you need to use **cvlc** instead of vlc. 54 | 55 | ``` 56 | cvlc -vvv /path/to/tv/playlist.m3u --sout-keep --sout '#transcode{vcodec=h264, acodec=aac, vb=800, ab=128} :standard{access=http, mux=ts, dst=}’ --sout-mux-caching=5000 57 | ``` 58 | **Second Note**: If you copy and paste this command, it may not work. If you are not immediately getting lots of output, **manually type the command**. 59 | 60 | --- 61 | 62 | *Step 3:* Open VLC on another computer/device and verify that your stream is working by using the ip address and port that you specified in the previous command. 63 | 64 | --- 65 | 66 | *Step 4: If you’ve made it to this step, your VLC http stream is successfully working and you are ready to install xTeVe. xTeVe is going to some configuration so let's start with that. 67 | 68 | Start xTeVe by navigating to the directory that you installed it and using the command **./xTeVe**. Take note of the url that is generated. Open up that webpage in your browser. Setup xTeVe to use XEPG. (You can setup xTeVe to run with systemd later) 69 | 70 | --- 71 | 72 | *Step 5:* Generate a M3U file for xTeVe. We need a file for xTeVe to know where to grab our http stream from vlc. Create a new file called **vlc_stream.m3u** and open it. Put the following inside of it (replacing the name of your channel and ip:port): 73 | 74 | ``` 75 | #EXTM3U 76 | #EXTINF:-1, 77 | http:// 78 | ``` 79 | 80 | --- 81 | 82 | *Step 6:* Setup xTeve Channel 83 | 84 | Import that into xTeVe by navigating to the Playlist tab and clicking “new”. Input a name and the path to your **vlc_stream.m3u**. (This is NOT the Python generated m3u playlist!) 85 | 86 | Go to the XMLTV tab, click new and input the path to your xmltv.xml (whether it was generated from the Python script or you have your own). 87 | 88 | Go to the mapping tab, click your channel and input the channel description, select the XMLTV file we created and the XMLTV channel. 89 | 90 | --- 91 | 92 | *Step 7:* xTeVe Settings 93 | This step is the most important of the tutorial. This took me countless hours to figure out. Navigate to the settings tab and scroll down to the streaming section. Change the stream buffer from no buffer to **VLC**. Scroll down a little bit more to the option labeled: *VLC / CVLC Options:*. Change your command to this: 94 | 95 | ``` 96 | -I dummy [URL] --sout #std{mux=ts,access=file,dst=-} --loop 97 | ``` 98 | 99 | All that was added was a --loop to keep the stream from replaying the same video over and over again. 100 | 101 | --- 102 | 103 | *Step 8:* Open Plex and go to setting and click Live TV & DVR. Add a new device and type in the IP address of your xTeVe client. It should only be the IP address and the port. Click through the settings and when you get to the guide page, do not enter your zip code. Click the orange text at the top and input the path to your xmltv.xml file. Finish the setup and navigate to the Live TV section to see your TV station in action! 104 | 105 | --- 106 | 107 | **Extras:** 108 | Since I am a perfectionist, I setup my tvchannel to be completely automated. I have created a cron job with scripts so I never have to touch it. In an ideal situation, you would want to setup your tv station on a VM so it is contained in its own instance. This makes it very easy to test without having to reboot your whole setup. Make sure you update the scripts with the correct details listed below! 109 | 110 | My cron job looks like this: 111 | 112 | ``` 113 | @reboot /path/to/tv/tvStart.sh 114 | * * * * * /path/to/tv/tvContinue.sh 115 | ``` 116 | *tvStart.sh & tvContinue.sh instructions:* 117 | 118 | *Editing the Path:* There is some editing needed in both **tvStart.sh** and **tvContinue.sh**. Open up both files and change all of the paths to your current setup. Just do a replace and search for **/path/to/tv/**. 119 | 120 | *Editing the VLC IP:* Locate the **ip:port** phrase in the VLC command and change it with your current setup. Note: This is not always the same ip address if you are running VLC on a different machine. 121 | 122 | *Editing the cURL statement (automatically refreshes the guide in Plex):* Start off by locating the **cURL** command in both scripts. 123 | - Get your **API token.** Instructions can be found [here](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/). 124 | - Replace the **PlexServerIP** with your server IP. 125 | - Replace the **DVR ID** with your DVR ID. Go to https://PlexServerIP:32400/livetv/dvrs/?X-Plex-Token=api-token and locate the ID of your DVR. 126 | - Populate the full statement. 127 | 128 | --- 129 | 130 | This guide took me countless hours and critical thinking to put together. My ultimate goal was to make this work with Plex. I had an initial version working with VLC but I was not satisfied with it just working on the VLC client. It had to be Plex. 131 | 132 | If you want to consider **donating** to me that would be great, but certainly not required! 133 | 134 | [Donation Link](https://paypal.me/tmurphy605) 135 | 136 | Feel free to message me with any questions you might have. 137 | 138 | -**Todd** 139 | 140 | [My Website](http://toddamurphy.me/) -------------------------------------------------------------------------------- /generatePlaylist.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import random 4 | import moviepy.editor as mp 5 | import time 6 | import math 7 | from datetime import datetime, timedelta 8 | import tvdb_api 9 | 10 | # Display help 11 | # Check if it is a backup file 12 | backup = False 13 | commercials = False 14 | if not len(sys.argv) > 1: 15 | print ("Incorrect Usage: python3 generateShowList.py ") 16 | exit() 17 | else: 18 | if sys.argv[1] == "yes": 19 | backup = True 20 | print ("BACKUP FLAG WAS SET TO TRUE") 21 | time.sleep(3) 22 | if sys.argv[2] == "yes": 23 | commercials = True 24 | print ("COMMERCIALS FLAG SET TO TRUE") 25 | time.sleep(3) 26 | if dir == "": 27 | print ("Please populate your 'dir' variable! It cannot be null. Open up the script") 28 | exit() 29 | 30 | 31 | ######################################################## 32 | ######################################################## 33 | # Edit these variable to your system! # 34 | ######################################################## 35 | ######################################################## 36 | 37 | cartoons = ["show1", "show2", "show3"] 38 | 39 | dir = "" # Directory to grab shows from (make sure there is a "/" at the end!) 40 | tvDirectory = "" # Directory where files will be generated (make sure there is a "/" at the end!) 41 | commercialsDirectory = "" # Directory where commercials will be pulled from (make sure there is a "/" at the end!) 42 | timezone = "" # Enter Timezone (Example: "-0400" for EST) 43 | showPoster = "" 44 | channelName = "" 45 | 46 | ######################################################## 47 | ######################################################## 48 | # END # 49 | ######################################################## 50 | ######################################################## 51 | 52 | cartoonsLeft = cartoons.copy() 53 | showDirectory = [] 54 | commercialList = [] 55 | commercialSpecific = {} 56 | previousRandomShow = 999 57 | playlistDuration = 0 58 | showDurations = [] 59 | showName = "" 60 | showDesc = "" 61 | showCounter = 0 62 | showLength = 0 63 | blockCounter = 0 64 | t = tvdb_api.Tvdb() 65 | 66 | 67 | extensions = ['.264', '.3g2', '.3gp', '.3gp2', '.3gpp', '.3gpp2', '.3mm', '.3p2', '.60d', '.787', '.89', '.aaf', '.aec', '.aep', '.aepx', 68 | '.aet', '.aetx', '.ajp', '.ale', '.am', '.amc', '.amv', '.amx', '.anim', '.aqt', '.arcut', '.arf', '.asf', '.asx', '.avb', 69 | '.avc', '.avd', '.avi', '.avp', '.avs', '.avs', '.avv', '.axm', '.bdm', '.bdmv', '.bdt2', '.bdt3', '.bik', '.bin', '.bix', 70 | '.bmk', '.bnp', '.box', '.bs4', '.bsf', '.bvr', '.byu', '.camproj', '.camrec', '.camv', '.ced', '.cel', '.cine', '.cip', 71 | '.clpi', '.cmmp', '.cmmtpl', '.cmproj', '.cmrec', '.cpi', '.cst', '.cvc', '.cx3', '.d2v', '.d3v', '.dat', '.dav', '.dce', 72 | '.dck', '.dcr', '.dcr', '.ddat', '.dif', '.dir', '.divx', '.dlx', '.dmb', '.dmsd', '.dmsd3d', '.dmsm', '.dmsm3d', '.dmss', 73 | '.dmx', '.dnc', '.dpa', '.dpg', '.dream', '.dsy', '.dv', '.dv-avi', '.dv4', '.dvdmedia', '.dvr', '.dvr-ms', '.dvx', '.dxr', 74 | '.dzm', '.dzp', '.dzt', '.edl', '.evo', '.eye', '.ezt', '.f4p', '.f4v', '.fbr', '.fbr', '.fbz', '.fcp', '.fcproject', '.ffd', 75 | '.flc', '.flh', '.fli', '.flv', '.flx', '.gfp', '.gl', '.gom', '.grasp', '.gts', '.gvi', '.gvp', '.h264', '.hdmov', '.hkm', 76 | '.ifo', '.imovieproj', '.imovieproject', '.ircp', '.irf', '.ism', '.ismc', '.ismv', '.iva', '.ivf', '.ivr', '.ivs', '.izz', 77 | '.izzy', '.jss', '.jts', '.jtv', '.k3g', '.kmv', '.ktn', '.lrec', '.lsf', '.lsx', '.m15', '.m1pg', '.m1v', '.m21', '.m21', 78 | '.m2a', '.m2p', '.m2t', '.m2ts', '.m2v', '.m4e', '.m4u', '.m4v', '.m75', '.mani', '.meta', '.mgv', '.mj2', '.mjp', '.mjpg', 79 | '.mk3d', '.mkv', '.mmv', '.mnv', '.mob', '.mod', '.modd', '.moff', '.moi', '.moov', '.mov', '.movie', '.mp21', '.mp21', 80 | '.mp2v', '.mp4', '.mp4v', '.mpe', '.mpeg', '.mpeg1', '.mpeg4', '.mpf', '.mpg', '.mpg2', '.mpgindex', '.mpl', '.mpl', 81 | '.mpls', '.mpsub', '.mpv', '.mpv2', '.mqv', '.msdvd', '.mse', '.msh', '.mswmm', '.mts', '.mtv', '.mvb', '.mvc', '.mvd', 82 | '.mve', '.mvex', '.mvp', '.mvp', '.mvy', '.mxf', '.mxv', '.mys', '.ncor', '.nsv', '.nut', '.nuv', '.nvc', '.ogm', '.ogv', '.ogx', 83 | '.osp', '.otrkey', '.pac', '.par', '.pds', '.pgi', '.photoshow', '.piv', '.pjs', '.playlist', '.plproj', '.pmf', '.pmv', '.pns', 84 | '.ppj', '.prel', '.pro', '.prproj', '.prtl', '.psb', '.psh', '.pssd', '.pva', '.pvr', '.pxv', '.qt', '.qtch', '.qtindex', '.qtl', 85 | '.qtm', '.qtz', '.r3d', '.rcd', '.rcproject', '.rdb', '.rec', '.rm', '.rmd', '.rmd', '.rmp', '.rms', '.rmv', '.rmvb', '.roq', '.rp', 86 | '.rsx', '.rts', '.rts', '.rum', '.rv', '.rvid', '.rvl', '.sbk', '.sbt', '.scc', '.scm', '.scm', '.scn', '.screenflow', '.sec', 87 | '.sedprj', '.seq', '.sfd', '.sfvidcap', '.siv', '.smi', '.smi', '.smil', '.smk', '.sml', '.smv', '.spl', '.sqz', '.srt', '.ssf', 88 | '.ssm', '.stl', '.str', '.stx', '.svi', '.swf', '.swi', '.swt', '.tda3mt', '.tdx', '.thp', '.tivo', '.tix', '.tod', '.tp', '.tp0', 89 | '.tpd', '.tpr', '.trp', '.ts', '.tsp', '.ttxt', '.tvs', '.usf', '.usm', '.vc1', '.vcpf', '.vcr', '.vcv', '.vdo', '.vdr', '.vdx', 90 | '.veg', '.vem', '.vep', '.vf', '.vft', '.vfw', '.vfz', '.vgz', '.vid', '.video', '.viewlet', '.viv', '.vivo', '.vlab', '.vob', 91 | '.vp3', '.vp6', '.vp7', '.vpj', '.vro', '.vs4', '.vse', '.vsp', '.w32', '.wcp', '.webm', '.wlmp', '.wm', '.wmd', '.wmmp', '.wmv', 92 | '.wmx', '.wot', '.wp3', '.wpl', '.wtv', '.wve', '.wvx', '.xej', '.xel', '.xesc', '.xfl', '.xlmv', '.xmv', '.xvid', '.y4m', '.yog', 93 | '.yuv', '.zeg', '.zm1', '.zm2', '.zm3', '.zmv'] 94 | 95 | 96 | print("#####GENERATING SHOW LIST#####") 97 | 98 | 99 | ######################################################## 100 | # Writes to the show list 101 | ######################################################## 102 | def writeToArray(path): 103 | global tempShowList 104 | global extensions 105 | 106 | ext = os.path.splitext(path)[-1].lower() 107 | 108 | if ext in extensions: 109 | tempShowList.append(path) 110 | #print("Added: " + path) 111 | ######################################################## 112 | 113 | 114 | ######################################################## 115 | # Check duration of file 116 | ######################################################## 117 | def checkDuration(file): 118 | try: 119 | return mp.VideoFileClip(file).duration 120 | except OSError: 121 | print("Could not get duration for: " + file) 122 | return 1799 # default value if it fails 123 | ######################################################## 124 | 125 | 126 | ######################################################## 127 | # Generate episdoe list that fit into 40min -1hr block 128 | ######################################################## 129 | def generateBlock(episodes): 130 | global playlistDuration 131 | returnList = [] 132 | durationCheck = False 133 | totalDuration = 0 134 | 135 | while not durationCheck: 136 | randomChoice = random.choice(episodes) 137 | currentDuration = checkDuration(randomChoice) 138 | totalDuration += currentDuration 139 | playlistDuration += currentDuration 140 | 141 | if totalDuration < 3600: 142 | returnList.append(randomChoice) 143 | else: 144 | durationCheck = True 145 | return returnList 146 | ######################################################## 147 | 148 | 149 | ######################################################## 150 | # Generate a random commercial 151 | ######################################################## 152 | def getRandomCommercial(listOfCommercials): 153 | randomNumber = random.randint(0, len(listOfCommercials)-1) 154 | return listOfCommercials[randomNumber] 155 | 156 | ######################################################## 157 | 158 | 159 | ######################################################## 160 | # Get Poster for show 161 | ######################################################## 162 | def getShowPoster(show_name): 163 | global showPoster 164 | global t 165 | 166 | try: 167 | show = t[show_name] # Get Show 168 | poster = show.data['poster'] # Get poster for show 169 | return poster 170 | except: #If not found return channel poster 171 | return showPoster 172 | 173 | ######################################################## 174 | 175 | 176 | ######################################################## 177 | # Get Description for show 178 | ######################################################## 179 | def getShowDescription(show_name): 180 | global t 181 | 182 | try: 183 | show = t[show_name] # Get Show 184 | desc = show.data['overview'] # Get description for show 185 | return desc 186 | except: #If not found return channel poster 187 | return "" 188 | 189 | ######################################################## 190 | 191 | 192 | # Loops into individual directories to get directly to files 193 | while len(cartoonsLeft) != 0: 194 | tempShowList = [] 195 | randomNumber = random.randint(0, len(cartoonsLeft)-1) 196 | currentShow = cartoonsLeft[randomNumber] 197 | cartoonsLeft.remove(currentShow) 198 | 199 | for file in os.listdir(dir + currentShow): 200 | if "Specials" in file or "Subs" in file: #omit specials, extras, subtitles and deleted scenes 201 | continue 202 | if os.path.isfile(dir + currentShow + "/" + file): 203 | writeToArray(dir + currentShow + "/" + file) 204 | else: 205 | for seasonFile in os.listdir(dir + currentShow + "/" + file): 206 | writeToArray(dir + currentShow + "/" + file + "/" + seasonFile) 207 | showDirectory.append(tempShowList.copy()) 208 | 209 | # Clear Temp Show List 210 | tempShowList = [] 211 | 212 | # Loops into commercials directory 213 | if (commercials): 214 | for file in os.listdir(commercialsDirectory): 215 | if "ignore" in file: #omit commercials you dont want 216 | continue 217 | if os.path.isfile(commercialsDirectory + file): 218 | writeToArray(commercialsDirectory + file) 219 | else: 220 | for subFile in os.listdir(commercialsDirectory + file): 221 | if os.path.isfile(commercialsDirectory + file + "/" + subFile): 222 | writeToArray(commercialsDirectory + file + "/" + subFile) 223 | else: 224 | tempSpecificList = [] 225 | tempSpecificShow = "" 226 | for specificCommercial in os.listdir(commercialsDirectory + file + "/" + subFile): 227 | tempSpecificList.append(commercialsDirectory + file + "/" + subFile + "/" + specificCommercial) 228 | tempSpecificShow = specificCommercial 229 | 230 | # After Specific Show list is generated, insert into dictionary 231 | commercialSpecific[tempSpecificShow] = tempSpecificList.copy() 232 | 233 | commercialList = tempShowList.copy() 234 | 235 | # Open Files 236 | if not backup: 237 | tvList = open(tvDirectory + "showList.txt", "w") 238 | m3u = open(tvDirectory + "playlist.m3u", "w") 239 | else: 240 | tvList = open(tvDirectory + "showList1.txt", "w") 241 | m3u = open(tvDirectory + "playlist1.m3u", "w") 242 | xmltv = open(tvDirectory + "temp_xmltv.xml", "w") 243 | 244 | # Initial line in M3U file 245 | m3u.write("#EXTM3U\n") 246 | 247 | # Initial line for xmltv 248 | xmltv.write("\n") 249 | xmltv.write("\n") 250 | xmltv.write("" + channelName + "\n") 251 | xmltv.write("\n") 252 | xmltv.write("\n") 253 | 254 | # Generate Blocks of Episodes & puts back into directory 255 | while blockCounter < len(showDirectory): 256 | print ("Generating Blocks..." + str(len(showDirectory) - blockCounter) + " left.") 257 | showDirectory[blockCounter] = generateBlock(showDirectory[blockCounter]) 258 | blockCounter += 1 259 | 260 | # Loops through show directory and generate random schedule 261 | while len(showDirectory) > 0: 262 | randomShow = random.randint(0, len(showDirectory)-1) # already a random show because populated in random order 263 | specificCommercialFound = False # Initial Specific Commercial 264 | initialRunThrough = True 265 | 266 | while len(showDirectory[randomShow]) > 0: 267 | randomEpisode = random.choice(showDirectory[randomShow]) 268 | randomEpisodeWrite = randomEpisode.encode('utf-8').strip().decode() 269 | 270 | # Get Name of Show 271 | showName = randomEpisode.split(dir)[1].split('/')[0] 272 | showName = showName.encode('utf-8').strip().decode() 273 | 274 | # Get Description of Show (Episode Name) 275 | showDesc = os.path.basename(randomEpisode) 276 | showDesc = os.path.splitext(showDesc)[0] 277 | 278 | if (commercials): 279 | # Only check for specific commercial on initial run-through 280 | if initialRunThrough: 281 | initialRunThrough = False # No longer initial run-through 282 | 283 | # Check to see if there is specific commercial 284 | for show in commercialSpecific.keys(): 285 | if show.lower() in showName.lower(): 286 | randomCommercial = random.choice(commercialSpecific[show]) 287 | specificCommercialFound = True # Found a specific commercial 288 | 289 | print("Writing: " + showName) 290 | 291 | # Write episode to txt file 292 | tvList.write(randomEpisodeWrite) 293 | tvList.write("\n") 294 | 295 | #Need to get duration for XML and array 296 | cur_duration = checkDuration(randomEpisode) 297 | 298 | # Write episode to m3u file 299 | m3u.write("#EXTINF: " + str(cur_duration) + ", " + showDesc + "\n") 300 | m3u.write("file://" + randomEpisodeWrite + "\n") 301 | 302 | # Add extra guide information 303 | showDesc += " ||\n\n " 304 | showDesc += getShowDescription(showName) 305 | 306 | #Unicode Stuff 307 | showName = showName.encode('ascii', 'ignore').decode('ascii') 308 | showDesc = showDesc.encode('ascii', 'ignore').decode('ascii') 309 | 310 | # Write episode to xmltv file 311 | xmltv.write("\n") 312 | xmltv.write("" + showName + "\n") 313 | xmltv.write("" + showDesc + "\n") 314 | xmltv.write("\n") 315 | xmltv.write("\n") 316 | 317 | # If commercials 318 | if (commercials): 319 | 320 | # If there is no specific commercial 321 | if not specificCommercialFound: 322 | randomCommercial = getRandomCommercial(commercialList).encode('utf-8').strip().decode() 323 | 324 | commercial_duration = checkDuration(randomCommercial) 325 | 326 | m3u.write("#EXTINF: " + str(commercial_duration) + ", Commercial\n") 327 | m3u.write("file://" + randomCommercial + "\n") 328 | 329 | # Round duration to the nearest second 330 | cur_duration = math.ceil(cur_duration) 331 | 332 | # Add Episode and Duration to dictionary 333 | showDurations.append(cur_duration) 334 | 335 | # Remove the episode 336 | #print ("Removing: " + randomEpisode) 337 | showDirectory[randomShow].remove(randomEpisode) 338 | 339 | specificCommercialFound = False 340 | 341 | # Remove show 342 | del showDirectory[randomShow] 343 | 344 | # Close xmltv 345 | xmltv.write("") 346 | 347 | # Close files 348 | tvList.close() 349 | m3u.close() 350 | xmltv.close() 351 | 352 | # store variables to file for later xmltv generation 353 | temp_variables = open(tvDirectory + "temp_variables.py", "w") 354 | temp_variables.write("tvDirectory='" + tvDirectory +"'") 355 | temp_variables.write("\n") 356 | if backup: 357 | temp_variables.write("backup=True") 358 | else: 359 | temp_variables.write("backup=False") 360 | temp_variables.write("\n") 361 | temp_variables.write("showDurations=" + str(showDurations)) 362 | temp_variables.close() 363 | 364 | print ("Playlist Duration in seconds: " + str(playlistDuration)) 365 | print("Finished! Happy Streaming!") 366 | --------------------------------------------------------------------------------