tartube/lib/utils.py

565 lines
14 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 A S Lewis
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
"""Utility functions used by code copied from youtube-dl-gui."""
# Import Gtk modules
from gi.repository import Gtk, Gdk
# Import other modules
import datetime
import locale
import math
import os
import re
import subprocess
import sys
import textwrap
# Import our modules
from . import constants
from . import mainapp
if mainapp.HAVE_VALIDATORS_FLAG:
from . import validators
# Functions
def add_links_from_clipboard(app_obj, widget):
"""Called by mainwin.AddVideoDialogue.__init__(),
mainwin.AddChannelDialogue.__init__() and
mainwin.AddPlaylistDialogue.__init__().
Function to add valid URLs from the clipboard to a Gtk.TextView, ignoring
anything that is not a valid URL.
Args:
app_obj (mainapp.TartubeApp): The main application
widget (Gtk.Entry or Gtk.TextBuffer): The widget to which valis URLs
should be added. If an entry, only the first valid URL is added.
If a textbuffer, all valid URLs are added
"""
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
cliptext = clipboard.wait_for_text()
valid_list = []
if cliptext is not None and cliptext != Gdk.SELECTION_CLIPBOARD:
for line in cliptext.split('\n'):
if not mainapp.HAVE_VALIDATORS_FLAG \
or not app_obj.use_module_validators_flag \
or validators.url(line):
valid_list.append(line)
if valid_list:
if isinstance(widget, Gtk.Entry):
widget.set_text(valid_list[0])
elif isinstance(widget, Gtk.TextBuffer):
widget.set_text(str.join('\n', valid_list))
def convert_item(item, to_unicode=False):
"""Based on the convert_item() function in youtube-dl-gui. Now called by
various functions in downloads.VideoDownloader and in this module.
Convert item between 'unicode' and 'str'.
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'.
Returns:
The converted item
"""
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(
(
convert_item(sub_item, to_unicode),
convert_item(item[sub_item], to_unicode),
)
)
else:
temp_list.append(convert_item(sub_item, to_unicode))
return type(item)(temp_list)
return item
def convert_path_to_temp(app_obj, old_path, move_flag=False):
"""Called by mainwin.MainWin.results_list_update_row() and
downloads.VideoDownloader.confirm_sim_video().
Converts a full path to a file that would be stored in Tartube's data
directory (mainapp.TartubeApp.downloads_dir) into the equivalent path in
Tartube's temporary directory (mainapp.TartubeApp.temp_dl_dir).
Optionally moves a file from one location to the other.
Regardless of whether the file is moved or not, creates the destination
sub-directory if it doesn't already exist, and deletes the destination file
if it already exists (both of which prevent exceptions being raised).
Args:
app_obj (mainapp.TartubeApp): The main application
old_path (string): Full path to the existing file
move_flag (True, False): If True, the file is actually moved to the
new location
Returns:
new_path: The converted full file path
"""
data_dir_len = len(app_obj.downloads_dir)
new_path = app_obj.temp_dl_dir + old_path[data_dir_len:]
new_dir, new_filename = os.path.split(new_path.strip("\""))
# The destination folder must exist, before moving files into it
if not os.path.exists(new_dir):
os.makedirs(new_dir)
# On MS Windows, a file name new_path must not exist, or an exception will
# be raised
if os.path.isfile(new_path):
os.remove(new_path)
# Move the file now, if the calling code requires that
if move_flag:
os.rename(old_path, new_path)
# Return the converted file path
return new_path
def convert_seconds_to_string(seconds, short_flag=False):
"""Can be called by anything.
Converts a time in seconds into a formatted string.
Args:
seconds (int or float): The time to convert
short_flag (True or False): If True, show '05:15' rather than '0:05:15'
Returns:
The converted string, e.g. '05:12' or '16:05:12'
"""
# Round up fractional seconds
if seconds is not None:
if seconds != int(seconds):
seconds = int(seconds) + 1
else:
seconds = 1
if short_flag and seconds < 3600:
# When required, show 05:15 rather than 0:05:15
minutes = int(seconds / 60)
seconds = int(seconds % 60)
return '{:02d}:{:02d}'.format(minutes, seconds)
else:
return str(datetime.timedelta(seconds=seconds))
def convert_youtube_to_hooktube(url):
"""Can be called by anything.
Converts a YouTube weblink to a HookTube weblink (but doesn't modify links
to other sites.
Args:
url (string): The weblink to convert
Returns:
The converted string
"""
if re.search(r'^https?:\/\/(www)+\.youtube\.com', url):
url = re.sub(
r'youtube\.com',
'hooktube.com',
url,
# Substitute first occurence only
1,
)
return url
def find_thumbnail(app_obj, video_obj, temp_dir_flag=False):
"""Called by mainwin.MainWin.results_list_update_row() and
mainwin.ComplexCatalogueItem.update_thumb_image().
No way to know which image format is used by all websites for their video
thumbnails, so look for the most common ones, and return the path to the
thumbnail file if one is found.
Args:
app_obj (mainapp.TartubeApp): The main application
video_obj (media.Video): The video object handling the downloaded video
temp_dir_flag (True, False): If True, this function will look in
Tartube's temporary data directory, if the thumbnail isn't found in
the main data directory.
Returns:
path (string): The full path to the thumbnail file, or None
"""
for ext in ('.jpg', '.png', '.gif'):
# Look in main data directory
path = os.path.join(
video_obj.file_dir,
video_obj.file_name + ext,
)
if os.path.isfile(path):
return path
elif temp_dir_flag:
# Look in temporary data directory
data_dir_len = len(app_obj.downloads_dir)
temp_path = app_obj.temp_dl_dir + path[data_dir_len:]
if os.path.isfile(temp_path):
return temp_path
return None
def format_bytes(num_bytes):
"""Based on the format_bytes() function in youtube-dl-gui. Now called by
media.Video.get_file_size_string() and so on.
Convert bytes into a formatted string, e.g. '23.5GiB'.
Args:
num_bytes (float): The number to convert
Returns:
The formatted string
"""
if num_bytes == 0.0:
exponent = 0
else:
exponent = int(math.log(num_bytes, constants.KILO_SIZE))
suffix = constants.FILESIZE_METRIC_LIST[exponent]
output_value = num_bytes / (constants.KILO_SIZE ** exponent)
return "%.2f%s" % (output_value, suffix)
def get_encoding():
"""Based on the get_encoding() function in youtube-dl-gui. Now called
by utils.convert_item().
Returns:
The system encoding.
"""
try:
encoding = locale.getpreferredencoding()
'TEST'.encode(encoding)
except:
encoding = 'UTF-8'
return encoding
def open_file(uri):
"""Can be called by anything.
Opens a file using the system's default software (e.g. open a media file in
the default media player; open a weblink in the default browser).
Args:
uri (string): The URI to open
"""
if sys.platform == "win32":
os.startfile(uri)
else:
opener ="open" if sys.platform == "darwin" else "xdg-open"
subprocess.call([opener, uri])
def remove_shortcuts(path):
"""Based on the remove_shortcuts() function in youtube-dl-gui. Now called
by options.OptionsParser.build_save_path().
Return the specified path after removing any shortcuts.
Args:
path (string): The path to convert
Returns:
The converted path
"""
return path.replace('~', os.path.expanduser('~'))
def shorten_string(string, num_chars):
"""Can be called by anything.
If string is longer than num_chars, truncates it and adds an ellipsis.
Args:
string (string): The string to convert
num_chars (int): The maximum length of the desired string
Returns:
The converted string
"""
if string and len(string) > num_chars:
num_chars -= 3
string = string[:num_chars] + '...'
return string
def to_string(data):
"""Based on the to_string() function in youtube-dl-gui. Now called by
by options.OptionsParser.parse(), .build_file_sizes() and so on.
Convert any data type to a string. Works for both Python2 and Python3.
Args:
data (-): The data type
Returns:
The converted string
"""
return '%s' % data
def upper_case_first(string):
"""Can be called by anything, but mainly used to capitalise
__main__.__packagename__.
Args:
string (string): The string to capitalise
Returns:
The converted string
"""
return string[0].upper() + string[1:]
def tidy_up_long_string(string, max_length=80, reduce_flag=True):
"""Called by mainwin.MainWin.errors_list_add_row() (or by anything else).
The specified string can contain any number of newline characters.
Replaces newline characters with a single space character.
Optionally reduces multiple whitespace characters and removes initial/
final whitespace character(s).
Then splits the string into a list of lines, each with the specified
maximum length.
Finally recombines those lines into a single string, with lines joined by
newline characters.
Args:
string (str): The string to convert
max_length (int): The maximum lenght of lines, before they are
recombined into a single string
reduce_flag (True, False): If True, initial and final whitespace is
removed, and multiple successive whitespace characters are
reduced to a single space character
Returns:
The converted string
"""
if string:
string = re.sub(r'\r\n', ' ', string)
if reduce_flag:
string = re.sub(r'\s+', ' ', string)
string = re.sub(r'^\s+', '', string)
string = re.sub(r'\s+$', '', string)
line_list = []
for line in string.split('\n'):
new_list = textwrap.wrap(
line,
width=max_length,
# Don't split up URLs
break_long_words=False,
break_on_hyphens=False,
)
for mini_line in new_list:
line_list.append(mini_line)
return '\n'.join(line_list)
else:
# Empty string
return string
def tidy_up_long_descrip(string, max_length=80):
"""Called by media.Video.set_video_descrip().
A modified version of utils.tidy_up_long_string. In this case, the
specified string can contain any number of newline characters. We begin
by splitting that string into a list of lines.
Then we split any line which is longer than the specified maximum length,
which gives us a (possibly longer) list of lines.
Finally we recombine those lines into a single string, with lines joined by
newline characters.
Args:
string (str): The string to convert
max_length (int): The maximum lenght of lines, before they are
recombined into a single string
Returns:
The converted string
"""
if string:
line_list = []
for line in string.split('\n'):
new_list = textwrap.wrap(
line,
width=max_length,
# Don't split up URLs
break_long_words=False,
break_on_hyphens=False,
)
for mini_line in new_list:
line_list.append(mini_line)
return '\n'.join(line_list)
else:
# Empty string
return string