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.

364 lines
9.4 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
9 years ago
10 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 locale
  13. import subprocess
  14. try:
  15. from twodict import TwoWayOrderedDict
  16. except ImportError as error:
  17. print error
  18. sys.exit(1)
  19. from .info import __appname__
  20. from .version import __version__
  21. _RANDOM_OBJECT = object()
  22. YOUTUBEDL_BIN = 'youtube-dl'
  23. if os.name == 'nt':
  24. YOUTUBEDL_BIN += '.exe'
  25. def get_encoding():
  26. """Return system encoding. """
  27. try:
  28. encoding = locale.getpreferredencoding()
  29. 'TEST'.encode(encoding)
  30. except:
  31. encoding = 'UTF-8'
  32. return encoding
  33. def convert_on_bounds(func):
  34. """Decorator to convert string inputs & outputs.
  35. Covert string inputs & outputs between 'str' and 'unicode' at the
  36. application bounds using the preferred system encoding. It will convert
  37. all the string params (args, kwargs) to 'str' type and all the
  38. returned strings values back to 'unicode'.
  39. """
  40. def convert_item(item, to_unicode=False):
  41. """The actual function which handles the conversion.
  42. Args:
  43. item (-): Can be any python item.
  44. to_unicode (boolean): When True it will convert all the 'str' types
  45. to 'unicode'. When False it will convert all the 'unicode'
  46. types back to 'str'.
  47. """
  48. if to_unicode and isinstance(item, str):
  49. # Convert str to unicode
  50. return item.decode(get_encoding(), 'ignore')
  51. if not to_unicode and isinstance(item, unicode):
  52. # Convert unicode to str
  53. return item.encode(get_encoding(), 'ignore')
  54. if hasattr(item, '__iter__'):
  55. # Handle iterables
  56. temp_list = []
  57. for sub_item in item:
  58. if isinstance(item, dict):
  59. temp_list.append((sub_item, covert_item(item[sub_item])))
  60. else:
  61. temp_list.append(convert_item(sub_item))
  62. return type(item)(temp_list)
  63. return item
  64. def wrapper(*args, **kwargs):
  65. returned_value = func(*convert_item(args), **convert_item(kwargs))
  66. return convert_item(returned_value, True)
  67. return wrapper
  68. # See: https://github.com/MrS0m30n3/youtube-dl-gui/issues/57
  69. # Patch os functions to convert between 'str' and 'unicode' on app bounds
  70. os_sep = unicode(os.sep)
  71. os_getenv = convert_on_bounds(os.getenv)
  72. os_makedirs = convert_on_bounds(os.makedirs)
  73. os_path_isdir = convert_on_bounds(os.path.isdir)
  74. os_path_exists = convert_on_bounds(os.path.exists)
  75. os_path_dirname = convert_on_bounds(os.path.dirname)
  76. os_path_abspath = convert_on_bounds(os.path.abspath)
  77. os_path_realpath = convert_on_bounds(os.path.realpath)
  78. os_path_expanduser = convert_on_bounds(os.path.expanduser)
  79. # Patch Windows specific functions
  80. if os.name == 'nt':
  81. os_startfile = convert_on_bounds(os.startfile)
  82. def remove_file(filename):
  83. if os_path_exists(filename):
  84. os.remove(filename)
  85. return True
  86. return False
  87. def remove_shortcuts(path):
  88. """Return given path after removing the shortcuts. """
  89. return path.replace('~', os_path_expanduser('~'))
  90. def absolute_path(filename):
  91. """Return absolute path to the given file. """
  92. return os_path_dirname(os_path_realpath(os_path_abspath(filename)))
  93. def open_file(file_path):
  94. """Open file in file_path using the default OS application.
  95. Returns:
  96. True on success else False.
  97. """
  98. file_path = remove_shortcuts(file_path)
  99. if not os_path_exists(file_path):
  100. return False
  101. if os.name == "nt":
  102. os_startfile(file_path)
  103. else:
  104. subprocess.call(("xdg-open", file_path))
  105. return True
  106. def encode_tuple(tuple_to_encode):
  107. """Turn size tuple into string. """
  108. return '%s/%s' % (tuple_to_encode[0], tuple_to_encode[1])
  109. def decode_tuple(encoded_tuple):
  110. """Turn tuple string back to tuple. """
  111. s = encoded_tuple.split('/')
  112. return int(s[0]), int(s[1])
  113. def check_path(path):
  114. """Create path if not exist. """
  115. if not os_path_exists(path):
  116. os_makedirs(path)
  117. def get_config_path():
  118. """Return user config path.
  119. Note:
  120. Windows = %AppData% + app_name
  121. Linux = ~/.config + app_name
  122. """
  123. if os.name == 'nt':
  124. path = os_getenv('APPDATA')
  125. else:
  126. path = os.path.join(os_path_expanduser('~'), '.config')
  127. return os.path.join(path, __appname__.lower())
  128. def shutdown_sys(password=None):
  129. """Shuts down the system.
  130. Returns True if no errors occur else False.
  131. Args:
  132. password (string): SUDO password for linux.
  133. Note:
  134. On Linux you need to provide sudo password if you don't
  135. have elevated privileges.
  136. """
  137. _stderr = subprocess.PIPE
  138. _stdin = None
  139. info = None
  140. encoding = get_encoding()
  141. if os.name == 'nt':
  142. cmd = ['shutdown', '/s', '/t', '1']
  143. # Hide subprocess window
  144. info = subprocess.STARTUPINFO()
  145. info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  146. else:
  147. if password:
  148. _stdin = subprocess.PIPE
  149. password = ('%s\n' % password).encode(encoding)
  150. cmd = ['sudo', '-S', '/sbin/shutdown', '-h', 'now']
  151. else:
  152. cmd = ['/sbin/shutdown', '-h', 'now']
  153. cmd = [item.encode(encoding, 'ignore') for item in cmd]
  154. shutdown_proc = subprocess.Popen(cmd,
  155. stderr=_stderr,
  156. stdin=_stdin,
  157. startupinfo=info)
  158. output = shutdown_proc.communicate(password)[1]
  159. return not output or output == "Password:"
  160. def to_string(data):
  161. """Convert data to string.
  162. Works for both Python2 & Python3. """
  163. return '%s' % data
  164. def get_time(seconds):
  165. """Convert given seconds to days, hours, minutes and seconds.
  166. Args:
  167. seconds (float): Time in seconds.
  168. Returns:
  169. Dictionary that contains the corresponding days, hours, minutes
  170. and seconds of the given seconds.
  171. """
  172. dtime = dict(seconds=0, minutes=0, hours=0, days=0)
  173. dtime['days'] = int(seconds / 86400)
  174. dtime['hours'] = int(seconds % 86400 / 3600)
  175. dtime['minutes'] = int(seconds % 86400 % 3600 / 60)
  176. dtime['seconds'] = int(seconds % 86400 % 3600 % 60)
  177. return dtime
  178. def get_locale_file():
  179. """Search for youtube-dlg locale file.
  180. Returns:
  181. The path to youtube-dlg locale file if exists else None.
  182. Note:
  183. Paths that get_locale_file() func searches.
  184. __main__ dir, library dir, /usr/share/youtube-dlg/locale
  185. """
  186. DIR_NAME = 'locale'
  187. SEARCH_DIRS = [
  188. os.path.join(absolute_path(sys.argv[0]), DIR_NAME),
  189. os.path.join(os_path_dirname(__file__), DIR_NAME),
  190. os.path.join('/usr', 'share', __appname__.lower(), DIR_NAME)
  191. ]
  192. if sys.platform == 'darwin':
  193. SEARCH_DIRS.append('/usr/local/Cellar/youtube-dl-gui/{version}/share/locale'.format(version=__version__))
  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. Note:
  203. Paths that get_icon_file() function searches.
  204. __main__ dir, library dir, $XDG_DATA_DIRS
  205. """
  206. ICON_NAME = "youtube-dl-gui.png"
  207. SIZES = ("256", "128", "64", "48", "32", "16")
  208. DIR_TEMPLATE = os.path.join("icons", "hicolor", "{size}x{size}", "apps", ICON_NAME)
  209. ICONS = [DIR_TEMPLATE.format(size=size) for size in SIZES]
  210. search_dirs = [
  211. os.path.join(absolute_path(sys.argv[0]), "data"),
  212. os.path.join(os_path_dirname(__file__), "data"),
  213. ]
  214. # Append $XDG_DATA_DIRS on search_dirs
  215. path = os_getenv("XDG_DATA_DIRS")
  216. if path is not None:
  217. for xdg_path in path.split(':'):
  218. search_dirs.append(xdg_path)
  219. # Also append /usr/share/pixmaps on search_dirs
  220. #search_dirs.append("/usr/share/pixmaps")
  221. for directory in search_dirs:
  222. for icon in ICONS:
  223. icon_file = os.path.join(directory, icon)
  224. if os_path_exists(icon_file):
  225. return icon_file
  226. return None
  227. def get_pixmaps_dir():
  228. """Return absolute path to the pixmaps icons folder."""
  229. # TODO probably will need support for other directories py2exe etc
  230. return os.path.join(absolute_path(__file__), "data", "pixmaps")
  231. def json_load(filename):
  232. if os_path_exists(filename):
  233. with open(filename) as input_json_file:
  234. return json.load(input_json_file)
  235. return []
  236. def json_store(filename, item):
  237. with open(filename, 'w') as output_json_file:
  238. json.dump(item, output_json_file)
  239. def read_formats():
  240. """Returns a twodict containing all the formats from 'data/formats'."""
  241. # TODO Support for other directories? Test with py2exe
  242. formats_file = os.path.join(absolute_path(__file__), "data", "formats")
  243. if os_path_exists(formats_file):
  244. formats_dict = TwoWayOrderedDict()
  245. with open(formats_file) as input_file:
  246. for line in input_file:
  247. format_id, format_label = line.split('-')
  248. formats_dict[format_id.strip()] = format_label.strip()
  249. return formats_dict
  250. return None