talks-reducer 0.3.0__py3-none-any.whl → 0.3.1__py3-none-any.whl

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.
talks_reducer/audio.py CHANGED
@@ -11,6 +11,8 @@ import numpy as np
11
11
  from audiotsm import phasevocoder
12
12
  from audiotsm.io.array import ArrayReader, ArrayWriter
13
13
 
14
+ from .ffmpeg import get_ffprobe_path
15
+
14
16
 
15
17
  def get_max_volume(samples: np.ndarray) -> float:
16
18
  """Return the maximum absolute volume in the provided sample array."""
@@ -21,8 +23,6 @@ def get_max_volume(samples: np.ndarray) -> float:
21
23
  def is_valid_input_file(filename: str) -> bool:
22
24
  """Check whether ``ffprobe`` recognises the input file and finds an audio stream."""
23
25
 
24
- from .ffmpeg import get_ffprobe_path
25
-
26
26
  ffprobe_path = get_ffprobe_path()
27
27
  command = [
28
28
  ffprobe_path,
@@ -36,29 +36,31 @@ def is_valid_input_file(filename: str) -> bool:
36
36
  "-show_entries",
37
37
  "stream=codec_type",
38
38
  ]
39
-
39
+
40
40
  # Hide console window on Windows
41
41
  creationflags = 0
42
42
  if sys.platform == "win32":
43
43
  # CREATE_NO_WINDOW = 0x08000000
44
44
  creationflags = 0x08000000
45
-
46
- process = subprocess.Popen(
47
- command,
48
- stdout=subprocess.PIPE,
49
- stderr=subprocess.PIPE,
50
- creationflags=creationflags
51
- )
52
- outs, errs = None, None
45
+
53
46
  try:
54
- outs, errs = process.communicate(timeout=1)
47
+ result = subprocess.run(
48
+ command,
49
+ capture_output=True,
50
+ text=True,
51
+ timeout=5,
52
+ creationflags=creationflags,
53
+ )
55
54
  except subprocess.TimeoutExpired:
56
55
  print("Timeout while checking the input file. Aborting. Command:")
57
56
  print(" ".join(command))
58
- process.kill()
59
- outs, errs = process.communicate()
60
- finally:
61
- return len(errs) == 0 and len(outs) > 0
57
+ return False
58
+
59
+ if result.returncode != 0:
60
+ return False
61
+
62
+ stdout = result.stdout or ""
63
+ return "codec_type=audio" in stdout
62
64
 
63
65
 
64
66
  def process_audio_chunks(
talks_reducer/gui.py CHANGED
@@ -395,6 +395,10 @@ class TalksReducerGUI:
395
395
  input_frame.rowconfigure(1, weight=1)
396
396
  self._configure_drop_targets(self.drop_zone)
397
397
  self._configure_drop_targets(self.input_list)
398
+ self.drop_zone.configure(cursor="hand2", takefocus=1)
399
+ self.drop_zone.bind("<Button-1>", self._on_drop_zone_click)
400
+ self.drop_zone.bind("<Return>", self._on_drop_zone_click)
401
+ self.drop_zone.bind("<space>", self._on_drop_zone_click)
398
402
 
399
403
  self.add_files_button = self.ttk.Button(
400
404
  input_frame, text="Add files", command=self._add_files
@@ -420,23 +424,28 @@ class TalksReducerGUI:
420
424
  options.grid(row=2, column=0, pady=(16, 0), sticky="ew")
421
425
  options.columnconfigure(0, weight=1)
422
426
 
423
- self.simple_mode_check = self.ttk.Checkbutton(
424
- options,
425
- text="Simple mode",
426
- variable=self.simple_mode_var,
427
- command=self._toggle_simple_mode,
428
- )
429
- self.simple_mode_check.grid(row=0, column=0, sticky="w")
427
+ checkbox_frame = self.ttk.Frame(options)
428
+ checkbox_frame.grid(row=0, column=0, columnspan=2, sticky="w")
430
429
 
431
- self.ttk.Checkbutton(options, text="Small video", variable=self.small_var).grid(
432
- row=1, column=0, sticky="w", pady=(8, 0)
433
- )
430
+ self.ttk.Checkbutton(
431
+ checkbox_frame,
432
+ text="Small video",
433
+ variable=self.small_var,
434
+ ).grid(row=0, column=0, sticky="w")
434
435
 
435
436
  self.ttk.Checkbutton(
436
- options,
437
+ checkbox_frame,
437
438
  text="Open after convert",
438
439
  variable=self.open_after_convert_var,
439
- ).grid(row=2, column=0, sticky="w", pady=(8, 0))
440
+ ).grid(row=0, column=1, sticky="w", padx=(12, 0))
441
+
442
+ self.simple_mode_check = self.ttk.Checkbutton(
443
+ checkbox_frame,
444
+ text="Simple mode",
445
+ variable=self.simple_mode_var,
446
+ command=self._toggle_simple_mode,
447
+ )
448
+ self.simple_mode_check.grid(row=0, column=2, sticky="w", padx=(12, 0))
440
449
 
441
450
  self.advanced_visible = self.tk.BooleanVar(value=False)
442
451
  self.advanced_button = self.ttk.Button(
@@ -444,10 +453,10 @@ class TalksReducerGUI:
444
453
  text="Advanced",
445
454
  command=self._toggle_advanced,
446
455
  )
447
- self.advanced_button.grid(row=0, column=1, sticky="e")
456
+ self.advanced_button.grid(row=1, column=1, sticky="e")
448
457
 
449
458
  self.advanced_frame = self.ttk.Frame(options, padding=self.PADDING)
450
- self.advanced_frame.grid(row=3, column=0, columnspan=2, sticky="nsew")
459
+ self.advanced_frame.grid(row=2, column=0, columnspan=2, sticky="nsew")
451
460
  self.advanced_frame.columnconfigure(1, weight=1)
452
461
 
453
462
  self.output_var = self.tk.StringVar()
@@ -509,9 +518,10 @@ class TalksReducerGUI:
509
518
  status_frame.columnconfigure(1, weight=1)
510
519
  status_frame.columnconfigure(2, weight=0)
511
520
 
512
-
513
521
  self.ttk.Label(status_frame, text="Status:").grid(row=0, column=0, sticky="w")
514
- self.status_label = self.tk.Label(status_frame, textvariable=self.status_var, anchor="e")
522
+ self.status_label = self.tk.Label(
523
+ status_frame, textvariable=self.status_var, anchor="e"
524
+ )
515
525
  self.status_label.grid(row=0, column=1, sticky="e")
516
526
 
517
527
  # Progress bar
@@ -527,7 +537,9 @@ class TalksReducerGUI:
527
537
  self.stop_button = self.ttk.Button(
528
538
  status_frame, text="Stop", command=self._stop_processing
529
539
  )
530
- self.stop_button.grid(row=2, column=0, columnspan=3, sticky="ew", pady=self.PADDING)
540
+ self.stop_button.grid(
541
+ row=2, column=0, columnspan=3, sticky="ew", pady=self.PADDING
542
+ )
531
543
  self.stop_button.grid_remove() # Hidden by default
532
544
 
533
545
  self.open_button = self.ttk.Button(
@@ -536,7 +548,9 @@ class TalksReducerGUI:
536
548
  command=self._open_last_output,
537
549
  state=self.tk.DISABLED,
538
550
  )
539
- self.open_button.grid(row=2, column=0, columnspan=3, sticky="ew", pady=self.PADDING)
551
+ self.open_button.grid(
552
+ row=2, column=0, columnspan=3, sticky="ew", pady=self.PADDING
553
+ )
540
554
  self.open_button.grid_remove()
541
555
 
542
556
  # Button shown when no other action buttons are visible
@@ -545,7 +559,9 @@ class TalksReducerGUI:
545
559
  text="Drop video to convert",
546
560
  state=self.tk.DISABLED,
547
561
  )
548
- self.drop_hint_button.grid(row=2, column=0, columnspan=3, sticky="ew", pady=self.PADDING)
562
+ self.drop_hint_button.grid(
563
+ row=2, column=0, columnspan=3, sticky="ew", pady=self.PADDING
564
+ )
549
565
  self.drop_hint_button.grid_remove() # Hidden by default
550
566
  self._configure_drop_targets(self.drop_hint_button)
551
567
 
@@ -607,11 +623,13 @@ class TalksReducerGUI:
607
623
  self.stop_button.grid_remove()
608
624
  self.advanced_button.grid_remove()
609
625
  self.advanced_frame.grid_remove()
610
- if hasattr(self, 'status_frame'):
626
+ if hasattr(self, "status_frame"):
611
627
  self.status_frame.grid_remove()
612
628
  self.run_after_drop_var.set(True)
613
629
  self._apply_window_size(simple=True)
614
- if self.status_var.get().lower() == "success" and hasattr(self, 'status_frame'):
630
+ if self.status_var.get().lower() == "success" and hasattr(
631
+ self, "status_frame"
632
+ ):
615
633
  self.status_frame.grid()
616
634
  self.open_button.grid()
617
635
  self.drop_hint_button.grid_remove()
@@ -619,7 +637,7 @@ class TalksReducerGUI:
619
637
  for widget in widgets:
620
638
  widget.grid()
621
639
  self.log_frame.grid()
622
- if hasattr(self, 'status_frame'):
640
+ if hasattr(self, "status_frame"):
623
641
  self.status_frame.grid()
624
642
  self.advanced_button.grid()
625
643
  if self.advanced_visible.get():
@@ -841,14 +859,19 @@ class TalksReducerGUI:
841
859
  widget.dnd_bind("<<Drop>>", self._on_drop) # type: ignore[attr-defined]
842
860
 
843
861
  # -------------------------------------------------------------- actions --
844
- def _add_files(self) -> None:
845
- files = self.filedialog.askopenfilenames(
862
+ def _ask_for_input_files(self) -> tuple[str, ...]:
863
+ """Prompt the user to select input files for processing."""
864
+
865
+ return self.filedialog.askopenfilenames(
846
866
  title="Select input files",
847
867
  filetypes=[
848
868
  ("Video files", "*.mp4 *.mkv *.mov *.avi *.m4v"),
849
869
  ("All", "*.*"),
850
870
  ],
851
871
  )
872
+
873
+ def _add_files(self) -> None:
874
+ files = self._ask_for_input_files()
852
875
  self._extend_inputs(files)
853
876
 
854
877
  def _add_directory(self) -> None:
@@ -888,6 +911,16 @@ class TalksReducerGUI:
888
911
  self.input_list.delete(0, self.tk.END)
889
912
  self._extend_inputs(cleaned, auto_run=True)
890
913
 
914
+ def _on_drop_zone_click(self, event: object) -> str | None:
915
+ """Open a file selection dialog when the drop zone is activated."""
916
+
917
+ files = self._ask_for_input_files()
918
+ if not files:
919
+ return "break"
920
+ self._clear_input_files()
921
+ self._extend_inputs(files, auto_run=True)
922
+ return "break"
923
+
891
924
  def _browse_path(
892
925
  self, variable, label: str
893
926
  ) -> None: # type: (tk.StringVar, str) -> None
@@ -1012,9 +1045,11 @@ class TalksReducerGUI:
1012
1045
  """Hide Stop button."""
1013
1046
  self.stop_button.grid_remove()
1014
1047
  # Show drop hint when stop button is hidden and no other buttons are visible
1015
- if (not self.open_button.winfo_viewable() and
1016
- hasattr(self, 'drop_hint_button') and
1017
- not self.drop_hint_button.winfo_viewable()):
1048
+ if (
1049
+ not self.open_button.winfo_viewable()
1050
+ and hasattr(self, "drop_hint_button")
1051
+ and not self.drop_hint_button.winfo_viewable()
1052
+ ):
1018
1053
  self.drop_hint_button.grid()
1019
1054
 
1020
1055
  def _collect_arguments(self) -> dict[str, object]:
@@ -1168,13 +1203,13 @@ class TalksReducerGUI:
1168
1203
  if is_processing:
1169
1204
  self._start_status_animation()
1170
1205
  # Show stop button during processing
1171
- if hasattr(self, 'status_frame'):
1206
+ if hasattr(self, "status_frame"):
1172
1207
  self.status_frame.grid()
1173
1208
  self.stop_button.grid()
1174
1209
  self.drop_hint_button.grid_remove()
1175
1210
 
1176
1211
  if lowered == "success":
1177
- if self.simple_mode_var.get() and hasattr(self, 'status_frame'):
1212
+ if self.simple_mode_var.get() and hasattr(self, "status_frame"):
1178
1213
  self.status_frame.grid()
1179
1214
  self.stop_button.grid_remove()
1180
1215
  self.drop_hint_button.grid_remove()
@@ -1184,11 +1219,15 @@ class TalksReducerGUI:
1184
1219
  else:
1185
1220
  self.open_button.grid_remove()
1186
1221
  print("not success status")
1187
- if self.simple_mode_var.get() and not is_processing and hasattr(self, 'status_frame'):
1222
+ if (
1223
+ self.simple_mode_var.get()
1224
+ and not is_processing
1225
+ and hasattr(self, "status_frame")
1226
+ ):
1188
1227
  self.status_frame.grid_remove()
1189
1228
  self.stop_button.grid_remove()
1190
1229
  # Show drop hint when no other buttons are visible
1191
- if hasattr(self, 'drop_hint_button'):
1230
+ if hasattr(self, "drop_hint_button"):
1192
1231
  self.drop_hint_button.grid()
1193
1232
 
1194
1233
  self.root.after(0, apply)
@@ -1219,11 +1258,11 @@ class TalksReducerGUI:
1219
1258
 
1220
1259
  def _calculate_gradient_color(self, percentage: int, darken: float = 1.0) -> str:
1221
1260
  """Calculate color gradient from red (0%) to green (100%).
1222
-
1261
+
1223
1262
  Args:
1224
1263
  percentage: The position in the gradient (0-100)
1225
1264
  darken: Value between 0.0 (black) and 1.0 (original brightness)
1226
-
1265
+
1227
1266
  Returns:
1228
1267
  Hex color code string
1229
1268
  """
@@ -1282,10 +1321,11 @@ class TalksReducerGUI:
1282
1321
 
1283
1322
  # Show stop button when progress < 100
1284
1323
  if percentage < 100:
1285
- if hasattr(self, 'status_frame'):
1324
+ if hasattr(self, "status_frame"):
1286
1325
  self.status_frame.grid()
1287
1326
  self.stop_button.grid()
1288
1327
  self.drop_hint_button.grid_remove()
1328
+
1289
1329
  self.root.after(0, updater)
1290
1330
 
1291
1331
  def _set_progress_bar_style(self, status: str) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: talks-reducer
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: CLI for speeding up long-form talks by removing silence
5
5
  Author: Talks Reducer Maintainers
6
6
  License-Expression: MIT
@@ -9,7 +9,7 @@ Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
10
  Requires-Dist: audiotsm>=0.1.2
11
11
  Requires-Dist: scipy>=1.10.0
12
- Requires-Dist: numpy<2.0.0,>=1.22.0
12
+ Requires-Dist: numpy>=1.22.0
13
13
  Requires-Dist: tqdm>=4.65.0
14
14
  Requires-Dist: tkinterdnd2>=0.3.0
15
15
  Requires-Dist: Pillow>=9.0.0
@@ -82,8 +82,9 @@ continues to work unchanged for local development.
82
82
  zone, hides the manual run controls and log, and automatically processes new
83
83
  files as soon as you drop them. Uncheck the box to return to the full layout
84
84
  with file pickers, the Run button, and detailed logging.
85
- - **Input drop zone** — drag files or folders from your desktop or add them via
86
- the Explorer/Finder dialog; duplicates are ignored.
85
+ - **Input drop zone** — drag files or folders from your desktop, click to open
86
+ the system file picker, or add them via the Explorer/Finder dialog; duplicates
87
+ are ignored.
87
88
  - **Small video** — toggles the `--small` preset used by the CLI.
88
89
  - **Open after convert** — controls whether the exported file is revealed in
89
90
  your system file manager as soon as each job finishes.
@@ -1,16 +1,16 @@
1
1
  talks_reducer/__init__.py,sha256=lb50C4_o_SLERyMyVpQfgHnXf49FJOIF9j05MZ8KAvM,158
2
2
  talks_reducer/__main__.py,sha256=azR_vh8HFPLaOnh-L6gUFWsL67I6iHtbeH5rQhsipGY,299
3
- talks_reducer/audio.py,sha256=we6-lyVjCJSFDEFUTPhtpiUmT2MZdPMpBrYN2ED_T9E,4440
3
+ talks_reducer/audio.py,sha256=sjHMeY0H9ESG-Gn5BX0wFRBX7sXjWwsgS8u9Vb0bJ88,4396
4
4
  talks_reducer/chunks.py,sha256=IpdZxRFPURSG5wP-OQ_p09CVP8wcKwIFysV29zOTSWI,2959
5
5
  talks_reducer/cli.py,sha256=ZQcay6NF32g0PF7OGLKWPY1TbIR1Hx2xaRlzOXK1lto,6508
6
6
  talks_reducer/ffmpeg.py,sha256=tM_T2mV_Y7U91QzWmTBEid9cjEobLpPnxsiOUfz_yDA,11697
7
- talks_reducer/gui.py,sha256=htD1rtfNL1CqFd0Cz_mQuchainNi2OQuT7QwMn6igmM,52548
7
+ talks_reducer/gui.py,sha256=42gWHLs1LlNvNYp-mEnUmpJd-XpempOF5tWytA5fTRs,53679
8
8
  talks_reducer/models.py,sha256=6cZRcJf0EBZIzNd-PWrh4Wdsoa4EBj5nSdB6BnFiOXM,1106
9
9
  talks_reducer/pipeline.py,sha256=deGvGMF3CSVd7lcpA7dke8dlLQw3mi_FEhSkFNte7Ro,8871
10
10
  talks_reducer/progress.py,sha256=Mh43M6VWhjjUv9CI22xfD2EJ_7Aq3PCueqefQ9Bd5-o,4565
11
- talks_reducer-0.3.0.dist-info/licenses/LICENSE,sha256=jN17mHNR3e84awmH3AbpWBcBDBzPxEH0rcOFoj1s7sQ,1124
12
- talks_reducer-0.3.0.dist-info/METADATA,sha256=rTooepcqImxAe-nByxim_P5qABcM-VkhnzV81WZLi6M,7625
13
- talks_reducer-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- talks_reducer-0.3.0.dist-info/entry_points.txt,sha256=LCzfSnh_7VXhvl9twoFSAj0C3sG7bayWs2LkxpH7hoI,100
15
- talks_reducer-0.3.0.dist-info/top_level.txt,sha256=pJWGcy__LR9JIEKH3QJyFmk9XrIsiFtqvuMNxFdIzDU,14
16
- talks_reducer-0.3.0.dist-info/RECORD,,
11
+ talks_reducer-0.3.1.dist-info/licenses/LICENSE,sha256=jN17mHNR3e84awmH3AbpWBcBDBzPxEH0rcOFoj1s7sQ,1124
12
+ talks_reducer-0.3.1.dist-info/METADATA,sha256=NU0eHj8kK0bC4Lz-AMjqxlAUhxKuaSgODYVr5iRkT9Y,7660
13
+ talks_reducer-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ talks_reducer-0.3.1.dist-info/entry_points.txt,sha256=LCzfSnh_7VXhvl9twoFSAj0C3sG7bayWs2LkxpH7hoI,100
15
+ talks_reducer-0.3.1.dist-info/top_level.txt,sha256=pJWGcy__LR9JIEKH3QJyFmk9XrIsiFtqvuMNxFdIzDU,14
16
+ talks_reducer-0.3.1.dist-info/RECORD,,