Updates and fixes
92
README.rst
|
@ -59,16 +59,16 @@ For a full list of new features and fixes, see `recent changes <CHANGES>`__.
|
|||
3 Downloads
|
||||
===========
|
||||
|
||||
Latest version, MS Windows: **v2.3.447 (20 Mar 2022)**
|
||||
Stable release: **v2.3.447 (20 Mar 2022)**
|
||||
|
||||
Latest version, everything else: **v2.3.250 (24 Mar 2022)**
|
||||
Development release: **v2.3.471 (29 Mar 2022)**
|
||||
|
||||
Official packages (also available from the `Github release page <https://github.com/axcore/tartube/releases>`__):
|
||||
|
||||
- `MS Windows (64-bit) installer <https://sourceforge.net/projects/tartube/files/v2.3.447/install-tartube-2.3.447-64bit.exe/download>`__ and `portable edition <https://sourceforge.net/projects/tartube/files/v2.3.447/tartube-2.3.447-64bit-portable.zip/download>`__ from Sourceforge
|
||||
- Tartube is no longer supported on MS Windows (32-bit) - see `7.22 Doesn't work on 32-bit Windows`_
|
||||
- `DEB package (for Debian-based distros, e.g. Ubuntu, Linux Mint) <https://sourceforge.net/projects/tartube/files/v2.3.447/python3-tartube_2.3.450.deb/download>`__ from Sourceforge
|
||||
- `RPM package (for RHEL-based distros, e.g. Fedora) <https://sourceforge.net/projects/tartube/files/v2.3.447/tartube-2.3.450.rpm/download>`__ from Sourceforge
|
||||
- Tartube is no longer supported on MS Windows (32-bit) - see `7.23 Doesn't work on 32-bit Windows`_
|
||||
- `DEB package (for Debian-based distros, e.g. Ubuntu, Linux Mint) <https://sourceforge.net/projects/tartube/files/v2.3.447/python3-tartube_2.3.447.deb/download>`__ from Sourceforge
|
||||
- `RPM package (for RHEL-based distros, e.g. Fedora) <https://sourceforge.net/projects/tartube/files/v2.3.447/tartube-2.3.447.rpm/download>`__ from Sourceforge
|
||||
|
||||
Official 'Strict' packages:
|
||||
|
||||
|
@ -713,7 +713,7 @@ Refreshing the database:
|
|||
|
||||
Updating packages:
|
||||
|
||||
- **Update** - Installs or updates **youtube-dl**, as described in `6.2 Updating the downloader`_. Also installs FFmpeg (on MS Windows only); see `6.4 Installing FFmpeg / AVConv`_
|
||||
- **Update** - Installs or updates **youtube-dl**, as described in `6.2 Updating the downloader`_. On MS Windows, also installs **FFmpeg** (see `6.4 Installing FFmpeg / AVConv`_) and **matplotlib** (see `7.30 Graphs not visible`_)
|
||||
- *Protip*: Do an **'Update'** operation before you do a **'Check'** or **'Download'** operation
|
||||
|
||||
Fetching information:
|
||||
|
@ -877,11 +877,9 @@ The **yt-dlp** tab contains download options that only work with `yt-dlp <https:
|
|||
A new window appears. You can use this window to congifure the scheduled download.
|
||||
|
||||
- In the **Download mode** box, select whether **Tartube** should check videos, download them, or perform a custom download (see `6.13 Custom downloads`_)
|
||||
- In the **Start mode** box, select whether this download should be performed once, or when **Tartube** starts, or at regular intervals
|
||||
- If you choose regular intervals, then you can set the length of the interval
|
||||
- In the **Start** tab, select whether this download should be performed once, or when **Tartube** starts, or at regular intervals, or at specified times
|
||||
|
||||
.. image:: screenshots/example20.png
|
||||
:alt: The drag-and-drop tab
|
||||
When you specify times (like 'Mondays at 15:00'), there is a five-minute window in which the scheduled download can begin. This means that, if you open Tartube at 15:02, the scheduled download will still start (but not if you open Tartube at 15:10).
|
||||
|
||||
Now click the **Media** tab. By default, a scheduled download checks or downloads everything in **Tartube**'s database, but if you don't want that, you can select individual channels, playlists and folders.
|
||||
|
||||
|
@ -1874,19 +1872,20 @@ Alternatively, you can update the entire database at once. (This may take a long
|
|||
* `7.18 YouTube name/password not accepted`_
|
||||
* `7.19 Georestriction workarounds don't work`_
|
||||
* `7.20 Video website blocks me`_
|
||||
* `7.21 MS Windows installer is too big`_
|
||||
* `7.22 Doesn't work on 32-bit Windows`_
|
||||
* `7.23 Tartube can't detect livestreams`_
|
||||
* `7.24 Livestream is already finished`_
|
||||
* `7.25 Can't hear livestream alarms`_
|
||||
* `7.26 Some icons not visible`_
|
||||
* `7.27 Video thumbnails not visible`_
|
||||
* `7.28 Video text not visible`_
|
||||
* `7.29 Graphs not visible`_
|
||||
* `7.30 Tartube is not visible in the system tray`_
|
||||
* `7.31 Tartube is not portable`_
|
||||
* `7.32 Run out of disk space`_
|
||||
* `7.33 No puedo hablar inglés`_
|
||||
* `7.21 Too many blocked videos`_
|
||||
* `7.22 MS Windows installer is too big`_
|
||||
* `7.23 Doesn't work on 32-bit Windows`_
|
||||
* `7.24 Tartube can't detect livestreams`_
|
||||
* `7.25 Livestream is already finished`_
|
||||
* `7.26 Can't hear livestream alarms`_
|
||||
* `7.27 Some icons not visible`_
|
||||
* `7.28 Video thumbnails not visible`_
|
||||
* `7.29 Video text not visible`_
|
||||
* `7.30 Graphs not visible`_
|
||||
* `7.31 Tartube is not visible in the system tray`_
|
||||
* `7.32 Tartube is not portable`_
|
||||
* `7.33 Run out of disk space`_
|
||||
* `7.34 No puedo hablar inglés`_
|
||||
|
||||
7.1 Tartube won't install/won't run/doesn't work
|
||||
------------------------------------------------
|
||||
|
@ -2190,7 +2189,19 @@ A: You can specify a list of proxies (**Edit > System preferences... > Operation
|
|||
|
||||
Unfortunately, it is not possible to switch between proxies while downloading a channel (youtube-dl does not offer that functionality). But the proxy list will work well if you're trying to download ten different channels.
|
||||
|
||||
7.21 MS Windows installer is too big
|
||||
7.21 Too many blocked videos
|
||||
----------------------------
|
||||
|
||||
*Q: The Videos tab is full of 'blocked' videos!*
|
||||
|
||||
.. image:: screenshots/example33.png
|
||||
:alt: A selection of blocked videos
|
||||
|
||||
A: If Tartube detects a video that has been age-restricted, censored or otherwise blocked, it is still added to the database. Unfortunately, Tartube doesn't know anything about the video, not even when it was uploaded, so videos like this appear at the top of the list.
|
||||
|
||||
If you don't want the blocked videos in your database, you can click **Edit > System preferences > Operations > Downloads**, and then de-select **Add censored, age-restricted and other blocked videos to the database**
|
||||
|
||||
7.22 MS Windows installer is too big
|
||||
------------------------------------
|
||||
|
||||
*Q: Why is the Windows installer so big?*
|
||||
|
@ -2212,7 +2223,7 @@ The NSIS scripts used to create the installers can be found here:
|
|||
|
||||
The scripts contain full instructions, so you should be able to create your own installer and then compare it to the official one.
|
||||
|
||||
7.22 Doesn't work on 32-bit Windows
|
||||
7.23 Doesn't work on 32-bit Windows
|
||||
-----------------------------------
|
||||
|
||||
*Q: Tartube does not install/work on 32-bit Windows*
|
||||
|
@ -2221,7 +2232,7 @@ A: Cygwin and MSYS2 have `dropped support for 32-bit Windows <https://www.msys2.
|
|||
|
||||
Therefore there will be no further releases of Tartube for 32-bit Windows. Old installers will still work, and for a time it will still be possible to use them to install Tartube and youtube-dl. However, as of March 2022 it is already not possible to install FFmpeg.
|
||||
|
||||
7.23 Tartube can't detect livestreams
|
||||
7.24 Tartube can't detect livestreams
|
||||
-------------------------------------
|
||||
|
||||
*Q: Tartube can't detect upcoming livestreams at all!*
|
||||
|
@ -2234,14 +2245,14 @@ If the `Python feedparser module <https://pypi.org/project/feedparser/>`__ is no
|
|||
|
||||
The Tartube installer for 64-bit MS Windows already contains a copy of **feedparser**, so there is no need to install it again.
|
||||
|
||||
7.24 Livestream is already finished
|
||||
7.25 Livestream is already finished
|
||||
-----------------------------------
|
||||
|
||||
*Q: Tartube is showing a livestream that finished hours/days/centuries ago!*
|
||||
|
||||
A: Right-click the video and select **Livestream > Not a livestream**.
|
||||
|
||||
7.25 Can't hear livestream alarms
|
||||
7.26 Can't hear livestream alarms
|
||||
---------------------------------
|
||||
|
||||
*Q: I set an alarm for an upcoming livestream, but I didn't hear anything!*
|
||||
|
@ -2254,7 +2265,7 @@ If the `Python playsound module <https://pypi.org/project/playsound/>`__ is not
|
|||
|
||||
The Tartube installer for 64-bit MS Windows already contains a copy of **playsound**, so there is no need to install it again.
|
||||
|
||||
7.26 Some icons not visible
|
||||
7.27 Some icons not visible
|
||||
---------------------------
|
||||
|
||||
*Q: Icons in the Videos tab are broken! They all look the same!*
|
||||
|
@ -2265,7 +2276,7 @@ A: Since v2.4, **Tartube** uses a set of custom icons, replacing system (stock)
|
|||
|
||||
If you want to restore stock icons, click **Edit > System preferences... > Windows > Main window** and then click **Replace stock icons with custom icons (in case stock icons are not visible)** to deselect it. Click the **OK** button to close the window, then restart **Tartube**.
|
||||
|
||||
7.27 Video thumbnails not visible
|
||||
7.28 Video thumbnails not visible
|
||||
---------------------------------
|
||||
|
||||
*Q: Tartube doesn't download video thumbnails any more! It used to work fine!*
|
||||
|
@ -2279,30 +2290,27 @@ If you have already downloaded a lot of **.webp** images, you can ask **Tartube*
|
|||
* Click **Operations > Tidy up files...**
|
||||
* In the dialogue window, click **Convert .webp files to .jpg using FFmpeg** to select it, then click the **OK** button
|
||||
|
||||
7.28 Video text not visible
|
||||
7.29 Video text not visible
|
||||
---------------------------
|
||||
|
||||
*Q: I can't see the text below each video!*
|
||||
|
||||
A: If the background colours in the Video Catalogue are getting in the way, you can change them: click **Edit > Sysem preferences... > Windows > Colours**.
|
||||
|
||||
7.29 Graphs not visible
|
||||
7.30 Graphs not visible
|
||||
-----------------------
|
||||
|
||||
*Q: My buddy installed Tartube, and he showed me some download history graphs. But when I looked for that on my computer, I couldn't find them!*
|
||||
|
||||
A: Tartube shows download statistics in a number of places, for example **Edit > System preferences... > Files > History**.
|
||||
|
||||
The graphs are created by `matplotlib <https://matplotlib.org/>`__, but none of the Tartube installers use it. If you want graphs, you have to install matplotlib yourself.
|
||||
The graphs are created by `matplotlib <https://matplotlib.org/>`__, but none of the Tartube installers include it (because it's quite a large download). If you want graphs, you have to install matplotlib yourself.
|
||||
|
||||
On Linux/BSD, use your system's software manager.
|
||||
|
||||
On MS Windows, do this:
|
||||
On MS Windows, click **Operations > Install matplotlib...**
|
||||
|
||||
- In Tartube's main menu, select **System > Open MSYS2 terminal...**
|
||||
- In the terminal window, type **pacman -S mingw-w64-x86_64-python-matplotlib**.
|
||||
|
||||
7.30 Tartube is not visible in the system tray
|
||||
7.31 Tartube is not visible in the system tray
|
||||
----------------------------------------------
|
||||
|
||||
*Q: Tartube is not visible in the system tray! There is just an empty space where the Tartube icon should be!*
|
||||
|
@ -2311,7 +2319,7 @@ A: This problem exists on certain Linux desktop environments (e.g. `Cinnamon <ht
|
|||
|
||||
Other desktop environments (e.g. `MATE <https://mate-desktop.org/>`__) display the **Tartube** icon correctly.
|
||||
|
||||
7.31 Tartube is not portable
|
||||
7.32 Tartube is not portable
|
||||
----------------------------
|
||||
|
||||
*Q: I want to install Tartube on a USB stick. How do I make Tartube portable?*
|
||||
|
@ -2328,7 +2336,7 @@ However, you can create an empty **settings.json** file in the source code direc
|
|||
|
||||
You can see both locations by clicking **Edit > System preferences... > Files > Config**.
|
||||
|
||||
7.32 Run out of disk space
|
||||
7.33 Run out of disk space
|
||||
--------------------------
|
||||
|
||||
*Q: When I try to download videos, Tartube refuses, complaining "You have only X / Y Mb remaining on your device". But I'm using an external hard drive with over a trillion terabytes of empty space!*
|
||||
|
@ -2339,14 +2347,14 @@ This seems to be an issue with the virtualisation software itself (we have confi
|
|||
|
||||
The only thing that can be done is to disable the checks and warnings altogether. Click **Edit > System preferences > Files > Device**, and deselect both **Warn user if disk space is less than** and **Halt downloads if disk space is less than**.
|
||||
|
||||
7.33 No puedo hablar inglés
|
||||
7.34 No puedo hablar inglés
|
||||
---------------------------
|
||||
|
||||
*Q: ¡No puedo usar YouTube porque no hablo inglés!*
|
||||
|
||||
A: Necesitamos más traductores.
|
||||
|
||||
If you would like to contribute a translation of this project, please read `this document <docs/translate.html>`__.
|
||||
If you would like to contribute a translation of this project, please read `this document <docs/translate.rst>`__.
|
||||
|
||||
8 Contributing
|
||||
==============
|
||||
|
|
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 4.7 KiB |
|
@ -1,4 +1,4 @@
|
|||
# Tartube v2.3.450 installer script for MS Windows
|
||||
# Tartube v2.3.471 installer script for MS Windows
|
||||
#
|
||||
# Copyright (C) 2019-2022 A S Lewis
|
||||
#
|
||||
|
@ -294,7 +294,7 @@
|
|||
|
||||
;Name and file
|
||||
Name "Tartube"
|
||||
OutFile "install-tartube-2.3.450-64bit.exe"
|
||||
OutFile "install-tartube-2.3.471-64bit.exe"
|
||||
|
||||
;Default installation folder
|
||||
InstallDir "$LOCALAPPDATA\Tartube"
|
||||
|
@ -397,7 +397,7 @@ Section "Tartube" SecClient
|
|||
# "Publisher" "A S Lewis"
|
||||
# WriteRegStr HKLM \
|
||||
# "Software\Microsoft\Windows\CurrentVersion\Uninstall\Tartube" \
|
||||
# "DisplayVersion" "2.3.450"
|
||||
# "DisplayVersion" "2.3.471"
|
||||
|
||||
# Create uninstaller
|
||||
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||
|
|
|
@ -42,8 +42,8 @@ import mainapp
|
|||
|
||||
# 'Global' variables
|
||||
__packagename__ = 'tartube'
|
||||
__version__ = '2.3.450'
|
||||
__date__ = '24 Mar 2022'
|
||||
__version__ = '2.3.471'
|
||||
__date__ = '29 Mar 2022'
|
||||
__copyright__ = 'Copyright \xa9 2019-2022 A S Lewis'
|
||||
__license__ = """
|
||||
Copyright \xa9 2019-2022 A S Lewis.
|
||||
|
|
|
@ -42,8 +42,8 @@ import mainapp
|
|||
|
||||
# 'Global' variables
|
||||
__packagename__ = 'tartube'
|
||||
__version__ = '2.3.450'
|
||||
__date__ = '24 Mar 2022'
|
||||
__version__ = '2.3.471'
|
||||
__date__ = '29 Mar 2022'
|
||||
__copyright__ = 'Copyright \xa9 2019-2022 A S Lewis'
|
||||
__license__ = """
|
||||
Copyright \xa9 2019-2022 A S Lewis.
|
||||
|
|
|
@ -42,8 +42,8 @@ import mainapp
|
|||
|
||||
# 'Global' variables
|
||||
__packagename__ = 'tartube'
|
||||
__version__ = '2.3.450'
|
||||
__date__ = '24 Mar 2022'
|
||||
__version__ = '2.3.471'
|
||||
__date__ = '29 Mar 2022'
|
||||
__copyright__ = 'Copyright \xa9 2019-2022 A S Lewis'
|
||||
__license__ = """
|
||||
Copyright \xa9 2019-2022 A S Lewis.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.TH man 1 "20 Mar 2022" "2.3.450" "tartube man page"
|
||||
.TH man 1 "29 Mar 2022" "2.3.471" "tartube man page"
|
||||
.SH NAME
|
||||
tartube \- GUI front-end for youtube-dl
|
||||
.SH SYNOPSIS
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[Desktop Entry]
|
||||
Name=Tartube
|
||||
Version=2.3.450
|
||||
Version=2.3.471
|
||||
Exec=tartube
|
||||
Icon=tartube
|
||||
Type=Application
|
||||
|
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 58 KiB |
2
setup.py
|
@ -185,7 +185,7 @@ for path in glob.glob('sounds/*'):
|
|||
# Setup
|
||||
setuptools.setup(
|
||||
name='tartube',
|
||||
version='2.3.450',
|
||||
version='2.3.471',
|
||||
description='GUI front-end for youtube-dl',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/plain',
|
||||
|
|
1089
tartube/config.py
2100
tartube/downloads.py
|
@ -808,6 +808,12 @@ THUMB_ICON_DICT = {
|
|||
'thumb_default_medium': 'thumb_default_medium.png',
|
||||
'thumb_default_large': 'thumb_default_large.png',
|
||||
'thumb_default_enormous': 'thumb_default_enormous.png',
|
||||
|
||||
'thumb_block_tiny': 'thumb_block_tiny.png',
|
||||
'thumb_block_small': 'thumb_block_small.png',
|
||||
'thumb_block_medium': 'thumb_block_medium.png',
|
||||
'thumb_block_large': 'thumb_block_large.png',
|
||||
'thumb_block_enormous': 'thumb_block_enormous.png',
|
||||
}
|
||||
|
||||
EXTERNAL_ICON_DICT = {
|
||||
|
|
181
tartube/info.py
|
@ -34,6 +34,7 @@ import requests
|
|||
import signal
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
||||
# Import our modules
|
||||
|
@ -110,22 +111,22 @@ class InfoManager(threading.Thread):
|
|||
# self.info_type is 'test_ytdl')
|
||||
self.video_obj = media_data_obj
|
||||
|
||||
# This object reads from the child process STDOUT and STDERR in an
|
||||
# asynchronous way
|
||||
# Standard Python synchronised queue classes
|
||||
self.stdout_queue = queue.Queue()
|
||||
self.stderr_queue = queue.Queue()
|
||||
# The downloads.PipeReader objects created to handle reading from the
|
||||
# pipes
|
||||
self.stdout_reader = downloads.PipeReader(self.stdout_queue)
|
||||
self.stderr_reader = downloads.PipeReader(self.stderr_queue)
|
||||
|
||||
# The child process created by self.create_child_process()
|
||||
# The child process created by self.run()
|
||||
self.child_process = None
|
||||
|
||||
# Read from the child process STDOUT (i.e. self.child_process.stdout)
|
||||
# and STDERR (i.e. self.child_process.stderr) in an asynchronous way
|
||||
# by polling this queue.PriorityQueue object
|
||||
self.queue = queue.PriorityQueue()
|
||||
self.stdout_reader = downloads.PipeReader(self.queue, 'stdout')
|
||||
self.stderr_reader = downloads.PipeReader(self.queue, 'stderr')
|
||||
|
||||
|
||||
# IV list - other
|
||||
# ---------------
|
||||
# The time (in seconds) between iterations of the loop in self.run()
|
||||
self.sleep_time = 0.1
|
||||
|
||||
# The type of information to fetch: 'formats' for a list of video
|
||||
# formats, 'subs' for a list of subtitles, 'test_ytdl' to test
|
||||
# youtube-dl with specified options, or 'version' to check for a new
|
||||
|
@ -219,7 +220,7 @@ class InfoManager(threading.Thread):
|
|||
if os.name != 'nt':
|
||||
ytdl_path = re.sub('^\~', os.path.expanduser('~'), ytdl_path)
|
||||
|
||||
# Prepare the system command
|
||||
# Prepare the system command...
|
||||
if self.info_type == 'formats':
|
||||
|
||||
cmd_list = [
|
||||
|
@ -267,80 +268,31 @@ class InfoManager(threading.Thread):
|
|||
|
||||
cmd_list.append(self.url_string)
|
||||
|
||||
# Create the new child process
|
||||
self.create_child_process(cmd_list)
|
||||
|
||||
# Show the system command in the Output tab
|
||||
# ...display it in the Output tab (if required)
|
||||
space = ' '
|
||||
self.app_obj.main_win_obj.output_tab_write_system_cmd(
|
||||
1,
|
||||
space.join(cmd_list),
|
||||
)
|
||||
|
||||
# So that we can read from the child process STDOUT and STDERR, attach
|
||||
# a file descriptor to the PipeReader objects
|
||||
# Create a new child process using that command...
|
||||
self.create_child_process(cmd_list)
|
||||
# ...and set up the PipeReader objects to read from the child process
|
||||
# STDOUT and STDERR
|
||||
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,
|
||||
)
|
||||
self.stdout_reader.attach_fh(self.child_process.stdout)
|
||||
self.stderr_reader.attach_fh(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():
|
||||
# Pause a moment between each iteration of the loop (we don't want
|
||||
# to hog system resources)
|
||||
time.sleep(self.sleep_time)
|
||||
|
||||
stdout = self.stdout_queue.get_nowait().rstrip()
|
||||
if stdout:
|
||||
|
||||
stdout = stdout.decode(utils.get_encoding(), 'replace')
|
||||
|
||||
self.output_list.append(stdout)
|
||||
self.stdout_list.append(stdout)
|
||||
|
||||
# Show command line output in the Output tab
|
||||
self.app_obj.main_win_obj.output_tab_write_stdout(
|
||||
1,
|
||||
stdout,
|
||||
)
|
||||
|
||||
# The child process has finished
|
||||
while not self.stderr_queue.empty():
|
||||
|
||||
# Read from the child process STDERR queue (we don't need to read
|
||||
# it in real time), and convert into unicode for python's
|
||||
# convenience
|
||||
stderr = self.stderr_queue.get_nowait().rstrip()
|
||||
if stderr:
|
||||
|
||||
stderr = stderr.decode(utils.get_encoding(), 'replace')
|
||||
|
||||
# While testing youtube-dl, don't treat anything as an error
|
||||
if self.info_type == 'test_ytdl':
|
||||
self.stdout_list.append(stderr)
|
||||
|
||||
# When fetching subtitles from a video that has none, don't
|
||||
# treat youtube-dl WARNING: messages as something that
|
||||
# makes the info operation fail
|
||||
elif self.info_type == 'subs':
|
||||
|
||||
if not re.search('^WARNING\:', stderr):
|
||||
self.stderr_list.append(stderr)
|
||||
|
||||
# When fetching formats, recognise all warnings as errors
|
||||
else:
|
||||
self.stderr_list.append(stderr)
|
||||
|
||||
# Show command line output in the Output tab
|
||||
self.app_obj.main_win_obj.output_tab_write_stderr(
|
||||
1,
|
||||
stderr,
|
||||
)
|
||||
# Read from the child process STDOUT and STDERR, in the correct
|
||||
# order, until there is nothing left to read
|
||||
while self.read_child_process():
|
||||
pass
|
||||
|
||||
# (Generate our own error messages for debugging purposes, in certain
|
||||
# situations)
|
||||
|
@ -558,6 +510,85 @@ class InfoManager(threading.Thread):
|
|||
return self.child_process.poll() is None
|
||||
|
||||
|
||||
def read_child_process(self):
|
||||
|
||||
"""Called by self.run().
|
||||
|
||||
Reads from the child process STDOUT and STDERR, in the correct order.
|
||||
|
||||
Return values:
|
||||
|
||||
True if either STDOUT or STDERR were read, None if both queues were
|
||||
empty
|
||||
|
||||
"""
|
||||
|
||||
# mini_list is in the form [time, pipe_type, data]
|
||||
try:
|
||||
mini_list = self.queue.get_nowait()
|
||||
|
||||
except:
|
||||
# Nothing left to read
|
||||
return None
|
||||
|
||||
# Failsafe check
|
||||
if not mini_list \
|
||||
or (mini_list[1] != 'stdout' and mini_list[1] != 'stderr'):
|
||||
|
||||
# Just in case...
|
||||
self.app_obj.system_error(
|
||||
601,
|
||||
'Malformed STDOUT or STDERR data',
|
||||
)
|
||||
|
||||
# STDOUT or STDERR has been read
|
||||
data = mini_list[2].rstrip()
|
||||
# On MS Windows we use cp1252, so that Tartube can communicate with the
|
||||
# Windows console
|
||||
data = data.decode(utils.get_encoding(), 'replace')
|
||||
|
||||
# STDOUT
|
||||
if mini_list[1] == 'stdout':
|
||||
|
||||
self.output_list.append(data)
|
||||
self.stdout_list.append(data)
|
||||
|
||||
# Show command line output in the Output tab
|
||||
self.app_obj.main_win_obj.output_tab_write_stdout(
|
||||
1,
|
||||
data,
|
||||
)
|
||||
|
||||
# STDERR
|
||||
else:
|
||||
|
||||
# While testing youtube-dl, don't treat anything as an error
|
||||
if self.info_type == 'test_ytdl':
|
||||
self.stdout_list.append(data)
|
||||
|
||||
# When fetching subtitles from a video that has none, don't treat
|
||||
# youtube-dl WARNING: messages as something that makes the info
|
||||
# operation fail
|
||||
elif self.info_type == 'subs':
|
||||
|
||||
if not re.search('^WARNING\:', data):
|
||||
self.stderr_list.append(data)
|
||||
|
||||
# When fetching formats, recognise all warnings as errors
|
||||
else:
|
||||
self.stderr_list.append(data)
|
||||
|
||||
# Show command line output in the Output tab
|
||||
self.app_obj.main_win_obj.output_tab_write_stderr(
|
||||
1,
|
||||
data,
|
||||
)
|
||||
|
||||
# Either (or both) of STDOUT and STDERR were non-empty
|
||||
self.queue.task_done()
|
||||
return True
|
||||
|
||||
|
||||
def stop_info_operation(self):
|
||||
|
||||
"""Called by mainapp.TartubeApp.do_shutdown(), .stop_continue(),
|
||||
|
|
|
@ -1642,22 +1642,19 @@ class TartubeApp(Gtk.Application):
|
|||
# (This does not affect real downloads, in which such videos are never
|
||||
# added to the download list)
|
||||
self.operation_sim_shortcut_flag = True
|
||||
|
||||
# Flag set to True if, during download operations (of all kinds), if a
|
||||
# job stalls, it should be restarted
|
||||
self.operation_auto_restart_flag = False
|
||||
# How many minutes of inactivity before restarting the job (minimum
|
||||
# value is 1, ignored if self.operation_sim_shortcut_flag is not set)
|
||||
self.operation_auto_restart_time = 10
|
||||
# Flag set to True if a job that experiences a network error should be
|
||||
# treated as a stalled download, and restarted; False if a network
|
||||
# error should terminate the job, as usual
|
||||
# Note that network errors are not easy to detect from youtube-dl's
|
||||
# output, so as of v2.3.012, detection may not work for every error
|
||||
self.operation_auto_restart_network_flag = False
|
||||
# When youtube-dl reports network problems, how many minutes should
|
||||
# Tartube wait before restarting the job (minimum value is 1,
|
||||
# ignore if self.operation_auto_restart_flag is not set)
|
||||
self.operation_auto_restart_time = 2
|
||||
# The maximum number of times to restart a stalled job. If 0, no
|
||||
# maximum applies. Ignored ignored if
|
||||
# self.operation_sim_shortcut_flag is not set)
|
||||
self.operation_auto_restart_max = 3
|
||||
self.operation_auto_restart_max = 5
|
||||
|
||||
# How to notify the user at the end of each download/update/refresh
|
||||
# operation: 'dialogue' to use a dialogue window, 'desktop' to use a
|
||||
# desktop notification, or 'default' to do neither
|
||||
|
@ -1751,10 +1748,22 @@ class TartubeApp(Gtk.Application):
|
|||
# are marked as missing. Ignored if self.track_missing_videos_flag or
|
||||
# self.track_missing_time_flag is False
|
||||
self.track_missing_time_days = 14
|
||||
# Flag set to True if, during a real (not simulated) download,
|
||||
# youtube-dl error/warning messages without a video ID (which is, at
|
||||
# the time of writing, most of them) should be assigned to the most
|
||||
# probable media.Video object; False if anonymous messages should be
|
||||
# assigned to the parent channel/playlist/folder instead
|
||||
# (Assigning anonymous messages to videos is not an exact science, but
|
||||
# should work well enough for most users)
|
||||
self.auto_assign_errors_warnings_flag = True
|
||||
# Flag set to True if, during a download operation, videos marked as
|
||||
# being censored, age-restricted or otherwise unavailable for
|
||||
# download should be added to the database
|
||||
self.add_blocked_videos_flag = True
|
||||
# Flag set to True if Tartube should retrieve the playlist ID from each
|
||||
# checked/downloaded video, and store it in the parent channel/
|
||||
# playlist. (The user can use the collected IDs to get a list of
|
||||
# playlists associated with a channel)
|
||||
# checked/downloaded video's metadata, and store it in the parent
|
||||
# channel/playlist. (The user can use the collected IDs to get a list
|
||||
# of playlists associated with a channel)
|
||||
self.store_playlist_id_flag = True
|
||||
|
||||
# Flag set to True if a list of timestamps should be extracted from a
|
||||
|
@ -2405,9 +2414,27 @@ class TartubeApp(Gtk.Application):
|
|||
ytdl_test_menu_action.connect('activate', self.on_menu_test_ytdl)
|
||||
self.add_action(ytdl_test_menu_action)
|
||||
|
||||
ffmpeg_menu_action = Gio.SimpleAction.new('install_ffmpeg_menu', None)
|
||||
ffmpeg_menu_action.connect('activate', self.on_menu_install_ffmpeg)
|
||||
self.add_action(ffmpeg_menu_action)
|
||||
if os.name == 'nt':
|
||||
|
||||
ffmpeg_menu_action = Gio.SimpleAction.new(
|
||||
'install_ffmpeg_menu',
|
||||
None,
|
||||
)
|
||||
ffmpeg_menu_action.connect(
|
||||
'activate',
|
||||
self.on_menu_install_ffmpeg,
|
||||
)
|
||||
self.add_action(ffmpeg_menu_action)
|
||||
|
||||
matplotlib_menu_action = Gio.SimpleAction.new(
|
||||
'install_matplotlib_menu',
|
||||
None,
|
||||
)
|
||||
matplotlib_menu_action.connect(
|
||||
'activate',
|
||||
self.on_menu_install_matplotlib,
|
||||
)
|
||||
self.add_action(matplotlib_menu_action)
|
||||
|
||||
tidy_up_menu_action = Gio.SimpleAction.new('tidy_up_menu', None)
|
||||
tidy_up_menu_action.connect('activate', self.on_menu_tidy_up)
|
||||
|
@ -3404,13 +3431,22 @@ class TartubeApp(Gtk.Application):
|
|||
|
||||
if not self.disable_load_save_flag:
|
||||
|
||||
# If scheduled download operation(s) are scheduled to occur on
|
||||
# startup, then prepare them to start
|
||||
# (For aesthetic reasons, that will be a few seconds from now)
|
||||
# If scheduled download operation(s) are scheduled to occur on or
|
||||
# some time after startup, then prepare them to start
|
||||
for scheduled_obj in self.scheduled_list:
|
||||
|
||||
if scheduled_obj.start_mode == 'start':
|
||||
# (For aesthetic reasons, the scheduled download does not
|
||||
# start immediately, but a few seconds from now)
|
||||
scheduled_obj.set_only_time(time.time())
|
||||
|
||||
elif scheduled_obj.start_mode == 'start_after':
|
||||
|
||||
wait_time = scheduled_obj.wait_value \
|
||||
* formats.TIME_METRIC_DICT[scheduled_obj.wait_unit]
|
||||
|
||||
scheduled_obj.set_only_time(time.time() + wait_time)
|
||||
|
||||
# Part 13 - Any debug stuff can go here
|
||||
# -------------------------------------
|
||||
pass
|
||||
|
@ -3582,6 +3618,8 @@ class TartubeApp(Gtk.Application):
|
|||
300-399: downloads.py (in use: 301-310)
|
||||
400-499: config.py (in use: 401-405)
|
||||
500-599: utils.py (in use: 501-503)
|
||||
600-699: info.py (in use: 601)
|
||||
700-799: updates.py (in use: 701-702)
|
||||
|
||||
"""
|
||||
|
||||
|
@ -4204,17 +4242,20 @@ class TartubeApp(Gtk.Application):
|
|||
if version >= 1004003: # v1.4.003
|
||||
self.operation_sim_shortcut_flag \
|
||||
= json_dict['operation_sim_shortcut_flag']
|
||||
|
||||
if version >= 2002112: # v2.2.112
|
||||
self.operation_auto_restart_flag \
|
||||
= json_dict['operation_auto_restart_flag']
|
||||
self.operation_auto_restart_time \
|
||||
= json_dict['operation_auto_restart_time']
|
||||
if version >= 2003012: # v2.3.012
|
||||
self.operation_auto_restart_network_flag \
|
||||
= json_dict['operation_auto_restart_network_flag']
|
||||
# Removed v2.3.461
|
||||
# if version >= 2003012: # v2.3.012
|
||||
# self.operation_auto_restart_network_flag \
|
||||
# = json_dict['operation_auto_restart_network_flag']
|
||||
if version >= 2002169: # v2.2.169
|
||||
self.operation_auto_restart_max \
|
||||
= json_dict['operation_auto_restart_max']
|
||||
|
||||
# # Removed v1.3.028
|
||||
# self.operation_dialogue_flag = json_dict['operation_dialogue_flag']
|
||||
if version >= 1003028: # v1.3.028
|
||||
|
@ -4264,6 +4305,9 @@ class TartubeApp(Gtk.Application):
|
|||
= json_dict['track_missing_time_flag']
|
||||
self.track_missing_time_days \
|
||||
= json_dict['track_missing_time_days']
|
||||
if version >= 2003464: # v2.3.464
|
||||
self.add_blocked_videos_flag \
|
||||
= json_dict['add_blocked_videos_flag']
|
||||
if version >= 2003382: # v2.3.382
|
||||
self.store_playlist_id_flag \
|
||||
= json_dict['store_playlist_id_flag']
|
||||
|
@ -4687,17 +4731,17 @@ class TartubeApp(Gtk.Application):
|
|||
"""
|
||||
|
||||
# Set up variables whose values are the default values of the old IVs
|
||||
scheduled_check_mode = 'none'
|
||||
scheduled_check_mode = 'disabled'
|
||||
scheduled_check_wait_value = 2
|
||||
scheduled_check_wait_unit = 'hours'
|
||||
scheduled_check_last_time = 0
|
||||
|
||||
scheduled_dl_mode = 'none'
|
||||
scheduled_dl_mode = 'disabled'
|
||||
scheduled_dl_wait_value = 2
|
||||
scheduled_dl_wait_unit = 'hours'
|
||||
scheduled_dl_last_time = 0
|
||||
|
||||
scheduled_custom_mode = 'none'
|
||||
scheduled_custom_mode = 'disabled'
|
||||
scheduled_custom_wait_value = 2
|
||||
scheduled_custom_wait_unit = 'hours'
|
||||
scheduled_custom_last_time = 0
|
||||
|
@ -4742,8 +4786,24 @@ class TartubeApp(Gtk.Application):
|
|||
scheduled_custom_last_time \
|
||||
= json_dict['scheduled_custom_last_time']
|
||||
|
||||
# v2.3.467, changes to the values of some media.Scheduled IVs
|
||||
if scheduled_check_mode == 'none':
|
||||
scheduled_check_mode = 'disabled'
|
||||
elif scheduled_check_mode == 'scheduled':
|
||||
scheduled_check_mode = 'repeat'
|
||||
|
||||
if scheduled_dl_mode == 'none':
|
||||
scheduled_dl_mode = 'disabled'
|
||||
elif scheduled_dl_mode == 'scheduled':
|
||||
scheduled_dl_mode = 'repeat'
|
||||
|
||||
if scheduled_custom_mode == 'none':
|
||||
scheduled_custom_mode = 'disabled'
|
||||
elif scheduled_custom_mode == 'scheduled':
|
||||
scheduled_custom_mode = 'repeat'
|
||||
|
||||
# Finally create new media.Scheduled objects
|
||||
if scheduled_check_mode != 'none':
|
||||
if scheduled_check_mode != 'disabled':
|
||||
|
||||
new_obj = media.Scheduled(
|
||||
'default_check',
|
||||
|
@ -4758,7 +4818,7 @@ class TartubeApp(Gtk.Application):
|
|||
|
||||
self.scheduled_list.append(new_obj)
|
||||
|
||||
if scheduled_dl_mode != 'none':
|
||||
if scheduled_dl_mode != 'disabled':
|
||||
|
||||
new_obj = media.Scheduled(
|
||||
'default_download',
|
||||
|
@ -4773,7 +4833,7 @@ class TartubeApp(Gtk.Application):
|
|||
|
||||
self.scheduled_list.append(new_obj)
|
||||
|
||||
if scheduled_custom_mode != 'none':
|
||||
if scheduled_custom_mode != 'disabled':
|
||||
|
||||
new_obj = media.Scheduled(
|
||||
'default_custom',
|
||||
|
@ -5065,11 +5125,11 @@ class TartubeApp(Gtk.Application):
|
|||
'operation_auto_update_flag': self.operation_auto_update_flag,
|
||||
'operation_save_flag': self.operation_save_flag,
|
||||
'operation_sim_shortcut_flag': self.operation_sim_shortcut_flag,
|
||||
|
||||
'operation_auto_restart_flag': self.operation_auto_restart_flag,
|
||||
'operation_auto_restart_time': self.operation_auto_restart_time,
|
||||
'operation_auto_restart_network_flag': \
|
||||
self.operation_auto_restart_network_flag,
|
||||
'operation_auto_restart_max': self.operation_auto_restart_max,
|
||||
|
||||
'operation_dialogue_mode': self.operation_dialogue_mode,
|
||||
'operation_convert_mode': self.operation_convert_mode,
|
||||
'use_module_moviepy_flag': self.use_module_moviepy_flag,
|
||||
|
@ -5090,6 +5150,7 @@ class TartubeApp(Gtk.Application):
|
|||
'track_missing_videos_flag': self.track_missing_videos_flag,
|
||||
'track_missing_time_flag': self.track_missing_time_flag,
|
||||
'track_missing_time_days': self.track_missing_time_days,
|
||||
'add_blocked_videos_flag': self.add_blocked_videos_flag,
|
||||
'store_playlist_id_flag': self.store_playlist_id_flag,
|
||||
|
||||
'video_timestamps_extract_json_flag': \
|
||||
|
@ -6803,8 +6864,26 @@ class TartubeApp(Gtk.Application):
|
|||
custom_dl_obj.ignore_if_no_subs_flag = False
|
||||
custom_dl_obj.dl_if_subs_list = []
|
||||
|
||||
if version < 2003464: # v2.3.464
|
||||
|
||||
# --- Do this last, or call to .check_integrity_db() fails -----------
|
||||
# This version adds a new IV to media.Video objects
|
||||
for media_data_obj in self.media_reg_dict.values():
|
||||
if isinstance(media_data_obj, media.Video):
|
||||
media_data_obj.block_flag = False
|
||||
|
||||
if version < 2003470: # v2.3.470
|
||||
|
||||
# This version modifies IVs in media.Scheduled objects
|
||||
for scheduled_obj in self.scheduled_list:
|
||||
scheduled_obj.timetable_list = []
|
||||
scheduled_obj.timetable_window = 300
|
||||
if scheduled_obj.start_mode == 'scheduled':
|
||||
scheduled_obj.start_mode = 'repeat'
|
||||
if scheduled_obj.start_mode == 'none':
|
||||
scheduled_obj.start_mode = 'disabled'
|
||||
|
||||
|
||||
# --- Do this last, or the call to .check_integrity_db() fails -------
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
if version < 2002115: # v2.2.115
|
||||
|
@ -10285,7 +10364,7 @@ class TartubeApp(Gtk.Application):
|
|||
1. Install youtube-dl (or a fork of it), or update it to its most
|
||||
recent version.
|
||||
|
||||
2. Install FFmpeg (on MS Windows only)
|
||||
2. Install FFmpeg or matplotlib(on MS Windows only)
|
||||
|
||||
Creates a new updates.UpdateManager object to handle the update
|
||||
operation. When the operation is complete,
|
||||
|
@ -10293,8 +10372,8 @@ class TartubeApp(Gtk.Application):
|
|||
|
||||
Args:
|
||||
|
||||
update_type (str): 'ffmpeg' to install FFmpeg, or 'ytdl' to
|
||||
install/update youtube-dl
|
||||
update_type (str): 'ffmpeg' to install FFmpeg, 'matplotlib' to
|
||||
install matplotlib, or 'ytdl' to install/update youtube-dl
|
||||
|
||||
"""
|
||||
|
||||
|
@ -10344,6 +10423,14 @@ class TartubeApp(Gtk.Application):
|
|||
+ ' system',
|
||||
)
|
||||
|
||||
elif update_type == 'matplotlib' and os.name != 'nt':
|
||||
# The same applies to matplotlib
|
||||
return self.system_error(
|
||||
999,
|
||||
'Update operation cannot install matplotlib on your' \
|
||||
+ ' operating system',
|
||||
)
|
||||
|
||||
# During an update operation, certain widgets are modified and/or
|
||||
# desensitised
|
||||
self.main_win_obj.sensitise_check_dl_buttons(False, update_type)
|
||||
|
@ -10379,7 +10466,8 @@ class TartubeApp(Gtk.Application):
|
|||
1. Install youtube-dl (or a fork of it), or update it to its most
|
||||
recent version.
|
||||
|
||||
2. Install FFmpeg (on MS Windows only)
|
||||
2. Install FFmpeg (on MS Windows only; at the moment, the wizard
|
||||
window does not try to install matplotlib)
|
||||
|
||||
Creates a new updates.UpdateManager object to handle the update
|
||||
operation. When the operation is complete,
|
||||
|
@ -10427,8 +10515,9 @@ class TartubeApp(Gtk.Application):
|
|||
|
||||
def update_manager_halt_timer(self):
|
||||
|
||||
"""Called by updates.UpdateManager.install_ffmpeg() or
|
||||
.install_ytdl() when those functions have finished.
|
||||
"""Called by updates.UpdateManager.install_ffmpeg(),
|
||||
.install_matplotlib or .install_ytdl() when those functions have
|
||||
finished.
|
||||
|
||||
During an update operation, a GObject timer was running. Let it
|
||||
continue running for a few seconds more.
|
||||
|
@ -10447,6 +10536,8 @@ class TartubeApp(Gtk.Application):
|
|||
widgets.
|
||||
"""
|
||||
|
||||
global HAVE_MATPLOTLIB_FLAG
|
||||
|
||||
# Import IVs from updates.UpdateManager, before it is destroyed
|
||||
update_type = self.update_manager_obj.update_type
|
||||
wiz_win_obj = self.update_manager_obj.wiz_win_obj
|
||||
|
@ -10474,9 +10565,13 @@ class TartubeApp(Gtk.Application):
|
|||
if os.name != 'nt' and not wiz_win_obj:
|
||||
self.auto_detect_paths()
|
||||
|
||||
# If matplotlib is successfully installed, update the setting
|
||||
if update_type == 'matplotlib' and success_flag:
|
||||
HAVE_MATPLOTLIB_FLAG = True
|
||||
|
||||
# After an update operation, save files, if allowed
|
||||
if not wiz_win_obj:
|
||||
|
||||
# After an update operation, save files, if allowed
|
||||
if self.operation_save_flag:
|
||||
self.save_config()
|
||||
self.save_db()
|
||||
|
@ -10490,7 +10585,7 @@ class TartubeApp(Gtk.Application):
|
|||
|
||||
# Then show a dialogue window/desktop notification, if allowed (and if
|
||||
# a download operation is not waiting to start)
|
||||
if update_type == 'ffmpeg':
|
||||
if update_type == 'ffmpeg' or update_type == 'matplotlib':
|
||||
|
||||
if not success_flag:
|
||||
msg = _('Installation failed')
|
||||
|
@ -19652,19 +19747,8 @@ class TartubeApp(Gtk.Application):
|
|||
|
||||
for scheduled_obj in self.scheduled_list:
|
||||
|
||||
wait_time = scheduled_obj.wait_value \
|
||||
+ formats.TIME_METRIC_DICT[scheduled_obj.wait_unit]
|
||||
|
||||
if (
|
||||
(
|
||||
scheduled_obj.start_mode == 'scheduled' \
|
||||
and scheduled_obj.last_time + wait_time < time.time()
|
||||
) or (
|
||||
scheduled_obj.start_mode == 'start' \
|
||||
and scheduled_obj.only_time > 0 \
|
||||
and scheduled_obj.only_time < time.time()
|
||||
)
|
||||
) and (
|
||||
if scheduled_obj.check_start() \
|
||||
and (
|
||||
not self.download_manager_obj \
|
||||
or scheduled_obj.join_mode != 'skip'
|
||||
):
|
||||
|
@ -19688,8 +19772,10 @@ class TartubeApp(Gtk.Application):
|
|||
|
||||
break
|
||||
|
||||
# 'start' should be done before 'scheduled'
|
||||
if scheduled_obj.start_mode == 'start':
|
||||
# 'start'/'start_after' should be done before 'repeat' and
|
||||
# 'timetable'
|
||||
if scheduled_obj.start_mode == 'start' \
|
||||
or scheduled_obj.start_mode == 'start_after':
|
||||
first_list.append(scheduled_obj)
|
||||
else:
|
||||
next_list.append(scheduled_obj)
|
||||
|
@ -22335,6 +22421,23 @@ class TartubeApp(Gtk.Application):
|
|||
self.update_manager_start('ffmpeg')
|
||||
|
||||
|
||||
def on_menu_install_matplotlib(self, action, par):
|
||||
|
||||
"""Called from a callback in self.do_startup().
|
||||
|
||||
Start an update operation to install matplotlib (on MS Windows only).
|
||||
|
||||
Args:
|
||||
|
||||
action (Gio.SimpleAction): Object generated by Gio
|
||||
|
||||
par (None): Ignored
|
||||
|
||||
"""
|
||||
|
||||
self.update_manager_start('matplotlib')
|
||||
|
||||
|
||||
def on_menu_live_preferences(self, action, par):
|
||||
|
||||
"""Called from a callback in self.do_startup().
|
||||
|
@ -22948,6 +23051,14 @@ class TartubeApp(Gtk.Application):
|
|||
# Set accessors
|
||||
|
||||
|
||||
def set_add_blocked_videos_flag(self, flag):
|
||||
|
||||
if not flag:
|
||||
self.add_blocked_videos_flag = False
|
||||
else:
|
||||
self.add_blocked_videos_flag = True
|
||||
|
||||
|
||||
def set_allow_ytdl_archive_flag(self, flag):
|
||||
|
||||
if not flag:
|
||||
|
@ -23026,6 +23137,14 @@ class TartubeApp(Gtk.Application):
|
|||
del self.media_reg_auto_alarm_dict[video_obj.dbid]
|
||||
|
||||
|
||||
def set_auto_assign_errors_warnings_flag(self, flag):
|
||||
|
||||
if not flag:
|
||||
self.auto_assign_errors_warnings_flag = False
|
||||
else:
|
||||
self.auto_assign_errors_warnings_flag = True
|
||||
|
||||
|
||||
def set_auto_clone_options_flag(self, flag):
|
||||
|
||||
if not flag:
|
||||
|
@ -24019,14 +24138,6 @@ class TartubeApp(Gtk.Application):
|
|||
self.operation_auto_restart_max = value
|
||||
|
||||
|
||||
def set_operation_auto_restart_network_flag(self, flag):
|
||||
|
||||
if not flag:
|
||||
self.operation_auto_restart_network_flag = False
|
||||
else:
|
||||
self.operation_auto_restart_network_flag = True
|
||||
|
||||
|
||||
def set_operation_auto_restart_time(self, value):
|
||||
|
||||
self.operation_auto_restart_time = value
|
||||
|
|
|
@ -137,6 +137,8 @@ class MainWin(Gtk.ApplicationWindow):
|
|||
self.update_ytdl_menu_item = None # Gtk.MenuItem
|
||||
self.test_ytdl_menu_item = None # Gtk.MenuItem
|
||||
self.install_ffmpeg_menu_item = None # Gtk.MenuItem
|
||||
self.install_matplotlib_menu_item = None
|
||||
# Gtk.MenuItem
|
||||
self.tidy_up_menu_item = None # Gtk.MenuItem
|
||||
self.stop_operation_menu_item = None # Gtk.MenuItem
|
||||
self.stop_soon_menu_item = None # Gtk.MenuItem
|
||||
|
@ -1427,16 +1429,26 @@ class MainWin(Gtk.ApplicationWindow):
|
|||
# Separator
|
||||
ops_sub_menu.append(Gtk.SeparatorMenuItem())
|
||||
|
||||
self.install_ffmpeg_menu_item = Gtk.MenuItem.new_with_mnemonic(
|
||||
_('_Install FFmpeg...'),
|
||||
)
|
||||
ops_sub_menu.append(self.install_ffmpeg_menu_item)
|
||||
self.install_ffmpeg_menu_item.set_action_name(
|
||||
'app.install_ffmpeg_menu',
|
||||
)
|
||||
if os.name == 'nt':
|
||||
|
||||
# Separator
|
||||
ops_sub_menu.append(Gtk.SeparatorMenuItem())
|
||||
self.install_ffmpeg_menu_item = Gtk.MenuItem.new_with_mnemonic(
|
||||
_('_Install FFmpeg...'),
|
||||
)
|
||||
ops_sub_menu.append(self.install_ffmpeg_menu_item)
|
||||
self.install_ffmpeg_menu_item.set_action_name(
|
||||
'app.install_ffmpeg_menu',
|
||||
)
|
||||
|
||||
self.install_matplotlib_menu_item = Gtk.MenuItem.new_with_mnemonic(
|
||||
_('_Install matplotlib...'),
|
||||
)
|
||||
ops_sub_menu.append(self.install_matplotlib_menu_item)
|
||||
self.install_matplotlib_menu_item.set_action_name(
|
||||
'app.install_matplotlib_menu',
|
||||
)
|
||||
|
||||
# Separator
|
||||
ops_sub_menu.append(Gtk.SeparatorMenuItem())
|
||||
|
||||
self.tidy_up_menu_item = Gtk.MenuItem.new_with_mnemonic(
|
||||
_('Tidy up _files...'),
|
||||
|
@ -3788,10 +3800,9 @@ class MainWin(Gtk.ApplicationWindow):
|
|||
|
||||
self.test_ytdl_menu_item.set_sensitive(sens_flag)
|
||||
|
||||
if os.name != 'nt':
|
||||
self.install_ffmpeg_menu_item.set_sensitive(False)
|
||||
else:
|
||||
if os.name == 'nt':
|
||||
self.install_ffmpeg_menu_item.set_sensitive(sens_flag)
|
||||
self.install_matplotlib_menu_item.set_sensitive(sens_flag)
|
||||
|
||||
self.stop_operation_menu_item.set_sensitive(False)
|
||||
self.stop_soon_menu_item.set_sensitive(False)
|
||||
|
@ -3927,11 +3938,16 @@ class MainWin(Gtk.ApplicationWindow):
|
|||
else:
|
||||
self.download_all_toolbutton.set_sensitive(sens_flag)
|
||||
|
||||
if not __main__.__pkg_strict_install_flag__:
|
||||
if __main__.__pkg_strict_install_flag__:
|
||||
self.update_ytdl_menu_item.set_sensitive(False)
|
||||
else:
|
||||
self.update_ytdl_menu_item.set_sensitive(sens_flag)
|
||||
|
||||
self.test_ytdl_menu_item.set_sensitive(sens_flag)
|
||||
self.install_ffmpeg_menu_item.set_sensitive(sens_flag)
|
||||
|
||||
if os.name == 'nt':
|
||||
self.install_ffmpeg_menu_item.set_sensitive(sens_flag)
|
||||
self.install_matplotlib_menu_item.set_sensitive(sens_flag)
|
||||
|
||||
if not_dl_operation_flag:
|
||||
self.update_live_menu_item.set_sensitive(sens_flag)
|
||||
|
@ -4324,7 +4340,8 @@ class MainWin(Gtk.ApplicationWindow):
|
|||
True at the end of it
|
||||
|
||||
operation_type (str): 'ffmpeg' for an update operation to install
|
||||
FFmpeg, 'ytdl' for an update operation to install/update
|
||||
FFmpeg, 'matplotlib' for an update operation to install
|
||||
matplotlib, 'ytdl' for an update operation to install/update
|
||||
youtube-dl, 'formats' for an info operation to fetch available
|
||||
video formats, 'subs' for an info operation to fetch
|
||||
available subtitles, 'test_ytdl' for an info operation in which
|
||||
|
@ -4334,9 +4351,10 @@ class MainWin(Gtk.ApplicationWindow):
|
|||
"""
|
||||
|
||||
if operation_type is not None \
|
||||
and operation_type != 'ffmpeg' and operation_type != 'ytdl' \
|
||||
and operation_type != 'formats' and operation_type != 'subs' \
|
||||
and operation_type != 'test_ytdl' and operation_type != 'version':
|
||||
and operation_type != 'ffmpeg' and operation_type != 'matplotlib' \
|
||||
and operation_type != 'ytdl' and operation_type != 'formats' \
|
||||
and operation_type != 'subs' and operation_type != 'test_ytdl' \
|
||||
and operation_type != 'version':
|
||||
return self.app_obj.system_error(
|
||||
205,
|
||||
'Invalid update/info operation argument',
|
||||
|
@ -4408,6 +4426,8 @@ class MainWin(Gtk.ApplicationWindow):
|
|||
|
||||
if operation_type == 'ffmpeg':
|
||||
msg = _('Installing FFmpeg')
|
||||
elif operation_type == 'matplotlib':
|
||||
msg = _('Installing matplotlib')
|
||||
elif operation_type == 'ytdl':
|
||||
msg = _('Updating downloader')
|
||||
elif operation_type == 'formats':
|
||||
|
@ -19960,7 +19980,7 @@ class ComplexCatalogueItem(object):
|
|||
self.thumb_image.set_from_pixbuf(arglist[0])
|
||||
thumb_flag = True
|
||||
|
||||
# No thumbnail file found, so use a default files
|
||||
# No thumbnail file found, so use a default file
|
||||
if not thumb_flag:
|
||||
if self.video_obj.fav_flag and self.video_obj.options_obj:
|
||||
self.thumb_image.set_from_pixbuf(
|
||||
|
@ -22478,8 +22498,14 @@ class GridCatalogueItem(ComplexCatalogueItem):
|
|||
# No thumbnail file found, so use a default icon file
|
||||
if not thumb_flag:
|
||||
|
||||
if not self.video_obj.block_flag:
|
||||
thumb_type = 'default'
|
||||
else:
|
||||
thumb_type = 'block'
|
||||
|
||||
pixbuf_name = 'thumb_' + thumb_type + '_' + thumb_size
|
||||
self.thumb_image.set_from_pixbuf(
|
||||
self.main_win_obj.pixbuf_dict['thumb_default_' + thumb_size],
|
||||
self.main_win_obj.pixbuf_dict[pixbuf_name],
|
||||
)
|
||||
|
||||
|
||||
|
|
240
tartube/media.py
|
@ -1757,10 +1757,57 @@ class Video(GenericMedia):
|
|||
# https://www.youtube.com/watch?v=)
|
||||
self.vid = None
|
||||
|
||||
# Flag set to True once the file has been downloaded, and is confirmed
|
||||
# to exist in Tartube's data directory
|
||||
self.dl_flag = False
|
||||
# Flag set to True if Tartube should always simulate the download of
|
||||
# video, or False if the downloads.DownloadManager object should
|
||||
# decide whether to simulate, or not
|
||||
self.dl_sim_flag = False
|
||||
# Flag set to True if a this video is a video clip has been split off
|
||||
# from another video in Tartube's database (which may or may not
|
||||
# still exist)
|
||||
self.split_flag = False
|
||||
# Flag set to True if the video is marked as being censored, age-
|
||||
# restricted or otherwise unavailable for download
|
||||
self.block_flag = False
|
||||
|
||||
# The size of the video (in bytes)
|
||||
self.file_size = None
|
||||
# The video's upload time (in Unix time)
|
||||
# YouTube (etc) only supplies a date, which Tartube then converts into
|
||||
# seconds, so videos uploaded on the same day will have the same
|
||||
# value for self.upload_time)
|
||||
self.upload_time = None
|
||||
# The time at which Tartube downloaded this video (in Unix time)
|
||||
# When downloading a channel or playlist, we assume that YouTube (etc)
|
||||
# supplies us with the most recent upload first
|
||||
# Therefore, when sorting videos by time, if self.upload_time is the
|
||||
# same (multiple videos were uploaded on the same day), then those
|
||||
# videos are sorted with the lowest value of self.receive_time first
|
||||
self.receive_time = None
|
||||
# The video's duration (in integer seconds)
|
||||
self.duration = None
|
||||
# For videos in a channel or playlist (i.e. a media.Video object whose
|
||||
# parent is a media.Channel or media.Playlist object), the video's
|
||||
# index in the channel/playlist. (The server supplies an index even
|
||||
# for a channel, and the user might want to convert a channel to a
|
||||
# playlist)
|
||||
# For videos whose parent is a media.Folder, the value remains as None
|
||||
self.index = None
|
||||
|
||||
# The video's filename and extension
|
||||
self.file_name = None
|
||||
self.file_ext = None
|
||||
|
||||
# Video description. A string of any length, containing newline
|
||||
# characters if necessary. (Set to None if the video description is
|
||||
# not known)
|
||||
self.descrip = None
|
||||
# Video short description - the first line in self.descrip, limited to
|
||||
# a certain number of characters (specifically,
|
||||
# mainwin.MainWin.very_long_string_max_len)
|
||||
self.short = None
|
||||
|
||||
# Livestream mode: 0 if the video is not a livestream (or if it was a
|
||||
# livestream which has now finished, and behaves like a normal
|
||||
|
@ -1814,59 +1861,16 @@ class Video(GenericMedia):
|
|||
# 'Waiting Videos' system folder
|
||||
self.waiting_flag = False
|
||||
|
||||
# The video's filename and extension
|
||||
self.file_name = None
|
||||
self.file_ext = None
|
||||
|
||||
# When a video is marked to be downloaded in the fixed 'Temporary
|
||||
# Videos' folder, we store the name of the original parent channel/
|
||||
# playlist/folder here, for display in the Video Catalogue
|
||||
self.orig_parent = None
|
||||
|
||||
# Flag set to True once the file has been downloaded, and is confirmed
|
||||
# to exist in Tartube's data directory
|
||||
self.dl_flag = False
|
||||
# Flag set to True if a this video is a video clip has been split off
|
||||
# from another video in Tartube's database (which may or may not
|
||||
# still exist)
|
||||
self.split_flag = False
|
||||
# The size of the video (in bytes)
|
||||
self.file_size = None
|
||||
# The video's upload time (in Unix time)
|
||||
# YouTube (etc) only supplies a date, which Tartube then converts into
|
||||
# seconds, so videos uploaded on the same day will have the same
|
||||
# value for self.upload_time)
|
||||
self.upload_time = None
|
||||
# The time at which Tartube downloaded this video (in Unix time)
|
||||
# When downloading a channel or playlist, we assume that YouTube (etc)
|
||||
# supplies us with the most recent upload first
|
||||
# Therefore, when sorting videos by time, if self.upload_time is the
|
||||
# same (multiple videos were uploaded on the same day), then those
|
||||
# videos are sorted with the lowest value of self.receive_time first
|
||||
self.receive_time = None
|
||||
# The video's duration (in integer seconds)
|
||||
self.duration = None
|
||||
# For videos in a channel or playlist (i.e. a media.Video object whose
|
||||
# parent is a media.Channel or media.Playlist object), the video's
|
||||
# index in the channel/playlist. (The server supplies an index even
|
||||
# for a channel, and the user might want to convert a channel to a
|
||||
# playlist)
|
||||
# For videos whose parent is a media.Folder, the value remains as None
|
||||
self.index = None
|
||||
# List of subtitles available for this video. Items in the list are
|
||||
# language codes gathered from the video's metadata file (e.g.
|
||||
# 'en_US', 'live_chat')
|
||||
self.subs_list = []
|
||||
|
||||
# Video description. A string of any length, containing newline
|
||||
# characters if necessary. (Set to None if the video description is
|
||||
# not known)
|
||||
self.descrip = None
|
||||
# Video short description - the first line in self.descrip, limited to
|
||||
# a certain number of characters (specifically,
|
||||
# mainwin.MainWin.very_long_string_max_len)
|
||||
self.short = None
|
||||
|
||||
# List of timestamps, extracted from the video's description and/or
|
||||
# metadata, or added manually by the user
|
||||
# List in groups of three, in the form
|
||||
|
@ -2063,8 +2067,13 @@ class Video(GenericMedia):
|
|||
else:
|
||||
live_str = ''
|
||||
|
||||
text \
|
||||
= ' #' + str(self.dbid) + live_str + ': ' + self.name + '\n\n'
|
||||
if self.block_flag:
|
||||
block_str = ' <' + _('BLOCKED') + '>'
|
||||
else:
|
||||
block_str = ''
|
||||
|
||||
text = ' #' + str(self.dbid) + live_str + block_str + ': ' \
|
||||
+ self.name + '\n\n'
|
||||
|
||||
if self.parent_obj:
|
||||
|
||||
|
@ -2565,6 +2574,14 @@ class Video(GenericMedia):
|
|||
self.archive_flag = False
|
||||
|
||||
|
||||
def set_block_flag(self, flag):
|
||||
|
||||
if flag:
|
||||
self.block_flag = True
|
||||
else:
|
||||
self.block_flag = False
|
||||
|
||||
|
||||
def set_bookmark_flag(self, flag):
|
||||
|
||||
if flag:
|
||||
|
@ -4174,9 +4191,9 @@ class Scheduled(object):
|
|||
custom downloads; the value is checked before being used, and
|
||||
converted to 'custom_sim' where necessary)
|
||||
|
||||
start_mode (str): 'none' to disable this schedule, 'start' to perform
|
||||
the operation whenever Tartube starts, or 'scheduled' to perform
|
||||
the operation at regular intervals
|
||||
start_mode (str): 'disabled' to disable this schedule, 'start' to
|
||||
perform the operation whenever Tartube starts, or 'repeat' to
|
||||
perform the operation at regular intervals
|
||||
|
||||
"""
|
||||
|
||||
|
@ -4202,26 +4219,49 @@ class Scheduled(object):
|
|||
# value, a 'real' download takes place
|
||||
# Ignored if self.dl_mode is not 'custom_real'
|
||||
self.custom_dl_uid = None
|
||||
# Start mode - 'none' to disable this schedule, 'start' to perform the
|
||||
# operation whenever Tartube starts, or 'scheduled' to perform the
|
||||
# operation at regular intervals
|
||||
|
||||
# Start mode
|
||||
# 'disabled' - disable this scheduled download
|
||||
# 'start' - perform the operation whenever Tartube starts
|
||||
# 'start_after' - perform the operation some time after Tartube
|
||||
# starts
|
||||
# 'repeat' - perform the operation at regular intervals
|
||||
# 'timetable' - perform the operation at pre-determined intervals
|
||||
self.start_mode = start_mode
|
||||
|
||||
# The time between scheduled downloads, when self.start_mode is
|
||||
# 'scheduled' (minimum value 1, ignored for other values of
|
||||
# self.start_mode)
|
||||
# The time between scheduled downloads (minimum value 1)
|
||||
# self.start_mode = 'start_after'
|
||||
# The time after Tartube starts at which the scheduled download
|
||||
# happens
|
||||
# self.start_mode = 'repeat':
|
||||
# The time between repeating scheduled downloads
|
||||
self.wait_value = 2
|
||||
# ...using this unit (any of the values in formats.TIME_METRIC_LIST;
|
||||
# the 'seconds' value is not available in the edit window's combobox)
|
||||
# self.wait_value uses this unit (any of the values in
|
||||
# formats.TIME_METRIC_LIST; but the 'seconds' value is not available
|
||||
# in the edit window's combobox)
|
||||
self.wait_unit = 'hours'
|
||||
|
||||
# Timetable of times at which the scheduled download happens. when
|
||||
# self.start_mode = 'timetable'
|
||||
# Each item in the list is a mini-list in the form
|
||||
# [ day_string, time_string ]
|
||||
# ... where 'day_string' is a kay in formats.SPECIFIED_DAYS_DICT (e.g.
|
||||
# 'every_day', 'monday', and 'time_string' is a 24-hour time in
|
||||
# the form 'hh:mm'
|
||||
self.timetable_list = []
|
||||
# A window of 5 minutes during which scheduled downloads can start
|
||||
# (i.e. if the timetable time is 14:00 and Tartube is started at
|
||||
# 14:02, the scheduled download still starts)
|
||||
# Absolute minimum value is 1, recommended minimum is 60
|
||||
self.timetable_window = 300
|
||||
|
||||
# The time (system time, in seconds) at which this scheduled download
|
||||
# last started (regardless of whether it was scheduled to begin at
|
||||
# that time, or not)
|
||||
self.last_time = 0
|
||||
# When self.start_mode is 'start', mainapp.TartubeApp.start sets this
|
||||
# value to the time at which the scheduled download should start
|
||||
# (which will be a few seconds after startup)
|
||||
# When self.start_mode is 'start' or 'start_after',
|
||||
# mainapp.TartubeApp.start sets this value to the time at which the
|
||||
# scheduled download should start
|
||||
# Once the scheduled download is started, the value is set back to 0
|
||||
self.only_time = 0
|
||||
|
||||
|
@ -4273,6 +4313,88 @@ class Scheduled(object):
|
|||
# Public class methods
|
||||
|
||||
|
||||
def check_start(self):
|
||||
|
||||
"""Called by mainapp.TartubeApp.script_slow_timer_callback().
|
||||
|
||||
Tests whether it is time to start this scheduled download, or not.
|
||||
|
||||
Return values:
|
||||
|
||||
True to start the scheduled download, False otherwise.
|
||||
|
||||
"""
|
||||
|
||||
wait_time = self.wait_value * formats.TIME_METRIC_DICT[self.wait_unit]
|
||||
|
||||
if (
|
||||
(
|
||||
self.start_mode == 'repeat' \
|
||||
and self.last_time + wait_time < time.time()
|
||||
) or (
|
||||
(
|
||||
self.start_mode == 'start' \
|
||||
or self.start_mode == 'start_after'
|
||||
) and self.only_time > 0 \
|
||||
and self.only_time < time.time()
|
||||
) or (
|
||||
self.start_mode == 'timetable' \
|
||||
and self.check_timetable()
|
||||
)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def check_timetable(self):
|
||||
|
||||
"""Called by self.check_start() when self.start_mode is 'timetable'.
|
||||
|
||||
Tests whether it is time to start this scheduled download, or not,
|
||||
depending on dates/times specified in self.timetable_list().
|
||||
|
||||
Return values:
|
||||
|
||||
True to start the scheduled download, False otherwise.
|
||||
|
||||
"""
|
||||
|
||||
local = utils.get_local_time()
|
||||
current_day = local.today().weekday()
|
||||
current_hours = int(local.strftime('%H'))
|
||||
current_minutes = int(local.strftime('%M'))
|
||||
|
||||
# Each 'mini_list' is in the form [ day_string, time_string ]
|
||||
for mini_list in self.timetable_list:
|
||||
|
||||
# Today?
|
||||
if not utils.check_day(current_day, mini_list[0]):
|
||||
continue
|
||||
|
||||
# Between these two times (a window of 5 minutes, by default)?
|
||||
early_time = datetime.datetime.now()
|
||||
early_time = early_time.replace(
|
||||
hour = int(mini_list[1][0:2]),
|
||||
minute = int(mini_list[1][3:5]),
|
||||
second = 0,
|
||||
)
|
||||
|
||||
late_time = early_time \
|
||||
+ datetime.timedelta(seconds=self.timetable_window)
|
||||
|
||||
# Give each scheduled download a two minute window in which to
|
||||
# start
|
||||
if early_time > datetime.datetime.fromtimestamp(
|
||||
self.last_time + self.timetable_window,
|
||||
) and datetime.datetime.fromtimestamp(time.time()) >= early_time \
|
||||
and datetime.datetime.fromtimestamp(time.time()) <= late_time:
|
||||
return True
|
||||
|
||||
# Try again later
|
||||
return False
|
||||
|
||||
|
||||
# Set accessors
|
||||
|
||||
|
||||
|
|
|
@ -747,7 +747,7 @@ class OptionsManager(object):
|
|||
'nomtime': False,
|
||||
'write_description': True,
|
||||
'write_info': True,
|
||||
'write_annotations': True,
|
||||
'write_annotations': False,
|
||||
'cookies_path': '',
|
||||
# THUMBNAIL IMAGES
|
||||
'write_thumbnail': True,
|
||||
|
|
|
@ -42,8 +42,8 @@ import mainapp
|
|||
|
||||
# 'Global' variables
|
||||
__packagename__ = 'tartube'
|
||||
__version__ = '2.3.450'
|
||||
__date__ = '24 Mar 2022'
|
||||
__version__ = '2.3.471'
|
||||
__date__ = '29 Mar 2022'
|
||||
__copyright__ = 'Copyright \xa9 2019-2022 A S Lewis'
|
||||
__license__ = """
|
||||
Copyright \xa9 2019-2022 A S Lewis.
|
||||
|
|
|
@ -35,6 +35,7 @@ import signal
|
|||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
||||
# Import our modules
|
||||
|
@ -54,7 +55,7 @@ class UpdateManager(threading.Thread):
|
|||
|
||||
Python class to create a system child process, to do one of two jobs:
|
||||
|
||||
1. Install FFmpeg (on MS Windows only)
|
||||
1. Install FFmpeg or matplotlib (on MS Windows only)
|
||||
|
||||
2. Install youtube-dl, or update it to its most recent version.
|
||||
|
||||
|
@ -65,8 +66,9 @@ class UpdateManager(threading.Thread):
|
|||
|
||||
app_obj (mainapp.TartubeApp): The main application
|
||||
|
||||
update_type (str): 'ffmpeg' to install FFmpeg (on MS Windows only), or
|
||||
'ytdl' to install/update youtube-dl
|
||||
update_type (str): 'ffmpeg' to install FFmpeg (on MS Windows only),
|
||||
'matplotlib' to install matplotlib (on MS Windows only), or 'ytdl'
|
||||
to install/update youtube-dl
|
||||
|
||||
wiz_win_obj (wizwin.SetupWizWin or None): The calling setup wizard
|
||||
window (if set, the main window doesn't exist yet)
|
||||
|
@ -89,24 +91,26 @@ class UpdateManager(threading.Thread):
|
|||
# exist yet)
|
||||
self.wiz_win_obj = wiz_win_obj
|
||||
|
||||
# This object reads from the child process STDOUT and STDERR in an
|
||||
# asynchronous way
|
||||
# Standard Python synchronised queue classes
|
||||
self.stdout_queue = queue.Queue()
|
||||
self.stderr_queue = queue.Queue()
|
||||
# The downloads.PipeReader objects created to handle reading from the
|
||||
# pipes
|
||||
self.stdout_reader = downloads.PipeReader(self.stdout_queue)
|
||||
self.stderr_reader = downloads.PipeReader(self.stderr_queue)
|
||||
|
||||
# The child process created by self.create_child_process()
|
||||
self.child_process = None
|
||||
|
||||
# Read from the child process STDOUT (i.e. self.child_process.stdout)
|
||||
# and STDERR (i.e. self.child_process.stderr) in an asynchronous way
|
||||
# by polling this queue.PriorityQueue object
|
||||
self.queue = queue.PriorityQueue()
|
||||
self.stdout_reader = downloads.PipeReader(self.queue, 'stdout')
|
||||
self.stderr_reader = downloads.PipeReader(self.queue, 'stderr')
|
||||
|
||||
|
||||
# IV list - other
|
||||
# ---------------
|
||||
# 'ffmpeg' to install FFmpeg (on MS Windows only), or 'ytdl' to
|
||||
# install/update youtube-dl
|
||||
# The time (in seconds) between iterations of the loop in
|
||||
# self.install_ffmpeg(), .install_matplotlib() and .install_ytdl()
|
||||
self.sleep_time = 0.1
|
||||
|
||||
# 'ffmpeg' to install FFmpeg (on MS Windows only), 'matplotlib' to
|
||||
# install matplotlib (on MS Windows only), or 'ytdl' to install/
|
||||
# update youtube-dl
|
||||
self.update_type = update_type
|
||||
# Flag set to True if the update operation succeeds, False if it fails
|
||||
self.success_flag = False
|
||||
|
@ -141,13 +145,16 @@ class UpdateManager(threading.Thread):
|
|||
|
||||
if self.update_type == 'ffmpeg':
|
||||
self.install_ffmpeg()
|
||||
elif self.update_type == 'matplotlib':
|
||||
self.install_matplotlib()
|
||||
else:
|
||||
self.install_ytdl()
|
||||
|
||||
|
||||
def create_child_process(self, cmd_list):
|
||||
|
||||
"""Called by self.install_ffmpeg() or .install_ytdl().
|
||||
"""Called by self.install_ffmpeg(), .install_matplotlib() or
|
||||
.install_ytdl().
|
||||
|
||||
Based on code from downloads.VideoDownloader.create_child_process().
|
||||
|
||||
|
@ -212,61 +219,32 @@ class UpdateManager(threading.Thread):
|
|||
else:
|
||||
binary = 'mingw-w64-x86_64-ffmpeg'
|
||||
|
||||
self.create_child_process(
|
||||
['pacman', '-S', binary, '--noconfirm'],
|
||||
)
|
||||
|
||||
# Show the system command in the Output tab
|
||||
# Prepare a system command...
|
||||
cmd_list = ['pacman', '-S', binary, '--noconfirm']
|
||||
# ...and display it in the Output tab (if required)
|
||||
self.install_ffmpeg_write_output(
|
||||
' '.join( ['pacman', '-S', binary, '--noconfirm'] ),
|
||||
' '.join(cmd_list),
|
||||
True, # A system command, not a message
|
||||
)
|
||||
|
||||
# So that we can read from the child process STDOUT and STDERR, attach
|
||||
# a file descriptor to the PipeReader objects
|
||||
# Create a new child process using that command...
|
||||
self.create_child_process(cmd_list)
|
||||
# ...and set up the PipeReader objects to read from the child process
|
||||
# STDOUT and STDERR
|
||||
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,
|
||||
)
|
||||
self.stdout_reader.attach_fh(self.child_process.stdout)
|
||||
self.stderr_reader.attach_fh(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():
|
||||
# Pause a moment between each iteration of the loop (we don't want
|
||||
# to hog system resources)
|
||||
time.sleep(self.sleep_time)
|
||||
|
||||
stdout = self.stdout_queue.get_nowait().rstrip()
|
||||
stdout = stdout.decode(utils.get_encoding(), 'replace')
|
||||
|
||||
if stdout:
|
||||
|
||||
# Show command line output in the Output tab (or wizard
|
||||
# window textview)
|
||||
self.install_ffmpeg_write_output(stdout)
|
||||
|
||||
# The child process has finished
|
||||
while not self.stderr_queue.empty():
|
||||
|
||||
# Read from the child process STDERR queue (we don't need to read
|
||||
# it in real time), and convert into unicode for python's
|
||||
# convenience
|
||||
stderr = self.stderr_queue.get_nowait().rstrip()
|
||||
stderr = stderr.decode(utils.get_encoding(), 'replace')
|
||||
|
||||
# Ignore pacman warning messages, e.g. 'warning: dependency cycle
|
||||
# detected:'
|
||||
if stderr and not re.search('^warning\:', stderr):
|
||||
|
||||
self.stderr_list.append(stderr)
|
||||
|
||||
# Show command line output in the Output tab (or wizard window
|
||||
# textview)
|
||||
self.install_ffmpeg_write_output(stderr)
|
||||
# Read from the child process STDOUT and STDERR, in the correct
|
||||
# order, until there is nothing left to read
|
||||
while self.read_ffmpeg_child_process():
|
||||
pass
|
||||
|
||||
# (Generate our own error messages for debugging purposes, in certain
|
||||
# situations)
|
||||
|
@ -326,6 +304,106 @@ class UpdateManager(threading.Thread):
|
|||
)
|
||||
|
||||
|
||||
def install_matplotlib(self):
|
||||
|
||||
"""Called by self.run().
|
||||
|
||||
A modified version of self.install_ytdl, that installs matplotlib on an
|
||||
MS Windows system.
|
||||
|
||||
Creates a child process to run the installation process.
|
||||
|
||||
Reads from the child process STDOUT and STDERR, and calls the main
|
||||
application with the result of the update (success or failure).
|
||||
"""
|
||||
|
||||
# Show information about the update operation in the Output tab
|
||||
self.install_matplotlib_write_output(
|
||||
_('Starting update operation, installing matplotlib'),
|
||||
)
|
||||
|
||||
# Create a new child process to install either the 64-bit or 32-bit
|
||||
# version of matplotlib, as appropriate
|
||||
if sys.maxsize <= 2147483647:
|
||||
binary = 'mingw-w64-i686-python-matplotlib'
|
||||
else:
|
||||
binary = 'mingw-w64-x86_64-python-matplotlib'
|
||||
|
||||
# Prepare a system command...
|
||||
cmd_list = ['pacman', '-S', binary, '--noconfirm']
|
||||
# ...and display it in the Output tab (if required)
|
||||
self.install_matplotlib_write_output(
|
||||
' '.join(cmd_list),
|
||||
True, # A system command, not a message
|
||||
)
|
||||
|
||||
# Create a new child process using that command...
|
||||
self.create_child_process(cmd_list)
|
||||
# ...and set up the PipeReader objects to read from the child process
|
||||
# STDOUT and STDERR
|
||||
if self.child_process is not None:
|
||||
self.stdout_reader.attach_fh(self.child_process.stdout)
|
||||
self.stderr_reader.attach_fh(self.child_process.stderr)
|
||||
|
||||
while self.is_child_process_alive():
|
||||
|
||||
# Pause a moment between each iteration of the loop (we don't want
|
||||
# to hog system resources)
|
||||
time.sleep(self.sleep_time)
|
||||
|
||||
# Read from the child process STDOUT and STDERR, in the correct
|
||||
# order, until there is nothing left to read
|
||||
while self.read_matplotlib_child_process():
|
||||
pass
|
||||
|
||||
# (Generate our own error messages for debugging purposes, in certain
|
||||
# situations)
|
||||
if self.child_process is None:
|
||||
self.stderr_list.append(_('matplotlib installation 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. self.success_flag is checked by
|
||||
# mainapp.TartubeApp.update_manager_finished
|
||||
if not self.stderr_list:
|
||||
self.success_flag = True
|
||||
|
||||
# Show a confirmation in the the Output tab (or wizard window textview)
|
||||
self.install_matplotlib_write_output(_('Update operation finished'))
|
||||
|
||||
# Let the timer run for a few more seconds to prevent Gtk errors (for
|
||||
# systems with Gtk < 3.24)
|
||||
self.app_obj.update_manager_halt_timer()
|
||||
|
||||
|
||||
def install_matplotlib_write_output(self, msg, system_cmd_flag=False):
|
||||
|
||||
"""Called by self.install_matplotlib().
|
||||
|
||||
Writes a message to the Output tab (or to the setup wizard window, if
|
||||
called from there).
|
||||
|
||||
Args:
|
||||
|
||||
msg (str): The message to display
|
||||
|
||||
system_cmd_flag (bool): If True, display system commands in a
|
||||
different colour in the Output tab (ignored when writing in
|
||||
the setup wizard window)
|
||||
|
||||
"""
|
||||
|
||||
if not system_cmd_flag:
|
||||
self.app_obj.main_win_obj.output_tab_write_stdout(1, msg)
|
||||
else:
|
||||
self.app_obj.main_win_obj.output_tab_write_system_cmd(1, msg)
|
||||
|
||||
|
||||
def install_ytdl(self):
|
||||
|
||||
"""Called by self.run().
|
||||
|
@ -382,7 +460,7 @@ class UpdateManager(threading.Thread):
|
|||
elif ytdl_update_current == 'ytdl_update_win_32':
|
||||
ytdl_update_current = 'ytdl_update_win_32_no_dependencies'
|
||||
|
||||
# Set the system command
|
||||
# Prepare a system command...
|
||||
if os.name == 'nt' \
|
||||
and ytdl_update_current == 'ytdl_update_custom_path' \
|
||||
and re.search('\.exe$', self.app_obj.ytdl_path):
|
||||
|
@ -406,86 +484,30 @@ class UpdateManager(threading.Thread):
|
|||
|
||||
mod_list.append(arg)
|
||||
|
||||
# Create a new child process using that command
|
||||
self.create_child_process(mod_list)
|
||||
|
||||
# Show the system command in the Output tab
|
||||
# ...and display it in the Output tab (if required)
|
||||
self.install_ytdl_write_output(
|
||||
' '.join(mod_list),
|
||||
True, # A system command, not a message
|
||||
)
|
||||
|
||||
# So that we can read from the child process STDOUT and STDERR, attach
|
||||
# a file descriptor to the PipeReader objects
|
||||
# Create a new child process using that command...
|
||||
self.create_child_process(mod_list)
|
||||
# ...and set up the PipeReader objects to read from the child process
|
||||
# STDOUT and STDERR
|
||||
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,
|
||||
)
|
||||
self.stdout_reader.attach_fh(self.child_process.stdout)
|
||||
self.stderr_reader.attach_fh(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():
|
||||
# Pause a moment between each iteration of the loop (we don't want
|
||||
# to hog system resources)
|
||||
time.sleep(self.sleep_time)
|
||||
|
||||
stdout = self.stdout_queue.get_nowait().rstrip()
|
||||
if stdout:
|
||||
|
||||
stdout = stdout.decode(utils.get_encoding(), 'replace')
|
||||
|
||||
# "It looks like you installed youtube-dl with a package
|
||||
# manager, pip, setup.py or a tarball. Please use that to
|
||||
# update."
|
||||
# "The script youtube-dl is installed in '...' which is not
|
||||
# on PATH. Consider adding this directory to PATH..."
|
||||
if re.search('It looks like you installed', stdout) \
|
||||
or re.search(
|
||||
'The script ' + downloader + ' is installed',
|
||||
stdout,
|
||||
):
|
||||
self.stderr_list.append(stdout)
|
||||
|
||||
else:
|
||||
|
||||
# Try to intercept the new version number for
|
||||
# youtube-dl
|
||||
self.intercept_version_from_stdout(stdout, downloader)
|
||||
self.stdout_list.append(stdout)
|
||||
|
||||
# Show command line output in the Output tab (or wizard
|
||||
# window textview)
|
||||
self.install_ytdl_write_output(stdout)
|
||||
|
||||
# The child process has finished
|
||||
while not self.stderr_queue.empty():
|
||||
|
||||
# Read from the child process STDERR queue (we don't need to read
|
||||
# it in real time), and convert into unicode for python's
|
||||
# convenience
|
||||
stderr = self.stderr_queue.get_nowait().rstrip()
|
||||
if stderr:
|
||||
|
||||
stderr = stderr.decode(utils.get_encoding(), 'replace')
|
||||
|
||||
# If the user has pip installed, rather than pip3, they will by
|
||||
# now (mid-2019) be seeing a Python 2.7 deprecation warning.
|
||||
# Ignore that message, if received
|
||||
# If a newer version of pip is available, the user will see a
|
||||
# 'You should consider upgrading' warning. Ignore that too,
|
||||
# if received
|
||||
if not re.search('DEPRECATION', stderr) \
|
||||
and not re.search('You are using pip version', stderr) \
|
||||
and not re.search('You should consider upgrading', stderr):
|
||||
self.stderr_list.append(stderr)
|
||||
|
||||
# Show command line output in the Output tab (or wizard window
|
||||
# textview)
|
||||
self.install_ytdl_write_output(stderr)
|
||||
# Read from the child process STDOUT and STDERR, in the correct
|
||||
# order, until there is nothing left to read
|
||||
while self.read_ytdl_child_process(downloader):
|
||||
pass
|
||||
|
||||
# (Generate our own error messages for debugging purposes, in certain
|
||||
# situations)
|
||||
|
@ -567,8 +589,10 @@ class UpdateManager(threading.Thread):
|
|||
"""
|
||||
|
||||
regex_list = [
|
||||
'Requirement already up\-to\-date.*\(([^\(\)]+)\)\s*$',
|
||||
'Requirement already satisfied.*\(([^\(\)]+)\)\s*$',
|
||||
'Requirement already up\-to\-date\: ' + downloader \
|
||||
+ ' in .*\(([^\(\)]+)\)\s*$',
|
||||
'Requirement already satisfied\: ' + downloader \
|
||||
+ ' in .*\(([^\(\)]+)\)\s*$',
|
||||
'yt-dlp is up to date \(([^\(\)]+)\)\s*$',
|
||||
'Successfully installed ' + downloader + '\-([^\(\)]+)\s*$',
|
||||
]
|
||||
|
@ -582,8 +606,8 @@ class UpdateManager(threading.Thread):
|
|||
|
||||
def is_child_process_alive(self):
|
||||
|
||||
"""Called by self.install_ffmpeg(), .install_ytdl() and
|
||||
.stop_update_operation().
|
||||
"""Called by self.install_ffmpeg(), .install_matplotlib(),
|
||||
.install_ytdl() and .stop_update_operation().
|
||||
|
||||
Based on code from downloads.VideoDownloader.is_child_process_alive().
|
||||
|
||||
|
@ -602,6 +626,218 @@ class UpdateManager(threading.Thread):
|
|||
return self.child_process.poll() is None
|
||||
|
||||
|
||||
def read_ffmpeg_child_process(self):
|
||||
|
||||
"""Called by self.install_ffmpeg().
|
||||
|
||||
Reads from the child process STDOUT and STDERR, in the correct order.
|
||||
|
||||
Return values:
|
||||
|
||||
True if either STDOUT or STDERR were read, None if both queues were
|
||||
empty
|
||||
|
||||
"""
|
||||
|
||||
# mini_list is in the form [time, pipe_type, data]
|
||||
try:
|
||||
mini_list = self.queue.get_nowait()
|
||||
|
||||
except:
|
||||
# Nothing left to read
|
||||
return None
|
||||
|
||||
# Failsafe check
|
||||
if not mini_list \
|
||||
or (mini_list[1] != 'stdout' and mini_list[1] != 'stderr'):
|
||||
|
||||
# Just in case...
|
||||
self.app_obj.system_error(
|
||||
701,
|
||||
'Malformed STDOUT or STDERR data',
|
||||
)
|
||||
|
||||
# STDOUT or STDERR has been read
|
||||
data = mini_list[2].rstrip()
|
||||
# On MS Windows we use cp1252, so that Tartube can communicate with the
|
||||
# Windows console
|
||||
data = data.decode(utils.get_encoding(), 'replace')
|
||||
|
||||
# STDOUT
|
||||
if mini_list[1] == 'stdout':
|
||||
|
||||
# Show command line output in the Output tab (or wizard window
|
||||
# textview)
|
||||
self.install_ffmpeg_write_output(data)
|
||||
|
||||
# STDERR
|
||||
else:
|
||||
|
||||
# Ignore pacman warning messages, e.g. 'warning: dependency cycle
|
||||
# detected:'
|
||||
if data and not re.search('^warning\:', data):
|
||||
|
||||
self.stderr_list.append(data)
|
||||
|
||||
# Show command line output in the Output tab (or wizard window
|
||||
# textview)
|
||||
self.install_ffmpeg_write_output(data)
|
||||
|
||||
# Either (or both) of STDOUT and STDERR were non-empty
|
||||
self.queue.task_done()
|
||||
return True
|
||||
|
||||
|
||||
def read_matplotlib_child_process(self):
|
||||
|
||||
"""Called by self.install_matplotlib().
|
||||
|
||||
Reads from the child process STDOUT and STDERR, in the correct order.
|
||||
|
||||
Return values:
|
||||
|
||||
True if either STDOUT or STDERR were read, None if both queues were
|
||||
empty
|
||||
|
||||
"""
|
||||
|
||||
# mini_list is in the form [time, pipe_type, data]
|
||||
try:
|
||||
mini_list = self.queue.get_nowait()
|
||||
|
||||
except:
|
||||
# Nothing left to read
|
||||
return None
|
||||
|
||||
# Failsafe check
|
||||
if not mini_list \
|
||||
or (mini_list[1] != 'stdout' and mini_list[1] != 'stderr'):
|
||||
|
||||
# Just in case...
|
||||
self.app_obj.system_error(
|
||||
701,
|
||||
'Malformed STDOUT or STDERR data',
|
||||
)
|
||||
|
||||
# STDOUT or STDERR has been read
|
||||
data = mini_list[2].rstrip()
|
||||
# On MS Windows we use cp1252, so that Tartube can communicate with the
|
||||
# Windows console
|
||||
data = data.decode(utils.get_encoding(), 'replace')
|
||||
|
||||
# STDOUT
|
||||
if mini_list[1] == 'stdout':
|
||||
|
||||
# Show command line output in the Output tab (or wizard window
|
||||
# textview)
|
||||
self.install_matplotlib_write_output(data)
|
||||
|
||||
# STDERR
|
||||
else:
|
||||
|
||||
# Ignore pacman warning messages, e.g. 'warning: dependency cycle
|
||||
# detected:'
|
||||
if data and not re.search('^warning\:', data):
|
||||
|
||||
self.stderr_list.append(data)
|
||||
|
||||
# Show command line output in the Output tab (or wizard window
|
||||
# textview)
|
||||
self.install_matplotlib_write_output(data)
|
||||
|
||||
# Either (or both) of STDOUT and STDERR were non-empty
|
||||
self.queue.task_done()
|
||||
return True
|
||||
|
||||
|
||||
def read_ytdl_child_process(self, downloader):
|
||||
|
||||
"""Called by self.install_ytdl().
|
||||
|
||||
Reads from the child process STDOUT and STDERR, in the correct order.
|
||||
|
||||
Args:
|
||||
|
||||
downloader (str): e.g. 'youtube-dl'
|
||||
|
||||
Return values:
|
||||
|
||||
True if either STDOUT or STDERR were read, None if both queues were
|
||||
empty
|
||||
|
||||
"""
|
||||
|
||||
# mini_list is in the form [time, pipe_type, data]
|
||||
try:
|
||||
mini_list = self.queue.get_nowait()
|
||||
|
||||
except:
|
||||
# Nothing left to read
|
||||
return None
|
||||
|
||||
# Failsafe check
|
||||
if not mini_list \
|
||||
or (mini_list[1] != 'stdout' and mini_list[1] != 'stderr'):
|
||||
|
||||
# Just in case...
|
||||
self.app_obj.system_error(
|
||||
702,
|
||||
'Malformed STDOUT or STDERR data',
|
||||
)
|
||||
|
||||
# STDOUT or STDERR has been read
|
||||
data = mini_list[2].rstrip()
|
||||
# On MS Windows we use cp1252, so that Tartube can communicate with the
|
||||
# Windows console
|
||||
data = data.decode(utils.get_encoding(), 'replace')
|
||||
|
||||
# STDOUT
|
||||
if mini_list[1] == 'stdout':
|
||||
|
||||
# "It looks like you installed youtube-dl with a package manager,
|
||||
# pip, setup.py or a tarball. Please use that to update."
|
||||
# "The script youtube-dl is installed in '...' which is not on
|
||||
# PATH. Consider adding this directory to PATH..."
|
||||
if re.search('It looks like you installed', data) \
|
||||
or re.search(
|
||||
'The script ' + downloader + ' is installed',
|
||||
data,
|
||||
):
|
||||
self.stderr_list.append(data)
|
||||
|
||||
else:
|
||||
|
||||
# Try to intercept the new version number for youtube-dl
|
||||
self.intercept_version_from_stdout(data, downloader)
|
||||
self.stdout_list.append(data)
|
||||
|
||||
# Show command line output in the Output tab (or wizard window
|
||||
# textview)
|
||||
self.install_ytdl_write_output(data)
|
||||
|
||||
# STDERR
|
||||
else:
|
||||
|
||||
# If the user has pip installed, rather than pip3, they will by now
|
||||
# (mid-2019) be seeing a Python 2.7 deprecation warning. Ignore
|
||||
# that message, if received
|
||||
# If a newer version of pip is available, the user will see a
|
||||
# 'You should consider upgrading' warning. Ignore that too, if
|
||||
# received
|
||||
if not re.search('DEPRECATION', data) \
|
||||
and not re.search('You are using pip version', data) \
|
||||
and not re.search('You should consider upgrading', data):
|
||||
self.stderr_list.append(data)
|
||||
|
||||
# Show command line output in the Output tab (or wizard window
|
||||
# textview)
|
||||
self.install_ytdl_write_output(data)
|
||||
|
||||
# Either (or both) of STDOUT and STDERR were non-empty
|
||||
self.queue.task_done()
|
||||
return True
|
||||
|
||||
|
||||
def stop_update_operation(self):
|
||||
|
||||
"""Called by mainapp.TartubeApp.do_shutdown(), .stop_continue(),
|
||||
|
|
|
@ -253,6 +253,44 @@ mark_end=None, drag_drop_text=None):
|
|||
)
|
||||
|
||||
|
||||
def check_day(this_day_num, target_day_str):
|
||||
|
||||
"""Can be called by anything.
|
||||
|
||||
formats.SPECIFIED_DAYS_DICT contains a set of strings representing one or
|
||||
more days, e.g. 'every_day', 'monday'.
|
||||
|
||||
Check whether one of those strings matches a particular day.
|
||||
|
||||
Args:
|
||||
|
||||
this_day_num (int): Number in the range 0 (Monday) to 6 (Sunday),
|
||||
usually representing today
|
||||
|
||||
target_day_str (str): One of the strings in formats.SPECIFIED_DAYS_DICT
|
||||
|
||||
Return values:
|
||||
|
||||
True if 'this_day_num' matches 'target_day_str', False otherwise
|
||||
|
||||
"""
|
||||
|
||||
if target_day_str != 'every_day':
|
||||
|
||||
if (target_day_str == 'weekdays' and this_day_num > 4) \
|
||||
or (target_day_str == 'weekends' and this_day_num < 5) \
|
||||
or (target_day_str == 'monday' and this_day_num != 0) \
|
||||
or (target_day_str == 'tuesday' and this_day_num != 1) \
|
||||
or (target_day_str == 'wednesday' and this_day_num != 2) \
|
||||
or (target_day_str == 'thursday' and this_day_num != 3) \
|
||||
or (target_day_str == 'friday' and this_day_num != 4) \
|
||||
or (target_day_str == 'saturday' and this_day_num != 5) \
|
||||
or (target_day_str == 'sunday' and this_day_num != 6):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def check_url(url):
|
||||
|
||||
"""Can be called by anything.
|
||||
|
|