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
  from pathlib import Path
3
2
 
4
3
  import ffmpeg
@@ -6,33 +5,81 @@ import questionary
6
5
  from rich.console import Console
7
6
 
8
7
  from peg_this.utils.ffmpeg_utils import run_command, has_audio_stream
8
+ from peg_this.utils.validation import (
9
+ validate_input_file, check_output_file, check_has_video_stream, press_continue
10
+ )
9
11
 
10
12
  console = Console()
11
13
 
12
14
 
13
15
  def extract_audio(file_path):
14
- """Extract the audio track from a video file."""
16
+ if not validate_input_file(file_path):
17
+ press_continue()
18
+ return
19
+
15
20
  if not has_audio_stream(file_path):
16
21
  console.print("[bold red]Error: No audio stream found in the file.[/bold red]")
17
- questionary.press_any_key_to_continue().ask()
22
+ press_continue()
18
23
  return
19
24
 
20
- audio_format = questionary.select("Select audio format:", choices=["mp3", "flac", "wav"], use_indicator=True).ask()
21
- if not audio_format: return
25
+ audio_format = questionary.select(
26
+ "Select audio format:",
27
+ choices=["mp3", "flac", "wav"]
28
+ ).ask()
29
+ if not audio_format:
30
+ return
22
31
 
23
32
  output_file = f"{Path(file_path).stem}_audio.{audio_format}"
24
- stream = ffmpeg.input(file_path).output(output_file, vn=None, acodec='libmp3lame' if audio_format == 'mp3' else audio_format, y=None)
25
-
26
- run_command(stream, f"Extracting audio to {audio_format.upper()}...", show_progress=True)
27
- console.print(f"[bold green]Successfully extracted audio to {output_file}[/bold green]")
28
- questionary.press_any_key_to_continue().ask()
33
+ action_result, final_output = check_output_file(output_file, "Audio file")
34
+
35
+ if action_result == 'cancel':
36
+ console.print("[yellow]Operation cancelled.[/yellow]")
37
+ press_continue()
38
+ return
39
+
40
+ stream = ffmpeg.input(file_path).output(
41
+ final_output,
42
+ vn=None,
43
+ acodec='libmp3lame' if audio_format == 'mp3' else audio_format
44
+ )
45
+
46
+ if action_result == 'overwrite':
47
+ stream = stream.overwrite_output()
48
+
49
+ if run_command(stream, f"Extracting audio to {audio_format.upper()}...", show_progress=True):
50
+ console.print(f"[bold green]Successfully extracted audio to {final_output}[/bold green]")
51
+ else:
52
+ console.print("[bold red]Failed to extract audio.[/bold red]")
53
+
54
+ press_continue()
29
55
 
30
56
 
31
57
  def remove_audio(file_path):
32
- """Create a silent version of a video."""
58
+ if not validate_input_file(file_path):
59
+ press_continue()
60
+ return
61
+
62
+ if not check_has_video_stream(file_path):
63
+ console.print("[bold red]Error: No video stream found in the file.[/bold red]")
64
+ press_continue()
65
+ return
66
+
33
67
  output_file = f"{Path(file_path).stem}_no_audio{Path(file_path).suffix}"
34
- stream = ffmpeg.input(file_path).output(output_file, vcodec='copy', an=None, y=None)
35
-
36
- run_command(stream, "Removing audio track...", show_progress=True)
37
- console.print(f"[bold green]Successfully removed audio, saved to {output_file}[/bold green]")
38
- questionary.press_any_key_to_continue().ask()
68
+ action_result, final_output = check_output_file(output_file, "Video file")
69
+
70
+ if action_result == 'cancel':
71
+ console.print("[yellow]Operation cancelled.[/yellow]")
72
+ press_continue()
73
+ return
74
+
75
+ stream = ffmpeg.input(file_path).output(final_output, vcodec='copy', an=None)
76
+
77
+ if action_result == 'overwrite':
78
+ stream = stream.overwrite_output()
79
+
80
+ if run_command(stream, "Removing audio track...", show_progress=True):
81
+ console.print(f"[bold green]Successfully removed audio, saved to {final_output}[/bold green]")
82
+ else:
83
+ console.print("[bold red]Failed to remove audio.[/bold red]")
84
+
85
+ press_continue()
@@ -1,4 +1,3 @@
1
-
2
1
  import os
3
2
  import logging
4
3
  from pathlib import Path
@@ -9,36 +8,43 @@ from rich.console import Console
9
8
 
10
9
  from peg_this.utils.ffmpeg_utils import run_command, has_audio_stream
11
10
  from peg_this.utils.ui_utils import get_media_files
11
+ from peg_this.utils.validation import check_disk_space, press_continue
12
12
 
13
13
  console = Console()
14
14
 
15
15
 
16
16
  def batch_convert():
17
- """Convert all media files in the directory to a specific format."""
18
17
  media_files = get_media_files()
19
18
  if not media_files:
20
19
  console.print("[bold yellow]No media files found in the current directory.[/bold yellow]")
21
- questionary.press_any_key_to_continue().ask()
20
+ press_continue()
22
21
  return
23
22
 
23
+ console.print(f"[dim]Found {len(media_files)} media file(s)[/dim]")
24
+
24
25
  output_format = questionary.select(
25
26
  "Select output format for the batch conversion:",
26
- choices=["mp4", "mkv", "mov", "avi", "webm", "mp3", "flac", "wav", "gif"],
27
- use_indicator=True
27
+ choices=["mp4", "mkv", "mov", "avi", "webm", "mp3", "flac", "wav", "gif"]
28
28
  ).ask()
29
- if not output_format: return
29
+ if not output_format:
30
+ return
30
31
 
31
32
  quality_preset = None
32
33
  if output_format in ["mp4", "mkv", "mov", "avi", "webm"]:
33
34
  quality_preset = questionary.select(
34
35
  "Select quality preset:",
35
- choices=["Same as source", "High (CRF 18)", "Medium (CRF 23)", "Low (CRF 28)"],
36
- use_indicator=True
36
+ choices=["Same as source", "High (CRF 18)", "Medium (CRF 23)", "Low (CRF 28)"]
37
37
  ).ask()
38
- if not quality_preset: return
38
+ if not quality_preset:
39
+ return
40
+
41
+ # Estimate disk space needed
42
+ total_size = sum(os.path.getsize(f) for f in media_files if os.path.exists(f))
43
+ if total_size > 0 and not check_disk_space(media_files[0], multiplier=len(media_files)):
44
+ return
39
45
 
40
46
  confirm = questionary.confirm(
41
- f"This will convert {len(media_files)} file(s) in the current directory to .{output_format}. Continue?",
47
+ f"This will convert {len(media_files)} file(s) to .{output_format}. Continue?",
42
48
  default=False
43
49
  ).ask()
44
50
 
@@ -48,78 +54,98 @@ def batch_convert():
48
54
 
49
55
  success_count = 0
50
56
  fail_count = 0
51
-
52
- for file in media_files:
53
- console.rule(f"Processing: {file}")
54
- file_path = os.path.abspath(file)
55
- is_gif = Path(file_path).suffix.lower() == '.gif'
56
- has_audio = has_audio_stream(file_path)
57
-
58
- if (is_gif or not has_audio) and output_format in ["mp3", "flac", "wav"]:
59
- console.print(f"[bold yellow]Skipping {file}: Source has no audio to convert.[/bold yellow]")
60
- continue
61
-
62
- output_file = f"{Path(file_path).stem}_batch.{output_format}"
63
- input_stream = ffmpeg.input(file_path)
64
- output_stream = None
65
- kwargs = {'y': None}
66
-
67
- try:
68
- if output_format in ["mp4", "mkv", "mov", "avi", "webm"]:
69
- if quality_preset == "Same as source":
70
- kwargs['c'] = 'copy'
71
- else:
72
- crf = quality_preset.split(" ")[-1][1:-1]
73
- kwargs['c:v'] = 'libx264'
74
- kwargs['crf'] = crf
75
- kwargs['pix_fmt'] = 'yuv420p'
76
- if has_audio:
77
- kwargs['c:a'] = 'aac'
78
- kwargs['b:a'] = '192k'
57
+ skipped_count = 0
58
+
59
+ try:
60
+ for i, file in enumerate(media_files):
61
+ console.rule(f"[{i+1}/{len(media_files)}] Processing: {file}")
62
+ file_path = os.path.abspath(file)
63
+
64
+ if not os.path.exists(file_path):
65
+ console.print(f"[yellow]Skipping {file}: File not found.[/yellow]")
66
+ skipped_count += 1
67
+ continue
68
+
69
+ is_gif = Path(file_path).suffix.lower() == '.gif'
70
+ has_audio = has_audio_stream(file_path)
71
+
72
+ if (is_gif or not has_audio) and output_format in ["mp3", "flac", "wav"]:
73
+ console.print(f"[yellow]Skipping {file}: Source has no audio to convert.[/yellow]")
74
+ skipped_count += 1
75
+ continue
76
+
77
+ output_file = f"{Path(file_path).stem}_batch.{output_format}"
78
+
79
+ # Skip if output already exists
80
+ if os.path.exists(output_file):
81
+ console.print(f"[yellow]Skipping {file}: Output already exists ({output_file})[/yellow]")
82
+ skipped_count += 1
83
+ continue
84
+
85
+ input_stream = ffmpeg.input(file_path)
86
+ output_stream = None
87
+ kwargs = {}
88
+
89
+ try:
90
+ if output_format in ["mp4", "mkv", "mov", "avi", "webm"]:
91
+ if quality_preset == "Same as source":
92
+ kwargs['c'] = 'copy'
79
93
  else:
