You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

367 lines
9.1 KiB

10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
7 years ago
  1. #!/usr/bin/env python2
  2. # -*- coding: utf-8 -*-
  3. """Youtubedlg module that contains util functions.
  4. Attributes:
  5. _RANDOM_OBJECT (object): Object that it's used as a default parameter.
  6. YOUTUBEDL_BIN (string): Youtube-dl binary filename.
  7. """
  8. from __future__ import unicode_literals
  9. import os
  10. import sys
  11. import json
  12. import math
  13. import locale
  14. import subprocess
  15. try:
  16. from twodict import TwoWayOrderedDict
  17. except ImportError as error:
  18. print error
  19. sys.exit(1)
  20. from .info import __appname__
  21. from .version import __version__
  22. _RANDOM_OBJECT = object()
  23. YOUTUBEDL_BIN = 'youtube-dl'
  24. if os.name == 'nt':
  25. YOUTUBEDL_BIN += '.exe'
  26. FILESIZE_METRICS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
  27. KILO_SIZE = 1024.0
  28. def get_encoding():
  29. """Return system encoding. """
  30. try:
  31. encoding = locale.getpreferredencoding()
  32. 'TEST'.encode(encoding)
  33. except:
  34. encoding = 'UTF-8'
  35. return encoding
  36. def convert_on_bounds(func):
  37. """Decorator to convert string inputs & outputs.
  38. Covert string inputs & outputs between 'str' and 'unicode' at the
  39. application bounds using the preferred system encoding. It will convert
  40. all the string params (args, kwargs) to 'str' type and all the
  41. returned strings values back to 'unicode'.
  42. """
  43. def convert_item(item, to_unicode=False):
  44. """The actual function which handles the conversion.
  45. Args:
  46. item (-): Can be any python item.
  47. to_unicode (boolean): When True it will convert all the 'str' types
  48. to 'unicode'. When False it will convert all the 'unicode'
  49. types back to 'str'.
  50. """
  51. if to_unicode and isinstance(item, str):
  52. # Convert str to unicode
  53. return item.decode(get_encoding(), 'ignore')
  54. if not to_unicode and isinstance(item, unicode):
  55. # Convert unicode to str
  56. return item.encode(get_encoding(), 'ignore')
  57. if hasattr(item, '__iter__'):
  58. # Handle iterables
  59. temp_list = []
  60. for sub_item in item:
  61. if isinstance(item, dict):
  62. temp_list.append((sub_item, convert_item(item[sub_item])))
  63. else:
  64. temp_list.append(convert_item(sub_item))
  65. return type(item)(temp_list)
  66. return item
  67. def wrapper(*args, **kwargs):
  68. returned_value = func(*convert_item(args), **convert_item(kwargs))
  69. return convert_item(returned_value, True)
  70. return wrapper
  71. # See: https://github.com/MrS0m30n3/youtube-dl-gui/issues/57
  72. # Patch os functions to convert between 'str' and 'unicode' on app bounds
  73. os_sep = unicode(os.sep)
  74. os_getenv = convert_on_bounds(os.getenv)
  75. os_makedirs = convert_on_bounds(os.makedirs)
  76. os_path_isdir = convert_on_bounds(os.path.isdir)
  77. os_path_exists = convert_on_bounds(os.path.exists)
  78. os_path_dirname = convert_on_bounds(os.path.dirname)
  79. os_path_abspath = convert_on_bounds(os.path.abspath)
  80. os_path_realpath = convert_on_bounds(os.path.realpath)
  81. os_path_expanduser = convert_on_bounds(os.path.expanduser)
  82. # Patch Windows specific functions
  83. if os.name == 'nt':
  84. os_startfile = convert_on_bounds(os.startfile)
  85. def remove_file(filename):
  86. if os_path_exists(filename):
  87. os.remove(filename)
  88. return True
  89. return False
  90. def remove_shortcuts(path):
  91. """Return given path after removing the shortcuts. """
  92. return path.replace('~', os_path_expanduser('~'))
  93. def absolute_path(filename):
  94. """Return absolute path to the given file. """
  95. return os_path_dirname(os_path_realpath(os_path_abspath(filename)))
  96. def open_file(file_path):
  97. """Open file in file_path using the default OS application.
  98. Returns:
  99. True on success else False.
  100. """
  101. file_path = remove_shortcuts(file_path)
  102. if not os_path_exists(file_path):
  103. return False
  104. if os.name == "nt":
  105. os_startfile(file_path)
  106. else:
  107. subprocess.call(("xdg-open", file_path))
  108. return True
  109. def encode_tuple(tuple_to_encode):
  110. """Turn size tuple into string. """
  111. return '%s/%s' % (tuple_to_encode[0], tuple_to_encode[1])
  112. def decode_tuple(encoded_tuple):
  113. """Turn tuple string back to tuple. """
  114. s = encoded_tuple.split('/')
  115. return int(s[0]), int(s[1])
  116. def check_path(path):
  117. """Create path if not exist. """
  118. if not os_path_exists(path):
  119. os_makedirs(path)
  120. def get_config_path():
  121. """Return user config path.
  122. Note:
  123. Windows = %AppData% + app_name
  124. Linux = ~/.config + app_name
  125. """
  126. if os.name == 'nt':
  127. path = os_getenv('APPDATA')
  128. else:
  129. path = os.path.join(os_path_expanduser('~'), '.config')
  130. return os.path.join(path, __appname__.lower())
  131. def shutdown_sys(password=None):
  132. """Shuts down the system.
  133. Returns True if no errors occur else False.
  134. Args:
  135. password (string): SUDO password for linux.
  136. Note:
  137. On Linux you need to provide sudo password if you don't
  138. have elevated privileges.
  139. """
  140. _stderr = subprocess.PIPE
  141. _stdin = None
  142. info = None
  143. encoding = get_encoding()
  144. if os.name == 'nt':
  145. cmd = ['shutdown', '/s', '/t', '1']
  146. # Hide subprocess window
  147. info = subprocess.STARTUPINFO()
  148. info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  149. else:
  150. if password:
  151. _stdin = subprocess.PIPE
  152. password = ('%s\n' % password).encode(encoding)
  153. cmd = ['sudo', '-S', '/sbin/shutdown', '-h', 'now']
  154. else:
  155. cmd = ['/sbin/shutdown', '-h', 'now']
  156. cmd = [item.encode(encoding, 'ignore') for item in cmd]
  157. shutdown_proc = subprocess.Popen(cmd,
  158. stderr=_stderr,
  159. stdin=_stdin,
  160. startupinfo=info)
  161. output = shutdown_proc.communicate(password)[1]
  162. return not output or output == "Password:"
  163. def to_string(data):
  164. """Convert data to string.
  165. Works for both Python2 & Python3. """
  166. return '%s' % data
  167. def get_time(seconds):
  168. """Convert given seconds to days, hours, minutes and seconds.
  169. Args:
  170. seconds (float): Time in seconds.
  171. Returns:
  172. Dictionary that contains the corresponding days, hours, minutes
  173. and seconds of the given seconds.
  174. """
  175. dtime = dict(seconds=0, minutes=0, hours=0, days=0)
  176. dtime['days'] = int(seconds / 86400)
  177. dtime['hours'] = int(seconds % 86400 / 3600)
  178. dtime['minutes'] = int(seconds % 86400 % 3600 / 60)
  179. dtime['seconds'] = int(seconds % 86400 % 3600 % 60)
  180. return dtime
  181. def get_locale_file():
  182. """Search for youtube-dlg locale file.
  183. Returns:
  184. The path to youtube-dlg locale file if exists else None.
  185. Note:
  186. Paths that get_locale_file() func searches.
  187. __main__ dir, library dir
  188. """
  189. DIR_NAME = "locale"
  190. SEARCH_DIRS = [
  191. os.path.join(absolute_path(sys.argv[0]), DIR_NAME),
  192. os.path.join(os_path_dirname(__file__), DIR_NAME),
  193. ]
  194. for directory in SEARCH_DIRS:
  195. if os_path_isdir(directory):
  196. return directory
  197. return None
  198. def get_icon_file():
  199. """Search for youtube-dlg app icon.
  200. Returns:
  201. The path to youtube-dlg icon file if exists, else returns None.
  202. """
  203. ICON_NAME = "youtube-dl-gui.png"
  204. pixmaps_dir = get_pixmaps_dir()
  205. if pixmaps_dir is not None:
  206. icon_file = os.path.join(pixmaps_dir, ICON_NAME)
  207. if os_path_exists(icon_file):
  208. return icon_file
  209. return None
  210. def get_pixmaps_dir():
  211. """Return absolute path to the pixmaps icons folder.
  212. Note:
  213. Paths we search: __main__ dir, library dir
  214. """
  215. search_dirs = [
  216. os.path.join(absolute_path(sys.argv[0]), "data"),
  217. os.path.join(os_path_dirname(__file__), "data")
  218. ]
  219. for directory in search_dirs:
  220. pixmaps_dir = os.path.join(directory, "pixmaps")
  221. if os_path_exists(pixmaps_dir):
  222. return pixmaps_dir
  223. return None
  224. def to_bytes(string):
  225. """Convert given youtube-dl size string to bytes."""
  226. value = 0.0
  227. for index, metric in enumerate(reversed(FILESIZE_METRICS)):
  228. if metric in string:
  229. value = float(string.split(metric)[0])
  230. break
  231. exponent = index * (-1) + (len(FILESIZE_METRICS) - 1)
  232. return round(value * (KILO_SIZE ** exponent), 2)
  233. def format_bytes(bytes):
  234. """Format bytes to youtube-dl size output strings."""
  235. if bytes == 0.0:
  236. exponent = 0
  237. else:
  238. exponent = int(math.log(bytes, KILO_SIZE))
  239. suffix = FILESIZE_METRICS[exponent]
  240. output_value = bytes / (KILO_SIZE ** exponent)
  241. return "%.2f%s" % (output_value, suffix)
  242. def build_command(options_list, url):
  243. """Build the youtube-dl command line string."""
  244. # If option has spaces wrap it with double quotes
  245. # Probably not the best solution since if the option already contains
  246. # double quotes it will be a mess, see issue #173
  247. options = ["\"{}\"".format(option) if " " in option else option for option in options_list]
  248. # Always wrap the url with double quotes
  249. url = "\"{}\"".format(url)
  250. return " ".join([YOUTUBEDL_BIN] + options + [url])