#!/usr/bin/env python3 # sortashuffle.py # REQUIREMENTS # - Must be run as administrator on Windows # - All episodes of each show must be in one folder; no season folders! # USAGE # 0. Copy this script to the playlist folder # 1. Modify the TARGET variable below # 2. Modify the SOURCES variable below # 3. Run the script as administrator # # To add/remove shows or regenerate playlist, # delete everything in TARGET (except this script) # and re-run the script. import os import random # Must be in format "C://Dir1//Dir2" TARGET = "" SOURCES = ["", ""] def collect_showlist(sources): """Collect show dirs and associated episodes in a nested list. Parameters: sources (list of str): List of paths to scan for episodes. Each path should represent a distinct show, with all episodes in one flat directory. Returns: list of list of str: Nested list structure where: - each sub-list represents a show. - elements of sub-lists represent episodes. Notes: - Episode filenames are returned in arbitrary filesystem order. - No filtering is performed. All files are included. - Does not perform recursive directory scanning """ return [[f for f in os.listdir(source)] for source in sources] def calculate_weights(showlist): """Calculate the weights to be used for selecting a show at random. The number of episodes is used as the weight because shows with more episodes need to be selected more often in order to maintain an even spread of each show across the whole playlist. Parameters: showlist (list of list of str): Nested list structure where: - each sub-list represents a show. - elements of sub-lists represent episodes. Returns: list of int: List where: - each element corresponds to a show in `showlist' - each element represents the remaining number of episodes. """ return [len(show) for show in showlist] def select_show(showlist, weightlist): """Select a show at random, accounting for weight. Parameters: showlist (list of list of str): Nested list structure where: - each sub-list represents a show. - elements of sub-lists represent episodes. weightlist (list of int): List where: - each element corresponds to a show in `showlist' - each element represents the remaining number of episodes. Returns: int: The list index of a show in the showlist. """ return random.choices(range(len(showlist)), weights=weightlist, k=1)[0] def shuffle(showlist, weightlist): """Shuffle the playlist. Parameters: showlist (list of list of str): Nested list structure where: - each sub-list represents a show. - elements of sub-lists represent episodes. weightlist (list of int): List where: - each element corresponds to a show in `showlist' - each element represents the remaining number of episodes. Returns: shuffled (list of str): Flat list of shuffled episodes. """ shuffled = [] while any(showlist): selection = select_show(showlist, weightlist) shuffled.append(SOURCES[selection] + "//" + showlist[selection].pop(0)) weightlist[selection] = len(showlist[selection]) return shuffled def deploy_symlinks(shuffled): """Deploy episode symlinks. Creates a symlink for each episode in `shuffled'. Uses the `count' in the for loop as filename to keep shuffled order. Parameters: shuffled (list of str): Flat list of shuffled episodes. Returns: count (int): The number of symlinks deployed. """ count = 0 for episode in shuffled: os.symlink(episode, TARGET + "//" + str(count)) count += 1 return count def deploy_index(shuffled): """Deploy playlist index. Create a file namned _PLAYLIST_INDEX.txt containing the shuffled list of episodes. Parameters: shuffled (list): Flat list of shuffled episodes. Returns: index (str): Full path and filename of _PLAYLIST_INDEX.txt. """ index = open(TARGET + "//" + "_PLAYLIST_INDEX.txt", 'w', encoding='utf-8') index.write("\n".join(shuffled)) index.close() return index def main(): """Shuffle shows but keeps episodes in order.""" SHOWLIST = collect_showlist(SOURCES) WEIGHTLIST = calculate_weights(SHOWLIST) SHUFFLED = shuffle(SHOWLIST, WEIGHTLIST) deploy_symlinks(SHUFFLED) deploy_index(SHUFFLED) return 0 if __name__ == "__main__": main()