Update to v2.3.549
This commit is contained in:
parent
8493d8b6d1
commit
e0a8fe1d6b
97
CHANGES
97
CHANGES
@ -1,3 +1,100 @@
|
||||
v2.3.549 (9 Apr 2022)
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
MAJOR NEW FEATURES
|
||||
- Each channel/playlist/folder in the Videos tab now has a checkbutton. You
|
||||
can select one or more items, if you want to check/download just those
|
||||
items. Click the buttons in the bottom-left corner to do that. Even when
|
||||
items are selected, you can still download everything using the main menu
|
||||
or from the toolbar. The checkbuttons can be hidden, if you don't want
|
||||
them: click Edit > System preferences... > Videos, and de-select 'Allow
|
||||
each row to be marked for checking/downloading' (Git #379)
|
||||
- New Drag and Drop tab. The tab is divided into a grid. Each zone represents a
|
||||
set of download options. Drag and drop a video from your browser (or a
|
||||
text URL) onto one of these zones, and it is added to the Classic Mode tab,
|
||||
ready for download using the specified download options. Because of Gtk
|
||||
issues, drag and drop from a browser does not work well on MS Windows.
|
||||
All users will receive a new set of download options called 'mp3', unless
|
||||
they already have one with that name
|
||||
- The layout of the Errors/Warnings tab has been redesigned. It is now fully
|
||||
searchable. All the checkbuttons like 'Show Tartube errors' are now
|
||||
applied immediately, and are reversible. (Previously, they only applied to
|
||||
errors/warnings received in the future)
|
||||
|
||||
MINOR NEW FEATURES
|
||||
- The Classic Mode tab has a new button at the bottom of the tab. When
|
||||
selected, youtube-dl creates an archive file. This is not recommended in
|
||||
most cases; it might be useful for downloading large channels/playlists, if
|
||||
the download might need to be resumed after an interruption (Git #285)
|
||||
- In the download options windows (e.g. Edit > General download options...),
|
||||
when advanced options are visible, the options were previously in a
|
||||
separate yt-dlp tab have been merged into the other tabs. This is because
|
||||
yt-dlp is now the default downloader for nearly all users
|
||||
- The layouts in the preferences window and several other edit windows have
|
||||
been improved (includes Git #360)
|
||||
- Custom downloads can now skip broadcasting livestreams, or livestreams that
|
||||
have already finished. They can also skip all videos EXCEPT current/former
|
||||
livestreams. New icons in the Videos tab highlights which videos are
|
||||
former livestreams; one for downloaded videos, one for checked videos
|
||||
(Git #358)
|
||||
- Tartube can now open in the system tray. See the new setting in Edit >
|
||||
System preferences > Windows > Tray (Git #365)
|
||||
- Tidy operations can now remove videos without URLs, or duplicate videos, from
|
||||
the Tartube database
|
||||
- In the video properties window, Timestamps tab, you can now update the list
|
||||
of timestamps manually by clicking the new 'Reset list using copied text'
|
||||
button, and then copy-pasting text into the dialogue window (for example,
|
||||
from the video's description) (Git #330)
|
||||
- Minor changes to the layout of the main window's menu
|
||||
|
||||
MAJOR FIXES
|
||||
- The dialogue windows for adding channels, playlists and folder dialogues
|
||||
accepted invalid characters and names. Fixed to prevent illegel directory/
|
||||
folder names on MS Windows, Linux and MacOS (Git #335)
|
||||
- A 'blocked' video (e.g. an age-restricted video from YouTube) remains
|
||||
invisible in the Videos tab, by default, even after it is successfully
|
||||
checked or downloaded (for example, after the user has supplied login
|
||||
credentials). Fixed
|
||||
- Errors/warnings assigned by Tartube to an individual video were not cleared
|
||||
when the video was checked/downloaded without further errors/warnings.
|
||||
Fixed
|
||||
- Fixed more crashes due to Gtk issues
|
||||
- The download options window could not be opened, when the
|
||||
'--cookies-from-browser' option was set with KEYRING and PROFILE
|
||||
components. Fixed (Git #394)
|
||||
- Fix python error when starting Tartube (Git #366)
|
||||
- Fixed video duplication error when downloading videos that already exist on
|
||||
the filesystem
|
||||
- After right-clicking a video and selecting 'Special > Download video clips'
|
||||
or 'Special > Remove video slices', the dialogue windows had several
|
||||
serious issues. Fixed them
|
||||
- In the Classic Mode tab, when a channel/playlist download is interrupted
|
||||
before the download is complete, the URL is not remembered for the next
|
||||
session. Fixed (Git #285)
|
||||
|
||||
MINOR FIXES
|
||||
- When checking/downloading produces no new videos, a 'newbie' dialogue is
|
||||
displayed pointing the user to possible solutions. The dialogue was
|
||||
displayed even when youtube-dl encountered videos that had already been
|
||||
downloaded, or that were registered in youtube-dl's archive file. This no
|
||||
longer happens (Git #368)
|
||||
- In the preferences window, Windows > Videos tab, the 'Show today and
|
||||
yesterday as the date, when possible' setting could not be disabled, once
|
||||
enabled. Fixed
|
||||
- In the edit window for custom downloads, in the Slices tab, the toggle
|
||||
buttons were all broken. Fixed them
|
||||
- After right-clicking a folder and selecting 'Folder contents > All contents >
|
||||
Empty folder', the text of the dialogue window was gibberish. For this
|
||||
procedure and related ones, improved the dialogue layout and tightened up
|
||||
the code. When emptying videos/channels/playlists/folders from the
|
||||
database (but not removing files on the filesystem), added a new
|
||||
confirmation dialogue (Git #332)
|
||||
- The video properties window did not show clip/chapter titles. Fixed
|
||||
(Git #330)
|
||||
- Improved wording in the 'Add channel' dialogue, specifically to remove the
|
||||
word 'automatically'. Updated the video/playlist/folder dialogues too
|
||||
(Git #277)
|
||||
|
||||
v2.3.484 (31 Mar 2022)
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
|
24
README.rst
24
README.rst
@ -59,16 +59,16 @@ For a full list of new features and fixes, see `recent changes <CHANGES>`__.
|
||||
3 Downloads
|
||||
===========
|
||||
|
||||
Stable release: **v2.3.484 (31 Mar 2022)**
|
||||
Stable release: **v2.3.549 (9 Apr 2022)**
|
||||
|
||||
Development release: **v2.3.518 (5 Apr 2022)**
|
||||
Development release: **v2.3.549 (9 Apr 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.484/install-tartube-2.3.484-64bit.exe/download>`__ and `portable edition <https://sourceforge.net/projects/tartube/files/v2.3.484/tartube-2.3.484-64bit-portable.zip/download>`__ from Sourceforge
|
||||
- `MS Windows (64-bit) installer <https://sourceforge.net/projects/tartube/files/v2.3.549/install-tartube-2.3.549-64bit.exe/download>`__ and `portable edition <https://sourceforge.net/projects/tartube/files/v2.3.549/tartube-2.3.549-64bit-portable.zip/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.484/python3-tartube_2.3.484.deb/download>`__ from Sourceforge
|
||||
- `RPM package (for RHEL-based distros, e.g. Fedora) <https://sourceforge.net/projects/tartube/files/v2.3.484/tartube-2.3.484.rpm/download>`__ from Sourceforge
|
||||
- `DEB package (for Debian-based distros, e.g. Ubuntu, Linux Mint) <https://sourceforge.net/projects/tartube/files/v2.3.549/python3-tartube_2.3.549.deb/download>`__ from Sourceforge
|
||||
- `RPM package (for RHEL-based distros, e.g. Fedora) <https://sourceforge.net/projects/tartube/files/v2.3.549/tartube-2.3.549.rpm/download>`__ from Sourceforge
|
||||
|
||||
Official 'Strict' packages:
|
||||
|
||||
@ -649,7 +649,7 @@ Secondly, you could import a text file contaiing a list of channels/playlists. Y
|
||||
|
||||
... where **<url>** is the web address of the channel/playlist. (Leave out the diamond brackets.)
|
||||
|
||||
When you're ready, click **Media > Import into database > Plain text export file...**
|
||||
When you're ready, click **Media > Export/import > Import into database > Plain text export file...**
|
||||
|
||||
6.8.2 Replacing generic channel/playlist names
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -1301,7 +1301,7 @@ You can export the contents of **Tartube**'s database and, at any time in the fu
|
||||
|
||||
It is important to note that *only a list of videos, channels, playlists and folders are exported*. The videos themselves are not exported, and neither are any thumbnail, description or metadata files.
|
||||
|
||||
- Click **Media > Export from database...**
|
||||
- Click **Media > Export/import > Export from database...**
|
||||
- In the dialogue window, choose what you want to export
|
||||
- If you want a list that you can edit in an ordinary text editor, select the **Export as plain text** option
|
||||
- If you want a list that yuu can edit in a spreadsheet, select the **Export as CSV** option
|
||||
@ -1312,7 +1312,7 @@ It is safe to share this export file with other people. It doesn't contain any p
|
||||
|
||||
This is how to import the data into a different **Tartube** database.
|
||||
|
||||
- Click **Media > Import into database...**
|
||||
- Click **Media > Export/import > Import into database...**
|
||||
- Select the export file you created earlier
|
||||
- A dialogue window will appear. You can choose how much of the database you want to import
|
||||
|
||||
@ -1992,7 +1992,7 @@ The export can then be re-imported into your current database in the normal way
|
||||
|
||||
A: Earlier versions of **Tartube** did in fact introduce occasional blips into the database. It's possible (though unlikely) that some blips still exist, despite the best efforts of the authors. If you really want to rebuild the database from scratch, this is how to do it.
|
||||
|
||||
Firstly, click **Media > Export from database...**. In the dialogue window, it's not necessary to select the button **Include lists of videos**. Click the **OK** button. Let Tartube create the backup file. You now have a backup of the names and URLs for every channel/playlist you've added.
|
||||
Firstly, click **Media > Export/import > Export from database...**. In the dialogue window, it's not necessary to select the button **Include lists of videos**. Click the **OK** button. Let Tartube create the backup file. You now have a backup of the names and URLs for every channel/playlist you've added.
|
||||
|
||||
Next, shut down **Tartube**.
|
||||
|
||||
@ -2000,7 +2000,7 @@ Next, shut down **Tartube**.
|
||||
|
||||
Now you can restart **Tartube**. **Tartube** will create a brand new database file.
|
||||
|
||||
Click **Media > Import into database > JSON export file...**. Import the file you created moments ago.
|
||||
Click **Media > Export/import > Import into database > JSON export file...**. Import the file you created moments ago.
|
||||
|
||||
All the channels/playlists should now be visible in the main window. Click the **Check All** button in the bottom-left corner and wait for it to finish.
|
||||
|
||||
@ -2117,9 +2117,9 @@ Tartube can merge a video and audio file together, long after they have been dow
|
||||
|
||||
A: In the main window's toolbar, click the **Hide (most) system folders** button (a red folder)
|
||||
|
||||
A: In the main menu, click **Media > Hide (most) system folders**
|
||||
A: In the main menu, click **Media > Show/hide > Hide (most) system folders**
|
||||
|
||||
A: Right-click the folders you don't want to see, and select **Folder actions > Hide folder**. To reverse this step, in the main menu click **Media > Show hidden folders**
|
||||
A: Right-click the folders you don't want to see, and select **Folder actions > Hide folder**. To reverse this step, in the main menu click **Media > Show/hide > Show hidden folders**
|
||||
|
||||
A: In the main menu, click **Edit > System preferences... > Windows > Videos**, and click **Show smaller icons in the Video Index** to select it
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
# Tartube v2.3.518 installer script for MS Windows
|
||||
# Tartube v2.3.549 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.518-64bit.exe"
|
||||
OutFile "install-tartube-2.3.549-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.518"
|
||||
# "DisplayVersion" "2.3.549"
|
||||
|
||||
# Create uninstaller
|
||||
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||
|
@ -42,8 +42,8 @@ import mainapp
|
||||
|
||||
# 'Global' variables
|
||||
__packagename__ = 'tartube'
|
||||
__version__ = '2.3.518'
|
||||
__date__ = '5 Apr 2022'
|
||||
__version__ = '2.3.549'
|
||||
__date__ = '9 Apr 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.518'
|
||||
__date__ = '5 Apr 2022'
|
||||
__version__ = '2.3.549'
|
||||
__date__ = '9 Apr 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.518'
|
||||
__date__ = '5 Apr 2022'
|
||||
__version__ = '2.3.549'
|
||||
__date__ = '9 Apr 2022'
|
||||
__copyright__ = 'Copyright \xa9 2019-2022 A S Lewis'
|
||||
__license__ = """
|
||||
Copyright \xa9 2019-2022 A S Lewis.
|
||||
|
@ -1,4 +1,4 @@
|
||||
.TH man 1 "5 Apr 2022" "2.3.518" "tartube man page"
|
||||
.TH man 1 "9 Apr 2022" "2.3.549" "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.518
|
||||
Version=2.3.549
|
||||
Exec=tartube
|
||||
Icon=tartube
|
||||
Type=Application
|
||||
|
2
setup.py
2
setup.py
@ -185,7 +185,7 @@ for path in glob.glob('sounds/*'):
|
||||
# Setup
|
||||
setuptools.setup(
|
||||
name='tartube',
|
||||
version='2.3.518',
|
||||
version='2.3.549',
|
||||
description='GUI front-end for youtube-dl',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/plain',
|
||||
|
@ -4646,16 +4646,23 @@ class OptionsEditWin(GenericEditWin):
|
||||
else:
|
||||
entry4.set_text(_('These options are not applied to anything'))
|
||||
|
||||
self.add_label(grid,
|
||||
_(
|
||||
'Additional download options, e.g. --write-subs (do not use -o' \
|
||||
+ ' or --output)',
|
||||
),
|
||||
0, 3, grid_width, 1,
|
||||
)
|
||||
if self.app_obj.simple_options_flag:
|
||||
|
||||
self.add_label(grid,
|
||||
_(
|
||||
'Additional download options, e.g. --write-subs (do not use' \
|
||||
+ ' -o or --output)',
|
||||
),
|
||||
0, 3, grid_width, 1,
|
||||
)
|
||||
|
||||
if not self.app_obj.simple_options_flag:
|
||||
|
||||
self.add_label(grid,
|
||||
_('Additional download options'),
|
||||
0, 3, 1, 1,
|
||||
)
|
||||
|
||||
if os.name == 'nt':
|
||||
|
||||
checkbutton = self.add_checkbutton(grid,
|
||||
@ -4663,7 +4670,7 @@ class OptionsEditWin(GenericEditWin):
|
||||
'Use ONLY these options (Tartube adds the output folder)',
|
||||
),
|
||||
None,
|
||||
0, 4, grid_width, 1,
|
||||
1, 3, (grid_width - 1), 1,
|
||||
)
|
||||
# (Signal connect appears below)
|
||||
|
||||
@ -4675,16 +4682,16 @@ class OptionsEditWin(GenericEditWin):
|
||||
+ ' directory)',
|
||||
),
|
||||
None,
|
||||
0, 4, grid_width, 1,
|
||||
1, 3, (grid_width - 1), 1,
|
||||
)
|
||||
# (Signal connect appears below)
|
||||
|
||||
checkbutton.set_active(self.retrieve_val('direct_cmd_flag'))
|
||||
|
||||
checkbutton2 = self.add_checkbutton(grid,
|
||||
_('Use only the URL specified below'),
|
||||
_('If URLs are specified below, use only those URLs'),
|
||||
'direct_url_flag',
|
||||
0, 5, grid_width, 1,
|
||||
1, 4, (grid_width - 1), 1,
|
||||
)
|
||||
|
||||
# (Signal connects from above)
|
||||
@ -4696,25 +4703,25 @@ class OptionsEditWin(GenericEditWin):
|
||||
|
||||
self.add_textview(grid,
|
||||
'extra_cmd_string',
|
||||
0, 6, grid_width, 1,
|
||||
0, 5, grid_width, 1,
|
||||
)
|
||||
|
||||
if self.app_obj.simple_options_flag:
|
||||
frame = self.add_pixbuf(grid,
|
||||
'hand_right_large',
|
||||
0, 7, 1, 1,
|
||||
0, 6, 1, 1,
|
||||
)
|
||||
frame.set_hexpand(False)
|
||||
|
||||
else:
|
||||
frame = self.add_pixbuf(grid,
|
||||
'hand_left_large',
|
||||
0, 7, 1, 1,
|
||||
0, 6, 1, 1,
|
||||
)
|
||||
frame.set_hexpand(False)
|
||||
|
||||
button2 = Gtk.Button()
|
||||
grid.attach(button2, 1, 7, (grid_width - 1), 1)
|
||||
grid.attach(button2, 1, 6, (grid_width - 1), 1)
|
||||
if not self.app_obj.simple_options_flag:
|
||||
button2.set_label(_('Hide advanced download options'))
|
||||
else:
|
||||
@ -4723,14 +4730,14 @@ class OptionsEditWin(GenericEditWin):
|
||||
|
||||
frame2 = self.add_pixbuf(grid,
|
||||
'copy_large',
|
||||
0, 8, 1, 1,
|
||||
0, 7, 1, 1,
|
||||
)
|
||||
frame2.set_hexpand(False)
|
||||
|
||||
button3 = Gtk.Button(
|
||||
_('Import general download options into this window'),
|
||||
)
|
||||
grid.attach(button3, 1, 8, (grid_width - 1), 1)
|
||||
grid.attach(button3, 1, 7, (grid_width - 1), 1)
|
||||
button3.connect('clicked', self.on_clone_options_clicked)
|
||||
if self.edit_obj == self.app_obj.general_options_obj:
|
||||
# No point cloning the General Options Manager onto itself
|
||||
@ -4738,14 +4745,14 @@ class OptionsEditWin(GenericEditWin):
|
||||
|
||||
frame3 = self.add_pixbuf(grid,
|
||||
'warning_large',
|
||||
0, 9, 1, 1,
|
||||
0, 8, 1, 1,
|
||||
)
|
||||
frame3.set_hexpand(False)
|
||||
|
||||
button4 = Gtk.Button(
|
||||
_('Completely reset all download options to their default values'),
|
||||
)
|
||||
grid.attach(button4, 1, 9, (grid_width - 1), 1)
|
||||
grid.attach(button4, 1, 8, (grid_width - 1), 1)
|
||||
button4.connect('clicked', self.on_reset_options_clicked)
|
||||
|
||||
|
||||
@ -4780,7 +4787,7 @@ class OptionsEditWin(GenericEditWin):
|
||||
self.setup_downloads_videos_tab(inner_notebook)
|
||||
self.setup_downloads_playlists_tab(inner_notebook)
|
||||
self.setup_downloads_limits_tab(inner_notebook)
|
||||
self.setup_downloads_formats_tab(inner_notebook)
|
||||
self.setup_downloads_merge_tab(inner_notebook)
|
||||
self.setup_downloads_extractor_tab(inner_notebook)
|
||||
self.setup_downloads_filtering_tab(inner_notebook)
|
||||
self.setup_downloads_external_tab(inner_notebook)
|
||||
@ -4970,11 +4977,11 @@ class OptionsEditWin(GenericEditWin):
|
||||
row_count = self.downloads_views_widgets(grid, row_count)
|
||||
|
||||
|
||||
def setup_downloads_formats_tab(self, inner_notebook):
|
||||
def setup_downloads_merge_tab(self, inner_notebook):
|
||||
|
||||
"""Called by self.setup_downloads_tab().
|
||||
|
||||
Sets up the 'Formats' inner notebook tab.
|
||||
Sets up the 'Merge' inner notebook tab.
|
||||
|
||||
Args:
|
||||
|
||||
@ -4983,13 +4990,14 @@ class OptionsEditWin(GenericEditWin):
|
||||
"""
|
||||
|
||||
tab, grid = self.add_inner_notebook_tab(
|
||||
_('_Formats'),
|
||||
_('_Merge'),
|
||||
inner_notebook,
|
||||
)
|
||||
|
||||
# Video format options (yt-dlp only)
|
||||
# Video/audio merge options (yt-dlp only)
|
||||
self.add_label(grid,
|
||||
'<u>' + _('Video format options') + '</u>' + self.ytdlp_only(),
|
||||
'<u>' + _('Video/audio merge options') + '</u>' \
|
||||
+ self.ytdlp_only(),
|
||||
0, 0, 1, 1,
|
||||
)
|
||||
|
||||
@ -5007,26 +5015,6 @@ class OptionsEditWin(GenericEditWin):
|
||||
)
|
||||
self.add_tooltip('--audio-multistreams', checkbutton2)
|
||||
|
||||
checkbutton3 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'Check formats selected are actually downloadable' \
|
||||
+ ' (Experimental)',
|
||||
),
|
||||
'check_formats',
|
||||
0, 3, 1, 1,
|
||||
)
|
||||
self.add_tooltip('--check-formats', checkbutton3)
|
||||
|
||||
checkbutton4 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'Allow unplayable formats to be listed and downloaded (also' \
|
||||
+ ' disables post-processing)',
|
||||
),
|
||||
'allow_unplayable_formats',
|
||||
0, 4, 1, 1,
|
||||
)
|
||||
self.add_tooltip('--allow-unplayable-formats', checkbutton4)
|
||||
|
||||
|
||||
def setup_downloads_extractor_tab(self, inner_notebook):
|
||||
|
||||
@ -5118,7 +5106,7 @@ class OptionsEditWin(GenericEditWin):
|
||||
"""
|
||||
|
||||
tab, grid = self.add_inner_notebook_tab(
|
||||
_('F_iltering'),
|
||||
_('_Filtering'),
|
||||
inner_notebook,
|
||||
)
|
||||
|
||||
@ -6752,6 +6740,33 @@ class OptionsEditWin(GenericEditWin):
|
||||
)
|
||||
self.add_tooltip('--youtube-skip-dash-manifest', checkbutton2)
|
||||
|
||||
# Other format options (yt-dlp only)
|
||||
self.add_label(grid,
|
||||
'<u>' + _('Other format options') + '</u>' \
|
||||
+ self.ytdlp_only(),
|
||||
0, (8 + extra_row), grid_width, 1,
|
||||
)
|
||||
|
||||
checkbutton3 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'Check formats selected are actually downloadable' \
|
||||
+ ' (Experimental)',
|
||||
),
|
||||
'check_formats',
|
||||
0, (9 + extra_row), grid_width, 1,
|
||||
)
|
||||
self.add_tooltip('--check-formats', checkbutton3)
|
||||
|
||||
checkbutton4 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'Allow unplayable formats to be listed and downloaded (also' \
|
||||
+ ' disables post-processing)',
|
||||
),
|
||||
'allow_unplayable_formats',
|
||||
0, (10 + extra_row), grid_width, 1,
|
||||
)
|
||||
self.add_tooltip('--allow-unplayable-formats', checkbutton4)
|
||||
|
||||
|
||||
def setup_convert_tab(self):
|
||||
|
||||
@ -14421,10 +14436,18 @@ class VideoEditWin(GenericEditWin):
|
||||
self.on_clear_stamp_button_clicked,
|
||||
)
|
||||
|
||||
button5 = Gtk.Button(_('Reset list using video description'))
|
||||
grid2.attach(button5, 2, 1, 2, 1)
|
||||
button5 = Gtk.Button(_('Reset list using copied text'))
|
||||
grid2.attach(button5, 0, 1, 2, 1)
|
||||
button5.set_hexpand(True)
|
||||
button5.connect(
|
||||
'clicked',
|
||||
self.on_copy_stamp_button_clicked,
|
||||
)
|
||||
|
||||
button6 = Gtk.Button(_('Reset list using video description'))
|
||||
grid2.attach(button6, 2, 1, 2, 1)
|
||||
button6.set_hexpand(True)
|
||||
button6.connect(
|
||||
'clicked',
|
||||
self.on_extract_stamp_button_clicked,
|
||||
)
|
||||
@ -14452,7 +14475,7 @@ class VideoEditWin(GenericEditWin):
|
||||
if mini_list[2] is None:
|
||||
clip_title = ''
|
||||
else:
|
||||
clip_title = mini_list[1]
|
||||
clip_title = mini_list[2]
|
||||
|
||||
self.timestamp_liststore.append(
|
||||
[ start_stamp, stop_stamp, clip_title ],
|
||||
@ -15616,6 +15639,50 @@ class VideoEditWin(GenericEditWin):
|
||||
SystemPrefWin(self.app_obj, 'clips')
|
||||
|
||||
|
||||
def on_copy_stamp_button_clicked(self, button):
|
||||
|
||||
"""Called from a callback in self.setup_timestamps_tab().
|
||||
|
||||
Updates the video's timestamp list using text the user has copied and
|
||||
pasted into a dialogue window.
|
||||
|
||||
Args:
|
||||
|
||||
button (Gtk.Button): The widget clicked
|
||||
|
||||
"""
|
||||
|
||||
# Open the dialogue window
|
||||
dialogue_win = mainwin.AddStampDialogue(
|
||||
self,
|
||||
self.app_obj.main_win_obj,
|
||||
)
|
||||
response = dialogue_win.run()
|
||||
|
||||
# Retrieve user choices from the dialogue window
|
||||
if response == Gtk.ResponseType.OK:
|
||||
|
||||
text = dialogue_win.textbuffer.get_text(
|
||||
dialogue_win.textbuffer.get_start_iter(),
|
||||
dialogue_win.textbuffer.get_end_iter(),
|
||||
# Don't include hidden characters
|
||||
False,
|
||||
)
|
||||
|
||||
# (Do not modify the existing list of timestampes, if no text was
|
||||
# added to the dialogue window)
|
||||
if text != '':
|
||||
self.edit_obj.extract_timestamps_from_descrip(
|
||||
self.app_obj,
|
||||
text,
|
||||
)
|
||||
|
||||
self.setup_timestamps_tab_update_treeview()
|
||||
|
||||
# ...before destroying the dialogue window
|
||||
dialogue_win.destroy()
|
||||
|
||||
|
||||
def on_delete_slice_button_clicked(self, button, treeview):
|
||||
|
||||
"""Called from a callback in self.setup_slices_tab().
|
||||
@ -20578,70 +20645,60 @@ class SystemPrefWin(GenericPrefWin):
|
||||
checkbutton8.connect('toggled', self.on_disable_dl_all_toggled)
|
||||
|
||||
checkbutton9 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'Show a \'Custom download all\' button in the Videos tab',
|
||||
),
|
||||
self.app_obj.show_custom_dl_button_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 7, grid_width, 1,
|
||||
)
|
||||
checkbutton9.connect('toggled', self.on_show_custom_dl_button_toggled)
|
||||
|
||||
checkbutton10 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'In the Progress tab, hide finished downloads',
|
||||
),
|
||||
self.app_obj.progress_list_hide_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 8, 1, 1,
|
||||
0, 7, 1, 1,
|
||||
)
|
||||
checkbutton10.connect('toggled', self.on_hide_button_toggled)
|
||||
checkbutton9.connect('toggled', self.on_hide_button_toggled)
|
||||
|
||||
checkbutton11 = self.add_checkbutton(grid,
|
||||
checkbutton10 = self.add_checkbutton(grid,
|
||||
_('Show downloads in reverse order'),
|
||||
self.app_obj.results_list_reverse_flag,
|
||||
True, # Can be toggled by user
|
||||
1, 8, 2, 1,
|
||||
1, 7, 2, 1,
|
||||
)
|
||||
checkbutton11.connect('toggled', self.on_reverse_button_toggled)
|
||||
checkbutton10.connect('toggled', self.on_reverse_button_toggled)
|
||||
|
||||
checkbutton12 = self.add_checkbutton(grid,
|
||||
checkbutton11 = self.add_checkbutton(grid,
|
||||
_('When Tartube starts, automatically open the Classic Mode tab'),
|
||||
self.app_obj.show_classic_tab_on_startup_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 9, grid_width, 1,
|
||||
0, 8, grid_width, 1,
|
||||
)
|
||||
checkbutton12.connect(
|
||||
checkbutton11.connect(
|
||||
'toggled',
|
||||
self.on_show_classic_mode_button_toggled,
|
||||
)
|
||||
if __main__.__pkg_no_download_flag__:
|
||||
checkbutton12.set_sensitive(False)
|
||||
checkbutton11.set_sensitive(False)
|
||||
|
||||
checkbutton13 = self.add_checkbutton(grid,
|
||||
checkbutton12 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'In the Classic Mode tab, when adding URLs, remove duplicates' \
|
||||
+ ' rather than retaining them',
|
||||
),
|
||||
self.app_obj.classic_duplicate_remove_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 10, grid_width, 1,
|
||||
0, 9, grid_width, 1,
|
||||
)
|
||||
checkbutton13.connect(
|
||||
checkbutton12.connect(
|
||||
'toggled',
|
||||
self.on_remove_duplicate_button_toggled,
|
||||
)
|
||||
|
||||
checkbutton14 = self.add_checkbutton(grid,
|
||||
checkbutton13 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'In the Errors/Warnings tab, don\'t reset the tab text when' \
|
||||
+ ' it is clicked',
|
||||
),
|
||||
self.app_obj.system_msg_keep_totals_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 11, grid_width, 1,
|
||||
0, 10, grid_width, 1,
|
||||
)
|
||||
checkbutton14.connect('toggled', self.on_system_keep_button_toggled)
|
||||
checkbutton13.connect('toggled', self.on_system_keep_button_toggled)
|
||||
|
||||
|
||||
def setup_windows_videos_tab(self, inner_notebook):
|
||||
@ -20666,113 +20723,123 @@ class SystemPrefWin(GenericPrefWin):
|
||||
)
|
||||
|
||||
checkbutton = self.add_checkbutton(grid,
|
||||
_(
|
||||
'Show smaller icons in the Video Index',
|
||||
),
|
||||
self.app_obj.show_small_icons_in_index_flag,
|
||||
_('Show a \'Custom download all\' button'),
|
||||
self.app_obj.show_custom_dl_button_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 1, grid_width, 1,
|
||||
)
|
||||
checkbutton.connect('toggled', self.on_show_custom_dl_button_toggled)
|
||||
|
||||
checkbutton2 = self.add_checkbutton(grid,
|
||||
_('Allow each row to be marked for checking/downloading'),
|
||||
self.app_obj.show_marker_in_index_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 2, grid_width, 1,
|
||||
)
|
||||
checkbutton.connect('toggled', self.on_show_small_icons_toggled)
|
||||
checkbutton2.connect('toggled', self.on_show_selector_button_toggled)
|
||||
|
||||
checkbutton2 = self.add_checkbutton(grid,
|
||||
checkbutton3 = self.add_checkbutton(grid,
|
||||
_('Show smaller icons'),
|
||||
self.app_obj.show_small_icons_in_index_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 3, grid_width, 1,
|
||||
)
|
||||
checkbutton3.connect('toggled', self.on_show_small_icons_toggled)
|
||||
|
||||
checkbutton4 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'In the Video Index, show detailed statistics about the videos' \
|
||||
+ ' in each channel / playlist / folder',
|
||||
'Show detailed statistics about the videos in each channel' \
|
||||
+ ' / playlist / folder',
|
||||
),
|
||||
self.app_obj.complex_index_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 3, grid_width, 1,
|
||||
0, 4, grid_width, 1,
|
||||
)
|
||||
checkbutton2.connect('toggled', self.on_complex_button_toggled)
|
||||
checkbutton4.connect('toggled', self.on_complex_button_toggled)
|
||||
|
||||
checkbutton3 = self.add_checkbutton(grid,
|
||||
checkbutton5 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'After clicking on a folder, automatically expand/collapse the' \
|
||||
+ ' tree around it',
|
||||
),
|
||||
self.app_obj.auto_expand_video_index_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 4, grid_width, 1,
|
||||
0, 5, grid_width, 1,
|
||||
)
|
||||
# (Signal connect appears below)
|
||||
|
||||
checkbutton4 = self.add_checkbutton(grid,
|
||||
checkbutton6 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'Expand the whole tree, not just the level beneath the clicked' \
|
||||
+ ' folder',
|
||||
),
|
||||
self.app_obj.full_expand_video_index_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 5, grid_width, 1,
|
||||
0, 6, grid_width, 1,
|
||||
)
|
||||
if not self.app_obj.auto_expand_video_index_flag:
|
||||
checkbutton4.set_sensitive(False)
|
||||
checkbutton6.set_sensitive(False)
|
||||
# (Signal connect appears below)
|
||||
|
||||
# (Signal connects from above)
|
||||
checkbutton3.connect(
|
||||
checkbutton5.connect(
|
||||
'toggled',
|
||||
self.on_expand_tree_toggled,
|
||||
checkbutton4,
|
||||
checkbutton6,
|
||||
)
|
||||
checkbutton4.connect('toggled', self.on_expand_full_tree_toggled)
|
||||
checkbutton6.connect('toggled', self.on_expand_full_tree_toggled)
|
||||
|
||||
# Video Catalogue (right side of the Videos tab)
|
||||
self.add_label(grid,
|
||||
'<u>' + _('Video Catalogue (right side of the Videos tab)') \
|
||||
+ '</u>',
|
||||
0, 6, grid_width, 1,
|
||||
)
|
||||
|
||||
checkbutton5 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'Show \'today\' and \'yesterday\' as the date, when possible',
|
||||
),
|
||||
self.app_obj.show_pretty_dates_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 7, grid_width, 1,
|
||||
)
|
||||
checkbutton5.connect('toggled', self.on_pretty_date_button_toggled)
|
||||
|
||||
checkbutton6 = self.add_checkbutton(grid,
|
||||
_('Show livestreams with a different background colour'),
|
||||
self.app_obj.livestream_use_colour_flag,
|
||||
checkbutton7 = self.add_checkbutton(grid,
|
||||
_('Show \'today\' and \'yesterday\' as the date, when possible'),
|
||||
self.app_obj.show_pretty_dates_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 8, grid_width, 1,
|
||||
)
|
||||
# (Signal connect appears below)
|
||||
checkbutton7.connect('toggled', self.on_pretty_date_button_toggled)
|
||||
|
||||
checkbutton7 = self.add_checkbutton(grid,
|
||||
_('Use same background colours for livestream and debut videos'),
|
||||
self.app_obj.livestream_simple_colour_flag,
|
||||
checkbutton8 = self.add_checkbutton(grid,
|
||||
_('Show livestreams with a different background colour'),
|
||||
self.app_obj.livestream_use_colour_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 9, grid_width, 1,
|
||||
)
|
||||
# (Signal connect appears below)
|
||||
|
||||
checkbutton9 = self.add_checkbutton(grid,
|
||||
_('Use same background colours for livestream and debut videos'),
|
||||
self.app_obj.livestream_simple_colour_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 10, grid_width, 1,
|
||||
)
|
||||
if not self.app_obj.livestream_use_colour_flag:
|
||||
checkbutton7.set_sensitive(False)
|
||||
checkbutton9.set_sensitive(False)
|
||||
# (Signal connect appears below)
|
||||
|
||||
# (Signal connects from above)
|
||||
checkbutton6.connect(
|
||||
checkbutton8.connect(
|
||||
'toggled',
|
||||
self.on_livestream_colour_button_toggled,
|
||||
checkbutton7,
|
||||
checkbutton9,
|
||||
)
|
||||
checkbutton7.connect(
|
||||
checkbutton9.connect(
|
||||
'toggled',
|
||||
self.on_livestream_simple_button_toggled,
|
||||
)
|
||||
|
||||
checkbutton8 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'Channel and playlist names are clickable (grid mode only)',
|
||||
),
|
||||
checkbutton10 = self.add_checkbutton(grid,
|
||||
_('Channel and playlist names are clickable (grid mode only)'),
|
||||
self.app_obj.catalogue_clickable_container_flag,
|
||||
True, # Can be toggled by user
|
||||
0, 10, grid_width, 1,
|
||||
0, 11, grid_width, 1,
|
||||
)
|
||||
checkbutton8.connect('toggled', self.on_clickable_button_toggled)
|
||||
checkbutton10.connect('toggled', self.on_clickable_button_toggled)
|
||||
|
||||
|
||||
def setup_windows_drag_tab(self, inner_notebook):
|
||||
@ -21072,6 +21139,24 @@ class SystemPrefWin(GenericPrefWin):
|
||||
_('Selected broadcasting videos'),
|
||||
)
|
||||
|
||||
self.setup_windows_colours_tab_add_row(grid,
|
||||
8,
|
||||
'drag_drop_notify',
|
||||
_('Drag and Drop notification'),
|
||||
)
|
||||
|
||||
self.setup_windows_colours_tab_add_row(grid,
|
||||
9,
|
||||
'drag_drop_odd',
|
||||
_('Drag and Drop background 1'),
|
||||
)
|
||||
|
||||
self.setup_windows_colours_tab_add_row(grid,
|
||||
10,
|
||||
'drag_drop_even',
|
||||
_('Drag and Drop background 2'),
|
||||
)
|
||||
|
||||
|
||||
def setup_windows_colours_tab_add_row(self, grid, row_num, key, descrip):
|
||||
|
||||
@ -22729,8 +22814,8 @@ class SystemPrefWin(GenericPrefWin):
|
||||
|
||||
checkbutton2 = self.add_checkbutton(grid,
|
||||
_(
|
||||
'Create an archive file when downloading from the Classic' \
|
||||
+ ' Mode tab (not recommended)',
|
||||
'Create an archive file when downloading from the Classic Mode' \
|
||||
+ ' tab',
|
||||
),
|
||||
self.app_obj.classic_ytdl_archive_flag,
|
||||
True, # Can be toggled by user
|
||||
@ -22738,6 +22823,14 @@ class SystemPrefWin(GenericPrefWin):
|
||||
)
|
||||
checkbutton2.connect('toggled', self.on_archive_classic_button_toggled)
|
||||
|
||||
self.add_label(grid,
|
||||
'<i>' + _(
|
||||
'This setting should only be enabled when downloading' \
|
||||
+ ' channels and playlists',
|
||||
) + '</i>',
|
||||
0, 9, grid_width, 1,
|
||||
)
|
||||
|
||||
|
||||
def setup_operations_livestreams_tab(self, inner_notebook):
|
||||
|
||||
@ -24337,11 +24430,11 @@ class SystemPrefWin(GenericPrefWin):
|
||||
|
||||
for i, column_title in enumerate(
|
||||
[
|
||||
'#', _('Name'), _('Videos tab'), _('Classic Mode tab'),
|
||||
_('Applied to media'),
|
||||
'#', _('Name'), _('Videos tab'), _('Classic Mode'),
|
||||
_('Dropzone'), _('Applied to media'),
|
||||
]
|
||||
):
|
||||
if i == 2 or i == 3:
|
||||
if i >= 2 and i <= 4:
|
||||
renderer_toggle = Gtk.CellRendererToggle()
|
||||
column_toggle = Gtk.TreeViewColumn(
|
||||
column_title,
|
||||
@ -24360,7 +24453,7 @@ class SystemPrefWin(GenericPrefWin):
|
||||
treeview.append_column(column_text)
|
||||
column_text.set_resizable(True)
|
||||
|
||||
self.options_liststore = Gtk.ListStore(int, str, bool, bool, str)
|
||||
self.options_liststore = Gtk.ListStore(int, str, bool, bool, bool, str)
|
||||
treeview.set_model(self.options_liststore)
|
||||
|
||||
# Initialise the list
|
||||
@ -24511,6 +24604,11 @@ class SystemPrefWin(GenericPrefWin):
|
||||
else:
|
||||
row_list.append(False)
|
||||
|
||||
if options_obj.uid in self.app_obj.classic_dropzone_list:
|
||||
row_list.append(True)
|
||||
else:
|
||||
row_list.append(False)
|
||||
|
||||
if options_obj.dbid is None:
|
||||
|
||||
row_list.append('')
|
||||
@ -25290,7 +25388,9 @@ class SystemPrefWin(GenericPrefWin):
|
||||
"""Called from callback in self.setup_operations_archive_tab().
|
||||
|
||||
Enables/disables creation of youtube-dl's archive file,
|
||||
ytdl-archive.txt, when downloading from the Classic Mode tab.
|
||||
ytdl-archive.txt, when downloading from the Classic Mode tab. Toggling
|
||||
the corresponding Gtk.ToggleButton in the Classic Mode tab sets the IV
|
||||
(and makes sure the two buttons have the same status).
|
||||
|
||||
Args:
|
||||
|
||||
@ -25298,12 +25398,13 @@ class SystemPrefWin(GenericPrefWin):
|
||||
|
||||
"""
|
||||
|
||||
if checkbutton.get_active() \
|
||||
and not self.app_obj.classic_ytdl_archive_flag:
|
||||
self.app_obj.set_classic_ytdl_archive_flag(True)
|
||||
elif not checkbutton.get_active() \
|
||||
and self.app_obj.classic_ytdl_archive_flag:
|
||||
self.app_obj.set_classic_ytdl_archive_flag(False)
|
||||
main_win_obj = self.app_obj.main_win_obj
|
||||
|
||||
other_flag = main_win_obj.classic_archive_button.get_active()
|
||||
if (checkbutton.get_active() and not other_flag):
|
||||
main_win_obj.classic_archive_button.set_active(True)
|
||||
elif (not checkbutton.get_active() and other_flag):
|
||||
main_win_obj.classic_archive_button.set_active(False)
|
||||
|
||||
|
||||
def on_archive_radiobutton_toggled(self, widget, radiobutton, \
|
||||
@ -30666,6 +30767,27 @@ class SystemPrefWin(GenericPrefWin):
|
||||
)
|
||||
|
||||
|
||||
def on_show_selector_button_toggled(self, checkbutton):
|
||||
|
||||
"""Called from callback in self.setup_windows_main_window_tab().
|
||||
|
||||
Enables/disables showing the selector button in each row of the Video
|
||||
Index.
|
||||
|
||||
Args:
|
||||
|
||||
checkbutton (Gtk.CheckButton): The widget clicked
|
||||
|
||||
"""
|
||||
|
||||
if checkbutton.get_active() \
|
||||
and not self.app_obj.show_marker_in_index_flag:
|
||||
self.app_obj.set_show_marker_in_index_flag(True)
|
||||
elif not checkbutton.get_active() \
|
||||
and self.app_obj.show_marker_in_index_flag:
|
||||
self.app_obj.set_show_marker_in_index_flag(False)
|
||||
|
||||
|
||||
def on_show_small_icons_toggled(self, checkbutton):
|
||||
|
||||
"""Called from callback in self.setup_windows_videos_tab().
|
||||
|
@ -1381,6 +1381,14 @@ class DownloadWorker(threading.Thread):
|
||||
for vid in self.downloader_obj.video_msg_buffer_dict.keys():
|
||||
self.downloader_obj.process_error_warning(vid)
|
||||
|
||||
# Unless the download was stopped manually (return code 5), any
|
||||
# 'dummy' media.Video objects can be set, so that their URLs are
|
||||
# not remembered in the next Tartube session
|
||||
if isinstance(media_data_obj, media.Video) \
|
||||
and media_data_obj.dummy_flag \
|
||||
and return_code < 5:
|
||||
media_data_obj.set_dummy_dl_flag(True)
|
||||
|
||||
# If the download stalled, -1 is returned. If we're allowed to
|
||||
# restart a stalled download, do that; otherwise give up
|
||||
if return_code > -1 \
|
||||
@ -1498,7 +1506,7 @@ class DownloadWorker(threading.Thread):
|
||||
and custom_dl_obj.split_flag \
|
||||
and media_data_obj.stamp_list
|
||||
) or app_obj.temp_stamp_list:
|
||||
return_code = self.downloader_obj.do_download_clip()
|
||||
return_code = self.downloader_obj.do_download_clips()
|
||||
else:
|
||||
return_code = self.downloader_obj.do_download_remove_slices()
|
||||
|
||||
@ -1645,6 +1653,7 @@ class DownloadWorker(threading.Thread):
|
||||
# the whole RSS feed)
|
||||
time_limit_video_obj = None
|
||||
check_source_list = []
|
||||
check_name_list = []
|
||||
|
||||
if app_obj.livestream_max_days:
|
||||
|
||||
@ -1656,15 +1665,16 @@ class DownloadWorker(threading.Thread):
|
||||
)
|
||||
|
||||
for child_obj in container_obj.child_list:
|
||||
if child_obj.source:
|
||||
|
||||
# An entry in the RSS feed is a new livestream, if it
|
||||
# doesn't match one of the videos in this list
|
||||
# (We don't need to check each RSS entry against the
|
||||
# entire contents of the channel/playlist - which might
|
||||
# be thousands of videos - just those up to the time
|
||||
# limit)
|
||||
# An entry in the RSS feed is a new livestream, if it doesn't
|
||||
# match one of the videos in these lists
|
||||
# (We don't need to check each RSS entry against the entire
|
||||
# contents of the channel/playlist - which might be thousands
|
||||
# of videos - just those up to the time limit)
|
||||
if child_obj.source:
|
||||
check_source_list.append(child_obj.source)
|
||||
if child_obj.name != app_obj.default_video_name:
|
||||
check_name_list.append(child_obj.name)
|
||||
|
||||
# The time limit will apply to this video, when found
|
||||
for child_obj in container_obj.child_list:
|
||||
@ -1682,6 +1692,8 @@ class DownloadWorker(threading.Thread):
|
||||
for child_obj in container_obj.child_list:
|
||||
if child_obj.source:
|
||||
check_source_list.append(child_obj.source)
|
||||
if child_obj.name != app_obj.default_video_name:
|
||||
check_name_list.append(child_obj.name)
|
||||
|
||||
for child_obj in container_obj.child_list:
|
||||
if child_obj.source \
|
||||
@ -1707,7 +1719,8 @@ class DownloadWorker(threading.Thread):
|
||||
# for livestreams now
|
||||
break
|
||||
|
||||
elif not entry_dict['link'] in check_source_list:
|
||||
elif not entry_dict['link'] in check_source_list \
|
||||
and not entry_dict['title'] in check_name_list:
|
||||
|
||||
# New livestream detected. Create a new JSONFetcher object to
|
||||
# fetch its JSON data
|
||||
@ -3889,6 +3902,10 @@ class VideoDownloader(object):
|
||||
|
||||
else:
|
||||
|
||||
# Register the download with DownloadManager, so that
|
||||
# download limits can be applied, if required
|
||||
self.download_manager_obj.register_video('old')
|
||||
|
||||
# This video applies towards the limit (if any) specified
|
||||
# by mainapp.TartubeApp.operation_download_limit
|
||||
self.video_limit_count += 1
|
||||
@ -3947,6 +3964,10 @@ class VideoDownloader(object):
|
||||
),
|
||||
)
|
||||
|
||||
# Register the download with DownloadManager, so that
|
||||
# download limits can be applied, if required
|
||||
self.download_manager_obj.register_video('new')
|
||||
|
||||
# The probable video ID, if captured, can now be reset
|
||||
self.probable_video_id = None
|
||||
|
||||
@ -4906,6 +4927,14 @@ class VideoDownloader(object):
|
||||
# (Flag set to True when self.confirm_new_video(), etc, are called)
|
||||
confirm_flag = False
|
||||
|
||||
# The '[download] XXX has already been recorded in the archive'
|
||||
# message does not cause a call to self.confirm_new_video(), etc,
|
||||
# so we must handle it here
|
||||
# (Note that the first word might be '[download]', or '[Youtube]', etc)
|
||||
if re.search('^.*has already been recorded in the archive$', stdout):
|
||||
self.download_manager_obj.register_video('other')
|
||||
return dl_stat_dict
|
||||
|
||||
# Extract the data
|
||||
stdout_list[0] = stdout_list[0].lstrip('\r')
|
||||
if stdout_list[0] == '[download]':
|
||||
|
@ -735,7 +735,7 @@ class FFmpegOptionsManager(object):
|
||||
source file is used (so that a specimen system command can be
|
||||
displayed in the edit window)
|
||||
|
||||
start_point, stop_points, clip_title, clip_dir (str):
|
||||
start_point, stop_point, clip_title, clip_dir (str):
|
||||
When splitting a video, the points at which to start/stop
|
||||
(timestamps or values in seconds), the clip title, and the
|
||||
destination directory for sections (if not the same as the
|
||||
@ -1142,11 +1142,8 @@ class FFmpegOptionsManager(object):
|
||||
return_list.append(str(start_point))
|
||||
|
||||
# (If no timestamp is specified, the end of the video is used)
|
||||
return_list.append('-to')
|
||||
if stop_point is None:
|
||||
# (A specimen time, in seconds)
|
||||
return_list.append('100')
|
||||
else:
|
||||
if stop_point is not None:
|
||||
return_list.append('-to')
|
||||
return_list.append(str(stop_point))
|
||||
|
||||
if clip_title is None or clip_title == "":
|
||||
@ -1179,3 +1176,11 @@ class FFmpegOptionsManager(object):
|
||||
return source_thumb_path, output_path, return_list
|
||||
else:
|
||||
return source_video_path, output_path, return_list
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -391,6 +391,9 @@ class TartubeApp(Gtk.Application):
|
||||
# should be replaced by a custom set of icons (in case the stock
|
||||
# icons are not visible, for some reason)
|
||||
self.show_custom_icons_flag = False
|
||||
# Flag set to True if a marker should be visible on each row in the
|
||||
# Video Index, false if not
|
||||
self.show_marker_in_index_flag = True
|
||||
# Flag set to True if small icons should be used in the Video Index,
|
||||
# False if large icons should be used
|
||||
self.show_small_icons_in_index_flag = False
|
||||
@ -1769,9 +1772,6 @@ class TartubeApp(Gtk.Application):
|
||||
# downloading from the Classic Mode tab (this is marked 'not
|
||||
# recommended' in the edit window)
|
||||
self.classic_ytdl_archive_flag = False
|
||||
# Flag set to True when re-downloading video(s), so that the archive
|
||||
# file is not used at all (otherwise, the re-download will fail)
|
||||
self.block_ytdl_archive_flag = False
|
||||
|
||||
# Flag set to True if, when checking videos/channels/playlists, we
|
||||
# should timeout after 60 seconds (in case youtube-dl gets stuck
|
||||
@ -2145,14 +2145,18 @@ class TartubeApp(Gtk.Application):
|
||||
# Dictionary of default background colours
|
||||
self.default_bg_table = {
|
||||
# Not selected
|
||||
'live_wait': [1, 0, 0, 0.1], # Red
|
||||
'live_now': [0, 1, 0, 0.2], # Green
|
||||
'debut_wait': [1, 1, 0, 0.2], # Yellow
|
||||
'debut_now': [0, 1, 1, 0.2], # Cyan
|
||||
'live_wait': [1, 0, 0, 0.1], # Red
|
||||
'live_now': [0, 1, 0, 0.2], # Green
|
||||
'debut_wait': [1, 1, 0, 0.2], # Yellow
|
||||
'debut_now': [0, 1, 1, 0.2], # Cyan
|
||||
# Selected
|
||||
'select': [0, 0, 1, 0.1], # Blue
|
||||
'select_wait': [1, 0, 1, 0.1], # Purple
|
||||
'select_live': [1, 0, 1, 0.1], # Purple
|
||||
'select': [0, 0, 1, 0.1], # Blue
|
||||
'select_wait': [1, 0, 1, 0.1], # Purple
|
||||
'select_live': [1, 0, 1, 0.1], # Purple
|
||||
# Drag and drop tab
|
||||
'drag_drop_notify': [1, 0, 1, 0.1], # Purple
|
||||
'drag_drop_odd': [1, 1, 0, 0.1], # Orange
|
||||
'drag_drop_even': [1, 1, 0, 0.05], # Pale orange
|
||||
}
|
||||
# Dictionary of customisable colours
|
||||
self.custom_bg_table = self.default_bg_table.copy()
|
||||
@ -2413,6 +2417,13 @@ class TartubeApp(Gtk.Application):
|
||||
show_hidden_menu_action.connect('activate', self.on_menu_show_hidden)
|
||||
self.add_action(show_hidden_menu_action)
|
||||
|
||||
unmark_all_menu_action = Gio.SimpleAction.new(
|
||||
'unmark_all_menu',
|
||||
None,
|
||||
)
|
||||
unmark_all_menu_action.connect('activate', self.on_menu_unmark_all)
|
||||
self.add_action(unmark_all_menu_action)
|
||||
|
||||
if self.debug_test_media_menu_flag:
|
||||
test_menu_action = Gio.SimpleAction.new('test_menu', None)
|
||||
test_menu_action.connect('activate', self.on_menu_test)
|
||||
@ -2791,7 +2802,7 @@ class TartubeApp(Gtk.Application):
|
||||
'check_all_button',
|
||||
None,
|
||||
)
|
||||
check_all_button_action.connect('activate', self.on_menu_check_all)
|
||||
check_all_button_action.connect('activate', self.on_button_check_all)
|
||||
self.add_action(check_all_button_action)
|
||||
|
||||
download_all_button_action = Gio.SimpleAction.new(
|
||||
@ -2800,7 +2811,7 @@ class TartubeApp(Gtk.Application):
|
||||
)
|
||||
download_all_button_action.connect(
|
||||
'activate',
|
||||
self.on_menu_download_all,
|
||||
self.on_button_download_all,
|
||||
)
|
||||
self.add_action(download_all_button_action)
|
||||
|
||||
@ -2810,7 +2821,7 @@ class TartubeApp(Gtk.Application):
|
||||
)
|
||||
custom_dl_all_button_action.connect(
|
||||
'activate',
|
||||
self.on_menu_custom_dl_all,
|
||||
self.on_button_custom_dl_all,
|
||||
)
|
||||
self.add_action(custom_dl_all_button_action)
|
||||
|
||||
@ -2909,6 +2920,16 @@ class TartubeApp(Gtk.Application):
|
||||
)
|
||||
self.add_action(classic_stop_button_action)
|
||||
|
||||
classic_archive_button_action = Gio.SimpleAction.new(
|
||||
'classic_archive_button',
|
||||
None,
|
||||
)
|
||||
classic_archive_button_action.connect(
|
||||
'activate',
|
||||
self.on_button_classic_archive,
|
||||
)
|
||||
self.add_action(classic_archive_button_action)
|
||||
|
||||
classic_ffmpeg_button_action = Gio.SimpleAction.new(
|
||||
'classic_ffmpeg_button',
|
||||
None,
|
||||
@ -3974,6 +3995,9 @@ class TartubeApp(Gtk.Application):
|
||||
if version >= 2001036: # v2.1.036
|
||||
self.show_custom_icons_flag \
|
||||
= json_dict['show_custom_icons_flag']
|
||||
if version >= 2003541: # v2.3.541
|
||||
self.show_marker_in_index_flag \
|
||||
= json_dict['show_marker_in_index_flag']
|
||||
if version >= 2001036: # v2.1.036
|
||||
self.show_small_icons_in_index_flag \
|
||||
= json_dict['show_small_icons_in_index_flag']
|
||||
@ -4591,6 +4615,12 @@ class TartubeApp(Gtk.Application):
|
||||
|
||||
if version >= 2003195: # v2.3.195
|
||||
self.custom_bg_table = json_dict['custom_bg_table']
|
||||
if version < 2003537: # v2.3.537
|
||||
# (New key-value pairs added)
|
||||
for key in [
|
||||
'drag_drop_notify', 'drag_drop_odd', 'drag_drop_even',
|
||||
]:
|
||||
self.custom_bg_table[key] = self.default_bg_table[key]
|
||||
|
||||
if version >= 2003230: # v2.3.230
|
||||
self.ytdlp_filter_options_flag \
|
||||
@ -5080,6 +5110,7 @@ class TartubeApp(Gtk.Application):
|
||||
'show_tooltips_flag': self.show_tooltips_flag,
|
||||
'show_tooltips_extra_flag': self.show_tooltips_extra_flag,
|
||||
'show_custom_icons_flag': self.show_custom_icons_flag,
|
||||
'show_marker_in_index_flag': self.show_marker_in_index_flag,
|
||||
'show_small_icons_in_index_flag': \
|
||||
self.show_small_icons_in_index_flag,
|
||||
'auto_expand_video_index_flag': self.auto_expand_video_index_flag,
|
||||
@ -5561,6 +5592,7 @@ class TartubeApp(Gtk.Application):
|
||||
# (Don't reset the Errors/Warnings tab, as failed attempts to load a
|
||||
# database generate messages there)
|
||||
if self.main_win_obj:
|
||||
self.main_win_obj.video_index_reset_marker()
|
||||
self.main_win_obj.video_index_reset()
|
||||
self.main_win_obj.video_catalogue_reset()
|
||||
self.main_win_obj.progress_list_reset()
|
||||
@ -7078,6 +7110,13 @@ class TartubeApp(Gtk.Application):
|
||||
custom_dl_obj.dl_if_stream_flag = False
|
||||
custom_dl_obj.dl_if_old_stream_flag = False
|
||||
|
||||
if version < 2003536: # v2.3.536
|
||||
|
||||
# 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.dummy_dl_flag = False
|
||||
|
||||
|
||||
# --- Do this last, or the call to .check_integrity_db() fails -------
|
||||
# --------------------------------------------------------------------
|
||||
@ -10363,10 +10402,6 @@ class TartubeApp(Gtk.Application):
|
||||
if self.main_win_obj.is_visible():
|
||||
self.main_win_obj.show_all()
|
||||
|
||||
# If the youtube-dl archive file(s) were temporarily blocked for a
|
||||
# video re-download, re-enable them
|
||||
self.block_ytdl_archive_flag = True
|
||||
|
||||
# If Tartube is due to shut down, then shut it down
|
||||
show_newbie_dialogue_flag = False
|
||||
|
||||
@ -11376,7 +11411,7 @@ class TartubeApp(Gtk.Application):
|
||||
del_corrupt_flag: True if corrupted video files should be
|
||||
deleted
|
||||
|
||||
exist_Flag: True if video files that should exist should be
|
||||
exist_flag: True if video files that should exist should be
|
||||
checked, in case they don't (and vice-versa)
|
||||
|
||||
del_video_flag: True if downloaded video files should be
|
||||
@ -11386,6 +11421,16 @@ class TartubeApp(Gtk.Application):
|
||||
name should be deleted (as artefacts of post-processing
|
||||
with FFmpeg or AVConv)
|
||||
|
||||
remove_no_url_flag: True if any media.Video objects whose URL
|
||||
is not set should be removed from the database (no files
|
||||
are deleted)
|
||||
|
||||
remove_dupe_flag: True if any media.Video objects, which are
|
||||
not marked as downloaded and which share a URL with
|
||||
another media.Video object with the same parent and which
|
||||
is marked as downloaded, should be removed from the
|
||||
database (no files are deleted)
|
||||
|
||||
del_archive_flag: True if all youtube-dl archive files should
|
||||
be deleted
|
||||
|
||||
@ -12023,6 +12068,11 @@ class TartubeApp(Gtk.Application):
|
||||
no_sort_flag,
|
||||
)
|
||||
|
||||
# Update the video name/nickname, if it is not set
|
||||
if video_obj.name == self.default_video_name:
|
||||
video_obj.set_name(filename)
|
||||
video_obj.set_nickname(filename)
|
||||
|
||||
# Update the filepath. Even if it is already known, the extension may
|
||||
# have changed (for example, after checking a video, then downloading
|
||||
# it)
|
||||
@ -12218,7 +12268,7 @@ class TartubeApp(Gtk.Application):
|
||||
if video_obj.name == self.default_video_name:
|
||||
video_obj.set_name(video_obj.file_name)
|
||||
# (The video's title, stored in the .nickname IV, will be updated
|
||||
# from the JSON data in a momemnt)
|
||||
# from the JSON data in a moment)
|
||||
video_obj.set_nickname(video_obj.file_name)
|
||||
|
||||
# Set the file size
|
||||
@ -14124,7 +14174,7 @@ class TartubeApp(Gtk.Application):
|
||||
if response != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
# Get a second confirmation, if required to delete files
|
||||
# Get a second confirmation
|
||||
if delete_file_flag:
|
||||
|
||||
self.dialogue_manager_obj.show_msg_dialogue(
|
||||
@ -14138,44 +14188,60 @@ class TartubeApp(Gtk.Application):
|
||||
# Arguments passed directly to .delete_container_continue()
|
||||
{
|
||||
'yes': 'delete_container_continue',
|
||||
'data': [media_data_obj, empty_flag],
|
||||
'data': [media_data_obj, empty_flag, delete_file_flag],
|
||||
}
|
||||
)
|
||||
|
||||
# No second confirmation required, so we can proceed directly to the
|
||||
# call to self.delete_container_complete()
|
||||
else:
|
||||
self.delete_container_complete(media_data_obj, empty_flag)
|
||||
|
||||
self.dialogue_manager_obj.show_msg_dialogue(
|
||||
_(
|
||||
'Are you SURE you want to remove these items from your' \
|
||||
+ ' database? This procedure cannot be reversed!',
|
||||
),
|
||||
'question',
|
||||
'yes-no',
|
||||
None, # Parent window is main window
|
||||
# Arguments passed directly to .delete_container_continue()
|
||||
{
|
||||
'yes': 'delete_container_continue',
|
||||
'data': [media_data_obj, empty_flag, delete_file_flag],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def delete_container_continue(self, data_list):
|
||||
|
||||
"""Called by self.delete_container().
|
||||
|
||||
When deleting a container, after the user has specified that files
|
||||
should be deleted too, this function is called to delete those files.
|
||||
After getting a confirmation from the user, continue with the
|
||||
deletion process.
|
||||
|
||||
Args:
|
||||
|
||||
data_list (list): A list of two items. The first is the container
|
||||
data_list (list): A list of three items. The first is the container
|
||||
media data object; the second is a flag set to True if the
|
||||
container should be emptied, rather than being deleted
|
||||
container should be emptied, rather than being deleted; the
|
||||
third is True if files should be deleted from the user's
|
||||
filesystem
|
||||
|
||||
"""
|
||||
|
||||
# Unpack the arguments
|
||||
media_data_obj = data_list[0]
|
||||
empty_flag = data_list[1]
|
||||
delete_file_flag = data_list[2]
|
||||
|
||||
# Confirmation obtained, so delete the files
|
||||
container_dir = media_data_obj.get_default_dir(self)
|
||||
if os.path.isdir(container_dir):
|
||||
self.remove_directory(container_dir)
|
||||
if delete_file_flag:
|
||||
|
||||
# If emptying the container rather than deleting it, just create a
|
||||
# replacement (empty) directory on the filesystem
|
||||
if empty_flag:
|
||||
self.make_directory(container_dir)
|
||||
container_dir = media_data_obj.get_default_dir(self)
|
||||
if os.path.isdir(container_dir):
|
||||
self.remove_directory(container_dir)
|
||||
|
||||
# If emptying the container rather than deleting it, just create a
|
||||
# replacement (empty) directory on the filesystem
|
||||
if empty_flag:
|
||||
self.make_directory(container_dir)
|
||||
|
||||
# Now call self.delete_container_complete() to handle the media data
|
||||
# registry
|
||||
@ -14185,8 +14251,8 @@ class TartubeApp(Gtk.Application):
|
||||
def delete_container_complete(self, media_data_obj, empty_flag,
|
||||
recursive_flag=False):
|
||||
|
||||
"""Called by self.delete_container() and .delete_container_continue().
|
||||
Subsequently called by this function recursively.
|
||||
"""Called by self.delete_container_continue(). Subsequently called by
|
||||
this function recursively.
|
||||
|
||||
Deletes a channel, playlist or folder object from the media data
|
||||
registry.
|
||||
@ -16482,6 +16548,10 @@ class TartubeApp(Gtk.Application):
|
||||
del self.media_unavailable_dict[old_name]
|
||||
self.media_unavailable_dict[new_name] = media_data_obj.dbid
|
||||
|
||||
# Update the IV which keeps track of Video Index markers, as it
|
||||
# stores the container's name as a key
|
||||
self.main_win_obj.video_index_update_marker(old_name, new_name)
|
||||
|
||||
# Reset the Video Index and the Video Catalogue (this prevents a
|
||||
# lot of problems)
|
||||
self.main_win_obj.video_index_catalogue_reset()
|
||||
@ -16550,6 +16620,10 @@ class TartubeApp(Gtk.Application):
|
||||
del self.media_unavailable_dict[old_name]
|
||||
self.media_unavailable_dict[new_name] = media_data_obj.dbid
|
||||
|
||||
# Update the IV which keeps track of Video Index markers, as it stores
|
||||
# the container's name as a key
|
||||
self.main_win_obj.video_index_update_marker(old_name, new_name)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@ -20656,6 +20730,24 @@ class TartubeApp(Gtk.Application):
|
||||
self.main_win_obj.video_catalogue_apply_filter()
|
||||
|
||||
|
||||
def on_button_cancel_date(self, action, par):
|
||||
|
||||
"""Called from a callback in self.do_startup().
|
||||
|
||||
Changes the Video Catalogue page to the first one, after showing a page
|
||||
matching a particular date.
|
||||
|
||||
Args:
|
||||
|
||||
action (Gio.SimpleAction): Object generated by Gio
|
||||
|
||||
par (None): Ignored
|
||||
|
||||
"""
|
||||
|
||||
self.main_win_obj.video_catalogue_unshow_date()
|
||||
|
||||
|
||||
def on_button_cancel_error_filter(self, action, par):
|
||||
|
||||
"""Called from a callback in self.do_startup().
|
||||
@ -20699,6 +20791,37 @@ class TartubeApp(Gtk.Application):
|
||||
self.main_win_obj.video_catalogue_cancel_filter()
|
||||
|
||||
|
||||
def on_button_check_all(self, action, par):
|
||||
|
||||
"""Called from a callback in self.do_startup().
|
||||
|
||||
Call a function to start a new download operation (if allowed).
|
||||
|
||||
Unlike the corresponding self.on_menu_check_all button, this function
|
||||
will check only the marked items, if any.
|
||||
|
||||
Args:
|
||||
|
||||
action (Gio.SimpleAction): Object generated by Gio
|
||||
|
||||
par (None): Ignored
|
||||
|
||||
"""
|
||||
|
||||
media_list = []
|
||||
for name in self.main_win_obj.video_index_marker_dict.keys():
|
||||
if name in self.media_name_dict:
|
||||
dbid = self.media_name_dict[name]
|
||||
if dbid in self.media_reg_dict:
|
||||
media_list.append(self.media_reg_dict[dbid])
|
||||
|
||||
self.download_manager_start(
|
||||
'sim',
|
||||
False, # Not called from self.script_slow_timer_callback()
|
||||
media_list, # May be empty, in which case everything is checked
|
||||
)
|
||||
|
||||
|
||||
def on_button_classic_add_urls(self, action, par):
|
||||
|
||||
"""Called from a callback in self.do_startup().
|
||||
@ -20718,6 +20841,27 @@ class TartubeApp(Gtk.Application):
|
||||
self.main_win_obj.classic_mode_tab_add_urls()
|
||||
|
||||
|
||||
def on_button_classic_archive(self, action, par):
|
||||
|
||||
"""Called from a callback in self.do_startup().
|
||||
|
||||
Enables/disables the youtube-dl archive file in downloads from the
|
||||
Classic Mode tab.
|
||||
|
||||
Args:
|
||||
|
||||
action (Gio.SimpleAction): Object generated by Gio
|
||||
|
||||
par (None): Ignored
|
||||
|
||||
"""
|
||||
|
||||
if self.main_win_obj.classic_archive_button.get_active():
|
||||
self.classic_ytdl_archive_flag = True
|
||||
else:
|
||||
self.classic_ytdl_archive_flag = False
|
||||
|
||||
|
||||
def on_button_classic_dest_dir(self, action, par):
|
||||
|
||||
"""Called from a callback in self.do_startup().
|
||||
@ -21146,13 +21290,6 @@ class TartubeApp(Gtk.Application):
|
||||
# Delete the files associated with the video
|
||||
self.delete_video_files(video_obj)
|
||||
|
||||
# If mainapp.TartubeApp.allow_ytdl_archive_flag is set,
|
||||
# youtube-dl will have created a ytdl_archive.txt, recording
|
||||
# every video ever downloaded in the parent directory. This
|
||||
# will prevent a successful re-downloading of the video
|
||||
# Temporarily block usage of the archive file
|
||||
self.block_ytdl_archive_flag = True
|
||||
|
||||
# Start the download operation
|
||||
if not self.classic_custom_dl_flag:
|
||||
self.download_manager_start('classic_real', False, video_list)
|
||||
@ -21287,12 +21424,15 @@ class TartubeApp(Gtk.Application):
|
||||
worker_obj.downloader_obj.stop()
|
||||
|
||||
|
||||
def on_button_cancel_date(self, action, par):
|
||||
def on_button_custom_dl_all(self, action, par):
|
||||
|
||||
"""Called from a callback in self.do_startup().
|
||||
|
||||
Changes the Video Catalogue page to the first one, after showing a page
|
||||
matching a particular date.
|
||||
Call a function to start a new (custom) download operation (if
|
||||
allowed).
|
||||
|
||||
Unlike the corresponding self.on_menu_custom_dl_all button, this
|
||||
function will custom download only the marked items, if any.
|
||||
|
||||
Args:
|
||||
|
||||
@ -21302,7 +21442,62 @@ class TartubeApp(Gtk.Application):
|
||||
|
||||
"""
|
||||
|
||||
self.main_win_obj.video_catalogue_unshow_date()
|
||||
media_list = []
|
||||
for name in self.main_win_obj.video_index_marker_dict.keys():
|
||||
if name in self.media_name_dict:
|
||||
dbid = self.media_name_dict[name]
|
||||
if dbid in self.media_reg_dict:
|
||||
media_list.append(self.media_reg_dict[dbid])
|
||||
|
||||
if not self.general_custom_dl_obj.dl_by_video_flag \
|
||||
or not self.general_custom_dl_obj.dl_precede_flag:
|
||||
|
||||
self.download_manager_start(
|
||||
'custom_real',
|
||||
False, # Not called by the timer
|
||||
media_list, # Download all media data objects
|
||||
self.general_custom_dl_obj,
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
self.download_manager_start(
|
||||
'custom_sim',
|
||||
False, # Not called by the timer
|
||||
media_list, # Download all media data objects
|
||||
self.general_custom_dl_obj,
|
||||
)
|
||||
|
||||
|
||||
def on_button_download_all(self, action, par):
|
||||
|
||||
"""Called from a callback in self.do_startup().
|
||||
|
||||
Call a function to start a new download operation (if allowed).
|
||||
|
||||
Unlike the corresponding self.on_menu_download_all button, this
|
||||
function will download only the marked items, if any.
|
||||
|
||||
Args:
|
||||
|
||||
action (Gio.SimpleAction): Object generated by Gio
|
||||
|
||||
par (None): Ignored
|
||||
|
||||
"""
|
||||
|
||||
media_list = []
|
||||
for name in self.main_win_obj.video_index_marker_dict.keys():
|
||||
if name in self.media_name_dict:
|
||||
dbid = self.media_name_dict[name]
|
||||
if dbid in self.media_reg_dict:
|
||||
media_list.append(self.media_reg_dict[dbid])
|
||||
|
||||
self.download_manager_start(
|
||||
'real',
|
||||
False, # Not called from self.script_slow_timer_callback()
|
||||
media_list, # May be empty, in which case everything is downloaded
|
||||
)
|
||||
|
||||
|
||||
def on_button_drag_drop_add(self, action, par):
|
||||
@ -22604,6 +22799,23 @@ class TartubeApp(Gtk.Application):
|
||||
self.main_win_obj.custom_dl_popup_menu()
|
||||
|
||||
|
||||
def on_menu_unmark_all(self, action, par):
|
||||
|
||||
"""Called from a callback in self.do_startup().
|
||||
|
||||
Unmarks all markers in the Video Index.
|
||||
|
||||
Args:
|
||||
|
||||
action (Gio.SimpleAction): Object generated by Gio
|
||||
|
||||
par (None): Ignored
|
||||
|
||||
"""
|
||||
|
||||
self.main_win_obj.video_index_reset_marker()
|
||||
|
||||
|
||||
def on_menu_download_all(self, action, par):
|
||||
|
||||
"""Called from a callback in self.do_startup().
|
||||
@ -23211,14 +23423,16 @@ class TartubeApp(Gtk.Application):
|
||||
'exist_flag': dialogue_win.checkbutton3.get_active(),
|
||||
'del_video_flag': dialogue_win.checkbutton4.get_active(),
|
||||
'del_others_flag': dialogue_win.checkbutton5.get_active(),
|
||||
'del_archive_flag': dialogue_win.checkbutton6.get_active(),
|
||||
'move_thumb_flag': dialogue_win.checkbutton7.get_active(),
|
||||
'del_thumb_flag': dialogue_win.checkbutton8.get_active(),
|
||||
'convert_webp_flag': dialogue_win.checkbutton9.get_active(),
|
||||
'move_data_flag': dialogue_win.checkbutton10.get_active(),
|
||||
'del_descrip_flag': dialogue_win.checkbutton11.get_active(),
|
||||
'del_json_flag': dialogue_win.checkbutton12.get_active(),
|
||||
'del_xml_flag': dialogue_win.checkbutton13.get_active(),
|
||||
'remove_no_url_flag': dialogue_win.checkbutton6.get_active(),
|
||||
'remove_dupe_flag': dialogue_win.checkbutton7.get_active(),
|
||||
'del_archive_flag': dialogue_win.checkbutton8.get_active(),
|
||||
'move_thumb_flag': dialogue_win.checkbutton9.get_active(),
|
||||
'del_thumb_flag': dialogue_win.checkbutton10.get_active(),
|
||||
'convert_webp_flag': dialogue_win.checkbutton11.get_active(),
|
||||
'move_data_flag': dialogue_win.checkbutton12.get_active(),
|
||||
'del_descrip_flag': dialogue_win.checkbutton13.get_active(),
|
||||
'del_json_flag': dialogue_win.checkbutton14.get_active(),
|
||||
'del_xml_flag': dialogue_win.checkbutton15.get_active(),
|
||||
}
|
||||
|
||||
# Now destroy the window
|
||||
@ -23227,28 +23441,23 @@ class TartubeApp(Gtk.Application):
|
||||
if response == Gtk.ResponseType.OK:
|
||||
|
||||
# If nothing was selected, then there is nothing to do
|
||||
# (Don't need to check 'del_others_flag' here)
|
||||
if not choices_dict['corrupt_flag'] \
|
||||
and not choices_dict['exist_flag'] \
|
||||
and not choices_dict['del_video_flag'] \
|
||||
and not choices_dict['del_thumb_flag'] \
|
||||
and not choices_dict['convert_webp_flag'] \
|
||||
and not choices_dict['del_descrip_flag'] \
|
||||
and not choices_dict['del_json_flag'] \
|
||||
and not choices_dict['del_xml_flag'] \
|
||||
and not choices_dict['del_archive_flag'] \
|
||||
and not choices_dict['move_thumb_flag'] \
|
||||
and not choices_dict['move_data_flag']:
|
||||
selected_flag = False
|
||||
for key in choices_dict.keys():
|
||||
if choices_dict[key]:
|
||||
selected_flag = True
|
||||
break
|
||||
|
||||
if not selected_flag:
|
||||
return
|
||||
|
||||
# Prompt the user for confirmation, before deleting any files
|
||||
if choices_dict['del_corrupt_flag'] \
|
||||
or choices_dict['del_video_flag'] \
|
||||
or choices_dict['del_archive_flag'] \
|
||||
or choices_dict['del_thumb_flag'] \
|
||||
or choices_dict['del_descrip_flag'] \
|
||||
or choices_dict['del_json_flag'] \
|
||||
or choices_dict['del_xml_flag'] \
|
||||
or choices_dict['del_archive_flag']:
|
||||
or choices_dict['del_xml_flag']:
|
||||
|
||||
self.dialogue_manager_obj.show_msg_dialogue(
|
||||
_(
|
||||
@ -23654,14 +23863,6 @@ class TartubeApp(Gtk.Application):
|
||||
self.bandwidth_default = value
|
||||
|
||||
|
||||
def set_block_ytdl_archive_flag(self, flag):
|
||||
|
||||
if not flag:
|
||||
self.block_ytdl_archive_flag = False
|
||||
else:
|
||||
self.block_ytdl_archive_flag = True
|
||||
|
||||
|
||||
def set_catalogue_draw_blocked_flag(self, flag):
|
||||
|
||||
if not flag:
|
||||
@ -24789,6 +24990,19 @@ class TartubeApp(Gtk.Application):
|
||||
)
|
||||
|
||||
|
||||
def set_show_marker_in_index_flag(self, flag):
|
||||
|
||||
if not flag:
|
||||
self.show_marker_in_index_flag = False
|
||||
else:
|
||||
self.show_marker_in_index_flag = True
|
||||
|
||||
# Reset all markers in the Video Index
|
||||
self.main_win_obj.video_index_reset_marker()
|
||||
# Redraw the Video Index and Video Catalogue
|
||||
self.main_win_obj.video_index_catalogue_reset()
|
||||
|
||||
|
||||
def set_show_small_icons_in_index_flag(self, flag):
|
||||
|
||||
if not flag:
|
||||
|
1081
tartube/mainwin.py
1081
tartube/mainwin.py
File diff suppressed because it is too large
Load Diff
@ -1975,6 +1975,18 @@ class Video(GenericMedia):
|
||||
# Valid values are those specified by formats.VIDEO_FORMAT_LIST,
|
||||
# formats.AUDIO_FORMAT_LIST and formats.VIDEO_RESOLUTION_LIST
|
||||
self.dummy_format = None
|
||||
# Flag set to True if the download was completed, in which case
|
||||
# self.source is not added to mainapp.TartubeApp.classic_pending_list
|
||||
# (remembering it for the next session)
|
||||
# Specifically, it remains False when the download is waiting to start,
|
||||
# or if the VideoDownloader returns a return value of STOPPED, or
|
||||
# if (during a download) self.dummy_path is still None, meaning no
|
||||
# videos have been downloaded
|
||||
# Once set to True, it is never set back to False. So, if the user
|
||||
# tries to re-download a channel/playlist and no new videos are
|
||||
# found, the flag remains set to True
|
||||
self.dummy_dl_flag = False
|
||||
|
||||
|
||||
# Code
|
||||
# ----
|
||||
@ -2179,7 +2191,7 @@ class Video(GenericMedia):
|
||||
self.set_video_descrip(app_obj, text, max_length)
|
||||
|
||||
|
||||
def extract_timestamps_from_descrip(self, app_obj):
|
||||
def extract_timestamps_from_descrip(self, app_obj, override_descrip=None):
|
||||
|
||||
"""Can be called by anything. Often called by
|
||||
self.set_video_descrip().
|
||||
@ -2200,16 +2212,24 @@ class Video(GenericMedia):
|
||||
|
||||
app_obj (mainapp.TartubeApp): The main application
|
||||
|
||||
override_descrip (str or None): If specified, extract timestamps
|
||||
from this string, rather than from self.descrip
|
||||
|
||||
"""
|
||||
|
||||
if self.descrip is None or self.descrip == '':
|
||||
if (self.descrip is None or self.descrip == '') \
|
||||
and (override_descrip is None or override_descrip == ''):
|
||||
return
|
||||
|
||||
regex = r'^\s*(' + app_obj.timestamp_regex + r')(\s.*)'
|
||||
rev_regex = r'^(.*\s)(' + app_obj.timestamp_regex + r')\s*$'
|
||||
digit_count = 0
|
||||
|
||||
line_list = self.descrip.split('\n')
|
||||
if override_descrip is not None and override_descrip != '':
|
||||
line_list = override_descrip.split('\n')
|
||||
else:
|
||||
line_list = self.descrip.split('\n')
|
||||
|
||||
temp_list = []
|
||||
stamp_list = []
|
||||
|
||||
@ -2677,6 +2697,14 @@ class Video(GenericMedia):
|
||||
self.source = url
|
||||
|
||||
|
||||
def set_dummy_dl_flag(self, flag):
|
||||
|
||||
if flag:
|
||||
self.dummy_dl_flag = True
|
||||
else:
|
||||
self.dummy_dl_flag = False
|
||||
|
||||
|
||||
def set_dummy_path(self, path):
|
||||
|
||||
self.dummy_path = path
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -167,24 +167,42 @@ class ProcessManager(threading.Thread):
|
||||
+ str(self.job_total) + ': ' + video_obj.name,
|
||||
)
|
||||
|
||||
if self.options_obj.options_dict['output_mode'] == 'split':
|
||||
default_flag = False
|
||||
if self.app_obj.temp_stamp_list:
|
||||
|
||||
# Split the video into video clips
|
||||
# Split the video into video clips, using the .stamp_list
|
||||
# specified directly by the user (instead of the one
|
||||
# specified by the media.Video object)
|
||||
dest_dir = self.split_video(video_obj)
|
||||
if self.fatal_error_flag:
|
||||
break
|
||||
|
||||
else:
|
||||
# Add the returned destination directory to a list,
|
||||
# first checking for duplicates
|
||||
if not dest_dir in check_dict:
|
||||
dest_dir_list.append(dest_dir)
|
||||
check_dict[dest_dir] = None
|
||||
elif self.app_obj.temp_slice_list:
|
||||
|
||||
# Produce a single output video with slices removed, using the
|
||||
# .slice_list specified directly by the user (instead of the
|
||||
# one specified by the media.Video object)
|
||||
dest_dir = self.slice_video(video_obj)
|
||||
|
||||
elif self.options_obj.options_dict['output_mode'] == 'split':
|
||||
|
||||
# Split the video into video clips, using the .stamp_list
|
||||
# specified by the media.Video object
|
||||
dest_dir = self.split_video(video_obj)
|
||||
|
||||
elif self.options_obj.options_dict['output_mode'] == 'slice':
|
||||
|
||||
# Produce a single output video with slices removed
|
||||
# Produce a single output video with slices removed, using the
|
||||
# .slice_list specified by the media.Video object
|
||||
dest_dir = self.slice_video(video_obj)
|
||||
|
||||
else:
|
||||
|
||||
# Process the video with FFmpeg. One source video produces one
|
||||
# output video
|
||||
self.process_video(video_obj)
|
||||
default_flag = True
|
||||
|
||||
if not default_flag:
|
||||
|
||||
if self.fatal_error_flag:
|
||||
# This is a fatal error
|
||||
break
|
||||
@ -196,12 +214,6 @@ class ProcessManager(threading.Thread):
|
||||
dest_dir_list.append(dest_dir)
|
||||
check_dict[dest_dir] = None
|
||||
|
||||
else:
|
||||
|
||||
# Process the video with FFmpeg. One source video produces one
|
||||
# output video
|
||||
self.process_video(video_obj)
|
||||
|
||||
# Pause a moment, before the next iteration of the loop (don't want
|
||||
# to hog resources)
|
||||
time.sleep(self.sleep_time)
|
||||
@ -333,7 +345,7 @@ class ProcessManager(threading.Thread):
|
||||
|
||||
|
||||
def process_video(self, orig_video_obj, dest_dir=None, start_point=None, \
|
||||
stop_point=None, clip_title=None):
|
||||
stop_point=None, clip_title=None, override_output_mode=None):
|
||||
|
||||
"""Called by self.run(), .slice_video() and .split_video().
|
||||
|
||||
@ -359,6 +371,12 @@ class ProcessManager(threading.Thread):
|
||||
clip_title (str): When splitting a video, the title of this video
|
||||
clip (if specified)
|
||||
|
||||
override_output_mode (str): When splitting/slicing a video, and the
|
||||
user has specified their own .stamp_list or .slice_list, then
|
||||
this value is set to 'split' or 'slice', overriding the
|
||||
'output_mode' of the FFmpegOptionsManager. Otherwise set to
|
||||
None
|
||||
|
||||
Return values:
|
||||
|
||||
True of success, False on failure
|
||||
@ -386,14 +404,30 @@ class ProcessManager(threading.Thread):
|
||||
|
||||
# Get the source/output files, ahd the full FFmpeg system command (as a
|
||||
# list, and including the source/output files)
|
||||
source_path, output_path, cmd_list = self.options_obj.get_system_cmd(
|
||||
self.app_obj,
|
||||
orig_video_obj,
|
||||
start_point,
|
||||
stop_point,
|
||||
clip_title,
|
||||
dest_dir,
|
||||
)
|
||||
if override_output_mode is None:
|
||||
|
||||
source_path, output_path, cmd_list \
|
||||
= self.options_obj.get_system_cmd(
|
||||
self.app_obj,
|
||||
orig_video_obj,
|
||||
start_point,
|
||||
stop_point,
|
||||
clip_title,
|
||||
dest_dir,
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
source_path, output_path, cmd_list \
|
||||
= self.options_obj.get_system_cmd(
|
||||
self.app_obj,
|
||||
orig_video_obj,
|
||||
start_point,
|
||||
stop_point,
|
||||
clip_title,
|
||||
dest_dir,
|
||||
{ 'output_mode': override_output_mode },
|
||||
)
|
||||
|
||||
if source_path is None:
|
||||
|
||||
@ -567,8 +601,11 @@ class ProcessManager(threading.Thread):
|
||||
)
|
||||
|
||||
# Import the correct slice list
|
||||
override_output_mode = None
|
||||
if self.app_obj.temp_slice_list:
|
||||
|
||||
override_output_mode = 'slice'
|
||||
|
||||
# Use the temporary buffer
|
||||
slice_list = self.app_obj.temp_slice_list.copy()
|
||||
temp_flag = True
|
||||
@ -661,6 +698,7 @@ class ProcessManager(threading.Thread):
|
||||
start_time,
|
||||
stop_time,
|
||||
'clip_' + str(i + 1), # Clip title
|
||||
override_output_mode,
|
||||
):
|
||||
# Don't continue creating more clips after an error
|
||||
self.fatal_error_flag = True
|
||||
@ -825,8 +863,11 @@ class ProcessManager(threading.Thread):
|
||||
return None
|
||||
|
||||
# Import the correct timestamp list
|
||||
override_output_mode = None
|
||||
if self.app_obj.temp_stamp_list:
|
||||
|
||||
override_output_mode = 'split'
|
||||
|
||||
# Use the temporary buffer
|
||||
stamp_list = self.app_obj.temp_stamp_list.copy()
|
||||
# (The temporary buffer, once used, must be emptied immediately)
|
||||
@ -899,6 +940,7 @@ class ProcessManager(threading.Thread):
|
||||
start_stamp,
|
||||
stop_stamp,
|
||||
clip_title,
|
||||
override_output_mode,
|
||||
):
|
||||
# Don't continue creating more clips after an error
|
||||
self.fatal_error_flag = True
|
||||
|
@ -42,8 +42,8 @@ import mainapp
|
||||
|
||||
# 'Global' variables
|
||||
__packagename__ = 'tartube'
|
||||
__version__ = '2.3.518'
|
||||
__date__ = '5 Apr 2022'
|
||||
__version__ = '2.3.549'
|
||||
__date__ = '9 Apr 2022'
|
||||
__copyright__ = 'Copyright \xa9 2019-2022 A S Lewis'
|
||||
__license__ = """
|
||||
Copyright \xa9 2019-2022 A S Lewis.
|
||||
|
139
tartube/tidy.py
139
tartube/tidy.py
@ -83,6 +83,16 @@ class TidyManager(threading.Thread):
|
||||
should be deleted (as artefacts of post-processing with FFmpeg
|
||||
or AVConv)
|
||||
|
||||
remove_no_url_flag: True if any media.Video objects whose URL is
|
||||
not set should be removed from the database (no files are
|
||||
deleted)
|
||||
|
||||
remove_dupe_flag: True if any media.Video objects, which are not
|
||||
marked as downloaded and which share a URL with another
|
||||
media.Video object with the same parent and which is marked as
|
||||
downloaded, should be removed from the database (no files are
|
||||
deleted)
|
||||
|
||||
del_archive_flag: True if all youtube-dl archive files should be
|
||||
deleted
|
||||
|
||||
@ -151,6 +161,14 @@ class TidyManager(threading.Thread):
|
||||
# True if all video/audio files with the same name should be deleted
|
||||
# (as artefacts of post-processing with FFmpeg or AVConv)
|
||||
self.del_others_flag = choices_dict['del_others_flag']
|
||||
# True if any media.Video objects whose URL is not set should be
|
||||
# removed from the database (no files are deleted)
|
||||
self.remove_no_url_flag = choices_dict['remove_no_url_flag']
|
||||
# True if any media.Video objects, which are not marked as downloaded
|
||||
# and which share a URL with another media.Video object with the same
|
||||
# parent and which is marked as downloaded, should be removed from
|
||||
# the database (no files are deleted)
|
||||
self.remove_dupe_flag = choices_dict['remove_dupe_flag']
|
||||
# True if all youtube-dl archive files should be deleted
|
||||
self.del_archive_flag = choices_dict['del_archive_flag']
|
||||
# True if all thumbnail files should be moved into a subdirectory
|
||||
@ -184,6 +202,8 @@ class TidyManager(threading.Thread):
|
||||
self.video_no_exist_count = 0
|
||||
self.video_deleted_count = 0
|
||||
self.other_deleted_count = 0
|
||||
self.remove_no_url_count = 0
|
||||
self.remove_dupe_count = 0
|
||||
self.archive_deleted_count = 0
|
||||
self.thumb_moved_count = 0
|
||||
self.thumb_deleted_count = 0
|
||||
@ -295,6 +315,27 @@ class TidyManager(threading.Thread):
|
||||
' ' + _('Delete other video/audio files:') + ' ' + text,
|
||||
)
|
||||
|
||||
if self.remove_no_url_flag:
|
||||
text = _('YES')
|
||||
else:
|
||||
text = _('NO')
|
||||
|
||||
self.app_obj.main_win_obj.output_tab_write_stdout(
|
||||
1,
|
||||
' ' + _('Remove no_URL videos from database:') + ' ' + text,
|
||||
)
|
||||
|
||||
if self.remove_dupe_flag:
|
||||
text = _('YES')
|
||||
else:
|
||||
text = _('NO')
|
||||
|
||||
self.app_obj.main_win_obj.output_tab_write_stdout(
|
||||
1,
|
||||
' ' + _('Remove undownloaded duplicate videos from database:') \
|
||||
+ ' ' + text,
|
||||
)
|
||||
|
||||
if self.del_archive_flag:
|
||||
text = _('YES')
|
||||
else:
|
||||
@ -454,6 +495,23 @@ class TidyManager(threading.Thread):
|
||||
+ str(self.other_deleted_count),
|
||||
)
|
||||
|
||||
if self.remove_no_url_flag:
|
||||
|
||||
self.app_obj.main_win_obj.output_tab_write_stdout(
|
||||
1,
|
||||
' ' + _('No-URL videos removed from database:') + ' ' \
|
||||
+ str(self.remove_no_url_count),
|
||||
)
|
||||
|
||||
if self.remove_dupe_flag:
|
||||
|
||||
self.app_obj.main_win_obj.output_tab_write_stdout(
|
||||
1,
|
||||
' ' \
|
||||
+ _('Undownloaded duplicate videos removed from database:') \
|
||||
+ ' ' + str(self.remove_dupe_count),
|
||||
)
|
||||
|
||||
if self.del_archive_flag:
|
||||
|
||||
self.app_obj.main_win_obj.output_tab_write_stdout(
|
||||
@ -564,6 +622,12 @@ class TidyManager(threading.Thread):
|
||||
if self.del_video_flag:
|
||||
self.delete_video(media_data_obj)
|
||||
|
||||
if self.remove_no_url_flag:
|
||||
self.remove_no_url(media_data_obj)
|
||||
|
||||
if self.remove_dupe_flag:
|
||||
self.remove_dupe(media_data_obj)
|
||||
|
||||
if self.del_archive_flag:
|
||||
self.delete_archive(media_data_obj)
|
||||
|
||||
@ -826,6 +890,81 @@ class TidyManager(threading.Thread):
|
||||
self.other_deleted_count += 1
|
||||
|
||||
|
||||
def remove_no_url(self, media_data_obj):
|
||||
|
||||
"""Called by self.tidy_directory().
|
||||
|
||||
Checks all child videos of the specified media data object. If the
|
||||
video has no URL, remove it from the database (but don't delete any
|
||||
files).
|
||||
|
||||
Args:
|
||||
|
||||
media_data_obj (media.Channel, media.Playlist or media.Folder):
|
||||
The media data object whose directory must be tidied up
|
||||
|
||||
"""
|
||||
|
||||
for video_obj in media_data_obj.compile_all_videos( [] ):
|
||||
|
||||
if video_obj.source is None:
|
||||
GObject.timeout_add(
|
||||
0,
|
||||
self.app_obj.delete_video,
|
||||
video_obj,
|
||||
)
|
||||
|
||||
self.remove_no_url_count += 1
|
||||
|
||||
|
||||
def remove_dupe(self, media_data_obj):
|
||||
|
||||
"""Called by self.tidy_directory().
|
||||
|
||||
Checks all child videos of the specified media data object. If the
|
||||
video is not marked as downloaded, and has the same URL as another
|
||||
child video (of the same specified media data object) which IS marked
|
||||
as downloaded, remove the undownloaded one from the database (but
|
||||
don't delete any files).
|
||||
|
||||
Args:
|
||||
|
||||
media_data_obj (media.Channel, media.Playlist or media.Folder):
|
||||
The media data object whose directory must be tidied up
|
||||
|
||||
"""
|
||||
|
||||
# Compile dictionaries of downloaded and undownloaded URLs
|
||||
dl_dict = {}
|
||||
not_dl_dict = {}
|
||||
|
||||
for video_obj in media_data_obj.compile_all_videos( [] ):
|
||||
|
||||
if video_obj.source is not None:
|
||||
if video_obj.dl_flag:
|
||||
dl_dict[video_obj.source] = video_obj.dbid
|
||||
else:
|
||||
not_dl_dict[video_obj.source] = video_obj.dbid
|
||||
|
||||
# Check undownloaded videos, looking for a matching downloaded video
|
||||
for url in not_dl_dict.keys():
|
||||
|
||||
if url in dl_dict:
|
||||
|
||||
# Duplicate found
|
||||
dbid = not_dl_dict[url]
|
||||
if dbid in self.app_obj.media_reg_dict:
|
||||
|
||||
duplicate_obj = self.app_obj.media_reg_dict[dbid]
|
||||
GObject.timeout_add(
|
||||
0,
|
||||
self.app_obj.delete_video,
|
||||
duplicate_obj,
|
||||
)
|
||||
|
||||
self.remove_dupe_count += 1
|
||||
|
||||
|
||||
def delete_archive(self, media_data_obj):
|
||||
|
||||
"""Called by self.tidy_directory().
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
|
||||
# Import Gtk modules
|
||||
from gi.repository import Gtk, Gdk
|
||||
from gi.repository import Gtk, Gdk, GObject
|
||||
|
||||
|
||||
# Import other modules
|
||||
@ -1968,11 +1968,8 @@ custom_dl_obj=None, divert_mode=None):
|
||||
# We don't use an archive file when downloading into a system folder,
|
||||
# unless a non-default location for the file has been specified
|
||||
if (
|
||||
not app_obj.block_ytdl_archive_flag \
|
||||
and (
|
||||
not dl_classic_flag and app_obj.allow_ytdl_archive_flag \
|
||||
or dl_classic_flag and app_obj.classic_ytdl_archive_flag
|
||||
)
|
||||
(not dl_classic_flag and app_obj.allow_ytdl_archive_flag) \
|
||||
or (dl_classic_flag and app_obj.classic_ytdl_archive_flag)
|
||||
):
|
||||
if not dl_classic_flag \
|
||||
and (
|
||||
@ -3198,7 +3195,7 @@ def tidy_up_container_name(app_obj, string, max_length):
|
||||
return ''
|
||||
|
||||
for illegal in app_obj.illegal_name_mswin_list:
|
||||
if re.search('^' + illegal + '\.'):
|
||||
if re.search('^' + illegal + '\.', string):
|
||||
return ''
|
||||
|
||||
# Forbidden characters on MS Windows: < > : " / \ | ? *
|
||||
|
Loading…
x
Reference in New Issue
Block a user