listpick 0.1.15.20__tar.gz → 0.1.16.1__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.

Potentially problematic release.


This version of listpick might be problematic. Click here for more details.

Files changed (65) hide show
  1. {listpick-0.1.15.20 → listpick-0.1.16.1}/CHANGELOG.md +19 -0
  2. {listpick-0.1.15.20/src/listpick.egg-info → listpick-0.1.16.1}/PKG-INFO +5 -5
  3. {listpick-0.1.15.20 → listpick-0.1.16.1}/README.md +4 -4
  4. {listpick-0.1.15.20 → listpick-0.1.16.1}/TODO.md +7 -0
  5. listpick-0.1.16.1/examples/data_generation/video_duplicates.toml +50 -0
  6. {listpick-0.1.15.20 → listpick-0.1.16.1}/setup.py +1 -1
  7. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/listpick_app.py +78 -27
  8. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/pane/get_data.py +20 -0
  9. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/pane/pane_functions.py +45 -5
  10. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/pane/pane_utils.py +1 -1
  11. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/generate_data.py +37 -1
  12. listpick-0.1.16.1/src/listpick/utils/generate_data_multithreaded.py +202 -0
  13. {listpick-0.1.15.20 → listpick-0.1.16.1/src/listpick.egg-info}/PKG-INFO +5 -5
  14. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick.egg-info/SOURCES.txt +1 -0
  15. listpick-0.1.15.20/examples/data_generation/video_duplicates.toml +0 -36
  16. {listpick-0.1.15.20 → listpick-0.1.16.1}/.gitignore +0 -0
  17. {listpick-0.1.15.20 → listpick-0.1.16.1}/LICENSE.txt +0 -0
  18. {listpick-0.1.15.20 → listpick-0.1.16.1}/assets/aria2tui_screenshot.png +0 -0
  19. {listpick-0.1.15.20 → listpick-0.1.16.1}/assets/file_compare.png +0 -0
  20. {listpick-0.1.15.20 → listpick-0.1.16.1}/assets/lpfman.png +0 -0
  21. {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/data_generation/list_files.toml +0 -0
  22. {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/data_generation/list_files_empty.toml +0 -0
  23. {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/data_generation/video_mediainfo.toml +0 -0
  24. {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/input_files/polynomials.tsv +0 -0
  25. {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/input_guides/gnuplot_graph.md +0 -0
  26. {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/picker/auxiallary_files/2024-25_Premier_League.pkl +0 -0
  27. {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/picker/footer_string_example.py +0 -0
  28. {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/picker/picker_example.py +0 -0
  29. {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/picker/template.py +0 -0
  30. {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/picker/wikipedia_table.py +0 -0
  31. {listpick-0.1.15.20 → listpick-0.1.16.1}/listpick.py +0 -0
  32. {listpick-0.1.15.20 → listpick-0.1.16.1}/requirements.txt +0 -0
  33. {listpick-0.1.15.20 → listpick-0.1.16.1}/setup.cfg +0 -0
  34. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/__init__.py +0 -0
  35. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/__main__.py +0 -0
  36. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/pane/__init__.py +0 -0
  37. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/__init__.py +0 -0
  38. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/build_help.py +0 -0
  39. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/footer.py +0 -0
  40. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/help_screen.py +0 -0
  41. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/input_field.py +0 -0
  42. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/keys.py +0 -0
  43. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/picker_colours.py +0 -0
  44. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/__init__.py +0 -0
  45. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/clipboard_operations.py +0 -0
  46. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/config.py +0 -0
  47. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/dump.py +0 -0
  48. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/filtering.py +0 -0
  49. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/graphing.py +0 -0
  50. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/keycodes.py +0 -0
  51. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/options_selectors.py +0 -0
  52. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/paste_operations.py +0 -0
  53. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/picker_log.py +0 -0
  54. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/search_and_filter_utils.py +0 -0
  55. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/searching.py +0 -0
  56. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/sorting.py +0 -0
  57. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/table_to_list_of_lists.py +0 -0
  58. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/user_input.py +0 -0
  59. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/utils.py +0 -0
  60. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick.egg-info/dependency_links.txt +0 -0
  61. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick.egg-info/entry_points.txt +0 -0
  62. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick.egg-info/requires.txt +0 -0
  63. {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick.egg-info/top_level.txt +0 -0
  64. {listpick-0.1.15.20 → listpick-0.1.16.1}/tests/kitty_control.sh +0 -0
  65. {listpick-0.1.15.20 → listpick-0.1.16.1}/tests/sorting_dates.csv +0 -0
@@ -1,5 +1,24 @@
1
1
  # CHANGELOG.md
2
2
  Note that the changes between 0.1.11.0 and 1.1.12.0 are listed under 0.1.11
3
+
4
+ ## [0.1.16.1] 2025-09-07
5
+ - Massive improvements to Picker data generation.
6
+ - Data generation is now multithreaded.
7
+ - ~5x quicker when getting information on video files
8
+ - Data generation is now done **asynchronously**.
9
+ - generate_picker_data sets the header and items of the Picker with only the Files column filled. The rest of the cells have "..." as a placeholder. Multiple threads are created and they generate data for each of the cells.
10
+ - The Picker can receive user interaction while the data loads.
11
+ - Implemented a task priority queue which the threads utilise to determine their next task.
12
+ - Cells that are in view are prioritised in the queue and are generated first.
13
+ - Created generate_picker_data_from_file() function and separated the generation-specific code into the original generate_picker_data() function.
14
+ - This allows one to utilise the new generation capabilities by simply passing a list of functions and filenames to the generate_picker_data() function--no toml file necessary.
15
+ - This has been implemented in lpfman to display file attributes.
16
+ - Bug fixes:
17
+ - Static footer string was not displayed in some cases.
18
+
19
+ ## [0.1.16.1] 2025-09-05
20
+ - Data generation is now multithreaded.
21
+
3
22
  ## [0.1.15.20] 2025-08-31
4
23
  - Fixed screen refresh function for default options selector.
5
24
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: listpick
3
- Version: 0.1.15.20
3
+ Version: 0.1.16.1
4
4
  Summary: Listpick is a powerful TUI data tool for creating TUI apps or viewing/comparing tabulated data.
5
5
  Home-page: https://github.com/grimandgreedy/listpick
6
6
  Author: Grim
@@ -45,7 +45,7 @@ Dynamic: summary
45
45
 
46
46
  listpick is a TUI tool which displays a tabulated list of rows and allows the user to operate upon these rows--select, copy, pipe. A very simple concept but also, I hope, a powerful tool that will make it easier for people to develop TUI apps.
47
47
 
48
- Rows of data can be viewed, selected, generated, saved, loaded, refreshed, modified or copied to the clipboard. Easy to integrate into your project by creating a `menu = Picker(stdscr, items:list[list[str]])` and then the menu will be displayed by running `menu.run()`.
48
+ Rows of data can be viewed, selected, generated, saved, loaded, refreshed, modified or copied to the clipboard. Easy to integrate into your project by creating a `menu = Picker(stdscr: curses.window, items: list[list[str]])` and then the menu will be displayed by running `menu.run()`.
49
49
 
50
50
  It works great as the backend for a TUI application and can also be used as a standalone data viewer.
51
51
 
@@ -53,13 +53,13 @@ It works great as the backend for a TUI application and can also be used as a st
53
53
 
54
54
  # Quickstart
55
55
 
56
- Install listpick
56
+ Install listpick:
57
57
 
58
58
  ```
59
59
  python -m pip installl "listpick[full]"
60
60
  ```
61
61
 
62
- Create a picker object to display rows:
62
+ Create a Picker:
63
63
 
64
64
  ```
65
65
  from listpick.listpick_app import Picker, start_curses, close_curses
@@ -79,7 +79,7 @@ x.run()
79
79
  close_curses(stdscr)
80
80
 
81
81
  ```
82
- Use the listpick binary to generate and display rows based on a list of commands:
82
+ Or use the listpick binary to generate and display rows based on a list of commands:
83
83
 
84
84
  ```
85
85
  wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/data_generation/list_files.toml
@@ -2,7 +2,7 @@
2
2
 
3
3
  listpick is a TUI tool which displays a tabulated list of rows and allows the user to operate upon these rows--select, copy, pipe. A very simple concept but also, I hope, a powerful tool that will make it easier for people to develop TUI apps.
4
4
 
5
- Rows of data can be viewed, selected, generated, saved, loaded, refreshed, modified or copied to the clipboard. Easy to integrate into your project by creating a `menu = Picker(stdscr, items:list[list[str]])` and then the menu will be displayed by running `menu.run()`.
5
+ Rows of data can be viewed, selected, generated, saved, loaded, refreshed, modified or copied to the clipboard. Easy to integrate into your project by creating a `menu = Picker(stdscr: curses.window, items: list[list[str]])` and then the menu will be displayed by running `menu.run()`.
6
6
 
7
7
  It works great as the backend for a TUI application and can also be used as a standalone data viewer.
8
8
 
@@ -10,13 +10,13 @@ It works great as the backend for a TUI application and can also be used as a st
10
10
 
11
11
  # Quickstart
12
12
 
13
- Install listpick
13
+ Install listpick:
14
14
 
15
15
  ```
16
16
  python -m pip installl "listpick[full]"
17
17
  ```
18
18
 
19
- Create a picker object to display rows:
19
+ Create a Picker:
20
20
 
21
21
  ```
22
22
  from listpick.listpick_app import Picker, start_curses, close_curses
@@ -36,7 +36,7 @@ x.run()
36
36
  close_curses(stdscr)
37
37
 
38
38
  ```
39
- Use the listpick binary to generate and display rows based on a list of commands:
39
+ Or use the listpick binary to generate and display rows based on a list of commands:
40
40
 
41
41
  ```
42
42
  wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/data_generation/list_files.toml
@@ -28,6 +28,12 @@
28
28
  > - [x] Selected column isn't highlighted properly when cell beginning is offscreen
29
29
  > - [x] Header rows not aligned properly sometimes...
30
30
  > - [ ] Check leftmost_char after resizing
31
+ > - [ ] Kill threads when we exit the picker.
32
+ > - [ ] Data locks when data is still being generated. No modifications.
33
+ > - [ ] (!!!) Cell cursor value with hidden columns
34
+ > - [ ] The selected column can be a hidden column
35
+ > - [ ] Hide all columns and toggle cell cursor mode. >> Hangs
36
+ > - [ ] Toggle last visible column in cell cursor mode. >> Hangs
31
37
 
32
38
 
33
39
 
@@ -388,6 +394,7 @@
388
394
  > - [x] Update footer height when there is a search query.
389
395
  > - [x] Header columns are not aligned with long header values.
390
396
  > - [x] Done: 2025-08-27
397
+ > - [ ] Pressing page forward/page back in an empty picker changes the index in the footer from 0 to 1...
391
398
 
392
399
 
393
400
 
@@ -0,0 +1,50 @@
1
+ #
2
+ # video_duplicates.toml
3
+ # Generate list of mp4 files along with their: sha1, duration, width, height, and size.
4
+ #
5
+ # Author: GrimAndGreedy
6
+ # License: MIT
7
+
8
+
9
+ [environment]
10
+ cwd="~/Videos/"
11
+
12
+ [data]
13
+ # files_command = "eza -1 --no-quotes | grep -E mp4$"
14
+
15
+ commands = [
16
+ # """find . -iname '*.m4v' -or -iname '*.webm'""",
17
+ """eza -1 --no-quotes | grep -E mp4$""",
18
+ """du -hs {} | awk '{{ print $1 }}'""",
19
+
20
+ # """sha1sum {} | awk '{{print $1}}'""",
21
+
22
+ ## Duration, width, height methods
23
+ """mediainfo --Inform="Video;%Duration/String%" {}""",
24
+ """mediainfo --Inform="Video;%Width%" {}""",
25
+ """mediainfo --Inform="Video;%Height%" {}""",
26
+ """mediainfo --Inform="Video;%BitRate/String%" {}""",
27
+ """mediainfo --Inform="Video;%CodecID%" {}""",
28
+ """mediainfo --Inform="Video;%BitDepth%" {}""",
29
+
30
+ ## Alternate duration, width, height methods
31
+ # """ffprobe -show_entries format=duration -v quiet -of csv="p=0" -i {}""",
32
+ # """ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 {}""",
33
+ # """ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 {}""",
34
+ ]
35
+
36
+ header = [
37
+ "File",
38
+ "Size",
39
+ # "sha1",
40
+ "Duration",
41
+ "Width",
42
+ "Height",
43
+ "Bit Rate",
44
+ "Codec",
45
+ "Bit Depth",
46
+ ]
47
+
48
+ # run = [
49
+ # "mpv",
50
+ # ]
@@ -16,7 +16,7 @@ with open("README.md", "r", encoding = "utf-8") as fh:
16
16
 
17
17
  setuptools.setup(
18
18
  name = "listpick",
19
- version = "0.1.15.20",
19
+ version = "0.1.16.1",
20
20
  author = "Grim",
21
21
  author_email = "grimandgreedy@protonmail.com",
22
22
  description = "Listpick is a powerful TUI data tool for creating TUI apps or viewing/comparing tabulated data.",
@@ -36,13 +36,14 @@ from listpick.utils.paste_operations import *
36
36
  from listpick.utils.searching import search
37
37
  from listpick.ui.help_screen import help_lines
38
38
  from listpick.ui.keys import picker_keys, notification_keys, options_keys, help_keys
39
- from listpick.utils.generate_data import generate_picker_data
39
+ # from listpick.utils.generate_data import generate_picker_data
40
+ from listpick.utils.generate_data_multithreaded import generate_picker_data_from_file
40
41
  from listpick.utils.dump import dump_state, load_state, dump_data
41
42
  from listpick.ui.build_help import build_help_rows
42
43
  from listpick.ui.footer import StandardFooter, CompactFooter, NoFooter
43
44
  from listpick.utils.picker_log import setup_logger
44
45
  from listpick.utils.user_input import get_char, open_tty
45
- from listpick.pane.pane_functions import right_split_file_attributes, right_split_graph, right_split_display_list
46
+ from listpick.pane.pane_functions import right_split_file_attributes, right_split_file_attributes_dynamic, right_split_graph, right_split_display_list
46
47
  from listpick.pane.get_data import *
47
48
 
48
49
 
@@ -77,7 +78,7 @@ class Picker:
77
78
  timer: float = 5,
78
79
 
79
80
  get_new_data: bool =False, # Whether we can get new data
80
- refresh_function: Optional[Callable] = lambda: [], # The function with which we get new data
81
+ refresh_function: Optional[Callable] = lambda items, header, visible_rows_indices, getting_data: None, # The function with which we get new data
81
82
  get_data_startup: bool =False, # Whether we should get data at statrup
82
83
  track_entries_upon_refresh: bool = True,
83
84
  pin_cursor: bool = False,
@@ -127,17 +128,18 @@ class Picker:
127
128
  columns_sort_method: list[int] = [0],
128
129
  key_chain: str = "",
129
130
  last_key: Optional[str] = None,
131
+ disabled_keys: list=[],
130
132
 
131
133
  paginate: bool =False,
132
134
  cancel_is_back: bool = False,
133
135
  mode_index: int = 0,
134
136
  modes: list[dict] = [],
135
- display_modes: bool =False,
136
- require_option: list=[],
137
- require_option_default: list=[],
137
+ display_modes: bool = False,
138
+ require_option: list[bool] = [],
139
+ require_option_default: bool = False,
138
140
  option_functions: list[Callable[..., Tuple[bool, str]]] = [],
139
141
  default_option_function: Callable[..., Tuple[bool, str]] = default_option_input,
140
- disabled_keys: list=[],
142
+
141
143
 
142
144
  show_header: bool = True,
143
145
  show_row_header: bool = False,
@@ -145,7 +147,7 @@ class Picker:
145
147
  footer_style: int = 0,
146
148
  footer_string: str="",
147
149
  footer_string_auto_refresh: bool=False,
148
- footer_string_refresh_function: Optional[Callable] = None,
150
+ footer_string_refresh_function: Optional[Callable] = lambda : None,
149
151
  footer_timer: float=1,
150
152
  get_footer_string_startup=False,
151
153
  unicode_char_width: bool = True,
@@ -195,6 +197,9 @@ class Picker:
195
197
  split_right: bool = False,
196
198
  right_panes: list = [],
197
199
  right_pane_index: int = 0,
200
+
201
+ # getting_data: threading.Event = threading.Event(),
202
+
198
203
  ):
199
204
  self.stdscr = stdscr
200
205
  self.items = items
@@ -280,7 +285,7 @@ class Picker:
280
285
  self.footer_string_auto_refresh = footer_string_auto_refresh
281
286
  self.footer_string_refresh_function = footer_string_refresh_function
282
287
  self.footer_timer = footer_timer
283
- self.get_footer_string_startup = get_footer_string_startup,
288
+ self.get_footer_string_startup = get_footer_string_startup
284
289
  self.unicode_char_width = unicode_char_width
285
290
 
286
291
 
@@ -312,7 +317,6 @@ class Picker:
312
317
 
313
318
 
314
319
  # Refresh function variables
315
- self.data_refreshed = False
316
320
  self.refreshing_data = False
317
321
  self.data_lock = threading.Lock()
318
322
  self.data_ready = False
@@ -347,12 +351,22 @@ class Picker:
347
351
  self.split_right = split_right
348
352
  self.right_panes = right_panes
349
353
  self.right_pane_index = right_pane_index
354
+ self.visible_rows_indices = []
355
+
356
+
357
+
358
+
350
359
  self.initialise_picker_state(reset_colours=self.reset_colours)
351
360
 
352
361
  # Note: We have to set the footer after initialising the picker state so that the footer can use the get_function_data method
353
362
  self.footer_options = [StandardFooter(self.stdscr, colours_start, self.get_function_data), CompactFooter(self.stdscr, colours_start, self.get_function_data), NoFooter(self.stdscr, colours_start, self.get_function_data)]
354
363
  self.footer = self.footer_options[self.footer_style]
355
- self.__version__ = "1.0"
364
+
365
+
366
+
367
+ # getting_data.is_set() is True when we are getting data
368
+ self.getting_data = threading.Event()
369
+ self.getting_data.set()
356
370
 
357
371
  def __sizeof__(self):
358
372
 
@@ -461,8 +475,11 @@ class Picker:
461
475
  end_index = min(start_index + self.items_per_page, len(self.indexed_items))
462
476
  if len(self.indexed_items) == 0: start_index, end_index = 0, 0
463
477
 
464
- rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
465
- return rows
478
+ self.visible_rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
479
+ # self.visible_rows_indices = [v[0] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else []
480
+ self.visible_rows_indices.clear()
481
+ self.visible_rows_indices.extend([v[0] for v in self.indexed_items[start_index:end_index]])
482
+ return self.visible_rows
466
483
 
467
484
  def initialise_picker_state(self, reset_colours=False) -> None:
468
485
  """ Initialise state variables for the picker. These are: debugging and colours. """
@@ -556,7 +573,10 @@ class Picker:
556
573
  self.cursor_pos_id = self.indexed_items[self.cursor_pos][1][self.id_column]
557
574
  self.cursor_pos_prev = self.cursor_pos
558
575
 
559
- self.items, self.header = self.refresh_function()
576
+
577
+
578
+ self.getting_data.clear()
579
+ self.refresh_function(self.items, self.header, self.visible_rows_indices, self.getting_data)
560
580
 
561
581
  self.items = pad_lists_to_same_length(self.items)
562
582
 
@@ -564,7 +584,7 @@ class Picker:
564
584
  ## Ensure that items is a List[List[Str]] object
565
585
  if len(self.items) > 0 and not isinstance(self.items[0], list):
566
586
  self.items = [[item] for item in self.items]
567
- self.items = [[str(cell) for cell in row] for row in self.items]
587
+ # self.items = [[str(cell) for cell in row] for row in self.items]
568
588
 
569
589
 
570
590
  # Ensure that header is of the same length as the rows
@@ -818,8 +838,9 @@ class Picker:
818
838
  # Determine widths based only on the currently indexed rows
819
839
  # rows = [v[1] for v in self.indexed_items] if len(self.indexed_items) else self.items
820
840
  # Determine widths based only on the currently displayed indexed rows
821
- rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
822
- self.column_widths = get_column_widths(rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=self.rows_w, unicode_char_width=self.unicode_char_width)
841
+ # rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
842
+ self.get_visible_rows()
843
+ self.column_widths = get_column_widths(self.visible_rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=self.rows_w, unicode_char_width=self.unicode_char_width)
823
844
  visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
824
845
  visible_columns_total_width = sum(visible_column_widths) + len(self.separator)*(len(visible_column_widths)-1)
825
846
 
@@ -1197,6 +1218,11 @@ class Picker:
1197
1218
  else:
1198
1219
  self.stdscr.addstr(0,self.term_w-3,"  ", curses.color_pair(self.colours_start+23) | curses.A_BOLD)
1199
1220
 
1221
+ # Display data fetch symbol
1222
+ if not self.getting_data.is_set():
1223
+ self.stdscr.addstr(0,self.term_w-3,"  ", curses.color_pair(self.colours_start+21) | curses.A_BOLD)
1224
+ # self.stdscr.addstr(0,self.term_w-6,"⏳", curses.color_pair(self.colours_start+21) | curses.A_BOLD)
1225
+
1200
1226
  ## Display footer
1201
1227
  if self.show_footer:
1202
1228
  # self.footer = NoFooter(self.stdscr, self.colours_start, self.get_function_data)
@@ -2121,7 +2147,7 @@ class Picker:
2121
2147
 
2122
2148
  self.stdscr = tmp
2123
2149
 
2124
- self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
2150
+ # self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
2125
2151
 
2126
2152
  self.set_function_data({}, reset_absent_variables=True)
2127
2153
  self.load_file(self.loaded_file)
@@ -2144,7 +2170,9 @@ class Picker:
2144
2170
  def fetch_data(self) -> None:
2145
2171
  """ Refesh data asynchronously. When data has been fetched self.data_ready is set to True. """
2146
2172
  self.logger.info(f"function: fetch_data()")
2147
- tmp_items, tmp_header = self.refresh_function()
2173
+ tmp_items, tmp_header = [], []
2174
+ self.getting_data.clear()
2175
+ self.refresh_function(tmp_items, tmp_header, self.visible_rows_indices, self.getting_data)
2148
2176
  if self.track_entries_upon_refresh:
2149
2177
  selected_indices = get_selected_indices(self.selections)
2150
2178
  self.ids = [item[self.id_column] for i, item in enumerate(self.items) if i in selected_indices]
@@ -2422,6 +2450,10 @@ class Picker:
2422
2450
  else: return False
2423
2451
 
2424
2452
  COLS, LINES = os.get_terminal_size()
2453
+
2454
+
2455
+ getting_data_prev = False
2456
+
2425
2457
  # Main loop
2426
2458
  while True:
2427
2459
  # key = self.stdscr.getch()
@@ -2430,6 +2462,16 @@ class Picker:
2430
2462
  if key != -1:
2431
2463
  self.logger.info(f"key={key}")
2432
2464
 
2465
+ # Ensure that
2466
+
2467
+ if not self.getting_data.is_set():
2468
+ self.initialise_variables()
2469
+ getting_data_prev = True
2470
+ elif getting_data_prev:
2471
+ ## Ensure that we reinitialise one final time after all data is retrieved.
2472
+ self.initialise_variables()
2473
+ getting_data_prev = False
2474
+
2433
2475
  self.term_resize_event = terminal_resized(COLS, LINES)
2434
2476
  COLS, LINES = os.get_terminal_size()
2435
2477
  if self.term_resize_event:
@@ -2948,8 +2990,8 @@ class Picker:
2948
2990
 
2949
2991
 
2950
2992
  ## Scroll with column select
2951
- rows = self.get_visible_rows()
2952
- self.column_widths = get_column_widths(rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=self.rows_w, unicode_char_width=self.unicode_char_width)
2993
+ self.get_visible_rows()
2994
+ self.column_widths = get_column_widths(self.visible_rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=self.rows_w, unicode_char_width=self.unicode_char_width)
2953
2995
  visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
2954
2996
  column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
2955
2997
  start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
@@ -2975,8 +3017,8 @@ class Picker:
2975
3017
  # curses.flash()
2976
3018
 
2977
3019
  ## Scroll with column select
2978
- rows = self.get_visible_rows()
2979
- self.column_widths = get_column_widths(rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=self.rows_w, unicode_char_width=self.unicode_char_width)
3020
+ self.get_visible_rows()
3021
+ self.column_widths = get_column_widths(self.visible_rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=self.rows_w, unicode_char_width=self.unicode_char_width)
2980
3022
  visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
2981
3023
  column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
2982
3024
  start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
@@ -3706,7 +3748,7 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
3706
3748
  # input_arg = args.filename
3707
3749
 
3708
3750
  elif args.generate:
3709
- function_data["refresh_function"] = lambda : generate_picker_data(args.generate)
3751
+ function_data["refresh_function"] = lambda items, header, visible_rows_indices, getting_data: generate_picker_data_from_file(args.generate, items, header, visible_rows_indices, getting_data)
3710
3752
  function_data["get_data_startup"] = True
3711
3753
  function_data["get_new_data"] = True
3712
3754
  return args, function_data
@@ -3975,7 +4017,7 @@ def main() -> None:
3975
4017
  # function_data["debug_level"] = 1
3976
4018
 
3977
4019
  function_data["split_right"] = False
3978
- function_data["right_pane_index"] = 3
4020
+ function_data["right_pane_index"] = 2
3979
4021
 
3980
4022
  function_data["right_panes"] = [
3981
4023
  # Graph or random numbers generated each second
@@ -3996,15 +4038,24 @@ def main() -> None:
3996
4038
  "data": ["Files", [str(x) for x in range(100)]],
3997
4039
  "refresh_time": 1.0,
3998
4040
  },
3999
- # File attribures
4041
+ # File attributes
4000
4042
  {
4001
4043
  "proportion": 2/3,
4002
4044
  "auto_refresh": False,
4003
4045
  "get_data": lambda data, state: [],
4004
4046
  "display": right_split_file_attributes,
4005
- "data": ["Files", [str(x) for x in range(100)]],
4047
+ "data": [],
4006
4048
  "refresh_time": 1.0,
4007
4049
  },
4050
+ # File attributes dynamic
4051
+ {
4052
+ "proportion": 2/3,
4053
+ "auto_refresh": True,
4054
+ "get_data": update_file_attributes,
4055
+ "display": right_split_file_attributes_dynamic,
4056
+ "data": [],
4057
+ "refresh_time": 2.0,
4058
+ },
4008
4059
  # List of random numbers generated each second
4009
4060
  {
4010
4061
  "proportion": 1/2,
@@ -8,6 +8,8 @@ Author: GrimAndGreedy
8
8
  License: MIT
9
9
  """
10
10
 
11
+ from listpick.pane.pane_utils import get_file_attributes
12
+
11
13
  def data_refresh_randint_by_row(data, state):
12
14
  """
13
15
  Add a random number to the data if row id is the same.
@@ -93,3 +95,21 @@ def get_dl(data, state):
93
95
  data[0].append(data[0][-1]+1)
94
96
  data[1].append(dl)
95
97
  return data
98
+
99
+
100
+ def update_file_attributes(data, state):
101
+ """
102
+ Get file attributes
103
+
104
+ data[0]: ["size: {}", filetype: {}, last modified: {}]
105
+ ]
106
+ data[1]: id
107
+ """
108
+ if state["indexed_items"]:
109
+ # id = state["indexed_items"][state["cursor_pos"]][1][state["id_column"]]
110
+ id = state["indexed_items"][state["cursor_pos"]][1][0]
111
+ else:
112
+ return [[], -1]
113
+ return [get_file_attributes(id), id]
114
+
115
+
@@ -11,15 +11,14 @@ License: MIT
11
11
  import curses
12
12
  import os
13
13
  from listpick.pane.pane_utils import get_file_attributes, get_graph_string, escape_ansi
14
+ from listpick.pane.get_data import update_file_attributes
14
15
 
15
- def right_split_file_attributes(stdscr, x, y, w, h, state, row, cell, past_data: list = [], data: list = [], test: bool = False):
16
+ def right_split_file_attributes(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
16
17
  """
17
18
  Display file attributes in right pane.
18
19
  """
19
20
  if test: return True
20
21
 
21
- os.chdir(os.path.expanduser("~/Downloads/new"))
22
-
23
22
  # Title
24
23
  title = "File attributes"
25
24
  if len(title) < w: title = f"{title:^{w}}"
@@ -45,7 +44,48 @@ def right_split_file_attributes(stdscr, x, y, w, h, state, row, cell, past_data:
45
44
 
46
45
  return []
47
46
 
48
- def right_split_graph(stdscr, x, y, w, h, state, row, cell, past_data: list = [], data: list = [], test: bool = False):
47
+
48
+ def right_split_file_attributes_dynamic(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
49
+ """
50
+ Display file attributes in right pane.
51
+ """
52
+ if test: return True
53
+
54
+ # Title
55
+ title = "File attributes"
56
+ if len(title) < w: title = f"{title:^{w}}"
57
+ stdscr.addstr(y, x,title[:w], curses.color_pair(state["colours_start"]+4) | curses.A_BOLD)
58
+
59
+ # Separator
60
+ for j in range(h):
61
+ stdscr.addstr(j+y, x, ' ', curses.color_pair(state["colours_start"]+16))
62
+
63
+ # Display pane count
64
+ pane_count = len(state["right_panes"])
65
+ pane_index = state["right_pane_index"]
66
+ if pane_count > 1:
67
+ s = f" {pane_index+1}/{pane_count} "
68
+ stdscr.addstr(y+h-1, x+w-len(s)-1, s, curses.color_pair(state["colours_start"]+20))
69
+
70
+ if len(state["indexed_items"]) == 0:
71
+ return []
72
+
73
+ # Filename/cursor cell value
74
+ stdscr.addstr(y+2, x+2, cell[:w-3])
75
+
76
+ # If the cursor-hovered file is different then reload the data
77
+ if data[1] != cell:
78
+ data[:] = update_file_attributes(data, state)
79
+
80
+ # attributes = get_file_attributes(cell)
81
+ if len(data) == 0: return []
82
+ attributes = data[0]
83
+ for i, attr in enumerate(attributes):
84
+ stdscr.addstr(y+3+i, x+4, attr[:w-5])
85
+
86
+ return []
87
+
88
+ def right_split_graph(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
49
89
  """
50
90
  Display a graph of the data in right pane.
51
91
 
@@ -95,7 +135,7 @@ def right_split_graph(stdscr, x, y, w, h, state, row, cell, past_data: list = []
95
135
 
96
136
 
97
137
 
98
- def right_split_display_list(stdscr, x, y, w, h, state, row, cell, past_data: list = [], data: list = [], test: bool = False):
138
+ def right_split_display_list(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
99
139
  """
100
140
  data[0]:str = title
101
141
  data[1]:list[str] = list of strings to display
@@ -83,6 +83,6 @@ def get_file_attributes(filename):
83
83
  return attributes
84
84
 
85
85
  except Exception as e:
86
- print(f"An error occurred: {e}")
86
+ # print(f"An error occurred: {e}")
87
87
  return []
88
88
 
@@ -15,8 +15,44 @@ import toml
15
15
  import logging
16
16
 
17
17
  logger = logging.getLogger('picker_log')
18
+ import concurrent.futures
18
19
 
19
20
  def generate_columns(funcs: list, files: list) -> list[list[str]]:
21
+ """
22
+ Takes a list of functions and a list of files.
23
+ Tasks are run in parallel using concurrent.futures.
24
+ """
25
+ logger.info("function: generate_columns (generate_data.py)")
26
+
27
+ results = []
28
+ # Create a future object for each combination of func and file
29
+ with concurrent.futures.ThreadPoolExecutor() as executor:
30
+ futures = [[executor.submit(func, file) for func in funcs] for file in files]
31
+
32
+ for file_futures in futures:
33
+ result = [future.result() for future in file_futures]
34
+ results.append(result)
35
+ return results
36
+
37
+
38
+ def generate_columns_multithread(funcs: list, files: list) -> list[list[str]]:
39
+ """
40
+ Takes a list of functions and a list of files.
41
+ Tasks are run in parallel using concurrent.futures.
42
+ """
43
+ logger.info("function: generate_columns (generate_data.py)")
44
+
45
+ results = []
46
+ # Create a future object for each combination of func and file
47
+ with concurrent.futures.ThreadPoolExecutor() as executor:
48
+ futures = [[executor.submit(func, file) for func in funcs] for file in files]
49
+
50
+ for file_futures in futures:
51
+ result = [future.result() for future in file_futures]
52
+ results.append(result)
53
+ return results
54
+
55
+ def generate_columns_single_thread(funcs: list, files: list) -> list[list[str]]:
20
56
  """
21
57
  Takes a list of functions and a list of files. Each function is run for each file and a list of lists is returned.
22
58
  """
@@ -30,7 +66,7 @@ def generate_columns(funcs: list, files: list) -> list[list[str]]:
30
66
  except:
31
67
  item.append("")
32
68
  items.append(item)
33
-
69
+
34
70
  return items
35
71
 
36
72
  def command_to_func(command: str) -> Callable:
@@ -0,0 +1,202 @@
1
+ #!/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ generate_data_multithreaded.py
5
+ Generate data for listpick Picker.
6
+
7
+
8
+ 1. Read toml file.
9
+ 2. Set environment variables.
10
+ 3. Get files from first command.
11
+ 4. Create items with "..." for cells to be filled.
12
+ 5. Create a priority queue which determines which cells are to be filled first.
13
+ 6. Create a queue updater which increases the priorty of cells which are on screen which does so each second.
14
+ 7. Create threads to start generating data for cells.
15
+
16
+ Author: GrimAndGreedy
17
+ License: MIT
18
+ """
19
+
20
+ import subprocess
21
+ import os
22
+ from typing import Tuple, Callable
23
+ import toml
24
+ import logging
25
+ import threading
26
+ from queue import PriorityQueue
27
+ import time
28
+
29
+ logger = logging.getLogger('picker_log')
30
+
31
+ def generate_columns_worker(
32
+ funcs: list,
33
+ files: list,
34
+ items: list[list[str]],
35
+ getting_data: threading.Event,
36
+ task_queue: PriorityQueue,
37
+ completed_cells: set,
38
+ ) -> None:
39
+ """ Get a task from the priorty queue and fill the data for that cell."""
40
+ while task_queue.qsize() > 0:
41
+ _, (i, j) = task_queue.get()
42
+
43
+ if (i, j) in completed_cells:
44
+ task_queue.task_done()
45
+ continue
46
+
47
+ generate_cell(
48
+ func=funcs[j],
49
+ file=files[i],
50
+ items=items,
51
+ row=i,
52
+ col=j+1,
53
+ )
54
+ completed_cells.add((i, j))
55
+ task_queue.task_done()
56
+ getting_data.set()
57
+
58
+ def generate_cell(func: Callable, file: str, items: list[list[str]], row: int, col: int) -> None:
59
+ """
60
+ Takes a function, file and a file and then sets items[row][col] to the result.
61
+ """
62
+
63
+ items[row][col] = func(file).strip()
64
+
65
+ def update_queue(task_queue: PriorityQueue, visible_rows_indices: list[int], rows: int, cols: int):
66
+ """ Increase the priority of getting the data for the cells that are currently visible. """
67
+ while task_queue.qsize() > 0:
68
+ time.sleep(0.1)
69
+ for row in visible_rows_indices:
70
+ for col in range(cols):
71
+ if 0 <= row < rows:
72
+ task_queue.put((1, (row, col)))
73
+
74
+ # Delay
75
+ time.sleep(0.9)
76
+
77
+ def command_to_func(command: str) -> Callable:
78
+ """
79
+ Convert a command string to a function that will run the command.
80
+
81
+ E.g.,
82
+ mediainfo {} | grep -i format
83
+ mediainfo {} | grep -i format | head -n 1 | awk '{{print $3}}'
84
+ """
85
+ logger.info("function: command_to_func (generate_data.py)")
86
+
87
+ func = lambda arg: subprocess.run(command.format(repr(arg)), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.decode("utf-8").strip()
88
+ return func
89
+
90
+ def load_environment(envs:dict):
91
+ """
92
+ Load environment variables from an envs dict.
93
+ """
94
+ logger.info("function: load_environment (generate_data.py)")
95
+
96
+ if "cwd" in envs:
97
+ os.chdir(os.path.expandvars(os.path.expanduser(envs["cwd"])))
98
+
99
+
100
+ def read_toml(file_path) -> Tuple[dict, list, list]:
101
+ """
102
+ Read toml file and return the environment, commands and header sections.
103
+ """
104
+ logger.info("function: read_toml (generate_data.py)")
105
+ with open(file_path, 'r') as file:
106
+ config = toml.load(file)
107
+
108
+ environment = config['environment'] if 'environment' in config else {}
109
+ data = config['data'] if 'data' in config else {}
110
+ commands = [command.strip() for command in data['commands']] if 'commands' in data else []
111
+ header = [header for header in data['header']] if 'header' in data else []
112
+ return environment, commands, header
113
+
114
+
115
+ def generate_picker_data_from_file(
116
+ file_path: str,
117
+ items,
118
+ header,
119
+ visible_rows_indices,
120
+ getting_data
121
+ ) -> None:
122
+ """
123
+ Generate data for Picker based upon the toml file commands.
124
+ """
125
+ logger.info("function: generate_picker_data (generate_data.py)")
126
+
127
+
128
+ environment, lines, hdr = read_toml(file_path)
129
+
130
+ # Load any environment variables from the toml file
131
+ if environment:
132
+ load_environment(environment)
133
+
134
+
135
+ # Get list of files to be displayed in the first column.
136
+ get_files_command = lines[0]
137
+ files = subprocess.run(get_files_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.decode("utf-8").strip().split("\n")
138
+ files = [file.strip() for file in files if files]
139
+
140
+ commands_list = [line.strip() for line in lines[1:]]
141
+ command_funcs = [command_to_func(command) for command in commands_list]
142
+
143
+ generate_picker_data(
144
+ files = files,
145
+ column_functions = command_funcs,
146
+ data_header = hdr,
147
+ items = items,
148
+ picker_header = header,
149
+ visible_rows_indices = visible_rows_indices,
150
+ getting_data = getting_data,
151
+ )
152
+
153
+ def generate_picker_data(
154
+ files: list[str],
155
+ column_functions: list[Callable],
156
+ data_header,
157
+ items,
158
+ picker_header,
159
+ visible_rows_indices,
160
+ getting_data
161
+ ) -> None:
162
+ """
163
+ Generate data from a list of files and a list of column functions which will be used to
164
+ generate subsequent columns.
165
+
166
+ This function is performed asynchronously with os.cpu_count() threads.
167
+
168
+ data_header: header list to be set for the picker
169
+ picker_header: the picker header will be passed in so that it can be set for the class
170
+
171
+ """
172
+ logger.info("function: generate_picker_data (generate_data.py)")
173
+
174
+ items.clear()
175
+ items.extend([[file] + ["..." for _ in column_functions] for file in files])
176
+ # items[:] = [[file] + ["..." for _ in column_functions] for file in files]
177
+ picker_header[:] = data_header
178
+
179
+
180
+ task_queue = PriorityQueue()
181
+ for i in range(len(files)):
182
+ for j in range(len(column_functions)):
183
+ task_queue.put((10, (i, j)))
184
+
185
+ num_workers = os.cpu_count()
186
+ if num_workers in [None, -1]: num_workers = 4
187
+ completed_cells = set()
188
+
189
+ for _ in range(num_workers):
190
+ gen_items_thread = threading.Thread(
191
+ target=generate_columns_worker,
192
+ args=(column_functions, files, items, getting_data, task_queue, completed_cells),
193
+ )
194
+ gen_items_thread.daemon = True
195
+ gen_items_thread.start()
196
+
197
+ update_queue_thread = threading.Thread(
198
+ target=update_queue,
199
+ args=(task_queue, visible_rows_indices, len(files), len(column_functions)),
200
+ )
201
+ update_queue_thread.daemon = True
202
+ update_queue_thread.start()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: listpick
3
- Version: 0.1.15.20
3
+ Version: 0.1.16.1
4
4
  Summary: Listpick is a powerful TUI data tool for creating TUI apps or viewing/comparing tabulated data.
5
5
  Home-page: https://github.com/grimandgreedy/listpick
6
6
  Author: Grim
@@ -45,7 +45,7 @@ Dynamic: summary
45
45
 
46
46
  listpick is a TUI tool which displays a tabulated list of rows and allows the user to operate upon these rows--select, copy, pipe. A very simple concept but also, I hope, a powerful tool that will make it easier for people to develop TUI apps.
47
47
 
48
- Rows of data can be viewed, selected, generated, saved, loaded, refreshed, modified or copied to the clipboard. Easy to integrate into your project by creating a `menu = Picker(stdscr, items:list[list[str]])` and then the menu will be displayed by running `menu.run()`.
48
+ Rows of data can be viewed, selected, generated, saved, loaded, refreshed, modified or copied to the clipboard. Easy to integrate into your project by creating a `menu = Picker(stdscr: curses.window, items: list[list[str]])` and then the menu will be displayed by running `menu.run()`.
49
49
 
50
50
  It works great as the backend for a TUI application and can also be used as a standalone data viewer.
51
51
 
@@ -53,13 +53,13 @@ It works great as the backend for a TUI application and can also be used as a st
53
53
 
54
54
  # Quickstart
55
55
 
56
- Install listpick
56
+ Install listpick:
57
57
 
58
58
  ```
59
59
  python -m pip installl "listpick[full]"
60
60
  ```
61
61
 
62
- Create a picker object to display rows:
62
+ Create a Picker:
63
63
 
64
64
  ```
65
65
  from listpick.listpick_app import Picker, start_curses, close_curses
@@ -79,7 +79,7 @@ x.run()
79
79
  close_curses(stdscr)
80
80
 
81
81
  ```
82
- Use the listpick binary to generate and display rows based on a list of commands:
82
+ Or use the listpick binary to generate and display rows based on a list of commands:
83
83
 
84
84
  ```
85
85
  wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/data_generation/list_files.toml
@@ -46,6 +46,7 @@ src/listpick/utils/config.py
46
46
  src/listpick/utils/dump.py
47
47
  src/listpick/utils/filtering.py
48
48
  src/listpick/utils/generate_data.py
49
+ src/listpick/utils/generate_data_multithreaded.py
49
50
  src/listpick/utils/graphing.py
50
51
  src/listpick/utils/keycodes.py
51
52
  src/listpick/utils/options_selectors.py
@@ -1,36 +0,0 @@
1
- #
2
- # video_duplicates.toml
3
- # Generate list of mp4 files along with their: sha1, duration, width, height, and size.
4
- #
5
- # Author: GrimAndGreedy
6
- # License: MIT
7
-
8
-
9
- [environment]
10
- cwd="~/Downloads/new/creators/ashla/"
11
- # cwd="~/Videos/"
12
-
13
- [data]
14
- files_command = "eza -1 --no-quotes | grep -E mp4$"
15
-
16
- commands = [
17
- """eza -1 --no-quotes | grep -E mp4$""",
18
- # """sha1sum {} | awk '{{print $1}}'""",
19
- """ffprobe -show_entries format=duration -v quiet -of csv="p=0" -i {}""",
20
- """ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 {}""",
21
- """ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 {}""",
22
- """du -hs {} | awk '{{ print $1 }}'"""
23
- ]
24
-
25
- header = [
26
- "file",
27
- # "sha1",
28
- "duration",
29
- "width",
30
- "height",
31
- "size",
32
- ]
33
-
34
- # run = [
35
- # "mpv",
36
- # ]
File without changes
File without changes
File without changes
File without changes