import argparse import datetime from glob import glob import json from listdict import ListDict import os import subprocess # globals mediadir = "/var/lib/ffplayout/tv-media" playlistdir = "/var/lib/ffplayout/playlists" channel = "newellijaytelevision" verbose = False from_date = datetime.date.today() + datetime.timedelta(days=1) # tomorrow by default to_date = datetime.date.today() + datetime.timedelta(days=1) # tomorrow by default playlist_duration = 6 * 60 * 60 # six hours, in seconds mediadirs = ListDict() def main(): parse_config() get_media_dirs(mediadir) for single_date in daterange(from_date, to_date): if verbose: print("Processing date", single_date) daylist = build_day(single_date) daylist_path = playlistdir + "/" + channel + "/" + single_date.strftime("%Y") + "/" + single_date.strftime("%m") + "/" + single_date.strftime("%d") + "/" if not os.path.isdir(daylist_path): os.makedirs(daylist_path) daylist_file = single_date.strftime("%Y") + "-" + single_date.strftime("%m") + "-" + single_date.strftime("%d") + ".json" with open(daylist_path + daylist_file, "w") as file: file.write(daylist) if verbose: print('wrote playlist file', daylist_path + daylist_file) def parse_config(): global mediadir, playlistdir, channel, verbose, from_date, to_date, playlist_duration parser = argparse.ArgumentParser( prog="mt_clockwheel", description="a simple clockwheel playlist generator for ffplayout", epilog="brought to you by your friends at Mountain Town Technology and Community Media Network" ) parser.add_argument('-d', '--directory', dest='mediadir', help='root directory for media library') parser.add_argument('-p', '--playlists', dest='playlistdir', help='root directory for playlists') parser.add_argument('-c', '--channel', dest='channel', help='channel name to generate playlists for') parser.add_argument('-v', '--verbose', action='store_true', help='provide verbose output logging') parser.add_argument('-f', '--from', dest='from_date', help='first date to generate playlist for') parser.add_argument('-t', '--to', dest='to_date', help='last date to generate playlist for') parser.add_argument('-l', '--length', dest='playlist_duration', help='duration (in seconds) for each playlist - if a factor of 24 * 60 * 60 then playlist will be duplicated to fill 24 hours') args = parser.parse_args() if args.verbose: verbose = True if args.mediadir: mediadir = args.mediadir if args.playlistdir: playlistdir = args.playlistdir if args.channel: channel = args.channel if args.from_date: from_date = datetime.datetime.strptime(args.from_date, "%Y-%m-%d").date() if args.to_date: to_date = datetime.datetime.strptime(args.to_date, "%Y-%m-%d").date() if args.playlist_duration: playlist_duration = args.playlist_duration if verbose: print("Arguments parsed, config:") print("mediadir:", mediadir) print("playlistdir:", playlistdir) print("channel:", channel) print("from_date:", from_date) print("to_date:", to_date) def get_media_dirs(rootdir): for file in os.listdir(rootdir): if file == "Commercials": pass elif file == "Television": get_media_dirs(os.path.join(rootdir, file)) else: d = os.path.join(rootdir, file) if os.path.isdir(d): if verbose: print("adding " + d + " to media dirs") mediadirs.add_item(d) def build_day(this_date): d = {} # empty dict that will become our JSON output d["channel"] = channel d["date"] = this_date.strftime("%Y-%m-%d") d["program"] = [] # empty list to populate with programs total_time = 0 get_commercial = False while total_time < playlist_duration: entry, length = get_playlist_entry(get_commercial) d["program"].append(entry) total_time += length if length > 0: get_commercial = not get_commercial if verbose: print(' added program:', json.dumps(entry), length) day_length = 24 * 60 * 60 if day_length % playlist_duration == 0: if verbose: print("looping playlist...") iterations = (day_length / playlist_duration) iteration = d["program"] # snapshot of the unit of iteration looped = [] if verbose: print("iteration: ", json.dumps(iteration, indent=4)) print("iterations: ", iterations) while iterations > 0: looped.extend(iteration) iterations -= 1 d["program"] = looped playlist = json.dumps(d) if verbose: print("playlist json:", playlist) return playlist def get_playlist_entry(get_commercial): exts = ['*.mp4', '*.webm', '*.mov'] entry_dir = mediadir + '/Commercials' if get_commercial else mediadirs.choose_random_item() media_files = ListDict() found_media = [f for ext in exts for f in glob(os.path.join(entry_dir, '**', ext), recursive=True)] for d in found_media: media_files.add_item(d) this_file = media_files.choose_random_item() if media_files.length() > 0 else "" if this_file == "": if verbose: print('cannot select file from empty folder') return {}, 0 # get out of this oopsie situation and try again if verbose: print('selected file: ' + this_file) # probe file for details metadata = json.loads(json.dumps(probe(this_file), sort_keys=True, indent=4)) if verbose: print('file metadata: ', metadata) print('format: ', metadata['format']) duration = int(float(metadata['format']['duration_secs'])) if "duration_secs" in metadata['format'].keys() else int(float(metadata["format"]["duration"])) if "duration" in metadata['format'].keys() else 0 if duration == 0: if verbose: print('cannot parse duration of ' + this_file) return {}, 0 # cannot parse duration, so skip this file entry = { "in": 0, "out": duration, "duration": duration, "source": this_file } if get_commercial: entry["category"] = "advertisement" length = entry["duration"] return entry, length def probe(file): cmd = ['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_format', '-show_streams', f"{file}"] p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() if len(out) > 0: result = json.loads(out) else: result = {} if err: print("===err===") print(err) if verbose: print('ffprobe result: ', result) return result # generator function to yield single dates from a date range def daterange(start_date, end_date): for n in range(int((end_date - start_date).days) + 1): # adding 1 to make the range inclusive yield start_date + datetime.timedelta(n) if __name__ == "__main__": main()