Add files via upload
parent
cdaa2ba53c
commit
c1916c395a
|
@ -0,0 +1,254 @@
|
||||||
|
Tartube
|
||||||
|
=======
|
||||||
|
|
||||||
|
Tartube is a GUI front-end for `youtube-dl <https://youtube-dl.org/>`__,
|
||||||
|
partly based on
|
||||||
|
`youtube-dl-gui <https://mrs0m30n3.github.io/youtube-dl-gui/>`__ and
|
||||||
|
written in Python 3 / Gtk 3.
|
||||||
|
|
||||||
|
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>`__.
|
||||||
|
|
||||||
|
Why should I use Tartube?
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
- You can download individual videos, and even whole channels and
|
||||||
|
playlists, from hundreds of different websites
|
||||||
|
- You can fetch information about those videos, channels and playlists,
|
||||||
|
without actually downloading anything
|
||||||
|
- 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
|
||||||
|
- Tartube can, in some circumstances, see videos that are
|
||||||
|
region-blocked and age-restricted
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
- A working installation of `youtube-dl <https://youtube-dl.org/>`__
|
||||||
|
- `Python 3+ <https://www.python.org/downloads>`__
|
||||||
|
- `Gtk 3+ <https://python-gtk-3-tutorial.readthedocs.io/en/latest/>`__
|
||||||
|
- `Python validators module <https://pypi.org/project/validators/>`__
|
||||||
|
optional, but recommended
|
||||||
|
- `Python moviepy module <https://pypi.org/project/moviepy/>`__
|
||||||
|
optional
|
||||||
|
|
||||||
|
Downloads
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `Source <http://tartube.sourceforge.io/>`__ from sourceforge.io
|
||||||
|
- `Source <https://github.com/axcore/tarbue>`__ from github
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
Install from source
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
1. Download & extract the source
|
||||||
|
2. Change directory into the Tartube directory
|
||||||
|
3. Run ``python setup.py install``
|
||||||
|
|
||||||
|
Install using PyPI
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
1. Run ``pip install tartube``
|
||||||
|
|
||||||
|
Install using MS Windows Installer
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
There is no installer for MS Windows yet.
|
||||||
|
|
||||||
|
Run without installing
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
1. Download & extract the source
|
||||||
|
2. Change directory into the Tartube directory
|
||||||
|
3. Run 'python tartube.py'
|
||||||
|
|
||||||
|
Frequently-Asked Questions
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
**Q: I can't install Tartube!**
|
||||||
|
|
||||||
|
A: I have no experience of writing in Python and I'm still working it
|
||||||
|
out for myself. Contact me on the `Github
|
||||||
|
issues <https://github.com/axcore/tartube/issues>`__ page if you can do
|
||||||
|
better than me.
|
||||||
|
|
||||||
|
**Q: I can't run Tartube!**
|
||||||
|
|
||||||
|
A: See above.
|
||||||
|
|
||||||
|
**Q: Tartube doesn't work properly!**
|
||||||
|
|
||||||
|
A: See above.
|
||||||
|
|
||||||
|
**Q: Tartube keeps crashing!**
|
||||||
|
|
||||||
|
A: See above.
|
||||||
|
|
||||||
|
**Q: How do I use Tartube?**
|
||||||
|
|
||||||
|
A: Assuming that Tartube is installed and running correctly, then you
|
||||||
|
should start by checking that youtube-dl is also installed and running
|
||||||
|
correctly.
|
||||||
|
|
||||||
|
- Click **Operations > Update youtube-dl**
|
||||||
|
|
||||||
|
There are several locations on your filesystem where youtube-dl might
|
||||||
|
have been installed. If the update operation fails, you should try
|
||||||
|
modifying Tartube's settings.
|
||||||
|
|
||||||
|
- Click **Edit > System preferences...**
|
||||||
|
- Click the **youtube-dl** tab
|
||||||
|
- Try changing the setting **'Actual path to use during
|
||||||
|
download/update/refresh operations'**
|
||||||
|
- Try changing the setting **'Shell command for update operations'**
|
||||||
|
- Try the update operation again
|
||||||
|
|
||||||
|
On the left side of the Tartube window is a list of folders. You can
|
||||||
|
store videos, channels and playlists inside these folders. You can even
|
||||||
|
store folders inside of other folders.
|
||||||
|
|
||||||
|
Tartube saves videos on your filesystem using exactly the same
|
||||||
|
structure.
|
||||||
|
|
||||||
|
When you start Tartube, there are five folders already visible. You
|
||||||
|
can't remove any of these folders (but you can hide them, if you want).
|
||||||
|
|
||||||
|
Videos saved to the 'Temporary Videos' folder are deleted when Tartube
|
||||||
|
shuts down.
|
||||||
|
|
||||||
|
Once you've finished adding videos, channels, playlists and folders,
|
||||||
|
there are four things Tartube can do:
|
||||||
|
|
||||||
|
- **'Check'** - Fetch information about videos, but don't download them
|
||||||
|
- **'Download'** - Actually download the videos. If you have disabled
|
||||||
|
downloads for a particular item, Tartube will just fetch information
|
||||||
|
about it instead
|
||||||
|
- **'Update'** - Updates youtube-dl, as described above
|
||||||
|
- **'Refresh'** - Examines your filesystem. If you have manually copied
|
||||||
|
any videos into Tartube's data directory, those videos are added to
|
||||||
|
Tartube's database
|
||||||
|
|
||||||
|
**Protip:** Do an **'Update'** operation before you do a **'Check'** or
|
||||||
|
**'Download'** operation
|
||||||
|
|
||||||
|
**Protip:** Do a **'Check'** operation before you do **'Refresh'**
|
||||||
|
operation
|
||||||
|
|
||||||
|
youtube-dl offers a large number of download options. This is how to set
|
||||||
|
them.
|
||||||
|
|
||||||
|
- Click **Edit > General download options...**
|
||||||
|
|
||||||
|
Any changes you make in the new window aren't actually applied until you
|
||||||
|
click the **'Apply'** or **'OK'** buttons.
|
||||||
|
|
||||||
|
Those are the *default* download options. If you want to apply a
|
||||||
|
*different* set of download options to a particular channel or
|
||||||
|
particular playlist, you can do so.
|
||||||
|
|
||||||
|
For example, suppose you have added these folders and channels:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Comedy folder
|
||||||
|
CollegeHumor channel
|
||||||
|
PewDiePie channel
|
||||||
|
Politics folder
|
||||||
|
Liberal folder
|
||||||
|
The Young Turks channel
|
||||||
|
Conservative folder
|
||||||
|
Joe Rogan channel
|
||||||
|
Mark Dice channel
|
||||||
|
|
||||||
|
The general download options apply to all of these channels. Now,
|
||||||
|
suppose you apply some download options to the Politics folder:
|
||||||
|
|
||||||
|
- Right-click the folder, and select **Apply download options...**
|
||||||
|
|
||||||
|
Tartube's database now looks something like this:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Comedy folder
|
||||||
|
CollegeHumor channel
|
||||||
|
PewDiePie channel
|
||||||
|
++Politics folder
|
||||||
|
Liberal folder
|
||||||
|
The Young Turks channel
|
||||||
|
Conservative folder
|
||||||
|
Joe Rogan channel
|
||||||
|
Mark Dice channel
|
||||||
|
|
||||||
|
The new download options (marked ++) apply to *everything* inside the
|
||||||
|
Politics folder - The Young Turks, Joe Rogan and Mark Dice.
|
||||||
|
|
||||||
|
Now, suppose you add another set of download options (marked @@) to the
|
||||||
|
Conservative folder:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Comedy folder
|
||||||
|
CollegeHumor channel
|
||||||
|
PewDiePie channel
|
||||||
|
**Politics folder
|
||||||
|
Liberal folder
|
||||||
|
The Young Turks channel
|
||||||
|
@@Conservative folder
|
||||||
|
Joe Rogan channel
|
||||||
|
Mark Dice channel
|
||||||
|
|
||||||
|
These new download options only apply to Joe Rogan and Mark Dice. They
|
||||||
|
don't apply to The Young Turks, which are still using the *previous* set
|
||||||
|
of download options. They don't apply to CollegeHumor or PewDiePie,
|
||||||
|
which are still using the *default* download options.
|
||||||
|
|
||||||
|
Future plans
|
||||||
|
------------
|
||||||
|
|
||||||
|
- Fix the endless crashes, somehow
|
||||||
|
- Support for multiple databases (so you can store videos on two
|
||||||
|
external hard drives at the same time)
|
||||||
|
- Add download scheduling
|
||||||
|
- Add video archiving
|
||||||
|
- Allow selection of multiple videos in the catalogue, so the same
|
||||||
|
action can be applied to all of them at the same time
|
||||||
|
- Tie channels and playlists together, so that they won't both download
|
||||||
|
the same video
|
||||||
|
- Add tooltips for everything
|
||||||
|
- Add more youtube-dl options
|
||||||
|
|
||||||
|
Known issues
|
||||||
|
------------
|
||||||
|
|
||||||
|
- Tartube crashes continuously and often
|
||||||
|
- Alphabetic sorting of channels/playlists/folders doesn't always work
|
||||||
|
as intended, due to an unresolved Gtk issue
|
||||||
|
- Channels/playlists/folder selection does not always work as intended,
|
||||||
|
due to an unresolved Gtk issue
|
||||||
|
- Users can type in comboboxes, but this should not be possible
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
------------
|
||||||
|
|
||||||
|
- Report a bug: Use the Github
|
||||||
|
`issues <https://github.com/axcore/tartube/issues>`__ page
|
||||||
|
|
||||||
|
Authors
|
||||||
|
-------
|
||||||
|
|
||||||
|
See the `AUTHORS <AUTHORS>`__ file.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
Tartube is licensed under the `GNU General Public License
|
||||||
|
v3.0 <https://www.gnu.org/licenses/gpl-3.0.en.html>`__.
|
||||||
|
|
||||||
|
✨🍰✨
|
|
@ -1 +1 @@
|
||||||
|
name = "tartube"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
#Tartube
|
112
lib/config.py
112
lib/config.py
|
@ -31,11 +31,11 @@ import os
|
||||||
|
|
||||||
|
|
||||||
# Import our modules
|
# Import our modules
|
||||||
import constants
|
|
||||||
import __main__
|
import __main__
|
||||||
import mainapp
|
from . import constants
|
||||||
import media
|
from . import mainapp
|
||||||
import utils
|
from . import media
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
|
@ -179,7 +179,7 @@ class GenericConfigWin(Gtk.Window):
|
||||||
|
|
||||||
also_self (an object inheriting from config.GenericConfigWin):
|
also_self (an object inheriting from config.GenericConfigWin):
|
||||||
another copy of self
|
another copy of self
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.app_obj.main_win_obj.del_child_window(self)
|
self.app_obj.main_win_obj.del_child_window(self)
|
||||||
|
@ -825,9 +825,9 @@ class GenericEditWin(GenericConfigWin):
|
||||||
showing their original values.
|
showing their original values.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
button (Gtk.Button): The widget clicked
|
button (Gtk.Button): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Destroy the window
|
# Destroy the window
|
||||||
|
@ -841,7 +841,7 @@ class GenericEditWin(GenericConfigWin):
|
||||||
Destroys any changes made by the user and then closes the window.
|
Destroys any changes made by the user and then closes the window.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
button (Gtk.Button): The widget clicked
|
button (Gtk.Button): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -861,7 +861,7 @@ class GenericEditWin(GenericConfigWin):
|
||||||
showing their original values.
|
showing their original values.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
button (Gtk.Button): The widget clicked
|
button (Gtk.Button): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -891,11 +891,11 @@ class GenericEditWin(GenericConfigWin):
|
||||||
selected, False if not.
|
selected, False if not.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton): The widget clicked
|
checkbutton (Gtk.CheckButton): The widget clicked
|
||||||
|
|
||||||
prop (string): The attribute in self.edit_obj to modify
|
prop (string): The attribute in self.edit_obj to modify
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not checkbutton.get_active():
|
if not checkbutton.get_active():
|
||||||
|
@ -911,11 +911,11 @@ class GenericEditWin(GenericConfigWin):
|
||||||
Temporarily stores the contents of the widget in self.edit_dict.
|
Temporarily stores the contents of the widget in self.edit_dict.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
combo (Gtk.ComboBox): The widget clicked
|
combo (Gtk.ComboBox): The widget clicked
|
||||||
|
|
||||||
prop (string): The attribute in self.edit_obj to modify
|
prop (string): The attribute in self.edit_obj to modify
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tree_iter = combo.get_active_iter()
|
tree_iter = combo.get_active_iter()
|
||||||
|
@ -931,11 +931,11 @@ class GenericEditWin(GenericConfigWin):
|
||||||
value, and stores the later in self.edit_dict.
|
value, and stores the later in self.edit_dict.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
combo (Gtk.ComboBox): The widget clicked
|
combo (Gtk.ComboBox): The widget clicked
|
||||||
|
|
||||||
prop (string): The attribute in self.edit_obj to modify
|
prop (string): The attribute in self.edit_obj to modify
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tree_iter = combo.get_active_iter()
|
tree_iter = combo.get_active_iter()
|
||||||
|
@ -950,11 +950,11 @@ class GenericEditWin(GenericConfigWin):
|
||||||
Temporarily stores the contents of the widget in self.edit_dict.
|
Temporarily stores the contents of the widget in self.edit_dict.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
entry (Gtk.Entry): The widget clicked
|
entry (Gtk.Entry): The widget clicked
|
||||||
|
|
||||||
prop (string): The attribute in self.edit_obj to modify
|
prop (string): The attribute in self.edit_obj to modify
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.edit_dict[prop] = entry.get_text()
|
self.edit_dict[prop] = entry.get_text()
|
||||||
|
@ -968,13 +968,13 @@ class GenericEditWin(GenericConfigWin):
|
||||||
(from those in the group) is the selected one.
|
(from those in the group) is the selected one.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton): The widget clicked
|
checkbutton (Gtk.CheckButton): The widget clicked
|
||||||
|
|
||||||
prop (string): The attribute in self.edit_obj to modify
|
prop (string): The attribute in self.edit_obj to modify
|
||||||
|
|
||||||
value (-): The attribute's new value
|
value (-): The attribute's new value
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if radiobutton.get_active():
|
if radiobutton.get_active():
|
||||||
|
@ -988,11 +988,11 @@ class GenericEditWin(GenericConfigWin):
|
||||||
Temporarily stores the contents of the widget in self.edit_dict.
|
Temporarily stores the contents of the widget in self.edit_dict.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
spinbutton (Gtk.SpinkButton): The widget clicked
|
spinbutton (Gtk.SpinkButton): The widget clicked
|
||||||
|
|
||||||
prop (string): The attribute in self.edit_obj to modify
|
prop (string): The attribute in self.edit_obj to modify
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.edit_dict[prop] = int(spinbutton.get_value())
|
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.
|
Temporarily stores the contents of the widget in self.edit_dict.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
textbuffer (Gtk.TextBuffer): The widget modified
|
textbuffer (Gtk.TextBuffer): The widget modified
|
||||||
|
|
||||||
prop (string): The attribute in self.edit_obj to modify
|
prop (string): The attribute in self.edit_obj to modify
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.edit_dict[prop] = textbuffer.get_text(
|
self.edit_dict[prop] = textbuffer.get_text(
|
||||||
|
@ -1030,9 +1030,9 @@ class GenericEditWin(GenericConfigWin):
|
||||||
Apply download options to the media data object.
|
Apply download options to the media data object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
button (Gtk.Button): The widget clicked
|
button (Gtk.Button): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.edit_obj.options_obj:
|
if self.edit_obj.options_obj:
|
||||||
|
@ -1056,9 +1056,9 @@ class GenericEditWin(GenericConfigWin):
|
||||||
Edit download options for the media data object.
|
Edit download options for the media data object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
button (Gtk.Button): The widget clicked
|
button (Gtk.Button): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.edit_obj.options_obj:
|
if not self.edit_obj.options_obj:
|
||||||
|
@ -1082,9 +1082,9 @@ class GenericEditWin(GenericConfigWin):
|
||||||
Remove download options from the media data object.
|
Remove download options from the media data object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
button (Gtk.Button): The widget clicked
|
button (Gtk.Button): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.edit_obj.options_obj:
|
if not self.edit_obj.options_obj:
|
||||||
|
@ -1567,7 +1567,7 @@ class OptionsEditWin(GenericEditWin):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The original or modified value of that attribute.
|
The original or modified value of that attribute.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if name in self.edit_dict:
|
if name in self.edit_dict:
|
||||||
|
@ -2452,7 +2452,7 @@ class OptionsEditWin(GenericEditWin):
|
||||||
button (Gtk.Button): The widget clicked
|
button (Gtk.Button): The widget clicked
|
||||||
|
|
||||||
entry (Gtk.Entry): Another widget to be modified by this function
|
entry (Gtk.Entry): Another widget to be modified by this function
|
||||||
|
|
||||||
combo (Gtk.ComboBox): Another widget to be modified by this
|
combo (Gtk.ComboBox): Another widget to be modified by this
|
||||||
function
|
function
|
||||||
|
|
||||||
|
@ -2489,7 +2489,7 @@ class OptionsEditWin(GenericEditWin):
|
||||||
combo (Gtk.ComboBox): The widget clicked
|
combo (Gtk.ComboBox): The widget clicked
|
||||||
|
|
||||||
entry (Gtk.Entry): Another widget to be modified by this function
|
entry (Gtk.Entry): Another widget to be modified by this function
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tree_iter = combo.get_active_iter()
|
tree_iter = combo.get_active_iter()
|
||||||
|
@ -2514,7 +2514,7 @@ class OptionsEditWin(GenericEditWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
entry (Gtk.Entry): The widget clicked
|
entry (Gtk.Entry): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Only set 'output_template' when option 3 is selected, which is when
|
# 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
|
other_liststore (Gtk.ListStore): Another widget to be modified by
|
||||||
this function
|
this function
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
selection = treeview.get_selection()
|
selection = treeview.get_selection()
|
||||||
|
@ -2592,7 +2592,7 @@ class OptionsEditWin(GenericEditWin):
|
||||||
|
|
||||||
treeview (Gtk.TreeView): Another widget to be modified by this
|
treeview (Gtk.TreeView): Another widget to be modified by this
|
||||||
function
|
function
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
selection = treeview.get_selection()
|
selection = treeview.get_selection()
|
||||||
|
@ -2647,11 +2647,11 @@ class OptionsEditWin(GenericEditWin):
|
||||||
|
|
||||||
add_button, up_button, down_button (Gtk.Button): Other widgets to
|
add_button, up_button, down_button (Gtk.Button): Other widgets to
|
||||||
be modified by this function
|
be modified by this function
|
||||||
|
|
||||||
|
|
||||||
treeview (Gtk.TreeView): Another widget to be modified by this
|
treeview (Gtk.TreeView): Another widget to be modified by this
|
||||||
function
|
function
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
selection = treeview.get_selection()
|
selection = treeview.get_selection()
|
||||||
|
@ -2702,7 +2702,7 @@ class OptionsEditWin(GenericEditWin):
|
||||||
|
|
||||||
treeview (Gtk.TreeView): Another widget to be modified by this
|
treeview (Gtk.TreeView): Another widget to be modified by this
|
||||||
function
|
function
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
selection = treeview.get_selection()
|
selection = treeview.get_selection()
|
||||||
|
@ -2758,7 +2758,7 @@ class OptionsEditWin(GenericEditWin):
|
||||||
function
|
function
|
||||||
|
|
||||||
prop (string): The attribute in self.edit_obj to modify
|
prop (string): The attribute in self.edit_obj to modify
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if radiobutton.get_active():
|
if radiobutton.get_active():
|
||||||
|
@ -4361,7 +4361,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton): The widget clicked
|
checkbutton (Gtk.CheckButton): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
other_flag = self.app_obj.main_win_obj.checkbutton2.get_active()
|
other_flag = self.app_obj.main_win_obj.checkbutton2.get_active()
|
||||||
|
@ -4398,7 +4398,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton): The widget clicked
|
checkbutton (Gtk.CheckButton): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
redraw_flag = False
|
redraw_flag = False
|
||||||
|
@ -4430,7 +4430,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
button (Gtk.Button): The widget clicked
|
button (Gtk.Button): The widget clicked
|
||||||
|
|
||||||
entry (Gtk.Entry): Another widget to be modified by this function
|
entry (Gtk.Entry): Another widget to be modified by this function
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dialogue_win = Gtk.FileChooserDialog(
|
dialogue_win = Gtk.FileChooserDialog(
|
||||||
|
@ -4488,7 +4488,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
radiobutton (Gtk.RadioButton): The widget clicked
|
radiobutton (Gtk.RadioButton): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
default_val = self.app_obj.match_default_chars
|
default_val = self.app_obj.match_default_chars
|
||||||
|
@ -4526,7 +4526,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
spinbutton (Gtk.SpinButton): The widget clicked
|
spinbutton (Gtk.SpinButton): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if spinbutton == self.spinbutton:
|
if spinbutton == self.spinbutton:
|
||||||
|
@ -4544,7 +4544,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton): The widget clicked
|
checkbutton (Gtk.CheckButton): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if checkbutton.get_active() \
|
if checkbutton.get_active() \
|
||||||
|
@ -4565,7 +4565,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton): The widget clicked
|
checkbutton (Gtk.CheckButton): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if checkbutton.get_active() and not self.app_obj.operation_save_flag:
|
if checkbutton.get_active() and not self.app_obj.operation_save_flag:
|
||||||
|
@ -4584,7 +4584,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton): The widget clicked
|
checkbutton (Gtk.CheckButton): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if checkbutton.get_active() \
|
if checkbutton.get_active() \
|
||||||
|
@ -4605,7 +4605,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton): The widget clicked
|
checkbutton (Gtk.CheckButton): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if checkbutton.get_active() \
|
if checkbutton.get_active() \
|
||||||
|
@ -4615,7 +4615,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
and self.app_obj.ytdl_write_stdout_flag:
|
and self.app_obj.ytdl_write_stdout_flag:
|
||||||
self.app_obj.set_ytdl_write_stdout_flag(False)
|
self.app_obj.set_ytdl_write_stdout_flag(False)
|
||||||
|
|
||||||
|
|
||||||
def on_update_combo_changed(self, combo):
|
def on_update_combo_changed(self, combo):
|
||||||
|
|
||||||
"""Called from a callback in self.setup_ytdl_tab().
|
"""Called from a callback in self.setup_ytdl_tab().
|
||||||
|
@ -4626,7 +4626,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
combo (Gtk.ComboBox): The widget clicked
|
combo (Gtk.ComboBox): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tree_iter = combo.get_active_iter()
|
tree_iter = combo.get_active_iter()
|
||||||
|
@ -4643,7 +4643,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton): The widget clicked
|
checkbutton (Gtk.CheckButton): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if checkbutton.get_active() \
|
if checkbutton.get_active() \
|
||||||
|
@ -4663,7 +4663,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton): The widget clicked
|
checkbutton (Gtk.CheckButton): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if checkbutton.get_active() \
|
if checkbutton.get_active() \
|
||||||
|
@ -4685,7 +4685,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton): The widget clicked
|
checkbutton (Gtk.CheckButton): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
other_flag = self.app_obj.main_win_obj.checkbutton.get_active()
|
other_flag = self.app_obj.main_win_obj.checkbutton.get_active()
|
||||||
|
@ -4707,7 +4707,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
spinbutton (Gtk.SpinButton): The widget clicked
|
spinbutton (Gtk.SpinButton): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.app_obj.main_win_obj.spinbutton.set_value(spinbutton.get_value())
|
self.app_obj.main_win_obj.spinbutton.set_value(spinbutton.get_value())
|
||||||
|
@ -4723,10 +4723,10 @@ class SystemPrefWin(GenericPrefWin):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
combo (Gtk.ComboBox): The widget clicked
|
combo (Gtk.ComboBox): The widget clicked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tree_iter = combo.get_active_iter()
|
tree_iter = combo.get_active_iter()
|
||||||
model = combo.get_model()
|
model = combo.get_model()
|
||||||
self.app_obj.set_ytdl_path(model[tree_iter][1])
|
self.app_obj.set_ytdl_path(model[tree_iter][1])
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,8 @@ FILESIZE_METRIC_LIST = [
|
||||||
# Main stages of the download operation
|
# Main stages of the download operation
|
||||||
MAIN_STAGE_QUEUED = 'Queued'
|
MAIN_STAGE_QUEUED = 'Queued'
|
||||||
MAIN_STAGE_ACTIVE = 'Active'
|
MAIN_STAGE_ACTIVE = 'Active'
|
||||||
MAIN_STAGE_PAUSED = 'Paused'
|
MAIN_STAGE_PAUSED = 'Paused' # (not actually used)
|
||||||
MAIN_STAGE_COMPLETED = 'Completed'
|
MAIN_STAGE_COMPLETED = 'Completed' # (not actually used)
|
||||||
MAIN_STAGE_ERROR = 'Error'
|
MAIN_STAGE_ERROR = 'Error'
|
||||||
# Sub-stages of the 'Active' stage
|
# Sub-stages of the 'Active' stage
|
||||||
ACTIVE_STAGE_PRE_PROCESS = 'Pre-processing'
|
ACTIVE_STAGE_PRE_PROCESS = 'Pre-processing'
|
||||||
|
|
482
lib/downloads.py
482
lib/downloads.py
|
@ -31,7 +31,7 @@ import datetime
|
||||||
import json
|
import json
|
||||||
import signal
|
import signal
|
||||||
import os
|
import os
|
||||||
import Queue
|
import queue
|
||||||
import requests
|
import requests
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
@ -40,16 +40,11 @@ import time
|
||||||
|
|
||||||
|
|
||||||
# Import our modules
|
# Import our modules
|
||||||
from constants import MAIN_STAGE_QUEUED, MAIN_STAGE_ACTIVE, \
|
from . import constants
|
||||||
MAIN_STAGE_PAUSED, MAIN_STAGE_COMPLETED, MAIN_STAGE_ERROR, \
|
from . import mainapp
|
||||||
ACTIVE_STAGE_PRE_PROCESS, ACTIVE_STAGE_DOWNLOAD, ACTIVE_STAGE_POST_PROCESS, \
|
from . import media
|
||||||
ACTIVE_STAGE_CHECKING, COMPLETED_STAGE_FINISHED, COMPLETED_STAGE_WARNING, \
|
from . import options
|
||||||
COMPLETED_STAGE_ALREADY, ERROR_STAGE_ERROR, ERROR_STAGE_STOPPED, \
|
from . import utils
|
||||||
ERROR_STAGE_ABORT
|
|
||||||
import mainapp
|
|
||||||
import media
|
|
||||||
import options
|
|
||||||
import utils
|
|
||||||
|
|
||||||
|
|
||||||
# Decorator to add thread synchronisation to some functions in the
|
# Decorator to add thread synchronisation to some functions in the
|
||||||
|
@ -184,8 +179,8 @@ class DownloadManager(threading.Thread):
|
||||||
download_item_obj = self.download_list_obj.fetch_next_item()
|
download_item_obj = self.download_list_obj.fetch_next_item()
|
||||||
|
|
||||||
# Exit this loop when there are no more downloads.DownloadItem
|
# Exit this loop when there are no more downloads.DownloadItem
|
||||||
# objects whose .status is MAIN_STAGE_QUEUED, and when all
|
# objects whose .status is constants.MAIN_STAGE_QUEUED, and when
|
||||||
# workers have finished their downloads
|
# all workers have finished their downloads
|
||||||
# Otherwise, wait for an available downloads.DownloadWorker, and
|
# Otherwise, wait for an available downloads.DownloadWorker, and
|
||||||
# then assign the next downloads.DownloadItem to it
|
# then assign the next downloads.DownloadItem to it
|
||||||
if not download_item_obj:
|
if not download_item_obj:
|
||||||
|
@ -210,7 +205,7 @@ class DownloadManager(threading.Thread):
|
||||||
# downloads.DownloadItem
|
# downloads.DownloadItem
|
||||||
self.download_list_obj.change_item_stage(
|
self.download_list_obj.change_item_stage(
|
||||||
download_item_obj.dbid,
|
download_item_obj.dbid,
|
||||||
MAIN_STAGE_ACTIVE,
|
constants.MAIN_STAGE_ACTIVE,
|
||||||
)
|
)
|
||||||
# Update the main window's progress bar
|
# Update the main window's progress bar
|
||||||
self.job_count += 1
|
self.job_count += 1
|
||||||
|
@ -324,7 +319,7 @@ class DownloadManager(threading.Thread):
|
||||||
|
|
||||||
True if all downloads.DownloadWorker objects have finished their
|
True if all downloads.DownloadWorker objects have finished their
|
||||||
jobs, otherwise returns False
|
jobs, otherwise returns False
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for worker_obj in self.worker_list:
|
for worker_obj in self.worker_list:
|
||||||
|
@ -344,7 +339,7 @@ class DownloadManager(threading.Thread):
|
||||||
|
|
||||||
The first available downloads.DownloadWorker, or None if there are
|
The first available downloads.DownloadWorker, or None if there are
|
||||||
no available workers.
|
no available workers.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for worker_obj in self.worker_list:
|
for worker_obj in self.worker_list:
|
||||||
|
@ -557,7 +552,7 @@ class DownloadWorker(threading.Thread):
|
||||||
download_item_obj (downloads.DownloadItem): The download item
|
download_item_obj (downloads.DownloadItem): The download item
|
||||||
object describing the URL from which youtube-dl should download
|
object describing the URL from which youtube-dl should download
|
||||||
video(s).
|
video(s).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.download_item_obj = download_item_obj
|
self.download_item_obj = download_item_obj
|
||||||
|
@ -712,7 +707,7 @@ class DownloadList(object):
|
||||||
dbid (int): The specified item's .dbid
|
dbid (int): The specified item's .dbid
|
||||||
|
|
||||||
new_stage: The new download stage, one of the values imported from
|
new_stage: The new download stage, one of the values imported from
|
||||||
constants.py (e.g. MAIN_STAGE_QUEUED)
|
constants.py (e.g. constants.MAIN_STAGE_QUEUED)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -810,14 +805,14 @@ class DownloadList(object):
|
||||||
|
|
||||||
The next downloads.DownloadItem object, or None if there are none
|
The next downloads.DownloadItem object, or None if there are none
|
||||||
left.
|
left.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for dbid in self.download_item_list:
|
for dbid in self.download_item_list:
|
||||||
this_item = self.download_item_dict[dbid]
|
this_item = self.download_item_dict[dbid]
|
||||||
|
|
||||||
# Don't return an item that's marked as MAIN_STAGE_ACTIVE
|
# Don't return an item that's marked as constants.MAIN_STAGE_ACTIVE
|
||||||
if this_item.stage == MAIN_STAGE_QUEUED:
|
if this_item.stage == constants.MAIN_STAGE_QUEUED:
|
||||||
return this_item
|
return this_item
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
@ -895,7 +890,7 @@ class DownloadItem(object):
|
||||||
# A unique ID for this object
|
# A unique ID for this object
|
||||||
self.dbid = dbid
|
self.dbid = dbid
|
||||||
# The current download stage
|
# The current download stage
|
||||||
self.stage = MAIN_STAGE_QUEUED
|
self.stage = constants.MAIN_STAGE_QUEUED
|
||||||
|
|
||||||
|
|
||||||
class VideoDownloader(object):
|
class VideoDownloader(object):
|
||||||
|
@ -984,8 +979,8 @@ class VideoDownloader(object):
|
||||||
# This object reads from the child process STDOUT and STDERR in an
|
# This object reads from the child process STDOUT and STDERR in an
|
||||||
# asynchronous way
|
# asynchronous way
|
||||||
# Standard Python synchronised queue classes
|
# Standard Python synchronised queue classes
|
||||||
self.stdout_queue = Queue.Queue()
|
self.stdout_queue = queue.Queue()
|
||||||
self.stderr_queue = Queue.Queue()
|
self.stderr_queue = queue.Queue()
|
||||||
# The downloads.PipeReader objects created to handle reading from the
|
# The downloads.PipeReader objects created to handle reading from the
|
||||||
# pipes
|
# pipes
|
||||||
self.stdout_reader = PipeReader(self.stdout_queue)
|
self.stdout_reader = PipeReader(self.stdout_queue)
|
||||||
|
@ -1064,7 +1059,7 @@ class VideoDownloader(object):
|
||||||
# Public class methods
|
# Public class methods
|
||||||
|
|
||||||
|
|
||||||
def do_download(self):
|
def OLDdo_download(self):
|
||||||
|
|
||||||
"""Called by downloads.DownloadWorker.run().
|
"""Called by downloads.DownloadWorker.run().
|
||||||
|
|
||||||
|
@ -1123,8 +1118,9 @@ class VideoDownloader(object):
|
||||||
# standard format, specified in the comments for
|
# standard format, specified in the comments for
|
||||||
# self.extract_stdout_data()
|
# self.extract_stdout_data()
|
||||||
dl_stat_dict = self.extract_stdout_data(stdout)
|
dl_stat_dict = self.extract_stdout_data(stdout)
|
||||||
# If the job's status is COMPLETED_STAGE_ALREADY or
|
# If the job's status is constants.COMPLETED_STAGE_ALREADY
|
||||||
# ERROR_STAGE_ABORT, set our self.return_code IV
|
# or constants.ERROR_STAGE_ABORT, set our
|
||||||
|
# self.return_code IV
|
||||||
self.extract_stdout_status(dl_stat_dict)
|
self.extract_stdout_status(dl_stat_dict)
|
||||||
# Pass the dictionary on to self.download_worker_obj so the
|
# Pass the dictionary on to self.download_worker_obj so the
|
||||||
# main window can be updated
|
# main window can be updated
|
||||||
|
@ -1183,6 +1179,130 @@ class VideoDownloader(object):
|
||||||
# Pass the result back to the parent downloads.DownloadWorker object
|
# Pass the result back to the parent downloads.DownloadWorker object
|
||||||
return self.return_code
|
return self.return_code
|
||||||
|
|
||||||
|
def do_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():
|
||||||
|
|
||||||
|
# # (Convert Python2 to Python3)
|
||||||
|
# stdout = self.stdout_queue.get_nowait().rstrip()
|
||||||
|
# stdout = utils.convert_item(stdout, to_unicode=True)
|
||||||
|
stdout = self.stdout_queue.get_nowait().rstrip().decode('utf-8')
|
||||||
|
|
||||||
|
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
|
||||||
|
# # (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)
|
||||||
|
|
||||||
|
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 close(self):
|
def close(self):
|
||||||
|
|
||||||
|
@ -1315,7 +1435,7 @@ class VideoDownloader(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def confirm_sim_video(self, json_dict):
|
def OLDconfirm_sim_video(self, json_dict):
|
||||||
|
|
||||||
"""Called by self.extract_stdout_data().
|
"""Called by self.extract_stdout_data().
|
||||||
|
|
||||||
|
@ -1515,6 +1635,208 @@ class VideoDownloader(object):
|
||||||
# Update the main window
|
# Update the main window
|
||||||
app_obj.announce_video_download(self.download_item_obj, video_obj)
|
app_obj.announce_video_download(self.download_item_obj, video_obj)
|
||||||
|
|
||||||
|
def confirm_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):
|
||||||
|
# # Convert Python2 to Python3
|
||||||
|
# fh = open(descrip_path, 'w')
|
||||||
|
fh = open(descrip_path, 'wb')
|
||||||
|
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 create_child_process(self, cmd_list):
|
def create_child_process(self, cmd_list):
|
||||||
|
|
||||||
|
@ -1659,8 +1981,8 @@ class VideoDownloader(object):
|
||||||
# Extract the data
|
# Extract the data
|
||||||
stdout_list[0] = stdout_list[0].lstrip('\r')
|
stdout_list[0] = stdout_list[0].lstrip('\r')
|
||||||
if stdout_list[0] == '[download]':
|
if stdout_list[0] == '[download]':
|
||||||
|
|
||||||
dl_stat_dict['status'] = ACTIVE_STAGE_DOWNLOAD
|
dl_stat_dict['status'] = constants.ACTIVE_STAGE_DOWNLOAD
|
||||||
|
|
||||||
# Get path, filename and extension
|
# Get path, filename and extension
|
||||||
if stdout_list[1] == 'Destination:':
|
if stdout_list[1] == 'Destination:':
|
||||||
|
@ -1705,7 +2027,7 @@ class VideoDownloader(object):
|
||||||
|
|
||||||
# Get file already downloaded status
|
# Get file already downloaded status
|
||||||
if stdout_list[-1] == 'downloaded':
|
if stdout_list[-1] == 'downloaded':
|
||||||
dl_stat_dict['status'] = COMPLETED_STAGE_ALREADY
|
dl_stat_dict['status'] = constants.COMPLETED_STAGE_ALREADY
|
||||||
path, filename, extension = self.extract_filename(
|
path, filename, extension = self.extract_filename(
|
||||||
' '.join(stdout_with_spaces_list[1:-4]),
|
' '.join(stdout_with_spaces_list[1:-4]),
|
||||||
)
|
)
|
||||||
|
@ -1718,14 +2040,14 @@ class VideoDownloader(object):
|
||||||
|
|
||||||
# Get filesize abort status
|
# Get filesize abort status
|
||||||
if stdout_list[-1] == 'Aborting.':
|
if stdout_list[-1] == 'Aborting.':
|
||||||
dl_stat_dict['status'] = ERROR_STAGE_ABORT
|
dl_stat_dict['status'] = constants.ERROR_STAGE_ABORT
|
||||||
|
|
||||||
elif stdout_list[0] == '[hlsnative]':
|
elif stdout_list[0] == '[hlsnative]':
|
||||||
|
|
||||||
# Get information from the native HLS extractor (see
|
# Get information from the native HLS extractor (see
|
||||||
# https://github.com/rg3/youtube-dl/blob/master/youtube_dl/
|
# https://github.com/rg3/youtube-dl/blob/master/youtube_dl/
|
||||||
# downloader/hls.py#L54
|
# downloader/hls.py#L54
|
||||||
dl_stat_dict['status'] = ACTIVE_STAGE_DOWNLOAD
|
dl_stat_dict['status'] = constants.ACTIVE_STAGE_DOWNLOAD
|
||||||
|
|
||||||
if len(stdout_list) == 7:
|
if len(stdout_list) == 7:
|
||||||
segment_no = float(stdout_list[6])
|
segment_no = float(stdout_list[6])
|
||||||
|
@ -1736,12 +2058,12 @@ class VideoDownloader(object):
|
||||||
dl_stat_dict['percent'] = percent
|
dl_stat_dict['percent'] = percent
|
||||||
|
|
||||||
elif stdout_list[0] == '[ffmpeg]':
|
elif stdout_list[0] == '[ffmpeg]':
|
||||||
|
|
||||||
# Using FFmpeg, not the the native HLS extractor
|
# Using FFmpeg, not the the native HLS extractor
|
||||||
# A successful video download is announced in one of several ways.
|
# A successful video download is announced in one of several ways.
|
||||||
# Use the first announcement to update self.video_check_dict, and
|
# Use the first announcement to update self.video_check_dict, and
|
||||||
# ignore subsequent announcements
|
# ignore subsequent announcements
|
||||||
dl_stat_dict['status'] = ACTIVE_STAGE_POST_PROCESS
|
dl_stat_dict['status'] = constants.ACTIVE_STAGE_POST_PROCESS
|
||||||
|
|
||||||
# Get the final file extension after the merging process has
|
# Get the final file extension after the merging process has
|
||||||
# completed
|
# completed
|
||||||
|
@ -1782,7 +2104,7 @@ class VideoDownloader(object):
|
||||||
self.confirm_new_video(path, filename, extension)
|
self.confirm_new_video(path, filename, extension)
|
||||||
|
|
||||||
elif stdout_list[0][0] == '{':
|
elif stdout_list[0][0] == '{':
|
||||||
|
|
||||||
# JSON data, the result of a simulated download. Convert to a
|
# JSON data, the result of a simulated download. Convert to a
|
||||||
# python dictionary
|
# python dictionary
|
||||||
if self.dl_sim_flag:
|
if self.dl_sim_flag:
|
||||||
|
@ -1805,17 +2127,17 @@ class VideoDownloader(object):
|
||||||
self.video_total += 1
|
self.video_total += 1
|
||||||
dl_stat_dict['playlist_size'] = self.video_total
|
dl_stat_dict['playlist_size'] = self.video_total
|
||||||
|
|
||||||
dl_stat_dict['status'] = ACTIVE_STAGE_CHECKING
|
dl_stat_dict['status'] = constants.ACTIVE_STAGE_CHECKING
|
||||||
|
|
||||||
elif stdout_list[0][0] != '[' or stdout_list[0] == '[debug]':
|
elif stdout_list[0][0] != '[' or stdout_list[0] == '[debug]':
|
||||||
|
|
||||||
# (Just ignore this output)
|
# (Just ignore this output)
|
||||||
return dl_stat_dict
|
return dl_stat_dict
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# The download has started
|
# The download has started
|
||||||
dl_stat_dict['status'] = ACTIVE_STAGE_PRE_PROCESS
|
dl_stat_dict['status'] = constants.ACTIVE_STAGE_PRE_PROCESS
|
||||||
|
|
||||||
return dl_stat_dict
|
return dl_stat_dict
|
||||||
|
|
||||||
|
@ -1827,12 +2149,13 @@ class VideoDownloader(object):
|
||||||
|
|
||||||
Based on YoutubeDLDownloader._extract_info().
|
Based on YoutubeDLDownloader._extract_info().
|
||||||
|
|
||||||
If the job's status is COMPLETED_STAGE_ALREADY or ERROR_STAGE_ABORT,
|
If the job's status is constants.COMPLETED_STAGE_ALREADY or
|
||||||
translate that into a new value for the return code, and then use that
|
constants.ERROR_STAGE_ABORT, translate that into a new value for the
|
||||||
value to actually set self.return_code (which halts the download).
|
return code, and then use that value to actually set self.return_code
|
||||||
|
(which halts the download).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
dl_stat_dict (dict): The Python dictionary returned by the call to
|
dl_stat_dict (dict): The Python dictionary returned by the call to
|
||||||
self.extract_stdout_data(), in the standard form described by
|
self.extract_stdout_data(), in the standard form described by
|
||||||
the comments for that function
|
the comments for that function
|
||||||
|
@ -1840,11 +2163,11 @@ class VideoDownloader(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if 'status' in dl_stat_dict:
|
if 'status' in dl_stat_dict:
|
||||||
if dl_stat_dict['status'] == COMPLETED_STAGE_ALREADY:
|
if dl_stat_dict['status'] == constants.COMPLETED_STAGE_ALREADY:
|
||||||
self.set_return_code(self.ALREADY)
|
self.set_return_code(self.ALREADY)
|
||||||
dl_stat_dict['status'] = None
|
dl_stat_dict['status'] = None
|
||||||
|
|
||||||
if dl_stat_dict['status'] == ERROR_STAGE_ABORT:
|
if dl_stat_dict['status'] == constants.ERROR_STAGE_ABORT:
|
||||||
self.set_return_code(self.FILESIZE_ABORT)
|
self.set_return_code(self.FILESIZE_ABORT)
|
||||||
dl_stat_dict['status'] = None
|
dl_stat_dict['status'] = None
|
||||||
|
|
||||||
|
@ -1859,7 +2182,7 @@ class VideoDownloader(object):
|
||||||
youtube-dl.
|
youtube-dl.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
Python list that contains the system command to execute.
|
Python list that contains the system command to execute.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -1913,7 +2236,7 @@ class VideoDownloader(object):
|
||||||
checks the STERR message to see if it's an error or just a warning.
|
checks the STERR message to see if it's an error or just a warning.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
stderr (string): A message from the child process STDERR.
|
stderr (string): A message from the child process STDERR.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -1944,23 +2267,23 @@ class VideoDownloader(object):
|
||||||
dl_stat_dict = {}
|
dl_stat_dict = {}
|
||||||
|
|
||||||
if self.return_code == self.OK:
|
if self.return_code == self.OK:
|
||||||
dl_stat_dict['status'] = COMPLETED_STAGE_FINISHED
|
dl_stat_dict['status'] = constants.COMPLETED_STAGE_FINISHED
|
||||||
elif self.return_code == self.ERROR:
|
elif self.return_code == self.ERROR:
|
||||||
dl_stat_dict['status'] = MAIN_STAGE_ERROR
|
dl_stat_dict['status'] = constants.MAIN_STAGE_ERROR
|
||||||
dl_stat_dict['eta'] = ''
|
dl_stat_dict['eta'] = ''
|
||||||
dl_stat_dict['speed'] = ''
|
dl_stat_dict['speed'] = ''
|
||||||
elif self.return_code == self.WARNING:
|
elif self.return_code == self.WARNING:
|
||||||
dl_stat_dict['status'] = COMPLETED_STAGE_WARNING
|
dl_stat_dict['status'] = constants.COMPLETED_STAGE_WARNING
|
||||||
dl_stat_dict['eta'] = ''
|
dl_stat_dict['eta'] = ''
|
||||||
dl_stat_dict['speed'] = ''
|
dl_stat_dict['speed'] = ''
|
||||||
elif self.return_code == self.STOPPED:
|
elif self.return_code == self.STOPPED:
|
||||||
dl_stat_dict['status'] = ERROR_STAGE_STOPPED
|
dl_stat_dict['status'] = constants.ERROR_STAGE_STOPPED
|
||||||
dl_stat_dict['eta'] = ''
|
dl_stat_dict['eta'] = ''
|
||||||
dl_stat_dict['speed'] = ''
|
dl_stat_dict['speed'] = ''
|
||||||
elif self.return_code == self.ALREADY:
|
elif self.return_code == self.ALREADY:
|
||||||
dl_stat_dict['status'] = COMPLETED_STAGE_ALREADY
|
dl_stat_dict['status'] = constants.COMPLETED_STAGE_ALREADY
|
||||||
else:
|
else:
|
||||||
dl_stat_dict['status'] = ERROR_STAGE_ABORT
|
dl_stat_dict['status'] = constants.ERROR_STAGE_ABORT
|
||||||
|
|
||||||
# Use some empty values in dl_stat_dict so that the Progress Tab
|
# Use some empty values in dl_stat_dict so that the Progress Tab
|
||||||
# doesn't show arbitrary data from the last file downloaded
|
# doesn't show arbitrary data from the last file downloaded
|
||||||
|
@ -1985,7 +2308,7 @@ class VideoDownloader(object):
|
||||||
is higher in the hierarchy of return codes than the current value.
|
is higher in the hierarchy of return codes than the current value.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
code (int): A return code in the range 0-5
|
code (int): A return code in the range 0-5
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -2033,7 +2356,7 @@ class PipeReader(threading.Thread):
|
||||||
process pipes in an asynchronous way.
|
process pipes in an asynchronous way.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
queue (Queue.Queue): Python queue to store the output of the child
|
queue (queue.Queue): Python queue to store the output of the child
|
||||||
process.
|
process.
|
||||||
|
|
||||||
Warnings:
|
Warnings:
|
||||||
|
@ -2075,7 +2398,7 @@ class PipeReader(threading.Thread):
|
||||||
# Public class methods
|
# Public class methods
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def OLDOLDrun(self):
|
||||||
|
|
||||||
"""Called as a result of self.__init__().
|
"""Called as a result of self.__init__().
|
||||||
|
|
||||||
|
@ -2103,6 +2426,42 @@ class PipeReader(threading.Thread):
|
||||||
|
|
||||||
time.sleep(self.sleep_time)
|
time.sleep(self.sleep_time)
|
||||||
|
|
||||||
|
def run(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:
|
||||||
|
# # Convert Python2 to Python3 - the for loop no longer
|
||||||
|
# # terminates, but produces endless <b''> strings instead
|
||||||
|
# for line in iter(self.file_descriptor.readline, str('')):
|
||||||
|
for line in iter(self.file_descriptor.readline, str('')):
|
||||||
|
|
||||||
|
if line == b'':
|
||||||
|
break
|
||||||
|
|
||||||
|
# # Convert Python2 to Python3
|
||||||
|
# if str('ffmpeg version') in line:
|
||||||
|
if str.encode('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 attach_file_descriptor(self, filedesc):
|
def attach_file_descriptor(self, filedesc):
|
||||||
|
|
||||||
|
@ -2113,9 +2472,9 @@ class PipeReader(threading.Thread):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
filedesc (filehandle): The open filehandle for STDOUT or STDERR
|
filedesc (filehandle): The open filehandle for STDOUT or STDERR
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.file_descriptor = filedesc
|
self.file_descriptor = filedesc
|
||||||
|
|
||||||
|
|
||||||
|
@ -2129,10 +2488,9 @@ class PipeReader(threading.Thread):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
timeout (-): No calling code sets a timeout
|
timeout (-): No calling code sets a timeout
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.running_flag = False
|
self.running_flag = False
|
||||||
super(PipeReader, self).join(timeout)
|
super(PipeReader, self).join(timeout)
|
||||||
|
|
||||||
|
|
||||||
|
|
14
lib/files.py
14
lib/files.py
|
@ -57,7 +57,7 @@ class FileManager(threading.Thread):
|
||||||
|
|
||||||
# Public class methods
|
# Public class methods
|
||||||
|
|
||||||
|
|
||||||
def load_json(self, full_path):
|
def load_json(self, full_path):
|
||||||
|
|
||||||
"""Can be called by anything.
|
"""Can be called by anything.
|
||||||
|
@ -66,7 +66,7 @@ class FileManager(threading.Thread):
|
||||||
dictionary and returns the dictionary.
|
dictionary and returns the dictionary.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
full_path (string): The full path to the JSON file
|
full_path (string): The full path to the JSON file
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -95,20 +95,20 @@ class FileManager(threading.Thread):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
full_path (string): The full path to the text file
|
full_path (string): The full path to the text file
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The contents of the text file as a string, or or None if the file
|
The contents of the text file as a string, or or None if the file
|
||||||
is missing or can't be loaded
|
is missing or can't be loaded
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not os.path.isfile(full_path):
|
if not os.path.isfile(full_path):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
with open(full_path, 'r') as text_file:
|
with open(full_path, 'r') as text_file:
|
||||||
text = text_file.read()
|
text = text_file.read()
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ class FileManager(threading.Thread):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A GdkPixbuf, or None if the file is missing or can't be loaded
|
A GdkPixbuf, or None if the file is missing or can't be loaded
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not os.path.isfile(full_path):
|
if not os.path.isfile(full_path):
|
||||||
|
|
164
lib/mainapp.py
164
lib/mainapp.py
|
@ -51,17 +51,17 @@ except:
|
||||||
|
|
||||||
|
|
||||||
# Import our modules
|
# Import our modules
|
||||||
import config
|
|
||||||
import downloads
|
|
||||||
import files
|
|
||||||
import __main__
|
import __main__
|
||||||
import mainwin
|
from . import config
|
||||||
import media
|
from . import downloads
|
||||||
import options
|
from . import files
|
||||||
import refresh
|
from . import mainwin
|
||||||
import testing
|
from . import media
|
||||||
import updates
|
from . import options
|
||||||
import utils
|
from . import refresh
|
||||||
|
from . import testing
|
||||||
|
from . import updates
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
|
@ -415,30 +415,34 @@ class TartubeApp(Gtk.Application):
|
||||||
# Debugging flags (can only be set by editing the source code)
|
# Debugging flags (can only be set by editing the source code)
|
||||||
# Delete the config file and the contents of Tartube's data directory
|
# Delete the config file and the contents of Tartube's data directory
|
||||||
# on startup
|
# on startup
|
||||||
self.debug_delete_data_flag = False
|
# self.debug_delete_data_flag = False
|
||||||
|
self.debug_delete_data_flag = True
|
||||||
# In the main window's menu, show a menu item for adding a set of
|
# In the main window's menu, show a menu item for adding a set of
|
||||||
# media data objects for testing
|
# media data objects for testing
|
||||||
self.debug_test_media_menu_flag = False
|
# self.debug_test_media_menu_flag = False
|
||||||
|
self.debug_test_media_menu_flag = True
|
||||||
# In the main window's toolbar, show a toolbar item for adding a set of
|
# In the main window's toolbar, show a toolbar item for adding a set of
|
||||||
# media data objects for testing
|
# media data objects for testing
|
||||||
self.debug_test_media_toolbar_flag = False
|
self.debug_test_media_toolbar_flag = False
|
||||||
# Show an dialogue window with 'Tartube is already running!' if the
|
# Show an dialogue window with 'Tartube is already running!' if the
|
||||||
# user tries to open a second instance of Tartube
|
# user tries to open a second instance of Tartube
|
||||||
self.debug_warn_multiple_flag = False
|
# self.debug_warn_multiple_flag = False
|
||||||
|
self.debug_warn_multiple_flag = True
|
||||||
# Open the main window in the top-left corner of the desktop
|
# Open the main window in the top-left corner of the desktop
|
||||||
self.debug_open_top_left_flag = False
|
# self.debug_open_top_left_flag = False
|
||||||
|
self.debug_open_top_left_flag = True
|
||||||
# Automatically open the system preferences window on startup
|
# Automatically open the system preferences window on startup
|
||||||
self.debug_open_pref_win_flag = False
|
self.debug_open_pref_win_flag = False
|
||||||
# For Tartube developers who don't want to manually change
|
# For Tartube developers who don't want to manually change
|
||||||
# self.ytdl_path and self.ytdl_update_current on every startup
|
# self.ytdl_path and self.ytdl_update_current on every startup
|
||||||
# (assuming that self.debug_delete_data_flag is True), modify those
|
# (assuming that self.debug_delete_data_flag is True), modify those
|
||||||
# IVs
|
# IVs
|
||||||
self.debug_modify_ytdl_flag = False
|
# self.debug_modify_ytdl_flag = False
|
||||||
self.debug_ytdl_path = None
|
# self.debug_ytdl_path = None
|
||||||
self.debug_ytdl_update_current = None
|
# self.debug_ytdl_update_current = None
|
||||||
# self.debug_modify_ytdl_flag = True
|
self.debug_modify_ytdl_flag = True
|
||||||
# self.debug_ytdl_path = 'youtube-dl'
|
self.debug_ytdl_path = 'youtube-dl'
|
||||||
# self.debug_ytdl_update_current = 'Update using pip'
|
self.debug_ytdl_update_current = 'Update using pip'
|
||||||
|
|
||||||
|
|
||||||
def do_startup(self):
|
def do_startup(self):
|
||||||
|
@ -694,7 +698,7 @@ class TartubeApp(Gtk.Application):
|
||||||
self.show_msg_dialogue(
|
self.show_msg_dialogue(
|
||||||
utils.upper_case_first(__main__.__packagename__) \
|
utils.upper_case_first(__main__.__packagename__) \
|
||||||
+ ' is already running!',
|
+ ' is already running!',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'warning',
|
'warning',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -870,7 +874,7 @@ class TartubeApp(Gtk.Application):
|
||||||
'There is ' + string + ' operation in progress.\n' \
|
'There is ' + string + ' operation in progress.\n' \
|
||||||
+ 'Are you sure you want to quit ' \
|
+ 'Are you sure you want to quit ' \
|
||||||
+ utils.upper_case_first(__main__.__packagename__) + '?',
|
+ utils.upper_case_first(__main__.__packagename__) + '?',
|
||||||
True, # Modal
|
True, # Modal
|
||||||
'question',
|
'question',
|
||||||
'yes-no',
|
'yes-no',
|
||||||
)
|
)
|
||||||
|
@ -925,7 +929,7 @@ class TartubeApp(Gtk.Application):
|
||||||
200-299: mainwin.py (in use: 201-234)
|
200-299: mainwin.py (in use: 201-234)
|
||||||
300-399: downloads.py (in use: 301-303)
|
300-399: downloads.py (in use: 301-303)
|
||||||
400-499: config.py (in use: 401-404)
|
400-499: config.py (in use: 401-404)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.main_win_obj:
|
if self.main_win_obj:
|
||||||
|
@ -1445,13 +1449,13 @@ class TartubeApp(Gtk.Application):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
msg (string): The message to display
|
msg (string): The message to display
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.main_win_obj:
|
if self.main_win_obj:
|
||||||
self.show_msg_dialogue(
|
self.show_msg_dialogue(
|
||||||
msg,
|
msg,
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -1565,7 +1569,7 @@ class TartubeApp(Gtk.Application):
|
||||||
return self.show_msg_dialogue(
|
return self.show_msg_dialogue(
|
||||||
'A download operation cannot start\nif one or more' \
|
'A download operation cannot start\nif one or more' \
|
||||||
+ ' configuration\nwindows are still open',
|
+ ' configuration\nwindows are still open',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -1601,7 +1605,7 @@ class TartubeApp(Gtk.Application):
|
||||||
|
|
||||||
return self.show_msg_dialogue(
|
return self.show_msg_dialogue(
|
||||||
msg,
|
msg,
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -1736,7 +1740,7 @@ class TartubeApp(Gtk.Application):
|
||||||
return self.show_msg_dialogue(
|
return self.show_msg_dialogue(
|
||||||
'An update operation cannot start\nif one or more' \
|
'An update operation cannot start\nif one or more' \
|
||||||
+ ' configuration\nwindows are still open',
|
+ ' configuration\nwindows are still open',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -1763,7 +1767,7 @@ class TartubeApp(Gtk.Application):
|
||||||
|
|
||||||
success_flag (True or False): True if the update operation
|
success_flag (True or False): True if the update operation
|
||||||
succeeded, False if not
|
succeeded, False if not
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Any code can check whether a download/update/refresh operation is in
|
# Any code can check whether a download/update/refresh operation is in
|
||||||
|
@ -1792,7 +1796,7 @@ class TartubeApp(Gtk.Application):
|
||||||
|
|
||||||
self.show_msg_dialogue(
|
self.show_msg_dialogue(
|
||||||
msg,
|
msg,
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'info',
|
'info',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -1854,7 +1858,7 @@ class TartubeApp(Gtk.Application):
|
||||||
return self.show_msg_dialogue(
|
return self.show_msg_dialogue(
|
||||||
'A refresh operation cannot start\nif one or more' \
|
'A refresh operation cannot start\nif one or more' \
|
||||||
+ ' configuration\nwindows are still open',
|
+ ' configuration\nwindows are still open',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -1904,7 +1908,7 @@ class TartubeApp(Gtk.Application):
|
||||||
|
|
||||||
self.show_msg_dialogue(
|
self.show_msg_dialogue(
|
||||||
msg,
|
msg,
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'info',
|
'info',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -2299,7 +2303,7 @@ class TartubeApp(Gtk.Application):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The new media.Channel object
|
The new media.Channel object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Channels can only be placed inside an unrestricted media.Folder
|
# Channels can only be placed inside an unrestricted media.Folder
|
||||||
|
@ -2546,7 +2550,7 @@ class TartubeApp(Gtk.Application):
|
||||||
+ 'to the top level of ' \
|
+ 'to the top level of ' \
|
||||||
+ utils.upper_case_first(__main__.__packagename__) \
|
+ utils.upper_case_first(__main__.__packagename__) \
|
||||||
+ '\'s data directory',
|
+ '\'s data directory',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'question',
|
'question',
|
||||||
'yes-no',
|
'yes-no',
|
||||||
)
|
)
|
||||||
|
@ -2606,7 +2610,7 @@ class TartubeApp(Gtk.Application):
|
||||||
return self.show_msg_dialogue(
|
return self.show_msg_dialogue(
|
||||||
'Channels, playlists and folders can\nonly be dragged into' \
|
'Channels, playlists and folders can\nonly be dragged into' \
|
||||||
+ ' a folder',
|
+ ' a folder',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -2616,7 +2620,7 @@ class TartubeApp(Gtk.Application):
|
||||||
return self.show_msg_dialogue(
|
return self.show_msg_dialogue(
|
||||||
'The fixed folder \'' + dest_obj.name \
|
'The fixed folder \'' + dest_obj.name \
|
||||||
+ '\'\ncannot be moved (but it can still\nbe hidden)',
|
+ '\'\ncannot be moved (but it can still\nbe hidden)',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -2626,7 +2630,7 @@ class TartubeApp(Gtk.Application):
|
||||||
return self.show_msg_dialogue(
|
return self.show_msg_dialogue(
|
||||||
'The folder \'' + dest_obj.name \
|
'The folder \'' + dest_obj.name \
|
||||||
+ '\'\ncan only contain videos',
|
+ '\'\ncan only contain videos',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -2655,7 +2659,7 @@ class TartubeApp(Gtk.Application):
|
||||||
+ 'This procedure will move all downloaded files\n' \
|
+ 'This procedure will move all downloaded files\n' \
|
||||||
+ 'to the new location' \
|
+ 'to the new location' \
|
||||||
+ temp_string,
|
+ temp_string,
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'question',
|
'question',
|
||||||
'yes-no',
|
'yes-no',
|
||||||
)
|
)
|
||||||
|
@ -2810,7 +2814,7 @@ class TartubeApp(Gtk.Application):
|
||||||
response2 = self.show_msg_dialogue(
|
response2 = self.show_msg_dialogue(
|
||||||
'Are you SURE you want to delete files?\nThis procedure' \
|
'Are you SURE you want to delete files?\nThis procedure' \
|
||||||
' cannot be reversed!',
|
' cannot be reversed!',
|
||||||
True, # Modal
|
True, # Modal
|
||||||
'question',
|
'question',
|
||||||
'yes-no',
|
'yes-no',
|
||||||
)
|
)
|
||||||
|
@ -2874,7 +2878,7 @@ class TartubeApp(Gtk.Application):
|
||||||
no_update_index_flag (True or False): False if the Video Index
|
no_update_index_flag (True or False): False if the Video Index
|
||||||
should not be updated, because the calling function wants to do
|
should not be updated, because the calling function wants to do
|
||||||
that itself.
|
that itself.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# (List of Video Index rows to update, at the end of this function)
|
# (List of Video Index rows to update, at the end of this function)
|
||||||
|
@ -2962,7 +2966,7 @@ class TartubeApp(Gtk.Application):
|
||||||
|
|
||||||
Marks a video object as downloaded (i.e. the video file exists on the
|
Marks a video object as downloaded (i.e. the video file exists on the
|
||||||
user's filesystem) or not downloaded.
|
user's filesystem) or not downloaded.
|
||||||
|
|
||||||
The video object's .dl_flag IV is updated.
|
The video object's .dl_flag IV is updated.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -2971,7 +2975,7 @@ class TartubeApp(Gtk.Application):
|
||||||
|
|
||||||
flag (True or False): True to mark the video as downloaded, False
|
flag (True or False): True to mark the video as downloaded, False
|
||||||
to mark it as not downloaded.
|
to mark it as not downloaded.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# (List of Video Index rows to update, at the end of this function)
|
# (List of Video Index rows to update, at the end of this function)
|
||||||
|
@ -3048,7 +3052,7 @@ class TartubeApp(Gtk.Application):
|
||||||
"""Can be called by anything.
|
"""Can be called by anything.
|
||||||
|
|
||||||
Marks a video object as favourite or not favourite.
|
Marks a video object as favourite or not favourite.
|
||||||
|
|
||||||
The video object's .fav_flag IV is updated.
|
The video object's .fav_flag IV is updated.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -3061,7 +3065,7 @@ class TartubeApp(Gtk.Application):
|
||||||
no_update_index_flag (True or False): False if the Video Index
|
no_update_index_flag (True or False): False if the Video Index
|
||||||
should not be updated, because the calling function wants to do
|
should not be updated, because the calling function wants to do
|
||||||
that itself.
|
that itself.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# (List of Video Index rows to update, at the end of this function)
|
# (List of Video Index rows to update, at the end of this function)
|
||||||
|
@ -3299,7 +3303,7 @@ class TartubeApp(Gtk.Application):
|
||||||
media_data_obj (media.Video, media.Channel, media.Playlist or
|
media_data_obj (media.Video, media.Channel, media.Playlist or
|
||||||
media.Folder): The media data object to which the download
|
media.Folder): The media data object to which the download
|
||||||
options are applied.
|
options are applied.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.current_manager_obj \
|
if self.current_manager_obj \
|
||||||
|
@ -3334,7 +3338,7 @@ class TartubeApp(Gtk.Application):
|
||||||
media_data_obj (media.Video, media.Channel, media.Playlist or
|
media_data_obj (media.Video, media.Channel, media.Playlist or
|
||||||
media.Folder): The media data object from which the download
|
media.Folder): The media data object from which the download
|
||||||
options are removed.
|
options are removed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.current_manager_obj or not media_data_obj.options_obj:
|
if self.current_manager_obj or not media_data_obj.options_obj:
|
||||||
|
@ -3378,7 +3382,7 @@ class TartubeApp(Gtk.Application):
|
||||||
'The video file is missing from ' \
|
'The video file is missing from ' \
|
||||||
+ utils.upper_case_first(__main__.__packagename__) \
|
+ utils.upper_case_first(__main__.__packagename__) \
|
||||||
+ '\'s\ndata directory (try downloading the\nvideo again!',
|
+ '\'s\ndata directory (try downloading the\nvideo again!',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -3466,9 +3470,9 @@ class TartubeApp(Gtk.Application):
|
||||||
|
|
||||||
|
|
||||||
# (Download operation timer)
|
# (Download operation timer)
|
||||||
|
|
||||||
|
|
||||||
|
def timer_callback(self):
|
||||||
def timer_callback(self):
|
|
||||||
|
|
||||||
"""Called by gobject timer created by self.download_manager_start().
|
"""Called by gobject timer created by self.download_manager_start().
|
||||||
|
|
||||||
|
@ -3483,7 +3487,7 @@ class TartubeApp(Gtk.Application):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
1 to keep the timer going, or None to halt it
|
1 to keep the timer going, or None to halt it
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.timer_check_time is None:
|
if self.timer_check_time is None:
|
||||||
|
@ -3502,16 +3506,16 @@ class TartubeApp(Gtk.Application):
|
||||||
# Not all downloaded files confirmed to exist yet, so return 1
|
# Not all downloaded files confirmed to exist yet, so return 1
|
||||||
# to keep the timer going a little longer
|
# to keep the timer going a little longer
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# The download operation has finished. The call to
|
# The download operation has finished. The call to
|
||||||
# self.download_manager_finished() destroys the timer
|
# self.download_manager_finished() destroys the timer
|
||||||
self.download_manager_finished()
|
self.download_manager_finished()
|
||||||
|
|
||||||
|
|
||||||
# (Menu item and toolbar button callbacks)
|
# (Menu item and toolbar button callbacks)
|
||||||
|
|
||||||
|
|
||||||
|
def on_button_stop_operation(self, action, par):
|
||||||
def on_button_stop_operation(self, action, par):
|
|
||||||
|
|
||||||
"""Called from a callback in self.do_startup().
|
"""Called from a callback in self.do_startup().
|
||||||
|
|
||||||
|
@ -3522,7 +3526,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.operation_halted_flag = True
|
self.operation_halted_flag = True
|
||||||
|
@ -3535,7 +3539,7 @@ class TartubeApp(Gtk.Application):
|
||||||
self.refresh_manager_obj.stop_refresh_operation()
|
self.refresh_manager_obj.stop_refresh_operation()
|
||||||
|
|
||||||
|
|
||||||
def on_button_switch_view(self, action, par):
|
def on_button_switch_view(self, action, par):
|
||||||
|
|
||||||
"""Called from a callback in self.do_startup().
|
"""Called from a callback in self.do_startup().
|
||||||
|
|
||||||
|
@ -3546,7 +3550,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.complex_catalogue_flag:
|
if not self.complex_catalogue_flag:
|
||||||
|
@ -3561,7 +3565,7 @@ class TartubeApp(Gtk.Application):
|
||||||
self.main_win_obj.video_index_current,
|
self.main_win_obj.video_index_current,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def on_menu_about(self, action, par):
|
def on_menu_about(self, action, par):
|
||||||
|
|
||||||
"""Called from a callback in self.do_startup().
|
"""Called from a callback in self.do_startup().
|
||||||
|
@ -3573,7 +3577,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dialogue_win = Gtk.AboutDialog()
|
dialogue_win = Gtk.AboutDialog()
|
||||||
|
@ -3610,7 +3614,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
action.destroy()
|
action.destroy()
|
||||||
|
@ -3654,7 +3658,7 @@ class TartubeApp(Gtk.Application):
|
||||||
|
|
||||||
self.show_msg_dialogue(
|
self.show_msg_dialogue(
|
||||||
'You must give the channel a name',
|
'You must give the channel a name',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -3667,7 +3671,7 @@ class TartubeApp(Gtk.Application):
|
||||||
):
|
):
|
||||||
self.show_msg_dialogue(
|
self.show_msg_dialogue(
|
||||||
'You must enter a valid URL',
|
'You must enter a valid URL',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -3712,7 +3716,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dialogue_win = mainwin.AddFolderDialogue(self.main_win_obj)
|
dialogue_win = mainwin.AddFolderDialogue(self.main_win_obj)
|
||||||
|
@ -3736,7 +3740,7 @@ class TartubeApp(Gtk.Application):
|
||||||
|
|
||||||
self.show_msg_dialogue(
|
self.show_msg_dialogue(
|
||||||
'You must give the folder a name',
|
'You must give the folder a name',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -3776,7 +3780,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dialogue_win = mainwin.AddPlaylistDialogue(self.main_win_obj)
|
dialogue_win = mainwin.AddPlaylistDialogue(self.main_win_obj)
|
||||||
|
@ -3802,7 +3806,7 @@ class TartubeApp(Gtk.Application):
|
||||||
|
|
||||||
self.show_msg_dialogue(
|
self.show_msg_dialogue(
|
||||||
'You must give the playlist a name',
|
'You must give the playlist a name',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -3815,7 +3819,7 @@ class TartubeApp(Gtk.Application):
|
||||||
):
|
):
|
||||||
self.show_msg_dialogue(
|
self.show_msg_dialogue(
|
||||||
'You must enter a valid URL',
|
'You must enter a valid URL',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -3860,7 +3864,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dialogue_win = mainwin.AddVideoDialogue(self.main_win_obj)
|
dialogue_win = mainwin.AddVideoDialogue(self.main_win_obj)
|
||||||
|
@ -3918,7 +3922,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.download_manager_start(True)
|
self.download_manager_start(True)
|
||||||
|
@ -3935,7 +3939,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.download_manager_start(False)
|
self.download_manager_start(False)
|
||||||
|
@ -3952,7 +3956,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config.OptionsEditWin(self, self.general_options_obj, None)
|
config.OptionsEditWin(self, self.general_options_obj, None)
|
||||||
|
@ -3969,7 +3973,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.refresh_manager_start()
|
self.refresh_manager_start()
|
||||||
|
@ -3986,7 +3990,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.save_db()
|
self.save_db()
|
||||||
|
@ -3997,7 +4001,7 @@ class TartubeApp(Gtk.Application):
|
||||||
|
|
||||||
self.show_msg_dialogue(
|
self.show_msg_dialogue(
|
||||||
'Database saved',
|
'Database saved',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'info',
|
'info',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -4014,7 +4018,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for name in self.media_name_dict:
|
for name in self.media_name_dict:
|
||||||
|
@ -4038,7 +4042,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config.SystemPrefWin(self)
|
config.SystemPrefWin(self)
|
||||||
|
@ -4056,7 +4060,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Add media data objects for testing: videos, channels, playlists and/
|
# Add media data objects for testing: videos, channels, playlists and/
|
||||||
|
@ -4090,7 +4094,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.update_manager_start()
|
self.update_manager_start()
|
||||||
|
@ -4107,7 +4111,7 @@ class TartubeApp(Gtk.Application):
|
||||||
action (Gio.SimpleAction): Object generated by Gio
|
action (Gio.SimpleAction): Object generated by Gio
|
||||||
|
|
||||||
par (None): Ignored
|
par (None): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.stop()
|
self.stop()
|
||||||
|
@ -4145,7 +4149,7 @@ class TartubeApp(Gtk.Application):
|
||||||
self.show_msg_dialogue(
|
self.show_msg_dialogue(
|
||||||
'There is already a ' + string + ' with that name\n' \
|
'There is already a ' + string + ' with that name\n' \
|
||||||
+ '(so please choose a different name)',
|
+ '(so please choose a different name)',
|
||||||
False, # Not modal
|
False, # Not modal
|
||||||
'error',
|
'error',
|
||||||
'ok',
|
'ok',
|
||||||
)
|
)
|
||||||
|
@ -4219,7 +4223,7 @@ class TartubeApp(Gtk.Application):
|
||||||
|
|
||||||
Applies or releases the simultaneous download limit. If a download
|
Applies or releases the simultaneous download limit. If a download
|
||||||
operation is in progress, the new setting is applied to the next
|
operation is in progress, the new setting is applied to the next
|
||||||
download job.
|
download job.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not flag:
|
if not flag:
|
||||||
|
|
235
lib/mainwin.py
235
lib/mainwin.py
|
@ -40,13 +40,14 @@ import time
|
||||||
|
|
||||||
|
|
||||||
# Import our modules
|
# Import our modules
|
||||||
|
from . import config
|
||||||
|
from . import constants
|
||||||
|
#from . import __main__
|
||||||
import __main__
|
import __main__
|
||||||
import config
|
from . import mainapp
|
||||||
import constants
|
from . import media
|
||||||
import mainapp
|
from . import options
|
||||||
import media
|
from . import utils
|
||||||
import options
|
|
||||||
import utils
|
|
||||||
|
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
|
@ -1255,7 +1256,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
Returns:
|
Returns:
|
||||||
-1 if row_iter1 comes before row_iter2, 1 if row_iter2 comes before
|
-1 if row_iter1 comes before row_iter2, 1 if row_iter2 comes before
|
||||||
row_iter1, 0 if their order should not be changed
|
row_iter1, 0 if their order should not be changed
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# If auto-sorting is disabled temporarily, we can prevent the list
|
# If auto-sorting is disabled temporarily, we can prevent the list
|
||||||
|
@ -1315,7 +1316,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def video_catalogue_auto_sort(self, row1, row2, data, notify):
|
def OLDvideo_catalogue_auto_sort(self, row1, row2, data, notify):
|
||||||
|
|
||||||
"""Sorting function created by self.videos_tab.
|
"""Sorting function created by self.videos_tab.
|
||||||
|
|
||||||
|
@ -1332,7 +1333,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
-1 if row1 comes before row2, 1 if row2 comes before row1, 0 if
|
-1 if row1 comes before row2, 1 if row2 comes before row1, 0 if
|
||||||
their order should not be changed
|
their order should not be changed
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -1359,6 +1360,64 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
else:
|
else:
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
def video_catalogue_auto_sort(self, row1, row2, data, notify):
|
||||||
|
|
||||||
|
"""Sorting function created by self.videos_tab.
|
||||||
|
|
||||||
|
Automatically sorts rows in the Video Catalogue.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
row1, row2 (mainwin.CatalogueRow): Two rows in the liststore, one
|
||||||
|
of which must be sorted before the other
|
||||||
|
|
||||||
|
data (None): Ignored
|
||||||
|
|
||||||
|
notify (False): Ignored
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
-1 if row1 comes before row2, 1 if row2 comes before row1, 0 if
|
||||||
|
their order should not be changed
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get the media.Video objects displayed on each row
|
||||||
|
obj1 = row1.video_obj
|
||||||
|
obj2 = row2.video_obj
|
||||||
|
|
||||||
|
# Sort videos by playlist index (if set), then by upload time, and then
|
||||||
|
# by receive (download) time
|
||||||
|
if obj1.index is not None and obj2.index is not None:
|
||||||
|
if obj1.index < obj2.index:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
# # Convert Python2 to Python3
|
||||||
|
# elif 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
|
||||||
|
elif obj1.upload_time is not None and obj2.upload_time is not None:
|
||||||
|
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:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
# (Video Index)
|
# (Video Index)
|
||||||
|
|
||||||
|
@ -1565,11 +1624,11 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
Also called by callbacks in mainapp.TartubeApp.on_menu_add_channel(),
|
Also called by callbacks in mainapp.TartubeApp.on_menu_add_channel(),
|
||||||
.cb on_menu_add_folder() and cb on_menu_add_playlist().
|
.cb on_menu_add_folder() and cb on_menu_add_playlist().
|
||||||
|
|
||||||
Adds a row to the Video Index.
|
Adds a row to the Video Index.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
media_data_obj (media.Video, media.Channel, media.Playlist,
|
media_data_obj (media.Video, media.Channel, media.Playlist,
|
||||||
media.Folder): The media data object for this row
|
media.Folder): The media data object for this row
|
||||||
|
|
||||||
|
@ -1659,10 +1718,10 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
Removes a row from the Video Index.
|
Removes a row from the Video Index.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
media_data_obj (media.Video, media.Channel, media.Playlist,
|
media_data_obj (media.Video, media.Channel, media.Playlist,
|
||||||
media.Folder): The media data object for this row
|
media.Folder): The media data object for this row
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Videos can't be shown in the Video Index
|
# Videos can't be shown in the Video Index
|
||||||
|
@ -1753,7 +1812,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Folder):
|
media_data_obj (media.Channel, media.Playlist or media.Folder):
|
||||||
The media data object whose row should be updated
|
The media data object whose row should be updated
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Videos can't be shown in the Video Index
|
# Videos can't be shown in the Video Index
|
||||||
|
@ -1770,7 +1829,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
# Update the treeview row
|
# Update the treeview row
|
||||||
tree_ref = self.video_index_row_dict[media_data_obj.name]
|
tree_ref = self.video_index_row_dict[media_data_obj.name]
|
||||||
model = tree_ref.get_model()
|
model = tree_ref.get_model()
|
||||||
tree_path = tree_ref.get_path()
|
tree_path = tree_ref.get_path()
|
||||||
tree_iter = model.get_iter(tree_path)
|
tree_iter = model.get_iter(tree_path)
|
||||||
model.set(tree_iter, 2, self.videx_index_get_icon(media_data_obj))
|
model.set(tree_iter, 2, self.videx_index_get_icon(media_data_obj))
|
||||||
|
@ -1794,9 +1853,9 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Folder):
|
media_data_obj (media.Channel, media.Playlist or media.Folder):
|
||||||
The media data object whose row should be updated
|
The media data object whose row should be updated
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Videos can't be shown in the Video Index
|
# Videos can't be shown in the Video Index
|
||||||
if isinstance(media_data_obj, media.Video):
|
if isinstance(media_data_obj, media.Video):
|
||||||
return self.app_obj.system_error(
|
return self.app_obj.system_error(
|
||||||
|
@ -1811,7 +1870,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
# Update the treeview row
|
# Update the treeview row
|
||||||
tree_ref = self.video_index_row_dict[media_data_obj.name]
|
tree_ref = self.video_index_row_dict[media_data_obj.name]
|
||||||
model = tree_ref.get_model()
|
model = tree_ref.get_model()
|
||||||
tree_path = tree_ref.get_path()
|
tree_path = tree_ref.get_path()
|
||||||
tree_iter = model.get_iter(tree_path)
|
tree_iter = model.get_iter(tree_path)
|
||||||
model.set(tree_iter, 3, self.video_index_get_text(media_data_obj))
|
model.set(tree_iter, 3, self.video_index_get_text(media_data_obj))
|
||||||
|
@ -1931,7 +1990,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
A string.
|
A string.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
text = utils.shorten_string(
|
text = utils.shorten_string(
|
||||||
media_data_obj.name,
|
media_data_obj.name,
|
||||||
|
@ -2320,7 +2379,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
"""Called from callbacks in self.on_video_index_selection_changed(),
|
"""Called from callbacks in self.on_video_index_selection_changed(),
|
||||||
mainapp.TartubeApp.on_button_switch_view(),
|
mainapp.TartubeApp.on_button_switch_view(),
|
||||||
.on_menu_add_video() and on_menu_test().
|
.on_menu_add_video() and on_menu_test().
|
||||||
|
|
||||||
When the user clicks on a media data object in the Video Index (a
|
When the user clicks on a media data object in the Video Index (a
|
||||||
channel, playlist or folder), this function is called to replace the
|
channel, playlist or folder), this function is called to replace the
|
||||||
contents of the Video Catalogue with all the video objects stored as
|
contents of the Video Catalogue with all the video objects stored as
|
||||||
|
@ -2339,7 +2398,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
each video.
|
each video.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
name (string): The selected media data object's name; one of the
|
name (string): The selected media data object's name; one of the
|
||||||
keys in self.media_name_dict
|
keys in self.media_name_dict
|
||||||
|
|
||||||
|
@ -2405,11 +2464,11 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
"""Called by self.results_list_update_row and a callback in
|
"""Called by self.results_list_update_row and a callback in
|
||||||
self.on_video_catalogue_enforce_check().
|
self.on_video_catalogue_enforce_check().
|
||||||
|
|
||||||
Also called by mainapp.TartubeApp.create_video_from_download(),
|
Also called by mainapp.TartubeApp.create_video_from_download(),
|
||||||
.announce_video_download(), .mark_video_new() and
|
.announce_video_download(), .mark_video_new() and
|
||||||
.mark_video_favourite().
|
.mark_video_favourite().
|
||||||
|
|
||||||
This function is called with a media.Video object. If that video is
|
This function is called with a media.Video object. If that video is
|
||||||
already visible in the Video Catalogue, updates the corresponding
|
already visible in the Video Catalogue, updates the corresponding
|
||||||
mainwin.SimpleCatalogueItem or mainwin.ComplexCatalogueItem (which
|
mainwin.SimpleCatalogueItem or mainwin.ComplexCatalogueItem (which
|
||||||
|
@ -2499,7 +2558,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
This function is called with a media.Video object. If that video is
|
This function is called with a media.Video object. If that video is
|
||||||
already visible in the Video Catalogue, removes the corresponding
|
already visible in the Video Catalogue, removes the corresponding
|
||||||
mainwin.SimpleCatalogueItem or mainwin.ComplexCatalogueItem .
|
mainwin.SimpleCatalogueItem or mainwin.ComplexCatalogueItem .
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
video_obj (media.Video) - The video to remove
|
video_obj (media.Video) - The video to remove
|
||||||
|
@ -2845,7 +2904,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
Progress List.
|
Progress List.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
download_list_obj (downloads.DownloadList): The download list
|
download_list_obj (downloads.DownloadList): The download list
|
||||||
object that has just been created
|
object that has just been created
|
||||||
|
|
||||||
|
@ -3038,7 +3097,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
str,
|
str,
|
||||||
)
|
)
|
||||||
self.results_list_treeview.set_model(self.results_list_liststore)
|
self.results_list_treeview.set_model(self.results_list_liststore)
|
||||||
|
|
||||||
# Reset IVs
|
# Reset IVs
|
||||||
self.results_list_row_count = 0
|
self.results_list_row_count = 0
|
||||||
self.results_list_temp_list = []
|
self.results_list_temp_list = []
|
||||||
|
@ -3276,7 +3335,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# File not found
|
# File not found
|
||||||
|
|
||||||
# If this was a simulated download, the key 'keep_description'
|
# If this was a simulated download, the key 'keep_description'
|
||||||
|
@ -3511,9 +3570,9 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
page_num (int) - The number of the newly-visible tab (the Videos
|
page_num (int) - The number of the newly-visible tab (the Videos
|
||||||
Tab is number 0)
|
Tab is number 0)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.visible_tab_num = page_num
|
self.visible_tab_num = page_num
|
||||||
|
|
||||||
if page_num == 2:
|
if page_num == 2:
|
||||||
|
@ -3535,7 +3594,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj \
|
if self.app_obj.current_manager_obj \
|
||||||
|
@ -3572,7 +3631,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj:
|
if self.app_obj.current_manager_obj:
|
||||||
|
@ -3597,7 +3656,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.app_obj.delete_container(media_data_obj)
|
self.app_obj.delete_container(media_data_obj)
|
||||||
|
@ -3615,7 +3674,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj:
|
if self.app_obj.current_manager_obj:
|
||||||
|
@ -3649,9 +3708,9 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
info (int): Ignored
|
info (int): Ignored
|
||||||
|
|
||||||
timestamp (int): Ignored
|
timestamp (int): Ignored
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Must override the usual Gtk handler
|
# Must override the usual Gtk handler
|
||||||
treeview.stop_emission('drag_data_received')
|
treeview.stop_emission('drag_data_received')
|
||||||
|
|
||||||
|
@ -3697,7 +3756,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
x, y (int): Cell coordinates in the treeview
|
x, y (int): Cell coordinates in the treeview
|
||||||
|
|
||||||
time (int): A timestamp
|
time (int): A timestamp
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Must override the usual Gtk handler
|
# Must override the usual Gtk handler
|
||||||
|
@ -3722,7 +3781,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj or not media_data_obj.options_obj:
|
if self.app_obj.current_manager_obj or not media_data_obj.options_obj:
|
||||||
|
@ -3752,7 +3811,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj:
|
if self.app_obj.current_manager_obj:
|
||||||
|
@ -3782,7 +3841,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.app_obj.mark_container_favourite(media_data_obj, True)
|
self.app_obj.mark_container_favourite(media_data_obj, True)
|
||||||
|
@ -3801,7 +3860,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.app_obj.mark_container_favourite(media_data_obj, False)
|
self.app_obj.mark_container_favourite(media_data_obj, False)
|
||||||
|
@ -3819,7 +3878,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.app_obj.mark_folder_hidden(media_data_obj, True)
|
self.app_obj.mark_folder_hidden(media_data_obj, True)
|
||||||
|
@ -3839,7 +3898,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Special arrangements for private folders
|
# Special arrangements for private folders
|
||||||
|
@ -3884,7 +3943,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Special arrangements for private folders
|
# Special arrangements for private folders
|
||||||
|
@ -3926,7 +3985,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.app_obj.move_container_to_top(media_data_obj)
|
self.app_obj.move_container_to_top(media_data_obj)
|
||||||
|
@ -3939,14 +3998,14 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
Refresh the right-clicked media data object, checking the corresponding
|
Refresh the right-clicked media data object, checking the corresponding
|
||||||
directory on the user's filesystem against video objects in the
|
directory on the user's filesystem against video objects in the
|
||||||
database.
|
database.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj:
|
if self.app_obj.current_manager_obj:
|
||||||
|
@ -3972,7 +4031,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj \
|
if self.app_obj.current_manager_obj \
|
||||||
|
@ -3998,9 +4057,9 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
treeview (Gtk.TreeView): The Video Index's treeview
|
treeview (Gtk.TreeView): The Video Index's treeview
|
||||||
|
|
||||||
event (Gdk.EventButton): The event emitting the Gtk signal
|
event (Gdk.EventButton): The event emitting the Gtk signal
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
|
if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
|
||||||
|
|
||||||
# If the user right-clicked on empty space, the call to
|
# If the user right-clicked on empty space, the call to
|
||||||
|
@ -4038,7 +4097,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
selection (Gtk.TreeSelection): Data for the selected row
|
selection (Gtk.TreeSelection): Data for the selected row
|
||||||
"""
|
"""
|
||||||
|
|
||||||
(model, iter) = selection.get_selected()
|
(model, iter) = selection.get_selected()
|
||||||
|
|
||||||
# Don't update the Video Catalogue during certain proecudres, such as
|
# Don't update the Video Catalogue during certain proecudres, such as
|
||||||
|
@ -4067,7 +4126,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path = media_data_obj.get_dir(self.app_obj)
|
path = media_data_obj.get_dir(self.app_obj)
|
||||||
|
@ -4086,7 +4145,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
media_data_obj (media.Channel, media.Playlist or media.Channel):
|
||||||
The clicked media data object
|
The clicked media data object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj:
|
if self.app_obj.current_manager_obj:
|
||||||
|
@ -4114,7 +4173,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj or media_data_obj.options_obj:
|
if self.app_obj.current_manager_obj or media_data_obj.options_obj:
|
||||||
|
@ -4127,7 +4186,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
media_data_obj.set_options_obj(options.OptionsManager())
|
media_data_obj.set_options_obj(options.OptionsManager())
|
||||||
# Update the video catalogue to show the right icon
|
# Update the video catalogue to show the right icon
|
||||||
self.video_catalogue_update_row(media_data_obj)
|
self.video_catalogue_update_row(media_data_obj)
|
||||||
|
|
||||||
# Open an edit window to show the options immediately
|
# Open an edit window to show the options immediately
|
||||||
config.OptionsEditWin(
|
config.OptionsEditWin(
|
||||||
self.app_obj,
|
self.app_obj,
|
||||||
|
@ -4147,7 +4206,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj:
|
if self.app_obj.current_manager_obj:
|
||||||
|
@ -4181,7 +4240,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj:
|
if self.app_obj.current_manager_obj:
|
||||||
|
@ -4206,7 +4265,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj or not media_data_obj.options_obj:
|
if self.app_obj.current_manager_obj or not media_data_obj.options_obj:
|
||||||
|
@ -4235,7 +4294,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# (Don't allow the user to change the setting of
|
# (Don't allow the user to change the setting of
|
||||||
|
@ -4268,7 +4327,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj:
|
if self.app_obj.current_manager_obj:
|
||||||
|
@ -4311,7 +4370,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj or not media_data_obj.options_obj:
|
if self.app_obj.current_manager_obj or not media_data_obj.options_obj:
|
||||||
|
@ -4324,7 +4383,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
media_data_obj.set_options_obj(None)
|
media_data_obj.set_options_obj(None)
|
||||||
# Update the video catalogue to show the right icon
|
# Update the video catalogue to show the right icon
|
||||||
self.video_catalogue_update_row(media_data_obj)
|
self.video_catalogue_update_row(media_data_obj)
|
||||||
|
|
||||||
|
|
||||||
def on_video_catalogue_show_properties(self, menu_item, media_data_obj):
|
def on_video_catalogue_show_properties(self, menu_item, media_data_obj):
|
||||||
|
|
||||||
|
@ -4337,7 +4396,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.app_obj.current_manager_obj:
|
if self.app_obj.current_manager_obj:
|
||||||
|
@ -4362,7 +4421,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not media_data_obj.fav_flag:
|
if not media_data_obj.fav_flag:
|
||||||
|
@ -4382,7 +4441,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not media_data_obj.new_flag:
|
if not media_data_obj.new_flag:
|
||||||
|
@ -4402,7 +4461,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Launch the video
|
# Launch the video
|
||||||
|
@ -4427,7 +4486,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Launch the video
|
# Launch the video
|
||||||
|
@ -4449,7 +4508,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
menu_item (Gtk.MenuItem): The clicked menu item
|
menu_item (Gtk.MenuItem): The clicked menu item
|
||||||
|
|
||||||
media_data_obj (media.Video) - The clicked video object
|
media_data_obj (media.Video) - The clicked video object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Launch the video
|
# Launch the video
|
||||||
|
@ -4460,7 +4519,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
self.app_obj.mark_video_new(media_data_obj, False)
|
self.app_obj.mark_video_new(media_data_obj, False)
|
||||||
|
|
||||||
|
|
||||||
def on_spinbutton_changed(self, spinbutton):
|
def on_spinbutton_changed(self, spinbutton):
|
||||||
|
|
||||||
"""Called from callback in self.setup_progress_tab().
|
"""Called from callback in self.setup_progress_tab().
|
||||||
|
|
||||||
|
@ -4491,7 +4550,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton) - The clicked widget
|
checkbutton (Gtk.CheckButton) - The clicked widget
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.checkbutton.get_active():
|
if self.checkbutton.get_active():
|
||||||
|
@ -4502,7 +4561,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
self.app_obj.set_num_worker_apply_flag(False)
|
self.app_obj.set_num_worker_apply_flag(False)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4517,7 +4576,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
spinbutton (Gtk.SpinButton): The clicked widget
|
spinbutton (Gtk.SpinButton): The clicked widget
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.app_obj.set_bandwidth_default(
|
self.app_obj.set_bandwidth_default(
|
||||||
|
@ -4536,7 +4595,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
checkbutton (Gtk.CheckButton): The clicked widget
|
checkbutton (Gtk.CheckButton): The clicked widget
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.app_obj.set_bandwidth_apply_flag(self.checkbutton2.get_active())
|
self.app_obj.set_bandwidth_apply_flag(self.checkbutton2.get_active())
|
||||||
|
@ -4552,7 +4611,7 @@ class MainWin(Gtk.ApplicationWindow):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
button (Gtk.Button): The clicked widget
|
button (Gtk.Button): The clicked widget
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.errors_list_reset()
|
self.errors_list_reset()
|
||||||
|
@ -4656,7 +4715,7 @@ class SimpleCatalogueItem(object):
|
||||||
|
|
||||||
|
|
||||||
# Public class methods
|
# Public class methods
|
||||||
|
|
||||||
|
|
||||||
def draw_widgets(self, catalogue_row):
|
def draw_widgets(self, catalogue_row):
|
||||||
|
|
||||||
|
@ -4808,7 +4867,7 @@ class SimpleCatalogueItem(object):
|
||||||
|
|
||||||
event_box (Gtk.EventBox), event (Gtk.EventButton): Data from the
|
event_box (Gtk.EventBox), event (Gtk.EventButton): Data from the
|
||||||
signal emitted by the click
|
signal emitted by the click
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
|
if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
|
||||||
|
@ -4881,7 +4940,7 @@ class ComplexCatalogueItem(object):
|
||||||
|
|
||||||
# Public class methods
|
# Public class methods
|
||||||
|
|
||||||
|
|
||||||
def draw_widgets(self, catalogue_row):
|
def draw_widgets(self, catalogue_row):
|
||||||
|
|
||||||
"""Called by mainwin.MainWin.video_catalogue_redraw_all() and
|
"""Called by mainwin.MainWin.video_catalogue_redraw_all() and
|
||||||
|
@ -5308,7 +5367,7 @@ class ComplexCatalogueItem(object):
|
||||||
|
|
||||||
event_box (Gtk.EventBox), event (Gtk.EventButton): Data from the
|
event_box (Gtk.EventBox), event (Gtk.EventButton): Data from the
|
||||||
signal emitted by the click
|
signal emitted by the click
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
|
if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
|
||||||
|
@ -5354,7 +5413,7 @@ class ComplexCatalogueItem(object):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
True to show the action has been handled
|
True to show the action has been handled
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Launch the video
|
# Launch the video
|
||||||
|
@ -5383,7 +5442,7 @@ class ComplexCatalogueItem(object):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
True to show the action has been handled
|
True to show the action has been handled
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Launch the video
|
# Launch the video
|
||||||
|
@ -5411,7 +5470,7 @@ class ComplexCatalogueItem(object):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
True to show the action has been handled
|
True to show the action has been handled
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Launch the video
|
# Launch the video
|
||||||
|
@ -5564,7 +5623,7 @@ class AddVideoDialogue(Gtk.Dialog):
|
||||||
|
|
||||||
# Public class methods
|
# Public class methods
|
||||||
|
|
||||||
|
|
||||||
def on_combo_changed(self, combo):
|
def on_combo_changed(self, combo):
|
||||||
|
|
||||||
"""Called from callback in self.__init__().
|
"""Called from callback in self.__init__().
|
||||||
|
@ -5575,7 +5634,7 @@ class AddVideoDialogue(Gtk.Dialog):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
combo (Gtk.ComboBox): The clicked widget
|
combo (Gtk.ComboBox): The clicked widget
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.parent_name = self.folder_list[combo.get_active()]
|
self.parent_name = self.folder_list[combo.get_active()]
|
||||||
|
@ -5700,7 +5759,7 @@ class AddChannelDialogue(Gtk.Dialog):
|
||||||
|
|
||||||
# Public class methods
|
# Public class methods
|
||||||
|
|
||||||
|
|
||||||
def on_combo_changed(self, combo):
|
def on_combo_changed(self, combo):
|
||||||
|
|
||||||
"""Called from callback in self.__init__().
|
"""Called from callback in self.__init__().
|
||||||
|
@ -5711,7 +5770,7 @@ class AddChannelDialogue(Gtk.Dialog):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
combo (Gtk.ComboBox): The clicked widget
|
combo (Gtk.ComboBox): The clicked widget
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.parent_name = self.folder_list[combo.get_active()]
|
self.parent_name = self.folder_list[combo.get_active()]
|
||||||
|
@ -5836,7 +5895,7 @@ class AddPlaylistDialogue(Gtk.Dialog):
|
||||||
|
|
||||||
# Public class methods
|
# Public class methods
|
||||||
|
|
||||||
|
|
||||||
def on_combo_changed(self, combo):
|
def on_combo_changed(self, combo):
|
||||||
|
|
||||||
"""Called from callback in self.__init__().
|
"""Called from callback in self.__init__().
|
||||||
|
@ -5847,7 +5906,7 @@ class AddPlaylistDialogue(Gtk.Dialog):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
combo (Gtk.ComboBox): The clicked widget
|
combo (Gtk.ComboBox): The clicked widget
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.parent_name = self.folder_list[combo.get_active()]
|
self.parent_name = self.folder_list[combo.get_active()]
|
||||||
|
@ -5958,7 +6017,7 @@ class AddFolderDialogue(Gtk.Dialog):
|
||||||
|
|
||||||
# Public class methods
|
# Public class methods
|
||||||
|
|
||||||
|
|
||||||
def on_combo_changed(self, combo):
|
def on_combo_changed(self, combo):
|
||||||
|
|
||||||
"""Called from callback in self.__init__().
|
"""Called from callback in self.__init__().
|
||||||
|
@ -5969,7 +6028,7 @@ class AddFolderDialogue(Gtk.Dialog):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
combo (Gtk.ComboBox): The clicked widget
|
combo (Gtk.ComboBox): The clicked widget
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.parent_name = self.folder_list[combo.get_active()]
|
self.parent_name = self.folder_list[combo.get_active()]
|
||||||
|
|
164
lib/media.py
164
lib/media.py
|
@ -26,14 +26,14 @@
|
||||||
|
|
||||||
# Import other modules
|
# Import other modules
|
||||||
import datetime
|
import datetime
|
||||||
|
import functools
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
# Import our modules
|
# Import our modules
|
||||||
import functools
|
from . import mainapp
|
||||||
import mainapp
|
from . import utils
|
||||||
import time
|
|
||||||
import utils
|
|
||||||
|
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
|
@ -121,7 +121,7 @@ class GenericContainer(GenericMedia):
|
||||||
video_list (list): A list of media.Video objects
|
video_list (list): A list of media.Video objects
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The modified video_list
|
The modified video_list
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -139,7 +139,7 @@ class GenericContainer(GenericMedia):
|
||||||
def count_descendants(self, count_list):
|
def count_descendants(self, count_list):
|
||||||
|
|
||||||
"""Can be called by anything. Currently called by
|
"""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.
|
function recursively.
|
||||||
|
|
||||||
Counts the number of child objects, and then calls this function
|
Counts the number of child objects, and then calls this function
|
||||||
|
@ -155,7 +155,7 @@ class GenericContainer(GenericMedia):
|
||||||
)
|
)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The modified count_list
|
The modified count_list
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -196,7 +196,7 @@ class GenericContainer(GenericMedia):
|
||||||
was not a child of this object
|
was not a child of this object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check this is really one of our children
|
# Check this is really one of our children
|
||||||
index = self.find_child_index(child_obj)
|
index = self.find_child_index(child_obj)
|
||||||
if index is None:
|
if index is None:
|
||||||
|
@ -227,7 +227,7 @@ class GenericContainer(GenericMedia):
|
||||||
|
|
||||||
An integer describing the position in self.child_list, or None of
|
An integer describing the position in self.child_list, or None of
|
||||||
the child object is not found in self.child_list
|
the child object is not found in self.child_list
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -252,7 +252,7 @@ class GenericContainer(GenericMedia):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The container object's level
|
The container object's level
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.parent_obj is None:
|
if self.parent_obj is None:
|
||||||
|
@ -283,7 +283,7 @@ class GenericContainer(GenericMedia):
|
||||||
can't be hidden directly.)
|
can't be hidden directly.)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
True or False.
|
True or False.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -357,7 +357,7 @@ class GenericContainer(GenericMedia):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The full path to the directory
|
The full path to the directory
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dir_list = [self.name]
|
dir_list = [self.name]
|
||||||
|
@ -401,6 +401,40 @@ class GenericRemoteContainer(GenericContainer):
|
||||||
self.vid_count += 1
|
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):
|
def do_sort(self, obj1, obj2):
|
||||||
|
|
||||||
"""Sorting function used by functools.cmp_to_key(), and called by
|
"""Sorting function used by functools.cmp_to_key(), and called by
|
||||||
|
@ -414,16 +448,21 @@ class GenericRemoteContainer(GenericContainer):
|
||||||
videos.
|
videos.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
obj1, obj2 (media.Video) - Video objects being sorted
|
obj1, obj2 (media.Video) - Video objects being sorted
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
-1 if obj1 comes first, 1 if obj2 comes first, 0 if they are equal
|
-1 if obj1 comes first, 1 if obj2 comes first, 0 if they are equal
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if obj1.upload_time < obj2.upload_time:
|
# # Convert Python2 to Python3
|
||||||
|
# if obj1.upload_time < obj2.upload_time:
|
||||||
|
# return 1
|
||||||
|
if obj1.upload_time is None or obj2.upload_time is None:
|
||||||
|
return 0
|
||||||
|
elif obj1.upload_time < obj2.upload_time:
|
||||||
return 1
|
return 1
|
||||||
elif obj1.upload_time == obj2.upload_time:
|
elif obj1.upload_time == obj2.upload_time:
|
||||||
if obj1.receive_time < obj2.receive_time:
|
if obj1.receive_time < obj2.receive_time:
|
||||||
|
@ -620,7 +659,7 @@ class Video(GenericMedia):
|
||||||
favourite.
|
favourite.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
True if the parent (or the parent's parent, and so on) is marked
|
True if the parent (or the parent's parent, and so on) is marked
|
||||||
favourite, False otherwise
|
favourite, False otherwise
|
||||||
|
|
||||||
|
@ -649,7 +688,7 @@ class Video(GenericMedia):
|
||||||
|
|
||||||
max_length (int): When storing the description in this object's
|
max_length (int): When storing the description in this object's
|
||||||
IVs, the maximum line length to use
|
IVs, the maximum line length to use
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
descrip_path = os.path.join(
|
descrip_path = os.path.join(
|
||||||
|
@ -774,7 +813,7 @@ class Video(GenericMedia):
|
||||||
max_length (int): A maximum line size
|
max_length (int): A maximum line size
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if descrip:
|
if descrip:
|
||||||
|
|
||||||
self.descrip = utils.tidy_up_long_descrip(descrip, max_length)
|
self.descrip = utils.tidy_up_long_descrip(descrip, max_length)
|
||||||
|
@ -797,7 +836,7 @@ class Video(GenericMedia):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The converted string, or None if self.file_size is not set
|
The converted string, or None if self.file_size is not set
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.file_size:
|
if self.file_size:
|
||||||
|
@ -816,7 +855,7 @@ class Video(GenericMedia):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The formatted string, or None if self.receive_time is not set
|
The formatted string, or None if self.receive_time is not set
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.receive_time:
|
if self.receive_time:
|
||||||
|
@ -835,7 +874,7 @@ class Video(GenericMedia):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The formatted string, or None if self.receive_time is not set
|
The formatted string, or None if self.receive_time is not set
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.receive_time:
|
if self.receive_time:
|
||||||
|
@ -854,7 +893,7 @@ class Video(GenericMedia):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The formatted string, or None if self.upload_time is not set
|
The formatted string, or None if self.upload_time is not set
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.upload_time:
|
if self.upload_time:
|
||||||
|
@ -873,7 +912,7 @@ class Video(GenericMedia):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The formatted string, or None if self.upload_time is not set
|
The formatted string, or None if self.upload_time is not set
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.upload_time:
|
if self.upload_time:
|
||||||
|
@ -1287,7 +1326,7 @@ class Folder(GenericContainer):
|
||||||
# def del_child(): # Inherited from GenericContainer
|
# def del_child(): # Inherited from GenericContainer
|
||||||
|
|
||||||
|
|
||||||
def do_sort(self, obj1, obj2):
|
def OLDdo_sort(self, obj1, obj2):
|
||||||
|
|
||||||
"""Sorting function used by functools.cmp_to_key(), and called by
|
"""Sorting function used by functools.cmp_to_key(), and called by
|
||||||
self.sort_children().
|
self.sort_children().
|
||||||
|
@ -1301,14 +1340,14 @@ class Folder(GenericContainer):
|
||||||
videos, sort by upload time.
|
videos, sort by upload time.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
obj1, obj2 (media.Video, media.Channel, media.Playlist or
|
obj1, obj2 (media.Video, media.Channel, media.Playlist or
|
||||||
media.Folder) - Media data objects being sorted
|
media.Folder) - Media data objects being sorted
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
-1 if obj1 comes first, 1 if obj2 comes first, 0 if they are equal
|
-1 if obj1 comes first, 1 if obj2 comes first, 0 if they are equal
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if str(obj1.__class__) == str(obj2.__class__) \
|
if str(obj1.__class__) == str(obj2.__class__) \
|
||||||
|
@ -1351,6 +1390,75 @@ class Folder(GenericContainer):
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def do_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):
|
||||||
|
|
||||||
|
# # Convert Python2 to Python3
|
||||||
|
# if obj1.upload_time < obj2.upload_time:
|
||||||
|
# return 1
|
||||||
|
if obj1.upload_time is None or obj2.upload_time is None:
|
||||||
|
return 0
|
||||||
|
elif 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 find_child_index(): # Inherited from GenericContainer
|
# def find_child_index(): # Inherited from GenericContainer
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,10 @@ import os
|
||||||
|
|
||||||
|
|
||||||
# Import our modules
|
# Import our modules
|
||||||
import constants
|
from . import constants
|
||||||
import mainapp
|
from . import mainapp
|
||||||
import media
|
from . import media
|
||||||
import utils
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
|
@ -539,7 +539,7 @@ class OptionsParser(object):
|
||||||
taken from options.OptionsManager.options_dict
|
taken from options.OptionsManager.options_dict
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
List of strings with all the youtube-dl command line options
|
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.
|
Check if options required by another option are enabled, or not.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
copy_dict (dict): Copy of the original options dictionary.
|
copy_dict (dict): Copy of the original options dictionary.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
True if any of the required options is enabled, otherwise returns
|
True if any of the required options is enabled, otherwise returns
|
||||||
False.
|
False.
|
||||||
|
|
||||||
|
@ -845,7 +845,7 @@ class OptionHolder(object):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
True if the option is a boolean switch, otherwise returns False
|
True if the option is a boolean switch, otherwise returns False
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return type(self.default_value) is bool
|
return type(self.default_value) is bool
|
||||||
|
|
|
@ -25,13 +25,13 @@
|
||||||
|
|
||||||
|
|
||||||
# Import other modules
|
# Import other modules
|
||||||
import constants
|
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
|
||||||
# Import our modules
|
# Import our modules
|
||||||
import media
|
from . import constants
|
||||||
|
from . import media
|
||||||
|
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
|
@ -80,7 +80,7 @@ class RefreshManager(threading.Thread):
|
||||||
|
|
||||||
# Public class methods
|
# Public class methods
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
"""Called by mainapp.TartubeApp.refresh_manager_start().
|
"""Called by mainapp.TartubeApp.refresh_manager_start().
|
||||||
|
|
|
@ -51,7 +51,7 @@ def add_test_media(app_obj):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
app_obj (mainapp.TartubeApp): The main application
|
app_obj (mainapp.TartubeApp): The main application
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Test videos
|
# Test videos
|
||||||
|
|
108
lib/updates.py
108
lib/updates.py
|
@ -26,7 +26,8 @@
|
||||||
|
|
||||||
# Import other modules
|
# Import other modules
|
||||||
import os
|
import os
|
||||||
import Queue
|
import queue
|
||||||
|
import re
|
||||||
import requests
|
import requests
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
@ -34,9 +35,8 @@ import threading
|
||||||
|
|
||||||
|
|
||||||
# Import our modules
|
# Import our modules
|
||||||
import downloads
|
from . import downloads
|
||||||
import re
|
from . import utils
|
||||||
import utils
|
|
||||||
|
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
|
@ -74,8 +74,8 @@ class UpdateManager(threading.Thread):
|
||||||
# This object reads from the child process STDOUT and STDERR in an
|
# This object reads from the child process STDOUT and STDERR in an
|
||||||
# asynchronous way
|
# asynchronous way
|
||||||
# Standard Python synchronised queue classes
|
# Standard Python synchronised queue classes
|
||||||
self.stdout_queue = Queue.Queue()
|
self.stdout_queue = queue.Queue()
|
||||||
self.stderr_queue = Queue.Queue()
|
self.stderr_queue = queue.Queue()
|
||||||
# The downloads.PipeReader objects created to handle reading from the
|
# The downloads.PipeReader objects created to handle reading from the
|
||||||
# pipes
|
# pipes
|
||||||
self.stdout_reader = downloads.PipeReader(self.stdout_queue)
|
self.stdout_reader = downloads.PipeReader(self.stdout_queue)
|
||||||
|
@ -104,8 +104,8 @@ class UpdateManager(threading.Thread):
|
||||||
|
|
||||||
# Public class methods
|
# Public class methods
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def OLDrun(self):
|
||||||
|
|
||||||
"""Called as a result of self.__init__().
|
"""Called as a result of self.__init__().
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ class UpdateManager(threading.Thread):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Prepare the system command
|
# Prepare the system command
|
||||||
|
|
||||||
# The user can change the system command for updating youtube-dl,
|
# The user can change the system command for updating youtube-dl,
|
||||||
# depending on how it was installed
|
# depending on how it was installed
|
||||||
# (For example, if youtube-dl was installed via pip, then it must be
|
# (For example, if youtube-dl was installed via pip, then it must be
|
||||||
|
@ -189,6 +189,94 @@ class UpdateManager(threading.Thread):
|
||||||
else:
|
else:
|
||||||
self.app_obj.update_manager_finished(True)
|
self.app_obj.update_manager_finished(True)
|
||||||
|
|
||||||
|
def run(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():
|
||||||
|
|
||||||
|
# # (Convert Python2 to Python3)
|
||||||
|
# stdout = self.stdout_queue.get_nowait().rstrip()
|
||||||
|
# stdout = utils.convert_item(stdout, to_unicode=True)
|
||||||
|
stdout = self.stdout_queue.get_nowait().rstrip().decode('utf-8')
|
||||||
|
|
||||||
|
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
|
||||||
|
# # (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)
|
||||||
|
|
||||||
|
# (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 create_child_process(self, cmd_list):
|
def create_child_process(self, cmd_list):
|
||||||
|
|
||||||
|
@ -263,7 +351,7 @@ class UpdateManager(threading.Thread):
|
||||||
|
|
||||||
"""Called by mainapp.TartubeApp.on_button_stop_operation(), .stop() and
|
"""Called by mainapp.TartubeApp.on_button_stop_operation(), .stop() and
|
||||||
a callback in .on_button_stop_operation().
|
a callback in .on_button_stop_operation().
|
||||||
|
|
||||||
Based on code from downloads.VideoDownloader.stop().
|
Based on code from downloads.VideoDownloader.stop().
|
||||||
|
|
||||||
Terminates the child process.
|
Terminates the child process.
|
||||||
|
|
24
lib/utils.py
24
lib/utils.py
|
@ -36,10 +36,10 @@ import textwrap
|
||||||
|
|
||||||
|
|
||||||
# Import our modules
|
# Import our modules
|
||||||
import constants
|
from . import constants
|
||||||
import mainapp
|
from . import mainapp
|
||||||
if mainapp.HAVE_VALIDATORS_FLAG:
|
if mainapp.HAVE_VALIDATORS_FLAG:
|
||||||
import validators
|
from . import validators
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
|
@ -90,7 +90,7 @@ def convert_item(item, to_unicode=False):
|
||||||
Convert item between 'unicode' and 'str'.
|
Convert item between 'unicode' and 'str'.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
item (-): Can be any python item.
|
item (-): Can be any python item.
|
||||||
|
|
||||||
to_unicode (boolean): When True it will convert all the 'str' types
|
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'.
|
back to 'str'.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The converted item
|
The converted item
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -310,7 +310,7 @@ def format_bytes(num_bytes):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The formatted string
|
The formatted string
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if num_bytes == 0.0:
|
if num_bytes == 0.0:
|
||||||
|
@ -332,7 +332,7 @@ def get_encoding():
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The system encoding.
|
The system encoding.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -354,7 +354,7 @@ def open_file(uri):
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
uri (string): The URI to open
|
uri (string): The URI to open
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
|
@ -378,7 +378,7 @@ def remove_shortcuts(path):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The converted path
|
The converted path
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return path.replace('~', os.path.expanduser('~'))
|
return path.replace('~', os.path.expanduser('~'))
|
||||||
|
@ -399,7 +399,7 @@ def shorten_string(string, num_chars):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The converted string
|
The converted string
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if string and len(string) > num_chars:
|
if string and len(string) > num_chars:
|
||||||
|
@ -423,7 +423,7 @@ def to_string(data):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The converted string
|
The converted string
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return '%s' % data
|
return '%s' % data
|
||||||
|
@ -441,7 +441,7 @@ def upper_case_first(string):
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
The converted string
|
The converted string
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return string[0].upper() + string[1:]
|
return string[0].upper() + string[1:]
|
||||||
|
|
25
setup.py
25
setup.py
|
@ -21,27 +21,30 @@
|
||||||
|
|
||||||
|
|
||||||
# Import modules
|
# Import modules
|
||||||
from setuptools import setup, find_packages
|
import setuptools
|
||||||
|
|
||||||
|
|
||||||
# Import documents
|
# Import documents
|
||||||
with open('README.rst') as f:
|
#with open('README.rst', 'r') as f:
|
||||||
readme = f.read()
|
# long_description = f.read()
|
||||||
|
#
|
||||||
with open('LICENSE') as f:
|
#with open('LICENSE') as f:
|
||||||
license = f.read()
|
# license = f.read()
|
||||||
|
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
setup(
|
setuptools.setup(
|
||||||
name='tartube',
|
name='tartube',
|
||||||
version='0.1.000',
|
version='0.1.007',
|
||||||
description='GUI front-end for youtube-dl',
|
description='GUI front-end for youtube-dl',
|
||||||
long_description=readme,
|
# 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',
|
||||||
author='A S Lewis',
|
author='A S Lewis',
|
||||||
author_email='aslewis@cpan.org',
|
author_email='aslewis@cpan.org',
|
||||||
url='https://github.com/axcore/tartube',
|
url='https://github.com/axcore/tartube',
|
||||||
license=license,
|
# license=license,
|
||||||
packages=find_packages(exclude=('tests', 'docs'))
|
license="""GPL3+""",
|
||||||
|
packages=setuptools.find_packages()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,8 @@ from lib import mainapp
|
||||||
|
|
||||||
# 'Global' variables
|
# 'Global' variables
|
||||||
__packagename__ = 'tartube'
|
__packagename__ = 'tartube'
|
||||||
__version__ = '0.1.000'
|
__version__ = '0.1.007'
|
||||||
__date__ = '27 May 2019'
|
__date__ = '28 May 2019'
|
||||||
__copyright__ = 'Copyright \xc2\xa9 2019 A S Lewis'
|
__copyright__ = 'Copyright \xc2\xa9 2019 A S Lewis'
|
||||||
__license__ = """
|
__license__ = """
|
||||||
Copyright \xc2\xa9 2019 A S Lewis.
|
Copyright \xc2\xa9 2019 A S Lewis.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
Loading…
Reference in New Issue