library 3.1.1__tar.gz → 3.2.2__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.1.1 → library-3.2.2}/.github/README.md +32 -5
- {library-3.1.1 → library-3.2.2}/PKG-INFO +33 -6
- {library-3.1.1 → library-3.2.2}/library/__main__.py +1 -1
- {library-3.1.1 → library-3.2.2}/library/createdb/av.py +1 -1
- {library-3.1.1 → library-3.2.2}/library/createdb/fs_add.py +11 -9
- {library-3.1.1 → library-3.2.2}/library/createdb/gallery_add.py +4 -1
- {library-3.1.1 → library-3.2.2}/library/createdb/gallery_backend.py +15 -11
- {library-3.1.1 → library-3.2.2}/library/createdb/reddit_add.py +1 -1
- {library-3.1.1 → library-3.2.2}/library/createdb/tabs_add.py +1 -1
- {library-3.1.1 → library-3.2.2}/library/createdb/tube_backend.py +0 -1
- {library-3.1.1 → library-3.2.2}/library/files/llm_map.py +2 -0
- {library-3.1.1 → library-3.2.2}/library/files/sample_compare.py +9 -7
- {library-3.1.1 → library-3.2.2}/library/folders/merge_mv.py +4 -1
- {library-3.1.1 → library-3.2.2}/library/folders/scatter.py +6 -1
- {library-3.1.1 → library-3.2.2}/library/mediadb/block.py +6 -2
- {library-3.1.1 → library-3.2.2}/library/mediadb/db_media.py +1 -1
- {library-3.1.1 → library-3.2.2}/library/mediadb/db_playlists.py +35 -11
- {library-3.1.1 → library-3.2.2}/library/mediadb/download.py +3 -1
- {library-3.1.1 → library-3.2.2}/library/mediadb/history.py +12 -1
- {library-3.1.1 → library-3.2.2}/library/mediafiles/media_check.py +1 -1
- {library-3.1.1 → library-3.2.2}/library/mediafiles/process_ffmpeg.py +1 -1
- {library-3.1.1 → library-3.2.2}/library/mediafiles/process_image.py +1 -1
- {library-3.1.1 → library-3.2.2}/library/mediafiles/unardel.py +4 -5
- {library-3.1.1 → library-3.2.2}/library/playback/media_player.py +1 -1
- {library-3.1.1 → library-3.2.2}/library/playback/media_printer.py +4 -4
- library-3.2.2/library/tablefiles/plot.py +309 -0
- {library-3.1.1 → library-3.2.2}/library/text/nouns.py +1 -1
- {library-3.1.1 → library-3.2.2}/library/usage.py +31 -4
- {library-3.1.1 → library-3.2.2}/library/utils/arg_utils.py +6 -5
- {library-3.1.1 → library-3.2.2}/library/utils/arggroups.py +511 -29
- {library-3.1.1 → library-3.2.2}/library/utils/consts.py +3 -3
- {library-3.1.1 → library-3.2.2}/library/utils/filter_engine.py +2 -10
- {library-3.1.1 → library-3.2.2}/library/utils/iterables.py +2 -2
- {library-3.1.1 → library-3.2.2}/library/utils/nums.py +13 -2
- {library-3.1.1 → library-3.2.2}/library/utils/objects.py +20 -0
- {library-3.1.1 → library-3.2.2}/library/utils/processes.py +1 -1
- {library-3.1.1 → library-3.2.2}/library/utils/sqlgroups.py +1 -0
- {library-3.1.1 → library-3.2.2}/library/utils/strings.py +6 -6
- {library-3.1.1 → library-3.2.2}/library/utils/web.py +56 -9
- {library-3.1.1 → library-3.2.2}/pyproject.toml +1 -1
- library-3.1.1/library/tablefiles/plot.py +0 -94
- {library-3.1.1 → library-3.2.2}/LICENSE +0 -0
- {library-3.1.1 → library-3.2.2}/library/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/assets/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/assets/calibre.css +0 -0
- {library-3.1.1 → library-3.2.2}/library/assets/kotobago.png +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/computer_info.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/computers_add.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/fs_add_metadata.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/getty_add.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/hn_add.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/links_add.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/nicotine_import.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/places_import.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/row_add.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/site_add.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/substack.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/subtitle.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/tables_add.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/tildes.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/torrents_add.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/tube_add.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/createdb/web_add.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/data/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/data/dictionary.pkl +0 -0
- {library-3.1.1 → library-3.2.2}/library/data/ffmpeg_errors.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/data/http_errors.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/data/imagemagick_errors.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/data/unar_errors.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/data/wordbank.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/data/yt_dlp_errors.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/editdb/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/editdb/dedupe_db.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/editdb/dedupe_media.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/editdb/merge_online_local.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/editdb/mpv_watchlater.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/editdb/pushshift.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/editdb/reddit_selftext.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/files/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/files/christen.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/files/sample_hash.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/files/similar_files.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/folders/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/folders/big_dirs.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/folders/filter_src.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/folders/mergerfs_cp.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/folders/mount_stats.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/folders/move_list.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/folders/similar_folders.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/fsdb/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/fsdb/disk_usage.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/fsdb/filesystem.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/fsdb/folder_stats.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/fsdb/search_db.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/lb.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediadb/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediadb/db_history.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediadb/download_status.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediadb/history_add.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediadb/optimize_db.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediadb/playlists.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediadb/redownload.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediadb/search.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediadb/stats.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediafiles/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediafiles/images_to_pdf.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediafiles/pdf_edit.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediafiles/process_media.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediafiles/process_text.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediafiles/torrents_dump.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/mediafiles/torrents_start.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/misc/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/misc/dedupe_czkawka.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/misc/export_text.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/misc/search_help.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/multidb/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/multidb/allocate_torrents.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/multidb/copy_play_counts.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/multidb/merge_dbs.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/playback/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/playback/links_open.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/playback/play_actions.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/playback/playback_control.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/playback/post_actions.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/playback/surf.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/playback/tabs_open.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/playback/torrents_info.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/playback/torrents_remaining.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/readme.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/tablefiles/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/tablefiles/columns.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/tablefiles/eda.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/tablefiles/incremental_diff.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/tablefiles/markdown_tables.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/tablefiles/mcda.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/text/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/text/cluster_sort.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/text/combinations.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/text/expand_links.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/text/extract_links.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/text/extract_text.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/text/json_keys_rename.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/text/markdown_links.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/text/regex_sort.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/text/timestamps.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/__init__.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/argparse_utils.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/date_utils.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/db_utils.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/devices.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/file_utils.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/gui.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/log_utils.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/mpv_utils.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/path_utils.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/pd_utils.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/printing.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/remote_processes.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/shell_utils.py +0 -0
- {library-3.1.1 → library-3.2.2}/library/utils/sql_utils.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.
|
|
102
|
+
library (v3.2.002; 103 subcommands)
|
|
103
103
|
|
|
104
104
|
Create database subcommands:
|
|
105
105
|
╭─────────────────┬──────────────────────────────────────────╮
|
|
@@ -1859,12 +1859,35 @@ BTW, for some cols like time_deleted you'll need to specify a where clause so th
|
|
|
1859
1859
|
<details><summary>Plot table-like files. A CLI interface to matplotlib</summary>
|
|
1860
1860
|
|
|
1861
1861
|
$ library plot -h
|
|
1862
|
-
usage: library plot PATH ... [--table STR] [--end-row INT]
|
|
1862
|
+
usage: library plot PATH ... [--table STR] [--end-row INT] -- [PLT.COMMAND ...]
|
|
1863
1863
|
|
|
1864
1864
|
Plot one or more files
|
|
1865
1865
|
|
|
1866
1866
|
Only 500,000 rows per file are loaded for performance purposes. Set `--end-row inf` to read all the rows and/or run out of RAM.
|
|
1867
1867
|
|
|
1868
|
+
With no commands after `--`, the table is analyzed like `library eda` and a set of figures is generated: histograms for numeric columns, bar charts of common values for low/medium-cardinality columns, time-series when a date column is detected, pairwise scatter plots of the most correlated numeric columns, a correlation heatmap, and a missing-values chart.
|
|
1869
|
+
|
|
1870
|
+
Histograms get a fitted PDF overlay, and each numeric column is drawn on a scale matched to its distribution (D'Agostino-Pearson test): normal data on a linear axis, log-normal or heavy right-skewed positive data on a log axis, data spanning orders of magnitude that includes zero/negative values on a symlog axis, and proportions in (0,1) on a logit axis. `--scale log|symlog|logit` forces that scale on all histograms; `--scale off` disables detection and draws plain linear histograms.
|
|
1871
|
+
|
|
1872
|
+
Pass a list of matplotlib.pyplot commands after `--` for manual control. Each command is a plt function name followed by column names (the first column is the x-axis, the rest are y-axes) and optional positional/keyword arguments. `index` refers to the row index. A single column is passed as a single dataset (e.g. for hist).
|
|
1873
|
+
|
|
1874
|
+
Axes are auto-labeled from the column names and units are guessed from common names (e.g. `size` -> bytes, `duration` -> seconds, `time_*` -> dates). A title is auto-added from the plotted columns (e.g. `scatter size duration` -> "Duration vs Size"). Pass an explicit `xlabel=` / `ylabel=` / `title=` to override.
|
|
1875
|
+
|
|
1876
|
+
A transformation pipeline can be applied before plotting: `--where` filters rows, `--classify` derives a `category` column from an expression or binning, and `--groupby` (with `--agg`) groups and aggregates rows. With `--groupby` the auto mode draws one bar chart per aggregated column (e.g. "Count per Category").
|
|
1877
|
+
|
|
1878
|
+
Examples:
|
|
1879
|
+
|
|
1880
|
+
lb plot data.csv
|
|
1881
|
+
lb plot data.csv -- plot x y
|
|
1882
|
+
lb plot data.csv -- scatter x y s=3 alpha=0.5 label=points
|
|
1883
|
+
lb plot data.csv -- hist x bins=50
|
|
1884
|
+
lb plot audio.db --table media --cols size,duration -- scatter size duration
|
|
1885
|
+
lb plot a.csv b.csv --join-tables -- sort -u time_created desc -- plot index time_created
|
|
1886
|
+
lb plot data.csv --where 'duration > 60'
|
|
1887
|
+
lb plot data.csv --classify 'size > 1GB' --groupby category
|
|
1888
|
+
lb plot data.csv --classify duration --bins 5 --groupby category --agg mean
|
|
1889
|
+
lb plot data.csv --groupby category --agg count --top 10
|
|
1890
|
+
|
|
1868
1891
|
|
|
1869
1892
|
</details>
|
|
1870
1893
|
|
|
@@ -2591,7 +2614,7 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
2591
2614
|
<details><summary>Show and manage playback history</summary>
|
|
2592
2615
|
|
|
2593
2616
|
$ library history -h
|
|
2594
|
-
usage: library history [--frequency daily weekly (monthly) yearly] [--limit LIMIT] DATABASE [(all) watching watched created modified deleted]
|
|
2617
|
+
usage: library history [--frequency daily weekly (monthly) yearly] [--limit LIMIT] DATABASE [PATH ...] [(all) watching watched created modified deleted]
|
|
2595
2618
|
|
|
2596
2619
|
View playback history
|
|
2597
2620
|
|
|
@@ -2617,8 +2640,12 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
2617
2640
|
Delete all history
|
|
2618
2641
|
library history web_add.image.db -L inf --delete-rows
|
|
2619
2642
|
|
|
2620
|
-
|
|
2621
|
-
|
|
2643
|
+
Delete history for exact paths
|
|
2644
|
+
library history ~/mc/links.db --delete-rows https://example.com/article
|
|
2645
|
+
cb | library history ~/mc/links.db --delete-rows -
|
|
2646
|
+
|
|
2647
|
+
See also: library stats -h
|
|
2648
|
+
library history-add -h
|
|
2622
2649
|
|
|
2623
2650
|
|
|
2624
2651
|
</details>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: library
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.2
|
|
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.
|
|
210
|
+
library (v3.2.002; 103 subcommands)
|
|
211
211
|
|
|
212
212
|
Create database subcommands:
|
|
213
213
|
╭─────────────────┬──────────────────────────────────────────╮
|
|
@@ -1967,12 +1967,35 @@ BTW, for some cols like time_deleted you'll need to specify a where clause so th
|
|
|
1967
1967
|
<details><summary>Plot table-like files. A CLI interface to matplotlib</summary>
|
|
1968
1968
|
|
|
1969
1969
|
$ library plot -h
|
|
1970
|
-
usage: library plot PATH ... [--table STR] [--end-row INT]
|
|
1970
|
+
usage: library plot PATH ... [--table STR] [--end-row INT] -- [PLT.COMMAND ...]
|
|
1971
1971
|
|
|
1972
1972
|
Plot one or more files
|
|
1973
1973
|
|
|
1974
1974
|
Only 500,000 rows per file are loaded for performance purposes. Set `--end-row inf` to read all the rows and/or run out of RAM.
|
|
1975
1975
|
|
|
1976
|
+
With no commands after `--`, the table is analyzed like `library eda` and a set of figures is generated: histograms for numeric columns, bar charts of common values for low/medium-cardinality columns, time-series when a date column is detected, pairwise scatter plots of the most correlated numeric columns, a correlation heatmap, and a missing-values chart.
|
|
1977
|
+
|
|
1978
|
+
Histograms get a fitted PDF overlay, and each numeric column is drawn on a scale matched to its distribution (D'Agostino-Pearson test): normal data on a linear axis, log-normal or heavy right-skewed positive data on a log axis, data spanning orders of magnitude that includes zero/negative values on a symlog axis, and proportions in (0,1) on a logit axis. `--scale log|symlog|logit` forces that scale on all histograms; `--scale off` disables detection and draws plain linear histograms.
|
|
1979
|
+
|
|
1980
|
+
Pass a list of matplotlib.pyplot commands after `--` for manual control. Each command is a plt function name followed by column names (the first column is the x-axis, the rest are y-axes) and optional positional/keyword arguments. `index` refers to the row index. A single column is passed as a single dataset (e.g. for hist).
|
|
1981
|
+
|
|
1982
|
+
Axes are auto-labeled from the column names and units are guessed from common names (e.g. `size` -> bytes, `duration` -> seconds, `time_*` -> dates). A title is auto-added from the plotted columns (e.g. `scatter size duration` -> "Duration vs Size"). Pass an explicit `xlabel=` / `ylabel=` / `title=` to override.
|
|
1983
|
+
|
|
1984
|
+
A transformation pipeline can be applied before plotting: `--where` filters rows, `--classify` derives a `category` column from an expression or binning, and `--groupby` (with `--agg`) groups and aggregates rows. With `--groupby` the auto mode draws one bar chart per aggregated column (e.g. "Count per Category").
|
|
1985
|
+
|
|
1986
|
+
Examples:
|
|
1987
|
+
|
|
1988
|
+
lb plot data.csv
|
|
1989
|
+
lb plot data.csv -- plot x y
|
|
1990
|
+
lb plot data.csv -- scatter x y s=3 alpha=0.5 label=points
|
|
1991
|
+
lb plot data.csv -- hist x bins=50
|
|
1992
|
+
lb plot audio.db --table media --cols size,duration -- scatter size duration
|
|
1993
|
+
lb plot a.csv b.csv --join-tables -- sort -u time_created desc -- plot index time_created
|
|
1994
|
+
lb plot data.csv --where 'duration > 60'
|
|
1995
|
+
lb plot data.csv --classify 'size > 1GB' --groupby category
|
|
1996
|
+
lb plot data.csv --classify duration --bins 5 --groupby category --agg mean
|
|
1997
|
+
lb plot data.csv --groupby category --agg count --top 10
|
|
1998
|
+
|
|
1976
1999
|
|
|
1977
2000
|
</details>
|
|
1978
2001
|
|
|
@@ -2699,7 +2722,7 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
2699
2722
|
<details><summary>Show and manage playback history</summary>
|
|
2700
2723
|
|
|
2701
2724
|
$ library history -h
|
|
2702
|
-
usage: library history [--frequency daily weekly (monthly) yearly] [--limit LIMIT] DATABASE [(all) watching watched created modified deleted]
|
|
2725
|
+
usage: library history [--frequency daily weekly (monthly) yearly] [--limit LIMIT] DATABASE [PATH ...] [(all) watching watched created modified deleted]
|
|
2703
2726
|
|
|
2704
2727
|
View playback history
|
|
2705
2728
|
|
|
@@ -2725,8 +2748,12 @@ Inspired somewhat by https://nikkhokkho.sourceforge.io/?page=FileOptimizer
|
|
|
2725
2748
|
Delete all history
|
|
2726
2749
|
library history web_add.image.db -L inf --delete-rows
|
|
2727
2750
|
|
|
2728
|
-
|
|
2729
|
-
|
|
2751
|
+
Delete history for exact paths
|
|
2752
|
+
library history ~/mc/links.db --delete-rows https://example.com/article
|
|
2753
|
+
cb | library history ~/mc/links.db --delete-rows -
|
|
2754
|
+
|
|
2755
|
+
See also: library stats -h
|
|
2756
|
+
library history-add -h
|
|
2730
2757
|
|
|
2731
2758
|
|
|
2732
2759
|
</details>
|
|
@@ -213,7 +213,7 @@ def munge_av_tags(args, m) -> dict:
|
|
|
213
213
|
args.delete_corrupt, corruption, duration
|
|
214
214
|
) and not file_utils.is_file_open(path):
|
|
215
215
|
threshold_str = (
|
|
216
|
-
strings.percent(args.delete_corrupt) if 0 < args.delete_corrupt < 1 else
|
|
216
|
+
strings.percent(args.delete_corrupt) if 0 < args.delete_corrupt < 1 else f"{args.delete_corrupt}s"
|
|
217
217
|
)
|
|
218
218
|
log.warning(
|
|
219
219
|
"Deleting %s corruption %.1f%% exceeded threshold %s", path, corruption * 100, threshold_str
|
|
@@ -77,15 +77,17 @@ def extract_chunk(args, media) -> None:
|
|
|
77
77
|
is_scan_all_files = getattr(args, "scan_all_files", False)
|
|
78
78
|
|
|
79
79
|
if objects.is_profile(args, DBType.image):
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
if is_scan_all_files:
|
|
81
|
+
image_media = list(media)
|
|
82
|
+
other_media = []
|
|
83
|
+
else:
|
|
84
|
+
image_media, other_media = [], []
|
|
85
|
+
for m in media:
|
|
86
|
+
if m["path"].rsplit(".", 1)[-1].lower() in consts.IMAGE_EXTENSIONS:
|
|
87
|
+
image_media.append(m)
|
|
88
|
+
else:
|
|
89
|
+
other_media.append(m)
|
|
83
90
|
if image_media:
|
|
84
|
-
other_media = [
|
|
85
|
-
m
|
|
86
|
-
for m in media
|
|
87
|
-
if m["path"].rsplit(".", 1)[-1].lower() not in consts.IMAGE_EXTENSIONS or is_scan_all_files
|
|
88
|
-
]
|
|
89
91
|
image_media = extract_image_metadata_chunk(image_media)
|
|
90
92
|
media = image_media + other_media
|
|
91
93
|
|
|
@@ -105,7 +107,7 @@ def extract_chunk(args, media) -> None:
|
|
|
105
107
|
if description:
|
|
106
108
|
tags += "\n" + description
|
|
107
109
|
if tags:
|
|
108
|
-
caption["
|
|
110
|
+
caption["caption_t0"] = {"time": 0, "text": tags}
|
|
109
111
|
|
|
110
112
|
captions.append(caption)
|
|
111
113
|
|
|
@@ -58,12 +58,15 @@ def gallery_add(args=None) -> None:
|
|
|
58
58
|
if not args.force and len(paths) > 9:
|
|
59
59
|
known_playlists = db_media.get_paths(args)
|
|
60
60
|
|
|
61
|
+
if args.safe:
|
|
62
|
+
gallery_backend.load_module_level_gallery_dl(args)
|
|
63
|
+
|
|
61
64
|
for path in paths:
|
|
62
65
|
if path in known_playlists:
|
|
63
66
|
log.info("Known already. Skipping %s", path)
|
|
64
67
|
continue
|
|
65
68
|
|
|
66
|
-
if args.safe and not gallery_backend.is_supported(
|
|
69
|
+
if args.safe and not gallery_backend.is_supported(path):
|
|
67
70
|
log.info("Skipping unsupported playlist (safe_mode) %s", path)
|
|
68
71
|
continue
|
|
69
72
|
|
|
@@ -12,7 +12,7 @@ from library.utils.log_utils import log
|
|
|
12
12
|
gallery_dl = None
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def load_module_level_gallery_dl(args) -> ModuleType:
|
|
15
|
+
def load_module_level_gallery_dl(args=None) -> ModuleType:
|
|
16
16
|
global gallery_dl
|
|
17
17
|
|
|
18
18
|
if gallery_dl is None:
|
|
@@ -20,12 +20,6 @@ def load_module_level_gallery_dl(args) -> ModuleType:
|
|
|
20
20
|
|
|
21
21
|
gallery_dl.config.load()
|
|
22
22
|
|
|
23
|
-
download_archive = Path(args.download_archive or "~/.local/share/gallerydl.sqlite3").expanduser().resolve()
|
|
24
|
-
if download_archive.exists():
|
|
25
|
-
gallery_dl.config.set(("extractor",), "archive", str(download_archive))
|
|
26
|
-
|
|
27
|
-
if hasattr(args, "prefix"):
|
|
28
|
-
gallery_dl.config.set(("extractor",), "base-directory", args.prefix)
|
|
29
23
|
gallery_dl.config.set(("extractor",), "parent-directory", False)
|
|
30
24
|
gallery_dl.config.set(
|
|
31
25
|
("extractor",),
|
|
@@ -47,13 +41,23 @@ def load_module_level_gallery_dl(args) -> ModuleType:
|
|
|
47
41
|
if consts.PYTEST_RUNNING:
|
|
48
42
|
gallery_dl.config.set(("extractor",), "download", False)
|
|
49
43
|
|
|
44
|
+
is_supported.extractors = gallery_dl.extractor.extractors()
|
|
45
|
+
|
|
46
|
+
if args is not None:
|
|
47
|
+
is_supported.configured = True
|
|
48
|
+
download_archive = Path(args.download_archive or "~/.local/share/gallerydl.sqlite3").expanduser().resolve()
|
|
49
|
+
if download_archive.exists():
|
|
50
|
+
gallery_dl.config.set(("extractor",), "archive", str(download_archive))
|
|
51
|
+
|
|
52
|
+
if hasattr(args, "prefix"):
|
|
53
|
+
gallery_dl.config.set(("extractor",), "base-directory", args.prefix)
|
|
54
|
+
|
|
50
55
|
return gallery_dl
|
|
51
56
|
|
|
52
57
|
|
|
53
|
-
def is_supported(
|
|
54
|
-
if getattr(is_supported, "
|
|
55
|
-
|
|
56
|
-
is_supported.extractors = gallery_dl.extractor.extractors()
|
|
58
|
+
def is_supported(url) -> bool:
|
|
59
|
+
if not getattr(is_supported, "configured", False):
|
|
60
|
+
raise RuntimeError("gallery_backend.is_supported() requires load_module_level_gallery_dl(args) to be called first")
|
|
57
61
|
|
|
58
62
|
return any(ie.pattern.match(url) and ie.category != "generic" for ie in is_supported.extractors)
|
|
59
63
|
|
|
@@ -141,7 +141,7 @@ def slim_post_data(d: dict, subreddit=None) -> dict:
|
|
|
141
141
|
skip_domains = ["linktr.ee", "twitter.com", "t.me", "patreon", "onlyfans", "fans.ly", "file-upload", "file-link"]
|
|
142
142
|
url = d.get("url")
|
|
143
143
|
if not url or any(domain in url for domain in skip_domains):
|
|
144
|
-
|
|
144
|
+
url = d.get("url_overridden_by_dest")
|
|
145
145
|
|
|
146
146
|
selftext = d.get("selftext")
|
|
147
147
|
if selftext and selftext in (
|
|
@@ -29,7 +29,7 @@ def parse_args() -> argparse.Namespace:
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def get_days(frequency):
|
|
32
|
-
d = {"weekly": 7, "monthly": 30, "quarterly":
|
|
32
|
+
d = {"weekly": 7, "monthly": 30, "quarterly": 91, "yearly": 364, "decadally": 3640}
|
|
33
33
|
return d.get(frequency, 7)
|
|
34
34
|
|
|
35
35
|
|
|
@@ -316,7 +316,6 @@ def get_extra_metadata(args, playlist_path, playlist_dl_opts=None) -> list[dict]
|
|
|
316
316
|
if len(captions) > 0:
|
|
317
317
|
args.db["captions"].insert_all(captions, alter=True)
|
|
318
318
|
|
|
319
|
-
entry["id"] = media_id
|
|
320
319
|
entry["playlists_id"] = playlists_id
|
|
321
320
|
entry["chapter_count"] = chapter_count
|
|
322
321
|
|
|
@@ -79,6 +79,7 @@ def llm_map():
|
|
|
79
79
|
import pandas as pd
|
|
80
80
|
|
|
81
81
|
results = []
|
|
82
|
+
base_llama_args = list(args.llama_args)
|
|
82
83
|
for path in gen_paths(args):
|
|
83
84
|
prompt = args.prompt
|
|
84
85
|
|
|
@@ -91,6 +92,7 @@ def llm_map():
|
|
|
91
92
|
for k, v in replacements.items():
|
|
92
93
|
prompt = prompt.replace(k, "\n" + v + "\n\n")
|
|
93
94
|
|
|
95
|
+
args.llama_args = list(base_llama_args)
|
|
94
96
|
if args.image_model:
|
|
95
97
|
args.llama_args += ["--image", str(Path(path).absolute())]
|
|
96
98
|
elif args.text:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
from concurrent.futures import ThreadPoolExecutor
|
|
3
|
-
from contextlib import suppress
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
from library import usage
|
|
@@ -45,13 +44,16 @@ def full_hash_compare(paths):
|
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
def sample_cmp(*paths, threads=1, gap=0.1, chunk_size=None, ignore_holes=False, skip_full_hash=False):
|
|
48
|
-
if len(paths) < 2:
|
|
49
|
-
raise ValueError("Not enough paths. Include 2 or more paths to compare")
|
|
50
|
-
|
|
51
47
|
path_stats = {}
|
|
48
|
+
existing_paths = []
|
|
52
49
|
for path in paths:
|
|
53
|
-
|
|
50
|
+
try:
|
|
54
51
|
path_stats[path] = Path(path).stat()
|
|
52
|
+
existing_paths.append(path)
|
|
53
|
+
except FileNotFoundError:
|
|
54
|
+
log.error("File not found %s", path)
|
|
55
|
+
if len(existing_paths) < 2:
|
|
56
|
+
raise ValueError("Not enough paths. Include 2 or more paths to compare")
|
|
55
57
|
|
|
56
58
|
sizes = [stat_res.st_size for stat_res in path_stats.values()]
|
|
57
59
|
if not all(size == sizes[0] for size in sizes):
|
|
@@ -71,7 +73,7 @@ def sample_cmp(*paths, threads=1, gap=0.1, chunk_size=None, ignore_holes=False,
|
|
|
71
73
|
with ThreadPoolExecutor(max_workers=4) as pool:
|
|
72
74
|
futures = {
|
|
73
75
|
path: pool.submit(sample_hash.sample_hash_file, path, threads=threads, gap=gap, chunk_size=chunk_size)
|
|
74
|
-
for path in
|
|
76
|
+
for path in existing_paths
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
paths_dict = {}
|
|
@@ -87,7 +89,7 @@ def sample_cmp(*paths, threads=1, gap=0.1, chunk_size=None, ignore_holes=False,
|
|
|
87
89
|
if is_equal:
|
|
88
90
|
if skip_full_hash:
|
|
89
91
|
log.info("Files might be equal:\n%s", paths_str)
|
|
90
|
-
elif full_hash_compare(
|
|
92
|
+
elif full_hash_compare(existing_paths):
|
|
91
93
|
log.info("Files are equal:\n%s", paths_str)
|
|
92
94
|
else:
|
|
93
95
|
log.info("Files are similar but NOT equal:\n%s", paths_str)
|
|
@@ -5,7 +5,7 @@ from collections.abc import Iterable
|
|
|
5
5
|
from library import usage
|
|
6
6
|
from library.folders import filter_src
|
|
7
7
|
from library.folders.filter_src import process_time_filters, track_moved
|
|
8
|
-
from library.utils import arggroups, argparse_utils, consts, devices, path_utils, shell_utils
|
|
8
|
+
from library.utils import arggroups, argparse_utils, consts, devices, path_utils, processes, shell_utils
|
|
9
9
|
from library.utils.consts import DBType
|
|
10
10
|
from library.utils.log_utils import log
|
|
11
11
|
from library.utils.shell_utils import rglob_gen
|
|
@@ -265,6 +265,9 @@ def gen_src_dest(args, sources: Iterable[str], destination: str, shortcut_allowe
|
|
|
265
265
|
|
|
266
266
|
|
|
267
267
|
def mmv_folders(args, mv_fn, sources, destination, shortcut_allowed=False):
|
|
268
|
+
if sources is None:
|
|
269
|
+
processes.exit_error("No paths passed in")
|
|
270
|
+
|
|
268
271
|
destination = os.path.realpath(destination) + (os.sep if destination.endswith(os.sep) else "")
|
|
269
272
|
|
|
270
273
|
sources = (
|
|
@@ -217,7 +217,12 @@ def get_rel_stats(parents, files) -> list[dict[str, float | str]]:
|
|
|
217
217
|
mount_space.append([parent, used])
|
|
218
218
|
|
|
219
219
|
return [
|
|
220
|
-
{
|
|
220
|
+
{
|
|
221
|
+
"mount": mount,
|
|
222
|
+
"used": used / total_used,
|
|
223
|
+
"free": (total_used - used) / total_used,
|
|
224
|
+
"total": used / total_used,
|
|
225
|
+
}
|
|
221
226
|
for mount, used in mount_space
|
|
222
227
|
]
|
|
223
228
|
|
|
@@ -227,7 +227,7 @@ def block(args=None) -> None:
|
|
|
227
227
|
msg += f" {key}: {preview}\n"
|
|
228
228
|
log.warning(msg)
|
|
229
229
|
else:
|
|
230
|
-
p
|
|
230
|
+
p.append(data[args.match_column])
|
|
231
231
|
if p[1]:
|
|
232
232
|
matching_media = list(
|
|
233
233
|
args.db.query(
|
|
@@ -237,7 +237,11 @@ def block(args=None) -> None:
|
|
|
237
237
|
)
|
|
238
238
|
|
|
239
239
|
if not matching_media:
|
|
240
|
-
|
|
240
|
+
if len(p) < 2:
|
|
241
|
+
unmatched_playlists.append(p)
|
|
242
|
+
continue
|
|
243
|
+
log.info("Blocking URL resolved by tube metadata: %s", p[1])
|
|
244
|
+
add_to_blocklist(args, p)
|
|
241
245
|
continue
|
|
242
246
|
|
|
243
247
|
if args.regex_sort:
|
|
@@ -29,7 +29,7 @@ def create(args):
|
|
|
29
29
|
);
|
|
30
30
|
"""
|
|
31
31
|
)
|
|
32
|
-
if args.action in ("tube-add", "gallery-add", "web-add", "site-add"):
|
|
32
|
+
if args.action in ("tube-add", "gallery-add", "web-add", "site-add", "computers-add"):
|
|
33
33
|
args.db.execute("DROP INDEX IF EXISTS idx_media_path;")
|
|
34
34
|
try:
|
|
35
35
|
args.db.execute("CREATE UNIQUE INDEX IF NOT EXISTS media_uniq_path_idx ON media (playlists_id, path);")
|
|
@@ -99,6 +99,8 @@ def _add(args, entry):
|
|
|
99
99
|
playlists_id = get_id(args, entry["path"])
|
|
100
100
|
if playlists_id:
|
|
101
101
|
entry["id"] = playlists_id
|
|
102
|
+
entry["time_modified"] = consts.now()
|
|
103
|
+
entry["time_deleted"] = 0
|
|
102
104
|
args.db["playlists"].upsert(entry, pk="id", alter=True)
|
|
103
105
|
else:
|
|
104
106
|
entry["time_created"] = consts.APPLICATION_START
|
|
@@ -123,8 +125,8 @@ def exists(args, playlist_path) -> bool:
|
|
|
123
125
|
def get_parentpath_playlists_id(args, playlist_path) -> int | None:
|
|
124
126
|
try:
|
|
125
127
|
known = args.db.pop(
|
|
126
|
-
"SELECT id FROM playlists WHERE ? LIKE path || '%' AND path != ?",
|
|
127
|
-
[str(playlist_path), str(playlist_path)],
|
|
128
|
+
"SELECT id FROM playlists WHERE ? LIKE path || ? || '%' AND path != ?",
|
|
129
|
+
[str(playlist_path), os.sep, str(playlist_path)],
|
|
128
130
|
)
|
|
129
131
|
except sqlite3.OperationalError as excinfo:
|
|
130
132
|
log.debug(excinfo)
|
|
@@ -174,17 +176,38 @@ def mark_media_deleted(args, playlist_paths) -> int:
|
|
|
174
176
|
return deleted_playlist_count
|
|
175
177
|
|
|
176
178
|
|
|
177
|
-
def delete_subpath_playlists(args, playlist_path) -> int | None:
|
|
179
|
+
def delete_subpath_playlists(args, playlist_path, replacement_playlist_id=None) -> int | None:
|
|
180
|
+
subpath_filter = """
|
|
181
|
+
COALESCE(time_deleted, 0)=0
|
|
182
|
+
AND path LIKE ?
|
|
183
|
+
AND path != ?
|
|
184
|
+
"""
|
|
185
|
+
subpath_params = [str(playlist_path) + os.sep + "%", str(playlist_path)]
|
|
186
|
+
|
|
187
|
+
if replacement_playlist_id:
|
|
188
|
+
try:
|
|
189
|
+
with args.db.conn:
|
|
190
|
+
args.db.conn.execute(
|
|
191
|
+
f"""
|
|
192
|
+
UPDATE media
|
|
193
|
+
SET playlists_id = ?
|
|
194
|
+
WHERE playlists_id IN (
|
|
195
|
+
SELECT id FROM playlists WHERE {subpath_filter}
|
|
196
|
+
)
|
|
197
|
+
""",
|
|
198
|
+
[replacement_playlist_id, *subpath_params],
|
|
199
|
+
)
|
|
200
|
+
except sqlite3.OperationalError:
|
|
201
|
+
pass
|
|
202
|
+
|
|
178
203
|
try:
|
|
179
204
|
with args.db.conn:
|
|
180
205
|
args.db.conn.execute(
|
|
181
|
-
"""
|
|
206
|
+
f"""
|
|
182
207
|
DELETE from playlists
|
|
183
|
-
WHERE
|
|
184
|
-
AND path LIKE ?
|
|
185
|
-
AND path != ?
|
|
208
|
+
WHERE {subpath_filter}
|
|
186
209
|
""",
|
|
187
|
-
|
|
210
|
+
subpath_params,
|
|
188
211
|
)
|
|
189
212
|
except sqlite3.OperationalError:
|
|
190
213
|
pass
|
|
@@ -196,15 +219,16 @@ def add(args, playlist_path: str, info: dict, check_subpath=False, extractor_key
|
|
|
196
219
|
parentpath_playlist_id = get_parentpath_playlists_id(args, playlist_path)
|
|
197
220
|
if parentpath_playlist_id:
|
|
198
221
|
return parentpath_playlist_id
|
|
199
|
-
else:
|
|
200
|
-
delete_subpath_playlists(args, playlist_path)
|
|
201
222
|
|
|
202
223
|
playlist = consolidate(args, objects.dumbcopy(info))
|
|
203
224
|
if playlist_path:
|
|
204
225
|
playlist = {**playlist, "path": playlist_path}
|
|
205
226
|
if extractor_key:
|
|
206
227
|
playlist["extractor_key"] = extractor_key
|
|
207
|
-
|
|
228
|
+
playlists_id = _add(args, objects.dict_filter_bool(playlist) or {})
|
|
229
|
+
if playlist_path and check_subpath:
|
|
230
|
+
delete_subpath_playlists(args, playlist_path, replacement_playlist_id=playlists_id)
|
|
231
|
+
return playlists_id
|
|
208
232
|
|
|
209
233
|
|
|
210
234
|
def media_exists(args, path, playlist_path) -> bool:
|
|
@@ -119,13 +119,15 @@ def download(args=None) -> None:
|
|
|
119
119
|
return
|
|
120
120
|
|
|
121
121
|
get_inner_urls = iterables.return_unique(extract_links.get_inner_urls, lambda d: d["link"])
|
|
122
|
+
if args.safe and args.profile == DBType.image:
|
|
123
|
+
gallery_backend.load_module_level_gallery_dl(args)
|
|
122
124
|
for m in media:
|
|
123
125
|
if args.blocklist_rules and sql_utils.is_blocked_dict_like_sql(m, args.blocklist_rules):
|
|
124
126
|
continue
|
|
125
127
|
|
|
126
128
|
if args.safe:
|
|
127
129
|
if (args.profile in (DBType.audio, DBType.video) and not tube_backend.is_supported(m["path"])) or (
|
|
128
|
-
args.profile in (DBType.image,) and not gallery_backend.is_supported(
|
|
130
|
+
args.profile in (DBType.image,) and not gallery_backend.is_supported(m["path"])
|
|
129
131
|
):
|
|
130
132
|
log.info("Skipping unsupported URL (safe_mode) %s", m["path"])
|
|
131
133
|
continue
|
|
@@ -3,20 +3,31 @@ import argparse
|
|
|
3
3
|
from library import usage
|
|
4
4
|
from library.mediadb import db_history
|
|
5
5
|
from library.playback import media_printer
|
|
6
|
-
from library.utils import arggroups, argparse_utils, sqlgroups
|
|
6
|
+
from library.utils import arggroups, argparse_utils, sqlgroups, strings
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def parse_args() -> argparse.Namespace:
|
|
10
10
|
parser = argparse_utils.ArgumentParser(usage=usage.history)
|
|
11
11
|
arggroups.sql_fs(parser)
|
|
12
|
+
arggroups.frequency(parser)
|
|
12
13
|
arggroups.history(parser)
|
|
13
14
|
arggroups.debug(parser)
|
|
14
15
|
|
|
15
16
|
arggroups.database(parser)
|
|
17
|
+
arggroups.paths_or_stdin(parser, required=False)
|
|
16
18
|
args = parser.parse_intermixed_args()
|
|
17
19
|
arggroups.args_post(args, parser)
|
|
20
|
+
arggroups.frequency_post(args)
|
|
18
21
|
|
|
22
|
+
args.paths = [strings.strip_enclosing_quotes(path) for path in args.paths or []]
|
|
19
23
|
arggroups.sql_fs_post(args)
|
|
24
|
+
if args.paths:
|
|
25
|
+
path_bindings = []
|
|
26
|
+
for index, path in enumerate(args.paths):
|
|
27
|
+
binding = f"history_path_{index}"
|
|
28
|
+
path_bindings.append(f":{binding}")
|
|
29
|
+
args.filter_bindings[binding] = path
|
|
30
|
+
args.where.append(f"path IN ({', '.join(path_bindings)})")
|
|
20
31
|
|
|
21
32
|
return args
|
|
22
33
|
|
|
@@ -223,7 +223,7 @@ def media_check() -> None:
|
|
|
223
223
|
threshold_str = (
|
|
224
224
|
strings.percent(args.delete_corrupt)
|
|
225
225
|
if 0 < args.delete_corrupt < 1
|
|
226
|
-
else
|
|
226
|
+
else f"{args.delete_corrupt}s"
|
|
227
227
|
)
|
|
228
228
|
log.warning(
|
|
229
229
|
"Deleting %s corruption %.1f%% exceeded threshold %s", path, corruption * 100, threshold_str
|
|
@@ -137,7 +137,7 @@ def process_path(args, path, include_timecode=False, **kwargs) -> str | None:
|
|
|
137
137
|
if not args.video_only and path_utils.ext(path) not in consts.IMAGE_ANIMATION_EXTENSIONS:
|
|
138
138
|
log.warning("No audio stream found: %s", path)
|
|
139
139
|
|
|
140
|
-
if video_stream and (video_stream.get("codec_name") or "") == "av1":
|
|
140
|
+
if video_stream and not args.audio_only and (video_stream.get("codec_name") or "") == "av1":
|
|
141
141
|
log.info("Video is already AV1: %s", path)
|
|
142
142
|
return str(path)
|
|
143
143
|
elif (not video_stream or args.audio_only) and audio_stream and (audio_stream.get("codec_name") or "") == "opus":
|
|
@@ -104,7 +104,7 @@ def process_path(args, path) -> str | None:
|
|
|
104
104
|
return None
|
|
105
105
|
|
|
106
106
|
if not output_path.exists():
|
|
107
|
-
return str(path) if path.exists else None
|
|
107
|
+
return str(path) if path.exists() else None
|
|
108
108
|
|
|
109
109
|
output_stats = output_path.stat()
|
|
110
110
|
if output_stats.st_size == 0 or (args.delete_larger and output_stats.st_size > original_stats.st_size):
|
|
@@ -145,11 +145,10 @@ def unardel() -> None:
|
|
|
145
145
|
if not os.path.exists(m["path"]):
|
|
146
146
|
log.error("[%s]: FileNotFoundError", m["path"])
|
|
147
147
|
m["time_deleted"] = consts.APPLICATION_START
|
|
148
|
-
continue
|
|
149
148
|
|
|
150
|
-
if args.move and not m.get("time_deleted")
|
|
151
|
-
dest = path_utils.relative_from_mountpoint(m["
|
|
152
|
-
shell_utils.rename_move_file(m["
|
|
153
|
-
elif args.move_broken and
|
|
149
|
+
if args.move and not m.get("time_deleted"):
|
|
150
|
+
dest = path_utils.relative_from_mountpoint(m["path"], args.move)
|
|
151
|
+
shell_utils.rename_move_file(m["path"], dest)
|
|
152
|
+
elif args.move_broken and m.get("time_deleted") and os.path.exists(m["path"]):
|
|
154
153
|
dest = path_utils.relative_from_mountpoint(m["path"], args.move_broken)
|
|
155
154
|
shell_utils.rename_move_file(m["path"], dest)
|
|
@@ -709,7 +709,7 @@ def play_list(args, media):
|
|
|
709
709
|
playlist = MediaPrefetcher(args, media)
|
|
710
710
|
playlist.fetch()
|
|
711
711
|
|
|
712
|
-
if args.multiple_playback
|
|
712
|
+
if args.multiple_playback and args.multiple_playback != 1:
|
|
713
713
|
multiple_player(args, playlist)
|
|
714
714
|
else:
|
|
715
715
|
while playlist.remaining:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import csv, json, os,
|
|
1
|
+
import csv, json, os, statistics, sys
|
|
2
2
|
from copy import deepcopy
|
|
3
3
|
from io import StringIO
|
|
4
4
|
from numbers import Number
|
|
@@ -77,12 +77,12 @@ def moved_media(args, moved_files: str | list, base_from, base_to) -> int:
|
|
|
77
77
|
for chunk_paths in df_chunked:
|
|
78
78
|
with args.db.conn:
|
|
79
79
|
cursor = args.db.conn.execute(
|
|
80
|
-
|
|
81
|
-
SET path=REPLACE(path,
|
|
80
|
+
"""UPDATE media
|
|
81
|
+
SET path=REPLACE(path, ?, ?)
|
|
82
82
|
where path in ("""
|
|
83
83
|
+ ",".join(["?"] * len(chunk_paths))
|
|
84
84
|
+ ")",
|
|
85
|
-
(*chunk_paths
|
|
85
|
+
(base_from, base_to, *chunk_paths),
|
|
86
86
|
)
|
|
87
87
|
modified_row_count += cursor.rowcount
|
|
88
88
|
|