library 3.2.6__tar.gz → 3.2.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {library-3.2.6 → library-3.2.7}/.github/README.md +15 -2
- {library-3.2.6 → library-3.2.7}/PKG-INFO +16 -3
- {library-3.2.6 → library-3.2.7}/library/__main__.py +1 -1
- library-3.2.7/library/mediafiles/torrents_dump.py +89 -0
- {library-3.2.6 → library-3.2.7}/library/playback/play_actions.py +2 -1
- {library-3.2.6 → library-3.2.7}/library/playback/playback_control.py +2 -2
- {library-3.2.6 → library-3.2.7}/library/playback/torrents_info.py +40 -3
- {library-3.2.6 → library-3.2.7}/library/usage.py +14 -1
- {library-3.2.6 → library-3.2.7}/library/utils/arggroups.py +17 -3
- {library-3.2.6 → library-3.2.7}/library/utils/devices.py +12 -0
- {library-3.2.6 → library-3.2.7}/pyproject.toml +1 -1
- library-3.2.6/library/mediafiles/torrents_dump.py +0 -67
- {library-3.2.6 → library-3.2.7}/LICENSE +0 -0
- {library-3.2.6 → library-3.2.7}/library/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/assets/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/assets/calibre.css +0 -0
- {library-3.2.6 → library-3.2.7}/library/assets/kotobago.png +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/av.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/computer_info.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/computers_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/fs_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/fs_add_metadata.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/gallery_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/gallery_backend.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/getty_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/hn_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/links_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/nicotine_import.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/places_import.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/reddit_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/row_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/site_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/substack.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/subtitle.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/tables_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/tabs_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/tildes.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/torrents_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/tube_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/tube_backend.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/createdb/web_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/data/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/data/dictionary.pkl +0 -0
- {library-3.2.6 → library-3.2.7}/library/data/ffmpeg_errors.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/data/http_errors.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/data/imagemagick_errors.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/data/unar_errors.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/data/wordbank.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/data/yt_dlp_errors.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/editdb/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/editdb/dedupe_db.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/editdb/dedupe_media.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/editdb/merge_online_local.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/editdb/mpv_watchlater.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/editdb/pushshift.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/editdb/reddit_selftext.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/files/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/files/christen.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/files/sample_compare.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/files/sample_hash.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/files/similar_files.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/folders/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/folders/big_dirs.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/folders/filter_src.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/folders/merge_mv.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/folders/mergerfs_cp.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/folders/mount_stats.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/folders/move_list.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/folders/scatter.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/folders/similar_folders.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/fsdb/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/fsdb/disk_usage.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/fsdb/filesystem.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/fsdb/folder_stats.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/fsdb/search_db.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/lb.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/block.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/db_history.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/db_media.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/db_playlists.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/download.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/download_status.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/history.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/history_add.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/optimize_db.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/playlists.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/redownload.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/search.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediadb/stats.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediafiles/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediafiles/images_to_pdf.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediafiles/media_check.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediafiles/pdf_edit.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediafiles/process_ffmpeg.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediafiles/process_image.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediafiles/process_media.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediafiles/process_text.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediafiles/torrents_start.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/mediafiles/unardel.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/misc/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/misc/dedupe_czkawka.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/misc/export_text.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/misc/search_help.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/multidb/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/multidb/allocate_torrents.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/multidb/copy_play_counts.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/multidb/merge_dbs.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/playback/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/playback/links_open.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/playback/media_player.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/playback/media_printer.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/playback/post_actions.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/playback/surf.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/playback/tabs_open.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/playback/torrents_remaining.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/readme.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/tablefiles/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/tablefiles/columns.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/tablefiles/eda.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/tablefiles/incremental_diff.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/tablefiles/markdown_tables.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/tablefiles/mcda.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/tablefiles/plot.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/text/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/text/cluster_sort.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/text/combinations.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/text/expand_links.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/text/extract_links.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/text/extract_text.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/text/json_keys_rename.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/text/markdown_links.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/text/nouns.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/text/regex_sort.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/text/timestamps.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/__init__.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/arg_utils.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/argparse_utils.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/consts.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/date_utils.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/db_utils.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/file_utils.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/filter_engine.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/gui.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/iterables.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/log_utils.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/mpv_utils.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/nums.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/objects.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/path_utils.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/pd_utils.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/printing.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/processes.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/remote_processes.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/shell_utils.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/sql_utils.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/sqlgroups.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/strings.py +0 -0
- {library-3.2.6 → library-3.2.7}/library/utils/web.py +0 -0
|
@@ -99,7 +99,7 @@ To stop playing press Ctrl+C in either the terminal or mpv
|
|
|
99
99
|
<details><summary>List all subcommands</summary>
|
|
100
100
|
|
|
101
101
|
$ library
|
|
102
|
-
library (v3.2.
|
|
102
|
+
library (v3.2.007; 102 subcommands)
|
|
103
103
|
|
|
104
104
|
Create database subcommands:
|
|
105
105
|
╭─────────────────┬──────────────────────────────────────────╮
|
|
@@ -2178,10 +2178,14 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
2178
2178
|
<details><summary>Print torrent file info</summary>
|
|
2179
2179
|
|
|
2180
2180
|
$ library torrents-dump -h
|
|
2181
|
-
usage: library torrents-dump PATH ...
|
|
2181
|
+
usage: library torrents-dump [--trackers] PATH ...
|
|
2182
2182
|
|
|
2183
2183
|
View torrent file metadata via libtorrent
|
|
2184
2184
|
|
|
2185
|
+
Aggregate torrent metadata by tracker domain:
|
|
2186
|
+
|
|
2187
|
+
library torrents-dump --trackers PATH ...
|
|
2188
|
+
|
|
2185
2189
|
|
|
2186
2190
|
</details>
|
|
2187
2191
|
|
|
@@ -3054,6 +3058,10 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
3054
3058
|
library torrents --dl --different-drives
|
|
3055
3059
|
library torrents --dl --different-drives /mnt/d4/
|
|
3056
3060
|
|
|
3061
|
+
Find incomplete torrents whose temp and final paths use the same drive:
|
|
3062
|
+
|
|
3063
|
+
library torrents --dl --same-drives
|
|
3064
|
+
|
|
3057
3065
|
Aggregate torrent info (torrents-status)
|
|
3058
3066
|
|
|
3059
3067
|
library torrents -pa
|
|
@@ -3070,6 +3078,10 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
3070
3078
|
|
|
3071
3079
|
library torrents --dl --progress=+10% --force-start
|
|
3072
3080
|
|
|
3081
|
+
Find torrents with a recorded completion timestamp
|
|
3082
|
+
|
|
3083
|
+
library torrents --completed
|
|
3084
|
+
|
|
3073
3085
|
Stop completed downloads
|
|
3074
3086
|
|
|
3075
3087
|
library torrents -S+5MiB --seeders=+5 --time-stalled=+45days --time-active=+180days --stop
|
|
@@ -3094,6 +3106,7 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
3094
3106
|
When --delete-rows is provided, the metadata is removed from qBittorrent
|
|
3095
3107
|
When --delete-files is provided, the downloaded files are deleted
|
|
3096
3108
|
When --delete-incomplete 80% is provided, any files that were downloaded less than 80% are deleted
|
|
3109
|
+
When --reset-progress is provided, the downloaded files are deleted and progress is reset without removing the torrents
|
|
3097
3110
|
|
|
3098
3111
|
|
|
3099
3112
|
</details>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: library
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.7
|
|
4
4
|
Summary: xk media library
|
|
5
5
|
Author-Email: Jacob Chapman <7908073+chapmanjacobd@users.noreply.github.com>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -207,7 +207,7 @@ To stop playing press Ctrl+C in either the terminal or mpv
|
|
|
207
207
|
<details><summary>List all subcommands</summary>
|
|
208
208
|
|
|
209
209
|
$ library
|
|
210
|
-
library (v3.2.
|
|
210
|
+
library (v3.2.007; 102 subcommands)
|
|
211
211
|
|
|
212
212
|
Create database subcommands:
|
|
213
213
|
╭─────────────────┬──────────────────────────────────────────╮
|
|
@@ -2286,10 +2286,14 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
2286
2286
|
<details><summary>Print torrent file info</summary>
|
|
2287
2287
|
|
|
2288
2288
|
$ library torrents-dump -h
|
|
2289
|
-
usage: library torrents-dump PATH ...
|
|
2289
|
+
usage: library torrents-dump [--trackers] PATH ...
|
|
2290
2290
|
|
|
2291
2291
|
View torrent file metadata via libtorrent
|
|
2292
2292
|
|
|
2293
|
+
Aggregate torrent metadata by tracker domain:
|
|
2294
|
+
|
|
2295
|
+
library torrents-dump --trackers PATH ...
|
|
2296
|
+
|
|
2293
2297
|
|
|
2294
2298
|
</details>
|
|
2295
2299
|
|
|
@@ -3162,6 +3166,10 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
3162
3166
|
library torrents --dl --different-drives
|
|
3163
3167
|
library torrents --dl --different-drives /mnt/d4/
|
|
3164
3168
|
|
|
3169
|
+
Find incomplete torrents whose temp and final paths use the same drive:
|
|
3170
|
+
|
|
3171
|
+
library torrents --dl --same-drives
|
|
3172
|
+
|
|
3165
3173
|
Aggregate torrent info (torrents-status)
|
|
3166
3174
|
|
|
3167
3175
|
library torrents -pa
|
|
@@ -3178,6 +3186,10 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
3178
3186
|
|
|
3179
3187
|
library torrents --dl --progress=+10% --force-start
|
|
3180
3188
|
|
|
3189
|
+
Find torrents with a recorded completion timestamp
|
|
3190
|
+
|
|
3191
|
+
library torrents --completed
|
|
3192
|
+
|
|
3181
3193
|
Stop completed downloads
|
|
3182
3194
|
|
|
3183
3195
|
library torrents -S+5MiB --seeders=+5 --time-stalled=+45days --time-active=+180days --stop
|
|
@@ -3202,6 +3214,7 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
3202
3214
|
When --delete-rows is provided, the metadata is removed from qBittorrent
|
|
3203
3215
|
When --delete-files is provided, the downloaded files are deleted
|
|
3204
3216
|
When --delete-incomplete 80% is provided, any files that were downloaded less than 80% are deleted
|
|
3217
|
+
When --reset-progress is provided, the downloaded files are deleted and progress is reset without removing the torrents
|
|
3205
3218
|
|
|
3206
3219
|
|
|
3207
3220
|
</details>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from library import usage
|
|
7
|
+
from library.createdb import torrents_add
|
|
8
|
+
from library.utils import arggroups, argparse_utils, printing, strings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def parse_args():
|
|
12
|
+
parser = argparse_utils.ArgumentParser(usage=usage.torrents_dump)
|
|
13
|
+
arggroups.debug(parser)
|
|
14
|
+
|
|
15
|
+
parser.add_argument("--trackers", action="store_true", help="Aggregate torrent metadata by tracker")
|
|
16
|
+
arggroups.paths_or_stdin(parser)
|
|
17
|
+
args = parser.parse_args()
|
|
18
|
+
arggroups.args_post(args, parser)
|
|
19
|
+
return args
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def gen_torrents(l):
|
|
23
|
+
for s in l:
|
|
24
|
+
p = Path(s)
|
|
25
|
+
if p.is_dir():
|
|
26
|
+
for file in p.rglob("*.torrent"):
|
|
27
|
+
if not file.is_dir():
|
|
28
|
+
yield file
|
|
29
|
+
elif p.is_file() and p.suffix == ".torrent":
|
|
30
|
+
yield p
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def aggregate_trackers(torrents):
|
|
34
|
+
trackers = {}
|
|
35
|
+
for _, torrent in torrents:
|
|
36
|
+
tracker = torrent["tracker"]
|
|
37
|
+
stats = trackers.setdefault(tracker, {"tracker": tracker, "count": 0, "size": 0})
|
|
38
|
+
stats["count"] += 1
|
|
39
|
+
stats["size"] += torrent["size"]
|
|
40
|
+
|
|
41
|
+
return [
|
|
42
|
+
{**stats, "size": strings.file_size(stats["size"])}
|
|
43
|
+
for stats in sorted(trackers.values(), key=lambda d: (d["size"], d["tracker"] or ""))
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def torrents_dump():
|
|
48
|
+
args = parse_args()
|
|
49
|
+
|
|
50
|
+
torrent_files = list(gen_torrents(args.paths))
|
|
51
|
+
with ThreadPoolExecutor() as executor:
|
|
52
|
+
metadata_results = executor.map(torrents_add.extract_metadata, torrent_files)
|
|
53
|
+
torrents = list(zip(torrent_files, metadata_results))
|
|
54
|
+
|
|
55
|
+
if args.trackers:
|
|
56
|
+
tracker_stats = aggregate_trackers(torrents)
|
|
57
|
+
if tracker_stats:
|
|
58
|
+
printing.table(tracker_stats)
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
rows = []
|
|
62
|
+
for torrent_path, d in torrents:
|
|
63
|
+
torrent = torrents_add.torrent_decode(torrent_path)
|
|
64
|
+
trackers = [t for t in torrent.trackers()]
|
|
65
|
+
announce_urls = []
|
|
66
|
+
for t in sorted(trackers, key=lambda t: (t.source, t.tier)):
|
|
67
|
+
try:
|
|
68
|
+
if url := t.url:
|
|
69
|
+
announce_urls.append(url)
|
|
70
|
+
except UnicodeDecodeError:
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
rows.append(
|
|
74
|
+
d
|
|
75
|
+
| {
|
|
76
|
+
"size": strings.file_size(d["size"]),
|
|
77
|
+
"size_avg": strings.file_size(d["size_avg"]),
|
|
78
|
+
"size_median": strings.file_size(d["size_median"]),
|
|
79
|
+
"time_uploaded": strings.relative_datetime(d["time_uploaded"]),
|
|
80
|
+
"time_created": strings.relative_datetime(d["time_created"]),
|
|
81
|
+
"time_modified": strings.relative_datetime(d["time_modified"]),
|
|
82
|
+
"time_created": strings.relative_datetime(d["time_created"]),
|
|
83
|
+
"files": d["files"][0]["path"] + ", ...",
|
|
84
|
+
"announce_urls": " ".join(announce_urls),
|
|
85
|
+
"web_seeds": " ".join(d["web_seeds"]),
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
printing.table(rows)
|
|
@@ -252,6 +252,7 @@ If you don't know the exact name of your chromecast group run `catt scan`
|
|
|
252
252
|
if args.chromecast:
|
|
253
253
|
from catt.api import CattDevice
|
|
254
254
|
|
|
255
|
+
args.chromecast_device = args.chromecast_device or devices.catt_default_device()
|
|
255
256
|
args.cc = CattDevice(args.chromecast_device, lazy=True)
|
|
256
257
|
args.cc_ip = devices.get_ip_of_chromecast(args.chromecast_device)
|
|
257
258
|
|
|
@@ -502,7 +503,7 @@ def watch() -> None:
|
|
|
502
503
|
|
|
503
504
|
|
|
504
505
|
def listen() -> None:
|
|
505
|
-
args = parse_args(SC.listen
|
|
506
|
+
args = parse_args(SC.listen) # no --cast-to means catt's own default speaker
|
|
506
507
|
args.profiles = [DBType.audio]
|
|
507
508
|
process_playqueue(args)
|
|
508
509
|
|
|
@@ -288,14 +288,14 @@ def catt_stop(args) -> None:
|
|
|
288
288
|
catt_device = []
|
|
289
289
|
if args.chromecast_device:
|
|
290
290
|
catt_device = ["-d", args.chromecast_device]
|
|
291
|
-
processes.cmd("catt", *catt_device, "stop")
|
|
291
|
+
processes.cmd("catt", *catt_device, "stop", strict=False)
|
|
292
292
|
|
|
293
293
|
|
|
294
294
|
def catt_pause(args) -> None:
|
|
295
295
|
catt_device = []
|
|
296
296
|
if args.chromecast_device:
|
|
297
297
|
catt_device = ["-d", args.chromecast_device]
|
|
298
|
-
processes.cmd("catt", *catt_device, "play_toggle")
|
|
298
|
+
processes.cmd("catt", *catt_device, "play_toggle", strict=False)
|
|
299
299
|
|
|
300
300
|
|
|
301
301
|
def kill_process(name) -> None:
|
|
@@ -47,6 +47,12 @@ def parse_args():
|
|
|
47
47
|
const="73%",
|
|
48
48
|
help="Delete incomplete files from matching torrents",
|
|
49
49
|
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--reset-progress",
|
|
52
|
+
"--reset",
|
|
53
|
+
action="store_true",
|
|
54
|
+
help="Delete matching torrent files and recheck progress without removing the torrent",
|
|
55
|
+
)
|
|
50
56
|
parser.add_argument("--move", type=Path, help="Directory to move folders/files")
|
|
51
57
|
arggroups.mmv_folders(parser)
|
|
52
58
|
parser.add_argument(
|
|
@@ -188,6 +194,8 @@ def filter_torrents_by_activity(args, torrents):
|
|
|
188
194
|
|
|
189
195
|
if args.complete:
|
|
190
196
|
torrents = [t for t in torrents if t.state_enum.is_complete]
|
|
197
|
+
if args.completed:
|
|
198
|
+
torrents = [t for t in torrents if t.completion_on > 0]
|
|
191
199
|
if args.incomplete:
|
|
192
200
|
torrents = [t for t in torrents if not t.state_enum.is_complete]
|
|
193
201
|
|
|
@@ -297,8 +305,10 @@ def filter_torrents_by_criteria(args, torrents):
|
|
|
297
305
|
|
|
298
306
|
if args.private is not None:
|
|
299
307
|
torrents = [t for t in torrents if args.private is t.private]
|
|
300
|
-
if args.different_drives:
|
|
301
|
-
torrents = [
|
|
308
|
+
if args.different_drives is not None:
|
|
309
|
+
torrents = [
|
|
310
|
+
t for t in torrents if torrent_paths_on_different_drives(t) is args.different_drives
|
|
311
|
+
]
|
|
302
312
|
if args.no_tagged:
|
|
303
313
|
tags = set(args.no_tagged)
|
|
304
314
|
torrents = [t for t in torrents if tags.isdisjoint(t.tags.split(", "))]
|
|
@@ -506,8 +516,10 @@ def map_value_status(t, status):
|
|
|
506
516
|
|
|
507
517
|
def torrent_paths_for_search(args, t):
|
|
508
518
|
path_search = getattr(args, "path_search", None)
|
|
519
|
+
if path_search is None and getattr(args, "different_drives", None) is not None:
|
|
520
|
+
path_search = "both"
|
|
509
521
|
if path_search is None:
|
|
510
|
-
path_search = "
|
|
522
|
+
path_search = "status"
|
|
511
523
|
if path_search == "download":
|
|
512
524
|
return [t.download_path]
|
|
513
525
|
if path_search == "save":
|
|
@@ -523,6 +535,26 @@ def torrent_paths_on_different_drives(t):
|
|
|
523
535
|
return path_utils.mountpoint(t.download_path) != path_utils.mountpoint(t.save_path)
|
|
524
536
|
|
|
525
537
|
|
|
538
|
+
def reset_torrent_progress(args, qbt_client, torrent):
|
|
539
|
+
originally_stopped = bool(torrent.state_enum.is_stopped)
|
|
540
|
+
qbt_client.torrents_stop(torrent_hashes=[torrent.hash])
|
|
541
|
+
|
|
542
|
+
paths = set()
|
|
543
|
+
for file in torrent_files(torrent):
|
|
544
|
+
for base_path in (torrent.download_path, torrent.save_path):
|
|
545
|
+
if base_path:
|
|
546
|
+
paths.add(Path(base_path) / file.name)
|
|
547
|
+
|
|
548
|
+
for file_path in paths:
|
|
549
|
+
if file_path.exists() and not file_path.is_dir():
|
|
550
|
+
log.info("Deleting file: %s", file_path)
|
|
551
|
+
file_path.unlink(missing_ok=True)
|
|
552
|
+
|
|
553
|
+
qbt_client.torrents_recheck(torrent_hashes=[torrent.hash])
|
|
554
|
+
if not originally_stopped:
|
|
555
|
+
qbt_client.torrents_start(torrent_hashes=[torrent.hash])
|
|
556
|
+
|
|
557
|
+
|
|
526
558
|
def set_torrent_paths(qbt_client, t, temp_path, download_path):
|
|
527
559
|
save_path = download_path if download_path is not None else t.save_path
|
|
528
560
|
if save_path:
|
|
@@ -942,6 +974,11 @@ def torrents_info():
|
|
|
942
974
|
log.warning("Keeping %s incomplete file: %s", strings.percent(1 - file.progress), file_path)
|
|
943
975
|
break # Stop after deleting first valid path
|
|
944
976
|
|
|
977
|
+
if args.reset_progress:
|
|
978
|
+
print("Resetting progress of", len(torrents))
|
|
979
|
+
for t in torrents:
|
|
980
|
+
reset_torrent_progress(args, qbt_client, t)
|
|
981
|
+
|
|
945
982
|
alt_move_syntax = any(
|
|
946
983
|
k not in args.defaults for k in ["temp_drive", "temp_prefix", "download_drive", "download_prefix"]
|
|
947
984
|
)
|
|
@@ -1976,9 +1976,13 @@ torrents_start = """library torrents-start [--prefix /mnt/d/] [--scan] PATH ...
|
|
|
1976
1976
|
--scan overrides the --tracker-dirnames layout when choosing save paths.
|
|
1977
1977
|
"""
|
|
1978
1978
|
|
|
1979
|
-
torrents_dump = """library torrents-dump PATH ...
|
|
1979
|
+
torrents_dump = """library torrents-dump [--trackers] PATH ...
|
|
1980
1980
|
|
|
1981
1981
|
View torrent file metadata via libtorrent
|
|
1982
|
+
|
|
1983
|
+
Aggregate torrent metadata by tracker domain:
|
|
1984
|
+
|
|
1985
|
+
library torrents-dump --trackers PATH ...
|
|
1982
1986
|
"""
|
|
1983
1987
|
|
|
1984
1988
|
torrents_info = """library torrents-info
|
|
@@ -2003,6 +2007,10 @@ torrents_info = """library torrents-info
|
|
|
2003
2007
|
library torrents --dl --different-drives
|
|
2004
2008
|
library torrents --dl --different-drives /mnt/d4/
|
|
2005
2009
|
|
|
2010
|
+
Find incomplete torrents whose temp and final paths use the same drive:
|
|
2011
|
+
|
|
2012
|
+
library torrents --dl --same-drives
|
|
2013
|
+
|
|
2006
2014
|
Aggregate torrent info (torrents-status)
|
|
2007
2015
|
|
|
2008
2016
|
library torrents -pa
|
|
@@ -2019,6 +2027,10 @@ torrents_info = """library torrents-info
|
|
|
2019
2027
|
|
|
2020
2028
|
library torrents --dl --progress=+10% --force-start
|
|
2021
2029
|
|
|
2030
|
+
Find torrents with a recorded completion timestamp
|
|
2031
|
+
|
|
2032
|
+
library torrents --completed
|
|
2033
|
+
|
|
2022
2034
|
Stop completed downloads
|
|
2023
2035
|
|
|
2024
2036
|
library torrents -S+5MiB --seeders=+5 --time-stalled=+45days --time-active=+180days --stop
|
|
@@ -2043,6 +2055,7 @@ torrents_info = """library torrents-info
|
|
|
2043
2055
|
When --delete-rows is provided, the metadata is removed from qBittorrent
|
|
2044
2056
|
When --delete-files is provided, the downloaded files are deleted
|
|
2045
2057
|
When --delete-incomplete 80% is provided, any files that were downloaded less than 80% are deleted
|
|
2058
|
+
When --reset-progress is provided, the downloaded files are deleted and progress is reset without removing the torrents
|
|
2046
2059
|
"""
|
|
2047
2060
|
|
|
2048
2061
|
torrents_remaining = """library torrents-remaining
|
|
@@ -2764,7 +2764,6 @@ def qBittorrent_torrents(parent_parser):
|
|
|
2764
2764
|
|
|
2765
2765
|
parser.add_argument(
|
|
2766
2766
|
"--complete",
|
|
2767
|
-
"--completed",
|
|
2768
2767
|
"--uploading",
|
|
2769
2768
|
"--upload",
|
|
2770
2769
|
"--up",
|
|
@@ -2773,6 +2772,11 @@ def qBittorrent_torrents(parent_parser):
|
|
|
2773
2772
|
action="store_true",
|
|
2774
2773
|
help="Include completed torrents",
|
|
2775
2774
|
)
|
|
2775
|
+
parser.add_argument(
|
|
2776
|
+
"--completed",
|
|
2777
|
+
action="store_true",
|
|
2778
|
+
help="Include torrents with a recorded completion timestamp",
|
|
2779
|
+
)
|
|
2776
2780
|
parser.add_argument(
|
|
2777
2781
|
"--incomplete",
|
|
2778
2782
|
"--downloading",
|
|
@@ -2795,11 +2799,21 @@ def qBittorrent_torrents(parent_parser):
|
|
|
2795
2799
|
help="Include torrents with files currently in use by processes",
|
|
2796
2800
|
)
|
|
2797
2801
|
parser.add_argument("--private", action=argparse.BooleanOptionalAction, help="Match only private torrents")
|
|
2798
|
-
parser.
|
|
2802
|
+
drive_filter = parser.add_mutually_exclusive_group()
|
|
2803
|
+
drive_filter.add_argument(
|
|
2799
2804
|
"--different-drives",
|
|
2800
|
-
|
|
2805
|
+
dest="different_drives",
|
|
2806
|
+
action="store_true",
|
|
2801
2807
|
help="Include torrents whose incomplete and final paths are on different mountpoints",
|
|
2802
2808
|
)
|
|
2809
|
+
drive_filter.add_argument(
|
|
2810
|
+
"--no-different-drives",
|
|
2811
|
+
"--same-drives",
|
|
2812
|
+
dest="different_drives",
|
|
2813
|
+
action="store_false",
|
|
2814
|
+
help="Include torrents whose incomplete and final paths are on the same mountpoint",
|
|
2815
|
+
)
|
|
2816
|
+
parser.set_defaults(different_drives=None)
|
|
2803
2817
|
parser.add_argument(
|
|
2804
2818
|
"--path-search",
|
|
2805
2819
|
choices=["status", "download", "save", "both"],
|
|
@@ -8,6 +8,18 @@ from library.utils.log_utils import log
|
|
|
8
8
|
webbrowser.register("termux-open-url '%s'", None)
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
def catt_default_device() -> str:
|
|
12
|
+
"""catt's own configured speaker, i.e. options.device in ~/.config/catt/catt.cfg."""
|
|
13
|
+
config = Path.home() / ".config" / "catt" / "catt.cfg"
|
|
14
|
+
if not config.exists():
|
|
15
|
+
return ""
|
|
16
|
+
for line in config.read_text().splitlines():
|
|
17
|
+
key, _, value = line.partition("=")
|
|
18
|
+
if key.strip() == "device":
|
|
19
|
+
return value.strip()
|
|
20
|
+
return ""
|
|
21
|
+
|
|
22
|
+
|
|
11
23
|
def get_ip_of_chromecast(device_name) -> str:
|
|
12
24
|
from pychromecast import discovery
|
|
13
25
|
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/python3
|
|
2
|
-
|
|
3
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
from library import usage
|
|
7
|
-
from library.createdb import torrents_add
|
|
8
|
-
from library.utils import arggroups, argparse_utils, printing, strings
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def parse_args():
|
|
12
|
-
parser = argparse_utils.ArgumentParser(usage=usage.torrents_dump)
|
|
13
|
-
arggroups.debug(parser)
|
|
14
|
-
|
|
15
|
-
arggroups.paths_or_stdin(parser)
|
|
16
|
-
args = parser.parse_args()
|
|
17
|
-
arggroups.args_post(args, parser)
|
|
18
|
-
return args
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def gen_torrents(l):
|
|
22
|
-
for s in l:
|
|
23
|
-
p = Path(s)
|
|
24
|
-
if p.is_dir():
|
|
25
|
-
for file in p.rglob("*.torrent"):
|
|
26
|
-
if not file.is_dir():
|
|
27
|
-
yield file
|
|
28
|
-
elif p.is_file() and p.suffix == ".torrent":
|
|
29
|
-
yield p
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def torrents_dump():
|
|
33
|
-
args = parse_args()
|
|
34
|
-
|
|
35
|
-
torrent_files = list(gen_torrents(args.paths))
|
|
36
|
-
with ThreadPoolExecutor() as executor:
|
|
37
|
-
metadata_results = executor.map(torrents_add.extract_metadata, torrent_files)
|
|
38
|
-
torrents = list(zip(torrent_files, metadata_results))
|
|
39
|
-
|
|
40
|
-
for i, (torrent_path, d) in enumerate(torrents):
|
|
41
|
-
torrent = torrents_add.torrent_decode(torrent_path)
|
|
42
|
-
trackers = [t for t in torrent.trackers()]
|
|
43
|
-
announce_urls = []
|
|
44
|
-
for t in sorted(trackers, key=lambda t: (t.source, t.tier)):
|
|
45
|
-
try:
|
|
46
|
-
if url := t.url:
|
|
47
|
-
announce_urls.append(url)
|
|
48
|
-
except UnicodeDecodeError:
|
|
49
|
-
continue
|
|
50
|
-
|
|
51
|
-
printing.table(
|
|
52
|
-
[
|
|
53
|
-
d
|
|
54
|
-
| {
|
|
55
|
-
"size": strings.file_size(d["size"]),
|
|
56
|
-
"size_avg": strings.file_size(d["size_avg"]),
|
|
57
|
-
"size_median": strings.file_size(d["size_median"]),
|
|
58
|
-
"time_uploaded": strings.relative_datetime(d["time_uploaded"]),
|
|
59
|
-
"time_created": strings.relative_datetime(d["time_created"]),
|
|
60
|
-
"time_modified": strings.relative_datetime(d["time_modified"]),
|
|
61
|
-
"time_created": strings.relative_datetime(d["time_created"]),
|
|
62
|
-
"files": d["files"][0]["path"] + ", ...",
|
|
63
|
-
"announce_urls": " ".join(announce_urls),
|
|
64
|
-
"web_seeds": " ".join(d["web_seeds"]),
|
|
65
|
-
}
|
|
66
|
-
]
|
|
67
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|