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.
Files changed (160) hide show
  1. {library-3.2.6 → library-3.2.7}/.github/README.md +15 -2
  2. {library-3.2.6 → library-3.2.7}/PKG-INFO +16 -3
  3. {library-3.2.6 → library-3.2.7}/library/__main__.py +1 -1
  4. library-3.2.7/library/mediafiles/torrents_dump.py +89 -0
  5. {library-3.2.6 → library-3.2.7}/library/playback/play_actions.py +2 -1
  6. {library-3.2.6 → library-3.2.7}/library/playback/playback_control.py +2 -2
  7. {library-3.2.6 → library-3.2.7}/library/playback/torrents_info.py +40 -3
  8. {library-3.2.6 → library-3.2.7}/library/usage.py +14 -1
  9. {library-3.2.6 → library-3.2.7}/library/utils/arggroups.py +17 -3
  10. {library-3.2.6 → library-3.2.7}/library/utils/devices.py +12 -0
  11. {library-3.2.6 → library-3.2.7}/pyproject.toml +1 -1
  12. library-3.2.6/library/mediafiles/torrents_dump.py +0 -67
  13. {library-3.2.6 → library-3.2.7}/LICENSE +0 -0
  14. {library-3.2.6 → library-3.2.7}/library/__init__.py +0 -0
  15. {library-3.2.6 → library-3.2.7}/library/assets/__init__.py +0 -0
  16. {library-3.2.6 → library-3.2.7}/library/assets/calibre.css +0 -0
  17. {library-3.2.6 → library-3.2.7}/library/assets/kotobago.png +0 -0
  18. {library-3.2.6 → library-3.2.7}/library/createdb/__init__.py +0 -0
  19. {library-3.2.6 → library-3.2.7}/library/createdb/av.py +0 -0
  20. {library-3.2.6 → library-3.2.7}/library/createdb/computer_info.py +0 -0
  21. {library-3.2.6 → library-3.2.7}/library/createdb/computers_add.py +0 -0
  22. {library-3.2.6 → library-3.2.7}/library/createdb/fs_add.py +0 -0
  23. {library-3.2.6 → library-3.2.7}/library/createdb/fs_add_metadata.py +0 -0
  24. {library-3.2.6 → library-3.2.7}/library/createdb/gallery_add.py +0 -0
  25. {library-3.2.6 → library-3.2.7}/library/createdb/gallery_backend.py +0 -0
  26. {library-3.2.6 → library-3.2.7}/library/createdb/getty_add.py +0 -0
  27. {library-3.2.6 → library-3.2.7}/library/createdb/hn_add.py +0 -0
  28. {library-3.2.6 → library-3.2.7}/library/createdb/links_add.py +0 -0
  29. {library-3.2.6 → library-3.2.7}/library/createdb/nicotine_import.py +0 -0
  30. {library-3.2.6 → library-3.2.7}/library/createdb/places_import.py +0 -0
  31. {library-3.2.6 → library-3.2.7}/library/createdb/reddit_add.py +0 -0
  32. {library-3.2.6 → library-3.2.7}/library/createdb/row_add.py +0 -0
  33. {library-3.2.6 → library-3.2.7}/library/createdb/site_add.py +0 -0
  34. {library-3.2.6 → library-3.2.7}/library/createdb/substack.py +0 -0
  35. {library-3.2.6 → library-3.2.7}/library/createdb/subtitle.py +0 -0
  36. {library-3.2.6 → library-3.2.7}/library/createdb/tables_add.py +0 -0
  37. {library-3.2.6 → library-3.2.7}/library/createdb/tabs_add.py +0 -0
  38. {library-3.2.6 → library-3.2.7}/library/createdb/tildes.py +0 -0
  39. {library-3.2.6 → library-3.2.7}/library/createdb/torrents_add.py +0 -0
  40. {library-3.2.6 → library-3.2.7}/library/createdb/tube_add.py +0 -0
  41. {library-3.2.6 → library-3.2.7}/library/createdb/tube_backend.py +0 -0
  42. {library-3.2.6 → library-3.2.7}/library/createdb/web_add.py +0 -0
  43. {library-3.2.6 → library-3.2.7}/library/data/__init__.py +0 -0
  44. {library-3.2.6 → library-3.2.7}/library/data/dictionary.pkl +0 -0
  45. {library-3.2.6 → library-3.2.7}/library/data/ffmpeg_errors.py +0 -0
  46. {library-3.2.6 → library-3.2.7}/library/data/http_errors.py +0 -0
  47. {library-3.2.6 → library-3.2.7}/library/data/imagemagick_errors.py +0 -0
  48. {library-3.2.6 → library-3.2.7}/library/data/unar_errors.py +0 -0
  49. {library-3.2.6 → library-3.2.7}/library/data/wordbank.py +0 -0
  50. {library-3.2.6 → library-3.2.7}/library/data/yt_dlp_errors.py +0 -0
  51. {library-3.2.6 → library-3.2.7}/library/editdb/__init__.py +0 -0
  52. {library-3.2.6 → library-3.2.7}/library/editdb/dedupe_db.py +0 -0
  53. {library-3.2.6 → library-3.2.7}/library/editdb/dedupe_media.py +0 -0
  54. {library-3.2.6 → library-3.2.7}/library/editdb/merge_online_local.py +0 -0
  55. {library-3.2.6 → library-3.2.7}/library/editdb/mpv_watchlater.py +0 -0
  56. {library-3.2.6 → library-3.2.7}/library/editdb/pushshift.py +0 -0
  57. {library-3.2.6 → library-3.2.7}/library/editdb/reddit_selftext.py +0 -0
  58. {library-3.2.6 → library-3.2.7}/library/files/__init__.py +0 -0
  59. {library-3.2.6 → library-3.2.7}/library/files/christen.py +0 -0
  60. {library-3.2.6 → library-3.2.7}/library/files/sample_compare.py +0 -0
  61. {library-3.2.6 → library-3.2.7}/library/files/sample_hash.py +0 -0
  62. {library-3.2.6 → library-3.2.7}/library/files/similar_files.py +0 -0
  63. {library-3.2.6 → library-3.2.7}/library/folders/__init__.py +0 -0
  64. {library-3.2.6 → library-3.2.7}/library/folders/big_dirs.py +0 -0
  65. {library-3.2.6 → library-3.2.7}/library/folders/filter_src.py +0 -0
  66. {library-3.2.6 → library-3.2.7}/library/folders/merge_mv.py +0 -0
  67. {library-3.2.6 → library-3.2.7}/library/folders/mergerfs_cp.py +0 -0
  68. {library-3.2.6 → library-3.2.7}/library/folders/mount_stats.py +0 -0
  69. {library-3.2.6 → library-3.2.7}/library/folders/move_list.py +0 -0
  70. {library-3.2.6 → library-3.2.7}/library/folders/scatter.py +0 -0
  71. {library-3.2.6 → library-3.2.7}/library/folders/similar_folders.py +0 -0
  72. {library-3.2.6 → library-3.2.7}/library/fsdb/__init__.py +0 -0
  73. {library-3.2.6 → library-3.2.7}/library/fsdb/disk_usage.py +0 -0
  74. {library-3.2.6 → library-3.2.7}/library/fsdb/filesystem.py +0 -0
  75. {library-3.2.6 → library-3.2.7}/library/fsdb/folder_stats.py +0 -0
  76. {library-3.2.6 → library-3.2.7}/library/fsdb/search_db.py +0 -0
  77. {library-3.2.6 → library-3.2.7}/library/lb.py +0 -0
  78. {library-3.2.6 → library-3.2.7}/library/mediadb/__init__.py +0 -0
  79. {library-3.2.6 → library-3.2.7}/library/mediadb/block.py +0 -0
  80. {library-3.2.6 → library-3.2.7}/library/mediadb/db_history.py +0 -0
  81. {library-3.2.6 → library-3.2.7}/library/mediadb/db_media.py +0 -0
  82. {library-3.2.6 → library-3.2.7}/library/mediadb/db_playlists.py +0 -0
  83. {library-3.2.6 → library-3.2.7}/library/mediadb/download.py +0 -0
  84. {library-3.2.6 → library-3.2.7}/library/mediadb/download_status.py +0 -0
  85. {library-3.2.6 → library-3.2.7}/library/mediadb/history.py +0 -0
  86. {library-3.2.6 → library-3.2.7}/library/mediadb/history_add.py +0 -0
  87. {library-3.2.6 → library-3.2.7}/library/mediadb/optimize_db.py +0 -0
  88. {library-3.2.6 → library-3.2.7}/library/mediadb/playlists.py +0 -0
  89. {library-3.2.6 → library-3.2.7}/library/mediadb/redownload.py +0 -0
  90. {library-3.2.6 → library-3.2.7}/library/mediadb/search.py +0 -0
  91. {library-3.2.6 → library-3.2.7}/library/mediadb/stats.py +0 -0
  92. {library-3.2.6 → library-3.2.7}/library/mediafiles/__init__.py +0 -0
  93. {library-3.2.6 → library-3.2.7}/library/mediafiles/images_to_pdf.py +0 -0
  94. {library-3.2.6 → library-3.2.7}/library/mediafiles/media_check.py +0 -0
  95. {library-3.2.6 → library-3.2.7}/library/mediafiles/pdf_edit.py +0 -0
  96. {library-3.2.6 → library-3.2.7}/library/mediafiles/process_ffmpeg.py +0 -0
  97. {library-3.2.6 → library-3.2.7}/library/mediafiles/process_image.py +0 -0
  98. {library-3.2.6 → library-3.2.7}/library/mediafiles/process_media.py +0 -0
  99. {library-3.2.6 → library-3.2.7}/library/mediafiles/process_text.py +0 -0
  100. {library-3.2.6 → library-3.2.7}/library/mediafiles/torrents_start.py +0 -0
  101. {library-3.2.6 → library-3.2.7}/library/mediafiles/unardel.py +0 -0
  102. {library-3.2.6 → library-3.2.7}/library/misc/__init__.py +0 -0
  103. {library-3.2.6 → library-3.2.7}/library/misc/dedupe_czkawka.py +0 -0
  104. {library-3.2.6 → library-3.2.7}/library/misc/export_text.py +0 -0
  105. {library-3.2.6 → library-3.2.7}/library/misc/search_help.py +0 -0
  106. {library-3.2.6 → library-3.2.7}/library/multidb/__init__.py +0 -0
  107. {library-3.2.6 → library-3.2.7}/library/multidb/allocate_torrents.py +0 -0
  108. {library-3.2.6 → library-3.2.7}/library/multidb/copy_play_counts.py +0 -0
  109. {library-3.2.6 → library-3.2.7}/library/multidb/merge_dbs.py +0 -0
  110. {library-3.2.6 → library-3.2.7}/library/playback/__init__.py +0 -0
  111. {library-3.2.6 → library-3.2.7}/library/playback/links_open.py +0 -0
  112. {library-3.2.6 → library-3.2.7}/library/playback/media_player.py +0 -0
  113. {library-3.2.6 → library-3.2.7}/library/playback/media_printer.py +0 -0
  114. {library-3.2.6 → library-3.2.7}/library/playback/post_actions.py +0 -0
  115. {library-3.2.6 → library-3.2.7}/library/playback/surf.py +0 -0
  116. {library-3.2.6 → library-3.2.7}/library/playback/tabs_open.py +0 -0
  117. {library-3.2.6 → library-3.2.7}/library/playback/torrents_remaining.py +0 -0
  118. {library-3.2.6 → library-3.2.7}/library/readme.py +0 -0
  119. {library-3.2.6 → library-3.2.7}/library/tablefiles/__init__.py +0 -0
  120. {library-3.2.6 → library-3.2.7}/library/tablefiles/columns.py +0 -0
  121. {library-3.2.6 → library-3.2.7}/library/tablefiles/eda.py +0 -0
  122. {library-3.2.6 → library-3.2.7}/library/tablefiles/incremental_diff.py +0 -0
  123. {library-3.2.6 → library-3.2.7}/library/tablefiles/markdown_tables.py +0 -0
  124. {library-3.2.6 → library-3.2.7}/library/tablefiles/mcda.py +0 -0
  125. {library-3.2.6 → library-3.2.7}/library/tablefiles/plot.py +0 -0
  126. {library-3.2.6 → library-3.2.7}/library/text/__init__.py +0 -0
  127. {library-3.2.6 → library-3.2.7}/library/text/cluster_sort.py +0 -0
  128. {library-3.2.6 → library-3.2.7}/library/text/combinations.py +0 -0
  129. {library-3.2.6 → library-3.2.7}/library/text/expand_links.py +0 -0
  130. {library-3.2.6 → library-3.2.7}/library/text/extract_links.py +0 -0
  131. {library-3.2.6 → library-3.2.7}/library/text/extract_text.py +0 -0
  132. {library-3.2.6 → library-3.2.7}/library/text/json_keys_rename.py +0 -0
  133. {library-3.2.6 → library-3.2.7}/library/text/markdown_links.py +0 -0
  134. {library-3.2.6 → library-3.2.7}/library/text/nouns.py +0 -0
  135. {library-3.2.6 → library-3.2.7}/library/text/regex_sort.py +0 -0
  136. {library-3.2.6 → library-3.2.7}/library/text/timestamps.py +0 -0
  137. {library-3.2.6 → library-3.2.7}/library/utils/__init__.py +0 -0
  138. {library-3.2.6 → library-3.2.7}/library/utils/arg_utils.py +0 -0
  139. {library-3.2.6 → library-3.2.7}/library/utils/argparse_utils.py +0 -0
  140. {library-3.2.6 → library-3.2.7}/library/utils/consts.py +0 -0
  141. {library-3.2.6 → library-3.2.7}/library/utils/date_utils.py +0 -0
  142. {library-3.2.6 → library-3.2.7}/library/utils/db_utils.py +0 -0
  143. {library-3.2.6 → library-3.2.7}/library/utils/file_utils.py +0 -0
  144. {library-3.2.6 → library-3.2.7}/library/utils/filter_engine.py +0 -0
  145. {library-3.2.6 → library-3.2.7}/library/utils/gui.py +0 -0
  146. {library-3.2.6 → library-3.2.7}/library/utils/iterables.py +0 -0
  147. {library-3.2.6 → library-3.2.7}/library/utils/log_utils.py +0 -0
  148. {library-3.2.6 → library-3.2.7}/library/utils/mpv_utils.py +0 -0
  149. {library-3.2.6 → library-3.2.7}/library/utils/nums.py +0 -0
  150. {library-3.2.6 → library-3.2.7}/library/utils/objects.py +0 -0
  151. {library-3.2.6 → library-3.2.7}/library/utils/path_utils.py +0 -0
  152. {library-3.2.6 → library-3.2.7}/library/utils/pd_utils.py +0 -0
  153. {library-3.2.6 → library-3.2.7}/library/utils/printing.py +0 -0
  154. {library-3.2.6 → library-3.2.7}/library/utils/processes.py +0 -0
  155. {library-3.2.6 → library-3.2.7}/library/utils/remote_processes.py +0 -0
  156. {library-3.2.6 → library-3.2.7}/library/utils/shell_utils.py +0 -0
  157. {library-3.2.6 → library-3.2.7}/library/utils/sql_utils.py +0 -0
  158. {library-3.2.6 → library-3.2.7}/library/utils/sqlgroups.py +0 -0
  159. {library-3.2.6 → library-3.2.7}/library/utils/strings.py +0 -0
  160. {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.006; 102 subcommands)
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.6
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.006; 102 subcommands)
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>
@@ -5,7 +5,7 @@ from tabulate import tabulate
5
5
  from library.utils import argparse_utils, iterables
6
6
  from library.utils.log_utils import log
7
7
 
8
- __version__ = "3.2.006"
8
+ __version__ = "3.2.007"
9
9
 
10
10
  progs = {
11
11
  "Create database subcommands": {
@@ -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, default_chromecast="Xylo and Orchestra")
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 = [t for t in torrents if torrent_paths_on_different_drives(t)]
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 = "both" if getattr(args, "different_drives", False) else "status"
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.add_argument(
2802
+ drive_filter = parser.add_mutually_exclusive_group()
2803
+ drive_filter.add_argument(
2799
2804
  "--different-drives",
2800
- action=argparse.BooleanOptionalAction,
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
 
@@ -35,7 +35,7 @@ dynamic = []
35
35
  name = "library"
36
36
  readme = ".github/README.md"
37
37
  requires-python = ">=3.12"
38
- version = "3.2.6"
38
+ version = "3.2.7"
39
39
 
40
40
  [project.license]
41
41
  file = "LICENSE"
@@ -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