80
- kwargs['an'] = None
81
- output_stream = input_stream.output(output_file, **kwargs)
82
-
83
- elif output_format in ["mp3", "flac", "wav"]:
84
- kwargs['vn'] = None
85
- kwargs['c:a'] = 'libmp3lame' if output_format == 'mp3' else output_format
86
- if output_format == 'mp3':
87
- kwargs['b:a'] = '192k' # Default bitrate for batch
88
- output_stream = input_stream.output(output_file, **kwargs)
89
-
90
- elif output_format == "gif":
91
- fps = "15"
92
- scale = "480"
93
- palette_file = f"palette_{Path(file_path).stem}.png"
94
-
95
- palette_gen_stream = input_stream.video.filter('fps', fps=fps).filter('scale', w=scale, h=-1, flags='lanczos').filter('palettegen')
96
- run_command(palette_gen_stream.output(palette_file, y=None), f"Generating palette for {file}...")
97
-
98
- if not os.path.exists(palette_file):
99
- console.print(f"[bold red]Failed to generate color palette for {file}.[/bold red]")
94
+ crf = quality_preset.split(" ")[-1][1:-1]
95
+ kwargs['c:v'] = 'libx264'
96
+ kwargs['crf'] = crf
97
+ kwargs['pix_fmt'] = 'yuv420p'
98
+ if has_audio:
99
+ kwargs['c:a'] = 'aac'
100
+ kwargs['b:a'] = '192k'
101
+ else:
102
+ kwargs['an'] = None
103
+ output_stream = input_stream.output(output_file, **kwargs)
104
+
105
+ elif output_format in ["mp3", "flac", "wav"]:
106
+ kwargs['vn'] = None
107
+ kwargs['c:a'] = 'libmp3lame' if output_format == 'mp3' else output_format
108
+ if output_format == 'mp3':
109
+ kwargs['b:a'] = '192k'
110
+ output_stream = input_stream.output(output_file, **kwargs)
111
+
112
+ elif output_format == "gif":
113
+ fps = 15
114
+ scale = 480
115
+ palette_file = f"palette_{Path(file_path).stem}.png"
116
+
117
+ try:
118
+ palette_gen_stream = input_stream.video.filter('fps', fps=fps).filter('scale', w=scale, h=-1, flags='lanczos').filter('palettegen')
119
+ run_command(palette_gen_stream.output(palette_file).overwrite_output(), f"Generating palette for {file}...")
120
+
121
+ if not os.path.exists(palette_file):
122
+ console.print(f"[red]Failed to generate color palette for {file}.[/red]")
123
+ fail_count += 1
124
+ continue
125
+
126
+ palette_input = ffmpeg.input(palette_file)
127
+ video_stream = input_stream.video.filter('fps', fps=fps).filter('scale', w=scale, h=-1, flags='lanczos')
128
+ final_stream = ffmpeg.filter([video_stream, palette_input], 'paletteuse')
129
+ output_stream = final_stream.output(output_file)
130
+ finally:
131
+ if os.path.exists(palette_file):
132
+ os.remove(palette_file)
133
+
134
+ if output_stream and run_command(output_stream, f"Converting {file}...", show_progress=True):
135
+ console.print(f" -> [green]Successfully converted to {output_file}[/green]")
136
+ success_count += 1
137
+ else:
138
+ console.print(f" -> [red]Failed to convert {file}.[/red]")
100
139
  fail_count += 1
101
- continue
102
-
103
- palette_input = ffmpeg.input(palette_file)
104
- video_stream = input_stream.video.filter('fps', fps=fps).filter('scale', w=scale, h=-1, flags='lanczos')
105
- final_stream = ffmpeg.filter([video_stream, palette_input], 'paletteuse')
106
- output_stream = final_stream.output(output_file, y=None)
107
-
108
- if output_stream and run_command(output_stream, f"Converting {file}...", show_progress=True):
109
- console.print(f" -> [bold green]Successfully converted to {output_file}[/bold green]")
110
- success_count += 1
111
- else:
112
- console.print(f" -> [bold red]Failed to convert {file}.[/bold red]")
113
- fail_count += 1
114
140
 
115
- if output_format == "gif" and os.path.exists(f"palette_{Path(file_path).stem}.png"):
116
- os.remove(f"palette_{Path(file_path).stem}.png")
141
+ except Exception as e:
142
+ console.print(f"[red]Error processing {file}: {e}[/red]")
143
+ logging.error(f"Batch convert error for file {file}: {e}")
144
+ fail_count += 1
117
145
 
118
- except Exception as e:
119
- console.print(f"[bold red]An unexpected error occurred while processing {file}: {e}[/bold red]")
120
- logging.error(f"Batch convert error for file {file}: {e}")
121
- fail_count += 1
146
+ except KeyboardInterrupt:
147
+ console.print("\n[yellow]Batch conversion interrupted by user.[/yellow]")
122
148
 
123
149
  console.rule("[bold green]Batch Conversion Complete[/bold green]")
124
- console.print(f"Successful: {success_count} | Failed: {fail_count}")
125
- questionary.press_any_key_to_continue().ask()
150
+ console.print(f"Successful: {success_count} | Failed: {fail_count} | Skipped: {skipped_count}")
151
+ press_continue()