Updates and fixes

master
A S Lewis 2022-03-29 15:39:01 +01:00
parent 03982bc855
commit 86091c6462
28 changed files with 3038 additions and 1551 deletions

View File

@ -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
==============

View File

@ -1 +1 @@
2.3.450
2.3.471

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -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"

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -1,6 +1,6 @@
[Desktop Entry]
Name=Tartube
Version=2.3.450
Version=2.3.471
Exec=tartube
Icon=tartube
Type=Application

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

BIN
screenshots/example33.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -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',

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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 = {

View File

@ -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(),

View File

@ -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

View File

@ -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],
)

View File

@ -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

View File

@ -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,

View File

@ -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.

View File

@ -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(),

View File

@ -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.