Advertisement
HYBRID_BEING

faide

May 26th, 2016
473
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 82.66 KB | None | 0 0
  1. #! /usr/bin/env python3
  2. #
  3. # FAide v1.0.2.0
  4. # Fansub.co aide script by [HYBRID BEING], 2016
  5. # This code is public domain.
  6. #
  7. # AS: I suck at making proper documentation (and can't be bothered
  8. # to check out documentation generators), so bear with this.
  9. #
  10. # FAide is a cross-platform Python script designed to automate a portion of
  11. # Fansub.co comparison procedure. Currently it can: 1) (with the help of
  12. # MediaInfo) extract technical data from video files, upload it to Pastebin,
  13. # compile necessary data into a comparison post template; 2) batch rename
  14. # screenshots made by Aegisub to format acceptable by Fansub.co.
  15. #
  16. # FAide works on Windows, Linux (tested on Ubuntu 15.04) and OS X (look below
  17. # for more info). It requires Python 3 (tested on 3.4) and Tk 8.6 (provided
  18. # with Windows Python installer and as a separate package on Linux), utilizes
  19. # MediaInfo (as CLI version executable on Windows, package on Linux and
  20. # OS X) for grabbing media info and designed to work with screenshots
  21. # generated by Aegisub.
  22. # On OS X Python distributed by python.org binds to Tk 8.5, so you
  23. # have to get Python that binds to version 8.6 (as well as said Tk version)
  24. # from a different source - "py34-tkinter" package from MacPorts (for which
  25. # you'll have to install Xcode) will suffice. Script was tested on
  26. # OS X El Capitan 10.11 with free CLI MediaInfo package (availiable on site).
  27. #
  28. # Here are some things to keep in mind:
  29. #
  30. # - Only files starting with text (supposedly group name) in square brackets
  31. # will be picked up by FAide. So, if some group tries to be original,
  32. # for example (Hi10)_Blood_Lad_-_02_(BD_720p)_(OWA-Kaitou)_(2C0F07A0).mkv,
  33. # prepend [OWA-Kaitou] at the beginning of the filename before you start making
  34. # screens from Aegisub. This way screenshots and video file will not be ignored
  35. # by FAide.
  36. #
  37. # - FAide keeps no history of it's Pastebin uploads, so take care when
  38. # uploading repeatedly. If same file is uploaded second time it will
  39. # most likely get caught by spam-filter.
  40. #
  41. # - Free Pastebin users are required to pass captcha if they make a new
  42. # paste earlier than a minute since the last one. Pastebin does not
  43. # elaborate on it's spam-filter (Pastebin PRO ad page states that captcha
  44. # appears for free users after making more than 10 pastes in 5 minutes,
  45. # but it's not even possible to make 10 pastes in 5 minutes without captcha
  46. # appearing, what the hell?!), thus an adjustable delay is introduced.
  47. # Might be a bit slower than doing it manually, but saves you the hassle.
  48. #
  49. # - "http://", "https://" and "www." will be removed from media info to bypass
  50. # Pastebin spam-filter.
  51. #
  52. # - Paste might still get caught by spam-filter despite significant
  53. # time interval and being unique. Spam-filter is especially picky with paste
  54. # titles (e.g. it'll get triggered by word "movie"). After each upload
  55. # manually check your latest pastes, to make sure all pastes passed the filter.
  56. # If any paste gets caught by spam-filter it can be accessed via
  57. # http://pastebin.com/%ID% (see "encoding" in output) for the next 10 minutes.
  58. #
  59. # - FAide will not check if faide.ini is the correct configuration file
  60. # on termination and will attempt to overwrite it. You shouldn't rename
  61. # any important files to faide.ini during script run (duh!).
  62. #
  63. # - FAide provides video format as "Format (Format version) (Format profile)"
  64. # (Format version and/or profile might not be extracted by MediaInfo and
  65. # might be omitted by FAide). Probably works for any video, but not 100% sure.
  66. #
  67. # - You can try to resize checkbox column and make last column narrower
  68. # than remaining space, but they will revert to previous size - that's
  69. # intentional. Weirdly enough (as always with Tkinter/Tk) treeview (base
  70. # control for checklist) has minimum size but not maximum size
  71. # for column width.
  72. #
  73. # - In text field horizontal scrollbar changes and disappears during vertical
  74. # scrolling. This is due to original Tk behavior and surprisingly i was
  75. # the first to be bothered by it. Who knows if this we'll get fixed
  76. # on our lifetime...
  77. #
  78. # - Checkboxes might look weird on some systems. Normally Tk checkboxes
  79. # include background around them, i tried to crop them out, but, alas,
  80. # that doesn't work flawlessly on all systems.
  81. #
  82. # - Vertical scrollbar may sometimes not disappear in video checklist when
  83. # dragging around separator between checklist and text field. Seems to be
  84. # a problem with treeview reconfiguration due to resize of panedwindow panes.
  85. # I honestly didn't bother getting to the bottom of this, so, I dunno,
  86. # blame Tkinter/Tk, or something.
  87. #
  88. # - On Linux, "Clam" theme was selected instead of default "Motif" theme,
  89. # because reasons.
  90. #
  91. # - FAide uses faide.ini to store settings. If file is not found FAide will
  92. # start with default parameters (and prompt user for username and password)
  93. # and will try to create file on termination. Here is a list of parameters:
  94. #
  95. # key_check (default: 1)
  96. #   Check if API user key (if provided) is correct on startup
  97. # uak
  98. #   API user key required to post to Pastebin. If none provided, user will be
  99. #   prompted on startup and, if cancelled, later, before extracting
  100. #   and sending media info.
  101. # delay (default: 60)
  102. #   Amount of seconds to wait between pastes in order to avoid Pastebin
  103. #   spam-filter. PRO users might benefit from setting this to 0 (not sure).
  104. # private (default: 0)
  105. #   Make paste unlisted (1) or private (2). Most useful to PRO users.
  106. # path (default: MediaInfo)
  107. #   (Windows only) Path to the MediaInfo executable (extension may be omitted).
  108. #   If not found on this path, user will be prompted for location.
  109. # last_upload
  110. #   Datetime of the previous paste upload to Pastebin. Next upload will be
  111. #   no earlier than this time + delay. Not recommended to alter manually.
  112. # geometry
  113. #   String storing window width, height, position and some element
  114. #   measurements (WxH+X+Y/P/V/I). If not provided, width and height will be
  115. #   set to half of usable screen width and height respectively, window
  116. #   will be centered in the usable area, "P/V/I" defaults to "30/60/80".
  117. #   Not recommended to alter manually.
  118. #
  119. # Credits:
  120. # Sentynel, https://sentynel.com/project/Pastebin_Script - for Pastebin upload
  121. # script, i based this upon;
  122. # PAGE, http://page.sourceforge.net/ - for scrolling widget classes;
  123. # New Mexico Tech http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html
  124. # and effbot.org http://effbot.org - for helpful though far from
  125. # excessive (not their fault, peace) Tkinter documentation;
  126. # FatCow http://www.fatcow.com/free-icons for Farm Fresh Icons;
  127. # Whoever else i forgot and lots of good people who post code on
  128. # net (especially on Stack Overflow).
  129. #
  130. # Fun fact: FAide name comes from "Fansub.co AIDE" and isn't actually
  131. # supposed to mean anything. Accidentally, according to Wiktionary, "faide"
  132. # seems to mean "longer" in Irish, and indeed, it took me longer than
  133. # i expected to write this script.
  134. #
  135. # PS: Tkinter/Tk sucks.
  136.  
  137. from configparser import ConfigParser, NoOptionError
  138. from contextlib import contextmanager
  139. from datetime import timedelta as td
  140. from datetime import datetime as dt
  141. from os import (
  142.     chdir,
  143.     fdopen,
  144.     getcwd,
  145.     O_CREAT,
  146.     O_RDWR,
  147.     O_WRONLY,
  148.     path,
  149.     rename,
  150.     walk
  151. )
  152. from os import open as os_open
  153. from re import search, match, sub, DOTALL
  154. from shutil import which
  155. from sys import exit as sys_exit
  156. from sys import platform as plat
  157. from time import sleep
  158. from urllib.error import URLError
  159. from urllib.parse import urlencode
  160. from urllib.request import urlopen
  161.  
  162. try:
  163.     from tkinter import (
  164.         ACTIVE,
  165.         BooleanVar,
  166.         DISABLED,
  167.         END,
  168.         Grid,
  169.         GROOVE,
  170.         IntVar,
  171.         NONE,
  172.         NORMAL,
  173.         Pack,
  174.         PhotoImage,
  175.         Place,
  176.         re,
  177.         RIGHT,
  178.         sys,
  179.         Text,
  180.         Tk,
  181.         Toplevel,
  182.         ttk,
  183.         X
  184.     )
  185. except:
  186.     sys_exit()
  187. from tkinter.ttk import (
  188.     Button,
  189.     Checkbutton,
  190.     Entry,
  191.     Frame,
  192.     Label,
  193.     Notebook,
  194.     Panedwindow,
  195.     Scrollbar,
  196.     Style,
  197.     Treeview
  198. )
  199. from tkinter import simpledialog as sdiag
  200. from tkinter import messagebox as mbox
  201. from tkinter.filedialog import askopenfilename as opendialog
  202.  
  203.  
  204. def hierarchy(widget):
  205.     _id = widget.winfo_id()
  206.     return {
  207.         "path": widget.winfo_pathname(_id),
  208.         "name": widget.winfo_name(),
  209.         "id": _id,
  210.         "widget": widget,
  211.         "class": widget.winfo_class(),
  212.         "visible": widget.winfo_viewable(),
  213.         "children": tuple([hierarchy(wgt) for wgt in widget.winfo_children()])
  214.     }
  215.  
  216.  
  217. def print_hierarchy(widget):
  218.     def print_partial(info, depth):
  219.         spaces = "  " * depth
  220.         content = "{name} [{id}] <{class}> {visible}".format(**info)
  221.         print(spaces, end="")
  222.         if info["widget"] == widget:
  223.             print("** {} **".format(content))
  224.         else:
  225.             print(content)
  226.         for child in info["children"]:
  227.             print_partial(child, depth + 1)
  228.     print_partial(hierarchy(widget.nametowidget(".")), 0)
  229.  
  230.  
  231. DEVKEY = "cb219b6e4ddd5b5e321d8307085dfc3c"
  232. API_POST = "http://pastebin.com/api/api_post.php"
  233. API_LOGIN = "http://pastebin.com/api/api_login.php"
  234. VIDEO = 0
  235. IMAGE = 1
  236. MINDIM = {"x": 600, "y": 384}
  237. DEFAULT_DELAY = 60
  238. SCRIPTNAME = "FAide"
  239.  
  240. IMAGE_REGEXP = r"^\[(.+?)].+?_\d{3}_(\d+)(\.png|jpg|jpeg)$"
  241. VIDEO_FORMATS = [
  242.     ".avi", ".m2v", ".m4v", ".mkv", ".mp4", ".mpeg", ".mpg", ".ogg", ".ogv",
  243.     ".ts", ".wmv"
  244. ]
  245.  
  246. RUN_STATE = 'idle'
  247. CFGDICT = {
  248.     "key_check": None,
  249.     "auk": None,
  250.     "delay": None,
  251.     "private": None,
  252.     "path": None,
  253.     "last_upload": None,
  254.     "geometry": None
  255. }
  256. OLD_CFGDICT = {
  257.     "key_check": None,
  258.     "auk": None,
  259.     "delay": None,
  260.     "private": None,
  261.     "path": None,
  262.     "last_upload": None,
  263.     "geometry": None
  264. }
  265. RAM = {}
  266.  
  267. DEFAULTS = {
  268.     "Settings": {"key_check": "", "private": "", "delay": ""},
  269.     "Session": {"geometry": ""}
  270. }
  271.  
  272. COMPARISON = """[php]{}
  273. $poll_id = ;
  274.  
  275. require("compare.require.php");
  276. [/php]
  277. """
  278. GROUP = """
  279. $group[] = array(
  280. "name" => "{groupname}",
  281. "filename" => "{filename}",
  282. "filesize" => "{filesize}",
  283. "format" => "{vidformat}",
  284. "chaptered" => "{chaptered}",
  285. "encoding" => "{pastekey}",
  286. "tl" => "Crunchyroll edit/FUNimation edit/Original",
  287. "japanese" => "Honorifics/No honorifics, Japanisms, Western/Eastern name order",
  288. "english" => "British English/American English",
  289. "kara" => "None/Hardsubbed/Simple softsubbed/Complex softsubbed",
  290. "speed" => "Fast/Normal/Slow",
  291. // "alt" => "Information on alternate subtitle tracks if present.",
  292. // "note" => "Any other notable things about release.",
  293. );
  294. """
  295.  
  296.  
  297. 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="""
  298. PASTEBIN_B64 = """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEV5gIkAAADy8/Rha3aNlJxWWVyDiZFrdX9xeYPNzMz19vfu7/Bze4UAAAAAAAAAAADAazG7AAAAEHRSTlP/////////////////AP//c7mGeQAAAFNJREFUCJljuBsKBmcZrq5atWvVqlXLgYwoEFzMcHUpCMAYq1AYIBVLQ0EMMEBmaMG1R4EgVPEShgQIg3OmLVAqdGko5+S7EF2cvHcZwM5ouHsXAEMISXWG5CkVAAAAAElFTkSuQmCC"""
  299. RENAME_B64 = """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAZ9JREFUeNqkUzlLA0EU/mZ2ks1hQAQtRDyCFlYpFDvBSkjjj1DwiqCVB9Y2VhYKYm1jIQjmB2jphX8hhY3gUQjZze7OPF92syroisGBZR773ne8mTeCiPCfJZIS6yvzZIQFwyUSBEkau/tHoh3ysea3UZmjOP6pSA1vn5HrawTmsxViVRKsa6XgP5+ge7N6K7QPQU0/UZ3kuK84COUw+HqrjOZOXzuSAlYqg53KMa44H3guyJgPiYwEFi+eoHxt0NuZxctDDRaDhGANFjG8w9ioM3GH8wTXdWBiAi7oLxbhBo9Q8S1MV9+QVRKSSQz/c7XAzWwPNLeWKxQglGoRsM8WxnBOxY5tC0wAdgEGRSRQdgjK57JQyoLWOu7u8xDjMaj7JmSUIgI7Ue2HbTud/n4FTY04vl8utT9E7ERa+H0SbbaetFLcixoYGUL5tIaGF4RUIZ1UIJWGSGcxms9g6vwV5DkQgcc9BTyZEbjuNaBuVicnyG+YJJXS0sLt5UzXeNtvYa2ySEaqVgkfLivvHRyKPxMkzP7dN4L/Pud3AQYAKoqz1m0k8iEAAAAASUVORK5CYII="""
  300. CLIPBOARD_B64 = """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RTQ0RUQzNDVBNzNFMTFFMUJEODVCNDNGMzRFRkE4NUQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RTQ0RUQzNDZBNzNFMTFFMUJEODVCNDNGMzRFRkE4NUQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFNDRFRDM0M0E3M0UxMUUxQkQ4NUI0M0YzNEVGQTg1RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFNDRFRDM0NEE3M0UxMUUxQkQ4NUI0M0YzNEVGQTg1RCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqYwzYoAAAIRSURBVHjajFPLbtNQED3XdrHi0qikGxYtFCEiHrXKCrEjEkiAhBpAiG+gfEJZIBbwASzoB8CyKhQhkBBZ9AdQU6dNYonSiEplwUtpnJDU9mXuOHbzUCVGuvZ47pnxOXPnCiklhBAYtBeLi1KGIYSm4eH8/BBA5Skz1OPVwvU3CPfzkD50Wk7qFk6eOIUwDKBpOhYeP5N26x0CQXC1tJEVSrudFAj9dt6+9xTbP1vQIJEubSAzkUHMLn3UgnX5CYgPpidScJYe5WMmUYHAx9cfLdy5eZWDta0tfF4rJnQtK4W5G9fYf/2hwPi+AjKQUHqV7Xzbwf27c9BJe2wB7an45NQk4xS+vwBTjYKjpoERXSMp1DfVOgqH9L2vMZRxcQMPJIRUAFGjzy3vIm2ZrL3TbuOIaXJCvdnG9wfHGafwAwz4R2yFSz7Onz0DdbKuW0U2O8X7m5VydHxd/JCE2H7tNVGsuszH85poVVxO2qP44AwkBYIeSqNjY5ixZ9kvlRxcmLHZX18v9jRVHs6gXq8zWCMNntfA5obDnJuNxuEMZHCgKZfLJb7rrCFrX0z86BREH96IHd0whu5DvjwOs1rjHnTCYyjbw7iIAc07flfw9iOSKTPoT88zgt/KfKry/tM2dbkS4XsLNP5itVZ4eUWq5K4+wXQHbqCaAt2A18FqHBPdC3Oa/HH8v/2hvC/K+SfAALCk8q8J0VwxAAAAAElFTkSuQmCC"""
  301. STOP_B64 = """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAixJREFUeNp8k8trE1EUxr9778zk0dii0FbFTYsgaFOLj66sf4DgzkWl3YlaFBfqTuiq4M7ulD5wl6ILF4rQhd0FF1JrCEkQXWg24qOPPGqcGJuZ6zmTTjqJkgsHknvO9zvfPfeOQGAlzw1NSGUkon290FrTDoeAEAL2+gZcpz55/nVuya/nGhEQjwupngxduYbIwUMk1U0wIVD9/g25xwvQrnOZIE9bACyGIvHVKagfX1B5SXkpPannwnURuzgOp/8IcotzgNOAeIDkWHyCLCbiUzehNr6isvwMwjRJK/fOpl3onR3ELlyC03sY2bmH3H1yLJlZkpRIDN+6DWNrHfarF1DRLshQGNIK7QX9533Ocx3Xs47Z0iV694mTqK08hxHpgrKo2LAozEBY3j7nuW7f0WNwdWNGBp+znlnzuqDdevtSlJNUn3pDdWIXwMMqbEKaBFCqmfjv4q5CQf8sNhsZLNClLbIYgpYduvtXSjciyuWAA/ohS0UalhW4+Q4AHlwlABDU1SZimBzwQHUHCr9ISTOwy9t0ggZApkq/ptMfPqHmaBh8A6YFSZNvD943qEmtrpH+mAfrfEcHHsQHrp/d333/zPBxRCJhctHqhN0KGlq1+htrmfd4W9y+dzebn6fHVPBH7kFGCTJ6egRWLIqWgVDVn4qN1XdprO6KabfQ8jExZDY+eONUT3TGeyRtAEk2UmV7+k728yMW//M1+hCKgQ6XkPfFPuCvAAMAu7jj1EHp4WIAAAAASUVORK5CYII="""
  302. 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="""
  303. 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="""
  304. 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"""
  305.  
  306. DT_TEMPLATE = "%Y-%m-%d %H:%M:%S.%f"
  307.  
  308.  
  309. def vp_start_gui():
  310.     """Starting point when module is the main routine."""
  311.     global W, root
  312.     if initialize():
  313.         sys_exit()
  314.     root = Tk()
  315.     root.focus_force()
  316.     root.title(SCRIPTNAME)
  317.     root.minsize(MINDIM["x"], MINDIM["y"])
  318.     root.geometry(RAM["geometry"])
  319.     W = MainWindow(root)
  320.     root.mainloop()
  321.  
  322.  
  323. def pastebin_req(url, reqcode):
  324.     try:
  325.         reply = urlopen(
  326.             url, urlencode(reqcode).encode('ascii')
  327.         ).read().decode('utf-8')
  328.     except URLError:
  329.         reply = "Network error"
  330.     return reply
  331.  
  332.  
  333. def fractsashpos(pww, sash, percent=None):
  334.     curpos = pww.sashpos(sash)
  335.     height = pww.sashpos(sash, pww.winfo_height())
  336.     if percent:
  337.         pww.sashpos(sash, round(height * int(percent) / 100))
  338.     else:
  339.         pww.sashpos(sash, curpos)
  340.         return round(curpos * 100 / height)
  341.  
  342.  
  343. def colsum(tvw):
  344.     return (sum([tvw.column(col, option="width")
  345.                  for col in tvw.cget("columns")]))
  346.  
  347.  
  348. def fractcolsep(tvw, column, percent=None):
  349.     totalw = colsum(tvw)
  350.     if percent:
  351.         tvw.column(column, width=(round(totalw * int(percent) / 100)))
  352.         tvw.column(tvw.cget("columns")[-1],
  353.                    width=(totalw - tvw.column(column, option="width")))
  354.     else:
  355.         return round(tvw.column(column, option="width") * 100 / totalw)
  356.  
  357.  
  358. def relpath(string=None):
  359.     reldir = path.dirname(path.realpath(__file__))
  360.     return path.join(reldir, string) if string else reldir
  361.  
  362.  
  363. def optregexp(obj):
  364.     return "" if obj is None else " ({})".format(
  365.         obj.group(1)
  366.     )
  367.  
  368.  
  369. def renameregexp(str):
  370.     return sub(r"^(.+ )0*(\d{6}\....)$",
  371.                r"\1\2",
  372.                sub(IMAGE_REGEXP, r"\1 000000\2\3", str))
  373.  
  374.  
  375. @contextmanager
  376. def open_safe(pathto, mode, fdmode):
  377.     try:
  378.         file = fdopen(os_open(pathto, mode | O_CREAT), fdmode)
  379.     except IOError as err:
  380.         yield None, err
  381.     else:
  382.         try:
  383.             yield file, None
  384.         finally:
  385.             file.close()
  386.  
  387.  
  388. def on_exit():
  389.     global RUN_STATE
  390.     if RUN_STATE == 'busy':
  391.         ans = mbox.askokcancel(SCRIPTNAME, "A task is in progress.\nDo you want to stop it?")
  392.         if ans:
  393.             RUN_STATE = 'terminating'
  394.     elif RUN_STATE == 'halting':
  395.         RUN_STATE = 'terminating'
  396.     elif RUN_STATE == 'idle':
  397.         CFGDICT["geometry"] = "{w}x{h}+{x}+{y}/{p}/{v}/{i}".format(
  398.             w=root.winfo_width(),
  399.             h=root.winfo_height(),
  400.             x=root.winfo_x(),
  401.             y=root.winfo_y(),
  402.             p=fractsashpos(W.panes, 0),
  403.             v=fractcolsep(W.video_cl, "name"),
  404.             i=fractcolsep(W.image_cl, "oname")
  405.         )
  406.         if CFGDICT != OLD_CFGDICT:
  407.             ini = ConfigParser()
  408.             ini.read_dict({"Settings": {}})
  409.             ini.read_dict({"Session": {}})
  410.  
  411.             ini.set("Settings", "key_check", str(int(CFGDICT["key_check"])))
  412.             if CFGDICT["auk"]:
  413.                 ini.set("Settings", "auk", CFGDICT["auk"])
  414.             ini.set("Settings", "delay", str(CFGDICT["delay"]))
  415.             ini.set("Settings", "private", str(CFGDICT["private"]))
  416.             if CFGDICT["path"]:
  417.                 ini.set("Settings", "path", CFGDICT["path"])
  418.             if CFGDICT["last_upload"]:
  419.                 ini.set("Session", "last_upload", str(CFGDICT["last_upload"]))
  420.             ini.set("Session", "geometry", CFGDICT["geometry"])
  421.  
  422.             while True:
  423.                 with open_safe("faide.ini", O_WRONLY, 'w+') as (inif, err):
  424.                     if err:
  425.                         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.")
  426.                         if action == "retry":
  427.                             continue
  428.                     else:
  429.                         inif.seek(0)
  430.                         inif.truncate()
  431.                         try:
  432.                             ini.write(inif)
  433.                         except:
  434.                             action = mbox.askretrycancel("Error", "Error occured while attempting to write to faide.ini.", icon="error", detail="Cancel to omit writing data to configuration file.")
  435.                             if action == "retry":
  436.                                 continue
  437.                 break
  438.         root.destroy()
  439.  
  440.  
  441. def initialize():
  442.     chdir(path.dirname(path.realpath(__file__)))
  443.     calc = Tk()
  444.     if plat.startswith(('linux', 'darwin')):
  445.         calc.wait_visibility(calc)
  446.     calc.wm_attributes('-alpha', 0)
  447.     calc.iconphoto(calc, PhotoImage(data=ICON_B64))
  448.  
  449.     RAM["fwidth"] = calc.winfo_rootx() - calc.winfo_x()
  450.     RAM["fcheight"] = calc.winfo_rooty() - calc.winfo_y()
  451.     try:
  452.         calc.state('zoomed')
  453.     except:
  454.         calc.attributes('-zoomed', True)
  455.     if plat.startswith(('linux', 'win32')):
  456.         calc.wait_visibility(calc)
  457.     usable_width = calc.winfo_width()
  458.     usable_height = calc.winfo_height()
  459.     calc.state("normal")
  460.  
  461.     calc.s = Style()
  462.     if plat.startswith('linux'):
  463.         calc.s.theme_use('clam')
  464.     calc.s.layout("Treeview", [('Treeview.treearea', {'sticky': 'nswe'})])
  465.     cb = Checkbox(calc)
  466.     tvw = Treeview(calc)
  467.     tvw.insert("", 0, "check")
  468.     tvw.place(x=0, y=0)
  469.     btn = Button(calc)
  470.     tvw.update_idletasks()
  471.     calc.s.configure("Treeview", rowheight=max(
  472.         tvw.bbox("check", "#0")[3], cb.winfo_reqheight())
  473.     )
  474.     cb.destroy()
  475.  
  476.     tvw.update_idletasks()
  477.     _, RAM["tvhh"], _, RAM["tvrh"] = tvw.bbox("check", "#0")
  478.     tvw.destroy()
  479.  
  480.     RAM["bh"] = btn.winfo_reqheight()
  481.     btn.destroy()
  482.  
  483.     w = max((usable_width // 2) - 2 * RAM["fwidth"], MINDIM["x"])
  484.     h = max((usable_height // 2) - RAM["fwidth"] - RAM["fcheight"],
  485.             MINDIM["y"])
  486.     x = usable_width // 2 - w // 2 + RAM["fwidth"]
  487.     y = usable_height // 2 - h // 2 + RAM["fcheight"]
  488.     geom_dict = {"w": w, "h": h, "x": x, "y": y}
  489.     default_geometry = "{w}x{h}+{x}+{y}/30/60/80".format(**geom_dict)
  490.     calc.geometry("{w}x{h}+{x}+{y}".format(**geom_dict))
  491.  
  492.     osname = plat
  493.     if not osname.startswith(('win32', 'linux', 'darwin')):
  494.         mbox.showerror(SCRIPTNAME, "Sorry, {plat} is not supported.".format(plat=osname))
  495.         return 1
  496.  
  497.     ini = ConfigParser(strict=False)
  498.     ini.read_dict(DEFAULTS)
  499.     with open_safe("faide.ini", O_RDWR, 'r+') as (inif, err):
  500.         try:
  501.             if err:
  502.                 raise err
  503.             ini.read_file(inif)
  504.         except:
  505.             if err:
  506.                 mbox.showerror("Error", "Error occured while opening faide.ini.\n{}".format(err))
  507.                 return 1
  508.             elif inif.read() != "":
  509.                 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.")
  510.                 return 1
  511.         # =====================================================================
  512.         try:
  513.             CFGDICT["key_check"] = ini.getboolean("Settings", "key_check")
  514.             OLD_CFGDICT["key_check"] = CFGDICT["key_check"]
  515.         except:
  516.             CFGDICT["key_check"] = 1
  517.         finally:
  518.             RAM["check_pending"] = CFGDICT["key_check"]
  519.         # =====================================================================
  520.         try:
  521.             CFGDICT["auk"] = ini.get("Settings", "auk")
  522.             if CFGDICT["auk"] == "":
  523.                 raise NoOptionError("Settings", "auk")
  524.             OLD_CFGDICT["auk"] = CFGDICT["auk"]
  525.             if RAM["check_pending"]:
  526.                 reqcode = {
  527.                     'api_option': 'userdetails',
  528.                     'api_dev_key': DEVKEY,
  529.                     'api_user_key': CFGDICT["auk"]
  530.                 }
  531.                 reply = pastebin_req(API_POST, reqcode)
  532.                 if reply == "Network error":
  533.                     mbox.showerror("Error", "User key check failed:\nNetwork error")
  534.                 elif "Bad API request" not in reply:
  535.                     RAM["check_pending"] = 0
  536.                 else:
  537.                     if "invalid api_user_key" in reply:
  538.                         raise NoOptionError("Settings", "auk")
  539.                     elif "Bad API request" in reply:
  540.                         mbox.showerror("Error", "User key check failed:\n" + reply)
  541.         except NoOptionError:
  542.             mbox.showerror("Error", "No valid user key set")
  543.             CFGDICT["auk"] = PassDialog(calc, "User key request").result
  544.         if not CFGDICT["auk"]:
  545.             mbox.showerror("Error", "User key request cancelled.")
  546.         # =====================================================================
  547.         try:
  548.             CFGDICT["delay"] = ini.getint("Settings", "delay")
  549.             OLD_CFGDICT["delay"] = CFGDICT["delay"]
  550.         except:
  551.             CFGDICT["delay"] = DEFAULT_DELAY
  552.         # =====================================================================
  553.         try:
  554.             CFGDICT["private"] = ini.getint("Settings", "private")
  555.             OLD_CFGDICT["private"] = CFGDICT["private"]
  556.         except:
  557.             CFGDICT["private"] = 0
  558.         # =====================================================================
  559.         try:
  560.             CFGDICT["path"] = ini.get("Settings", "path")
  561.             OLD_CFGDICT["path"] = CFGDICT["path"]
  562.         except:
  563.             CFGDICT["path"] = "MediaInfo.exe"
  564.         # =====================================================================
  565.         try:
  566.             CFGDICT["last_upload"] = dt.strptime(
  567.                 ini.get("Session", "last_upload"), DT_TEMPLATE
  568.             )
  569.             OLD_CFGDICT["last_upload"] = CFGDICT["last_upload"]
  570.             RAM["next_upload"] = (
  571.                 CFGDICT["last_upload"] +
  572.                 td(seconds=CFGDICT["delay"])
  573.             )
  574.         except:
  575.             RAM["next_upload"] = dt.now()
  576.         # =====================================================================
  577.         try:
  578.             CFGDICT["geometry"] = ini.get("Session", "geometry")
  579.             matchobj = match(r"(\d+x\d+[-+]\d+[-+]\d+)/(\d\d)/(\d\d)/(\d\d)",
  580.                              CFGDICT["geometry"])
  581.             if not matchobj:
  582.                 raise NoOptionError("Session", "geometry")
  583.             RAM.update(zip(("geometry", "pane", "vcol", "icol"),
  584.                            matchobj.groups()))
  585.             OLD_CFGDICT["geometry"] = CFGDICT["geometry"]
  586.         except:
  587.             CFGDICT["geometry"] = default_geometry
  588.             RAM.update(
  589.                 zip(("geometry", "pane", "vcol", "icol"),
  590.                     match(r"(\d+x\d+[-+]\d+[-+]\d+)/(\d\d)/(\d\d)/(\d\d)",
  591.                           CFGDICT["geometry"]).groups())
  592.             )
  593.         # =====================================================================
  594.         inif.seek(0)
  595.     calc.destroy()
  596.  
  597.  
  598. class PassDialog(sdiag.Dialog):
  599.     def __init__(self, master, title=None):
  600.         Toplevel.__init__(self, master)
  601.         self.transient(master)
  602.         self.resizable(0, 0)
  603.  
  604.         if title:
  605.             self.title(title)
  606.  
  607.         self.parent = master
  608.         self.result = None
  609.  
  610.         body = Frame(self)
  611.         self.initial_focus = self.body(body)
  612.         body.pack(padx=5, pady=5)
  613.  
  614.         self.buttonbox()
  615.  
  616.         self.grab_set()
  617.  
  618.         if not self.initial_focus:
  619.             self.initial_focus = self
  620.  
  621.         self.protocol("WM_DELETE_WINDOW", self.cancel)
  622.  
  623.         self.update_idletasks()
  624.  
  625.         self.geometry("+{x}+{y}".format(
  626.             x=(master.winfo_x() +
  627.                (master.winfo_width() - self.winfo_width()) // 2),
  628.             y=(master.winfo_y() +
  629.                (master.winfo_height() - self.winfo_height()) // 2)
  630.         ))
  631.  
  632.         self.initial_focus.focus_set()
  633.         self.wait_window(self)
  634.  
  635.     def body(self, master):
  636.  
  637.         master.pack(fill=X, pady=5)
  638.         master.grid_columnconfigure(0, minsize=100)
  639.         master.grid_columnconfigure(1, minsize=300, weight=1)
  640.  
  641.         self._key_icon = PhotoImage(data=KEY_B64)
  642.  
  643.         box = Frame(master)
  644.         Label(box, image=self._key_icon, justify='left').grid(
  645.             row=0, column=0, sticky='w'
  646.         )
  647.         Label(
  648.             box,
  649.             text="Please provide your username\n"
  650.                  "and password to request user key.",
  651.             justify='left'
  652.         ).grid(row=0, column=1, sticky='w', padx=10)
  653.         box.grid(row=0, columnspan=2, sticky='w', pady=10, padx=10)
  654.  
  655.         Label(master, text="Username:").grid(row=1, sticky='w', padx=10)
  656.         Label(master, text="Password:").grid(row=2, sticky='w', padx=10)
  657.  
  658.         self.username_field = Entry(master)
  659.         self.password_field = Entry(master, show="*")
  660.  
  661.         self.username_field.grid(row=1, column=1, sticky='we', pady=2, padx=10)
  662.         self.password_field.grid(row=2, column=1, sticky='we', pady=2, padx=10)
  663.  
  664.         return self.username_field  # initial focus
  665.  
  666.     def buttonbox(self):
  667.         # add standard button box. override if you don't want the
  668.         # standard buttons
  669.  
  670.         box = Frame(self)
  671.  
  672.         Button(
  673.             box, text="Cancel", width=10, command=self.cancel
  674.         ).pack(side=RIGHT, padx=10, pady=10, anchor='w')
  675.         Button(
  676.             box, text="OK", width=10, command=self.ok, default=ACTIVE
  677.         ).pack(side=RIGHT, pady=10, anchor='w')
  678.  
  679.         self.bind("<Return>", self.ok)
  680.         self.bind("<Escape>", self.cancel)
  681.  
  682.         box.pack(fill=X)
  683.  
  684.     def validate(self):
  685.         reqcode = {
  686.             'api_dev_key': DEVKEY,
  687.             'api_user_name': self.username_field.get(),
  688.             'api_user_password': self.password_field.get()
  689.         }
  690.         reply = pastebin_req(API_LOGIN, reqcode)
  691.         if reply == "Network error":
  692.             mbox.showerror(SCRIPTNAME, "Pastebin login error:\nNetwork error")
  693.             return 0
  694.         elif "Bad API request" in reply:
  695.             mbox.showerror(SCRIPTNAME, "Pastebin login error:\n" + reply)
  696.             return 0
  697.         else:
  698.             self.result = reply
  699.             return 1
  700.  
  701.  
  702. class MainWindow(Toplevel):
  703.     def __init__(self, master=None):
  704.         self.style = Style()
  705.         if plat.startswith('linux'):
  706.             self.style.theme_use('clam')
  707.  
  708.         if plat.startswith('darwin'):
  709.             self.style.layout("TCheckbutton", [('Checkbutton.button', {})])
  710.         else:
  711.             self.style.layout("TCheckbutton", [('Checkbutton.indicator', {})])
  712.         self.style.configure("Treeview", rowheight=RAM["tvrh"])
  713.         self.style.configure("Treeview", borderwidth=0)
  714.         if plat.startswith('darwin'):
  715.             self.style.layout(
  716.                 "Treeview", [('Treeview.field', {'sticky': 'nswe'})]
  717.             )
  718.         else:
  719.             self.style.layout(
  720.                 "Treeview", [('Treeview.treearea', {'sticky': 'nswe'})]
  721.             )
  722.         self.style.layout(
  723.             "CL.TFrame",
  724.             [('Treeview.field', {'sticky': 'nswe', 'border': '1'})]
  725.         )
  726.         self.style.configure(
  727.             "Txt.TFrame",
  728.             borderwidth=1
  729.         )
  730.  
  731.         self._refresh_icon = PhotoImage(data=REFRESH_B64)
  732.         self._pastebin_icon = PhotoImage(data=PASTEBIN_B64)
  733.         self._stop_icon = PhotoImage(data=STOP_B64)
  734.         self._clipboard_icon = PhotoImage(data=CLIPBOARD_B64)
  735.         self._rename_icon = PhotoImage(data=RENAME_B64)
  736.  
  737.         self.ntbk = Notebook(master)
  738.         self.ntbk.place(x=5, y=5, relh=1, relw=1, h=-10, w=-10)
  739.         self.ntbk.configure(width=104)
  740.         self.ntbk.configure(takefocus='')
  741.         self.ntbk_pg0 = Frame(self.ntbk)
  742.         self.ntbk.add(self.ntbk_pg0)
  743.         self.ntbk.tab(0, text="Upload media info", underline='-1',)
  744.         self.ntbk_pg1 = Frame(self.ntbk)
  745.         self.ntbk.add(self.ntbk_pg1)
  746.         self.ntbk.tab(1, text="Rename screenshots", underline='-1',)
  747.  
  748.         self.panes = Panedwindow(self.ntbk_pg0, orient='vertical')
  749.         self.panes.place(
  750.             x=5, y=5, relh=1, height=(-15 - RAM["bh"]), relw=1, width=-10
  751.         )
  752.         self.video_cl = CheckList(
  753.             self.panes, clid=VIDEO, columns=("name", "stat")
  754.         )
  755.         self.panes.add(self.video_cl, weight=1)
  756.         self.video_cl.heading("name", text="Filename")
  757.         self.video_cl.heading("stat", text="Status")
  758.         self.video_cl.column(
  759.             "#0", stretch=0, width=max(RAM["tvrh"], RAM["tvhh"]), minwidth=max(
  760.                 RAM["tvrh"], RAM["tvhh"]
  761.             )
  762.         )
  763.         self.video_cl.column("name", minwidth=max(RAM["tvrh"], RAM["tvhh"]))
  764.         self.video_cl.column("stat", minwidth=max(RAM["tvrh"], RAM["tvhh"]))
  765.         self.video_cl.var.trace('w', lambda n, m, x: self.readycheck(VIDEO))
  766.         self.scroll_txt = ScrolledText(self.panes)
  767.  
  768.         self.video_cl.update()
  769.         fractcolsep(self.video_cl, "name", RAM["vcol"])
  770.  
  771.         self.panes.add(self.scroll_txt, weight=1)
  772.         self.scroll_txt.configure(background='#d9d9d9')
  773.         self.scroll_txt.configure(highlightthickness=0)
  774.         self.scroll_txt.configure(borderwidth='0')
  775.         self.scroll_txt.configure(foreground='black')
  776.         self.scroll_txt.configure(highlightbackground='#d9d9d9')
  777.         self.scroll_txt.configure(highlightcolor='black')
  778.         self.scroll_txt.configure(insertbackground='black')
  779.         self.scroll_txt.configure(selectbackground='#c4c4c4')
  780.         self.scroll_txt.configure(selectforeground='black')
  781.         self.scroll_txt.configure(takefocus='0')
  782.         self.scroll_txt.configure(wrap=NONE)
  783.         self.scroll_txt.config(state=DISABLED)
  784.  
  785.         self.panes.update()
  786.         fractsashpos(self.panes, 0, RAM["pane"])
  787.  
  788.         self.video_refresh_btn = Button(self.ntbk_pg0)
  789.         self.video_refresh_btn.place(anchor='sw', rely=1, x=5, y=-5)
  790.         self.video_refresh_btn.configure(
  791.             command=lambda: self.refresh(VIDEO)
  792.         )
  793.         self.video_refresh_btn.configure(takefocus='')
  794.         self.video_refresh_btn.configure(image=self._refresh_icon)
  795.  
  796.         self.mi_upload_btn = Button(self.ntbk_pg0)
  797.         self.mi_upload_btn.place(anchor='se', relx=1, rely=1, x=-5, y=-5)
  798.         self.mi_upload_btn.configure(command=self.upload)
  799.         self.mi_upload_btn.configure(takefocus='')
  800.         self.mi_upload_btn.configure(
  801.             text="Upload media info to Pastebin"
  802.         )
  803.         self.mi_upload_btn.configure(image=self._pastebin_icon)
  804.         self.mi_upload_btn.configure(compound='left')
  805.         self.mi_upload_btn.config(state=DISABLED)
  806.  
  807.         self.clipboard_btn = Button(self.ntbk_pg0)
  808.         self.clipboard_btn.place(
  809.             anchor='se',
  810.             relx=1,
  811.             rely=1,
  812.             x=(-self.mi_upload_btn.winfo_reqwidth() - 10),
  813.             y=-5)
  814.         self.clipboard_btn.configure(command=lambda: self.clipboard(VIDEO))
  815.         self.clipboard_btn.configure(takefocus='')
  816.         self.clipboard_btn.configure(
  817.             text="Copy output to clipboard"
  818.         )
  819.         self.clipboard_btn.configure(image=self._clipboard_icon)
  820.         self.clipboard_btn.configure(compound='left')
  821.         self.clipboard_btn.config(state=DISABLED)
  822.  
  823.         self.image_cl = CheckList(
  824.             self.ntbk_pg1, clid=IMAGE, columns=("oname", "nname")
  825.         )
  826.         self.image_cl.place(
  827.             x=5, y=5, relh=1, height=(-15 - RAM["bh"]), relw=1, width=-10
  828.         )
  829.         self.image_cl.heading("oname", text="Filename")
  830.         self.image_cl.heading("nname", text="New filename")
  831.         self.image_cl.column(
  832.             "#0", stretch=0, width=max(RAM["tvrh"], RAM["tvhh"]), minwidth=max(
  833.                 RAM["tvrh"], RAM["tvhh"]
  834.             )
  835.         )
  836.         self.image_cl.column("oname", minwidth=max(RAM["tvrh"], RAM["tvhh"]))
  837.         self.image_cl.column("nname", minwidth=max(RAM["tvrh"], RAM["tvhh"]))
  838.         self.image_cl.var.trace('w', lambda n, m, x: self.readycheck(IMAGE))
  839.  
  840.         self.ntbk.select(1)
  841.         self.image_cl.update()
  842.         fractcolsep(self.image_cl, "oname", RAM["icol"])
  843.         self.ntbk.select(0)
  844.  
  845.         self.image_refresh_btn = Button(self.ntbk_pg1)
  846.         self.image_refresh_btn.place(anchor='sw', rely=1, x=5, y=-5)
  847.         self.image_refresh_btn.configure(
  848.             command=lambda: self.refresh(IMAGE)
  849.         )
  850.         self.image_refresh_btn.configure(takefocus='')
  851.         self.image_refresh_btn.configure(image=self._refresh_icon)
  852.  
  853.         self.rename_images_btn = Button(self.ntbk_pg1)
  854.         self.rename_images_btn.place(anchor='se', relx=1, rely=1, x=-5, y=-5)
  855.         self.rename_images_btn.configure(command=self.picrename)
  856.         self.rename_images_btn.configure(takefocus='')
  857.         self.rename_images_btn.configure(text="Rename screenshots")
  858.         self.rename_images_btn.configure(image=self._rename_icon)
  859.         self.rename_images_btn.configure(compound='left')
  860.         self.rename_images_btn.config(state=DISABLED)
  861.  
  862.         root.protocol('WM_DELETE_WINDOW', on_exit)
  863.  
  864.         self.refresh(VIDEO)
  865.         self.refresh(IMAGE)
  866.  
  867.     def readycheck(self, cltype):
  868.         if cltype == VIDEO:
  869.             if self.video_cl.var.get() == 0:
  870.                 self.mi_upload_btn.config(state=DISABLED)
  871.             elif ((plat.startswith('win32') and
  872.                    path.exists(CFGDICT["path"])) or
  873.                   (plat.startswith(('darwin', 'linux')) and
  874.                    which("mediainfo"))):
  875.                 self.mi_upload_btn.config(state=NORMAL)
  876.         elif cltype == IMAGE:
  877.             if self.image_cl.var.get() == 0 or self.image_cl.err:
  878.                 self.rename_images_btn.config(state=DISABLED)
  879.             else:
  880.                 self.rename_images_btn.config(state=NORMAL)
  881.  
  882.     def refresh(self, cltype):
  883.         if cltype == VIDEO:
  884.             self.scroll_txt.config(state=NORMAL)
  885.             self.scroll_txt.delete('1.0', END)
  886.             self.scroll_txt.config(state=DISABLED)
  887.             self.clipboard_btn.config(state=DISABLED)
  888.             self.video_cl.populate(cltype)
  889.             self.mi_upload_btn.config(state=DISABLED)
  890.             if plat.startswith('win32'):
  891.                 if path.exists(CFGDICT["path"]):
  892.                     self.mi_upload_btn.config(state=NORMAL)
  893.                 else:
  894.                     pathto = opendialog(
  895.                         defaultextension=".exe",
  896.                         filetypes=[('Executable files', '*.exe'), ],
  897.                         initialfile="MediaInfo.exe",
  898.                         title="Locate MediaInfo.exe..."
  899.                     )
  900.                     if not pathto:
  901.                         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.")
  902.                     else:
  903.                         CFGDICT["path"] = pathto
  904.                         self.mi_upload_btn.config(state=NORMAL)
  905.             elif plat.startswith(('linux', 'darwin')):
  906.                 if which("mediainfo"):
  907.                     self.mi_upload_btn.config(state=NORMAL)
  908.                 else:
  909.                     mbox.showerror(SCRIPTNAME, "MediaInfo package is not installed.\nInstall MediaInfo package and refresh.")
  910.             self.video_cl.event_generate("<Configure>")
  911.         elif cltype == IMAGE:
  912.             self.rename_images_btn.config(state=DISABLED)
  913.             self.image_cl.populate(cltype)
  914.             self.image_cl.event_generate("<Configure>")
  915.         root.update()
  916.  
  917.     def clipboard(self, cltype):
  918.         if cltype == VIDEO:
  919.             root.clipboard_clear()
  920.             root.clipboard_append(self.scroll_txt.get('1.0', 'end-1c'))
  921.  
  922.     def stop(self):
  923.         global RUN_STATE
  924.         RUN_STATE = 'halting'
  925.         self.mi_upload_btn.config(state=DISABLED)
  926.  
  927.     def upload(self):
  928.         global RUN_STATE
  929.         if self.video_cl.getmarked():
  930.             RUN_STATE = 'busy'
  931.             self.scroll_txt.config(state=NORMAL)
  932.             self.scroll_txt.delete('1.0', END)
  933.             self.scroll_txt.config(state=DISABLED)
  934.             self.video_cl.disable()
  935.             self.video_refresh_btn.configure(state=DISABLED)
  936.             self.mi_upload_btn.config(
  937.                 command=self.stop, text="Stop task", image=self._stop_icon
  938.             )
  939.             for i in self.video_cl.getmarked():
  940.                 self.video_cl.set(i, "stat", "Waiting...")
  941.             root.update()
  942.             output = self._upload_process()
  943.             if RUN_STATE == 'terminating':
  944.                 root.destroy()
  945.             if output != "":
  946.                 self.scroll_txt.config(state=NORMAL)
  947.                 self.scroll_txt.insert(END, COMPARISON.format(output))
  948.                 self.scroll_txt.config(state=DISABLED)
  949.                 self.clipboard_btn.config(state=NORMAL)
  950.             self.mi_upload_btn.config(state=NORMAL)
  951.             self.mi_upload_btn.config(
  952.                 command=self.upload,
  953.                 text="Upload media info to Pastebin",
  954.                 image=self._pastebin_icon
  955.             )
  956.             self.video_cl.enable()
  957.             self.video_refresh_btn.configure(state=NORMAL)
  958.             self.video_cl.unmarkall()
  959.             RUN_STATE = 'idle'
  960.  
  961.     def _upload_process(self):
  962.         global RUN_STATE
  963.         from subprocess import Popen, PIPE
  964.         if plat.startswith('win32'):
  965.             from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
  966.         output = ""
  967.         if CFGDICT["auk"] and CFGDICT["auk"] != "" and RAM["check_pending"]:
  968.             reqcode = {
  969.                 'api_option': 'userdetails',
  970.                 'api_dev_key': DEVKEY,
  971.                 'api_user_key': CFGDICT["auk"]
  972.             }
  973.             reply = pastebin_req(API_POST, reqcode)
  974.             if reply == "Network error":
  975.                 mbox.showerror("Error", "User key check failed:\nNetwork error")
  976.                 return ""
  977.             elif "Bad API request" not in reply:
  978.                 RAM["check_pending"] = 0
  979.             else:
  980.                 if "invalid api_user_key" in reply:
  981.                     CFGDICT["auk"] = None
  982.                 elif "Bad API request" in reply:
  983.                     mbox.showerror("Error", "User key check failed:\n" + reply)
  984.                     return ""
  985.         if not CFGDICT["auk"]:
  986.             mbox.showerror("Error", "No valid user key set")
  987.             CFGDICT["auk"] = PassDialog(root, "User key request").result
  988.             if not CFGDICT["auk"]:
  989.                 mbox.showerror("Error", "User key request cancelled.")
  990.                 return ""
  991.         for i in self.video_cl.getmarked():
  992.             self.video_cl.set(i, "stat", "Processing...")
  993.             filen = self.video_cl.set(i, "name")
  994.             root.update()
  995.             if RAM["next_upload"] > dt.now():
  996.                 time_left = RAM["next_upload"] - dt.now()
  997.                 self.video_cl.set(
  998.                     i, "stat", "Waiting for " + str(time_left) + "s..."
  999.                 )
  1000.                 while True:
  1001.                     if RUN_STATE == 'terminating' or RUN_STATE == 'halting':
  1002.                         [self.video_cl.set(item, "stat", "Cancelled") for
  1003.                          item in self.video_cl.getmarked() if
  1004.                          self.video_cl.set(item, "stat") != "Done"]
  1005.                         mbox.showerror("Error", "Media info upload cancelled")
  1006.                         return output
  1007.                     time_left = RAM["next_upload"] - dt.now()
  1008.                     sec = time_left.seconds
  1009.                     if dt.now() > RAM["next_upload"]:
  1010.                         sec = 0
  1011.                     self.video_cl.set(
  1012.                         i, "stat", "Waiting for " + str(sec).zfill(
  1013.                             len(str(CFGDICT["delay"]))
  1014.                         ) + "s..."
  1015.                     )
  1016.                     if RAM["next_upload"] <= dt.now():
  1017.                         break
  1018.                     root.update()
  1019.                     sleep(.1)
  1020.             elif RUN_STATE == 'terminating' or RUN_STATE == 'halting':
  1021.                 [self.video_cl.set(item, "stat", "Cancelled") for
  1022.                  item in self.video_cl.getmarked() if
  1023.                  self.video_cl.set(item, "stat") != "Done"]
  1024.                 mbox.showerror("Error", "Media info upload cancelled")
  1025.                 return output
  1026.  
  1027.             self.video_cl.set(i, "stat", "Extracting media info...")
  1028.  
  1029.             try:
  1030.                 mipath = "mediainfo"
  1031.                 startupinfo = None
  1032.                 if plat.startswith('win32'):
  1033.                     mipath = CFGDICT["path"]
  1034.                     startupinfo = STARTUPINFO()
  1035.                     startupinfo.dwFlags |= STARTF_USESHOWWINDOW
  1036.                 process = Popen(
  1037.                     [mipath, filen],
  1038.                     startupinfo=startupinfo,
  1039.                     stdout=PIPE
  1040.                 )
  1041.             except:
  1042.                 self.video_cl.set(i, "stat", "MediaInfo failure")
  1043.                 root.update()
  1044.                 continue
  1045.             else:
  1046.                 (out, _) = process.communicate()
  1047.                 info = sub(r"\r\n?|\n", r"\n", out.decode())
  1048.                 exit_code = process.wait()
  1049.                 if exit_code != 0:
  1050.                     self.video_cl.set(
  1051.                         i, "stat", "MediaInfo error: " + str(exit_code)
  1052.                     )
  1053.                     mbox.showerror("Error", "MediaInfo failed.\nError code: " + str(exit_code))
  1054.                     root.update()
  1055.                     continue
  1056.  
  1057.             if search(r"\n\nVideo\n", info):
  1058.                 info = sub(r"www\.|http(s)?://", "", info)
  1059.                 self.video_cl.set(i, "stat", "Uploading to Pastebin...")
  1060.                 reqcode = {
  1061.                     'api_option': 'paste',
  1062.                     'api_paste_name': filen,
  1063.                     'api_paste_code': info,
  1064.                     'api_paste_private': CFGDICT["private"],
  1065.                     'api_dev_key': DEVKEY,
  1066.                     'api_user_key': CFGDICT["auk"]
  1067.                 }
  1068.                 reply = pastebin_req(API_POST, reqcode)
  1069.                 if reply == "Network error":
  1070.                     self.video_cl.set(
  1071.                         i, "stat", "Upload error: Network error"
  1072.                     )
  1073.                 elif "maximum number of" in reply:
  1074.                     mbox.showerror("Error", dt.now().strftime(DT_TEMPLATE) + ": MediaInfo failed.\nError code: " + str(exit_code))
  1075.                     return output
  1076.                 elif "Bad API request" in reply:
  1077.                     self.video_cl.set(
  1078.                         i, "stat", "Upload error: " + reply
  1079.                     )
  1080.                 else:
  1081.                     self.video_cl.set(i, "stat", "Done")
  1082.  
  1083.                     groupname = search(r"^\[(.*?)\]", filen).group(1)
  1084.                     filesize = search(
  1085.                         r"\nFile size *?: (.*?)\n", info
  1086.                     ).group(1)
  1087.                     videoinfo = search(
  1088.                         r"\n\nVideo\n(.*?)\n\n", info, DOTALL
  1089.                     ).group(1)
  1090.  
  1091.                     vidformat = "{}{}{}".format(
  1092.                         search(r"\nFormat *?: (.*?)\n", videoinfo).group(1),
  1093.                         optregexp(search(r"\nFormat version *?: (.*?)\n",
  1094.                                          videoinfo)),
  1095.                         optregexp(search(r"\nFormat profile *?: (.*?)\n",
  1096.                                          videoinfo))
  1097.                     )
  1098.                     chaptered = "Yes" if search(r"\n\nMenu\n", info) else "No"
  1099.                     pastekey = reply.rpartition("/")[2]
  1100.  
  1101.                     def phpescape(str):
  1102.                         return str.replace("[", "{").replace("]", "}")
  1103.  
  1104.                     output += GROUP.format(
  1105.                         groupname=phpescape(groupname),
  1106.                         filename=phpescape(filen),
  1107.                         filesize=filesize,
  1108.                         vidformat=phpescape(vidformat),
  1109.                         chaptered=chaptered,
  1110.                         pastekey=pastekey
  1111.                     )
  1112.  
  1113.                     CFGDICT["last_upload"] = dt.now()
  1114.                     RAM["next_upload"] = (
  1115.                         dt.now() + td(seconds=CFGDICT["delay"])
  1116.                     )
  1117.             else:
  1118.                 self.video_cl.set(i, "stat", "Not a valid video file")
  1119.             root.update()
  1120.         return output
  1121.  
  1122.     def picrename(self):
  1123.         self.image_cl.disable()
  1124.         self.image_refresh_btn.configure(state=DISABLED)
  1125.         for item in self.image_cl.getmarked():
  1126.             rename(
  1127.                 self.image_cl.set(item, "oname"),
  1128.                 self.image_cl.set(item, "nname")
  1129.             )
  1130.             self.image_cl.set(
  1131.                 item, "oname", self.image_cl.set(item, "nname")
  1132.             )
  1133.             self.image_cl.set(item, "nname", "Done")
  1134.         self.image_cl.enable()
  1135.         self.image_refresh_btn.configure(state=NORMAL)
  1136.         self.image_cl.unmarkall()
  1137.  
  1138.  
  1139. class Secret(sdiag.Dialog):
  1140.     # You found it, Sherlock!
  1141.     def __init__(self, master):
  1142.         Toplevel.__init__(self, master)
  1143.         self.transient(master)
  1144.         self.resizable(0, 0)
  1145.         frm = Frame(self)
  1146.         frm.grid(row=0, column=0)
  1147.         self._secret_icon = PhotoImage(data=SECRET_B64)
  1148.         Label(frm, image=self._secret_icon, justify='left').grid(
  1149.             row=0, column=0, sticky='w', padx=10, pady=10
  1150.         )
  1151.         Label(frm, text="But nothing happened!", justify='left').grid(
  1152.             row=0, column=1, sticky='w', padx=10, pady=10
  1153.         )
  1154.         self.update_idletasks()
  1155.         x = max(0, min(
  1156.             self.winfo_screenwidth() - self.winfo_width() - RAM["fwidth"] * 2,
  1157.             root.winfo_x() + (root.winfo_width() - self.winfo_width()) // 2
  1158.         ))
  1159.         y = max(0, min(
  1160.             (
  1161.                 self.winfo_screenheight() - self.winfo_height() -
  1162.                 RAM["fwidth"] - RAM["fcheight"]
  1163.             ),
  1164.             root.winfo_y() + (root.winfo_height() - self.winfo_height()) // 2
  1165.         ))
  1166.         self.geometry("+{x}+{y}".format(x=x, y=y))
  1167.  
  1168.  
  1169. # The following code is added to facilitate the Scrolled widgets you specified.
  1170. class AutoScroll(object):
  1171.     """Configure the scrollbars for a widget."""
  1172.  
  1173.     def __init__(self, master, exp=None):
  1174.         #  Rozen. Added the try-except clauses so that this class
  1175.         #  could be used for scrolled entry widget for which vertical
  1176.         #  scrolling is not supported. 5/7/14.
  1177.         try:
  1178.             self.vsb = Scrollbar(master, orient='vertical', command=self.yview)
  1179.         except:
  1180.             pass
  1181.         self.hsb = Scrollbar(master, orient='horizontal', command=self.xview)
  1182.         try:
  1183.             self.configure(yscrollcommand=self._autoscroll(self.vsb, exp))
  1184.         except:
  1185.             pass
  1186.         self.configure(xscrollcommand=self._autoscroll(self.hsb, exp))
  1187.  
  1188.         self.grid(column=0, row=0, sticky='nsew')
  1189.         try:
  1190.             self.vsb.grid(column=1, row=0, sticky='ns')
  1191.         except:
  1192.             pass
  1193.         self.hsb.grid(column=0, row=1, sticky='ew')
  1194.         self.frm = Frame(master, relief=GROOVE)
  1195.         self.frm.grid(column=1, row=1, sticky='nsew')
  1196.         self.frm.bind("<Button-1>", lambda e: Secret(master))
  1197.  
  1198.         master.grid_columnconfigure(0, weight=1)
  1199.         master.grid_rowconfigure(0, weight=1)
  1200.         # Copy geometry methods of master (taken from ScrolledText.py)
  1201.  
  1202.         methods = (
  1203.             Pack.__dict__.keys() | Grid.__dict__.keys() | Place.__dict__.keys()
  1204.         )
  1205.  
  1206.         for meth in methods:
  1207.             if (meth[0] != "_" and meth not in ("config", "configure") and
  1208.                     meth not in type(self).__bases__[0].__dict__):
  1209.                 setattr(self, meth, getattr(master, meth))
  1210.  
  1211.     def chlst_scroll(self, exp):
  1212.         # Checklist elements scroll
  1213.         root.update()
  1214.         for i, widg in enumerate(exp[1]):
  1215.             bbx = Treeview.bbox(self, i, "#0")
  1216.             if bbx == "":
  1217.                 widg.place_forget()
  1218.             else:
  1219.                 x, y, _, _ = bbx
  1220.                 widg.place(
  1221.                     x=(x + (max(RAM["tvrh"], RAM["tvhh"]) - 2) / 2),
  1222.                     y=(y + RAM["tvrh"] / 2),
  1223.                     anchor='center'
  1224.                 )
  1225.                 exp[0].place(
  1226.                     x=(x + (max(RAM["tvrh"], RAM["tvhh"]) - 2) / 2),
  1227.                     anchor='center'
  1228.                 )
  1229.         root.update()
  1230.  
  1231.     def _autoscroll(self, sbar, exp):
  1232.         """Hide and show scrollbar as needed."""
  1233.         def wrapped(first, last):
  1234.             first, last = float(first), float(last)
  1235.             if exp:
  1236.                 self.chlst_scroll(exp)
  1237.             if first <= 0 and last >= 1:
  1238.                 sbar.grid_remove()
  1239.             else:
  1240.                 sbar.grid()
  1241.             if self.hsb.winfo_ismapped() and self.vsb.winfo_ismapped():
  1242.                 self.frm.grid()
  1243.             else:
  1244.                 self.frm.grid_remove()
  1245.             sbar.set(first, last)
  1246.         return wrapped
  1247.  
  1248.     def __str__(self):
  1249.         return str(self.master)
  1250.  
  1251.  
  1252. def _create_container(func):
  1253.     """Creates a ttk Frame with a given master, and use this new frame to
  1254.    place the scrollbars and the widget."""
  1255.     def wrapped(cls, master, **kw):
  1256.         container = Frame(master)
  1257.         return func(cls, container, **kw)
  1258.     return wrapped
  1259.  
  1260.  
  1261. class Checkbox(Checkbutton):
  1262.     @_create_container
  1263.     def __init__(self, master, **kw):
  1264.         Checkbutton.__init__(self, master, **kw)
  1265.         self.place(x=0, y=0)
  1266.         master.config(
  1267.             height=self.winfo_reqheight(),
  1268.             width=self.winfo_reqheight()
  1269.         )
  1270.  
  1271.         methods = (
  1272.             Pack.__dict__.keys() | Grid.__dict__.keys() | Place.__dict__.keys()
  1273.         )
  1274.  
  1275.         for meth in methods:
  1276.             if meth[0] != "_" and meth not in ("config", "configure"):
  1277.                 setattr(self, meth, getattr(master, meth))
  1278.  
  1279.     def annihilate(self):
  1280.         self.master.destroy()
  1281.  
  1282.  
  1283. class CheckList(AutoScroll, Treeview):
  1284.     @_create_container
  1285.     def __init__(self, master, clid, **kw):
  1286.         master.config(style="CL.TFrame", border=1)
  1287.         self._cblist = []
  1288.         self.clid = clid
  1289.         self.grabbed = None
  1290.         self._cbmaster = None
  1291.         self.err = 0
  1292.         self.var = IntVar()
  1293.         self.var.set(0)
  1294.         Treeview.__init__(self, master, **kw)
  1295.         statevar = BooleanVar()
  1296.         self._cbmaster = Checkbox(
  1297.             self, variable=statevar, command=self._toggleslave
  1298.         )
  1299.         AutoScroll.__init__(self, master, [self._cbmaster, self._cblist])
  1300.         self._cbmaster.var = statevar
  1301.         self._cbmaster.var.set(0)
  1302.         self._cbmaster.place(
  1303.             x=((max(RAM["tvrh"], RAM["tvhh"]) - 2) / 2),
  1304.             y=((RAM["tvhh"] - 2) / 2),
  1305.             anchor='center'
  1306.         )
  1307.         self._cbmaster.config(state=DISABLED)
  1308.         self.bindtags(self.bindtags() + ("CheckList",))
  1309.         self.bind_class(
  1310.             "CheckList",
  1311.             "<Configure>",
  1312.             lambda event: event.widget.chlst_scroll([
  1313.                 event.widget._cbmaster, event.widget._cblist
  1314.             ])
  1315.         )
  1316.         self.bind_class(
  1317.             "CheckList",
  1318.             "<space>",
  1319.             lambda event: event.widget._togglesel(event.widget.selection())
  1320.         )
  1321.         self.bind("<Button-1>", self._headerresize_down)
  1322.         self.bind("<ButtonRelease-1>", self._headerresize_up)
  1323.  
  1324.     def _headerresize_down(self, event):
  1325.         if 0 or (self.identify_region(event.x, event.y) == 'separator' and
  1326.                  self.identify_column(event.x) == '#0'):
  1327.             self.grabbed = '#0'
  1328.         if 0 or (self.identify_region(event.x, event.y) == 'separator' and
  1329.                  self.identify_column(event.x) == '#2'):
  1330.             self.grabbed = '#2'
  1331.  
  1332.     def _headerresize_up(self, event):
  1333.         if self.grabbed == '#0':
  1334.             self.grabbed = False
  1335.             self.column("#1", width=(
  1336.                 self.column(
  1337.                     "#1", option='width'
  1338.                 ) + self.column(
  1339.                     "#0", option='width'
  1340.                 ) - max(RAM["tvrh"], RAM["tvhh"])
  1341.             ))
  1342.             self.column("#0", width=max(RAM["tvrh"], RAM["tvhh"]))
  1343.         totalw = self.winfo_width()
  1344.         if 0 or (self.column("#0", option='width') +
  1345.                  self.column("#1", option='width') +
  1346.                  self.column("#2", option='width') < totalw and
  1347.                  self.grabbed == '#2'):
  1348.             self.column('#2', width=(
  1349.                 totalw - self.column("#0", option='width') - self.column(
  1350.                     "#1", option='width'
  1351.                 )
  1352.             ))
  1353.  
  1354.     def bbox(self, *args, **kw):
  1355.         super(Checklist, self).bbox(*args, **kw)
  1356.  
  1357.     def insert(self, master, index, iid=None, **kw):
  1358.         self._cbmaster.config(state=NORMAL)
  1359.         if self.var:
  1360.             self.var.set(self.var.get() + 1)
  1361.         super().insert(master, index, iid, **kw)
  1362.         statevar = BooleanVar()
  1363.         cbx = Checkbox(self, variable=statevar)
  1364.         cbx.config(command=lambda cbx=cbx: self._togglecb(cbx))
  1365.         cbx.var = statevar
  1366.         cbx.var.set(0)
  1367.         cbx.place(
  1368.             x=((max(RAM["tvrh"], RAM["tvhh"]) - 2) / 2),
  1369.             y=(RAM["tvhh"] + RAM["tvrh"] * (.5 + len(self._cblist))),
  1370.             anchor='center'
  1371.         )
  1372.         self._cblist.append(cbx)
  1373.  
  1374.     def delete(self, *items):
  1375.         for item in reversed(items):
  1376.             i = self.index(item)
  1377.             if self._cblist[i].var.get():
  1378.                 self.var.set(self.var.get() - 1)
  1379.             for trc in self._cblist[i].var.trace_vinfo():
  1380.                 self._cblist[i].var.trace_vdelete(*trc)
  1381.             self._cblist[i].annihilate()
  1382.             del self._cblist[i]
  1383.         if len(self._cblist) == 0:
  1384.             self._cbmaster.config(state=DISABLED)
  1385.         super().delete(*items)
  1386.  
  1387.     def markall(self):
  1388.         if not self._cbmaster.var.get():
  1389.             self._cbmaster.invoke()
  1390.  
  1391.     def unmarkall(self):
  1392.         if self._cbmaster.var.get():
  1393.             self._cbmaster.invoke()
  1394.         else:
  1395.             for cbx in (cbx for cbx in self._cblist if cbx.var.get()):
  1396.                 cbx.invoke()
  1397.  
  1398.     def toggleall(self):
  1399.         self._cbmaster.invoke()
  1400.  
  1401.     def _toggleslave(self):
  1402.         val = self._cbmaster.var.get()
  1403.         for cbx in self._cblist:
  1404.             if cbx.var.get() != val:
  1405.                 cbx.invoke()
  1406.         self.var.set(len(self._cblist) if self._cbmaster.var.get() else 0)
  1407.  
  1408.     def _togglesel(self, sel):
  1409.         cbsel = [self._cblist[self.index(item)] for item in sel]
  1410.         total = sum([cbx.var.get() for cbx in cbsel])
  1411.         seq = (
  1412.             cbsel if total == len(cbsel) or total == 0
  1413.             else [cbx for cbx in cbsel if not cbx.var.get()]
  1414.         )
  1415.         for cbx in seq:
  1416.             cbx.invoke()
  1417.  
  1418.     def toggle(self, item):
  1419.         self._cblist[self.index(item)].invoke()
  1420.  
  1421.     def _togglecb(self, cbx):
  1422.         cbx.var.set(cbx.var.get())
  1423.         if cbx.var.get():
  1424.             self.var.set(self.var.get() + 1)
  1425.             if self.var.get() == len(self._cblist):
  1426.                 self._cbmaster.var.set(1)
  1427.         else:
  1428.             self.var.set(self.var.get() - 1)
  1429.             self._cbmaster.var.set(0)
  1430.  
  1431.     def observe(self, item, callback):
  1432.         self._cblist[self.index(item)].var.trace('w', callback)
  1433.  
  1434.     def ismarked(self, item):
  1435.         return self._cblist[self.index(item)].var.get()
  1436.  
  1437.     def getmarked(self):
  1438.         return [
  1439.             item for item in self.get_children()
  1440.             if self._cblist[self.index(item)].var.get() == 1
  1441.         ]
  1442.  
  1443.     def getcbvar(self, i):
  1444.         return self._cblist[i].var
  1445.  
  1446.     def disable(self):
  1447.         self._cbmaster.config(state=DISABLED)
  1448.         for cbx in self._cblist:
  1449.             cbx.config(state=DISABLED)
  1450.  
  1451.     def enable(self):
  1452.         self._cbmaster.config(state=NORMAL)
  1453.         for cbx in self._cblist:
  1454.             cbx.config(state=NORMAL)
  1455.  
  1456.     def populate(self, cltype):
  1457.         self._cbmaster.config(state=NORMAL)
  1458.         self.unmarkall()
  1459.         self.delete(*self.get_children())
  1460.         i = 0
  1461.         for filen in sorted(next(walk(getcwd()))[2]):
  1462.             if (cltype == VIDEO and search(r"^\[.+?\]", filen) and
  1463.                     path.splitext(filen)[1] in VIDEO_FORMATS):
  1464.                 self.insert("", "end", str(i), values=(filen, ""))
  1465.                 i += 1
  1466.             elif (cltype == IMAGE and
  1467.                   search(IMAGE_REGEXP, filen)):
  1468.                 self.insert(
  1469.                     "", "end", str(i), values=(filen, renameregexp(filen))
  1470.                 )
  1471.                 self.getcbvar(i).trace(
  1472.                     'w', lambda n, m, x, i=i: self._conflictcheck(i)
  1473.                 )
  1474.                 i += 1
  1475.         self.toggleall()
  1476.  
  1477.     def _conflictcheck(self, i):
  1478.         # Disable button if nothing selected.
  1479.         item = self.get_children()[i]
  1480.         if "Done" in self.set(item, "nname") and self.ismarked(item) == 1:
  1481.             if mbox.askokcancel(SCRIPTNAME, "File was already renamed.\nDo you want to refresh the list?"):
  1482.                 self.populate(IMAGE)
  1483.             else:
  1484.                 self.getcbvar(i).set(0)
  1485.         else:
  1486.             if 0 or (not self.ismarked(item) and
  1487.                      self.set(item, "nname").startswith((
  1488.                          "File exists: ", "Rename conflict: "
  1489.                      ))):
  1490.                 self.set(item, "nname", renameregexp(self.set(item, "oname")))
  1491.             from collections import defaultdict
  1492.             dict = defaultdict(list)
  1493.             lst = self.getmarked()
  1494.             directory = next(walk(getcwd()))[2]
  1495.             for it, item1 in enumerate(lst):
  1496.                 dict[renameregexp(self.set(item1, "oname"))].append(it)
  1497.             self.err = 1
  1498.             for k, v in dict.items():
  1499.                 error = ""
  1500.                 if k in directory:
  1501.                     error = "File exists: "
  1502.                 elif len(v) > 1:
  1503.                     error = "Rename conflict: "
  1504.                 else:
  1505.                     self.err = 0
  1506.                 for it in v:
  1507.                     if self.set(lst[it], "nname") != "Done":
  1508.                         self.set(lst[it], "nname", error + renameregexp(
  1509.                             self.set(lst[it], "oname")
  1510.                         ))
  1511.  
  1512.  
  1513. class ScrolledText(AutoScroll, Text):
  1514.     """A standard Tkinter Text widget with scrollbars that will
  1515.    automatically show/hide as needed."""
  1516.     @_create_container
  1517.     def __init__(self, master, **kw):
  1518.         master.config(style="CL.TFrame", borderwidth=1)
  1519.         Text.__init__(self, master, **kw)
  1520.         AutoScroll.__init__(self, master)
  1521.  
  1522. if __name__ == '__main__':
  1523.     vp_start_gui()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement