Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #! /usr/bin/env python3
- #
- # FAide v1.0.2.0
- # Fansub.co aide script by [HYBRID BEING], 2016
- # This code is public domain.
- #
- # AS: I suck at making proper documentation (and can't be bothered
- # to check out documentation generators), so bear with this.
- #
- # FAide is a cross-platform Python script designed to automate a portion of
- # Fansub.co comparison procedure. Currently it can: 1) (with the help of
- # MediaInfo) extract technical data from video files, upload it to Pastebin,
- # compile necessary data into a comparison post template; 2) batch rename
- # screenshots made by Aegisub to format acceptable by Fansub.co.
- #
- # FAide works on Windows, Linux (tested on Ubuntu 15.04) and OS X (look below
- # for more info). It requires Python 3 (tested on 3.4) and Tk 8.6 (provided
- # with Windows Python installer and as a separate package on Linux), utilizes
- # MediaInfo (as CLI version executable on Windows, package on Linux and
- # OS X) for grabbing media info and designed to work with screenshots
- # generated by Aegisub.
- # On OS X Python distributed by python.org binds to Tk 8.5, so you
- # have to get Python that binds to version 8.6 (as well as said Tk version)
- # from a different source - "py34-tkinter" package from MacPorts (for which
- # you'll have to install Xcode) will suffice. Script was tested on
- # OS X El Capitan 10.11 with free CLI MediaInfo package (availiable on site).
- #
- # Here are some things to keep in mind:
- #
- # - Only files starting with text (supposedly group name) in square brackets
- # will be picked up by FAide. So, if some group tries to be original,
- # for example (Hi10)_Blood_Lad_-_02_(BD_720p)_(OWA-Kaitou)_(2C0F07A0).mkv,
- # prepend [OWA-Kaitou] at the beginning of the filename before you start making
- # screens from Aegisub. This way screenshots and video file will not be ignored
- # by FAide.
- #
- # - FAide keeps no history of it's Pastebin uploads, so take care when
- # uploading repeatedly. If same file is uploaded second time it will
- # most likely get caught by spam-filter.
- #
- # - Free Pastebin users are required to pass captcha if they make a new
- # paste earlier than a minute since the last one. Pastebin does not
- # elaborate on it's spam-filter (Pastebin PRO ad page states that captcha
- # appears for free users after making more than 10 pastes in 5 minutes,
- # but it's not even possible to make 10 pastes in 5 minutes without captcha
- # appearing, what the hell?!), thus an adjustable delay is introduced.
- # Might be a bit slower than doing it manually, but saves you the hassle.
- #
- # - "http://", "https://" and "www." will be removed from media info to bypass
- # Pastebin spam-filter.
- #
- # - Paste might still get caught by spam-filter despite significant
- # time interval and being unique. Spam-filter is especially picky with paste
- # titles (e.g. it'll get triggered by word "movie"). After each upload
- # manually check your latest pastes, to make sure all pastes passed the filter.
- # If any paste gets caught by spam-filter it can be accessed via
- # http://pastebin.com/%ID% (see "encoding" in output) for the next 10 minutes.
- #
- # - FAide will not check if faide.ini is the correct configuration file
- # on termination and will attempt to overwrite it. You shouldn't rename
- # any important files to faide.ini during script run (duh!).
- #
- # - FAide provides video format as "Format (Format version) (Format profile)"
- # (Format version and/or profile might not be extracted by MediaInfo and
- # might be omitted by FAide). Probably works for any video, but not 100% sure.
- #
- # - You can try to resize checkbox column and make last column narrower
- # than remaining space, but they will revert to previous size - that's
- # intentional. Weirdly enough (as always with Tkinter/Tk) treeview (base
- # control for checklist) has minimum size but not maximum size
- # for column width.
- #
- # - In text field horizontal scrollbar changes and disappears during vertical
- # scrolling. This is due to original Tk behavior and surprisingly i was
- # the first to be bothered by it. Who knows if this we'll get fixed
- # on our lifetime...
- #
- # - Checkboxes might look weird on some systems. Normally Tk checkboxes
- # include background around them, i tried to crop them out, but, alas,
- # that doesn't work flawlessly on all systems.
- #
- # - Vertical scrollbar may sometimes not disappear in video checklist when
- # dragging around separator between checklist and text field. Seems to be
- # a problem with treeview reconfiguration due to resize of panedwindow panes.
- # I honestly didn't bother getting to the bottom of this, so, I dunno,
- # blame Tkinter/Tk, or something.
- #
- # - On Linux, "Clam" theme was selected instead of default "Motif" theme,
- # because reasons.
- #
- # - FAide uses faide.ini to store settings. If file is not found FAide will
- # start with default parameters (and prompt user for username and password)
- # and will try to create file on termination. Here is a list of parameters:
- #
- # key_check (default: 1)
- # Check if API user key (if provided) is correct on startup
- # uak
- # API user key required to post to Pastebin. If none provided, user will be
- # prompted on startup and, if cancelled, later, before extracting
- # and sending media info.
- # delay (default: 60)
- # Amount of seconds to wait between pastes in order to avoid Pastebin
- # spam-filter. PRO users might benefit from setting this to 0 (not sure).
- # private (default: 0)
- # Make paste unlisted (1) or private (2). Most useful to PRO users.
- # path (default: MediaInfo)
- # (Windows only) Path to the MediaInfo executable (extension may be omitted).
- # If not found on this path, user will be prompted for location.
- # last_upload
- # Datetime of the previous paste upload to Pastebin. Next upload will be
- # no earlier than this time + delay. Not recommended to alter manually.
- # geometry
- # String storing window width, height, position and some element
- # measurements (WxH+X+Y/P/V/I). If not provided, width and height will be
- # set to half of usable screen width and height respectively, window
- # will be centered in the usable area, "P/V/I" defaults to "30/60/80".
- # Not recommended to alter manually.
- #
- # Credits:
- # Sentynel, https://sentynel.com/project/Pastebin_Script - for Pastebin upload
- # script, i based this upon;
- # PAGE, http://page.sourceforge.net/ - for scrolling widget classes;
- # New Mexico Tech http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html
- # and effbot.org http://effbot.org - for helpful though far from
- # excessive (not their fault, peace) Tkinter documentation;
- # FatCow http://www.fatcow.com/free-icons for Farm Fresh Icons;
- # Whoever else i forgot and lots of good people who post code on
- # net (especially on Stack Overflow).
- #
- # Fun fact: FAide name comes from "Fansub.co AIDE" and isn't actually
- # supposed to mean anything. Accidentally, according to Wiktionary, "faide"
- # seems to mean "longer" in Irish, and indeed, it took me longer than
- # i expected to write this script.
- #
- # PS: Tkinter/Tk sucks.
- from configparser import ConfigParser, NoOptionError
- from contextlib import contextmanager
- from datetime import timedelta as td
- from datetime import datetime as dt
- from os import (
- chdir,
- fdopen,
- getcwd,
- O_CREAT,
- O_RDWR,
- O_WRONLY,
- path,
- rename,
- walk
- )
- from os import open as os_open
- from re import search, match, sub, DOTALL
- from shutil import which
- from sys import exit as sys_exit
- from sys import platform as plat
- from time import sleep
- from urllib.error import URLError
- from urllib.parse import urlencode
- from urllib.request import urlopen
- try:
- from tkinter import (
- ACTIVE,
- BooleanVar,
- DISABLED,
- END,
- Grid,
- GROOVE,
- IntVar,
- NONE,
- NORMAL,
- Pack,
- PhotoImage,
- Place,
- re,
- RIGHT,
- sys,
- Text,
- Tk,
- Toplevel,
- ttk,
- X
- )
- except:
- sys_exit()
- from tkinter.ttk import (
- Button,
- Checkbutton,
- Entry,
- Frame,
- Label,
- Notebook,
- Panedwindow,
- Scrollbar,
- Style,
- Treeview
- )
- from tkinter import simpledialog as sdiag
- from tkinter import messagebox as mbox
- from tkinter.filedialog import askopenfilename as opendialog
- def hierarchy(widget):
- _id = widget.winfo_id()
- return {
- "path": widget.winfo_pathname(_id),
- "name": widget.winfo_name(),
- "id": _id,
- "widget": widget,
- "class": widget.winfo_class(),
- "visible": widget.winfo_viewable(),
- "children": tuple([hierarchy(wgt) for wgt in widget.winfo_children()])
- }
- def print_hierarchy(widget):
- def print_partial(info, depth):
- spaces = " " * depth
- content = "{name} [{id}] <{class}> {visible}".format(**info)
- print(spaces, end="")
- if info["widget"] == widget:
- print("** {} **".format(content))
- else:
- print(content)
- for child in info["children"]:
- print_partial(child, depth + 1)
- print_partial(hierarchy(widget.nametowidget(".")), 0)
- DEVKEY = "cb219b6e4ddd5b5e321d8307085dfc3c"
- API_POST = "http://pastebin.com/api/api_post.php"
- API_LOGIN = "http://pastebin.com/api/api_login.php"
- VIDEO = 0
- IMAGE = 1
- MINDIM = {"x": 600, "y": 384}
- DEFAULT_DELAY = 60
- SCRIPTNAME = "FAide"
- IMAGE_REGEXP = r"^\[(.+?)].+?_\d{3}_(\d+)(\.png|jpg|jpeg)$"
- VIDEO_FORMATS = [
- ".avi", ".m2v", ".m4v", ".mkv", ".mp4", ".mpeg", ".mpg", ".ogg", ".ogv",
- ".ts", ".wmv"
- ]
- RUN_STATE = 'idle'
- CFGDICT = {
- "key_check": None,
- "auk": None,
- "delay": None,
- "private": None,
- "path": None,
- "last_upload": None,
- "geometry": None
- }
- OLD_CFGDICT = {
- "key_check": None,
- "auk": None,
- "delay": None,
- "private": None,
- "path": None,
- "last_upload": None,
- "geometry": None
- }
- RAM = {}
- DEFAULTS = {
- "Settings": {"key_check": "", "private": "", "delay": ""},
- "Session": {"geometry": ""}
- }
- COMPARISON = """[php]{}
- $poll_id = ;
- require("compare.require.php");
- [/php]
- """
- GROUP = """
- $group[] = array(
- "name" => "{groupname}",
- "filename" => "{filename}",
- "filesize" => "{filesize}",
- "format" => "{vidformat}",
- "chaptered" => "{chaptered}",
- "encoding" => "{pastekey}",
- "tl" => "Crunchyroll edit/FUNimation edit/Original",
- "japanese" => "Honorifics/No honorifics, Japanisms, Western/Eastern name order",
- "english" => "British English/American English",
- "kara" => "None/Hardsubbed/Simple softsubbed/Complex softsubbed",
- "speed" => "Fast/Normal/Slow",
- // "alt" => "Information on alternate subtitle tracks if present.",
- // "note" => "Any other notable things about release.",
- );
- """
- REFRESH_B64 = """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAkRJREFUeNqMk21Ik1EUx3/Ps9lwQuJMZQs1HBREEjEhpA8RVraJEkTuU1QEFX1RKAyLvgRluimEVCAUSBGGQvYeIYOgDyEKIiXRC6Q1ldLJcqLS9tzunU2ZrdZ9OPBwzzm/e8//noMQgnSrys+020fKQJ3/WPnZTpvf20lF058QTd1A07TljX0tdBkCr6FCpZnkEZVbYEPuegrzL3D23kkCjWgpAW6fJg5sr2SzYx26Loh/QiNmaIxM3iEnMw97dj2X7p/necMSxJwguVsQR/eUYbG8Y3R+GuVWEUoiBdAs8t9isBANJZUQB1S3mq6VlxZgZA3z5ssiH8cgGoWJ75BhkiXsBEeujYz5/TT3ttLXSFkSwDBip+xF4wy9h8EhgrNBbr2+zoNEUNbVnIHail3ceHIzkTyYpEFNmyYMAyLf6Hjpo13uT3r8tOkahx6fRvM0E8SE4+mZpWT5rF1SHq+ur4jo+g2ckDa+9wriWFUNPYFXdNeFlBSl0tZ4fNTGYpzYVkr2Jif0PFoRcflKu+VbH3cfxrA+ZEf5DBHZQEoPex5YM6GkGIrsZj58srEYnupOekbZA6K+uo6wcZsfCyFkCZikiMqdaNi1lkLmIjbuBoZ51iCKzas7y2q2EZ7VMeZhY4FXQkQcEIdInUaCIXr7+2RyXI+x1Z3oku06cPlgO19DTYxOjdP/Wd5Cumci8FMCYnO8CFzk3HLZKYZJQcTbsU5xpMOpnK5VVpIU/ZdpdKnp87QQTjtp/xhnddrWdPm/BBgApkXmx/xBgD0AAAAASUVORK5CYII="""
- PASTEBIN_B64 = """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEV5gIkAAADy8/Rha3aNlJxWWVyDiZFrdX9xeYPNzMz19vfu7/Bze4UAAAAAAAAAAADAazG7AAAAEHRSTlP/////////////////AP//c7mGeQAAAFNJREFUCJljuBsKBmcZrq5atWvVqlXLgYwoEFzMcHUpCMAYq1AYIBVLQ0EMMEBmaMG1R4EgVPEShgQIg3OmLVAqdGko5+S7EF2cvHcZwM5ouHsXAEMISXWG5CkVAAAAAElFTkSuQmCC"""
- RENAME_B64 = """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAZ9JREFUeNqkUzlLA0EU/mZ2ks1hQAQtRDyCFlYpFDvBSkjjj1DwiqCVB9Y2VhYKYm1jIQjmB2jphX8hhY3gUQjZze7OPF92syroisGBZR773ne8mTeCiPCfJZIS6yvzZIQFwyUSBEkau/tHoh3ysea3UZmjOP6pSA1vn5HrawTmsxViVRKsa6XgP5+ge7N6K7QPQU0/UZ3kuK84COUw+HqrjOZOXzuSAlYqg53KMa44H3guyJgPiYwEFi+eoHxt0NuZxctDDRaDhGANFjG8w9ioM3GH8wTXdWBiAi7oLxbhBo9Q8S1MV9+QVRKSSQz/c7XAzWwPNLeWKxQglGoRsM8WxnBOxY5tC0wAdgEGRSRQdgjK57JQyoLWOu7u8xDjMaj7JmSUIgI7Ue2HbTud/n4FTY04vl8utT9E7ERa+H0SbbaetFLcixoYGUL5tIaGF4RUIZ1UIJWGSGcxms9g6vwV5DkQgcc9BTyZEbjuNaBuVicnyG+YJJXS0sLt5UzXeNtvYa2ySEaqVgkfLivvHRyKPxMkzP7dN4L/Pud3AQYAKoqz1m0k8iEAAAAASUVORK5CYII="""
- CLIPBOARD_B64 = """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RTQ0RUQzNDVBNzNFMTFFMUJEODVCNDNGMzRFRkE4NUQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RTQ0RUQzNDZBNzNFMTFFMUJEODVCNDNGMzRFRkE4NUQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFNDRFRDM0M0E3M0UxMUUxQkQ4NUI0M0YzNEVGQTg1RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFNDRFRDM0NEE3M0UxMUUxQkQ4NUI0M0YzNEVGQTg1RCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqYwzYoAAAIRSURBVHjajFPLbtNQED3XdrHi0qikGxYtFCEiHrXKCrEjEkiAhBpAiG+gfEJZIBbwASzoB8CyKhQhkBBZ9AdQU6dNYonSiEplwUtpnJDU9mXuOHbzUCVGuvZ47pnxOXPnCiklhBAYtBeLi1KGIYSm4eH8/BBA5Skz1OPVwvU3CPfzkD50Wk7qFk6eOIUwDKBpOhYeP5N26x0CQXC1tJEVSrudFAj9dt6+9xTbP1vQIJEubSAzkUHMLn3UgnX5CYgPpidScJYe5WMmUYHAx9cfLdy5eZWDta0tfF4rJnQtK4W5G9fYf/2hwPi+AjKQUHqV7Xzbwf27c9BJe2wB7an45NQk4xS+vwBTjYKjpoERXSMp1DfVOgqH9L2vMZRxcQMPJIRUAFGjzy3vIm2ZrL3TbuOIaXJCvdnG9wfHGafwAwz4R2yFSz7Onz0DdbKuW0U2O8X7m5VydHxd/JCE2H7tNVGsuszH85poVVxO2qP44AwkBYIeSqNjY5ixZ9kvlRxcmLHZX18v9jRVHs6gXq8zWCMNntfA5obDnJuNxuEMZHCgKZfLJb7rrCFrX0z86BREH96IHd0whu5DvjwOs1rjHnTCYyjbw7iIAc07flfw9iOSKTPoT88zgt/KfKry/tM2dbkS4XsLNP5itVZ4eUWq5K4+wXQHbqCaAt2A18FqHBPdC3Oa/HH8v/2hvC/K+SfAALCk8q8J0VwxAAAAAElFTkSuQmCC"""
- STOP_B64 = """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAixJREFUeNp8k8trE1EUxr9778zk0dii0FbFTYsgaFOLj66sf4DgzkWl3YlaFBfqTuiq4M7ulD5wl6ILF4rQhd0FF1JrCEkQXWg24qOPPGqcGJuZ6zmTTjqJkgsHknvO9zvfPfeOQGAlzw1NSGUkon290FrTDoeAEAL2+gZcpz55/nVuya/nGhEQjwupngxduYbIwUMk1U0wIVD9/g25xwvQrnOZIE9bACyGIvHVKagfX1B5SXkpPannwnURuzgOp/8IcotzgNOAeIDkWHyCLCbiUzehNr6isvwMwjRJK/fOpl3onR3ELlyC03sY2bmH3H1yLJlZkpRIDN+6DWNrHfarF1DRLshQGNIK7QX9533Ocx3Xs47Z0iV694mTqK08hxHpgrKo2LAozEBY3j7nuW7f0WNwdWNGBp+znlnzuqDdevtSlJNUn3pDdWIXwMMqbEKaBFCqmfjv4q5CQf8sNhsZLNClLbIYgpYduvtXSjciyuWAA/ohS0UalhW4+Q4AHlwlABDU1SZimBzwQHUHCr9ISTOwy9t0ggZApkq/ptMfPqHmaBh8A6YFSZNvD943qEmtrpH+mAfrfEcHHsQHrp/d333/zPBxRCJhctHqhN0KGlq1+htrmfd4W9y+dzebn6fHVPBH7kFGCTJ6egRWLIqWgVDVn4qN1XdprO6KabfQ8jExZDY+eONUT3TGeyRtAEk2UmV7+k728yMW//M1+hCKgQ6XkPfFPuCvAAMAu7jj1EHp4WIAAAAASUVORK5CYII="""
- KEY_B64 = """iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsSAAALEgHS3X78AAAGBUlEQVRYhcWXfWxVZx3HP89zzrn33HN6e1/KWHFCIQpl3ZiGIIvJ6IhxJgNDFpWFGJOmIzGsHSGaZYuJ/uPcgjAzYVOmkmaaUMYyM6YSDRqnDihbGYVmGysoLaXry7239972tr2397w8/nFuZ52dW1sSfn+d5Lx8P7/v8/s+5xyhlOJmlryp6oAOIIRY7HPEy4/ffbdl8Kgu+bpSPmZEp1R0Qcqir+QfL/QVmr/3m7fH/+dGpdRiAfQT3994zI5aX1u7eQe31m+ASA0oBSic8TTD777JP0/9llK5/KstP+x4GPBuFIDxpx98oX/5untrb9+yE+HmcFOdeFPvo5SPUD4iUktoyUaUkaT794cY6Ok6/tUfnd0OuIsF0F55fMMv1mz40s6GB3bhDZzESXWgFAihQCmk8PA9B98po9esJ7TqG1x85Wf0X+pq2/Zkx7cBb6FDKH7Z8vl7Eonozob7m/GuvoCb7ULoJtKIII0IuazL+bNpLnbmyY0JvLFupt85yF1bH6LKMh968J7lNbDwFBg1tnz0M43fhLEzeKUcQjdBhtB0nWxqko6/9Q0+f/zyY4devfJY5+n0YHZU4ZUzeKm/89lN2/nWvZ8+DOj6AgHMiC6+XLu6Hj/zO4QWQiBQ0kNIxbWeFCcvDj/169eu/RXADMmpZZ+yn1tyyy2UU6dZWtdMWBf3AZGFOhASSpm6kUNJHSkl1//Vg9QMpNRxpj2SdigPDAADwbFCaDpIA8PrB4UJhBfqgERIKGcQCMRtO6gTx7h25TKr6teyev1KvpgtPPHC7vUpECSjxhMrVi/BFxpCGKhyCj2kA8iFAgQxL/QCQd4Rgro1DfRdvsTK+rVsvG/dqt63r58UQF39rSyptXEdFzQDd/wqjuMDlZ1wQSUEnusiEQFCwMCK1Q309bzLyjX1LL2tBuU5+K6H57mAhtQESikEomLlAg0oOd71gcv9+EKgBl9CLHsQRdDVivoGenveQ/kCTwl8BQiJ0CRC08iPTgGUFwogD+9a94AdksujiWpKExMgNdTQMWTtdpRywfeoW1PPtSuXEFIDqSGljtQNHNfl+pUcfanJFwFvvjuhPLrnc001tmjb1PoTzOJfmBwvoHyBWWUjlUIs2w5CIdDwhl4iMMVH+T7l0gSDfSkudQ2NbnuyYzPQOx8AeXTPXU1JS7Q1tvwYM1FN5q2fk1xWR3FqEqdUxAiHCYfCaIYRDAQSz3FwykV8d5qe8328fzU/um3v2a3AVSD7SYdQHt2zrilp0dbYshczZpPuPIBZlUAA05MFpqamsDwbUKjpIiAQEqYny4ylx+l7Z4BUptS945nOXUA/kP+kSyDbd9/ZVGPLtsaWfZhxi/Rbz2Hacex4Dbl0iuJEAc3QyA0V6Ls4jJACBMGkS8FQfvrUn7szLx59faADGARGAQc+PoayffedTUlLtG1q2YsZmxGPURVPkk2PUJosoBsGueECZ1/v/8fO57ufovKqrZQLFIBspeuJ2ef/H4Bsf+SOwPbWvURiFqlzzxKpjhFN1jCWyeCUJjFCBtmhcd4IxL8LpIDpWc/xCSJXqnT9Xx+hHwUg21vv+GDNI9UWqXMHMaviRBNJXj14AsMwEJpACEGq4JxpPtT9HYK1zTHri+fjai4AeaS1oSlhqbbG1v2Vzg9i2nGi8ThjmTR6SGfr/vP3VzpyK6KD8xWfC0Aeabm9KWn5bY2tTxOJWYycO4Bpx7ATCfKjozhOEU0KgN6KoCKwtzhf8Q8DyCMP/0fcikUY6TyAaUepisUZy6Y5fewCelgnX/JPEQxVer6CHwUg21vXNsdN/3DjI/uxYhYj536KacWIxhPk86M40yW0kM6WfV1fIbC7tFjx2QDhRJVxePPufZjVYVJvPkPYjmFFq0kPD/HG8ffQQxrZonuawPoRAstvGICVWBrDLJ8g1z2CnUxiV1WTHhoG4aOHJFufvjDT+QhBrm9IzQBoo0Nj/OHZ1/jgV1EEEdOkIDfln+EGdz5TM1txAlgFJOa4xgUyBBm/YZ1/GCAE2IAxxzWzY+bOcX5xdbN/z/8N80SNfyLLvVEAAAAASUVORK5CYII="""
- ICON_B64 = """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAAyE2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNC4xLWMwMzQgNDYuMjcyOTc2LCBTYXQgSmFuIDI3IDIwMDcgMjI6Mzc6MzcgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhhcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx4YXA6Q3JlYXRvclRvb2w+QWRvYmUgRmlyZXdvcmtzIENTMzwveGFwOkNyZWF0b3JUb29sPgogICAgICAgICA8eGFwOkNyZWF0ZURhdGU+MjAwNy0wOC0xNlQwNzoyNjo1M1o8L3hhcDpDcmVhdGVEYXRlPgogICAgICAgICA8eGFwOk1vZGlmeURhdGU+MjAwNy0wOC0xNlQwNzozMDo0MFo8L3hhcDpNb2RpZnlEYXRlPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIj4KICAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9wbmc8L2RjOmZvcm1hdD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz6CvB0wAAACt0lEQVQ4jY3SS0hUcRQG8O/8752r4+SYmo5ZZGX0sNK0aIoeRNFjn2AhQhuhQgoLspZF5M5FCLUpxE0UBBW0qOiJIGhkVCQ9bHxUPhhHx2bmzn2d06KXGkFneQ78OJzvkIgoAP5Ix8W9qVj/KTedXC3CAWEGPEmwqG7NCF5YV9PyBICHWUUiEnj/4GxTevLrMc3IsoJFFeP5y3ZYIuKMvb2vxQZ7Ql4q4WfSz22qb2udjVCko7V6avj1lexQWW8o3KDZLpc7nuggiKHRlKGpx5/uNedNDLxaq+lz9m8+fOXpdEAlYwOnlZ5lF4UbVMr2Vlsu+wRCIlCWK4Gk7VWW7juT0nz+tGVOnQGgzQBcM75q7oKqpOVymceSSQQCiAggRfCJoMD2ZH3uog1Rcd3wX4Cw688r3ZZ2WAwiEgIJAUIEBsBE0F2P/cUVeyZEOAiAZgDMAogITZsQQQAwfvQUESkRYXH/CgGKPUmN9T4iQ6MECB7RT4OgEYFBsHRFsaGuu0HX4fjsFBQo8+V45Hmeoas+XZFJBCGCECAAHEUUNXR69/XNowp2vMivzX4DGYFQs5WK+yIPWijLUIMZmvIUkSgiNjQV9ftUT/elgzvnryz1ZZhW1a3jW2tmP5LRdfVIo50cb1J6wM5fGo4XV+xOM3NqsPM2xfo7NxauWE7Z8xfDMRPoe/wMk1/Giqovd47+AgiAr7utcYc5MXLatc0qsBcUFjgOJzVSc5Zs34Ls0EKQUogPfcDL6w/ZY6v4wNWuURKRP/cAfAD0n1kzAPfOyV01OQsK2krCYWTm5MJKfMPo6xd4eLOzryQUKJ8O/LNuHt1Sl1Nc2L6wcg0ECtGPvRh5OxCzTafkvwAAuFEfrnNMt33esgJMfZ4Ytm2nvLb9RfS/AQC4dmhDXiLtnsgy6Hxte08aAL4DjP9ad/OaKrQAAAAASUVORK5CYII="""
- SECRET_B64 = """iVBORw0KGgoAAAANSUhEUgAAACoAAAAzCAYAAADhGpoIAAADFUlEQVR42uWYPU7EMBCFc4SUlByLI1BuSUlJyTG2pKTkGFtScoR0gbHyzNuXseONHZSISKMsiYk/z59n3HUNrte+G026vV8GOZxPV7Cn08sI2SUo4IZhGM/n93DfDSyDGhgEoLuBPQwow/Z974LmYG3cn/l0AH28z2oVsCz8nn0aYt+zxW8Cax++XD6DKGhOdGywzmSlzbTKoHa3iXHX3x6kCS98MxdQreZg8Z7fBW1Oi97MXzEJYBUEv/UdS7E2nx/70WQL2BwktuNik9ugr+E5wF4+HsYWsDlIG4PxLiQ0h4E6kUECuBUsQ7IG4ZOuJg3AQCAMbZAQe7cm+ux/AHO56+K38dyeYSGL37cBMDFD4zkWY++9j9lkgFGJm8C0xdpYliLAHLBCs3YZJGjkB8KAGICfMWiw1DSmKvWktKuQwacMJiERWDRq/slucBMczKv+ypARkCCi5lSwCILkOsD28JtB2dQaPAyZ0uAMliARJFoCrgIFLEej+iMmjZICZcjp7oEi4ps1a7lIhmgUq0a1/EPxUQ0aJ1MNOqZPgV9Bkja5QqqqiDAJ58WlYIkgAgqTh79btyaaoFMpR3NlrEFloZ7JtTBerdEcpE6kbsL5UVOSB1qlVS83epApWEQ0V/pcOGtVv0qrqQSuoNxpKiyXatySaHVfdTiRC55SrXILYWA4NeG2g90AtWgz0BSsZ3qAhh2IalAAat9UB8p5cyHBz7ZgKkYYFmLPeCdk0CLo2Z6uiV9EK6xZtTVlEQbK+WRxce7uSgkBUGqbjcB33VXBjDIS4Fyx3eQCuiVqkcxmdQsW2dkYOFWYr+p4Z3v122sUDzjnvx6w9mfQbt0OZYBPD7+SAE4Jj7HfnsmripMAOYFpdCpwCSTM7hXmVbDQJneJmJgXgYV4plbIlOmrD8C0D1czssa9gNMoT/VjTU7qMPlVm0Kg3P56WlQITUVNjxPxcbvHlDQFlwe5BNAkiJZAVcuq7dLJa08KF2G7I1zsX4eATUXvLhehBxa622zif1u5yCFgUVseAnaTXPmvQYv7n8LrG9rENHlni+TvAAAAAElFTkSuQmCC"""
- DT_TEMPLATE = "%Y-%m-%d %H:%M:%S.%f"
- def vp_start_gui():
- """Starting point when module is the main routine."""
- global W, root
- if initialize():
- sys_exit()
- root = Tk()
- root.focus_force()
- root.title(SCRIPTNAME)
- root.minsize(MINDIM["x"], MINDIM["y"])
- root.geometry(RAM["geometry"])
- W = MainWindow(root)
- root.mainloop()
- def pastebin_req(url, reqcode):
- try:
- reply = urlopen(
- url, urlencode(reqcode).encode('ascii')
- ).read().decode('utf-8')
- except URLError:
- reply = "Network error"
- return reply
- def fractsashpos(pww, sash, percent=None):
- curpos = pww.sashpos(sash)
- height = pww.sashpos(sash, pww.winfo_height())
- if percent:
- pww.sashpos(sash, round(height * int(percent) / 100))
- else:
- pww.sashpos(sash, curpos)
- return round(curpos * 100 / height)
- def colsum(tvw):
- return (sum([tvw.column(col, option="width")
- for col in tvw.cget("columns")]))
- def fractcolsep(tvw, column, percent=None):
- totalw = colsum(tvw)
- if percent:
- tvw.column(column, width=(round(totalw * int(percent) / 100)))
- tvw.column(tvw.cget("columns")[-1],
- width=(totalw - tvw.column(column, option="width")))
- else:
- return round(tvw.column(column, option="width") * 100 / totalw)
- def relpath(string=None):
- reldir = path.dirname(path.realpath(__file__))
- return path.join(reldir, string) if string else reldir
- def optregexp(obj):
- return "" if obj is None else " ({})".format(
- obj.group(1)
- )
- def renameregexp(str):
- return sub(r"^(.+ )0*(\d{6}\....)$",
- r"\1\2",
- sub(IMAGE_REGEXP, r"\1 000000\2\3", str))
- @contextmanager
- def open_safe(pathto, mode, fdmode):
- try:
- file = fdopen(os_open(pathto, mode | O_CREAT), fdmode)
- except IOError as err:
- yield None, err
- else:
- try:
- yield file, None
- finally:
- file.close()
- def on_exit():
- global RUN_STATE
- if RUN_STATE == 'busy':
- ans = mbox.askokcancel(SCRIPTNAME, "A task is in progress.\nDo you want to stop it?")
- if ans:
- RUN_STATE = 'terminating'
- elif RUN_STATE == 'halting':
- RUN_STATE = 'terminating'
- elif RUN_STATE == 'idle':
- CFGDICT["geometry"] = "{w}x{h}+{x}+{y}/{p}/{v}/{i}".format(
- w=root.winfo_width(),
- h=root.winfo_height(),
- x=root.winfo_x(),
- y=root.winfo_y(),
- p=fractsashpos(W.panes, 0),
- v=fractcolsep(W.video_cl, "name"),
- i=fractcolsep(W.image_cl, "oname")
- )
- if CFGDICT != OLD_CFGDICT:
- ini = ConfigParser()
- ini.read_dict({"Settings": {}})
- ini.read_dict({"Session": {}})
- ini.set("Settings", "key_check", str(int(CFGDICT["key_check"])))
- if CFGDICT["auk"]:
- ini.set("Settings", "auk", CFGDICT["auk"])
- ini.set("Settings", "delay", str(CFGDICT["delay"]))
- ini.set("Settings", "private", str(CFGDICT["private"]))
- if CFGDICT["path"]:
- ini.set("Settings", "path", CFGDICT["path"])
- if CFGDICT["last_upload"]:
- ini.set("Session", "last_upload", str(CFGDICT["last_upload"]))
- ini.set("Session", "geometry", CFGDICT["geometry"])
- while True:
- with open_safe("faide.ini", O_WRONLY, 'w+') as (inif, err):
- if err:
- action = mbox.askretrycancel("Error", "Error occured while opening faide.ini for write:\n{}".format(err), icon="error", detail="Cancel to omit writing data to configuration file.")
- if action == "retry":
- continue
- else:
- inif.seek(0)
- inif.truncate()
- try:
- ini.write(inif)
- except:
- action = mbox.askretrycancel("Error", "Error occured while attempting to write to faide.ini.", icon="error", detail="Cancel to omit writing data to configuration file.")
- if action == "retry":
- continue
- break
- root.destroy()
- def initialize():
- chdir(path.dirname(path.realpath(__file__)))
- calc = Tk()
- if plat.startswith(('linux', 'darwin')):
- calc.wait_visibility(calc)
- calc.wm_attributes('-alpha', 0)
- calc.iconphoto(calc, PhotoImage(data=ICON_B64))
- RAM["fwidth"] = calc.winfo_rootx() - calc.winfo_x()
- RAM["fcheight"] = calc.winfo_rooty() - calc.winfo_y()
- try:
- calc.state('zoomed')
- except:
- calc.attributes('-zoomed', True)
- if plat.startswith(('linux', 'win32')):
- calc.wait_visibility(calc)
- usable_width = calc.winfo_width()
- usable_height = calc.winfo_height()
- calc.state("normal")
- calc.s = Style()
- if plat.startswith('linux'):
- calc.s.theme_use('clam')
- calc.s.layout("Treeview", [('Treeview.treearea', {'sticky': 'nswe'})])
- cb = Checkbox(calc)
- tvw = Treeview(calc)
- tvw.insert("", 0, "check")
- tvw.place(x=0, y=0)
- btn = Button(calc)
- tvw.update_idletasks()
- calc.s.configure("Treeview", rowheight=max(
- tvw.bbox("check", "#0")[3], cb.winfo_reqheight())
- )
- cb.destroy()
- tvw.update_idletasks()
- _, RAM["tvhh"], _, RAM["tvrh"] = tvw.bbox("check", "#0")
- tvw.destroy()
- RAM["bh"] = btn.winfo_reqheight()
- btn.destroy()
- w = max((usable_width // 2) - 2 * RAM["fwidth"], MINDIM["x"])
- h = max((usable_height // 2) - RAM["fwidth"] - RAM["fcheight"],
- MINDIM["y"])
- x = usable_width // 2 - w // 2 + RAM["fwidth"]
- y = usable_height // 2 - h // 2 + RAM["fcheight"]
- geom_dict = {"w": w, "h": h, "x": x, "y": y}
- default_geometry = "{w}x{h}+{x}+{y}/30/60/80".format(**geom_dict)
- calc.geometry("{w}x{h}+{x}+{y}".format(**geom_dict))
- osname = plat
- if not osname.startswith(('win32', 'linux', 'darwin')):
- mbox.showerror(SCRIPTNAME, "Sorry, {plat} is not supported.".format(plat=osname))
- return 1
- ini = ConfigParser(strict=False)
- ini.read_dict(DEFAULTS)
- with open_safe("faide.ini", O_RDWR, 'r+') as (inif, err):
- try:
- if err:
- raise err
- ini.read_file(inif)
- except:
- if err:
- mbox.showerror("Error", "Error occured while opening faide.ini.\n{}".format(err))
- return 1
- elif inif.read() != "":
- mbox.showerror("Error", "Error occured while attempting to parse faide.ini.\nfaide.ini is not a correct configuration file.", detail="Remove faide.ini from script directory to load script with default settings or provide a correct configuration file.")
- return 1
- # =====================================================================
- try:
- CFGDICT["key_check"] = ini.getboolean("Settings", "key_check")
- OLD_CFGDICT["key_check"] = CFGDICT["key_check"]
- except:
- CFGDICT["key_check"] = 1
- finally:
- RAM["check_pending"] = CFGDICT["key_check"]
- # =====================================================================
- try:
- CFGDICT["auk"] = ini.get("Settings", "auk")
- if CFGDICT["auk"] == "":
- raise NoOptionError("Settings", "auk")
- OLD_CFGDICT["auk"] = CFGDICT["auk"]
- if RAM["check_pending"]:
- reqcode = {
- 'api_option': 'userdetails',
- 'api_dev_key': DEVKEY,
- 'api_user_key': CFGDICT["auk"]
- }
- reply = pastebin_req(API_POST, reqcode)
- if reply == "Network error":
- mbox.showerror("Error", "User key check failed:\nNetwork error")
- elif "Bad API request" not in reply:
- RAM["check_pending"] = 0
- else:
- if "invalid api_user_key" in reply:
- raise NoOptionError("Settings", "auk")
- elif "Bad API request" in reply:
- mbox.showerror("Error", "User key check failed:\n" + reply)
- except NoOptionError:
- mbox.showerror("Error", "No valid user key set")
- CFGDICT["auk"] = PassDialog(calc, "User key request").result
- if not CFGDICT["auk"]:
- mbox.showerror("Error", "User key request cancelled.")
- # =====================================================================
- try:
- CFGDICT["delay"] = ini.getint("Settings", "delay")
- OLD_CFGDICT["delay"] = CFGDICT["delay"]
- except:
- CFGDICT["delay"] = DEFAULT_DELAY
- # =====================================================================
- try:
- CFGDICT["private"] = ini.getint("Settings", "private")
- OLD_CFGDICT["private"] = CFGDICT["private"]
- except:
- CFGDICT["private"] = 0
- # =====================================================================
- try:
- CFGDICT["path"] = ini.get("Settings", "path")
- OLD_CFGDICT["path"] = CFGDICT["path"]
- except:
- CFGDICT["path"] = "MediaInfo.exe"
- # =====================================================================
- try:
- CFGDICT["last_upload"] = dt.strptime(
- ini.get("Session", "last_upload"), DT_TEMPLATE
- )
- OLD_CFGDICT["last_upload"] = CFGDICT["last_upload"]
- RAM["next_upload"] = (
- CFGDICT["last_upload"] +
- td(seconds=CFGDICT["delay"])
- )
- except:
- RAM["next_upload"] = dt.now()
- # =====================================================================
- try:
- CFGDICT["geometry"] = ini.get("Session", "geometry")
- matchobj = match(r"(\d+x\d+[-+]\d+[-+]\d+)/(\d\d)/(\d\d)/(\d\d)",
- CFGDICT["geometry"])
- if not matchobj:
- raise NoOptionError("Session", "geometry")
- RAM.update(zip(("geometry", "pane", "vcol", "icol"),
- matchobj.groups()))
- OLD_CFGDICT["geometry"] = CFGDICT["geometry"]
- except:
- CFGDICT["geometry"] = default_geometry
- RAM.update(
- zip(("geometry", "pane", "vcol", "icol"),
- match(r"(\d+x\d+[-+]\d+[-+]\d+)/(\d\d)/(\d\d)/(\d\d)",
- CFGDICT["geometry"]).groups())
- )
- # =====================================================================
- inif.seek(0)
- calc.destroy()
- class PassDialog(sdiag.Dialog):
- def __init__(self, master, title=None):
- Toplevel.__init__(self, master)
- self.transient(master)
- self.resizable(0, 0)
- if title:
- self.title(title)
- self.parent = master
- self.result = None
- body = Frame(self)
- self.initial_focus = self.body(body)
- body.pack(padx=5, pady=5)
- self.buttonbox()
- self.grab_set()
- if not self.initial_focus:
- self.initial_focus = self
- self.protocol("WM_DELETE_WINDOW", self.cancel)
- self.update_idletasks()
- self.geometry("+{x}+{y}".format(
- x=(master.winfo_x() +
- (master.winfo_width() - self.winfo_width()) // 2),
- y=(master.winfo_y() +
- (master.winfo_height() - self.winfo_height()) // 2)
- ))
- self.initial_focus.focus_set()
- self.wait_window(self)
- def body(self, master):
- master.pack(fill=X, pady=5)
- master.grid_columnconfigure(0, minsize=100)
- master.grid_columnconfigure(1, minsize=300, weight=1)
- self._key_icon = PhotoImage(data=KEY_B64)
- box = Frame(master)
- Label(box, image=self._key_icon, justify='left').grid(
- row=0, column=0, sticky='w'
- )
- Label(
- box,
- text="Please provide your username\n"
- "and password to request user key.",
- justify='left'
- ).grid(row=0, column=1, sticky='w', padx=10)
- box.grid(row=0, columnspan=2, sticky='w', pady=10, padx=10)
- Label(master, text="Username:").grid(row=1, sticky='w', padx=10)
- Label(master, text="Password:").grid(row=2, sticky='w', padx=10)
- self.username_field = Entry(master)
- self.password_field = Entry(master, show="*")
- self.username_field.grid(row=1, column=1, sticky='we', pady=2, padx=10)
- self.password_field.grid(row=2, column=1, sticky='we', pady=2, padx=10)
- return self.username_field # initial focus
- def buttonbox(self):
- # add standard button box. override if you don't want the
- # standard buttons
- box = Frame(self)
- Button(
- box, text="Cancel", width=10, command=self.cancel
- ).pack(side=RIGHT, padx=10, pady=10, anchor='w')
- Button(
- box, text="OK", width=10, command=self.ok, default=ACTIVE
- ).pack(side=RIGHT, pady=10, anchor='w')
- self.bind("<Return>", self.ok)
- self.bind("<Escape>", self.cancel)
- box.pack(fill=X)
- def validate(self):
- reqcode = {
- 'api_dev_key': DEVKEY,
- 'api_user_name': self.username_field.get(),
- 'api_user_password': self.password_field.get()
- }
- reply = pastebin_req(API_LOGIN, reqcode)
- if reply == "Network error":
- mbox.showerror(SCRIPTNAME, "Pastebin login error:\nNetwork error")
- return 0
- elif "Bad API request" in reply:
- mbox.showerror(SCRIPTNAME, "Pastebin login error:\n" + reply)
- return 0
- else:
- self.result = reply
- return 1
- class MainWindow(Toplevel):
- def __init__(self, master=None):
- self.style = Style()
- if plat.startswith('linux'):
- self.style.theme_use('clam')
- if plat.startswith('darwin'):
- self.style.layout("TCheckbutton", [('Checkbutton.button', {})])
- else:
- self.style.layout("TCheckbutton", [('Checkbutton.indicator', {})])
- self.style.configure("Treeview", rowheight=RAM["tvrh"])
- self.style.configure("Treeview", borderwidth=0)
- if plat.startswith('darwin'):
- self.style.layout(
- "Treeview", [('Treeview.field', {'sticky': 'nswe'})]
- )
- else:
- self.style.layout(
- "Treeview", [('Treeview.treearea', {'sticky': 'nswe'})]
- )
- self.style.layout(
- "CL.TFrame",
- [('Treeview.field', {'sticky': 'nswe', 'border': '1'})]
- )
- self.style.configure(
- "Txt.TFrame",
- borderwidth=1
- )
- self._refresh_icon = PhotoImage(data=REFRESH_B64)
- self._pastebin_icon = PhotoImage(data=PASTEBIN_B64)
- self._stop_icon = PhotoImage(data=STOP_B64)
- self._clipboard_icon = PhotoImage(data=CLIPBOARD_B64)
- self._rename_icon = PhotoImage(data=RENAME_B64)
- self.ntbk = Notebook(master)
- self.ntbk.place(x=5, y=5, relh=1, relw=1, h=-10, w=-10)
- self.ntbk.configure(width=104)
- self.ntbk.configure(takefocus='')
- self.ntbk_pg0 = Frame(self.ntbk)
- self.ntbk.add(self.ntbk_pg0)
- self.ntbk.tab(0, text="Upload media info", underline='-1',)
- self.ntbk_pg1 = Frame(self.ntbk)
- self.ntbk.add(self.ntbk_pg1)
- self.ntbk.tab(1, text="Rename screenshots", underline='-1',)
- self.panes = Panedwindow(self.ntbk_pg0, orient='vertical')
- self.panes.place(
- x=5, y=5, relh=1, height=(-15 - RAM["bh"]), relw=1, width=-10
- )
- self.video_cl = CheckList(
- self.panes, clid=VIDEO, columns=("name", "stat")
- )
- self.panes.add(self.video_cl, weight=1)
- self.video_cl.heading("name", text="Filename")
- self.video_cl.heading("stat", text="Status")
- self.video_cl.column(
- "#0", stretch=0, width=max(RAM["tvrh"], RAM["tvhh"]), minwidth=max(
- RAM["tvrh"], RAM["tvhh"]
- )
- )
- self.video_cl.column("name", minwidth=max(RAM["tvrh"], RAM["tvhh"]))
- self.video_cl.column("stat", minwidth=max(RAM["tvrh"], RAM["tvhh"]))
- self.video_cl.var.trace('w', lambda n, m, x: self.readycheck(VIDEO))
- self.scroll_txt = ScrolledText(self.panes)
- self.video_cl.update()
- fractcolsep(self.video_cl, "name", RAM["vcol"])
- self.panes.add(self.scroll_txt, weight=1)
- self.scroll_txt.configure(background='#d9d9d9')
- self.scroll_txt.configure(highlightthickness=0)
- self.scroll_txt.configure(borderwidth='0')
- self.scroll_txt.configure(foreground='black')
- self.scroll_txt.configure(highlightbackground='#d9d9d9')
- self.scroll_txt.configure(highlightcolor='black')
- self.scroll_txt.configure(insertbackground='black')
- self.scroll_txt.configure(selectbackground='#c4c4c4')
- self.scroll_txt.configure(selectforeground='black')
- self.scroll_txt.configure(takefocus='0')
- self.scroll_txt.configure(wrap=NONE)
- self.scroll_txt.config(state=DISABLED)
- self.panes.update()
- fractsashpos(self.panes, 0, RAM["pane"])
- self.video_refresh_btn = Button(self.ntbk_pg0)
- self.video_refresh_btn.place(anchor='sw', rely=1, x=5, y=-5)
- self.video_refresh_btn.configure(
- command=lambda: self.refresh(VIDEO)
- )
- self.video_refresh_btn.configure(takefocus='')
- self.video_refresh_btn.configure(image=self._refresh_icon)
- self.mi_upload_btn = Button(self.ntbk_pg0)
- self.mi_upload_btn.place(anchor='se', relx=1, rely=1, x=-5, y=-5)
- self.mi_upload_btn.configure(command=self.upload)
- self.mi_upload_btn.configure(takefocus='')
- self.mi_upload_btn.configure(
- text="Upload media info to Pastebin"
- )
- self.mi_upload_btn.configure(image=self._pastebin_icon)
- self.mi_upload_btn.configure(compound='left')
- self.mi_upload_btn.config(state=DISABLED)
- self.clipboard_btn = Button(self.ntbk_pg0)
- self.clipboard_btn.place(
- anchor='se',
- relx=1,
- rely=1,
- x=(-self.mi_upload_btn.winfo_reqwidth() - 10),
- y=-5)
- self.clipboard_btn.configure(command=lambda: self.clipboard(VIDEO))
- self.clipboard_btn.configure(takefocus='')
- self.clipboard_btn.configure(
- text="Copy output to clipboard"
- )
- self.clipboard_btn.configure(image=self._clipboard_icon)
- self.clipboard_btn.configure(compound='left')
- self.clipboard_btn.config(state=DISABLED)
- self.image_cl = CheckList(
- self.ntbk_pg1, clid=IMAGE, columns=("oname", "nname")
- )
- self.image_cl.place(
- x=5, y=5, relh=1, height=(-15 - RAM["bh"]), relw=1, width=-10
- )
- self.image_cl.heading("oname", text="Filename")
- self.image_cl.heading("nname", text="New filename")
- self.image_cl.column(
- "#0", stretch=0, width=max(RAM["tvrh"], RAM["tvhh"]), minwidth=max(
- RAM["tvrh"], RAM["tvhh"]
- )
- )
- self.image_cl.column("oname", minwidth=max(RAM["tvrh"], RAM["tvhh"]))
- self.image_cl.column("nname", minwidth=max(RAM["tvrh"], RAM["tvhh"]))
- self.image_cl.var.trace('w', lambda n, m, x: self.readycheck(IMAGE))
- self.ntbk.select(1)
- self.image_cl.update()
- fractcolsep(self.image_cl, "oname", RAM["icol"])
- self.ntbk.select(0)
- self.image_refresh_btn = Button(self.ntbk_pg1)
- self.image_refresh_btn.place(anchor='sw', rely=1, x=5, y=-5)
- self.image_refresh_btn.configure(
- command=lambda: self.refresh(IMAGE)
- )
- self.image_refresh_btn.configure(takefocus='')
- self.image_refresh_btn.configure(image=self._refresh_icon)
- self.rename_images_btn = Button(self.ntbk_pg1)
- self.rename_images_btn.place(anchor='se', relx=1, rely=1, x=-5, y=-5)
- self.rename_images_btn.configure(command=self.picrename)
- self.rename_images_btn.configure(takefocus='')
- self.rename_images_btn.configure(text="Rename screenshots")
- self.rename_images_btn.configure(image=self._rename_icon)
- self.rename_images_btn.configure(compound='left')
- self.rename_images_btn.config(state=DISABLED)
- root.protocol('WM_DELETE_WINDOW', on_exit)
- self.refresh(VIDEO)
- self.refresh(IMAGE)
- def readycheck(self, cltype):
- if cltype == VIDEO:
- if self.video_cl.var.get() == 0:
- self.mi_upload_btn.config(state=DISABLED)
- elif ((plat.startswith('win32') and
- path.exists(CFGDICT["path"])) or
- (plat.startswith(('darwin', 'linux')) and
- which("mediainfo"))):
- self.mi_upload_btn.config(state=NORMAL)
- elif cltype == IMAGE:
- if self.image_cl.var.get() == 0 or self.image_cl.err:
- self.rename_images_btn.config(state=DISABLED)
- else:
- self.rename_images_btn.config(state=NORMAL)
- def refresh(self, cltype):
- if cltype == VIDEO:
- self.scroll_txt.config(state=NORMAL)
- self.scroll_txt.delete('1.0', END)
- self.scroll_txt.config(state=DISABLED)
- self.clipboard_btn.config(state=DISABLED)
- self.video_cl.populate(cltype)
- self.mi_upload_btn.config(state=DISABLED)
- if plat.startswith('win32'):
- if path.exists(CFGDICT["path"]):
- self.mi_upload_btn.config(state=NORMAL)
- else:
- pathto = opendialog(
- defaultextension=".exe",
- filetypes=[('Executable files', '*.exe'), ],
- initialfile="MediaInfo.exe",
- title="Locate MediaInfo.exe..."
- )
- if not pathto:
- mbox.showerror(SCRIPTNAME, "MediaInfo executable not found.\nRefresh to check for MediaInfo.exe in the script directory.\nIf not found you will be prompted to locate it manually.")
- else:
- CFGDICT["path"] = pathto
- self.mi_upload_btn.config(state=NORMAL)
- elif plat.startswith(('linux', 'darwin')):
- if which("mediainfo"):
- self.mi_upload_btn.config(state=NORMAL)
- else:
- mbox.showerror(SCRIPTNAME, "MediaInfo package is not installed.\nInstall MediaInfo package and refresh.")
- self.video_cl.event_generate("<Configure>")
- elif cltype == IMAGE:
- self.rename_images_btn.config(state=DISABLED)
- self.image_cl.populate(cltype)
- self.image_cl.event_generate("<Configure>")
- root.update()
- def clipboard(self, cltype):
- if cltype == VIDEO:
- root.clipboard_clear()
- root.clipboard_append(self.scroll_txt.get('1.0', 'end-1c'))
- def stop(self):
- global RUN_STATE
- RUN_STATE = 'halting'
- self.mi_upload_btn.config(state=DISABLED)
- def upload(self):
- global RUN_STATE
- if self.video_cl.getmarked():
- RUN_STATE = 'busy'
- self.scroll_txt.config(state=NORMAL)
- self.scroll_txt.delete('1.0', END)
- self.scroll_txt.config(state=DISABLED)
- self.video_cl.disable()
- self.video_refresh_btn.configure(state=DISABLED)
- self.mi_upload_btn.config(
- command=self.stop, text="Stop task", image=self._stop_icon
- )
- for i in self.video_cl.getmarked():
- self.video_cl.set(i, "stat", "Waiting...")
- root.update()
- output = self._upload_process()
- if RUN_STATE == 'terminating':
- root.destroy()
- if output != "":
- self.scroll_txt.config(state=NORMAL)
- self.scroll_txt.insert(END, COMPARISON.format(output))
- self.scroll_txt.config(state=DISABLED)
- self.clipboard_btn.config(state=NORMAL)
- self.mi_upload_btn.config(state=NORMAL)
- self.mi_upload_btn.config(
- command=self.upload,
- text="Upload media info to Pastebin",
- image=self._pastebin_icon
- )
- self.video_cl.enable()
- self.video_refresh_btn.configure(state=NORMAL)
- self.video_cl.unmarkall()
- RUN_STATE = 'idle'
- def _upload_process(self):
- global RUN_STATE
- from subprocess import Popen, PIPE
- if plat.startswith('win32'):
- from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
- output = ""
- if CFGDICT["auk"] and CFGDICT["auk"] != "" and RAM["check_pending"]:
- reqcode = {
- 'api_option': 'userdetails',
- 'api_dev_key': DEVKEY,
- 'api_user_key': CFGDICT["auk"]
- }
- reply = pastebin_req(API_POST, reqcode)
- if reply == "Network error":
- mbox.showerror("Error", "User key check failed:\nNetwork error")
- return ""
- elif "Bad API request" not in reply:
- RAM["check_pending"] = 0
- else:
- if "invalid api_user_key" in reply:
- CFGDICT["auk"] = None
- elif "Bad API request" in reply:
- mbox.showerror("Error", "User key check failed:\n" + reply)
- return ""
- if not CFGDICT["auk"]:
- mbox.showerror("Error", "No valid user key set")
- CFGDICT["auk"] = PassDialog(root, "User key request").result
- if not CFGDICT["auk"]:
- mbox.showerror("Error", "User key request cancelled.")
- return ""
- for i in self.video_cl.getmarked():
- self.video_cl.set(i, "stat", "Processing...")
- filen = self.video_cl.set(i, "name")
- root.update()
- if RAM["next_upload"] > dt.now():
- time_left = RAM["next_upload"] - dt.now()
- self.video_cl.set(
- i, "stat", "Waiting for " + str(time_left) + "s..."
- )
- while True:
- if RUN_STATE == 'terminating' or RUN_STATE == 'halting':
- [self.video_cl.set(item, "stat", "Cancelled") for
- item in self.video_cl.getmarked() if
- self.video_cl.set(item, "stat") != "Done"]
- mbox.showerror("Error", "Media info upload cancelled")
- return output
- time_left = RAM["next_upload"] - dt.now()
- sec = time_left.seconds
- if dt.now() > RAM["next_upload"]:
- sec = 0
- self.video_cl.set(
- i, "stat", "Waiting for " + str(sec).zfill(
- len(str(CFGDICT["delay"]))
- ) + "s..."
- )
- if RAM["next_upload"] <= dt.now():
- break
- root.update()
- sleep(.1)
- elif RUN_STATE == 'terminating' or RUN_STATE == 'halting':
- [self.video_cl.set(item, "stat", "Cancelled") for
- item in self.video_cl.getmarked() if
- self.video_cl.set(item, "stat") != "Done"]
- mbox.showerror("Error", "Media info upload cancelled")
- return output
- self.video_cl.set(i, "stat", "Extracting media info...")
- try:
- mipath = "mediainfo"
- startupinfo = None
- if plat.startswith('win32'):
- mipath = CFGDICT["path"]
- startupinfo = STARTUPINFO()
- startupinfo.dwFlags |= STARTF_USESHOWWINDOW
- process = Popen(
- [mipath, filen],
- startupinfo=startupinfo,
- stdout=PIPE
- )
- except:
- self.video_cl.set(i, "stat", "MediaInfo failure")
- root.update()
- continue
- else:
- (out, _) = process.communicate()
- info = sub(r"\r\n?|\n", r"\n", out.decode())
- exit_code = process.wait()
- if exit_code != 0:
- self.video_cl.set(
- i, "stat", "MediaInfo error: " + str(exit_code)
- )
- mbox.showerror("Error", "MediaInfo failed.\nError code: " + str(exit_code))
- root.update()
- continue
- if search(r"\n\nVideo\n", info):
- info = sub(r"www\.|http(s)?://", "", info)
- self.video_cl.set(i, "stat", "Uploading to Pastebin...")
- reqcode = {
- 'api_option': 'paste',
- 'api_paste_name': filen,
- 'api_paste_code': info,
- 'api_paste_private': CFGDICT["private"],
- 'api_dev_key': DEVKEY,
- 'api_user_key': CFGDICT["auk"]
- }
- reply = pastebin_req(API_POST, reqcode)
- if reply == "Network error":
- self.video_cl.set(
- i, "stat", "Upload error: Network error"
- )
- elif "maximum number of" in reply:
- mbox.showerror("Error", dt.now().strftime(DT_TEMPLATE) + ": MediaInfo failed.\nError code: " + str(exit_code))
- return output
- elif "Bad API request" in reply:
- self.video_cl.set(
- i, "stat", "Upload error: " + reply
- )
- else:
- self.video_cl.set(i, "stat", "Done")
- groupname = search(r"^\[(.*?)\]", filen).group(1)
- filesize = search(
- r"\nFile size *?: (.*?)\n", info
- ).group(1)
- videoinfo = search(
- r"\n\nVideo\n(.*?)\n\n", info, DOTALL
- ).group(1)
- vidformat = "{}{}{}".format(
- search(r"\nFormat *?: (.*?)\n", videoinfo).group(1),
- optregexp(search(r"\nFormat version *?: (.*?)\n",
- videoinfo)),
- optregexp(search(r"\nFormat profile *?: (.*?)\n",
- videoinfo))
- )
- chaptered = "Yes" if search(r"\n\nMenu\n", info) else "No"
- pastekey = reply.rpartition("/")[2]
- def phpescape(str):
- return str.replace("[", "{").replace("]", "}")
- output += GROUP.format(
- groupname=phpescape(groupname),
- filename=phpescape(filen),
- filesize=filesize,
- vidformat=phpescape(vidformat),
- chaptered=chaptered,
- pastekey=pastekey
- )
- CFGDICT["last_upload"] = dt.now()
- RAM["next_upload"] = (
- dt.now() + td(seconds=CFGDICT["delay"])
- )
- else:
- self.video_cl.set(i, "stat", "Not a valid video file")
- root.update()
- return output
- def picrename(self):
- self.image_cl.disable()
- self.image_refresh_btn.configure(state=DISABLED)
- for item in self.image_cl.getmarked():
- rename(
- self.image_cl.set(item, "oname"),
- self.image_cl.set(item, "nname")
- )
- self.image_cl.set(
- item, "oname", self.image_cl.set(item, "nname")
- )
- self.image_cl.set(item, "nname", "Done")
- self.image_cl.enable()
- self.image_refresh_btn.configure(state=NORMAL)
- self.image_cl.unmarkall()
- class Secret(sdiag.Dialog):
- # You found it, Sherlock!
- def __init__(self, master):
- Toplevel.__init__(self, master)
- self.transient(master)
- self.resizable(0, 0)
- frm = Frame(self)
- frm.grid(row=0, column=0)
- self._secret_icon = PhotoImage(data=SECRET_B64)
- Label(frm, image=self._secret_icon, justify='left').grid(
- row=0, column=0, sticky='w', padx=10, pady=10
- )
- Label(frm, text="But nothing happened!", justify='left').grid(
- row=0, column=1, sticky='w', padx=10, pady=10
- )
- self.update_idletasks()
- x = max(0, min(
- self.winfo_screenwidth() - self.winfo_width() - RAM["fwidth"] * 2,
- root.winfo_x() + (root.winfo_width() - self.winfo_width()) // 2
- ))
- y = max(0, min(
- (
- self.winfo_screenheight() - self.winfo_height() -
- RAM["fwidth"] - RAM["fcheight"]
- ),
- root.winfo_y() + (root.winfo_height() - self.winfo_height()) // 2
- ))
- self.geometry("+{x}+{y}".format(x=x, y=y))
- # The following code is added to facilitate the Scrolled widgets you specified.
- class AutoScroll(object):
- """Configure the scrollbars for a widget."""
- def __init__(self, master, exp=None):
- # Rozen. Added the try-except clauses so that this class
- # could be used for scrolled entry widget for which vertical
- # scrolling is not supported. 5/7/14.
- try:
- self.vsb = Scrollbar(master, orient='vertical', command=self.yview)
- except:
- pass
- self.hsb = Scrollbar(master, orient='horizontal', command=self.xview)
- try:
- self.configure(yscrollcommand=self._autoscroll(self.vsb, exp))
- except:
- pass
- self.configure(xscrollcommand=self._autoscroll(self.hsb, exp))
- self.grid(column=0, row=0, sticky='nsew')
- try:
- self.vsb.grid(column=1, row=0, sticky='ns')
- except:
- pass
- self.hsb.grid(column=0, row=1, sticky='ew')
- self.frm = Frame(master, relief=GROOVE)
- self.frm.grid(column=1, row=1, sticky='nsew')
- self.frm.bind("<Button-1>", lambda e: Secret(master))
- master.grid_columnconfigure(0, weight=1)
- master.grid_rowconfigure(0, weight=1)
- # Copy geometry methods of master (taken from ScrolledText.py)
- methods = (
- Pack.__dict__.keys() | Grid.__dict__.keys() | Place.__dict__.keys()
- )
- for meth in methods:
- if (meth[0] != "_" and meth not in ("config", "configure") and
- meth not in type(self).__bases__[0].__dict__):
- setattr(self, meth, getattr(master, meth))
- def chlst_scroll(self, exp):
- # Checklist elements scroll
- root.update()
- for i, widg in enumerate(exp[1]):
- bbx = Treeview.bbox(self, i, "#0")
- if bbx == "":
- widg.place_forget()
- else:
- x, y, _, _ = bbx
- widg.place(
- x=(x + (max(RAM["tvrh"], RAM["tvhh"]) - 2) / 2),
- y=(y + RAM["tvrh"] / 2),
- anchor='center'
- )
- exp[0].place(
- x=(x + (max(RAM["tvrh"], RAM["tvhh"]) - 2) / 2),
- anchor='center'
- )
- root.update()
- def _autoscroll(self, sbar, exp):
- """Hide and show scrollbar as needed."""
- def wrapped(first, last):
- first, last = float(first), float(last)
- if exp:
- self.chlst_scroll(exp)
- if first <= 0 and last >= 1:
- sbar.grid_remove()
- else:
- sbar.grid()
- if self.hsb.winfo_ismapped() and self.vsb.winfo_ismapped():
- self.frm.grid()
- else:
- self.frm.grid_remove()
- sbar.set(first, last)
- return wrapped
- def __str__(self):
- return str(self.master)
- def _create_container(func):
- """Creates a ttk Frame with a given master, and use this new frame to
- place the scrollbars and the widget."""
- def wrapped(cls, master, **kw):
- container = Frame(master)
- return func(cls, container, **kw)
- return wrapped
- class Checkbox(Checkbutton):
- @_create_container
- def __init__(self, master, **kw):
- Checkbutton.__init__(self, master, **kw)
- self.place(x=0, y=0)
- master.config(
- height=self.winfo_reqheight(),
- width=self.winfo_reqheight()
- )
- methods = (
- Pack.__dict__.keys() | Grid.__dict__.keys() | Place.__dict__.keys()
- )
- for meth in methods:
- if meth[0] != "_" and meth not in ("config", "configure"):
- setattr(self, meth, getattr(master, meth))
- def annihilate(self):
- self.master.destroy()
- class CheckList(AutoScroll, Treeview):
- @_create_container
- def __init__(self, master, clid, **kw):
- master.config(style="CL.TFrame", border=1)
- self._cblist = []
- self.clid = clid
- self.grabbed = None
- self._cbmaster = None
- self.err = 0
- self.var = IntVar()
- self.var.set(0)
- Treeview.__init__(self, master, **kw)
- statevar = BooleanVar()
- self._cbmaster = Checkbox(
- self, variable=statevar, command=self._toggleslave
- )
- AutoScroll.__init__(self, master, [self._cbmaster, self._cblist])
- self._cbmaster.var = statevar
- self._cbmaster.var.set(0)
- self._cbmaster.place(
- x=((max(RAM["tvrh"], RAM["tvhh"]) - 2) / 2),
- y=((RAM["tvhh"] - 2) / 2),
- anchor='center'
- )
- self._cbmaster.config(state=DISABLED)
- self.bindtags(self.bindtags() + ("CheckList",))
- self.bind_class(
- "CheckList",
- "<Configure>",
- lambda event: event.widget.chlst_scroll([
- event.widget._cbmaster, event.widget._cblist
- ])
- )
- self.bind_class(
- "CheckList",
- "<space>",
- lambda event: event.widget._togglesel(event.widget.selection())
- )
- self.bind("<Button-1>", self._headerresize_down)
- self.bind("<ButtonRelease-1>", self._headerresize_up)
- def _headerresize_down(self, event):
- if 0 or (self.identify_region(event.x, event.y) == 'separator' and
- self.identify_column(event.x) == '#0'):
- self.grabbed = '#0'
- if 0 or (self.identify_region(event.x, event.y) == 'separator' and
- self.identify_column(event.x) == '#2'):
- self.grabbed = '#2'
- def _headerresize_up(self, event):
- if self.grabbed == '#0':
- self.grabbed = False
- self.column("#1", width=(
- self.column(
- "#1", option='width'
- ) + self.column(
- "#0", option='width'
- ) - max(RAM["tvrh"], RAM["tvhh"])
- ))
- self.column("#0", width=max(RAM["tvrh"], RAM["tvhh"]))
- totalw = self.winfo_width()
- if 0 or (self.column("#0", option='width') +
- self.column("#1", option='width') +
- self.column("#2", option='width') < totalw and
- self.grabbed == '#2'):
- self.column('#2', width=(
- totalw - self.column("#0", option='width') - self.column(
- "#1", option='width'
- )
- ))
- def bbox(self, *args, **kw):
- super(Checklist, self).bbox(*args, **kw)
- def insert(self, master, index, iid=None, **kw):
- self._cbmaster.config(state=NORMAL)
- if self.var:
- self.var.set(self.var.get() + 1)
- super().insert(master, index, iid, **kw)
- statevar = BooleanVar()
- cbx = Checkbox(self, variable=statevar)
- cbx.config(command=lambda cbx=cbx: self._togglecb(cbx))
- cbx.var = statevar
- cbx.var.set(0)
- cbx.place(
- x=((max(RAM["tvrh"], RAM["tvhh"]) - 2) / 2),
- y=(RAM["tvhh"] + RAM["tvrh"] * (.5 + len(self._cblist))),
- anchor='center'
- )
- self._cblist.append(cbx)
- def delete(self, *items):
- for item in reversed(items):
- i = self.index(item)
- if self._cblist[i].var.get():
- self.var.set(self.var.get() - 1)
- for trc in self._cblist[i].var.trace_vinfo():
- self._cblist[i].var.trace_vdelete(*trc)
- self._cblist[i].annihilate()
- del self._cblist[i]
- if len(self._cblist) == 0:
- self._cbmaster.config(state=DISABLED)
- super().delete(*items)
- def markall(self):
- if not self._cbmaster.var.get():
- self._cbmaster.invoke()
- def unmarkall(self):
- if self._cbmaster.var.get():
- self._cbmaster.invoke()
- else:
- for cbx in (cbx for cbx in self._cblist if cbx.var.get()):
- cbx.invoke()
- def toggleall(self):
- self._cbmaster.invoke()
- def _toggleslave(self):
- val = self._cbmaster.var.get()
- for cbx in self._cblist:
- if cbx.var.get() != val:
- cbx.invoke()
- self.var.set(len(self._cblist) if self._cbmaster.var.get() else 0)
- def _togglesel(self, sel):
- cbsel = [self._cblist[self.index(item)] for item in sel]
- total = sum([cbx.var.get() for cbx in cbsel])
- seq = (
- cbsel if total == len(cbsel) or total == 0
- else [cbx for cbx in cbsel if not cbx.var.get()]
- )
- for cbx in seq:
- cbx.invoke()
- def toggle(self, item):
- self._cblist[self.index(item)].invoke()
- def _togglecb(self, cbx):
- cbx.var.set(cbx.var.get())
- if cbx.var.get():
- self.var.set(self.var.get() + 1)
- if self.var.get() == len(self._cblist):
- self._cbmaster.var.set(1)
- else:
- self.var.set(self.var.get() - 1)
- self._cbmaster.var.set(0)
- def observe(self, item, callback):
- self._cblist[self.index(item)].var.trace('w', callback)
- def ismarked(self, item):
- return self._cblist[self.index(item)].var.get()
- def getmarked(self):
- return [
- item for item in self.get_children()
- if self._cblist[self.index(item)].var.get() == 1
- ]
- def getcbvar(self, i):
- return self._cblist[i].var
- def disable(self):
- self._cbmaster.config(state=DISABLED)
- for cbx in self._cblist:
- cbx.config(state=DISABLED)
- def enable(self):
- self._cbmaster.config(state=NORMAL)
- for cbx in self._cblist:
- cbx.config(state=NORMAL)
- def populate(self, cltype):
- self._cbmaster.config(state=NORMAL)
- self.unmarkall()
- self.delete(*self.get_children())
- i = 0
- for filen in sorted(next(walk(getcwd()))[2]):
- if (cltype == VIDEO and search(r"^\[.+?\]", filen) and
- path.splitext(filen)[1] in VIDEO_FORMATS):
- self.insert("", "end", str(i), values=(filen, ""))
- i += 1
- elif (cltype == IMAGE and
- search(IMAGE_REGEXP, filen)):
- self.insert(
- "", "end", str(i), values=(filen, renameregexp(filen))
- )
- self.getcbvar(i).trace(
- 'w', lambda n, m, x, i=i: self._conflictcheck(i)
- )
- i += 1
- self.toggleall()
- def _conflictcheck(self, i):
- # Disable button if nothing selected.
- item = self.get_children()[i]
- if "Done" in self.set(item, "nname") and self.ismarked(item) == 1:
- if mbox.askokcancel(SCRIPTNAME, "File was already renamed.\nDo you want to refresh the list?"):
- self.populate(IMAGE)
- else:
- self.getcbvar(i).set(0)
- else:
- if 0 or (not self.ismarked(item) and
- self.set(item, "nname").startswith((
- "File exists: ", "Rename conflict: "
- ))):
- self.set(item, "nname", renameregexp(self.set(item, "oname")))
- from collections import defaultdict
- dict = defaultdict(list)
- lst = self.getmarked()
- directory = next(walk(getcwd()))[2]
- for it, item1 in enumerate(lst):
- dict[renameregexp(self.set(item1, "oname"))].append(it)
- self.err = 1
- for k, v in dict.items():
- error = ""
- if k in directory:
- error = "File exists: "
- elif len(v) > 1:
- error = "Rename conflict: "
- else:
- self.err = 0
- for it in v:
- if self.set(lst[it], "nname") != "Done":
- self.set(lst[it], "nname", error + renameregexp(
- self.set(lst[it], "oname")
- ))
- class ScrolledText(AutoScroll, Text):
- """A standard Tkinter Text widget with scrollbars that will
- automatically show/hide as needed."""
- @_create_container
- def __init__(self, master, **kw):
- master.config(style="CL.TFrame", borderwidth=1)
- Text.__init__(self, master, **kw)
- AutoScroll.__init__(self, master)
- if __name__ == '__main__':
- vp_start_gui()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement