1c Replace
1c Replace
47 command=self.update_view).pack(side=tk.LEFT, padx=5)
48
49 # Progress bar
50 self.progress = ttk.Progressbar(file_frame, orient="horizontal", length=300, mode
="determinate")
51 self.progress.grid(row=2, column=0, columnspan=3, sticky=tk.EW, pady=5)
52
53 # Content frames
54 content_frame = tk.Frame(main_frame)
55 content_frame.pack(fill=tk.BOTH, expand=True, pady=10)
56
57 # Split content frame
58 content_paned = tk.PanedWindow(content_frame, orient=tk.HORIZONTAL)
59 content_paned.pack(fill=tk.BOTH, expand=True)
60
61 # Original content
62 original_frame = tk.LabelFrame(content_paned, text="Original Content")
63 content_paned.add(original_frame)
64
65 self.original_text = scrolledtext.ScrolledText(original_frame, wrap=tk.WORD)
66 self.original_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
67
68 # Processed content
69 processed_frame = tk.LabelFrame(content_paned, text="Processed Content")
70 content_paned.add(processed_frame)
71
72 self.processed_text = scrolledtext.ScrolledText(processed_frame, wrap=tk.WORD)
73 self.processed_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
74
75 # Action buttons
76 button_frame = tk.Frame(main_frame, padx=10, pady=10)
77 button_frame.pack(fill=tk.X)
78
79 self.process_button = tk.Button(button_frame, text="Process File", command=self.
start_processing)
80 self.process_button.pack(side=tk.LEFT, padx=5)
81
82 self.save_button = tk.Button(button_frame, text="Save As...", command=self.
start_saving)
83 self.save_button.pack(side=tk.LEFT, padx=5)
84
85 tk.Button(button_frame, text="Exit", command=self.root.quit).pack(side=tk.RIGHT,
padx=5)
86
87 # Status bar
88 self.status_var = tk.StringVar()
89 self.status_var.set("Ready")
90 status_bar = tk.Label(self.root, textvariable=self.status_var, bd=1, relief=tk.
SUNKEN, anchor=tk.W)
91 status_bar.pack(side=tk.BOTTOM, fill=tk.X)
92
93 # Help button
94 tk.Button(button_frame, text="Help", command=self.show_help).pack(side=tk.RIGHT,
padx=5)
95
96 def browse_file(self):
97 file_path = filedialog.askopenfilename(
98 title="Select a file",
99 filetypes=(("All files", "*.*"), ("Text files", "*.txt"))
100 )
101 if file_path:
102 self.file_path.set(file_path)
103 self.load_file()
104
105 def load_file(self):
106 try:
107 self.status_var.set("Loading file preview...")
108 self.root.update_idletasks()
109
110 # Get file size
111 self.file_size = os.path.getsize(self.file_path.get())
112
113 # Only load a preview for display
114 MAX_PREVIEW = 100000 # 100KB preview
115 with open(self.file_path.get(), 'rb') as f:
116 self.preview_content = f.read(min(MAX_PREVIEW, self.file_size))
117
118 self.update_view()
119 self.status_var.set(f"Loaded: {self.file_path.get()} ({self.format_size(self.
file_size)})")
120 except Exception as e:
121 messagebox.showerror("Error", f"Failed to load file: {str(e)}")
122 self.status_var.set("Error loading file")
123
124 def format_size(self, size):
125 """Format file size in human-readable format"""
126 for unit in ['B', 'KB', 'MB', 'GB']:
127 if size < 1024.0:
128 return f"{size:.2f} {unit}"
129 size /= 1024.0
130 return f"{size:.2f} TB"
131
132 def update_view(self):
133 if not hasattr(self, 'preview_content'):
134 return
135
136 self.original_text.delete(1.0, tk.END)
137
138 if self.view_mode.get() == "text":
139 # Try to decode with different encodings
140 try:
141 text_content = self.preview_content.decode('utf-8', errors='replace')
142 except UnicodeDecodeError:
143 text_content = self.preview_content.decode('latin-1')
144
145 self.original_text.insert(tk.END, text_content)
146
147 if self.file_size > len(self.preview_content):
148 self.original_text.insert(tk.END, "\n\n[File preview only. Full content
will be processed.]")
149 else: # Hex view
150 hex_content = self.format_hex_view(self.preview_content)
151 self.original_text.insert(tk.END, hex_content)
152
153 if self.file_size > len(self.preview_content):
154 self.original_text.insert(tk.END, "\n\n[File preview only. Full content
will be processed.]")
155
156 def format_hex_view(self, binary_data):
157 """Format binary data as a hex dump with ASCII representation"""
158 result = []
159 chunk_size = 16
160
161 # Limit the amount of data to display in hex view
162 MAX_HEX_DISPLAY = 5000 # Show only first 5KB in hex view
163 display_data = binary_data[:MAX_HEX_DISPLAY]
164
165 for i in range(0, len(display_data), chunk_size):
166 chunk = display_data[i:i+chunk_size]
167 hex_line = ' '.join(f'{b:02x}' for b in chunk)
168
169 # ASCII representation
170 ascii_line = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in chunk)
171
172 # Add offset, hex values, and ASCII representation
173 result.append(f"{i:08x}: {hex_line:<48} |{ascii_line}|")
174
175 if len(binary_data) > MAX_HEX_DISPLAY:
176 result.append("...\n[Hex view truncated for performance]")
177
178 return '\n'.join(result)
179
180 def start_processing(self):
181 """Start processing in a separate thread"""
182 if not self.file_path.get():
183 messagebox.showwarning("Warning", "Please load a file first.")
184 return
185
186 # Disable buttons during processing
187 self.process_button.config(state=tk.DISABLED)
188 self.save_button.config(state=tk.DISABLED)
189
190 self.status_var.set("Processing file...")
191 self.progress["value"] = 0
192 self.root.update_idletasks()
193
194 # Start processing thread
195 threading.Thread(target=self.process_file, daemon=True).start()
196
197 def process_file(self):
198 try:
199 start_time = time.time()
200
201 # Determine if we should use the ultra-fast method for large files
202 if self.file_size > 1024 * 1024: # 1MB
203 self.ultra_fast_process()
204 else:
205 # For small files, use the original method which is already fast enough
206 self.normal_process()
207
208 elapsed = time.time() - start_time
209 print(f"Processing completed in {elapsed:.2f} seconds")
210
211 # Update the UI with a preview
212 self.root.after(0, self.update_processed_preview)
213
214 except Exception as e:
215 import traceback
216 traceback.print_exc()
217 self.root.after(0, lambda: self.show_error(f"Failed to process file: {str(e)
}"))
218
219 def normal_process(self):
220 """Process small files using the normal method"""
221 with open(self.file_path.get(), 'rb') as f:
222 content = f.read()
223
224 # Process the content
225 result = bytearray()
226 for byte in content:
227 if byte in self.control_chars:
228 # Replace control character with hex representation
229 result.extend(f"<{byte:02x}>".encode('utf-8'))
230 else:
231 # Keep normal character
232 result.append(byte)
233
234 self.processed_data = bytes(result)
235 self.root.after(0, lambda: self.update_progress(100))
236
237 def ultra_fast_process(self):
238 """Ultra-fast processing for large files using direct file I/O"""
239 # Create a temporary output file
240 temp_output_path = self.file_path.get() + ".temp"
241
242 try:
243 # Open input file for reading and output file for writing
244 with open(self.file_path.get(), 'rb') as in_file, open(temp_output_path, 'wb'
) as out_file:
245 # Use a large buffer for reading
246 CHUNK_SIZE = 10 * 1024 * 1024 # 10MB chunks
247 total_chunks = (self.file_size + CHUNK_SIZE - 1) // CHUNK_SIZE
248
249 # Pre-compute all possible replacements
250 replacements = {}
251 for i in self.control_chars:
252 replacements[i] = f"<{i:02x}>".encode('utf-8')
253
254 # Process file in chunks
255 for chunk_idx in range(total_chunks):
256 # Read a chunk
257 chunk = in_file.read(CHUNK_SIZE)
258 if not chunk:
259 break
260
261 # Process chunk with ultra-fast method
262 result = bytearray()
263 for byte in chunk:
264 if byte in replacements:
265 result.extend(replacements[byte])
266 else:
267 result.append(byte)
268
269 # Write processed chunk to output file
270 out_file.write(result)
271
272 # Update progress
273 progress = min(100, int(100 * (chunk_idx + 1) / total_chunks))
274 self.root.after(0, lambda p=progress: self.update_progress(p))
275
276 # Read a preview of the processed data for display
277 with open(temp_output_path, 'rb') as f:
278 preview_size = min(100000, os.path.getsize(temp_output_path))
279 self.processed_preview = f.read(preview_size)
280
281 # Store the path to the processed data instead of loading it all
282 self.processed_file_path = temp_output_path
283 self.using_temp_file = True
284
285 except Exception as e:
286 # Clean up temp file if there was an error
287 if os.path.exists(temp_output_path):
288 try:
289 os.remove(temp_output_path)
290 except:
291 pass
292 raise e
293
294 def update_progress(self, value):
295 """Update progress bar"""
296 self.progress["value"] = value
297 self.root.update_idletasks()
298
299 def update_processed_preview(self):
300 """Update the processed text preview"""
301 self.processed_text.delete(1.0, tk.END)
302
303 # Get preview data
304 if hasattr(self, 'using_temp_file') and self.using_temp_file:
305 preview = self.processed_preview
306 is_truncated = os.path.getsize(self.processed_file_path) > len(preview)
307 else:
308 # For small files processed in memory
309 preview_size = min(100000, len(self.processed_data))
310 preview = self.processed_data[:preview_size]
311 is_truncated = len(self.processed_data) > preview_size
312
313 try:
314 text_content = preview.decode('utf-8', errors='replace')
315 except UnicodeDecodeError:
316 text_content = preview.decode('latin-1')
317
318 self.processed_text.insert(tk.END, text_content)
319
320 if is_truncated:
321 self.processed_text.insert(tk.END, "\n\n[Preview only. Full content will be
saved.]")
322
323 # Re-enable buttons
324 self.process_button.config(state=tk.NORMAL)
325 self.save_button.config(state=tk.NORMAL)
326
327 self.status_var.set("Processing complete")
328 messagebox.showinfo("Success", "Symbols replaced successfully!")
329
330 def start_saving(self):
331 """Start saving in a separate thread"""
332 if not (hasattr(self, 'processed_data') or hasattr(self, 'processed_file_path')):
333 messagebox.showwarning("Warning", "No processed content to save. Please
process a file first.")
334 return
335
336 save_path = filedialog.asksaveasfilename(
337 title="Save As",
338 defaultextension=".txt",
339 filetypes=(("Text files", "*.txt"), ("All files", "*.*"))
340 )
341
342 if save_path:
343 # Disable buttons during saving
344 self.process_button.config(state=tk.DISABLED)
345 self.save_button.config(state=tk.DISABLED)
346
347 self.status_var.set("Saving file...")
348 self.progress["value"] = 0
349 self.root.update_idletasks()
350
351 # Start saving thread
352 threading.Thread(target=lambda: self.save_as(save_path), daemon=True).start()
353
354 def save_as(self, save_path):
355 try:
356 start_time = time.time()
357
358 # Check if we're using a temporary file (for large files)
359 if hasattr(self, 'using_temp_file') and self.using_temp_file:
360 # For large files, just rename/move the temp file
361 # This is extremely fast as it avoids copying the data
362 if os.path.exists(save_path):
363 os.remove(save_path) # Remove existing file if it exists
364
365 # On Windows, we need to use os.rename
366 # On Unix, we could use shutil.move
367 os.rename(self.processed_file_path, save_path)
368
369 # Update progress immediately
370 self.root.after(0, lambda: self.update_save_progress(100, save_path))
371 else:
372 # For small files, write the data directly
373 with open(save_path, 'wb') as f:
374 f.write(self.processed_data)
375 self.root.after(0, lambda: self.update_save_progress(100, save_path))
376
377 elapsed = time.time() - start_time
378 print(f"Saving completed in {elapsed:.2f} seconds")
379
380 except Exception as e:
381 self.root.after(0, lambda: self.show_error(f"Failed to save file: {str(e)}"))
382
383 def update_save_progress(self, value, save_path):
384 """Update progress bar during save"""
385 self.progress["value"] = value
386
387 if value >= 100:
388 self.status_var.set(f"Saved to: {save_path}")
389 # Re-enable buttons
390 self.process_button.config(state=tk.NORMAL)
391 self.save_button.config(state=tk.NORMAL)
392 messagebox.showinfo("Success", f"File saved successfully to {save_path}")
393
394 def show_error(self, message):
395 """Show error message and reset UI"""
396 messagebox.showerror("Error", message)
397 self.status_var.set("Error")
398 # Re-enable buttons
399 self.process_button.config(state=tk.NORMAL)
400 self.save_button.config(state=tk.NORMAL)
401
402 # Clean up temporary file if it exists
403 if hasattr(self, 'processed_file_path') and hasattr(self, 'using_temp_file'):
404 if self.using_temp_file and os.path.exists(self.processed_file_path):
405 try:
406 os.remove(self.processed_file_path)
407 except:
408 pass
409
410 def show_help(self):
411 """Display help information"""
412 help_text = """Hex Symbol Replacer
413
414 This tool replaces control characters and other special bytes with their hexadecimal
415 representation in angle brackets.
416
417 For example:
418 - ASCII 0x1C (File Separator) becomes <1c>
419 - ASCII 0x0E (Shift Out) becomes <0e>
420 - ASCII 0x1D (Group Separator) becomes <1d>
421
422 How to use:
423 1. Click "Browse..." to select a file
424 2. Choose "Text View" or "Hex View" to see the original content
425 3. Click "Process File" to replace special characters
426 4. Click "Save As..." to save the processed content to a new file
427
428 Performance Tips:
429 - The tool uses an ultra-fast method for large files
430 - Only a preview is shown in the interface for large files
431 - The full file will be processed and saved correctly
432
433 Note: This tool handles binary files and preserves all non-control characters.
434 """
435 help_window = tk.Toplevel(self.root)
436 help_window.title("Help")
437 help_window.geometry("500x400")
438
439 help_text_widget = scrolledtext.ScrolledText(help_window, wrap=tk.WORD)
440 help_text_widget.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
441 help_text_widget.insert(tk.END, help_text)
442 help_text_widget.config(state=tk.DISABLED)
443
444 if __name__ == "__main__":
445 root = tk.Tk()
446 app = HexSymbolReplacerApp(root)
447 root.mainloop()
448