peg-this 4.0.0__py3-none-any.whl → 4.1.0__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.
@@ -1,4 +1,3 @@
1
-
2
1
  import os
3
2
  from pathlib import Path
4
3
 
@@ -7,32 +6,52 @@ import questionary
7
6
  from rich.console import Console
8
7
 
9
8
  from peg_this.utils.ffmpeg_utils import run_command, has_audio_stream
9
+ from peg_this.utils.validation import (
10
+ validate_input_file, check_output_file, validate_positive_integer, press_continue
11
+ )
10
12
 
11
13
  console = Console()
12
14
 
13
15
 
14
16
  def convert_file(file_path):
15
- """Convert the file to a different format."""
17
+ if not validate_input_file(file_path):
18
+ press_continue()
19
+ return
20
+
16
21
  is_gif = Path(file_path).suffix.lower() == '.gif'
17
22
  has_audio = has_audio_stream(file_path)
18
23
 
19
- output_format = questionary.select("Select the output format:", choices=["mp4", "mkv", "mov", "avi", "webm", "mp3", "flac", "wav", "gif"], use_indicator=True).ask()
20
- if not output_format: return
24
+ output_format = questionary.select(
25
+ "Select the output format:",
26
+ choices=["mp4", "mkv", "mov", "avi", "webm", "mp3", "flac", "wav", "gif"]
27
+ ).ask()
28
+ if not output_format:
29
+ return
21
30
 
22
31
  if (is_gif or not has_audio) and output_format in ["mp3", "flac", "wav"]:
23
32
  console.print("[bold red]Error: Source has no audio to convert.[/bold red]")
24
- questionary.press_any_key_to_continue().ask()
33
+ press_continue()
25
34
  return
26
35
 
27
36
  output_file = f"{Path(file_path).stem}_converted.{output_format}"
28
-
37
+ action_result, final_output = check_output_file(output_file, "Output file")
38
+
39
+ if action_result == 'cancel':
40
+ console.print("[yellow]Operation cancelled.[/yellow]")
41
+ press_continue()
42
+ return
43
+
29
44
  input_stream = ffmpeg.input(file_path)
30
45
  output_stream = None
31
- kwargs = {'y': None}
46
+ kwargs = {}
32
47
 
33
48
  if output_format in ["mp4", "mkv", "mov", "avi", "webm"]:
34
- quality = questionary.select("Select quality preset:", choices=["Same as source", "High (CRF 18)", "Medium (CRF 23)", "Low (CRF 28)"], use_indicator=True).ask()
35
- if not quality: return
49
+ quality = questionary.select(
50
+ "Select quality preset:",
51
+ choices=["Same as source", "High (CRF 18)", "Medium (CRF 23)", "Low (CRF 28)"]
52
+ ).ask()
53
+ if not quality:
54
+ return
36
55
 
37
56
  if quality == "Same as source":
38
57
  kwargs['c'] = 'copy'
@@ -46,73 +65,101 @@ def convert_file(file_path):
46
65
  kwargs['b:a'] = '192k'
47
66
  else:
48
67
  kwargs['an'] = None
49
- output_stream = input_stream.output(output_file, **kwargs)
68
+ output_stream = input_stream.output(final_output, **kwargs)
50
69
 
51
70
  elif output_format in ["mp3", "flac", "wav"]:
52
71
  kwargs['vn'] = None
53
72
  if output_format == 'mp3':
54
- bitrate = questionary.select("Select audio bitrate:", choices=["128k", "192k", "256k", "320k"]).ask()
55
- if not bitrate: return
73
+ bitrate = questionary.select(
74
+ "Select audio bitrate:",
75
+ choices=["128k", "192k", "256k", "320k"]
76
+ ).ask()
77
+ if not bitrate:
78
+ return
56
79
  kwargs['c:a'] = 'libmp3lame'
57
80
  kwargs['b:a'] = bitrate
58
81
  else:
59
82
  kwargs['c:a'] = output_format
60
- output_stream = input_stream.output(output_file, **kwargs)
83
+ output_stream = input_stream.output(final_output, **kwargs)
61
84
 
62
85
  elif output_format == "gif":
63
86
  fps = questionary.text("Enter frame rate (e.g., 15):", default="15").ask()
64
- if not fps: return
87
+ if not fps:
88
+ return
89
+ fps_val = validate_positive_integer(fps, "Frame rate")
90
+ if not fps_val:
91
+ press_continue()
92
+ return
93
+
65
94
  scale = questionary.text("Enter width in pixels (e.g., 480):", default="480").ask()
66
- if not scale: return
67
-
68
- palette_file = f"palette_{Path(file_path).stem}.png"
69
-
70
- # Correctly chain filters for palette generation using explicit w/h arguments
71
- palette_gen_stream = input_stream.video.filter('fps', fps=fps).filter('scale', w=scale, h=-1, flags='lanczos').filter('palettegen')
72
- run_command(palette_gen_stream.output(palette_file, y=None), "Generating color palette...")
73
-
74
- if not os.path.exists(palette_file):
75
- console.print("[bold red]Failed to generate color palette for GIF.[/bold red]")
76
- questionary.press_any_key_to_continue().ask()
95
+ if not scale:
96
+ return
97
+ scale_val = validate_positive_integer(scale, "Width")
98
+ if not scale_val:
99
+ press_continue()
77
100
  return
78
101
 
79
- palette_input = ffmpeg.input(palette_file)
80
- video_stream = input_stream.video.filter('fps', fps=fps).filter('scale', w=scale, h=-1, flags='lanczos')
81
-
82
- final_stream = ffmpeg.filter([video_stream, palette_input], 'paletteuse')
83
- output_stream = final_stream.output(output_file, y=None)
102
+ palette_file = f"palette_{Path(file_path).stem}.png"
84
103
 
85
- if output_stream and run_command(output_stream, f"Converting to {output_format}...", show_progress=True):
86
- console.print(f"[bold green]Successfully converted to {output_file}[/bold green]")
87
- else:
88
- console.print("[bold red]Conversion failed.[/bold red]")
104
+ try:
105
+ palette_gen_stream = input_stream.video.filter('fps', fps=fps_val).filter('scale', w=scale_val, h=-1, flags='lanczos').filter('palettegen')
106
+ run_command(palette_gen_stream.output(palette_file).overwrite_output(), "Generating color palette...")
107
+
108
+ if not os.path.exists(palette_file):
109
+ console.print("[bold red]Failed to generate color palette for GIF.[/bold red]")
110
+ press_continue()
111
+ return
112
+
113
+ palette_input = ffmpeg.input(palette_file)
114
+ video_stream = input_stream.video.filter('fps', fps=fps_val).filter('scale', w=scale_val, h=-1, flags='lanczos')
115
+ final_stream = ffmpeg.filter([video_stream, palette_input], 'paletteuse')
116
+ output_stream = final_stream.output(final_output)
117
+
118
+ finally:
119
+ if os.path.exists(palette_file):
120
+ os.remove(palette_file)
121
+
122
+ if output_stream:
123
+ if action_result == 'overwrite':
124
+ output_stream = output_stream.overwrite_output()
125
+
126
+ if run_command(output_stream, f"Converting to {output_format}...", show_progress=True):
127
+ console.print(f"[bold green]Successfully converted to {final_output}[/bold green]")
128
+ else:
129
+ console.print("[bold red]Conversion failed.[/bold red]")
89
130
 
90
- if output_format == "gif" and os.path.exists(f"palette_{Path(file_path).stem}.png"):
91
- os.remove(f"palette_{Path(file_path).stem}.png")
92
-
93
- questionary.press_any_key_to_continue().ask()
131
+ press_continue()
94
132
 
95
133
 
96
134
  def convert_image(file_path):
97
- """Convert an image to a different format."""
135
+ if not validate_input_file(file_path):
136
+ press_continue()
137
+ return
138
+
98
139
  output_format = questionary.select(
99
140
  "Select the output format:",
100
- choices=["jpg", "png", "webp", "bmp", "tiff"],
101
- use_indicator=True
141
+ choices=["jpg", "png", "webp", "bmp", "tiff"]
102
142
  ).ask()
103
- if not output_format: return
143
+ if not output_format:
144
+ return
104
145
 
105
146
  output_file = f"{Path(file_path).stem}_converted.{output_format}"
106
- kwargs = {'y': None}
107
-
108
- # For JPG and WEBP, allow quality selection
147
+ action_result, final_output = check_output_file(output_file, "Image file")
148
+
149
+ if action_result == 'cancel':
150
+ console.print("[yellow]Operation cancelled.[/yellow]")
151
+ press_continue()
152
+ return
153
+
154
+ kwargs = {}
155
+
109
156
  if output_format in ['jpg', 'webp']:
110
157
  quality_preset = questionary.select(
111
158
  "Select quality preset:",
112
- choices=["High (95%)", "Medium (80%)", "Low (60%)"],
113
- use_indicator=True
159
+ choices=["High (95%)", "Medium (80%)", "Low (60%)"]
114
160
  ).ask()
115
- if not quality_preset: return
161
+ if not quality_preset:
162
+ return
116
163
 
117
164
  quality_map = {"High (95%)": "95", "Medium (80%)": "80", "Low (60%)": "60"}
118
165
  quality = quality_map[quality_preset]
@@ -123,103 +170,140 @@ def convert_image(file_path):
123
170
  elif output_format == 'webp':
124
171
  kwargs['quality'] = quality
125
172
 
126
- stream = ffmpeg.input(file_path).output(output_file, **kwargs)
127
-
173
+ stream = ffmpeg.input(file_path).output(final_output, **kwargs)
174
+
175
+ if action_result == 'overwrite':
176
+ stream = stream.overwrite_output()
177
+
128
178
  if run_command(stream, f"Converting to {output_format.upper()}..."):
129
- console.print(f"[bold green]Successfully converted image to {output_file}[/bold green]")
179
+ console.print(f"[bold green]Successfully converted image to {final_output}[/bold green]")
130
180
  else:
131
181
  console.print("[bold red]Image conversion failed.[/bold red]")
132
-
133
- questionary.press_any_key_to_continue().ask()
182
+
183
+ press_continue()
134
184
 
135
185
 
136
186
  def resize_image(file_path):
137
- """Resize an image to new dimensions."""
187
+ if not validate_input_file(file_path):
188
+ press_continue()
189
+ return
190
+
138
191
  console.print("Enter new dimensions. Use [bold]-1[/bold] for one dimension to preserve aspect ratio.")
139
192
  width = questionary.text("Enter new width (e.g., 1280 or -1):").ask()
140
- if not width: return
193
+ if not width:
194
+ return
141
195
  height = questionary.text("Enter new height (e.g., 720 or -1):").ask()
142
- if not height: return
196
+ if not height:
197
+ return
143
198
 
144
- try:
145
- if int(width) == -1 and int(height) == -1:
146
- console.print("[bold red]Error: Width and Height cannot both be -1.[/bold red]")
147
- questionary.press_any_key_to_continue().ask()
148
- return
149
- except ValueError:
150
- console.print("[bold red]Error: Invalid dimensions. Please enter numbers.[/bold red]")
151
- questionary.press_any_key_to_continue().ask()
199
+ width_val = validate_positive_integer(width, "Width")
200
+ height_val = validate_positive_integer(height, "Height")
201
+
202
+ if width_val is None or height_val is None:
203
+ press_continue()
204
+ return
205
+
206
+ if width_val == -1 and height_val == -1:
207
+ console.print("[bold red]Error: Width and Height cannot both be -1.[/bold red]")
208
+ press_continue()
152
209
  return
153
210
 
154
211
  output_file = f"{Path(file_path).stem}_resized{Path(file_path).suffix}"
155
-
156
- stream = ffmpeg.input(file_path).filter('scale', w=width, h=height).output(output_file, y=None)
157
-
212
+ action_result, final_output = check_output_file(output_file, "Image file")
213
+
214
+ if action_result == 'cancel':
215
+ console.print("[yellow]Operation cancelled.[/yellow]")
216
+ press_continue()
217
+ return
218
+
219
+ stream = ffmpeg.input(file_path).filter('scale', w=width_val, h=height_val).output(final_output)
220
+
221
+ if action_result == 'overwrite':
222
+ stream = stream.overwrite_output()
223
+
158
224
  if run_command(stream, "Resizing image..."):
159
- console.print(f"[bold green]Successfully resized image to {output_file}[/bold green]")
225
+ console.print(f"[bold green]Successfully resized image to {final_output}[/bold green]")
160
226
  else:
161
227
  console.print("[bold red]Image resizing failed.[/bold red]")
162
-
163
- questionary.press_any_key_to_continue().ask()
228
+
229
+ press_continue()
164
230
 
165
231
 
166
232
  def rotate_image(file_path):
167
- """Rotate an image."""
233
+ if not validate_input_file(file_path):
234
+ press_continue()
235
+ return
236
+
168
237
  rotation = questionary.select(
169
238
  "Select rotation:",
170
- choices=[
171
- "90 degrees clockwise",
172
- "90 degrees counter-clockwise",
173
- "180 degrees"
174
- ],
175
- use_indicator=True
239
+ choices=["90 degrees clockwise", "90 degrees counter-clockwise", "180 degrees"]
176
240
  ).ask()
