1-
21import os
32import logging
43from pathlib import Path
98
109from peg_this .utils .ffmpeg_utils import run_command , has_audio_stream
1110from peg_this .utils .ui_utils import get_media_files
11+ from peg_this .utils .validation import check_disk_space , press_continue
1212
1313console = Console ()
1414
1515
1616def batch_convert ():
17- """Convert all media files in the directory to a specific format."""
1817 media_files = get_media_files ()
1918 if not media_files :
2019 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 ()
2221 return
2322
23+ console .print (f"[dim]Found { len (media_files )} media file(s)[/dim]" )
24+
2425 output_format = questionary .select (
2526 "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" ]
2828 ).ask ()
29- if not output_format : return
29+ if not output_format :
30+ return
3031
3132 quality_preset = None
3233 if output_format in ["mp4" , "mkv" , "mov" , "avi" , "webm" ]:
3334 quality_preset = questionary .select (
3435 "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)" ]
3737 ).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
3945
4046 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?" ,
4248 default = False
4349 ).ask ()
4450
@@ -48,78 +54,98 @@ def batch_convert():
4854
4955 success_count = 0
5056 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'
7993 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]" )
100139 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
114140
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
117145
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]" )
122148
123149 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 ()
0 commit comments