Redesign UI with modern dark theme and improved UX#1
Redesign UI with modern dark theme and improved UX#1DevStrategist wants to merge 1 commit intomainfrom
Conversation
- Implement modern dark color scheme (Tokyo Night inspired) - Add custom styled widgets: ModernButton, ModernSpinbox, CardFrame - Create compact, no-scroll layout for all controls - Add labeled time inputs (Hours, Min, Sec, Ms) for clarity - Make preset buttons additive (adjust values instead of replacing) - Add syntax highlighting for timestamps in preview - Add line numbers to preview panel - Include status bar with keyboard shortcut hints - Add tooltips to all buttons - Improve sync dialog with proper sizing and "Calculate" button - Add keyboard shortcuts (Cmd+O, Cmd+S, Cmd+Shift+A) - Show subtitle count in status bar after file load - Add Reset button to restore original content before saving
WalkthroughThe subtitle_sync.py file receives a complete UI/UX overhaul, transitioning from a ttk-based interface to a modern dark-themed design. Six new custom widget classes (Colors, Tooltip, ModernButton, ModernSpinbox, CardFrame, StatusBar) are introduced, and the SRTShifter class is heavily refactored to incorporate a new layout with panel-based UI construction, file loading with syntax highlighting, timestamp preview shifting, keyboard shortcuts, and enhanced status feedback. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as SRTShifter UI
participant SRTShifter as SRTShifter Class
participant FileSystem as File System
participant Preview as Preview Engine
User->>UI: Click Browse/Load File
UI->>SRTShifter: browse_file()
SRTShifter->>FileSystem: Read SRT File
FileSystem-->>SRTShifter: File Content
SRTShifter->>SRTShifter: highlight_syntax()
SRTShifter->>SRTShifter: update_line_numbers()
SRTShifter->>UI: Update Display + Count Subtitles
UI-->>User: File Loaded, UI Enabled
User->>UI: Enter Time Shift Value
UI->>SRTShifter: preview_shift(shift_value)
SRTShifter->>Preview: shift_timestamp() for all timestamps
Preview-->>SRTShifter: Shifted Content
SRTShifter->>UI: Update Preview Panel
UI-->>User: Preview Updated
User->>UI: Click Apply/Save
UI->>SRTShifter: show_sync_dialog()
SRTShifter->>FileSystem: Write Shifted SRT
FileSystem-->>SRTShifter: File Saved
SRTShifter->>UI: StatusBar: Success Message
UI-->>User: Save Complete
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
subtitle_sync.py (1)
1063-1119: Preview shift implementation is correct; minor robustness improvements possibleThe core shifting logic is sound:
- Shift magnitude comes from the time fields; sign from
direction_var.- You re-read the original file each time, so shifts are not compounded.
shift_timestampclamps negative results to zero.- Highlighting and line numbers are refreshed correctly, and Save/Reset are enabled only after a successful preview.
Two minor improvement ideas:
- The broad
except Exceptioncould be narrowed to expected errors (e.g.,OSError,ValueError) so true programming errors propagate during development.- For very large SRTs, consider streaming or line-wise processing to avoid holding the entire file in memory twice, though this is likely fine for most real-world subtitle sizes.
🧹 Nitpick comments (7)
subtitle_sync.py (7)
43-86: Tooltip scheduling is generally fine; optional cleanup of pendingaftercallbacksThe tooltip logic works, but
schedule_showdoesn’t cancel any existing pendingafterjob. It’s harmless (extra callbacks just early-return), but you could optionally clear any previousscheduled_idbefore scheduling a new one to avoid redundant callbacks and keep the state tighter.
91-220: Custom button looks good; clarifyconfig/configureusage for future callersThe
ModernButtonbehavior and styling look solid. The only caveat is thatconfig/configureare overridden in a way that treatsstatespecially and may ignore additional options whenstateis present (e.g.,config(state=tk.DISABLED, text="X")wouldn’t passtextthrough). That’s fine for current usage (you only passstate), but it’s worth documenting or refactoring to support mixed keyword updates if this widget is reused more broadly.
225-311: Unusedwidthargument inModernSpinbox.__init__
widthis accepted but never used. Either wire it into the entry/overall layout or drop the parameter to avoid confusion and satisfy linting.
241-247: Unicode minus sign may confuse tooling and future editsYou’re using the Unicode minus sign
−in several labels/strings. Visually it’s nice, but it triggers linters and can be hard to distinguish from-in editors/diffs.Consider standardizing on ASCII
-in source and, if you really want the typographic minus, generating it at runtime or centralizing it in a constant so it’s easier to grep/change later.Also applies to: 503-507, 528-535, 818-820, 1031-1033
753-760: Shortcuts are macOS-centric; consider Control-based bindings tooKey bindings use
<Command-*>, which is perfect on macOS but will be inert on most Windows/Linux setups.If you expect cross-platform use, consider also binding the Control variants (and updating the status bar hint accordingly), for example:
self.root.bind("<Control-o>", lambda e: self.browse_file()) self.root.bind("<Control-s>", lambda e: self.save_file()) self.root.bind("<Control-Shift-a>", lambda e: self.preview_shift())
924-1035: Sync dialog and shift calculation look correct; ms handling for negative deltas is a bit opaqueThe sync dialog UX and the resulting shift computation are solid:
parse_timestamptotimedelta, thenshift_delta = new_td - current_tdis correct.- Direction (
+/-) is derived fromshift_delta.total_seconds().- The decomposition into hours/minutes/seconds plus milliseconds correctly handles negative timedeltas by using
abs(total_seconds)for the h/m/s and the microsecond trick for ms.The milliseconds expression for negative deltas:
milliseconds = int( (shift_delta.microseconds // 1000) if shift_delta.total_seconds() >= 0 else (1000 - (shift_delta.microseconds // 1000)) % 1000 )is mathematically correct but non-obvious. A short comment explaining why the negative branch is needed (Python’s internal representation of negative timedeltas) would help future maintainers.
1178-1187: Bareexceptaroundiconbitmap
iconbitmap('')is wrapped in a bareexceptthat ignores all errors. Similar to the scaling block, consider at least catchingtk.TclErrorexplicitly or skipping the call entirely if there’s no valid icon path yet.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
subtitle_sync.py(5 hunks)
🧰 Additional context used
🪛 Ruff (0.14.8)
subtitle_sync.py
56-56: Unused method argument: event
(ARG002)
59-59: Unused method argument: event
(ARG002)
79-79: Unused method argument: event
(ARG002)
188-188: Unused method argument: event
(ARG002)
193-193: Unused method argument: event
(ARG002)
197-197: Unused method argument: event
(ARG002)
202-202: Unused method argument: event
(ARG002)
227-227: Unused method argument: width
(ARG002)
241-241: String contains ambiguous − (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?
(RUF001)
246-246: Unused lambda argument: e
(ARG005)
247-247: Unused lambda argument: e
(ARG005)
263-263: Unused lambda argument: e
(ARG005)
264-264: Unused lambda argument: e
(ARG005)
274-274: Unused method argument: event
(ARG002)
277-277: Unused method argument: event
(ARG002)
289-289: Unused method argument: event
(ARG002)
297-297: Unused method argument: event
(ARG002)
402-402: Do not use bare except
(E722)
402-403: try-except-pass detected, consider logging the exception
(S110)
501-501: Unused lambda argument: e
(ARG005)
503-503: String contains ambiguous − (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?
(RUF001)
508-508: Unused lambda argument: e
(ARG005)
528-528: String contains ambiguous − (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?
(RUF001)
528-528: String contains ambiguous − (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?
(RUF001)
533-533: Unused lambda argument: e
(ARG005)
534-534: Unused lambda argument: e
(ARG005)
535-535: Unused lambda argument: e
(ARG005)
588-588: Unused function argument: event
(ARG001)
715-715: Unused method argument: event
(ARG002)
755-755: Unused lambda argument: e
(ARG005)
756-756: Unused lambda argument: e
(ARG005)
757-757: Unused lambda argument: e
(ARG005)
758-758: Unused lambda argument: e
(ARG005)
759-759: Unused lambda argument: e
(ARG005)
818-818: String contains ambiguous − (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?
(RUF001)
878-878: Do not catch blind exception: Exception
(BLE001)
879-879: Use explicit conversion flag
Replace with conversion flag
(RUF010)
919-919: Do not catch blind exception: Exception
(BLE001)
920-920: Use explicit conversion flag
Replace with conversion flag
(RUF010)
999-999: Unused lambda argument: e
(ARG005)
1000-1000: Unused lambda argument: e
(ARG005)
1031-1031: String contains ambiguous − (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?
(RUF001)
1120-1120: Do not catch blind exception: Exception
(BLE001)
1121-1121: Use explicit conversion flag
Replace with conversion flag
(RUF010)
1161-1161: Do not catch blind exception: Exception
(BLE001)
1162-1162: Use explicit conversion flag
Replace with conversion flag
(RUF010)
1184-1184: Do not use bare except
(E722)
1184-1185: try-except-pass detected, consider logging the exception
(S110)
🔇 Additional comments (7)
subtitle_sync.py (7)
342-383: Status bar implementation matches PR goalsThe
StatusBarencapsulation and itsset_status/set_infoAPI are clean and make it straightforward to drive state from the rest of the app. This aligns nicely with the “status bar with state + shortcuts” objective.
399-403: Avoid bareexceptaround scaling callThe HiDPI scaling block uses a bare
except: pass. It will silently swallow any exception, including unexpected programming errors.Consider narrowing this to
tk.TclError(or at least logging the exception) so genuine issues aren’t hidden:- try: - self.root.tk.call('tk', 'scaling', 2.0) # HiDPI support - except: - pass + try: + self.root.tk.call("tk", "scaling", 2.0) # HiDPI support + except tk.TclError: + # Scaling not supported on this platform/installation + pass[ suggest_recommended_refactor ]
456-568: Left panel/layout and button enablement look solidThe left panel composition, initial disabled state for Apply/Save/Reset, and enabling logic in
browse_fileare coherent and prevent invalid actions before a file is loaded. This matches the compact, no-scroll controls requirement well.
569-598: Time input validation is robustThe
create_labeled_time_inputhelper withFocusOutclamping to[min_val, max_val]provides good protection against invalid values. Combined with the stringvars, this keeps the shift controls resilient.
715-752: Line-number and syntax-highlighting implementation is correct and efficient enough for typical SRT sizesThe
update_line_numbersandhighlight_syntaxfunctions correctly map regex match offsets to Tk text indices using"1.0 + N chars", and they handle timestamps, arrows, and numeric cue indices cleanly. This should give a nice preview experience without correctness issues for normal-sized subtitle files.
775-821: Preset logic correctly applies additive deltas regardless of directionThe
apply_presetimplementation is nicely thought through: computingnew_total_ms = current_total_ms + (delta_ms * direction)and flipping direction when crossing zero ensures that pressing+1sor−1salways adjusts the actual shift by that amount, independent of the current sign. Hours/minutes/seconds/ms recomposition is also correct and keeps values in sensible ranges.
885-923:sync_selected_lineflow is safe and user-friendlyThe selection handling, validation of a timestamp line, and graceful warnings for “no selection” / “invalid selection” are well done. Temporarily toggling the text widget to
NORMALand ensuring it’s set back toDISABLEDon all early-returns avoids leaving the preview editable.
| def create_right_panel(self, parent): | ||
| """Create the right preview panel""" | ||
| right_panel = tk.Frame(parent, bg=Colors.BG_DARK) | ||
| right_panel.grid(row=0, column=1, sticky="nsew") | ||
| right_panel.rowconfigure(1, weight=1) | ||
| right_panel.columnconfigure(0, weight=1) | ||
|
|
||
| # Title and sync button for content panel | ||
| content_header = ttk.Frame(right_panel) | ||
| content_header.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10)) | ||
| content_header.columnconfigure(0, weight=1) | ||
|
|
||
| content_title = ttk.Label(content_header, text="SRT Content Preview", | ||
| font=("Arial", 12, "bold")) | ||
| content_title.grid(row=0, column=0, sticky=tk.W) | ||
|
|
||
| self.sync_btn = ttk.Button(content_header, text="Sync Selected Line", | ||
| command=self.sync_selected_line, state=tk.DISABLED) | ||
| self.sync_btn.grid(row=0, column=1, padx=(10, 0)) | ||
|
|
||
| # Text widget with scrollbar | ||
| text_frame = ttk.Frame(right_panel) | ||
| text_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) | ||
| text_frame.columnconfigure(0, weight=1) | ||
| text_frame.rowconfigure(0, weight=1) | ||
|
|
||
| # Scrollbar | ||
| scrollbar = ttk.Scrollbar(text_frame) | ||
| scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S)) | ||
|
|
||
| # Text widget | ||
| self.content_text = tk.Text(text_frame, wrap=tk.WORD, | ||
| yscrollcommand=scrollbar.set, | ||
| font=("Courier", 10), | ||
| bg="#f5f5f5", fg="#1a1a1a", relief=tk.SUNKEN, | ||
| padx=10, pady=10) | ||
| self.content_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) | ||
| scrollbar.config(command=self.content_text.yview) | ||
|
|
||
| # Make text selectable | ||
| # Header | ||
| header = tk.Frame(right_panel, bg=Colors.BG_DARK) | ||
| header.grid(row=0, column=0, sticky="ew", pady=(0, 10)) | ||
|
|
||
| tk.Label(header, text="Preview", font=("SF Pro Display", 12, "bold"), | ||
| bg=Colors.BG_DARK, fg=Colors.TEXT_PRIMARY).pack(side=tk.LEFT) | ||
|
|
||
| self.sync_btn = ModernButton(header, text="Sync Line", | ||
| command=self.sync_selected_line, width=100, | ||
| style="secondary", tooltip="Sync selected timestamp") | ||
| self.sync_btn.pack(side=tk.RIGHT) | ||
| self.sync_btn.config(state=tk.DISABLED) | ||
|
|
||
| # Text area container | ||
| text_container = tk.Frame(right_panel, bg=Colors.BG_MEDIUM, | ||
| highlightbackground=Colors.BORDER, | ||
| highlightthickness=1) | ||
| text_container.grid(row=1, column=0, sticky="nsew") | ||
| text_container.rowconfigure(0, weight=1) | ||
| text_container.columnconfigure(0, weight=1) | ||
|
|
||
| # Line numbers | ||
| self.line_numbers = tk.Text(text_container, width=4, font=("SF Mono", 11), | ||
| bg=Colors.BG_LIGHT, fg=Colors.TEXT_SECONDARY, | ||
| relief=tk.FLAT, padx=8, pady=12, state=tk.DISABLED, | ||
| cursor="arrow", highlightthickness=0) | ||
| self.line_numbers.grid(row=0, column=0, sticky="ns") | ||
|
|
||
| # Main text widget | ||
| self.content_text = tk.Text(text_container, wrap=tk.WORD, | ||
| font=("SF Mono", 11), bg=Colors.BG_MEDIUM, | ||
| fg=Colors.TEXT_PRIMARY, relief=tk.FLAT, | ||
| padx=12, pady=12, insertbackground=Colors.TEXT_PRIMARY, | ||
| selectbackground=Colors.ACCENT_PRIMARY, | ||
| selectforeground=Colors.BG_DARK, | ||
| highlightthickness=0) | ||
| self.content_text.grid(row=0, column=1, sticky="nsew") | ||
|
|
||
| # Vertical scrollbar only | ||
| v_scrollbar = tk.Scrollbar(text_container, orient=tk.VERTICAL, | ||
| command=self.on_scroll_y) | ||
| v_scrollbar.grid(row=0, column=2, sticky="ns") | ||
|
|
||
| self.content_text.config(yscrollcommand=v_scrollbar.set) | ||
|
|
||
| # Configure tags for syntax highlighting | ||
| self.content_text.tag_configure("timestamp", | ||
| foreground=Colors.TIMESTAMP_FG, | ||
| font=("SF Mono", 11, "bold")) | ||
| self.content_text.tag_configure("number", | ||
| foreground=Colors.ACCENT_PURPLE) | ||
| self.content_text.tag_configure("arrow", | ||
| foreground=Colors.TEXT_SECONDARY) | ||
|
|
||
| # Bind events | ||
| self.content_text.bind("<<Selection>>", self.on_text_selection) | ||
| self.content_text.bind("<KeyRelease>", self.update_line_numbers) | ||
| self.content_text.bind("<MouseWheel>", self.on_mousewheel) | ||
|
|
||
| # Initial message | ||
| self.content_text.insert("1.0", "No file loaded.\n\nSelect an SRT file to view its content here.") | ||
| self.show_welcome_message() | ||
|
|
There was a problem hiding this comment.
Welcome message claims drag-and-drop support that isn’t implemented
The welcome copy advertises “Click \"Browse Files\" or drop an SRT file” and “Click \"Apply Shift\" to preview”, but:
- The actual button labels are “Browse” and “Apply”.
- There’s no drag-and-drop handling implemented anywhere.
This can confuse users and QA following the instructions. I’d recommend aligning the text with the actual controls and either removing or implementing drag-and-drop.
- 1. Click "Browse Files" or drop an SRT file
+ 1. Click "Browse" to open an SRT file
- 3. Click "Apply Shift" to preview
+ 3. Click "Apply" to preview the shiftCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In subtitle_sync.py around lines 598 to 666, the welcome text in
show_welcome_message advertises “Browse Files”, “Apply Shift”, and drag-and-drop
support which don’t match the UI: the buttons are labeled “Browse” and “Apply”
and there's no drag-and-drop handling. Fix by updating the welcome copy to
exactly reference the existing controls (use "Browse" and "Apply") and remove
any mention of drag-and-drop, or alternatively implement drag-and-drop support
(e.g., add platform-appropriate DnD bindings or tkdnd integration, handle
dropped file paths, validate SRT, and call the same load logic as the Browse
button). Ensure the final text matches the chosen approach and tests open a file
via the Browse button and, if added, via DnD.
| def reset_preview(self): | ||
| """Reset to original content""" | ||
| if self.original_content: | ||
| self.content_text.config(state=tk.NORMAL) | ||
| self.content_text.delete("1.0", tk.END) | ||
| self.content_text.insert("1.0", self.original_content) | ||
| self.highlight_syntax() | ||
| self.content_text.config(state=tk.DISABLED) | ||
| self.update_line_numbers() | ||
|
|
||
| self.shifted_content = None | ||
| self.save_btn.config(state=tk.DISABLED) | ||
| self.reset_btn.config(state=tk.DISABLED) | ||
|
|
||
| self.status_bar.set_status("Preview reset to original", "info") | ||
|
|
||
| def save_file(self): | ||
| """Save the shifted content to a file""" | ||
| if not self.shifted_content: | ||
| messagebox.showerror("Error", "No shifted content to save. Click 'Apply Shift' first.") | ||
| self.show_error("Error", "No shifted content to save. Click 'Apply Shift' first.") | ||
| return | ||
|
|
||
| try: | ||
| # Ask where to save | ||
| save_path = filedialog.asksaveasfilename( | ||
| title="Save Shifted SRT File", | ||
| defaultextension=".srt", | ||
| initialfile=os.path.basename(self.file_path).replace('.srt', '_shifted.srt'), | ||
| initialfile=os.path.basename(self.file_path).replace('.srt', '_synced.srt'), | ||
| filetypes=[("SRT files", "*.srt"), ("All files", "*.*")] | ||
| ) | ||
|
|
||
| if save_path: | ||
| with open(save_path, 'w', encoding='utf-8') as f: | ||
| f.write(self.shifted_content) | ||
|
|
||
| messagebox.showinfo("Success", | ||
| f"File saved successfully!\n\nSaved to: {os.path.basename(save_path)}") | ||
| self.status_bar.set_status(f"Saved: {os.path.basename(save_path)}", "success") | ||
| self.show_success("Success", f"File saved successfully!\n\n{os.path.basename(save_path)}") | ||
|
|
||
| except Exception as e: | ||
| messagebox.showerror("Error", f"An error occurred:\n{str(e)}") | ||
| self.show_error("Error", f"An error occurred:\n{str(e)}") | ||
|
|
There was a problem hiding this comment.
Reset and save flows behave correctly; tighten error handling and message text
Behavior-wise this section looks good:
reset_previewrestores the original content, reapplies highlighting, disables Save/Reset, and clearsshifted_content.save_fileguards against saving before a preview and uses a sensible default filename with_synced.
Two nits:
- The “No shifted content to save. Click 'Apply Shift' first.” message is slightly out of sync with the button text (“Apply”). Consider updating the copy.
- As above, the
except Exception as eblocks could be narrowed and/or logged so unexpected programming errors aren’t silently wrapped as generic runtime errors.
- self.show_error("Error", "No shifted content to save. Click 'Apply Shift' first.")
+ self.show_error("Error", "No shifted content to save. Click 'Apply' first.")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def reset_preview(self): | |
| """Reset to original content""" | |
| if self.original_content: | |
| self.content_text.config(state=tk.NORMAL) | |
| self.content_text.delete("1.0", tk.END) | |
| self.content_text.insert("1.0", self.original_content) | |
| self.highlight_syntax() | |
| self.content_text.config(state=tk.DISABLED) | |
| self.update_line_numbers() | |
| self.shifted_content = None | |
| self.save_btn.config(state=tk.DISABLED) | |
| self.reset_btn.config(state=tk.DISABLED) | |
| self.status_bar.set_status("Preview reset to original", "info") | |
| def save_file(self): | |
| """Save the shifted content to a file""" | |
| if not self.shifted_content: | |
| messagebox.showerror("Error", "No shifted content to save. Click 'Apply Shift' first.") | |
| self.show_error("Error", "No shifted content to save. Click 'Apply Shift' first.") | |
| return | |
| try: | |
| # Ask where to save | |
| save_path = filedialog.asksaveasfilename( | |
| title="Save Shifted SRT File", | |
| defaultextension=".srt", | |
| initialfile=os.path.basename(self.file_path).replace('.srt', '_shifted.srt'), | |
| initialfile=os.path.basename(self.file_path).replace('.srt', '_synced.srt'), | |
| filetypes=[("SRT files", "*.srt"), ("All files", "*.*")] | |
| ) | |
| if save_path: | |
| with open(save_path, 'w', encoding='utf-8') as f: | |
| f.write(self.shifted_content) | |
| messagebox.showinfo("Success", | |
| f"File saved successfully!\n\nSaved to: {os.path.basename(save_path)}") | |
| self.status_bar.set_status(f"Saved: {os.path.basename(save_path)}", "success") | |
| self.show_success("Success", f"File saved successfully!\n\n{os.path.basename(save_path)}") | |
| except Exception as e: | |
| messagebox.showerror("Error", f"An error occurred:\n{str(e)}") | |
| self.show_error("Error", f"An error occurred:\n{str(e)}") | |
| def reset_preview(self): | |
| """Reset to original content""" | |
| if self.original_content: | |
| self.content_text.config(state=tk.NORMAL) | |
| self.content_text.delete("1.0", tk.END) | |
| self.content_text.insert("1.0", self.original_content) | |
| self.highlight_syntax() | |
| self.content_text.config(state=tk.DISABLED) | |
| self.update_line_numbers() | |
| self.shifted_content = None | |
| self.save_btn.config(state=tk.DISABLED) | |
| self.reset_btn.config(state=tk.DISABLED) | |
| self.status_bar.set_status("Preview reset to original", "info") | |
| def save_file(self): | |
| """Save the shifted content to a file""" | |
| if not self.shifted_content: | |
| self.show_error("Error", "No shifted content to save. Click 'Apply' first.") | |
| return | |
| try: | |
| save_path = filedialog.asksaveasfilename( | |
| title="Save Shifted SRT File", | |
| defaultextension=".srt", | |
| initialfile=os.path.basename(self.file_path).replace('.srt', '_synced.srt'), | |
| filetypes=[("SRT files", "*.srt"), ("All files", "*.*")] | |
| ) | |
| if save_path: | |
| with open(save_path, 'w', encoding='utf-8') as f: | |
| f.write(self.shifted_content) | |
| self.status_bar.set_status(f"Saved: {os.path.basename(save_path)}", "success") | |
| self.show_success("Success", f"File saved successfully!\n\n{os.path.basename(save_path)}") | |
| except Exception as e: | |
| self.show_error("Error", f"An error occurred:\n{str(e)}") |
🧰 Tools
🪛 Ruff (0.14.8)
1161-1161: Do not catch blind exception: Exception
(BLE001)
1162-1162: Use explicit conversion flag
Replace with conversion flag
(RUF010)
🤖 Prompt for AI Agents
In subtitle_sync.py around lines 1124 to 1163, update the save failure message
to match the UI button text by changing "No shifted content to save. Click
'Apply Shift' first." to "No shifted content to save. Click 'Apply' first.";
also tighten the exception handling in save_file by catching file/IO related
errors (e.g., OSError, IOError) to show user-facing error messages and log them,
and handle unexpected exceptions separately (log the full exception and re-raise
or re-show a generic error) so programming errors are not silently swallowed.
|
Note Docstrings generation - SUCCESS |
Docstrings generation was requested by @DevStrategist. * #1 (comment) The following files were modified: * `subtitle_sync.py`
Summary
Changes
Visual Design
UX Improvements
Technical
Test plan
Summary by CodeRabbit
New Features
User Experience
✏️ Tip: You can customize this high-level summary in your review settings.