diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml
new file mode 100644
index 00000000..46c11abe
--- /dev/null
+++ b/.idea/dbnavigator.xml
@@ -0,0 +1,454 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Python3_Projects/Organize_Media/Organize_Media.py b/Python3_Projects/Organize_Media/Organize_Media.py
index b9dc4533..3ca47318 100644
--- a/Python3_Projects/Organize_Media/Organize_Media.py
+++ b/Python3_Projects/Organize_Media/Organize_Media.py
@@ -3,71 +3,18 @@
import os
import json
from tkinter import *
-from tkinter.ttk import Progressbar, Style, Separator
+from tkinter.ttk import Progressbar, Style
from PIL import Image, ImageTk
-from imdb import IMDb
from copy import deepcopy
import encodings.idna
+import sys
from settings.general_functions import tv_show_ep_from_file_name, tv_show_ep_from_folder_structure, \
initcap_file_name, rename_all_media_in_directory, get_media_title
from settings.mo_functions import get_downloads_or_media_path, save_paths_to_json
-
-
-class CreateToolTip(object):
- """
- create a tooltip for a given widget
- """
- def __init__(self, widget, text='widget info', bg='#ffffff', fg='#000000'):
- self.waittime = 500 #miliseconds
- self.wraplength = 180 #pixels
- self.widget = widget
- self.text = text
- self.bg = bg
- self.fg = fg
- self.widget.bind("", self.enter)
- self.widget.bind("", self.leave)
- self.widget.bind("", self.leave)
- self.id = None
- self.tw = None
-
- def enter(self, event=None):
- self.schedule()
-
- def leave(self, event=None):
- self.unschedule()
- self.hidetip()
-
- def schedule(self):
- self.unschedule()
- self.id = self.widget.after(self.waittime, self.showtip)
-
- def unschedule(self):
- id = self.id
- self.id = None
- if id:
- self.widget.after_cancel(id)
-
- def showtip(self, event=None):
- x = y = 0
- x, y, cx, cy = self.widget.bbox("insert")
- x += self.widget.winfo_rootx() + 25
- y += self.widget.winfo_rooty() + 20
- # creates a toplevel window
- self.tw = Toplevel(self.widget)
- # Leaves only the label and removes the app window
- self.tw.wm_overrideredirect(True)
- self.tw.wm_geometry("+%d+%d" % (x, y))
- label = Label(self.tw, text=self.text, justify='left',
- background=self.bg, foreground=self.fg, relief='solid', borderwidth=1,
- wraplength=self.wraplength)
- label.pack(ipadx=1)
-
- def hidetip(self):
- tw = self.tw
- self.tw = None
- if tw:
- tw.destroy()
+from components.checklist import CheckList
+from components.tooltip import CreateToolTip
+from components.menu_bar import MenuBar
class Organize(Tk):
@@ -75,7 +22,7 @@ class Organize(Tk):
def __init__(self):
Tk.__init__(self)
self.title('Media Organizer')
- self.iconbitmap('Images/organize_media.ico')
+ # self.iconbitmap('Images/organize_media.ico')
self.colors = {
'main': '#%02x%02x%02x' % (20, 20, 20),
@@ -88,13 +35,13 @@ def __init__(self):
font_default = 'Constantia'
self.fonts = {
- 'xxsmall': (font_default, 11),
- 'xsmall': (font_default, 12),
- 'small': (font_default, 13),
- 'medium': (font_default, 14),
- 'large': (font_default, 18),
- 'xlarge': (font_default, 22),
- 'xxlarge': (font_default, 40),
+ 'xxsmall': (font_default, 11, 'normal'),
+ 'xsmall': (font_default, 12, 'normal'),
+ 'small': (font_default, 13, 'normal'),
+ 'medium': (font_default, 14, 'normal'),
+ 'large': (font_default, 18, 'normal'),
+ 'xlarge': (font_default, 22, 'normal'),
+ 'xxlarge': (font_default, 40, 'normal'),
}
self.config(bg=self.colors['main'])
@@ -109,7 +56,6 @@ def __init__(self):
self.configure(bg=self.colors['main'])
''' Filter media will contain a dictionary like:
{path: {file_name: name, title: title, kind: kind, ...}}'''
- self.filtered_media = {}
self.all_media_info = {}
self.imdb_info = {}
self.disable_buttons = False
@@ -119,27 +65,27 @@ def __init__(self):
self.starting_height = self.winfo_height()
# Frames
- self.left_frame = Frame(self, bg=self.colors['main'])
- self.status_bar = Canvas(self, bg=self.colors['sub'], bd=0, highlightthickness=0,
+ self.main_frame = Frame(self, bg=self.colors['main'], bd=1, relief=RIDGE, name='main_frame')
+ self.left_frame = Frame(self.main_frame, bg=self.colors['main'], bd=1, relief=RIDGE)
+ self.status_bar = Canvas(self.main_frame, bg=self.colors['main'], bd=0, highlightthickness=0,
width=self.starting_width, height=0, relief=SUNKEN)
self.status_bar.pack(side=BOTTOM, fill=X)
self.left_frame.pack(side=LEFT, fill=Y, ipadx=14, ipady=14)
- self.middle_canvas = Canvas(self, bg=self.colors['sub'], bd=2, highlightthickness=0, relief=SUNKEN)
- self.canvas_frame = Frame(self.middle_canvas, bg=self.colors['sub'])
+ self.middle_canvas = Canvas(self.main_frame, bg=self.colors['main'], highlightthickness=0)
+ self.canvas_frame = Frame(self.middle_canvas, bg=self.colors['main'])
self.bind('', self.mid_canvas_dim)
self.middle_canvas.create_window((0, 0), window=self.canvas_frame, anchor=NW)
- y_scroll_bar = Scrollbar(self, command=self.middle_canvas.yview, orient=VERTICAL)
- x_scroll_bar = Scrollbar(self, command=self.middle_canvas.xview, orient=HORIZONTAL)
+ y_scroll_bar = Scrollbar(self.main_frame, command=self.middle_canvas.yview, orient=VERTICAL)
+ x_scroll_bar = Scrollbar(self.main_frame, command=self.middle_canvas.xview, orient=HORIZONTAL)
self.middle_canvas.config(yscrollcommand=y_scroll_bar.set, xscrollcommand=x_scroll_bar.set)
y_scroll_bar.pack(side=RIGHT, fill=Y)
x_scroll_bar.pack(side=BOTTOM, fill=X)
self.middle_canvas.pack(side=LEFT, fill=BOTH, expand=True, ipadx=4)
self.bind_all("", self._on_mousewheel)
-
self.image_config = {
# Used by the buttons on the left of the main app
- 'Locate Media': {'image': 'dir.png', 'image_size': (64, 64)},
+ 'Locate Media': {'image': 'dir.png', 'image_size': (36, 36)},
'Select Media': {'image': 'filter.png', 'image_size': (64, 64)},
'Organize': {'image': 'organize_media.png', 'image_size': (64, 64)},
# Used later by the filter window
@@ -152,15 +98,23 @@ def __init__(self):
image = img.resize(self.image_config[label_text]['image_size'], Image.ANTIALIAS)
self.image_config[label_text]['image_obj'] = ImageTk.PhotoImage(image)
+ self.image_config = {k: v['image_obj'] for k, v in self.image_config.items()}
+
note_text = 'If you have a Movies/TV Shows folder with a name other than "Movies" or "TV Shows",' \
' in your media folder, you should rename them. Casing matters!\n\n' \
'If you do not have the folders, they will be created for you.'
+ # Menu
+ menu = MenuBar(self, font=self.fonts['small'], bg=self.colors['main'], fg=self.colors['font'],
+ activebackground=self.colors['alt'], activeforeground=self.colors['font'])
+ tab = menu.add_menu_tab(text='File')
+ tab.add_menu_item(text='Locate Media', image=self.image_config['Locate Media'], command=self.locate_media)
+ tab.add_separator()
+ tab.add_menu_item(text='Exit', command=self.quit)
+ self.main_frame.pack(side=BOTTOM, fill=BOTH, expand=True)
+
# Buttons
self.buttons_config = {
- 'Locate Media': {'command': self.locate_media,
- 'tooltip': 'Select your downloads folder and media folder; Your media folder will contain'
- ' your "Movies" and "TV Shows" folders'},
'Select Media': {'command': self.on_press_select_media,
'tooltip': 'Gathers a list of media files from your downloads folder, and'
' allows you to filter which files to organize'},
@@ -173,9 +127,11 @@ def __init__(self):
padding_frame = Frame(self.left_frame, bg=self.colors['main'])
padding_frame.pack(side=TOP, padx=10, pady=10, fill=X)
# Button
- button = Button(padding_frame, image=self.image_config[label_text]['image_obj'], command=cfg['command'],
- cursor="hand2", bg=self.colors['main'], relief=FLAT, anchor=CENTER)
- button.pack(fill=X)
+ button = Button(padding_frame, image=self.image_config[label_text], command=cfg['command'],
+ activebackground=self.colors['sub'], activeforeground=self.colors['font'],
+ bg=self.colors['main'], cursor="hand2", highlightthickness=0, bd=0, relief=FLAT,
+ anchor=CENTER)
+ button.pack()
self.buttons_config[label_text]['button'] = button
# Button label
label = Label(padding_frame, text=label_text, font=self.fonts['xsmall'],
@@ -254,109 +210,112 @@ def mid_canvas_dim(self, event):
def _on_mousewheel(self, event):
x, y = self.winfo_pointerxy()
- canvas = self.winfo_containing(x, y)
- widget_path = str(canvas.master)
- if widget_path == '.' or widget_path.startswith('.!canvas2') or widget_path.startswith('.filterwindow'):
+ widget = self.winfo_containing(x, y)
+ widget_path = str(widget)
+ if widget_path.startswith('.main_frame.!canvas2'):
self.middle_canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def get_media_info_from_paths(self, paths):
""" Gets all media information from media files in the given paths
+ Output sample:
+ '/USER/Downloads': {
+ 'TV Shows': {'House': {'Season 1': {'C:/USER/Downloads/house.s01e01.avi': House - S01E01'},
+ 'Season 2': {'C:/USER/Downloads/house.s02e01.avi': House - S02E01'}}}
+ 'Movies': {'C:/USER/Downloads/thor.avi': Thor'}
+ }
+
:param paths: A list of paths to media files
:return: A dictionary of information about each file
"""
- self.progress_bar_appear()
- self.progress_bar['maximum'] = len([file for path in paths
- for x, y, files in os.walk(path)
- for file in files
- if file.split('.')[-1] in self.media_extensions])
- self.progress_bar['value'] = 0
- self.s.configure(style=self.style, text='Getting media info...')
- file_info = {}
- # Get a list of files to gather info for, and extract info from the local file
- for folder_path in paths:
- file_info[folder_path] = {}
- for path, folders, files in sorted(os.walk(folder_path)):
- for file in sorted(files):
- current_file_path = os.path.join(path, file)
- extension = file.split('.')[-1]
- # If its a media file
- if extension in self.media_extensions and os.path.isfile(current_file_path):
- renamed_file = initcap_file_name(file)
- file_folder_path = os.path.dirname(current_file_path)
- bottom_folder = os.path.basename(file_folder_path)
- # Split the file's path by the folder_path to get the folders inside of the main folder
- if folder_path != file_folder_path:
- split_path = [x for x in path.split(folder_path) if x]
- # This is just making sure the string doesn't start with a slash
- top_folder = split_path[-1] if split_path[-1][0] not in ['\\', '/'] \
- else split_path[-1][1:]
- top_folder = top_folder.split('\\')[0].split('/')[0]
- top_folder_title = str(get_media_title(None,
- initcap_file_name(top_folder.replace('.', ' ')))
- ).title()
- else:
- bottom_folder = None
- top_folder_title = None
-
- # Use the Season / Episode extracted based on the path,
- # if it could not be extracted from the file name
- tv_show_episode, season, episode = tv_show_ep_from_file_name(renamed_file)
- s, e = tv_show_ep_from_folder_structure(current_file_path)
- t = None
- if not season and s:
- season = s
- t = top_folder_title
- if not episode and e:
- episode = e
- t = top_folder_title
-
- # Set the kind and season based on the season/episode extracted
- if tv_show_episode or (season or season == 0) or (episode or episode == 0):
- kind = 'TV Show'
- # Assume it is the first season if there is no season extracted,
- # but an episode was extracted, and there is a top folder
- if not (season or season == 0) and (episode or episode == 0) and t:
- season = 1
- # If the folder containing the show has 'extras' in the name,
- # then it is safe to assume that this is an extra episode
- if 'extras' in str(bottom_folder).lower():
- season = -1 # Extras are identified as season -1
- # Assume it is a movie if it extracted only an episode; i.e. Star Wars Episode 1
- elif not (season or season == 0) and (episode or episode == 0) and not t:
+
+ def extract_info():
+ file_info = {}
+ for folder_path in paths:
+ file_info[folder_path] = {}
+ for path, folders, files in sorted(os.walk(folder_path)):
+ for file in sorted(files):
+ file_path = os.path.join(path, file)
+ extension = file.split('.')[-1]
+ # If its a media file
+ if extension in self.media_extensions and os.path.isfile(file_path):
+ renamed_file = initcap_file_name(file)
+ file_folder_path = os.path.dirname(file_path)
+ bottom_folder = os.path.basename(file_folder_path)
+ # Split the file's path by the folder_path to get the folders inside of the main folder
+ if folder_path != file_folder_path:
+ split_path = [x for x in path.split(folder_path) if x]
+ # This is just making sure the string doesn't start with a slash
+ top_folder = split_path[-1] if split_path[-1][0] not in ['\\', '/'] \
+ else split_path[-1][1:]
+ top_folder = top_folder.split('\\')[0].split('/')[0]
+ top_folder_title = str(get_media_title(None,
+ initcap_file_name(top_folder.replace('.', ' ')))
+ ).title()
+ else:
+ bottom_folder = None
+ top_folder_title = None
+
+ # Use the Season / Episode extracted based on the path,
+ # if it could not be extracted from the file name
+ tv_show_episode, season, episode = tv_show_ep_from_file_name(renamed_file)
+ s, e = tv_show_ep_from_folder_structure(file_path)
+ season = season or s
+ episode = episode or e
+
+ # Set the kind and season based on the season/episode extracted
+ if (season or season == 0) or (episode or episode == 0):
+ kind = 'TV Show'
+ # Assume it is the first season if there is no season extracted,
+ # but an episode was extracted, and there is a top folder
+ if not (season or season == 0) and (episode or episode == 0) and top_folder_title:
+ season = 1
+ # If the folder containing the show has 'extras' in the name,
+ # then it is safe to assume that this is an extra episode
+ if 'extras' in str(bottom_folder).lower():
+ season = -1 # Extras are identified as season -1
+ # Assume it is a movie if it extracted only an episode; i.e. Star Wars Episode 1
+ elif not (season or season == 0) and (episode or episode == 0) and not top_folder_title:
+ kind = 'Movie'
+ else:
kind = 'Movie'
- else:
- kind = 'Movie'
-
- title = get_media_title(tv_show_episode, renamed_file)
- years = re.findall(r'20\d\d|19\d\d', file)
- year = int(years[0]) if years else None
-
- # Set the title for the Show/Movie based the show/season/episode info extracted
- if kind == 'TV Show':
- if (season or season == 0) and season != -1 and (episode or episode == 0):
- title = top_folder_title if top_folder_title else title
- renamed_file_name = f'{title} - ' \
- f'S{ "0" + str(season) if season < 10 else season}' \
- f'E{ "0" + str(episode) if episode < 10 else episode}'
+
+ title = get_media_title(tv_show_episode, renamed_file)
+ years = re.findall(r'20\d\d|19\d\d', file)
+ year = int(years[0]) if years else None
+ title_w_year = f'{title} ({year})' if year else title
+
+ # Set the title for the Show/Movie based the show/season/episode info extracted
+ if kind == 'TV Show':
+ if (season or season == 0) and season != -1 and (episode or episode == 0):
+ e_str = ''
+ episode = episode if isinstance(episode, list) else [episode]
+ for e in episode:
+ e_str += f'E{"0" + str(e) if e < 10 else e}'
+ title = top_folder_title if not tv_show_episode else title
+ renamed_file_name = f'{title} - ' \
+ f'S{"0" + str(season) if season < 10 else season}' + e_str
+ else:
+ renamed_file_name = renamed_file.split('.')[0]
else:
- renamed_file_name = renamed_file.split('.')[0]
- else:
- renamed_file_name = title
-
- file_info[folder_path][current_file_path] = {
- 'title': title, 'renamed_file_name': renamed_file_name, 'file_name': file,
- 'file_path': current_file_path, 'top_folder_title': top_folder_title,
- 'kind': kind, 'year': year, 'season': season, 'episode': episode}
- self.progress_bar['value'] += 1
- self.s.configure(style=self.style,
- text=f'Got info for {file}\nIn the {folder_path} directory\n')
- if not file_info[folder_path]:
- del file_info[folder_path]
-
- top_folder_dict = {}
- for folder_path, info in file_info.items():
- for file_path, i in info.items():
+ renamed_file_name = title_w_year
+
+ file_info[folder_path][file_path] = {'kind': kind, 'year': year,
+ 'title': title_w_year, 'season': season,
+ 'top_folder_title': top_folder_title,
+ 'renamed_file': f'{renamed_file_name}.{extension}'}
+ self.progress_bar['value'] += 1
+ self.s.configure(style=self.style,
+ text=f'Got info for {file}\nIn the {folder_path} directory\n')
+ if not file_info[folder_path]:
+ del file_info[folder_path]
+
+ update_dict(file_info[folder_path])
+ return file_info
+
+ def update_dict(dictionary):
+ top_folder_dict = {}
+ for file_path, i in dictionary.items():
if i['top_folder_title']:
if i['top_folder_title'] not in top_folder_dict:
top_folder_dict[i['top_folder_title']] = {'title_list': [i['title']],
@@ -370,250 +329,123 @@ def get_media_info_from_paths(self, paths):
if i['year']:
top_folder_dict[i['top_folder_title']]['year'] = i['year']
- for top_folder, info in top_folder_dict.items():
- top_folder_dict[top_folder]['num_unique_titles'] = len(info['title_list'])
- for folder_path, info in file_info.items():
- for file_path, i in info.items():
+ for top_folder, info in top_folder_dict.items():
+ top_folder_dict[top_folder]['num_unique_titles'] = len(info['title_list'])
+ for file_path, i in dictionary.items():
if i['top_folder_title']:
- file_info[folder_path][file_path]['kind'] = top_folder_dict[i['top_folder_title']]['kind']
- file_info[folder_path][file_path]['year'] = top_folder_dict[i['top_folder_title']]['year']
- if not file_info[folder_path][file_path]['season'] \
- and file_info[folder_path][file_path]['kind'] == 'TV Show':
- file_info[folder_path][file_path]['season'] = -1
- file_info[folder_path][file_path]['renamed_file_name'] = initcap_file_name(i['file_name']
- ).split('.')[0]
+ dictionary[file_path]['kind'] = top_folder_dict[i['top_folder_title']]['kind']
+ dictionary[file_path]['year'] = top_folder_dict[i['top_folder_title']]['year']
+ if not dictionary[file_path]['season'] \
+ and dictionary[file_path]['kind'] == 'TV Show':
+ dictionary[file_path]['season'] = -1
+ dictionary[file_path]['renamed_file_name'] = initcap_file_name(os.path.basename(file_path)
+ ).split('.')[0]
if top_folder_dict[i['top_folder_title']]['num_unique_titles'] > 1:
- file_info[folder_path][file_path]['title'] = i['top_folder_title']
- del top_folder_dict
- self.progress_complete('Gathered Media!')
- return file_info
-
- def media_files_info(self, folder_paths=[]):
- """ Gets information about each media file in a path, from IMDb.
+ dictionary[file_path]['title'] = i['top_folder_title']
+ del top_folder_dict
+
+ def convert_dict(dictionary):
+ converted_dict = {}
+ for folder, file in dictionary.items():
+ converted_dict[folder] = {'TV Shows': {}, 'Movies': {}}
+ for file_path, i in file.items():
+ alias = i['renamed_file']
+ title = i['title']
+ season = 'Extras' if i['season'] == -1 else f"Season {i['season']}"
+ if i['kind'] == 'TV Show':
+ if title not in converted_dict[folder]['TV Shows']:
+ converted_dict[folder]['TV Shows'][title] = {season: {file_path: alias}}
+ elif season not in converted_dict[folder]['TV Shows'][title]:
+ converted_dict[folder]['TV Shows'][title][season] = {file_path: alias}
+ elif file_path not in converted_dict[folder]['TV Shows'][title][season]:
+ converted_dict[folder]['TV Shows'][title][season][file_path] = alias
+ if i['kind'] == 'Movie':
+ converted_dict[folder]['Movies'][file_path] = alias
+ return converted_dict
+
+ def remove_empty_items(dictionary, new_dict):
+ for key, value in dictionary.items():
+ if value and isinstance(value, dict):
+ new_dict[key] = {}
+ remove_empty_items(dictionary[key], new_dict[key])
+ elif value:
+ new_dict[key] = value
- Output sample:
- 'C:/USER/Downloads': {
- 'C:/USER/Downloads/house.s01e01.avi': {
- 'title': 'House (2004)',
- 'title': 'House',
- 'renamed_file_name': 'House - S01E01 - Pilot',
- 'kind': 'TV Show',
- 'season': 1,
- 'episode': 1,
- 'genres': ['Drama', 'Comedy'],
- 'file_name': 'house.s01e01.avi'}}
-
- :param folder_paths: The path to your media files
- :return: A dictionary of information about each file
- """
- self.all_media_info = self.get_media_info_from_paths(folder_paths)
-
- if not self.all_media_info:
- self.progress_bar_appear()
- self.progress_complete('\nNo Media Detected...\n')
- return 'No Media'
-
- else:
- self.filter_window(self.all_media_info)
-
- def filter_window(self, all_media):
- """ The filter window that appears to filter the media files to be sorted.
-
- :param all_media: The dictionary of all the media to filter
- :return: Filtered media
- """
- self.filtered_media = deepcopy(all_media)
+ self.progress_bar_appear()
+ self.progress_bar['maximum'] = len([file for path in paths
+ for x, y, files in os.walk(path)
+ for file in files
+ if file.split('.')[-1] in self.media_extensions])
+ self.progress_bar['value'] = 0
+ self.s.configure(style=self.style, text='Getting media info...')
+ # Gets info from media files
+ file_info = extract_info()
+ # Converts the dictionary to be compatible with checklist
+ file_info = convert_dict(file_info)
+ file_info_new = {}
+ remove_empty_items(file_info, file_info_new)
+ self.progress_complete('Gathered Media!')
+ return file_info_new
- def upon_select(widget):
- d = widget['destination']
- fp = widget['file_path']
- # Add or remove files from list when they are toggled
- if widget['button'].var.get():
- if fp not in self.filtered_media[d]:
- self.filtered_media[d][fp] = all_media[d][fp]
- widget['button']['bg'] = self.colors['sub']
- else:
- if fp in self.filtered_media[d]:
- del self.filtered_media[d][fp]
- widget['button']['bg'] = self.colors['main']
+ def recursively_organize_shows_and_movies(self, delete_folders=True):
- def toggle_all(x):
- button, kind, files_dict = x
- deselect_image = self.image_config['deselect']['image_obj']
- select_image = self.image_config['select']['image_obj']
- # Toggle each item in the dictionary based on the toggle
- toggle = True if str(button['image']) == str(select_image) else False
- for file, widget in files_dict.items():
- widget['button'].var = BooleanVar(value=toggle)
- widget['button']['variable'] = widget['button'].var
- upon_select(widget)
- if toggle:
- button['image'] = deselect_image
- if self.toggle_all_buttons[kind] == button:
- for title, b in self.toggle_title_buttons[kind].items():
- b['image'] = deselect_image
- else:
- button['image'] = select_image
- if self.toggle_all_buttons[kind] == button:
- for title, b in self.toggle_title_buttons[kind].items():
- b['image'] = select_image
-
- if self.toggle_all_buttons[kind] != button:
- off_title_buttons = [str(btn['image']) for t, btn in self.toggle_title_buttons[kind].items()
- if str(btn['image']) == str(select_image)]
- if off_title_buttons:
- self.toggle_all_buttons[kind]['image'] = select_image
+ def inc_max_progress(d):
+ """ Helper callback function to increment the max progress bar value for each file """
+ for k, v in d.items():
+ if not isinstance(v, dict):
+ self.progress_bar['maximum'] += 1
else:
- self.toggle_all_buttons[kind]['image'] = deselect_image
-
- def collapse_show(checklist_frame):
- if checklist_frame.winfo_viewable():
- checklist_frame.pack_forget()
- else:
- checklist_frame.pack(fill=X)
-
- def create_checklist(frame, files):
- kind_mapping = {'TV Show': 'TV Shows', 'Movie': 'Movies'}
- self.toggle_all_buttons = {k: {} for k in kind_mapping.keys()}
- self.toggle_title_buttons = {k: {} for k in kind_mapping.keys()}
- for folder_path, info in files.items():
- destination_frame = Frame(frame, bg=self.colors['main'], bd=1, relief=RIDGE)
- destination_frame.pack(side=LEFT, padx=4, pady=4, anchor=N)
- destination_title = Label(destination_frame, text=folder_path, anchor=NW, justify=LEFT,
- bg=self.colors['main'], fg=self.colors['font'], font=self.fonts['medium'])
- destination_title.pack(side=TOP, fill=X, anchor=W)
- Separator(destination_frame).pack(side=TOP, fill=X)
- sections = sorted(list(set([i['kind'] for f, i in info.items()])), reverse=True)
- for kind in sections:
- kind_dict = dict()
- if sections.index(kind) > 0:
- # Add a separator between shows and movies
- Separator(destination_frame).pack(fill=X, pady=8)
- media_type_frame = Frame(destination_frame, bg=self.colors['main'])
- media_type_frame.pack(side=TOP, fill=X, anchor=NW)
- # Toggle everything for each 'Kind'
- self.toggle_all_buttons[kind] = Button(media_type_frame, cursor='hand2',
- image=self.image_config['deselect']['image_obj'],
- relief=FLAT, bg=self.colors['main'])
- self.toggle_all_buttons[kind].pack(side=LEFT, anchor=NW)
- media_type_label = Label(media_type_frame, text=kind_mapping[kind], font=self.fonts['small'], anchor=NW,
- width=50, bg=self.colors['main'], fg=self.colors['font'], justify=LEFT)
- media_type_label.pack(side=LEFT, fill=X)
- media_content_frame = Frame(destination_frame, bg=self.colors['main'], bd=2, relief=SUNKEN)
- media_content_frame.pack(side=TOP, fill=X, anchor=NW)
- if kind == 'Movie':
- movie_frame = Frame(destination_frame, bg=self.colors['main'], bd=2, relief=SUNKEN)
- movie_frame.pack(side=TOP, fill=X, anchor=NW)
- for t in sorted(list(set([v['title'] for k, v in info.items() if v['kind'] == kind]))):
- dictionary = dict()
- if kind == 'TV Show':
- show_frame = Frame(media_content_frame, bg=self.colors['main'])
- show_frame.pack(side=TOP, fill=X, anchor=NW)
- show_title_frame = Frame(show_frame, bg=self.colors['special'])
- show_title_frame.pack(side=TOP, fill=X, anchor=NW)
- # Toggle everything for each TV Show title
- self.toggle_title_buttons[kind][t] = Button(show_title_frame,
- image=self.image_config['deselect']['image_obj'],
- text='deselect', cursor='hand2', relief=FLAT,
- bg=self.colors['main'])
- self.toggle_title_buttons[kind][t].pack(side=LEFT, fill=Y)
- show_title_button = Button(show_title_frame, text=t, cursor='hand2',
- font=self.fonts['small'], anchor=W, bd=2, relief=RAISED,
- bg=self.colors['main'], fg=self.colors['font'],
- justify=LEFT)
- show_title_button.pack(side=LEFT, fill=X, expand=True, anchor=W, padx=1, pady=1)
- show_checklist_frame = Frame(show_frame, bg=self.colors['main'], bd=2, relief=SUNKEN)
- show_checklist_frame.pack(side=TOP, fill=X, anchor=NW)
- show_title_button['command'] = lambda x=show_checklist_frame: collapse_show(x)
- show_checklist_frame.pack_forget()
- season_frames = {}
- try:
- for s in sorted(list(set([v['season'] for k, v in info.items() if v['title'] == t]))):
- season_frames[s] = Frame(show_checklist_frame, bg=self.colors['main'])
- season_frames[s].pack(side=TOP, fill=X, anchor=NW)
- season = 'Season ' + str(s) if s != -1 else 'Extras'
- season_label = Label(season_frames[s], text=season, font=self.fonts['small'],
- bg=self.colors['alt'], fg=self.colors['font'])
- season_label.pack(side=TOP, fill=X, padx=1, pady=1)
- except Exception as e:
- print(t)
- print([v['season'] for k, v in info.items() if v['title'] == t])
- raise Exception(e)
-
- for file_path, i in {f: i for f, i in info.items() if i['title'] == t}.items():
- dictionary[file_path] = i
- if kind == 'TV Show':
- dictionary[file_path]['button'] = Checkbutton(season_frames[i['season']])
- else:
- dictionary[file_path]['button'] = Checkbutton(movie_frame)
- dictionary[file_path]['button'].config(text=i['renamed_file_name'], fg=self.colors['font'],
- onvalue=True, offvalue=False, anchor=NW,
- bg=self.colors['sub'], font=self.fonts['xsmall'],
- selectcolor=self.colors['main'])
- dictionary[file_path]['button'].var = BooleanVar(value=True)
- dictionary[file_path]['button']['variable'] = dictionary[file_path]['button'].var
- dictionary[file_path]['button']['command'] = lambda w=dictionary[file_path]: upon_select(w)
- dictionary[file_path]['button'].pack(side=TOP, fill=X, anchor=NW, pady=1, padx=1)
- dictionary[file_path]['destination'] = folder_path
- kind_dict[file_path] = dictionary[file_path]
- if kind == 'TV Show':
- self.toggle_title_buttons[kind][t]['command'] = lambda d=(self.toggle_title_buttons[kind][t]
- , kind, dictionary): toggle_all(d)
- self.toggle_all_buttons[kind]['command'] = lambda d=(self.toggle_all_buttons[kind], kind,
- kind_dict): toggle_all(d)
-
- main_frame = Frame(self.canvas_frame, bg=self.colors['sub'], name='filterwindow')
- main_frame.pack(fill=BOTH)
- create_checklist(main_frame, files=all_media)
+ inc_max_progress(d[k])
+
+ def move_files(file_dict, path=[], level=0):
+ """ Callback function to moves the files """
+ level += 1
+ for file_path, new_file_name in file_dict.items():
+ file_folder = os.path.dirname(file_path)
+ if not isinstance(new_file_name, dict):
+ delete = False
+ output_path = os.path.join(media_path, *path)
+ if not os.path.exists(output_path):
+ os.makedirs(output_path)
+ move_path = os.path.join(output_path, os.path.basename(file_path))
+ rename_path = os.path.join(output_path, new_file_name)
+ if not os.path.exists(move_path) and not os.path.exists(rename_path):
+ # Move to the new location
+ shutil.move(file_path, move_path)
+ # Then rename the file
+ os.rename(move_path, rename_path)
+ status_message = f'Moved & Renamed:\nFrom: {file_path}\nTo: {rename_path}'
+ delete = True
+ elif os.path.exists(move_path) and not os.path.exists(rename_path):
+ # Then rename the file
+ os.rename(move_path, rename_path)
+ status_message = f'Renamed:\nFrom: {file_path}\nTo: {rename_path}'
+ else:
+ status_message = f'Skipping: {file_path}\nFile exists {rename_path}'
+
+ self.progress_bar['value'] += 1
+ self.s.configure(style=self.style, text=status_message)
+
+ # Add the moved file's folder path to the list of folders to delete
+ if file_folder != dl_path and file_folder != media_path and file_folder != output_path and delete \
+ and file_folder not in folders_to_delete:
+ folders_to_delete.append(file_folder)
+ else:
+ if len(path) >= level:
+ path = path[:level-1]
+ path.append(file_path)
+ move_files(file_dict[file_path], path=path, level=level)
- def recursively_organize_shows_and_movies(self, delete_folders=True):
dl_path = get_downloads_or_media_path('downloads')
media_path = get_downloads_or_media_path('media')
folders_to_delete = []
- self.progress_bar_appear()
- self.progress_bar['maximum'] = len(self.filtered_media.keys())
+ self.progress_bar['maximum'] = 0
self.progress_bar['value'] = 0
+ inc_max_progress(self.all_media_info)
+ self.progress_bar_appear()
- for folder_path, i in self.filtered_media.items():
- for file_path, info in i.items():
- path = os.path.dirname(file_path)
- file = os.path.basename(file_path)
- extension = file.split('.')[-1]
- skip = False
- # Route for TV Shows
- if info['kind'] == 'TV Show':
- season_folder = 'Extras' if info["season"] == -1 else f'Season {info["season"]}'
- output_folder = os.path.join(media_path, 'TV Shows', info['title'], season_folder)
- # Route for Movies
- else:
- output_folder = os.path.join(media_path, 'Movies')
- output_path = os.path.join(output_folder, file)
- renamed_file = info['renamed_file_name'] + '.' + extension
- renamed_file_path = os.path.join(output_folder, renamed_file)
- # Create output folder if it does not exist
- if not os.path.exists(output_folder):
- os.makedirs(output_folder)
- # Move and then rename file
- if not os.path.exists(output_path) and not os.path.exists(renamed_file_path):
- shutil.move(file_path, output_path)
- os.rename(output_path, renamed_file_path)
- status_message = f'Moved & Renamed {info["kind"].title()}:\nFrom: {file}\nTo: {renamed_file}'
- # Update status and increment progress bar to show that the file has moved
-
- elif not os.path.exists(renamed_file_path):
- os.rename(output_path, renamed_file_path)
- # Update status and increment progress bar to show that the file has moved
- status_message = f'File exists in {output_folder},' \
- f' Renamed {info["kind"].title()}:\nFrom: {file}\nTo: {renamed_file}'
- else:
- skip = True
- status_message = f'Skipping: {file}\nFile exists in {output_folder}:\n{renamed_file_path}'
- self.progress_bar['value'] += 1
- self.s.configure(style=self.style, text=status_message)
- # Add the moved file's folder path to the list of folders to delete
- if path != dl_path and path != media_path and path != os.path.dirname(output_path) \
- and path not in folders_to_delete and not skip:
- folders_to_delete.append(path)
+ for folder_path, i in self.all_media_info.items():
+ move_files(i)
# Delete folders that contained media files that were moved
if delete_folders:
@@ -640,8 +472,8 @@ def on_submit():
submit_button.destroy()
main_frame.destroy()
- main_frame = Frame(self.canvas_frame, bg=self.colors['main'], bd=1, relief=RAISED)
- main_frame.pack(padx=8, pady=90)
+ main_frame = Frame(self.canvas_frame, bg=self.colors['sub'], bd=1, relief=RIDGE)
+ main_frame.pack(padx=8, pady=8)
self.toggle_buttons_enabled()
self.final_todo_list = []
@@ -651,24 +483,29 @@ def on_submit():
'media': 'Movies and TV Shows'
}
Label(main_frame, text='Which folders would you like to organize?', font=self.fonts['medium'],
- bg=self.colors['main'], fg=self.colors['font']).pack(side=TOP, fill=X, ipady=20, ipadx=20)
+ bg=self.colors['sub'], fg=self.colors['font']).pack(side=TOP, fill=X, ipady=20, ipadx=20)
- options_frame = Frame(main_frame, bg=self.colors['main'], bd=2, relief=SUNKEN)
- options_frame.pack(side=TOP)
+ outer_options_frame = Frame(main_frame, bg=self.colors['main'], bd=1, relief=RIDGE)
+ outer_options_frame.pack(side=TOP)
+ options_frame = Frame(outer_options_frame, bg=self.colors['main'])
+ options_frame.pack(padx=6, pady=6)
dictionary = dict()
for i, desc in todo_list.items():
dictionary[i] = {'button': Checkbutton(options_frame, text=desc, onvalue=True, offvalue=False,
- font=self.fonts['xsmall'],
- anchor=NW, bg=self.colors['alt'], fg=self.colors['font'],
+ activebackground=self.colors['alt'],
+ activeforeground=self.colors['font'],
+ anchor=W, cursor='hand2', font=self.fonts['xsmall'],
+ bg=self.colors['alt'], fg=self.colors['font'],
selectcolor=self.colors['main'])}
dictionary[i]['button'].var = BooleanVar(value=True)
self.final_todo_list.append(i)
dictionary[i]['button']['variable'] = dictionary[i]['button'].var
dictionary[i]['button']['command'] = lambda w=dictionary[i]: upon_select(w)
- dictionary[i]['button'].pack(side=TOP, fill=X, padx=1, pady=1)
+ dictionary[i]['button'].pack(fill=X, padx=1, pady=1)
dictionary[i]['todo'] = i
- submit_button = Button(main_frame, text='Select', command=on_submit, font=self.fonts['small'],
+ submit_button = Button(main_frame, text='Select', command=on_submit, font=self.fonts['small'], cursor='hand2',
+ activebackground=self.colors['special'], activeforeground=self.colors['font'],
bg=self.colors['special'], fg=self.colors['font'])
submit_button.pack(side=BOTTOM, pady=20)
self.wait_window(main_frame)
@@ -699,11 +536,18 @@ def on_press_select_media(self):
else:
paths.append(get_downloads_or_media_path(option))
- outcome = self.media_files_info(folder_paths=paths)
- if outcome != 'No Media':
- self.organize_button_appear()
- else:
+ self.all_media_info = self.get_media_info_from_paths(paths)
+
+ if not self.all_media_info:
+ self.progress_bar_appear()
+ self.progress_complete('\nNo Media Detected...\n')
self.organize_button_disappear()
+ else:
+ CheckList(self.canvas_frame, dictionary=self.all_media_info, toggle_config=self.image_config,
+ toggle_type='image', font=self.fonts['large'],
+ bg=self.colors['main'], fg=self.colors['font'],
+ highlight_color=self.colors['sub'], border_color=self.colors['special'])
+ self.organize_button_appear()
def on_press_organize_media(self):
""" Start organizing media """
@@ -712,7 +556,7 @@ def on_press_organize_media(self):
def organize_button_appear(self):
self.buttons_config['Organize']['button'].pack()
- self.buttons_config['Organize']['label'].pack()
+ self.buttons_config['Organize']['label'].pack(fill=X)
def organize_button_disappear(self):
self.buttons_config['Organize']['button'].pack_forget()
@@ -728,7 +572,7 @@ def progress_bar_appear(self):
def progress_complete(self, message):
self.toggle_buttons_enabled()
- self.progress_bar['value'] = 0
+ self.progress_bar['value'] = self.progress_bar['maximum']
self.s.configure(style=self.style, text=message)
def toggle_buttons_enabled(self):
diff --git a/Python3_Projects/Organize_Media/components/__init__.py b/Python3_Projects/Organize_Media/components/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/Python3_Projects/Organize_Media/components/checklist.py b/Python3_Projects/Organize_Media/components/checklist.py
new file mode 100644
index 00000000..a986f78d
--- /dev/null
+++ b/Python3_Projects/Organize_Media/components/checklist.py
@@ -0,0 +1,274 @@
+from tkinter import *
+from copy import deepcopy
+from PIL import Image, ImageTk
+# Import components
+from .collapsible_frame import CollapsibleFrame
+
+
+class CheckList(Frame):
+ """ Creates a series of checklists based on a dictionary.
+ The first level of Keys int the dictionary are a column (and the title of that column).
+ Each level of the dictionary adds a sub-section.
+ The lowest level of the dictionary is the checklist.
+
+ Dictionary Sample:
+ {
+ 'column': {
+ 'section':
+ sub_section: {
+ 'checklist_name': 'alias_name'
+ }
+ }
+ }
+ }
+
+ Sample output:
+ Column1 | Column2
+ section1 | section1
+ sub_section1 | sub_section1
+ [x] checkbutton1 | [x] checkbutton1
+ [x] checkbutton2 | [x] checkbutton2
+ sub_section2 | sub_section2
+ [x] checkbutton1 | [x] checkbutton1
+ [x] checkbutton2 | [x] checkbutton2
+ section2 | section2
+ sub_section1 | sub_section1
+ [x] checkbutton1 | [x] checkbutton1
+
+
+ :param dictionary: The dictionary of all the media to filter
+ :return: Filtered media
+ """
+
+ def __init__(self, widget, dictionary, **kwargs):
+ Frame.__init__(self)
+ # self.config(bg=bg)
+ self.bg = kwargs.pop('bg', None)
+ self.fg = kwargs.pop('fg', None)
+ self.highlight_color = kwargs.pop('highlight_color', None)
+ self.border_color = kwargs.pop('border_color', None)
+ self.section_color = kwargs.pop('section_color', None)
+ self.font = kwargs.pop('font', ('none', 20, 'bold'))
+ self.dictionary = dictionary
+ self.reference_dict = deepcopy(dictionary)
+ self.check_button_dict = dict()
+ self.toggle_button_dict = deepcopy(dictionary)
+ self.toggle_config = kwargs.pop('toggle_config', {'deselect': 'X', 'select': 'O'})
+ self.toggle_type = kwargs.pop('toggle_type', 'text')
+ self.deepest_level = 0
+
+ main_frame = Frame(widget, bg=self.bg, name='checklist')
+ main_frame.pack(fill=BOTH)
+ self.construct_columns(main_frame, checklist_dict=self.dictionary)
+
+ def add_to_dict(self, path, update_dict, source_dict):
+ if len(path) == 1 or path[0] not in update_dict:
+ update_dict[path[0]] = source_dict[path[0]]
+ else:
+ self.add_to_dict(path[1:], update_dict[path[0]], source_dict[path[0]])
+
+ def remove_from_dict(self, path, update_dict):
+ if len(path) == 1 and path[0] in update_dict:
+ del update_dict[path[0]]
+ elif path[0] in update_dict:
+ self.remove_from_dict(path[1:], update_dict[path[0]])
+
+ def upon_select(self, widget):
+ # Add or remove items from list when they are toggled
+ path = self.check_button_dict[widget]
+ if widget.var.get():
+ self.add_to_dict(path, self.dictionary, self.reference_dict)
+ widget['bg'] = self.highlight_color
+ else:
+ self.remove_from_dict(path, self.dictionary)
+ widget['bg'] = self.bg
+
+ def all_children(self, widget, callback):
+ _list = widget.winfo_children()
+ for w in _list:
+ callback.append(w)
+ self.all_children(w, callback)
+ return callback
+
+ def update_button_toggle(self, path, dictionary, frame, first_run=True):
+ if len(path) >= 1 and isinstance(dictionary[path[0]], dict):
+ f = dictionary[path[0]]['frame']
+ b = dictionary[path[0]]['button']
+ callback = []
+ checkbutton_children = [c for c in self.all_children(f, callback)
+ if type(c) == Checkbutton and not c.var.get()]
+ if checkbutton_children:
+ b[self.toggle_type] = self.toggle_config['select']
+ else:
+ b[self.toggle_type] = self.toggle_config['deselect']
+ # Set all child toggle buttons equal to the parent on the first
+ if first_run:
+ callback = []
+ toggle_children = [b for b in self.all_children(frame, callback)
+ if type(b) == Button
+ and b[self.toggle_type] in [str(i) for i in self.toggle_config.values()]]
+ for child in toggle_children:
+ child[self.toggle_type] = b[self.toggle_type]
+ self.update_button_toggle(path[1:], dictionary[path[0]], frame, first_run=False)
+
+ def toggle_all(self, x):
+ """ Functionality when clicking a toggle all button:
+ - Checks all dependant check buttons: on or off depending on the status of the toggle button
+ - Adjusts the status of other dependant toggle buttons """
+
+ frame, button, path = x
+ child_callback = []
+ checklist_children = [obj for obj in self.all_children(frame, child_callback) if type(obj) == Checkbutton]
+ # Toggle each item in the dictionary based on the toggle
+ toggle = str(button[self.toggle_type]) == str(self.toggle_config['select'])
+ for c in checklist_children:
+ c.var = BooleanVar(value=toggle)
+ c['variable'] = c.var
+ self.upon_select(c)
+
+ # Go down the path, and toggle button on or off based on each dependant checkbutton's status
+ self.update_button_toggle(path, self.toggle_button_dict, frame)
+
+ def create_checklist_button(self, frame, dict_path, **kwargs):
+ """ Creates a Checkbutton and adds it to the dictionary of Checkbuttons """
+ name = kwargs.pop('name', None)
+ font = kwargs.pop('font', None)
+ cbutton = Checkbutton(frame, text=name, fg=self.fg, onvalue=True, highlightthickness=0, bd=0,
+ offvalue=False, anchor=W, justify=LEFT, font=font, cursor='hand2',
+ selectcolor=self.highlight_color, bg=self.highlight_color,
+ activeforeground=self.fg, activebackground=self.highlight_color)
+ cbutton.var = BooleanVar(value=True)
+ cbutton['variable'] = cbutton.var
+ cbutton['command'] = lambda w=cbutton: self.upon_select(w)
+ self.check_button_dict[cbutton] = dict_path
+
+ def add_to_toggle_button_dict(self, path, dict, button, frame):
+ if len(path) > 1:
+ self.add_to_toggle_button_dict(path[1:], dict[path[0]], button, frame)
+ elif len(path) == 1:
+ dict[path[0]]['button'] = button
+ dict[path[0]]['frame'] = frame
+
+ def create_section_frame(self, frame, **kwargs):
+ """ Create the section frame for a given section """
+ path = kwargs.pop('path', None)
+ title = kwargs.pop('title', None)
+ font = kwargs.pop('font', None)
+ padx = kwargs.pop('padx', None)
+ pady = kwargs.pop('pady', None)
+ start_collapsed = kwargs.pop('start_collapsed', True)
+
+ section_frame = Frame(frame, bg=self.bg)
+ section_frame.pack(side=TOP, fill=X, padx=padx, pady=pady, anchor=NW)
+
+ f = CollapsibleFrame(section_frame, title=title, font=font, bg=self.bg, fg=self.fg,
+ highlight_color=self.highlight_color, title_color=self.border_color,
+ start_collapsed=start_collapsed)
+ section_bottom_frame = Frame(f.collapse_frame, bg=self.bg)
+ toggle_button = Button(f.top_frame, cursor='hand2', relief=FLAT, bd=0, highlightthickness=0,
+ bg=self.bg, fg=self.fg, activebackground=self.bg, activeforeground=self.fg)
+ toggle_button[self.toggle_type] = self.toggle_config['deselect']
+ toggle_button['command'] = lambda x=(section_bottom_frame, toggle_button, path): self.toggle_all(x)
+ toggle_button.pack(side=LEFT)
+ self.add_to_toggle_button_dict(path=path, dict=self.toggle_button_dict, button=toggle_button,
+ frame=section_bottom_frame)
+
+ section_bottom_frame.pack(side=TOP, fill=X, anchor=NW)
+
+ return section_bottom_frame
+
+ def construct_sections(self, frame, level=0, section=None, checklist_dict=None,
+ path=None, **kwargs):
+ """ Constructs all sections for a given dictionary.
+ Will continue creating sections until the value is not a dictionary. """
+ font = kwargs.pop('font', None)
+ padx = kwargs.pop('padx', None)
+ pady = kwargs.pop('pady', None)
+ # Use the level to keep track of how deep we are in the dictionary
+ level += 1
+ if level > self.deepest_level:
+ self.deepest_level = level
+ font = (font[0], font[1] - 1, font[2])
+ for sub_section in sorted([k for k in checklist_dict[section].keys()]):
+ # Adjust path for the section based on the level of the dictionary
+ if len(path) >= level + 1:
+ path = path[:level + 1]
+ path.pop(level)
+ path.append(sub_section)
+
+ if isinstance(checklist_dict[section][sub_section], dict):
+ section_frame = self.create_section_frame(frame, title=sub_section, font=font, padx=padx, pady=pady,
+ path=path)
+ self.construct_sections(frame=section_frame, font=font, padx=padx, pady=pady,
+ section=sub_section, checklist_dict=checklist_dict[section],
+ path=path, level=level)
+ else:
+ self.create_checklist_button(frame, name=checklist_dict[section][sub_section], font=font,
+ dict_path=path)
+
+ def construct_columns(self, frame, checklist_dict):
+ """ Constructs the columns for the checklists.
+ Assumes the first key in the dict is the column name """
+ temp = deepcopy(checklist_dict)
+ indent = 25
+ num_cols = 0
+ for column in sorted(list(temp.keys())):
+ num_cols += 1
+ if isinstance(temp[column], dict):
+ toggle_buttons = []
+ c_frame = Frame(frame, bg=self.bg)
+ c_frame.pack(side=LEFT, anchor=NW)
+ column_frame = self.create_section_frame(c_frame, title=column, font=self.font, path=[column],
+ toggle_buttons=toggle_buttons, start_collapsed=False,
+ padx=2, pady=2)
+ self.construct_sections(frame=column_frame, font=self.font, section=column,
+ checklist_dict=temp, path=[column], toggle_buttons=toggle_buttons,
+ padx=(indent, 0), pady=4)
+ else:
+ raise Exception(f'Not a proper dictionary. Expected {column} to have a dictionary.')
+ # Pack the Check Buttons
+ for ch in self.check_button_dict.keys():
+ ch['font'] = (self.font[0], self.font[1] - self.deepest_level, self.font[2])
+ ch.pack(side=TOP, fill=X, anchor=NW, pady=1, padx=(indent, 0))
+
+
+if __name__ == '__main__':
+ dictionary = {
+ 'HEADER_1': {
+ 'Zheckbox_1': 'Zheck 1',
+ 'Section_1': {
+ 'Sub_Section_1': {
+ 'Checkbox_1': 'Check 1'}},
+ 'Section_2': {
+ 'Sub_Section_1': {
+ 'Checkbox_1': 'Check 1',
+ 'Checkbox_2': 'Check 2'},
+ 'Sub_Section_2': {
+ 'Checkbox_1': 'Check 1'}},
+ 'Section_3': {
+ 'Checkbox_1': 'Check 1'},
+ 'Checkbox_2': 'Check 2'},
+ 'HEADER_2': {
+ 'Section_1': {
+ 'Checkbox_1': 'Check 1'},
+ 'Checkbox_1': 'Check 1'},
+ 'HEADER_3': {
+ 'Checkbox_1': 'Check 1'}
+ }
+
+ colors = {
+ 'main': '#%02x%02x%02x' % (20, 20, 20),
+ 'sub': '#%02x%02x%02x' % (35, 35, 35),
+ 'special': '#%02x%02x%02x' % (92, 15, 128),
+ 'special_alt': '#%02x%02x%02x' % (180, 53, 240),
+ 'alt': '#%02x%02x%02x' % (60, 111, 194),
+ 'font': '#%02x%02x%02x' % (255, 255, 255)
+ }
+ font = ('Arial', 20, 'bold')
+
+ app = Tk()
+ app.title('Example Checklist')
+ checklist = CheckList(app, dictionary=dictionary, bg=colors['main'], fg=colors['font'], font=font,
+ highlight_color=colors['sub'], border_color=colors['special'])
+ checklist.pack()
+ app.mainloop()
diff --git a/Python3_Projects/Organize_Media/components/collapsible_frame.py b/Python3_Projects/Organize_Media/components/collapsible_frame.py
new file mode 100644
index 00000000..1ff94080
--- /dev/null
+++ b/Python3_Projects/Organize_Media/components/collapsible_frame.py
@@ -0,0 +1,53 @@
+from tkinter import *
+
+
+class CollapsibleFrame(Frame):
+ """ Creates a collapsible frame, with a button to expand and collapse the frame.
+ Frame starts collapsed by default. """
+ def __init__(self, top, **kwargs):
+ Frame.__init__(self)
+ self.bg = kwargs.pop('bg', None)
+ self.fg = kwargs.pop('fg', None)
+ self.title_color = kwargs.pop('title_color', None)
+ self.highlight_color = kwargs.pop('highlight_color', None)
+ self.font = kwargs.pop('font', None)
+ self.title = kwargs.pop('title', None)
+ self.alignment = kwargs.pop('alignment', W)
+ self.start_collapsed = kwargs.pop('start_collapsed', True)
+
+ main_frame = Frame(top, bg=self.bg, bd=0, relief=RIDGE)
+ main_frame.pack(fill=BOTH, expand=True)
+ self.top_frame = Frame(main_frame, bg=self.bg)
+ self.top_frame.pack(fill=X)
+ title_frame = Frame(self.top_frame, bg=self.title_color)
+ title_frame.pack(side=RIGHT, fill=X, expand=True)
+
+ title = Button(title_frame, text=self.title, bg=self.bg, fg=self.fg, cursor='hand2', relief=RIDGE, bd=0,
+ font=self.font, anchor=self.alignment, command=self.collapse, highlightthickness=0,
+ activebackground=self.highlight_color, activeforeground=self.fg)
+ self.collapse_frame = Frame(main_frame, bg=self.bg)
+ title.pack(side=TOP, fill=X, padx=1, pady=1, ipadx=4, anchor=self.alignment)
+ if not self.start_collapsed:
+ self.collapse_frame.pack(fill=X)
+
+ def collapse(self):
+ if self.collapse_frame.winfo_viewable():
+ self.collapse_frame.pack_forget()
+ else:
+ self.collapse_frame.pack(fill=X)
+
+
+if __name__ == '__main__':
+ """ Example """
+
+ app = Tk()
+ app.title('Test')
+ app.geometry('300x300')
+
+ f = CollapsibleFrame(app, title='Title', bg='black', fg='white', title_color='yellow')
+ label = Label(f.collapse_frame, text=' - Test')
+ label.pack(side=LEFT)
+ button = Button(f.top_frame, text='Add Button')
+ button.pack(side=LEFT)
+
+ app.mainloop()
diff --git a/Python3_Projects/Organize_Media/components/menu_bar.py b/Python3_Projects/Organize_Media/components/menu_bar.py
new file mode 100644
index 00000000..4de749c0
--- /dev/null
+++ b/Python3_Projects/Organize_Media/components/menu_bar.py
@@ -0,0 +1,129 @@
+from tkinter import *
+from tkinter.ttk import Separator
+
+
+class MenuButton:
+ def __init__(self, widget, cascade_window, **kwargs):
+
+ self.text = kwargs.pop('text', None)
+ self.font = kwargs.pop('font', None)
+ self.bg = kwargs.pop('bg', None)
+ self.fg = kwargs.pop('fg', None)
+ self.activebackground = kwargs.pop('activebackground', self.bg)
+ self.activeforeground = kwargs.pop('activeforeground', self.fg)
+ self.cursor = kwargs.pop('cursor', 'hand2')
+
+ self.__cascade_window = cascade_window
+ self.__cascade_frame = Frame(self.__cascade_window, bg=self.bg, bd=2, relief=SUNKEN)
+
+ self.__button = Button(widget, text=self.text, command=self.__toggle_cascade_window, cursor=self.cursor,
+ font=self.font, relief=FLAT, bd=0, bg=self.bg, fg=self.fg,
+ activebackground=self.activebackground, activeforeground=self.activeforeground)
+ self.__button.pack(side=LEFT, padx=(3, 0), pady=(3, 1))
+
+ self.__button.bind('', self.__on_enter_tab)
+ self.__button.bind('', self.__set_background_default)
+
+ def add_menu_item(self, text=None, image=None, command=None):
+ b = Button(self.__cascade_frame, text=text, image=image, compound=LEFT, command=command, cursor=self.cursor,
+ font=self.font, relief=FLAT, bd=0, bg=self.bg, fg=self.fg,
+ activebackground=self.activebackground, activeforeground=self.activeforeground)
+ b.pack(side=TOP, fill=X, padx=2, pady=1)
+ b.bind('', self.__set_background_default)
+ b.bind('', self.__set_background_highlight)
+
+ def add_separator(self):
+ Separator(self.__cascade_frame).pack(fill=X, padx=2, pady=1)
+
+ def __on_enter_tab(self, event):
+ self.__set_background_highlight(event)
+ # Hid all frames, show the cascade window, and then show the frame we want
+ for f in self.__cascade_window.winfo_children():
+ f.pack_forget()
+ self.__cascade_window.update()
+ x, y = self.__button.winfo_rootx(), self.__button.winfo_rooty() + self.__button.winfo_reqheight()
+ self.__cascade_window.geometry(f'+{x}+{y}')
+ self.__cascade_frame.pack()
+
+ def __toggle_cascade_window(self):
+ if self.__cascade_window.winfo_ismapped():
+ self.__cascade_window.withdraw()
+ else:
+ self.__cascade_window.update()
+ x, y = self.__button.winfo_rootx(), self.__button.winfo_rooty() + self.__button.winfo_reqheight()
+ self.__cascade_window.geometry(f'+{x}+{y}')
+ self.__cascade_window.deiconify()
+
+ def __set_background_highlight(self, event):
+ event.widget.config(bg=self.activebackground, fg=self.activeforeground)
+
+ def __set_background_default(self, event):
+ event.widget.config(bg=self.bg, fg=self.fg)
+
+
+class MenuBar(Frame):
+ def __init__(self, widget, **kwargs):
+ Frame.__init__(self, widget)
+
+ def on_click(event):
+ """ toggle off the cascade window """
+ if self.__cascade_window.winfo_ismapped():
+ # Does not turn off right away to give time for selecting options
+ self.after(300, self.__cascade_window.withdraw)
+
+ def on_window_adjustment(event):
+ """ toggle off the cascade window """
+ self.__cascade_window.withdraw()
+
+ self.font = kwargs.pop('font', None)
+ self.bg = kwargs.pop('bg', None)
+ self.fg = kwargs.pop('fg', None)
+ self.activebackground = kwargs.pop('activebackground', self.bg)
+ self.activeforeground = kwargs.pop('activeforeground', self.fg)
+ self.cursor = kwargs.pop('cursor', 'hand2')
+
+ self.__menu_bar = Frame(widget, bg=self.bg, bd=1, relief=RIDGE, name='menu_bar')
+ self.__menu_bar.pack(side=TOP, fill=X)
+
+ self.__cascade_window = Toplevel(widget)
+ self.__cascade_window.overrideredirect(True)
+ self.__cascade_window.withdraw()
+ self.bind_all('', on_click)
+ self.master.bind('', on_window_adjustment)
+
+ def add_menu_tab(self, text=None):
+ b = MenuButton(self.__menu_bar, self.__cascade_window, text=text, cursor=self.cursor,
+ font=self.font, bd=0, bg=self.bg, fg=self.fg,
+ activebackground=self.activebackground, activeforeground=self.activeforeground)
+ return b
+
+
+if __name__ == '__main__':
+ def on_save():
+ print('Save...')
+
+ def on_open():
+ print('Open...')
+
+ def on_copy():
+ print('Copy...')
+
+ def on_cut():
+ print('Cut...')
+
+ app = Tk()
+ app.title('Menu Bar Example')
+ app.geometry('300x200')
+ app.config(bg='darkgrey')
+
+ menu = MenuBar(app, font=('Helvetica', 12, 'bold'), bg='black', fg='white',
+ activebackground='#444444', activeforeground='white')
+ tab1 = menu.add_menu_tab(text='File')
+ tab2 = menu.add_menu_tab(text='Edit')
+ tab1.add_menu_item(text='Save', command=on_save)
+ tab1.add_separator()
+ tab1.add_menu_item(text='Exit', command=app.destroy)
+ tab2.add_menu_item(text='Copy', command=on_copy)
+ tab2.add_menu_item(text='Cut', command=on_cut)
+
+ app.mainloop()
diff --git a/Python3_Projects/Organize_Media/components/tooltip.py b/Python3_Projects/Organize_Media/components/tooltip.py
new file mode 100644
index 00000000..59eff3d8
--- /dev/null
+++ b/Python3_Projects/Organize_Media/components/tooltip.py
@@ -0,0 +1,56 @@
+from tkinter import *
+
+
+class CreateToolTip(object):
+ """ Create a tooltip for a given widget """
+
+ def __init__(self, widget, text='widget info', bg='#ffffff', fg='#000000'):
+ self.waittime = 500 #miliseconds
+ self.wraplength = 180 #pixels
+ self.widget = widget
+ self.text = text
+ self.bg = bg
+ self.fg = fg
+ self.widget.bind("", self.enter)
+ self.widget.bind("", self.leave)
+ self.widget.bind("", self.leave)
+ self.id = None
+ self.tw = None
+
+ def enter(self, event=None):
+ self.schedule()
+
+ def leave(self, event=None):
+ self.unschedule()
+ self.hidetip()
+
+ def schedule(self):
+ self.unschedule()
+ self.id = self.widget.after(self.waittime, self.showtip)
+
+ def unschedule(self):
+ id = self.id
+ self.id = None
+ if id:
+ self.widget.after_cancel(id)
+
+ def showtip(self, event=None):
+ x = y = 0
+ x, y, cx, cy = self.widget.bbox("insert")
+ x += self.widget.winfo_rootx() + 25
+ y += self.widget.winfo_rooty() + 20
+ # creates a toplevel window
+ self.tw = Toplevel(self.widget)
+ # Leaves only the label and removes the app window
+ self.tw.wm_overrideredirect(True)
+ self.tw.wm_geometry("+%d+%d" % (x, y))
+ label = Label(self.tw, text=self.text, justify='left',
+ background=self.bg, foreground=self.fg, relief='solid', borderwidth=1,
+ wraplength=self.wraplength)
+ label.pack(ipadx=1)
+
+ def hidetip(self):
+ tw = self.tw
+ self.tw = None
+ if tw:
+ tw.destroy()
\ No newline at end of file
diff --git a/Python3_Projects/Organize_Media/requirements.txt b/Python3_Projects/Organize_Media/requirements.txt
new file mode 100644
index 00000000..df39c0ef
--- /dev/null
+++ b/Python3_Projects/Organize_Media/requirements.txt
@@ -0,0 +1,2 @@
+python3-tk
+pillow
diff --git a/Python3_Projects/Organize_Media/settings/config.json b/Python3_Projects/Organize_Media/settings/config.json
index bc367e41..f4c22b9a 100644
--- a/Python3_Projects/Organize_Media/settings/config.json
+++ b/Python3_Projects/Organize_Media/settings/config.json
@@ -15,5 +15,7 @@
"required_paths": [
"downloads",
"media"
- ]
+ ],
+ "downloads": "C:/Users/JustinGrilli/Documents/TEST OM/downloads",
+ "media": "C:/Users/JustinGrilli/Documents/TEST OM/media"
}
\ No newline at end of file
diff --git a/Python3_Projects/Organize_Media/settings/general_functions.py b/Python3_Projects/Organize_Media/settings/general_functions.py
index 24f80798..c7f42597 100644
--- a/Python3_Projects/Organize_Media/settings/general_functions.py
+++ b/Python3_Projects/Organize_Media/settings/general_functions.py
@@ -29,10 +29,10 @@ def tv_show_ep_from_file_name(file):
episode = None
# Search through the file name for the following patterns
patterns = [
- # s1e1 or s01e01
- r'[^\w\d](s\d{1,2}e\d{1,2})$|'
- r'^(s\d{1,2}e\d{1,2})[^\w\d]|'
- r'[^\w\d](s\d{1,2}e\d{1,2})[^\w\d]',
+ # s1e1 or s01e01 or s01e01e02
+ r'[^\w\d](s\d{1,2}e\d{1,2}e?\d{0,2})$|'
+ r'^(s\d{1,2}e\d{1,2}e?\d{0,2})[^\w\d]|'
+ r'[^\w\d](s\d{1,2}e\d{1,2}e?\d{0,2})[^\w\d]',
# s1 e1 or s01 e01
r'[^\w\d](s\d{1,2}[^\w\d]e\d{1,2})$|'
r'^(s\d{1,2}[^\w\d]e\d{1,2})[^\w\d]|'
@@ -57,8 +57,9 @@ def tv_show_ep_from_file_name(file):
if matches:
if patterns.index(pattern) != 4:
tv_show_episode = [x for x in matches[0] if x][0]
- season, episode = [int(x) for x in re.findall(r'\d+', tv_show_episode)]
- return tv_show_episode, season, episode
+ nums = [int(x) for x in re.findall(r'\d+', tv_show_episode)]
+ # tv_ep, season, episode(s)
+ return tv_show_episode, nums[0], nums[1:] if len(nums) > 2 else nums[1]
else:
matches = [num for num in matches[0]
if num and int(num[-2:]) < 30 and num[-2:] != '00' and num not in bad_nums
@@ -145,10 +146,8 @@ def get_media_title(tv_show_episode, file_name):
r'[a-z:\- ]+[0-9]?[^\d]\([a-z]+\)[^\d]|'
r'[a-z:\- ]+[0-9]?[^\d]|[0-9]+[^\w\d]?'
r')', clean_file_name, re.IGNORECASE)
- year = re.findall(r'19\d\d|20\d\d', clean_file_name)
title = re.sub(r'[^a-zA-Z0-9\-]', ' ', title[0]).strip() if title else None
- new_file_name = f'{title} ({year[0]})' if year else title if title else None
- return new_file_name
+ return title
def rename_all_media_in_directory(media_info):
diff --git a/Python3_Projects/Organize_Media/test.py b/Python3_Projects/Organize_Media/test.py
deleted file mode 100644
index 7365adcf..00000000
--- a/Python3_Projects/Organize_Media/test.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import re
-from settings.general_functions import initcap_file_name
-
-clean_file_name = 'x-factor (1999)'
-clean_file_name = initcap_file_name(clean_file_name)
-print(clean_file_name)
-title = re.findall(r'^('
- r'[a-z:\- ]+[0-9]?$|'
- r'[a-z:\- ]+[0-9]?[^\d]\([a-z]+\)$|'
- r'[a-z:\- ]+[0-9]?[^\d]\([a-z]+\)[^\d]|'
- r'[a-z:\- ]+[0-9]?[^\d]|[0-9]+[^\w\d]?'
- r')', clean_file_name, re.IGNORECASE)
-print(title)