565 lines
14 KiB
Python
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
|
|
|
|
|