diff --git a/video_concat.py b/video_concat.py index 279071b..d21fa61 100755 --- a/video_concat.py +++ b/video_concat.py @@ -316,7 +316,7 @@ class App(_BaseClass): # type: ignore[valid-type] # Name row r1 = ctk.CTkFrame(body, fg_color="transparent") - r1.pack(fill="x", pady=(0, 8)) + r1.pack(fill="x", pady=(0, 4)) ctk.CTkLabel(r1, text="File name:", width=96, anchor="w").pack(side="left") ctk.CTkEntry(r1, textvariable=self.out_name_var).pack(side="left", fill="x", expand=True) ctk.CTkOptionMenu( @@ -324,6 +324,24 @@ class App(_BaseClass): # type: ignore[valid-type] variable=self.out_ext_var, width=84 ).pack(side="left", padx=(8, 0)) + # Auto-rename button — converts BiliBili Live filename to readable format + self.rename_btn = ctk.CTkButton( + r1, text="✨ Rename", width=110, + fg_color=("#EDE9FE", "#3B1F6E"), + text_color=("#5B21B6", "#C4B5FD"), + hover_color=("#DDD6FE", "#4C2889"), + command=self._apply_bililive_rename + ) + self.rename_btn.pack(side="left", padx=(8, 0)) + + # Preview label: shows what the rename will produce before applying + self.rename_preview_lbl = ctk.CTkLabel( + body, text="", + font=ctk.CTkFont(size=11), text_color=("#7C3AED", "#A78BFA"), + anchor="w" + ) + self.rename_preview_lbl.pack(fill="x", pady=(0, 4)) + # Directory row r2 = ctk.CTkFrame(body, fg_color="transparent") r2.pack(fill="x") @@ -504,8 +522,61 @@ class App(_BaseClass): # type: ignore[valid-type] self.out_path_lbl.configure( text=f"Output path: {out}" if out else "Output path: —" ) + self._refresh_rename_preview() self._refresh_start() + def _refresh_rename_preview(self): + """Show a live preview of what the rename button will produce.""" + if not hasattr(self, "rename_preview_lbl"): + return + current = self.out_name_var.get().strip() + renamed = self._bililive_rename(current) + if renamed and renamed != current: + self.rename_preview_lbl.configure( + text=f" → {renamed}" + ) + self.rename_btn.configure(state="normal") + else: + self.rename_preview_lbl.configure(text="") + # Only disable if the name cannot be converted + self.rename_btn.configure(state="disabled" if not renamed else "normal") + + + # ══════════════════════════════════════════════════════════════════════════ + # BiliBili Live filename auto-rename + # ══════════════════════════════════════════════════════════════════════════ + + # Pattern: [2025-06-28 20-31-15][纳豆nado][title content] + _BILILIVE_RE = re.compile( + r"^\[(\d{4})-(\d{2})-(\d{2}) \d{2}-\d{2}-\d{2}\]\[[^\]]*\]\[(.+)\]$" + ) + + @staticmethod + def _bililive_rename(stem: str) -> str: + """ + Convert a BiliBili Live recording stem to a readable name. + Input: [2026-03-14 22-46-37][纳豆nado][【点歌专场】今晚九点来捏] + Output: 2026年03月14日【直播回放】【点歌专场】今晚九点来捏 + Returns the converted string, or "" if the stem does not match. + """ + # Strip any trailing _concat suffix before matching + test = stem.removesuffix("_concat").strip() + m = App._BILILIVE_RE.match(test) + if not m: + return "" + year, month, day, title = m.groups() + return f"{year}年{month}月{day}日【直播回放】{title}" + + def _apply_bililive_rename(self): + """Apply the rename conversion to the current output name.""" + current = self.out_name_var.get().strip() + renamed = self._bililive_rename(current) + if not renamed: + return + self.out_name_var.set(renamed) + self.rename_preview_lbl.configure(text=" ✓ Renamed!") + self.rename_btn.configure(state="disabled") + def _refresh_start(self): if not hasattr(self, "start_btn"): return # UI not fully built yet @@ -572,7 +643,8 @@ class App(_BaseClass): # type: ignore[valid-type] if not self.out_dir_var.get().strip(): self.out_dir_var.set(str(Path(self.items[0]["path"]).parent)) if self.out_name_var.get() in ("", "output_concat"): - self.out_name_var.set(Path(self.items[0]["path"]).stem + "_concat") + # Keep the raw BiliBili stem so the rename pattern can match it + self.out_name_var.set(Path(self.items[0]["path"]).stem) self._rebuild_list() self._update_count()