1+ import os , json , tkinter as tk , ctypes , threading
2+ from tkinter import filedialog , ttk
3+
4+ class MyApp (tk .Tk ):
5+ def __init__ (self ):
6+ super ().__init__ ()
7+ self .ffmpeg_path = tk .StringVar ()
8+ self .video_path = tk .StringVar ()
9+ self .audio_path = tk .StringVar ()
10+ self .output_path = tk .StringVar ()
11+ self .file_name = tk .StringVar ()
12+
13+ if os .path .exists ("ffmpeg_path.json" ):
14+ with open ("ffmpeg_path.json" , "r" ) as f :
15+ try :
16+ data = json .load (f )
17+ self .ffmpeg_path .set (data .get ("ffmpeg_path" , "" ))
18+ self .video_path .set (data .get ("video_path" , "" ))
19+ self .audio_path .set (data .get ("audio_path" , "" ))
20+ self .output_path .set (data .get ("output_path" , "" ))
21+ self .file_name .set (data .get ("file_name" , "output.mp4" ))
22+ except json .JSONDecodeError :
23+ self .ffmpeg_path .set ("" )
24+ self .video_path .set ("" )
25+ self .audio_path .set ("" )
26+ self .output_path .set ("" )
27+ self .file_name .set ("output.mp4" )
28+
29+ # Frame for FFmpeg path
30+ ffmpeg_frame = tk .Frame (self )
31+ ffmpeg_frame .pack (pady = 10 , anchor = "w" , padx = 10 )
32+ tk .Label (ffmpeg_frame , text = "FFmpeg Path, leave blank if in PATH" ).pack (anchor = "w" )
33+ self .ffmpeg_path_entry = tk .Entry (ffmpeg_frame , textvariable = self .ffmpeg_path , width = 50 )
34+ self .ffmpeg_path_entry .pack (side = tk .LEFT )
35+ self .ffmpeg_browse_button = tk .Button (ffmpeg_frame , text = "Browse" , command = self .browse_ffmpeg , borderwidth = 0 , highlightthickness = 0 , background = "#3C3C3C" , cursor = "hand2" )
36+ self .ffmpeg_browse_button .pack (side = tk .LEFT , padx = 10 )
37+
38+ # Frame for Video path
39+ video_frame = tk .Frame (self )
40+ video_frame .pack (pady = 10 , anchor = "w" , padx = 10 )
41+ tk .Label (video_frame , text = "Video Path" ).pack (anchor = "w" )
42+ self .video_path_entry = tk .Entry (video_frame , textvariable = self .video_path , width = 50 )
43+ self .video_path_entry .pack (side = tk .LEFT )
44+ self .video_browse_button = tk .Button (video_frame , text = "Browse" , command = self .browse_video , borderwidth = 0 , highlightthickness = 0 , background = "#3C3C3C" , cursor = "hand2" )
45+ self .video_browse_button .pack (side = tk .LEFT , padx = 10 )
46+
47+ # Frame for Audio path
48+ audio_frame = tk .Frame (self )
49+ audio_frame .pack (pady = 10 , anchor = "w" , padx = 10 )
50+ tk .Label (audio_frame , text = "Audio Path" ).pack (anchor = "w" )
51+ self .audio_path_entry = tk .Entry (audio_frame , textvariable = self .audio_path , width = 50 )
52+ self .audio_path_entry .pack (side = tk .LEFT )
53+ self .audio_browse_button = tk .Button (audio_frame , text = "Browse" , command = self .browse_audio , borderwidth = 0 , highlightthickness = 0 , background = "#3C3C3C" , cursor = "hand2" )
54+ self .audio_browse_button .pack (side = tk .LEFT , padx = 10 )
55+
56+
57+ # Output path
58+ output_frame = tk .Frame (self )
59+ output_frame .pack (pady = 10 , anchor = "w" , padx = 10 )
60+ tk .Label (output_frame , text = "Output Path" ).pack (anchor = "w" )
61+ self .output_path_entry = tk .Entry (output_frame , textvariable = self .output_path , width = 50 )
62+ self .output_path_entry .pack (side = tk .LEFT )
63+ self .output_browse_button = tk .Button (output_frame , text = "Browse" , command = self .browse_output , borderwidth = 0 , highlightthickness = 0 , background = "#3C3C3C" , cursor = "hand2" )
64+ self .output_browse_button .pack (side = tk .LEFT , padx = 10 )
65+
66+ output_file_frame = tk .Frame (self )
67+ output_file_frame .pack (pady = 10 , anchor = "w" , padx = 10 )
68+ tk .Label (output_file_frame , text = "Output File Name" ).pack (anchor = "w" )
69+ self .output_file_name_entry = tk .Entry (output_file_frame , textvariable = self .file_name , width = 50 )
70+ self .output_file_name_entry .pack (side = tk .LEFT )
71+ self .output_browse_button = tk .Button (output_file_frame , text = "Browse" , command = self .browse_output_file )
72+ self .output_browse_button .pack (side = tk .LEFT , padx = 10 )
73+
74+ # Buttons
75+ button_frame = tk .Frame (self )
76+ button_frame .pack (pady = 10 , anchor = "w" , padx = 10 )
77+ self .save_button = tk .Button (button_frame , text = "Save Paths" , command = self .save_paths , borderwidth = 0 , highlightthickness = 0 , background = "#3C3C3C" , cursor = "hand2" )
78+ self .save_button .pack (side = tk .LEFT )
79+ self .run_button = tk .Button (button_frame , text = "Merge" , command = self .thread_run_ffmpeg , borderwidth = 0 , highlightthickness = 0 , background = "#3C3C3C" , cursor = "hand2" )
80+ self .run_button .pack (side = tk .LEFT , padx = 10 )
81+ self .clear_paths_button = tk .Button (button_frame , text = "Clear Paths" , command = self .clear_paths , borderwidth = 0 , highlightthickness = 0 , background = "#3C3C3C" , cursor = "hand2" )
82+ self .clear_paths_button .pack (side = tk .LEFT , padx = (0 ,10 ))
83+
84+ # note
85+ note_frame = tk .Frame (self )
86+ note_frame .pack (pady = 10 , anchor = "w" , padx = 10 )
87+ tk .Label (note_frame , text = "Note: If ffmpeg path is blank, it will be searched in PATH." , foreground = "#5E5E5E" ).pack (anchor = "w" )
88+ tk .Label (note_frame , text = "Note: If output path is blank, it will be saved in app directory." , foreground = "#5E5E5E" ).pack (anchor = "w" )
89+ tk .Label (note_frame , text = "Note: If ffmpeg asks you something in the terminal, please do as it says." , foreground = "#5E5E5E" ).pack (anchor = "w" )
90+
91+ def display_notification (self , title , message ):
92+ ctypes .windll .user32 .MessageBoxW (0 , message , title , 0x40 | 0x40000 )
93+ def clear_paths (self ):
94+ self .ffmpeg_path .set ("" )
95+ self .video_path .set ("" )
96+ self .audio_path .set ("" )
97+ self .output_path .set ("" )
98+ self .file_name .set ("output.mp4" )
99+ with open ("ffmpeg_path.json" , "w" ) as f :
100+ json .dump ({"ffmpeg_path" : "" , "video_path" : "" , "audio_path" : "" , "output_path" : "" , "file_name" : "output.mp4" }, f )
101+ def browse_ffmpeg (self ):
102+ path = filedialog .askdirectory ()
103+ if path :
104+ self .ffmpeg_path .set (os .path .join (path , "ffmpeg" ))
105+
106+ def browse_video (self ):
107+ path = filedialog .askopenfilename (filetypes = [("Video Files" , "*.mp4;*.mkv;*.avi;*.mov;*.webm" )])
108+ if path :
109+ self .video_path .set (path )
110+
111+ def browse_audio (self ):
112+ path = filedialog .askopenfilename (filetypes = [("Audio Files" , "*.mp3;*.aac;*.wav;*.flac;*.opus;*.m4a;*.webm" )])
113+ if path :
114+ self .audio_path .set (path )
115+
116+ def browse_output (self ):
117+ path = filedialog .askdirectory ()
118+ if path :
119+ self .output_path .set (path )
120+
121+ def browse_output_file (self ):
122+ file_path = filedialog .asksaveasfilename (filetypes = [("Video Files" , "*.mp4;*.mkv;*.avi;*.mov;*.webm" )])
123+ if file_path :
124+ self .file_name .set (os .path .basename (file_path ))
125+
126+
127+ def save_paths (self ):
128+ paths = {
129+ "ffmpeg_path" : self .ffmpeg_path .get (),
130+ "video_path" : self .video_path .get (),
131+ "audio_path" : self .audio_path .get (),
132+ "output_path" : self .output_path .get (),
133+ "file_name" : self .file_name .get ()
134+ }
135+ with open ("ffmpeg_path.json" , "w" ) as f :
136+ json .dump (paths , f )
137+
138+ def run_ffmpeg (self ):
139+ with open ("ffmpeg_path.json" , "r" ) as f :
140+ try :
141+ data = json .load (f )
142+ self .ffmpeg_path .set (data .get ("ffmpeg_path" , "" ))
143+ self .video_path .set (data .get ("video_path" , "" ))
144+ self .audio_path .set (data .get ("audio_path" , "" ))
145+ self .output_path .set (data .get ("output_path" , "" ))
146+ self .file_name .set (data .get ("file_name" , "" ))
147+ except json .JSONDecodeError :
148+ self .ffmpeg_path .set ("" )
149+ self .video_path .set ("" )
150+ self .audio_path .set ("" )
151+ self .output_path .set ("" )
152+ self .file_name .set ("" )
153+
154+ ffmpeg_path = self .ffmpeg_path .get ()
155+ video_path = self .video_path .get ()
156+ audio_path = self .audio_path .get ()
157+ output_path = self .output_path .get ().replace ("/" , "\\ " )
158+ file_name = self .file_name .get ()
159+
160+ if video_path and audio_path :
161+ if not ffmpeg_path :
162+ ffmpeg_path = "ffmpeg"
163+ if not output_path :
164+ output_path = os .getcwd ()
165+ #print(f'{ffmpeg_path} -i "{video_path}" -i "{audio_path}" -c:v copy -c:a aac "{output_path}\\{file_name}"')
166+ os .system (f'{ ffmpeg_path } -i "{ video_path } " -i "{ audio_path } " -c:v copy -c:a aac "{ output_path } \\ { file_name } "' )
167+ self .display_notification ("FFmpeg Audio and Video Merger" , f"Video and audio merged successfully\n { output_path } \\ { file_name } " )
168+ else :
169+ print ("Please make sure all paths are set." )
170+
171+ def thread_run_ffmpeg (self ):
172+ threading .Thread (target = self .run_ffmpeg ).start ()
173+
174+ if __name__ == "__main__" :
175+ app = MyApp ()
176+ app .title ("FFmpeg Audio and Video Merger" )
177+ app .minsize (400 , 400 )
178+ app .resizable (False , False )
179+ app .tk_setPalette (background = '#2b2b2b' , foreground = 'white' )
180+ try :
181+ app .iconphoto (False , tk .PhotoImage (file = 'ffmpeg_icon.png' ))
182+ except tk .TclError :
183+ print ("Icon not found, using default icon" )
184+
185+ app .mainloop ()
0 commit comments