177
- if not rotation: return
241
+ if not rotation:
242
+ return
178
243
 
179
244
  output_file = f"{Path(file_path).stem}_rotated{Path(file_path).suffix}"
180
-
245
+ action_result, final_output = check_output_file(output_file, "Image file")
246
+
247
+ if action_result == 'cancel':
248
+ console.print("[yellow]Operation cancelled.[/yellow]")
249
+ press_continue()
250
+ return
251
+
181
252
  stream = ffmpeg.input(file_path)
182
253
  if rotation == "90 degrees clockwise":
183
254
  stream = stream.filter('transpose', 1)
184
255
  elif rotation == "90 degrees counter-clockwise":
185
256
  stream = stream.filter('transpose', 2)
186
257
  elif rotation == "180 degrees":
187
- # Apply 90-degree rotation twice for 180 degrees
188
258
  stream = stream.filter('transpose', 2).filter('transpose', 2)
189
259
 
190
- output_stream = stream.output(output_file, y=None)
191
-
260
+ output_stream = stream.output(final_output)
261
+
262
+ if action_result == 'overwrite':
263
+ output_stream = output_stream.overwrite_output()
264
+
192
265
  if run_command(output_stream, "Rotating image..."):
193
- console.print(f"[bold green]Successfully rotated image and saved to {output_file}[/bold green]")
266
+ console.print(f"[bold green]Successfully rotated image and saved to {final_output}[/bold green]")
194
267
  else:
195
268
  console.print("[bold red]Image rotation failed.[/bold red]")
196
-
197
- questionary.press_any_key_to_continue().ask()
269
+
270
+ press_continue()
198
271
 
199
272
 
200
273
  def flip_image(file_path):
201
- """Flip an image horizontally or vertically."""
274
+ if not validate_input_file(file_path):
275
+ press_continue()
276
+ return
277
+
202
278
  flip_direction = questionary.select(
203
279
  "Select flip direction:",
204
- choices=["Horizontal", "Vertical"],
205
- use_indicator=True
280
+ choices=["Horizontal", "Vertical"]
206
281
  ).ask()
207
- if not flip_direction: return
282
+ if not flip_direction:
283
+ return
208
284
 
209
285
  output_file = f"{Path(file_path).stem}_flipped{Path(file_path).suffix}"
210
-
286
+ action_result, final_output = check_output_file(output_file, "Image file")
287
+
288
+ if action_result == 'cancel':
289
+ console.print("[yellow]Operation cancelled.[/yellow]")
290
+ press_continue()
291
+ return
292
+
211
293
  stream = ffmpeg.input(file_path)
212
294
  if flip_direction == "Horizontal":
213
295
  stream = stream.filter('hflip')
214
296
  else:
215
297
  stream = stream.filter('vflip')
216
298
 
217
- output_stream = stream.output(output_file, y=None)
218
-
299
+ output_stream = stream.output(final_output)
300
+
301
+ if action_result == 'overwrite':
302
+ output_stream = output_stream.overwrite_output()
303
+
219
304
  if run_command(output_stream, "Flipping image..."):
220
- console.print(f"[bold green]Successfully flipped image and saved to {output_file}[/bold green]")
305
+ console.print(f"[bold green]Successfully flipped image and saved to {final_output}[/bold green]")
221
306
  else:
222
307
  console.print("[bold red]Image flipping failed.[/bold red]")
223
-
224
- questionary.press_any_key_to_continue().ask()
225
308
 
309
+ press_continue()
peg_this/features/crop.py CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  import os
3
2
  from pathlib import Path
4
3
 
@@ -7,6 +6,9 @@ import questionary
7
6
  from rich.console import Console
8
7
 
9
8
  from peg_this.utils.ffmpeg_utils import run_command, has_audio_stream
9
+ from peg_this.utils.validation import (
10
+ validate_input_file, check_output_file, warn_reencode, press_continue
11
+ )
10
12
 
11
13
  try:
12
14
  import tkinter as tk
@@ -19,29 +21,38 @@ console = Console()
19
21
 
20
22
 
21
23
  def crop_video(file_path):
22
- """Visually crop a video by selecting an area."""
23
24
  if not tk:
24
25
  console.print("[bold red]Cannot perform visual cropping: tkinter & Pillow are not installed.[/bold red]")
26
+ console.print("[dim]Install them with: pip install tk Pillow[/dim]")
27
+ press_continue()
28
+ return
29
+
30
+ if not validate_input_file(file_path):
31
+ press_continue()
25
32
  return
26
33
 
27
34
  preview_frame = f"preview_{Path(file_path).stem}.jpg"
28
35
  try:
29
- # Extract a frame from the middle of the video for preview
30
36
  probe = ffmpeg.probe(file_path)
31
- duration = float(probe['format']['duration'])
37
+ duration = float(probe['format'].get('duration', 0))
38
+
39
+ if duration <= 0:
40
+ console.print("[bold red]Error: Could not determine video duration.[/bold red]")
41
+ press_continue()
42
+ return
43
+
32
44
  mid_point = duration / 2
33
-
34
- # Corrected frame extraction command with `-q:v`
45
+
35
46
  run_command(
36
- ffmpeg.input(file_path, ss=mid_point).output(preview_frame, vframes=1, **{'q:v': 2}, y=None),
47
+ ffmpeg.input(file_path, ss=mid_point).output(preview_frame, vframes=1, **{'q:v': 2}).overwrite_output(),
37
48
  "Extracting a frame for preview..."
38
49
  )
39
50
 
40
51
  if not os.path.exists(preview_frame):
41
52
  console.print("[bold red]Could not extract a frame from the video.[/bold red]")
53
+ press_continue()
42
54
  return
43
55
 
44
- # --- Tkinter GUI for Cropping ---
45
56
  root = tk.Tk()
46
57
  root.title("Crop Video - Drag to select area, close window to confirm")
47
58
  root.attributes("-topmost", True)
@@ -67,65 +78,77 @@ def crop_video(file_path):
67
78
 
68
79
  canvas.bind("<ButtonPress-1>", on_press)
69
80
  canvas.bind("<B1-Motion>", on_drag)
70
-
81
+
71
82
  messagebox.showinfo("Instructions", "Click and drag to draw a cropping rectangle.\nClose this window when you are done.", parent=root)
72
83
  root.mainloop()
73
84
 
74
- # --- Cropping Logic ---
75
85
  crop_w = abs(rect_coords['x2'] - rect_coords['x1'])
76
86
  crop_h = abs(rect_coords['y2'] - rect_coords['y1'])
77
87
  crop_x = min(rect_coords['x1'], rect_coords['x2'])
78
88
  crop_y = min(rect_coords['y1'], rect_coords['y2'])
79
89
 
80
- if crop_w < 2 or crop_h < 2: # Avoid tiny, invalid crops
90
+ if crop_w < 2 or crop_h < 2:
81
91
  console.print("[bold yellow]Cropping cancelled as no valid area was selected.[/bold yellow]")
82
92
  return
83
93
 
84
94
  console.print(f"Selected crop area: [bold]width={crop_w} height={crop_h} at (x={crop_x}, y={crop_y})[/bold]")
85
95
 
86
96
  output_file = f"{Path(file_path).stem}_cropped{Path(file_path).suffix}"
87
-
97
+ action_result, final_output = check_output_file(output_file, "Video file")
98
+
99
+ if action_result == 'cancel':
100
+ console.print("[yellow]Operation cancelled.[/yellow]")
101
+ return
102
+
103
+ warn_reencode("Video cropping")
104
+
88
105
  input_stream = ffmpeg.input(file_path)
89
106
  video_stream = input_stream.video.filter('crop', w=crop_w, h=crop_h, x=crop_x, y=crop_y)
90
-
91
- kwargs = {'y': None} # Overwrite output
92
- # Check for audio and copy it if it exists
107
+
93
108
  if has_audio_stream(file_path):
94
109
  audio_stream = input_stream.audio
95
- kwargs['c:a'] = 'copy'
96
- stream = ffmpeg.output(video_stream, audio_stream, output_file, **kwargs)
110
+ stream = ffmpeg.output(video_stream, audio_stream, final_output, **{'c:a': 'copy'})
97
111
  else:
98
- stream = ffmpeg.output(video_stream, output_file, **kwargs)
112
+ stream = ffmpeg.output(video_stream, final_output)
113
+
114
+ if action_result == 'overwrite':
115
+ stream = stream.overwrite_output()
99
116
 
100
- run_command(stream, "Applying crop to video...", show_progress=True)
101
- console.print(f"[bold green]Successfully cropped video and saved to {output_file}[/bold green]")
117
+ if run_command(stream, "Applying crop to video...", show_progress=True):
118
+ console.print(f"[bold green]Successfully cropped video and saved to {final_output}[/bold green]")
119
+ else:
120
+ console.print("[bold red]Video cropping failed.[/bold red]")
102
121
 
122
+ except Exception as e:
123
+ console.print(f"[bold red]An error occurred: {e}[/bold red]")
103
124
  finally:
104
125
  if os.path.exists(preview_frame):
105
126
  os.remove(preview_frame)
106
- questionary.press_any_key_to_continue().ask()
127
+ press_continue()
107
128
 
108
129
 
109
130
  def crop_image(file_path):
110
- """Visually crop an image by selecting an area."""
111
131
  if not tk:
112
132
  console.print("[bold red]Cannot perform visual cropping: tkinter & Pillow are not installed.[/bold red]")
113
- console.print("Please install them with: [bold]pip install tk Pillow[/bold]")
114
- questionary.press_any_key_to_continue().ask()
133
+ console.print("[dim]Install them with: pip install tk Pillow[/dim]")
134
+ press_continue()
135
+ return
136
+
137
+ if not validate_input_file(file_path):
138
+ press_continue()
115
139
  return
116
140
 
117
141
  try:
118
- # --- Tkinter GUI for Cropping ---
119
142
  root = tk.Tk()
120
143
  root.title("Crop Image - Drag to select area, close window to confirm")
121
144
  root.attributes("-topmost", True)
122
145
 
123
146
  img = Image.open(file_path)
124
-
147
+
125
148
  max_width = root.winfo_screenwidth() - 100
126
149
  max_height = root.winfo_screenheight() - 100
127
150
  img.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)
128
-
151
+
129
152
  img_tk = ImageTk.PhotoImage(img)
130
153
 
131
154
  canvas = tk.Canvas(root, width=img.width, height=img.height, cursor="cross")
@@ -146,11 +169,10 @@ def crop_image(file_path):
146
169
 
147
170
  canvas.bind("<ButtonPress-1>", on_press)
148
171
  canvas.bind("<B1-Motion>", on_drag)
149
-
172
+
150
173
  messagebox.showinfo("Instructions", "Click and drag to draw a cropping rectangle.\nClose this window when you are done.", parent=root)
151
174
  root.mainloop()
152
175
 
153
- # --- Cropping Logic ---
154
176
  crop_w = abs(rect_coords['x2'] - rect_coords['x1'])
155
177
  crop_h = abs(rect_coords['y2'] - rect_coords['y1'])
156
178
  crop_x = min(rect_coords['x1'], rect_coords['x2'])
@@ -158,21 +180,28 @@ def crop_image(file_path):
158
180
 
159
181
  if crop_w < 2 or crop_h < 2:
160
182
  console.print("[bold yellow]Cropping cancelled as no valid area was selected.[/bold yellow]")
161
- questionary.press_any_key_to_continue().ask()
162
183
  return
163
184
 
164
185
  console.print(f"Selected crop area: [bold]width={crop_w} height={crop_h} at (x={crop_x}, y={crop_y})[/bold]")
165
186
 
166
187
  output_file = f"{Path(file_path).stem}_cropped{Path(file_path).suffix}"
167
-
168
- stream = ffmpeg.input(file_path).filter('crop', w=crop_w, h=crop_h, x=crop_x, y=crop_y).output(output_file, y=None)
188
+ action_result, final_output = check_output_file(output_file, "Image file")
189
+
190
+ if action_result == 'cancel':
191
+ console.print("[yellow]Operation cancelled.[/yellow]")
192
+ return
193
+
194
+ stream = ffmpeg.input(file_path).filter('crop', w=crop_w, h=crop_h, x=crop_x, y=crop_y).output(final_output)
195
+
196
+ if action_result == 'overwrite':
197
+ stream = stream.overwrite_output()
169
198
 
170
199
  if run_command(stream, "Applying crop to image..."):
171
- console.print(f"[bold green]Successfully cropped image and saved to {output_file}[/bold green]")
200
+ console.print(f"[bold green]Successfully cropped image and saved to {final_output}[/bold green]")
172
201
  else:
173
202
  console.print("[bold red]Image cropping failed.[/bold red]")
174
203
 
175
204
  except Exception as e:
176
205
  console.print(f"[bold red]An error occurred during cropping: {e}[/bold red]")
177
206
  finally:
178
- questionary.press_any_key_to_continue().ask()
207
+ press_continue()