Add files via upload
This commit is contained in:
parent
424ccecc6b
commit
8cca9e28d5
13
README.rst
13
README.rst
@ -10,6 +10,12 @@ Tartube is **alpha software**. It crashes a lot. If you find this
|
||||
frustrating, find a solution and then `send it to
|
||||
me <https://github.com/axcore/tartube/issues>`__.
|
||||
|
||||
Screenshots
|
||||
-----------
|
||||
|
||||
.. image:: screenshots/tartube.png
|
||||
:alt: Tartube screenshot
|
||||
|
||||
Why should I use Tartube?
|
||||
-------------------------
|
||||
|
||||
@ -20,8 +26,7 @@ Why should I use Tartube?
|
||||
- Tartube will organise your videos into convenient folders
|
||||
- Certain popular video websites manipulate search results, repeatedly
|
||||
unsubscribe people from their favourite channels and/or deliberately
|
||||
conceal videos which challenge the Californian political consensus.
|
||||
Tartube won't do any of those things
|
||||
conceal videos which challenge their preferred political views. Tartube won't do any of those things
|
||||
- Tartube can, in some circumstances, see videos that are
|
||||
region-blocked and age-restricted
|
||||
|
||||
@ -50,7 +55,7 @@ Install from source
|
||||
|
||||
1. Download & extract the source
|
||||
2. Change directory into the Tartube directory
|
||||
3. Run ``python setup.py install``
|
||||
3. Run ``python3 setup.py install``
|
||||
|
||||
Install using PyPI
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
@ -67,7 +72,7 @@ Run without installing
|
||||
|
||||
1. Download & extract the source
|
||||
2. Change directory into the Tartube directory
|
||||
3. Run 'python tartube.py'
|
||||
3. Run 'python3 tartube.py'
|
||||
|
||||
Frequently-Asked Questions
|
||||
--------------------------
|
||||
|
104
lib/config.py
104
lib/config.py
@ -179,7 +179,7 @@ class GenericConfigWin(Gtk.Window):
|
||||
|
||||
also_self (an object inheriting from config.GenericConfigWin):
|
||||
another copy of self
|
||||
|
||||
|
||||
"""
|
||||
|
||||
self.app_obj.main_win_obj.del_child_window(self)
|
||||
@ -825,9 +825,9 @@ class GenericEditWin(GenericConfigWin):
|
||||
showing their original values.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
button (Gtk.Button): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# Destroy the window
|
||||
@ -841,7 +841,7 @@ class GenericEditWin(GenericConfigWin):
|
||||
Destroys any changes made by the user and then closes the window.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
button (Gtk.Button): The widget clicked
|
||||
|
||||
"""
|
||||
@ -861,7 +861,7 @@ class GenericEditWin(GenericConfigWin):
|
||||
showing their original values.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
button (Gtk.Button): The widget clicked
|
||||
|
||||
"""
|
||||
@ -891,11 +891,11 @@ class GenericEditWin(GenericConfigWin):
|
||||
selected, False if not.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
checkbutton (Gtk.CheckButton): The widget clicked
|
||||
|
||||
prop (string): The attribute in self.edit_obj to modify
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if not checkbutton.get_active():
|
||||
@ -911,11 +911,11 @@ class GenericEditWin(GenericConfigWin):
|
||||
Temporarily stores the contents of the widget in self.edit_dict.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
combo (Gtk.ComboBox): The widget clicked
|
||||
|
||||
prop (string): The attribute in self.edit_obj to modify
|
||||
|
||||
|
||||
"""
|
||||
|
||||
tree_iter = combo.get_active_iter()
|
||||
@ -931,11 +931,11 @@ class GenericEditWin(GenericConfigWin):
|
||||
value, and stores the later in self.edit_dict.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
combo (Gtk.ComboBox): The widget clicked
|
||||
|
||||
prop (string): The attribute in self.edit_obj to modify
|
||||
|
||||
|
||||
"""
|
||||
|
||||
tree_iter = combo.get_active_iter()
|
||||
@ -950,11 +950,11 @@ class GenericEditWin(GenericConfigWin):
|
||||
Temporarily stores the contents of the widget in self.edit_dict.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
entry (Gtk.Entry): The widget clicked
|
||||
|
||||
prop (string): The attribute in self.edit_obj to modify
|
||||
|
||||
|
||||
"""
|
||||
|
||||
self.edit_dict[prop] = entry.get_text()
|
||||
@ -968,13 +968,13 @@ class GenericEditWin(GenericConfigWin):
|
||||
(from those in the group) is the selected one.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
checkbutton (Gtk.CheckButton): The widget clicked
|
||||
|
||||
prop (string): The attribute in self.edit_obj to modify
|
||||
|
||||
value (-): The attribute's new value
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if radiobutton.get_active():
|
||||
@ -988,11 +988,11 @@ class GenericEditWin(GenericConfigWin):
|
||||
Temporarily stores the contents of the widget in self.edit_dict.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
spinbutton (Gtk.SpinkButton): The widget clicked
|
||||
|
||||
prop (string): The attribute in self.edit_obj to modify
|
||||
|
||||
|
||||
"""
|
||||
|
||||
self.edit_dict[prop] = int(spinbutton.get_value())
|
||||
@ -1005,11 +1005,11 @@ class GenericEditWin(GenericConfigWin):
|
||||
Temporarily stores the contents of the widget in self.edit_dict.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
textbuffer (Gtk.TextBuffer): The widget modified
|
||||
|
||||
prop (string): The attribute in self.edit_obj to modify
|
||||
|
||||
|
||||
"""
|
||||
|
||||
self.edit_dict[prop] = textbuffer.get_text(
|
||||
@ -1030,9 +1030,9 @@ class GenericEditWin(GenericConfigWin):
|
||||
Apply download options to the media data object.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
button (Gtk.Button): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if self.edit_obj.options_obj:
|
||||
@ -1056,9 +1056,9 @@ class GenericEditWin(GenericConfigWin):
|
||||
Edit download options for the media data object.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
button (Gtk.Button): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if not self.edit_obj.options_obj:
|
||||
@ -1082,9 +1082,9 @@ class GenericEditWin(GenericConfigWin):
|
||||
Remove download options from the media data object.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
button (Gtk.Button): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if not self.edit_obj.options_obj:
|
||||
@ -1567,7 +1567,7 @@ class OptionsEditWin(GenericEditWin):
|
||||
Returns:
|
||||
|
||||
The original or modified value of that attribute.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if name in self.edit_dict:
|
||||
@ -2452,7 +2452,7 @@ class OptionsEditWin(GenericEditWin):
|
||||
button (Gtk.Button): The widget clicked
|
||||
|
||||
entry (Gtk.Entry): Another widget to be modified by this function
|
||||
|
||||
|
||||
combo (Gtk.ComboBox): Another widget to be modified by this
|
||||
function
|
||||
|
||||
@ -2489,7 +2489,7 @@ class OptionsEditWin(GenericEditWin):
|
||||
combo (Gtk.ComboBox): The widget clicked
|
||||
|
||||
entry (Gtk.Entry): Another widget to be modified by this function
|
||||
|
||||
|
||||
"""
|
||||
|
||||
tree_iter = combo.get_active_iter()
|
||||
@ -2514,7 +2514,7 @@ class OptionsEditWin(GenericEditWin):
|
||||
Args:
|
||||
|
||||
entry (Gtk.Entry): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# Only set 'output_template' when option 3 is selected, which is when
|
||||
@ -2540,7 +2540,7 @@ class OptionsEditWin(GenericEditWin):
|
||||
|
||||
other_liststore (Gtk.ListStore): Another widget to be modified by
|
||||
this function
|
||||
|
||||
|
||||
"""
|
||||
|
||||
selection = treeview.get_selection()
|
||||
@ -2592,7 +2592,7 @@ class OptionsEditWin(GenericEditWin):
|
||||
|
||||
treeview (Gtk.TreeView): Another widget to be modified by this
|
||||
function
|
||||
|
||||
|
||||
"""
|
||||
|
||||
selection = treeview.get_selection()
|
||||
@ -2647,11 +2647,11 @@ class OptionsEditWin(GenericEditWin):
|
||||
|
||||
add_button, up_button, down_button (Gtk.Button): Other widgets to
|
||||
be modified by this function
|
||||
|
||||
|
||||
|
||||
treeview (Gtk.TreeView): Another widget to be modified by this
|
||||
function
|
||||
|
||||
|
||||
"""
|
||||
|
||||
selection = treeview.get_selection()
|
||||
@ -2702,7 +2702,7 @@ class OptionsEditWin(GenericEditWin):
|
||||
|
||||
treeview (Gtk.TreeView): Another widget to be modified by this
|
||||
function
|
||||
|
||||
|
||||
"""
|
||||
|
||||
selection = treeview.get_selection()
|
||||
@ -2758,7 +2758,7 @@ class OptionsEditWin(GenericEditWin):
|
||||
function
|
||||
|
||||
prop (string): The attribute in self.edit_obj to modify
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if radiobutton.get_active():
|
||||
@ -4361,7 +4361,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
checkbutton (Gtk.CheckButton): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
other_flag = self.app_obj.main_win_obj.checkbutton2.get_active()
|
||||
@ -4398,7 +4398,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
checkbutton (Gtk.CheckButton): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
redraw_flag = False
|
||||
@ -4430,7 +4430,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
button (Gtk.Button): The widget clicked
|
||||
|
||||
entry (Gtk.Entry): Another widget to be modified by this function
|
||||
|
||||
|
||||
"""
|
||||
|
||||
dialogue_win = Gtk.FileChooserDialog(
|
||||
@ -4488,7 +4488,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
radiobutton (Gtk.RadioButton): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
default_val = self.app_obj.match_default_chars
|
||||
@ -4526,7 +4526,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
spinbutton (Gtk.SpinButton): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if spinbutton == self.spinbutton:
|
||||
@ -4544,7 +4544,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
checkbutton (Gtk.CheckButton): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if checkbutton.get_active() \
|
||||
@ -4565,7 +4565,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
checkbutton (Gtk.CheckButton): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if checkbutton.get_active() and not self.app_obj.operation_save_flag:
|
||||
@ -4584,7 +4584,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
checkbutton (Gtk.CheckButton): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if checkbutton.get_active() \
|
||||
@ -4605,7 +4605,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
checkbutton (Gtk.CheckButton): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if checkbutton.get_active() \
|
||||
@ -4615,7 +4615,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
and self.app_obj.ytdl_write_stdout_flag:
|
||||
self.app_obj.set_ytdl_write_stdout_flag(False)
|
||||
|
||||
|
||||
|
||||
def on_update_combo_changed(self, combo):
|
||||
|
||||
"""Called from a callback in self.setup_ytdl_tab().
|
||||
@ -4626,7 +4626,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
combo (Gtk.ComboBox): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
tree_iter = combo.get_active_iter()
|
||||
@ -4643,7 +4643,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
checkbutton (Gtk.CheckButton): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if checkbutton.get_active() \
|
||||
@ -4663,7 +4663,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
checkbutton (Gtk.CheckButton): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if checkbutton.get_active() \
|
||||
@ -4685,7 +4685,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
checkbutton (Gtk.CheckButton): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
other_flag = self.app_obj.main_win_obj.checkbutton.get_active()
|
||||
@ -4707,7 +4707,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
spinbutton (Gtk.SpinButton): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
self.app_obj.main_win_obj.spinbutton.set_value(spinbutton.get_value())
|
||||
@ -4723,10 +4723,10 @@ class SystemPrefWin(GenericPrefWin):
|
||||
Args:
|
||||
|
||||
combo (Gtk.ComboBox): The widget clicked
|
||||
|
||||
|
||||
"""
|
||||
|
||||
tree_iter = combo.get_active_iter()
|
||||
model = combo.get_model()
|
||||
self.app_obj.set_ytdl_path(model[tree_iter][1])
|
||||
|
||||
|
||||
|
466
lib/downloads.py
466
lib/downloads.py
@ -98,6 +98,8 @@ class DownloadManager(threading.Thread):
|
||||
|
||||
def __init__(self, app_obj, force_sim_flag, download_list_obj):
|
||||
|
||||
print('dl 101 __init__')
|
||||
|
||||
super(DownloadManager, self).__init__()
|
||||
|
||||
# IV list - class objects
|
||||
@ -172,6 +174,8 @@ class DownloadManager(threading.Thread):
|
||||
download operation is complete.
|
||||
"""
|
||||
|
||||
print('dl 177 run')
|
||||
|
||||
# Perform the download operation until there is nothing left to
|
||||
# download, or until something has called
|
||||
# self.stop_download_operation()
|
||||
@ -267,6 +271,8 @@ class DownloadManager(threading.Thread):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 274 change_worker_count')
|
||||
|
||||
# How many workers do we have already?
|
||||
current = len(self.worker_list)
|
||||
# If this object hasn't set up its worker pool yet, let the setup code
|
||||
@ -319,9 +325,11 @@ class DownloadManager(threading.Thread):
|
||||
|
||||
True if all downloads.DownloadWorker objects have finished their
|
||||
jobs, otherwise returns False
|
||||
|
||||
|
||||
"""
|
||||
|
||||
print('dl 331 check_workers_all_finished')
|
||||
|
||||
for worker_obj in self.worker_list:
|
||||
if not worker_obj.available_flag:
|
||||
return False
|
||||
@ -339,9 +347,11 @@ class DownloadManager(threading.Thread):
|
||||
|
||||
The first available downloads.DownloadWorker, or None if there are
|
||||
no available workers.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
print('dl 353 get_available_worker')
|
||||
|
||||
for worker_obj in self.worker_list:
|
||||
if worker_obj.available_flag:
|
||||
return worker_obj
|
||||
@ -362,6 +372,8 @@ class DownloadManager(threading.Thread):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 375 remove_worker')
|
||||
|
||||
new_list = []
|
||||
|
||||
for other_obj in self.worker_list:
|
||||
@ -382,6 +394,8 @@ class DownloadManager(threading.Thread):
|
||||
loop, the downloads.DownloadWorker objects are cleaned up.
|
||||
"""
|
||||
|
||||
print('dl 397 stop_download_operation')
|
||||
|
||||
self.running_flag = False
|
||||
|
||||
|
||||
@ -411,6 +425,8 @@ class DownloadWorker(threading.Thread):
|
||||
|
||||
def __init__(self, download_manager_obj):
|
||||
|
||||
print('dl 428 __init__')
|
||||
|
||||
super(DownloadWorker, self).__init__()
|
||||
|
||||
# IV list - class objects
|
||||
@ -465,6 +481,8 @@ class DownloadWorker(threading.Thread):
|
||||
create a new downloads.VideoDownloader object and wait for the result.
|
||||
"""
|
||||
|
||||
print('dl 484 run')
|
||||
|
||||
# Import the main application (for convenience)
|
||||
app_obj = self.download_manager_obj.app_obj
|
||||
|
||||
@ -534,6 +552,8 @@ class DownloadWorker(threading.Thread):
|
||||
Tidy up IVs and stop any child processes.
|
||||
"""
|
||||
|
||||
print('dl 555 close')
|
||||
|
||||
self.running_flag = False
|
||||
if self.video_downloader_obj:
|
||||
self.video_downloader_obj.stop()
|
||||
@ -552,9 +572,11 @@ class DownloadWorker(threading.Thread):
|
||||
download_item_obj (downloads.DownloadItem): The download item
|
||||
object describing the URL from which youtube-dl should download
|
||||
video(s).
|
||||
|
||||
|
||||
"""
|
||||
|
||||
print('dl 578 prepare_download')
|
||||
|
||||
self.download_item_obj = download_item_obj
|
||||
self.options_manager_obj = download_item_obj.options_manager_obj
|
||||
self.options_list = self.download_manager_obj.options_parser_obj.parse(
|
||||
@ -569,6 +591,8 @@ class DownloadWorker(threading.Thread):
|
||||
|
||||
"""Called by downloads.DownloadManager.change_worker_count()."""
|
||||
|
||||
print('dl 594 set_doomed_flag')
|
||||
|
||||
self.doomed_flag = flag
|
||||
|
||||
|
||||
@ -595,6 +619,8 @@ class DownloadWorker(threading.Thread):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 622 data_callback')
|
||||
|
||||
app_obj = self.download_manager_obj.app_obj
|
||||
app_obj.main_win_obj.progress_list_receive_dl_stats(
|
||||
self.download_item_obj,
|
||||
@ -635,6 +661,8 @@ class DownloadList(object):
|
||||
|
||||
def __init__(self, app_obj, media_data_obj):
|
||||
|
||||
print('dl 664 __init__')
|
||||
|
||||
# IV list - class objects
|
||||
# -----------------------
|
||||
self.app_obj = app_obj
|
||||
@ -711,6 +739,8 @@ class DownloadList(object):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 742 change_item_stage')
|
||||
|
||||
self.download_item_dict[dbid].stage = new_stage
|
||||
|
||||
|
||||
@ -743,6 +773,8 @@ class DownloadList(object):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 776 create_item')
|
||||
|
||||
# Get the options.OptionsManager object that applies to this media
|
||||
# data object
|
||||
# (The manager might be specified by obj itself, or it might be
|
||||
@ -805,9 +837,11 @@ class DownloadList(object):
|
||||
|
||||
The next downloads.DownloadItem object, or None if there are none
|
||||
left.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
print('dl 843 fetch_next_item')
|
||||
|
||||
for dbid in self.download_item_list:
|
||||
this_item = self.download_item_dict[dbid]
|
||||
|
||||
@ -841,6 +875,8 @@ class DownloadList(object):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 878 get_options_manager')
|
||||
|
||||
if media_data_obj.options_obj:
|
||||
return media_data_obj.options_obj
|
||||
elif media_data_obj.parent_obj:
|
||||
@ -877,6 +913,8 @@ class DownloadItem(object):
|
||||
|
||||
def __init__(self, dbid, media_data_obj, options_manager_obj):
|
||||
|
||||
print('dl 916 __init__')
|
||||
|
||||
# IV list - class objects
|
||||
# -----------------------
|
||||
# The media data object to be downloaded
|
||||
@ -965,6 +1003,8 @@ class VideoDownloader(object):
|
||||
def __init__(self, download_manager_obj, download_worker_obj, \
|
||||
download_item_obj):
|
||||
|
||||
print('dl 1006 __init__')
|
||||
|
||||
# IV list - class objects
|
||||
# -----------------------
|
||||
# The downloads.DownloadManager object handling the entire download
|
||||
@ -1059,126 +1099,6 @@ class VideoDownloader(object):
|
||||
# Public class methods
|
||||
|
||||
|
||||
def OLDdo_download(self):
|
||||
|
||||
"""Called by downloads.DownloadWorker.run().
|
||||
|
||||
Based on YoutubeDLDownloader.download().
|
||||
|
||||
Downloads video(s) from a URL described by self.download_item_obj.
|
||||
|
||||
Returns:
|
||||
|
||||
The final return code, a value in the range 0-5 (as described
|
||||
above)
|
||||
|
||||
"""
|
||||
|
||||
# Import the main application (for convenience)
|
||||
app_obj = self.download_manager_obj.app_obj
|
||||
|
||||
# Set the default return code. Everything is OK unless we encounter
|
||||
# any problems
|
||||
self.return_code = self.OK
|
||||
|
||||
# Reset the errors/warnings stored in the media data object, the last
|
||||
# time it was checked/downloaded
|
||||
self.download_item_obj.media_data_obj.reset_error_warning()
|
||||
|
||||
# Prepare a system command...
|
||||
cmd_list = self.get_system_cmd()
|
||||
# ...and create a new child process using that command
|
||||
self.create_child_process(cmd_list)
|
||||
|
||||
# So that we can read from the child process STDOUT and STDERR, attach
|
||||
# a file descriptor to the PipeReader objects
|
||||
if self.child_process is not None:
|
||||
|
||||
self.stdout_reader.attach_file_descriptor(
|
||||
self.child_process.stdout,
|
||||
)
|
||||
|
||||
self.stderr_reader.attach_file_descriptor(
|
||||
self.child_process.stderr,
|
||||
)
|
||||
|
||||
# While downloading the video, update the callback function with
|
||||
# the status of the current job
|
||||
while self.is_child_process_alive():
|
||||
|
||||
# Read from the child process STDOUT, and convert into unicode for
|
||||
# Python's convenience
|
||||
while not self.stdout_queue.empty():
|
||||
|
||||
stdout = self.stdout_queue.get_nowait().rstrip()
|
||||
stdout = utils.convert_item(stdout, to_unicode=True)
|
||||
|
||||
if stdout:
|
||||
# Convert the statistics into a python dictionary in a
|
||||
# standard format, specified in the comments for
|
||||
# self.extract_stdout_data()
|
||||
dl_stat_dict = self.extract_stdout_data(stdout)
|
||||
# If the job's status is constants.COMPLETED_STAGE_ALREADY
|
||||
# or constants.ERROR_STAGE_ABORT, set our
|
||||
# self.return_code IV
|
||||
self.extract_stdout_status(dl_stat_dict)
|
||||
# Pass the dictionary on to self.download_worker_obj so the
|
||||
# main window can be updated
|
||||
self.download_worker_obj.data_callback(dl_stat_dict)
|
||||
|
||||
if (app_obj.ytdl_write_stdout_flag):
|
||||
print(stdout)
|
||||
|
||||
# The child process has finished
|
||||
while not self.stderr_queue.empty():
|
||||
|
||||
# Read from the child process STDERR queue (we don't need to read
|
||||
# it in real time), and convert into unicode for python's
|
||||
# convenience
|
||||
stderr = self.stderr_queue.get_nowait().rstrip()
|
||||
stderr = utils.convert_item(stderr, to_unicode=True)
|
||||
|
||||
if self.is_warning(stderr):
|
||||
self.set_return_code(self.WARNING)
|
||||
self.download_item_obj.media_data_obj.set_warning(stderr)
|
||||
|
||||
else:
|
||||
self.set_return_code(self.ERROR)
|
||||
self.download_item_obj.media_data_obj.set_error(stderr)
|
||||
|
||||
if (app_obj.ytdl_write_stderr_flag):
|
||||
print(stderr)
|
||||
|
||||
# We also set the return code to self.ERROR if the download didn't
|
||||
# start or if the child process return code is greater than 0
|
||||
# Original notes from youtube-dl-gui:
|
||||
# NOTE: In Linux if the called script is just empty Python exits
|
||||
# normally (ret=0), so we cant detect this or similar cases
|
||||
# using the code below
|
||||
# NOTE: In Unix a negative return code (-N) indicates that the child
|
||||
# was terminated by signal N (e.g. -9 = SIGKILL)
|
||||
if self.child_process is None:
|
||||
self.set_return_code(self.ERROR)
|
||||
self.download_item_obj.media_data_obj.set_error(
|
||||
'Download did not start',
|
||||
)
|
||||
|
||||
elif self.child_process.returncode > 0:
|
||||
self.set_return_code(self.ERROR)
|
||||
self.download_item_obj.media_data_obj.set_error(
|
||||
'Child process exited with non-zero code: {}'.format(
|
||||
self.child_process.returncode,
|
||||
)
|
||||
)
|
||||
|
||||
# Pass a dictionary of values to downloads.DownloadWorker, confirming
|
||||
# the result of the job. The values are passed on to the main
|
||||
# window
|
||||
self.last_data_callback()
|
||||
|
||||
# Pass the result back to the parent downloads.DownloadWorker object
|
||||
return self.return_code
|
||||
|
||||
def do_download(self):
|
||||
|
||||
"""Called by downloads.DownloadWorker.run().
|
||||
@ -1194,6 +1114,8 @@ class VideoDownloader(object):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 1117 do_download')
|
||||
|
||||
# Import the main application (for convenience)
|
||||
app_obj = self.download_manager_obj.app_obj
|
||||
|
||||
@ -1257,11 +1179,11 @@ class VideoDownloader(object):
|
||||
# Read from the child process STDERR queue (we don't need to read
|
||||
# it in real time), and convert into unicode for python's
|
||||
# convenience
|
||||
# # (Convert Python2 to Python3)
|
||||
# # (Convert Python2 to Python3)
|
||||
# stderr = self.stderr_queue.get_nowait().rstrip()
|
||||
# stderr = utils.convert_item(stderr, to_unicode=True)
|
||||
stderr = self.stderr_queue.get_nowait().rstrip().decode('utf-8')
|
||||
|
||||
|
||||
if self.is_warning(stderr):
|
||||
self.set_return_code(self.WARNING)
|
||||
self.download_item_obj.media_data_obj.set_warning(stderr)
|
||||
@ -1311,6 +1233,8 @@ class VideoDownloader(object):
|
||||
Destructor function for this object.
|
||||
"""
|
||||
|
||||
print('dl 1236 close')
|
||||
|
||||
# Tell the PipeReader objects to shut down, thus joining their threads
|
||||
self.stdout_reader.join()
|
||||
self.stderr_reader.join()
|
||||
@ -1337,6 +1261,8 @@ class VideoDownloader(object):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 1264 confirm_new_video')
|
||||
|
||||
if not self.video_num in self.video_check_dict:
|
||||
self.video_check_dict[self.video_num] = filename
|
||||
|
||||
@ -1386,6 +1312,8 @@ class VideoDownloader(object):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 1315 confirm_old_video')
|
||||
|
||||
# Create shortcut variables (for convenience)
|
||||
app_obj = self.download_manager_obj.app_obj
|
||||
media_data_obj = self.download_item_obj.media_data_obj
|
||||
@ -1435,206 +1363,6 @@ class VideoDownloader(object):
|
||||
)
|
||||
|
||||
|
||||
def OLDconfirm_sim_video(self, json_dict):
|
||||
|
||||
"""Called by self.extract_stdout_data().
|
||||
|
||||
After a successful simulated download, youtube-dl presents us with JSON
|
||||
data for the video. Use that data to update everything.
|
||||
|
||||
Args:
|
||||
|
||||
json_dict (dict): JSON data from STDOUT, converted into a python
|
||||
dictionary
|
||||
|
||||
"""
|
||||
|
||||
# IMport the main application (for convenience)
|
||||
app_obj = self.download_manager_obj.app_obj
|
||||
|
||||
# From the JSON dictionary, extract the data we need
|
||||
if '_filename' in json_dict:
|
||||
full_path = json_dict['_filename']
|
||||
path, filename, extension = self.extract_filename(full_path)
|
||||
else:
|
||||
return app_obj.system_error(
|
||||
302,
|
||||
'Missing filename in JSON data',
|
||||
)
|
||||
|
||||
if 'upload_date' in json_dict:
|
||||
# date_string in form YYYYMMDD
|
||||
date_string = json_dict['upload_date']
|
||||
dt_obj = datetime.datetime.strptime(date_string, '%Y%m%d')
|
||||
upload_time = dt_obj.strftime('%s')
|
||||
else:
|
||||
upload_time = None
|
||||
|
||||
if 'duration' in json_dict:
|
||||
duration = json_dict['duration']
|
||||
else:
|
||||
duration = None
|
||||
|
||||
if 'title' in json_dict:
|
||||
name = json_dict['title']
|
||||
else:
|
||||
name = None
|
||||
|
||||
if 'description' in json_dict:
|
||||
descrip = json_dict['description']
|
||||
else:
|
||||
descrip = None
|
||||
|
||||
if 'thumbnail' in json_dict:
|
||||
thumbnail = json_dict['thumbnail']
|
||||
else:
|
||||
thumbnail = None
|
||||
|
||||
if 'webpage_url' in json_dict:
|
||||
source = json_dict['webpage_url']
|
||||
else:
|
||||
source = None
|
||||
|
||||
if 'playlist_index' in json_dict:
|
||||
playlist_index = json_dict['playlist_index']
|
||||
else:
|
||||
playlist_index = None
|
||||
|
||||
# Does an existing media.Video object match this video?
|
||||
media_data_obj = self.download_item_obj.media_data_obj
|
||||
video_obj = None
|
||||
if isinstance(media_data_obj, media.Video):
|
||||
video_obj = media_data_obj
|
||||
else:
|
||||
# media_data_obj is a media.Channel or media.Playlist object. Check
|
||||
# its child objects, looking for a matching video
|
||||
# (video_obj is set to None, if no match is found)
|
||||
video_obj = media_data_obj.find_matching_video(app_obj, filename)
|
||||
|
||||
if not video_obj:
|
||||
|
||||
# No matching media.Video object found, so create a new one
|
||||
video_obj = app_obj.create_video_from_download(
|
||||
self.download_item_obj,
|
||||
path,
|
||||
filename,
|
||||
extension,
|
||||
)
|
||||
|
||||
# Update its IVs with the JSON information we extracted
|
||||
if name is not None:
|
||||
video_obj.set_name(name)
|
||||
|
||||
if upload_time is not None:
|
||||
video_obj.set_upload_time(upload_time)
|
||||
|
||||
if duration is not None:
|
||||
video_obj.set_duration(duration)
|
||||
|
||||
if source is not None:
|
||||
video_obj.set_source(source)
|
||||
|
||||
if descrip is not None:
|
||||
video_obj.set_video_descrip(
|
||||
descrip,
|
||||
app_obj.main_win_obj.long_string_max_len,
|
||||
)
|
||||
|
||||
# Only save the playlist index when this video is actually stored
|
||||
# inside a media.Playlist object
|
||||
if isinstance(video_obj.parent_obj, media.Playlist) \
|
||||
and playlist_index is not None:
|
||||
video_obj.set_index(playlist_index)
|
||||
|
||||
else:
|
||||
|
||||
# If the 'Add videos' button was used, the path/filename/extension
|
||||
# won't be set yet
|
||||
if not video_obj.file_dir and full_path:
|
||||
video_obj.set_file(path, filename, extension)
|
||||
|
||||
# Update any video object IVs that are not set
|
||||
if video_obj.name == app_obj.default_video_name \
|
||||
and name is not None:
|
||||
video_obj.set_name(name)
|
||||
|
||||
if not video_obj.upload_time and upload_time is not None:
|
||||
video_obj.set_upload_time(upload_time)
|
||||
|
||||
if not video_obj.duration and duration is not None:
|
||||
video_obj.set_duration(duration)
|
||||
|
||||
if not video_obj.source and source is not None:
|
||||
video_obj.set_source(source)
|
||||
|
||||
if not video_obj.descrip and descrip is not None:
|
||||
video_obj.set_video_descrip(
|
||||
descrip,
|
||||
app_obj.main_win_obj.long_string_max_len,
|
||||
)
|
||||
|
||||
# Only save the playlist index when this video is actually stored
|
||||
# inside a media.Playlist object
|
||||
if not video_obj.index \
|
||||
and isinstance(video_obj.parent_obj, media.Playlist) \
|
||||
and playlist_index is not None:
|
||||
video_obj.set_index(playlist_index)
|
||||
|
||||
# Deal with the video description, JSON data and thumbnail, according
|
||||
# to the settings in options.OptionsManager
|
||||
options_dict =self.download_worker_obj.options_manager_obj.options_dict
|
||||
|
||||
if descrip and options_dict['write_description']:
|
||||
descrip_path = os.path.join(path, filename + '.description')
|
||||
if not options_dict['sim_keep_description']:
|
||||
descrip_path = utils.convert_path_to_temp(
|
||||
app_obj,
|
||||
descrip_path,
|
||||
)
|
||||
|
||||
# (Don't replace a file that already exists)
|
||||
if not os.path.isfile(descrip_path):
|
||||
fh = open(descrip_path, 'w')
|
||||
fh.write(descrip.encode('utf-8'))
|
||||
fh.close()
|
||||
|
||||
if options_dict['write_info']:
|
||||
json_path = os.path.join(path, filename + '.info.json')
|
||||
if not options_dict['sim_keep_info']:
|
||||
json_path = utils.convert_path_to_temp(app_obj, json_path)
|
||||
|
||||
if not os.path.isfile(json_path):
|
||||
with open(json_path, 'w') as outfile:
|
||||
json.dump(json_dict, outfile, indent=4)
|
||||
|
||||
if thumbnail and options_dict['write_thumbnail']:
|
||||
|
||||
# Download the thumbnail, if we don't already have it
|
||||
# The thumbnail's URL is something like
|
||||
# 'https://i.ytimg.com/vi/abcdefgh/maxresdefault.jpg'
|
||||
# When saved to disc by youtube-dl, the file is given the same name
|
||||
# as the video (but with a different extension)
|
||||
# Get the thumbnail's extension...
|
||||
remote_file, remote_ext = os.path.splitext(thumbnail)
|
||||
|
||||
# ...and thus get the filename used by youtube-dl when storing the
|
||||
# thumbnail locally
|
||||
thumb_path = os.path.join(
|
||||
video_obj.file_dir,
|
||||
video_obj.file_name + remote_ext,
|
||||
)
|
||||
|
||||
if not options_dict['sim_keep_thumbnail']:
|
||||
thumb_path = utils.convert_path_to_temp(app_obj, thumb_path)
|
||||
|
||||
if not os.path.isfile(thumb_path):
|
||||
request_obj = requests.get(thumbnail)
|
||||
with open(thumb_path, 'wb') as outfile:
|
||||
outfile.write(request_obj.content)
|
||||
|
||||
# Update the main window
|
||||
app_obj.announce_video_download(self.download_item_obj, video_obj)
|
||||
|
||||
def confirm_sim_video(self, json_dict):
|
||||
|
||||
"""Called by self.extract_stdout_data().
|
||||
@ -1649,7 +1377,9 @@ class VideoDownloader(object):
|
||||
|
||||
"""
|
||||
|
||||
# IMport the main application (for convenience)
|
||||
print('dl 1380 confirm_sim_video')
|
||||
|
||||
# Import the main application (for convenience)
|
||||
app_obj = self.download_manager_obj.app_obj
|
||||
|
||||
# From the JSON dictionary, extract the data we need
|
||||
@ -1859,6 +1589,8 @@ class VideoDownloader(object):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 1592 create_child_process')
|
||||
|
||||
info = preexec = None
|
||||
if os.name == 'nt':
|
||||
# Hide the child process window that MS Windows helpfully creates
|
||||
@ -1911,6 +1643,8 @@ class VideoDownloader(object):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 1646 extract_filename')
|
||||
|
||||
path, fullname = os.path.split(input_data.strip("\""))
|
||||
filename, extension = os.path.splitext(fullname)
|
||||
|
||||
@ -1956,6 +1690,8 @@ class VideoDownloader(object):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 1693 extract_stdout_data')
|
||||
|
||||
# Initialise the dictionary with default key-value pairs for the main
|
||||
# window to display, to be overwritten (if possible) with new key-
|
||||
# value pairs as this function interprets the STDOUT message
|
||||
@ -1981,7 +1717,7 @@ class VideoDownloader(object):
|
||||
# Extract the data
|
||||
stdout_list[0] = stdout_list[0].lstrip('\r')
|
||||
if stdout_list[0] == '[download]':
|
||||
|
||||
|
||||
dl_stat_dict['status'] = constants.ACTIVE_STAGE_DOWNLOAD
|
||||
|
||||
# Get path, filename and extension
|
||||
@ -2043,7 +1779,7 @@ class VideoDownloader(object):
|
||||
dl_stat_dict['status'] = constants.ERROR_STAGE_ABORT
|
||||
|
||||
elif stdout_list[0] == '[hlsnative]':
|
||||
|
||||
|
||||
# Get information from the native HLS extractor (see
|
||||
# https://github.com/rg3/youtube-dl/blob/master/youtube_dl/
|
||||
# downloader/hls.py#L54
|
||||
@ -2058,7 +1794,7 @@ class VideoDownloader(object):
|
||||
dl_stat_dict['percent'] = percent
|
||||
|
||||
elif stdout_list[0] == '[ffmpeg]':
|
||||
|
||||
|
||||
# Using FFmpeg, not the the native HLS extractor
|
||||
# A successful video download is announced in one of several ways.
|
||||
# Use the first announcement to update self.video_check_dict, and
|
||||
@ -2104,7 +1840,7 @@ class VideoDownloader(object):
|
||||
self.confirm_new_video(path, filename, extension)
|
||||
|
||||
elif stdout_list[0][0] == '{':
|
||||
|
||||
|
||||
# JSON data, the result of a simulated download. Convert to a
|
||||
# python dictionary
|
||||
if self.dl_sim_flag:
|
||||
@ -2130,12 +1866,12 @@ class VideoDownloader(object):
|
||||
dl_stat_dict['status'] = constants.ACTIVE_STAGE_CHECKING
|
||||
|
||||
elif stdout_list[0][0] != '[' or stdout_list[0] == '[debug]':
|
||||
|
||||
|
||||
# (Just ignore this output)
|
||||
return dl_stat_dict
|
||||
|
||||
else:
|
||||
|
||||
|
||||
# The download has started
|
||||
dl_stat_dict['status'] = constants.ACTIVE_STAGE_PRE_PROCESS
|
||||
|
||||
@ -2155,13 +1891,15 @@ class VideoDownloader(object):
|
||||
(which halts the download).
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
dl_stat_dict (dict): The Python dictionary returned by the call to
|
||||
self.extract_stdout_data(), in the standard form described by
|
||||
the comments for that function
|
||||
|
||||
"""
|
||||
|
||||
print('dl 1901 extract_stdout_status')
|
||||
|
||||
if 'status' in dl_stat_dict:
|
||||
if dl_stat_dict['status'] == constants.COMPLETED_STAGE_ALREADY:
|
||||
self.set_return_code(self.ALREADY)
|
||||
@ -2182,11 +1920,13 @@ class VideoDownloader(object):
|
||||
youtube-dl.
|
||||
|
||||
Returns:
|
||||
|
||||
|
||||
Python list that contains the system command to execute.
|
||||
|
||||
"""
|
||||
|
||||
print('dl 1928 get_system_cmd')
|
||||
|
||||
options_list = self.download_worker_obj.options_list
|
||||
|
||||
# Simulate the download, rather than actually downloading videos, if
|
||||
@ -2220,6 +1960,8 @@ class VideoDownloader(object):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 1963 is_child_process_alive')
|
||||
|
||||
if self.child_process is None:
|
||||
return False
|
||||
|
||||
@ -2236,7 +1978,7 @@ class VideoDownloader(object):
|
||||
checks the STERR message to see if it's an error or just a warning.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
stderr (string): A message from the child process STDERR.
|
||||
|
||||
Returns:
|
||||
@ -2245,6 +1987,8 @@ class VideoDownloader(object):
|
||||
|
||||
"""
|
||||
|
||||
print('dl 1990 is_warning')
|
||||
|
||||
return stderr.split(':')[0] == 'WARNING'
|
||||
|
||||
|
||||
@ -2264,6 +2008,8 @@ class VideoDownloader(object):
|
||||
The new key-value pairs are used to update the main window.
|
||||
"""
|
||||
|
||||
print('dl 2011 last_data_callback')
|
||||
|
||||
dl_stat_dict = {}
|
||||
|
||||
if self.return_code == self.OK:
|
||||
@ -2308,11 +2054,13 @@ class VideoDownloader(object):
|
||||
is higher in the hierarchy of return codes than the current value.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
code (int): A return code in the range 0-5
|
||||
|
||||
"""
|
||||
|
||||
print('dl 2062 set_return_code')
|
||||
|
||||
if code >= self.return_code:
|
||||
self.return_code = code
|
||||
|
||||
@ -2325,6 +2073,8 @@ class VideoDownloader(object):
|
||||
self.STOPPED.
|
||||
"""
|
||||
|
||||
print('dl 2076 stop')
|
||||
|
||||
if self.is_child_process_alive():
|
||||
|
||||
if os.name == 'nt':
|
||||
@ -2371,6 +2121,8 @@ class PipeReader(threading.Thread):
|
||||
|
||||
def __init__(self, queue):
|
||||
|
||||
print('dl 2124 __init__')
|
||||
|
||||
super(PipeReader, self).__init__()
|
||||
|
||||
# IV list - other
|
||||
@ -2398,34 +2150,6 @@ class PipeReader(threading.Thread):
|
||||
# Public class methods
|
||||
|
||||
|
||||
def OLDOLDrun(self):
|
||||
|
||||
"""Called as a result of self.__init__().
|
||||
|
||||
Reads from STDOUT or STERR using the attached filed descriptor.
|
||||
"""
|
||||
|
||||
# Use this flag so that the loop can ignore FFmpeg error messsages
|
||||
# (because the parent VideoDownloader object shouldn't use that as a
|
||||
# serious error)
|
||||
ignore_line = False
|
||||
|
||||
while self.running_flag:
|
||||
|
||||
if self.file_descriptor is not None:
|
||||
for line in iter(self.file_descriptor.readline, str('')):
|
||||
|
||||
if str('ffmpeg version') in line:
|
||||
ignore_line = True
|
||||
|
||||
if not ignore_line:
|
||||
self.output_queue.put_nowait(line)
|
||||
|
||||
self.file_descriptor = None
|
||||
ignore_line = False
|
||||
|
||||
time.sleep(self.sleep_time)
|
||||
|
||||
def run(self):
|
||||
|
||||
"""Called as a result of self.__init__().
|
||||
@ -2433,6 +2157,8 @@ class PipeReader(threading.Thread):
|
||||
Reads from STDOUT or STERR using the attached filed descriptor.
|
||||
"""
|
||||
|
||||
print('dl 2160 run')
|
||||
|
||||
# Use this flag so that the loop can ignore FFmpeg error messsages
|
||||
# (because the parent VideoDownloader object shouldn't use that as a
|
||||
# serious error)
|
||||
@ -2472,9 +2198,11 @@ class PipeReader(threading.Thread):
|
||||
Args:
|
||||
|
||||
filedesc (filehandle): The open filehandle for STDOUT or STDERR
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
print('dl 2204 attach_file_descriptor')
|
||||
|
||||
self.file_descriptor = filedesc
|
||||
|
||||
|
||||
@ -2488,9 +2216,11 @@ class PipeReader(threading.Thread):
|
||||
Args:
|
||||
|
||||
timeout (-): No calling code sets a timeout
|
||||
|
||||
|
||||
"""
|
||||
|
||||
print('dl 2222 join')
|
||||
|
||||
self.running_flag = False
|
||||
super(PipeReader, self).join(timeout)
|
||||
|
||||
|
14
lib/files.py
14
lib/files.py
@ -57,7 +57,7 @@ class FileManager(threading.Thread):
|
||||
|
||||
# Public class methods
|
||||
|
||||
|
||||
|
||||
def load_json(self, full_path):
|
||||
|
||||
"""Can be called by anything.
|
||||
@ -66,7 +66,7 @@ class FileManager(threading.Thread):
|
||||
dictionary and returns the dictionary.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
full_path (string): The full path to the JSON file
|
||||
|
||||
Returns:
|
||||
@ -95,20 +95,20 @@ class FileManager(threading.Thread):
|
||||
Args:
|
||||
|
||||
full_path (string): The full path to the text file
|
||||
|
||||
|
||||
Returns:
|
||||
|
||||
The contents of the text file as a string, or or None if the file
|
||||
is missing or can't be loaded
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if not os.path.isfile(full_path):
|
||||
return None
|
||||
|
||||
|
||||
with open(full_path, 'r') as text_file:
|
||||
text = text_file.read()
|
||||
|
||||
|
||||
return text
|
||||
|
||||
|
||||
@ -129,7 +129,7 @@ class FileManager(threading.Thread):
|
||||
Returns:
|
||||
|
||||
A GdkPixbuf, or None if the file is missing or can't be loaded
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if not os.path.isfile(full_path):
|
||||
|
323
lib/mainapp.py
323
lib/mainapp.py
File diff suppressed because it is too large
Load Diff
678
lib/mainwin.py
678
lib/mainwin.py
File diff suppressed because it is too large
Load Diff
142
lib/media.py
142
lib/media.py
@ -121,7 +121,7 @@ class GenericContainer(GenericMedia):
|
||||
video_list (list): A list of media.Video objects
|
||||
|
||||
Returns:
|
||||
|
||||
|
||||
The modified video_list
|
||||
|
||||
"""
|
||||
@ -139,7 +139,7 @@ class GenericContainer(GenericMedia):
|
||||
def count_descendants(self, count_list):
|
||||
|
||||
"""Can be called by anything. Currently called by
|
||||
mainwin.DeleteContainerDialogue.__init__(), and then again by this
|
||||
mainwin.DeleteContainerDialogue.__init__(), and then again by this
|
||||
function recursively.
|
||||
|
||||
Counts the number of child objects, and then calls this function
|
||||
@ -155,7 +155,7 @@ class GenericContainer(GenericMedia):
|
||||
)
|
||||
|
||||
Returns:
|
||||
|
||||
|
||||
The modified count_list
|
||||
|
||||
"""
|
||||
@ -196,7 +196,7 @@ class GenericContainer(GenericMedia):
|
||||
was not a child of this object
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Check this is really one of our children
|
||||
index = self.find_child_index(child_obj)
|
||||
if index is None:
|
||||
@ -227,7 +227,7 @@ class GenericContainer(GenericMedia):
|
||||
|
||||
An integer describing the position in self.child_list, or None of
|
||||
the child object is not found in self.child_list
|
||||
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
@ -252,7 +252,7 @@ class GenericContainer(GenericMedia):
|
||||
Returns:
|
||||
|
||||
The container object's level
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if self.parent_obj is None:
|
||||
@ -283,7 +283,7 @@ class GenericContainer(GenericMedia):
|
||||
can't be hidden directly.)
|
||||
|
||||
Returns:
|
||||
|
||||
|
||||
True or False.
|
||||
|
||||
"""
|
||||
@ -357,7 +357,7 @@ class GenericContainer(GenericMedia):
|
||||
Returns:
|
||||
|
||||
The full path to the directory
|
||||
|
||||
|
||||
"""
|
||||
|
||||
dir_list = [self.name]
|
||||
@ -401,40 +401,6 @@ class GenericRemoteContainer(GenericContainer):
|
||||
self.vid_count += 1
|
||||
|
||||
|
||||
def OLDdo_sort(self, obj1, obj2):
|
||||
|
||||
"""Sorting function used by functools.cmp_to_key(), and called by
|
||||
self.sort_children().
|
||||
|
||||
Sort videos by upload time, with the most recent video first.
|
||||
|
||||
When downloading a channel or playlist, we assume that YouTube (etc)
|
||||
supplies us with the most recent upload first. Therefore, when the
|
||||
upload time is the same, sort by the order in youtube-dl fetches the
|
||||
videos.
|
||||
|
||||
Args:
|
||||
|
||||
obj1, obj2 (media.Video) - Video objects being sorted
|
||||
|
||||
Returns:
|
||||
|
||||
-1 if obj1 comes first, 1 if obj2 comes first, 0 if they are equal
|
||||
|
||||
"""
|
||||
|
||||
if obj1.upload_time < obj2.upload_time:
|
||||
return 1
|
||||
elif obj1.upload_time == obj2.upload_time:
|
||||
if obj1.receive_time < obj2.receive_time:
|
||||
return -1
|
||||
elif obj1.receive_time == obj2.receive_time:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
else:
|
||||
return -1
|
||||
|
||||
def do_sort(self, obj1, obj2):
|
||||
|
||||
"""Sorting function used by functools.cmp_to_key(), and called by
|
||||
@ -448,13 +414,13 @@ class GenericRemoteContainer(GenericContainer):
|
||||
videos.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
obj1, obj2 (media.Video) - Video objects being sorted
|
||||
|
||||
Returns:
|
||||
|
||||
|
||||
-1 if obj1 comes first, 1 if obj2 comes first, 0 if they are equal
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# # Convert Python2 to Python3
|
||||
@ -659,7 +625,7 @@ class Video(GenericMedia):
|
||||
favourite.
|
||||
|
||||
Returns:
|
||||
|
||||
|
||||
True if the parent (or the parent's parent, and so on) is marked
|
||||
favourite, False otherwise
|
||||
|
||||
@ -688,7 +654,7 @@ class Video(GenericMedia):
|
||||
|
||||
max_length (int): When storing the description in this object's
|
||||
IVs, the maximum line length to use
|
||||
|
||||
|
||||
"""
|
||||
|
||||
descrip_path = os.path.join(
|
||||
@ -813,7 +779,7 @@ class Video(GenericMedia):
|
||||
max_length (int): A maximum line size
|
||||
|
||||
"""
|
||||
|
||||
|
||||
if descrip:
|
||||
|
||||
self.descrip = utils.tidy_up_long_descrip(descrip, max_length)
|
||||
@ -836,7 +802,7 @@ class Video(GenericMedia):
|
||||
Returns:
|
||||
|
||||
The converted string, or None if self.file_size is not set
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if self.file_size:
|
||||
@ -855,7 +821,7 @@ class Video(GenericMedia):
|
||||
Returns:
|
||||
|
||||
The formatted string, or None if self.receive_time is not set
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if self.receive_time:
|
||||
@ -874,7 +840,7 @@ class Video(GenericMedia):
|
||||
Returns:
|
||||
|
||||
The formatted string, or None if self.receive_time is not set
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if self.receive_time:
|
||||
@ -893,7 +859,7 @@ class Video(GenericMedia):
|
||||
Returns:
|
||||
|
||||
The formatted string, or None if self.upload_time is not set
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if self.upload_time:
|
||||
@ -912,7 +878,7 @@ class Video(GenericMedia):
|
||||
Returns:
|
||||
|
||||
The formatted string, or None if self.upload_time is not set
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if self.upload_time:
|
||||
@ -1326,70 +1292,6 @@ class Folder(GenericContainer):
|
||||
# def del_child(): # Inherited from GenericContainer
|
||||
|
||||
|
||||
def OLDdo_sort(self, obj1, obj2):
|
||||
|
||||
"""Sorting function used by functools.cmp_to_key(), and called by
|
||||
self.sort_children().
|
||||
|
||||
Sorts the child media.Video, media.Channel, media.Playlist and
|
||||
media.Folder objects.
|
||||
|
||||
Firstly, sort by class - folders, channels/playlists, then videos.
|
||||
|
||||
Within folders, channels and playlists, sort alphabetically. Within
|
||||
videos, sort by upload time.
|
||||
|
||||
Args:
|
||||
|
||||
obj1, obj2 (media.Video, media.Channel, media.Playlist or
|
||||
media.Folder) - Media data objects being sorted
|
||||
|
||||
Returns:
|
||||
|
||||
-1 if obj1 comes first, 1 if obj2 comes first, 0 if they are equal
|
||||
|
||||
"""
|
||||
|
||||
if str(obj1.__class__) == str(obj2.__class__) \
|
||||
or (
|
||||
isinstance(obj1, GenericRemoteContainer) \
|
||||
and isinstance(obj2, GenericRemoteContainer)
|
||||
):
|
||||
if isinstance(obj1, Video):
|
||||
|
||||
if obj1.upload_time < obj2.upload_time:
|
||||
return 1
|
||||
elif obj1.upload_time == obj2.upload_time:
|
||||
if obj1.receive_time < obj2.receive_time:
|
||||
return -1
|
||||
elif obj1.receive_time == obj2.receive_time:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
else:
|
||||
return -1
|
||||
|
||||
else:
|
||||
if obj1.name.lower() < obj2.name.lower:
|
||||
return -1
|
||||
elif obj1.name.lower() == obj2.name.lower:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
else:
|
||||
|
||||
if isinstance(obj1, Folder):
|
||||
return -1
|
||||
elif isinstance(obj2, Folder):
|
||||
return 1
|
||||
elif isinstance(obj1, Channel) or isinstance(obj1, Playlist):
|
||||
return -1
|
||||
elif isinstance(obj2, Channel) or isinstance(obj2, Playlist):
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def do_sort(self, obj1, obj2):
|
||||
|
||||
"""Sorting function used by functools.cmp_to_key(), and called by
|
||||
@ -1404,14 +1306,14 @@ class Folder(GenericContainer):
|
||||
videos, sort by upload time.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
obj1, obj2 (media.Video, media.Channel, media.Playlist or
|
||||
media.Folder) - Media data objects being sorted
|
||||
|
||||
Returns:
|
||||
|
||||
|
||||
-1 if obj1 comes first, 1 if obj2 comes first, 0 if they are equal
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if str(obj1.__class__) == str(obj2.__class__) \
|
||||
|
@ -539,7 +539,7 @@ class OptionsParser(object):
|
||||
taken from options.OptionsManager.options_dict
|
||||
|
||||
Returns:
|
||||
|
||||
|
||||
List of strings with all the youtube-dl command line options
|
||||
|
||||
"""
|
||||
@ -822,11 +822,11 @@ class OptionHolder(object):
|
||||
Check if options required by another option are enabled, or not.
|
||||
|
||||
Args:
|
||||
|
||||
|
||||
copy_dict (dict): Copy of the original options dictionary.
|
||||
|
||||
Returns:
|
||||
|
||||
|
||||
True if any of the required options is enabled, otherwise returns
|
||||
False.
|
||||
|
||||
@ -845,7 +845,7 @@ class OptionHolder(object):
|
||||
Returns:
|
||||
|
||||
True if the option is a boolean switch, otherwise returns False
|
||||
|
||||
|
||||
"""
|
||||
|
||||
return type(self.default_value) is bool
|
||||
|
@ -80,7 +80,7 @@ class RefreshManager(threading.Thread):
|
||||
|
||||
# Public class methods
|
||||
|
||||
|
||||
|
||||
def run(self):
|
||||
|
||||
"""Called by mainapp.TartubeApp.refresh_manager_start().
|
||||
|
@ -51,7 +51,7 @@ def add_test_media(app_obj):
|
||||
Args:
|
||||
|
||||
app_obj (mainapp.TartubeApp): The main application
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# Test videos
|
||||
|
@ -104,90 +104,6 @@ class UpdateManager(threading.Thread):
|
||||
|
||||
# Public class methods
|
||||
|
||||
|
||||
def OLDrun(self):
|
||||
|
||||
"""Called as a result of self.__init__().
|
||||
|
||||
Based on code from downloads.VideoDownloader.do_download().
|
||||
|
||||
Creates a child process to run the youtube-dl update.
|
||||
|
||||
Reads from the child process STDOUT and STDERR, and calls the main
|
||||
application with the result of the update (success or failure).
|
||||
"""
|
||||
|
||||
# Prepare the system command
|
||||
|
||||
# The user can change the system command for updating youtube-dl,
|
||||
# depending on how it was installed
|
||||
# (For example, if youtube-dl was installed via pip, then it must be
|
||||
# updated via pip)
|
||||
cmd_list \
|
||||
= self.app_obj.ytdl_update_dict[self.app_obj.ytdl_update_current]
|
||||
|
||||
# Create a new child process using that command
|
||||
self.create_child_process(cmd_list)
|
||||
|
||||
# So that we can read from the child process STDOUT and STDERR, attach
|
||||
# a file descriptor to the PipeReader objects
|
||||
if self.child_process is not None:
|
||||
|
||||
self.stdout_reader.attach_file_descriptor(
|
||||
self.child_process.stdout,
|
||||
)
|
||||
|
||||
self.stderr_reader.attach_file_descriptor(
|
||||
self.child_process.stderr,
|
||||
)
|
||||
|
||||
while self.is_child_process_alive():
|
||||
|
||||
# Read from the child process STDOUT, and convert into unicode for
|
||||
# Python's convenience
|
||||
while not self.stdout_queue.empty():
|
||||
|
||||
stdout = self.stdout_queue.get_nowait().rstrip()
|
||||
stdout = utils.convert_item(stdout, to_unicode=True)
|
||||
|
||||
if stdout:
|
||||
# "It looks like you installed youtube-dl with a package
|
||||
# manager, pip, setup.py or a tarball. Please use that to
|
||||
# update."
|
||||
if re.search('It looks like you installed', stdout):
|
||||
self.stderr_list.append(stdout)
|
||||
else:
|
||||
self.stdout_list.append(stdout)
|
||||
|
||||
# The child process has finished
|
||||
while not self.stderr_queue.empty():
|
||||
|
||||
# Read from the child process STDERR queue (we don't need to read
|
||||
# it in real time), and convert into unicode for python's
|
||||
# convenience
|
||||
stderr = self.stderr_queue.get_nowait().rstrip()
|
||||
stderr = utils.convert_item(stderr, to_unicode=True)
|
||||
|
||||
if stderr:
|
||||
self.stderr_list.append(stderr)
|
||||
|
||||
# (Generate our own error messages for debugging purposes, in certain
|
||||
# situations)
|
||||
if self.child_process is None:
|
||||
self.stderr_list.append('Download did not start')
|
||||
|
||||
elif self.child_process.returncode > 0:
|
||||
self.stderr_list.append(
|
||||
'Child process exited with non-zero code: {}'.format(
|
||||
self.child_process.returncode,
|
||||
)
|
||||
)
|
||||
|
||||
# Operation complete; inform the main application of success or failure
|
||||
if self.stderr_list:
|
||||
self.app_obj.update_manager_finished(False)
|
||||
else:
|
||||
self.app_obj.update_manager_finished(True)
|
||||
|
||||
def run(self):
|
||||
|
||||
@ -202,7 +118,7 @@ class UpdateManager(threading.Thread):
|
||||
"""
|
||||
|
||||
# Prepare the system command
|
||||
|
||||
|
||||
# The user can change the system command for updating youtube-dl,
|
||||
# depending on how it was installed
|
||||
# (For example, if youtube-dl was installed via pip, then it must be
|
||||
@ -251,11 +167,11 @@ class UpdateManager(threading.Thread):
|
||||
# Read from the child process STDERR queue (we don't need to read
|
||||
# it in real time), and convert into unicode for python's
|
||||
# convenience
|
||||
# # (Convert Python2 to Python3)
|
||||
# # (Convert Python2 to Python3)
|
||||
# stderr = self.stderr_queue.get_nowait().rstrip()
|
||||
# stderr = utils.convert_item(stderr, to_unicode=True)
|
||||
stderr = self.stderr_queue.get_nowait().rstrip().decode('utf-8')
|
||||
|
||||
|
||||
if stderr:
|
||||
self.stderr_list.append(stderr)
|
||||
|
||||
@ -351,7 +267,7 @@ class UpdateManager(threading.Thread):
|
||||
|
||||
"""Called by mainapp.TartubeApp.on_button_stop_operation(), .stop() and
|
||||
a callback in .on_button_stop_operation().
|
||||
|
||||
|
||||
Based on code from downloads.VideoDownloader.stop().
|
||||
|
||||
Terminates the child process.
|
||||
|
18
lib/utils.py
18
lib/utils.py
@ -90,7 +90,7 @@ def convert_item(item, to_unicode=False):
|
||||
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
|
||||
@ -98,7 +98,7 @@ def convert_item(item, to_unicode=False):
|
||||
back to 'str'.
|
||||
|
||||
Returns:
|
||||
|
||||
|
||||
The converted item
|
||||
|
||||
"""
|
||||
@ -310,7 +310,7 @@ def format_bytes(num_bytes):
|
||||
Returns:
|
||||
|
||||
The formatted string
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if num_bytes == 0.0:
|
||||
@ -332,7 +332,7 @@ def get_encoding():
|
||||
Returns:
|
||||
|
||||
The system encoding.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
@ -354,7 +354,7 @@ def open_file(uri):
|
||||
Args:
|
||||
|
||||
uri (string): The URI to open
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if sys.platform == "win32":
|
||||
@ -378,7 +378,7 @@ def remove_shortcuts(path):
|
||||
Returns:
|
||||
|
||||
The converted path
|
||||
|
||||
|
||||
"""
|
||||
|
||||
return path.replace('~', os.path.expanduser('~'))
|
||||
@ -399,7 +399,7 @@ def shorten_string(string, num_chars):
|
||||
Returns:
|
||||
|
||||
The converted string
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if string and len(string) > num_chars:
|
||||
@ -423,7 +423,7 @@ def to_string(data):
|
||||
Returns:
|
||||
|
||||
The converted string
|
||||
|
||||
|
||||
"""
|
||||
|
||||
return '%s' % data
|
||||
@ -441,7 +441,7 @@ def upper_case_first(string):
|
||||
Returns:
|
||||
|
||||
The converted string
|
||||
|
||||
|
||||
"""
|
||||
|
||||
return string[0].upper() + string[1:]
|
||||
|
BIN
screenshots/tartube.png
Normal file
BIN
screenshots/tartube.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 228 KiB |
4
setup.py
4
setup.py
@ -35,11 +35,11 @@ import setuptools
|
||||
# Setup
|
||||
setuptools.setup(
|
||||
name='tartube',
|
||||
version='0.1.007',
|
||||
version='0.1.015',
|
||||
description='GUI front-end for youtube-dl',
|
||||
# long_description=long_description,
|
||||
long_description="""Tartube is a GUI front-end for youtube-dl, partly based on youtube-dl-gui and written in Python 3 / Gtk 3""",
|
||||
long_description_content_type='text/markdown',
|
||||
long_description_content_type='text/markdown',
|
||||
author='A S Lewis',
|
||||
author_email='aslewis@cpan.org',
|
||||
url='https://github.com/axcore/tartube',
|
||||
|
@ -34,8 +34,8 @@ from lib import mainapp
|
||||
|
||||
# 'Global' variables
|
||||
__packagename__ = 'tartube'
|
||||
__version__ = '0.1.007'
|
||||
__date__ = '28 May 2019'
|
||||
__version__ = '0.1.015'
|
||||
__date__ = '3 Jun 2019'
|
||||
__copyright__ = 'Copyright \xc2\xa9 2019 A S Lewis'
|
||||
__license__ = """
|
||||
Copyright \xc2\xa9 2019 A S Lewis.
|
||||
|
Loading…
x
Reference in New Issue
Block a user