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.
361 lines
9.2 KiB
361 lines
9.2 KiB
#!/usr/bin/env python2
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""Youtubedlg module that contains util functions.
|
|
|
|
Attributes:
|
|
_RANDOM_OBJECT (object): Object that it's used as a default parameter.
|
|
|
|
YOUTUBEDL_BIN (string): Youtube-dl binary filename.
|
|
|
|
"""
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import locale
|
|
import subprocess
|
|
|
|
try:
|
|
from twodict import TwoWayOrderedDict
|
|
except ImportError as error:
|
|
print error
|
|
sys.exit(1)
|
|
|
|
from .info import __appname__
|
|
from .version import __version__
|
|
|
|
|
|
_RANDOM_OBJECT = object()
|
|
|
|
|
|
YOUTUBEDL_BIN = 'youtube-dl'
|
|
if os.name == 'nt':
|
|
YOUTUBEDL_BIN += '.exe'
|
|
|
|
|
|
def get_encoding():
|
|
"""Return system encoding. """
|
|
try:
|
|
encoding = locale.getpreferredencoding()
|
|
'TEST'.encode(encoding)
|
|
except:
|
|
encoding = 'UTF-8'
|
|
|
|
return encoding
|
|
|
|
|
|
def convert_on_bounds(func):
|
|
"""Decorator to convert string inputs & outputs.
|
|
|
|
Covert string inputs & outputs between 'str' and 'unicode' at the
|
|
application bounds using the preferred system encoding. It will convert
|
|
all the string params (args, kwargs) to 'str' type and all the
|
|
returned strings values back to 'unicode'.
|
|
|
|
"""
|
|
def convert_item(item, to_unicode=False):
|
|
"""The actual function which handles the conversion.
|
|
|
|
Args:
|
|
item (-): Can be any python item.
|
|
|
|
to_unicode (boolean): When True it will convert all the 'str' types
|
|
to 'unicode'. When False it will convert all the 'unicode'
|
|
types back to 'str'.
|
|
|
|
"""
|
|
if to_unicode and isinstance(item, str):
|
|
# Convert str to unicode
|
|
return item.decode(get_encoding(), 'ignore')
|
|
|
|
if not to_unicode and isinstance(item, unicode):
|
|
# Convert unicode to str
|
|
return item.encode(get_encoding(), 'ignore')
|
|
|
|
if hasattr(item, '__iter__'):
|
|
# Handle iterables
|
|
temp_list = []
|
|
|
|
for sub_item in item:
|
|
if isinstance(item, dict):
|
|
temp_list.append((sub_item, covert_item(item[sub_item])))
|
|
else:
|
|
temp_list.append(convert_item(sub_item))
|
|
|
|
return type(item)(temp_list)
|
|
|
|
return item
|
|
|
|
def wrapper(*args, **kwargs):
|
|
returned_value = func(*convert_item(args), **convert_item(kwargs))
|
|
|
|
return convert_item(returned_value, True)
|
|
|
|
return wrapper
|
|
|
|
|
|
# See: https://github.com/MrS0m30n3/youtube-dl-gui/issues/57
|
|
# Patch os functions to convert between 'str' and 'unicode' on app bounds
|
|
os_sep = unicode(os.sep)
|
|
os_getenv = convert_on_bounds(os.getenv)
|
|
os_makedirs = convert_on_bounds(os.makedirs)
|
|
os_path_isdir = convert_on_bounds(os.path.isdir)
|
|
os_path_exists = convert_on_bounds(os.path.exists)
|
|
os_path_dirname = convert_on_bounds(os.path.dirname)
|
|
os_path_abspath = convert_on_bounds(os.path.abspath)
|
|
os_path_realpath = convert_on_bounds(os.path.realpath)
|
|
os_path_expanduser = convert_on_bounds(os.path.expanduser)
|
|
|
|
# Patch Windows specific functions
|
|
if os.name == 'nt':
|
|
os_startfile = convert_on_bounds(os.startfile)
|
|
|
|
def remove_file(filename):
|
|
if os_path_exists(filename):
|
|
os.remove(filename)
|
|
return True
|
|
|
|
return False
|
|
|
|
def remove_shortcuts(path):
|
|
"""Return given path after removing the shortcuts. """
|
|
return path.replace('~', os_path_expanduser('~'))
|
|
|
|
|
|
def absolute_path(filename):
|
|
"""Return absolute path to the given file. """
|
|
return os_path_dirname(os_path_realpath(os_path_abspath(filename)))
|
|
|
|
|
|
def open_file(file_path):
|
|
"""Open file in file_path using the default OS application.
|
|
|
|
Returns:
|
|
True on success else False.
|
|
|
|
"""
|
|
file_path = remove_shortcuts(file_path)
|
|
|
|
if not os_path_exists(file_path):
|
|
return False
|
|
|
|
if os.name == "nt":
|
|
os_startfile(file_path)
|
|
else:
|
|
subprocess.call(("xdg-open", file_path))
|
|
|
|
return True
|
|
|
|
|
|
def encode_tuple(tuple_to_encode):
|
|
"""Turn size tuple into string. """
|
|
return '%s/%s' % (tuple_to_encode[0], tuple_to_encode[1])
|
|
|
|
|
|
def decode_tuple(encoded_tuple):
|
|
"""Turn tuple string back to tuple. """
|
|
s = encoded_tuple.split('/')
|
|
return int(s[0]), int(s[1])
|
|
|
|
|
|
def check_path(path):
|
|
"""Create path if not exist. """
|
|
if not os_path_exists(path):
|
|
os_makedirs(path)
|
|
|
|
|
|
def get_config_path():
|
|
"""Return user config path.
|
|
|
|
Note:
|
|
Windows = %AppData% + app_name
|
|
Linux = ~/.config + app_name
|
|
|
|
"""
|
|
if os.name == 'nt':
|
|
path = os_getenv('APPDATA')
|
|
else:
|
|
path = os.path.join(os_path_expanduser('~'), '.config')
|
|
|
|
return os.path.join(path, __appname__.lower())
|
|
|
|
|
|
def shutdown_sys(password=None):
|
|
"""Shuts down the system.
|
|
Returns True if no errors occur else False.
|
|
|
|
Args:
|
|
password (string): SUDO password for linux.
|
|
|
|
Note:
|
|
On Linux you need to provide sudo password if you don't
|
|
have elevated privileges.
|
|
|
|
"""
|
|
_stderr = subprocess.PIPE
|
|
_stdin = None
|
|
info = None
|
|
encoding = get_encoding()
|
|
|
|
if os.name == 'nt':
|
|
cmd = ['shutdown', '/s', '/t', '1']
|
|
|
|
# Hide subprocess window
|
|
info = subprocess.STARTUPINFO()
|
|
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
else:
|
|
if password:
|
|
_stdin = subprocess.PIPE
|
|
password = ('%s\n' % password).encode(encoding)
|
|
cmd = ['sudo', '-S', '/sbin/shutdown', '-h', 'now']
|
|
else:
|
|
cmd = ['/sbin/shutdown', '-h', 'now']
|
|
|
|
cmd = [item.encode(encoding, 'ignore') for item in cmd]
|
|
|
|
shutdown_proc = subprocess.Popen(cmd,
|
|
stderr=_stderr,
|
|
stdin=_stdin,
|
|
startupinfo=info)
|
|
|
|
output = shutdown_proc.communicate(password)[1]
|
|
|
|
return not output or output == "Password:"
|
|
|
|
|
|
def to_string(data):
|
|
"""Convert data to string.
|
|
Works for both Python2 & Python3. """
|
|
return '%s' % data
|
|
|
|
|
|
def get_time(seconds):
|
|
"""Convert given seconds to days, hours, minutes and seconds.
|
|
|
|
Args:
|
|
seconds (float): Time in seconds.
|
|
|
|
Returns:
|
|
Dictionary that contains the corresponding days, hours, minutes
|
|
and seconds of the given seconds.
|
|
|
|
"""
|
|
dtime = dict(seconds=0, minutes=0, hours=0, days=0)
|
|
|
|
dtime['days'] = int(seconds / 86400)
|
|
dtime['hours'] = int(seconds % 86400 / 3600)
|
|
dtime['minutes'] = int(seconds % 86400 % 3600 / 60)
|
|
dtime['seconds'] = int(seconds % 86400 % 3600 % 60)
|
|
|
|
return dtime
|
|
|
|
|
|
def get_locale_file():
|
|
"""Search for youtube-dlg locale file.
|
|
|
|
Returns:
|
|
The path to youtube-dlg locale file if exists else None.
|
|
|
|
Note:
|
|
Paths that get_locale_file() func searches.
|
|
|
|
__main__ dir, library dir, /usr/share/youtube-dlg/locale
|
|
|
|
"""
|
|
DIR_NAME = 'locale'
|
|
|
|
SEARCH_DIRS = [
|
|
os.path.join(absolute_path(sys.argv[0]), DIR_NAME),
|
|
os.path.join(os_path_dirname(__file__), DIR_NAME),
|
|
os.path.join('/usr', 'share', __appname__.lower(), DIR_NAME)
|
|
]
|
|
|
|
for directory in SEARCH_DIRS:
|
|
if os_path_isdir(directory):
|
|
return directory
|
|
|
|
return None
|
|
|
|
|
|
def get_icon_file():
|
|
"""Search for youtube-dlg app icon.
|
|
|
|
Returns:
|
|
The path to youtube-dlg icon file if exists, else returns None.
|
|
|
|
Note:
|
|
Paths that get_icon_file() function searches.
|
|
|
|
__main__ dir, library dir, $XDG_DATA_DIRS
|
|
|
|
"""
|
|
ICON_NAME = "youtube-dl-gui.png"
|
|
SIZES = ("256", "128", "64", "48", "32", "16")
|
|
|
|
DIR_TEMPLATE = os.path.join("icons", "hicolor", "{size}x{size}", "apps", ICON_NAME)
|
|
|
|
ICONS = [DIR_TEMPLATE.format(size=size) for size in SIZES]
|
|
|
|
search_dirs = [
|
|
os.path.join(absolute_path(sys.argv[0]), "data"),
|
|
os.path.join(os_path_dirname(__file__), "data"),
|
|
]
|
|
|
|
# Append $XDG_DATA_DIRS on search_dirs
|
|
path = os_getenv("XDG_DATA_DIRS")
|
|
|
|
if path is not None:
|
|
for xdg_path in path.split(':'):
|
|
search_dirs.append(xdg_path)
|
|
|
|
# Also append /usr/share/pixmaps on search_dirs
|
|
#search_dirs.append("/usr/share/pixmaps")
|
|
|
|
for directory in search_dirs:
|
|
for icon in ICONS:
|
|
icon_file = os.path.join(directory, icon)
|
|
|
|
if os_path_exists(icon_file):
|
|
return icon_file
|
|
|
|
return None
|
|
|
|
|
|
def get_pixmaps_dir():
|
|
"""Return absolute path to the pixmaps icons folder."""
|
|
# TODO probably will need support for other directories py2exe etc
|
|
return os.path.join(absolute_path(__file__), "data", "pixmaps")
|
|
|
|
|
|
def json_load(filename):
|
|
if os_path_exists(filename):
|
|
with open(filename) as input_json_file:
|
|
return json.load(input_json_file)
|
|
|
|
return []
|
|
|
|
|
|
def json_store(filename, item):
|
|
with open(filename, 'w') as output_json_file:
|
|
json.dump(item, output_json_file)
|
|
|
|
def read_formats():
|
|
"""Returns a twodict containing all the formats from 'data/formats'."""
|
|
# TODO Support for other directories? Test with py2exe
|
|
formats_file = os.path.join(absolute_path(__file__), "data", "formats")
|
|
|
|
if os_path_exists(formats_file):
|
|
formats_dict = TwoWayOrderedDict()
|
|
|
|
with open(formats_file) as input_file:
|
|
for line in input_file:
|
|
format_id, format_label = line.split('-')
|
|
|
|
formats_dict[format_id.strip()] = format_label.strip()
|
|
|
|
return formats_dict
|
|
|
|
return None
|