Python版本 """ 基于音节/字数精确分配时间 """ import re import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext # ==================== 核心逻辑 ==================== def count_english_syllables(word): w = word.strip('.,;:!?"\'()[]{}').lower() if not w: return 0 vowels = set("aeiouy") count = 0 prev_is_vowel = False for ch in w: is_vowel = ch in vowels if is_vowel and not prev_is_vowel: count += 1 prev_is_vowel = is_vowel if w.endswith("e") and not w.endswith("le") and count > 1: count -= 1 if w.endswith("le") and len(w) > 2 and w[-3] not in vowels: count += 1 return max(count, 1) def count_pronunciation_units(text): total = 0 tokens = re.findall(r'[\u4e00-\u9fff]|[a-zA-Z]+|\d+%?|[^\s]', text) for token in tokens: if re.match(r'[\u4e00-\u9fff]', token): total += 1 elif re.match(r'[a-zA-Z]+', token): total += count_english_syllables(token) elif re.match(r'\d+%?', token): digits = token.replace('%', '') total += min(len(digits) + 1, 5) return total def split_sentences(text): parts = re.split(r'(?<=[.,;])\s+', text) return [p.strip() for p in parts if p.strip()] def ms_to_parts(ms): total_sec = ms / 1000 m = int(total_sec // 60) s = total_sec - m * 60 return m, s def fmt_lyrics3(ms): m, s = ms_to_parts(ms) return f"[{m:02d}:{s:05.2f}]" def fmt_srt(ms): m, s = ms_to_parts(ms) return f"{m:02d}:{s:05.2f}".replace(".", ",") def fmt_ass(ms): m, s = ms_to_parts(ms) h = m // 60 m = m % 60 return f"{h}:{m:02d}:{s:05.2f}" def fmt_vtt(ms): m, s = ms_to_parts(ms) return f"{m:02d}:{s:06.3f}" def generate_subtitles(text, total_seconds, fmt="lyrics3"): total_ms = float(total_seconds) * 1000 sentences = split_sentences(text) if not sentences: return "", 0 units_per_sentence = [count_pronunciation_units(s) for s in sentences] total_units = sum(units_per_sentence) if total_units == 0: return "", 0 ms_per_unit = total_ms / total_units timings = [] cur = 0.0 for i, s in enumerate(sentences): dur = units_per_sentence[i] * ms_per_unit timings.append((cur, cur + dur, s)) cur += dur formatters = {"lyrics3": fmt_lyrics3, "srt": fmt_srt, "ass": fmt_ass, "vtt": fmt_vtt} f = formatters.get(fmt, fmt_lyrics3) lines = [] if fmt == "vtt": lines.append("WEBVTT\n") elif fmt == "ass": lines.append("[Script Info]") lines.append("ScriptType: v4.00+") lines.append("PlayResX: 384") lines.append("PlayResY: 288\n") lines.append("[V4+ Styles]") lines.append("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding") lines.append("Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1\n") lines.append("[Events]") lines.append("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text") for i, (start, end, sent) in enumerate(timings): if fmt == "srt": lines.append(str(i + 1)) lines.append(f"{f(start)} --> {f(end)}") lines.append(sent) lines.append("") elif fmt == "ass": lines.append(f"Dialogue: 0,{f(start)},{f(end)},Default,,0,0,0,,{sent}") elif fmt == "vtt": lines.append(f"{f(start)} --> {f(end)}") lines.append(sent) lines.append("") else: lines.append(f"{f(start)}{sent}") return "\n".join(lines), len(sentences) # ==================== GUI ==================== class SubtitleApp: def __init__(self, root): self.root = root self.root.title("字幕时间轴生成器") self.root.geometry("900x720") self.root.configure(bg="#1e1e2e") self.root.minsize(700, 550) # 样式 style = ttk.Style() style.theme_use("clam") style.configure("TFrame", background="#1e1e2e") style.configure("TLabel", background="#1e1e2e", foreground="#cdd6f4", font=("Segoe UI", 11)) style.configure("TButton", background="#45475a", foreground="#cdd6f4", font=("Segoe UI", 10), borderwidth=0, padding=8) style.map("TButton", background=[("active", "#585b70")]) style.configure("TEntry", fieldbackground="#313244", foreground="#cdd6f4", font=("Segoe UI", 11), padding=6) style.configure("TCombobox", fieldbackground="#313244", foreground="#cdd6f4", font=("Segoe UI", 11), padding=4) self.build_ui() def build_ui(self): # 主容器 main = ttk.Frame(self.root, padding=20) main.pack(fill="both", expand=True) # 标题 title = tk.Label(main, text="🎬 字幕时间轴生成器", font=("Segoe UI", 20, "bold"), bg="#1e1e2e", fg="#cba6f7") title.pack(pady=(0, 5)) subtitle = tk.Label(main, text="基于音节/字数精确分配时间 · 支持中英混合", font=("Segoe UI", 10), bg="#1e1e2e", fg="#6c7086") subtitle.pack(pady=(0, 20)) # 输入区域 input_frame = ttk.Frame(main) input_frame.pack(fill="both", expand=True) lbl_input = tk.Label(input_frame, text="📝 输入文本(按句号/逗号/分号自动分句)", font=("Segoe UI", 11, "bold"), bg="#1e1e2e", fg="#a6adc8") lbl_input.pack(anchor="w") self.text_input = scrolledtext.ScrolledText( input_frame, height=10, font=("Segoe UI", 11), bg="#313244", fg="#cdd6f4", insertbackground="#cdd6f4", relief="flat", borderwidth=0, padx=12, pady=12, wrap=tk.WORD, highlightthickness=1, highlightbackground="#45475a" ) self.text_input.pack(fill="both", expand=True, pady=(5, 15)) # 设置行 settings_frame = ttk.Frame(main) settings_frame.pack(fill="x", pady=(0, 15)) # 时长 dur_frame = ttk.Frame(settings_frame) dur_frame.pack(side="left", padx=(0, 30)) ttk.Label(dur_frame, text="⏱ 总时长(秒)").pack(side="left", padx=(0, 8)) self.duration_var = tk.StringVar(value="73") self.duration_entry = ttk.Entry(dur_frame, textvariable=self.duration_var, width=10) self.duration_entry.pack(side="left") # 格式 fmt_frame = ttk.Frame(settings_frame) fmt_frame.pack(side="left", padx=(0, 30)) ttk.Label(fmt_frame, text="📄 输出格式").pack(side="left", padx=(0, 8)) self.format_var = tk.StringVar(value="lyrics3") fmt_combo = ttk.Combobox(fmt_frame, textvariable=self.format_var, values=["lyrics3", "srt", "ass", "vtt"], state="readonly", width=10) fmt_combo.pack(side="left") # 统计 self.stats_var = tk.StringVar(value="分句: 0 句 | 总发音单位: 0") stats_label = tk.Label(settings_frame, textvariable=self.stats_var, font=("Segoe UI", 10), bg="#1e1e2e", fg="#6c7086") stats_label.pack(side="right") # 按钮 btn_frame = ttk.Frame(main) btn_frame.pack(fill="x", pady=(0, 15)) self.btn_generate = tk.Button( btn_frame, text="✨ 生成字幕", font=("Segoe UI", 12, "bold"), bg="#cba6f7", fg="#1e1e2e", activebackground="#b4befe", activeforeground="#1e1e2e", relief="flat", borderwidth=0, padx=24, pady=10, cursor="hand2", command=self.on_generate ) self.btn_generate.pack(side="left", padx=(0, 10)) self.btn_copy = tk.Button( btn_frame, text="📋 复制全部", font=("Segoe UI", 11), bg="#45475a", fg="#cdd6f4", activebackground="#585b70", activeforeground="#cdd6f4", relief="flat", borderwidth=0, padx=20, pady=10, cursor="hand2", command=self.on_copy ) self.btn_copy.pack(side="left", padx=(0, 10)) self.btn_save = tk.Button( btn_frame, text="💾 保存文件", font=("Segoe UI", 11), bg="#45475a", fg="#cdd6f4", activebackground="#585b70", activeforeground="#cdd6f4", relief="flat", borderwidth=0, padx=20, pady=10, cursor="hand2", command=self.on_save ) self.btn_save.pack(side="left") # 输出区域 output_frame = ttk.Frame(main) output_frame.pack(fill="both", expand=True) lbl_output = tk.Label(output_frame, text="📤 生成结果", font=("Segoe UI", 11, "bold"), bg="#1e1e2e", fg="#a6adc8") lbl_output.pack(anchor="w") self.text_output = scrolledtext.ScrolledText( output_frame, height=12, font=("Cascadia Code", 10), bg="#11111b", fg="#a6e3a1", insertbackground="#a6e3a1", relief="flat", borderwidth=0, padx=12, pady=12, wrap=tk.NONE, highlightthickness=1, highlightbackground="#45475a" ) self.text_output.pack(fill="both", expand=True, pady=(5, 0)) def on_generate(self): text = self.text_input.get("1.0", "end-1c").strip() if not text: messagebox.showwarning("提示", "请先输入文本。") return try: duration = float(self.duration_var.get()) if duration <= 0: raise ValueError except ValueError: messagebox.showwarning("提示", "请输入有效的正数时长(秒)。") return fmt = self.format_var.get() result, count = generate_subtitles(text, duration, fmt) self.text_output.delete("1.0", "end") if result: self.text_output.insert("1.0", result) total_units = sum(count_pronunciation_units(s) for s in split_sentences(text)) self.stats_var.set(f"分句: {count} 句 | 总发音单位: {total_units}") def on_copy(self): result = self.text_output.get("1.0", "end-1c").strip() if result: self.root.clipboard_clear() self.root.clipboard_append(result) messagebox.showinfo("提示", "已复制到剪贴板!") def on_save(self): result = self.text_output.get("1.0", "end-1c").strip() if not result: messagebox.showwarning("提示", "没有可保存的内容,请先生成字幕。") return fmt = self.format_var.get() exts = {"lyrics3": ".txt", "srt": ".srt", "ass": ".ass", "vtt": ".vtt"} ext = exts.get(fmt, ".txt") path = filedialog.asksaveasfilename( defaultextension=ext, filetypes=[(f"{fmt.upper()} 字幕", f"*{ext}"), ("所有文件", "*.*")] ) if path: with open(path, "w", encoding="utf-8") as f: f.write(result) messagebox.showinfo("提示", f"已保存到:{path}") if __name__ == "__main__": root = tk.Tk() app = SubtitleApp(root) root.mainloop() HTML版本 3 个帖子 - 3 位参与者 阅读完整话题
用过: typeless (不开会员有使用字数限制,不支持接入其他 api) 闪电说(可以付费也可以自行接入 api) 豆包输入法(免费,但是会替换 mac 系统默认的输入法,我想要的只是语音输入) 以上用的都不太满意 相对比较满意的是闪电说,可以接第三方 api. 但是程序员,最喜欢折腾.我找到了 handy 的项目,这个项目是 mit 协议语音输入软件,仅支持本地模型. 我把这个项目改造了一下,接入了豆包流式语音识别 2.0,并对该模型支持了逐字上屏的功能.目前体验下来识别效果很满意,可以说中英混着说,速度也还可以. 目前我仅编译了 mac 版本,欢迎大家体验,提 PR. 下载地址: https://github.com/LLP2333/Handy/releases 如果安装后无法打开,可以尝试执行 ```bash xattr -cr /Applications/ Handy.app ```
以前代码行数,字数这些指标,虽然有争议,但也有一定道理,大概还是能反映产出的 即使是写垃圾代码垃圾报告,堆字数也要真埋头苦干的 但是现在生成更多产出没代价了,输出指标太容易 Hack 了,随随便便生成几十张 PPT 一堆图,但信息量为 0.真在一线的大家都在用 AI,是怎么做出来的,投入多少精力,传递多少信息一眼就能看出来. 我不想看你的产出,更多的输出不代表有生产力.一张图能讲明白的事情不要放两张图,才是提高生产力 https://blog.yqiao.me/2026/05/show-me-less-output.html
以前代码行数,字数这些指标,虽然有争议,但也有一定道理,大概还是能反映产出的 即使是写垃圾代码垃圾报告,堆字数也要真埋头苦干的 但是现在生成更多产出没代价了,输出指标太容易 Hack 了,随随便便生成几十张 PPT 一堆图,但信息量为 0.真在一线的大家都在用 AI,是怎么做出来的,投入多少精力,传递多少信息一眼就能看出来. 我不想看你的产出,更多的输出不代表有生产力.一张图能讲明白的事情不要放两张图,才是提高生产力 https://blog.yqiao.me/2026/05/show-me-less-output.html
以前代码行数,字数这些指标,虽然有争议,但也有一定道理,大概还是能反映产出的 即使是写垃圾代码垃圾报告,堆字数也要真埋头苦干的 但是现在生成更多产出没代价了,输出指标太容易 Hack 了,随随便便生成几十张 PPT 一堆图,但信息量为 0.真在一线的大家都在用 AI,是怎么做出来的,投入多少精力,传递多少信息一眼就能看出来. 我不想看你的产出,更多的输出不代表有生产力.一张图能讲明白的事情不要放两张图,才是提高生产力 https://blog.yqiao.me/2026/05/show-me-less-output.html
以前代码行数,字数这些指标,虽然有争议,但也有一定道理,大概还是能反映产出的 即使是写垃圾代码垃圾报告,堆字数也要真埋头苦干的 但是现在生成更多产出没代价了,输出指标太容易 Hack 了,随随便便生成几十张 PPT 一堆图,但信息量为 0.真在一线的大家都在用 AI,是怎么做出来的,投入多少精力,传递多少信息一眼就能看出来. 我不想看你的产出,更多的输出不代表有生产力.一张图能讲明白的事情不要放两张图,才是提高生产力 https://blog.yqiao.me/2026/05/show-me-less-output.html
以前代码行数,字数这些指标,虽然有争议,但也有一定道理,大概还是能反映产出的 即使是写垃圾代码垃圾报告,堆字数也要真埋头苦干的 但是现在生成更多产出没代价了,输出指标太容易 Hack 了,随随便便生成几十张 PPT 一堆图,但信息量为 0.真在一线的大家都在用 AI,是怎么做出来的,投入多少精力,传递多少信息一眼就能看出来. 我不想看你的产出,更多的输出不代表有生产力.一张图能讲明白的事情不要放两张图,才是提高生产力 https://blog.yqiao.me/2026/05/show-me-less-output.html
这几天同学在看我给龙虾下命令时感到疑惑,以及他们问我要怎么说才能让豆包改论文时,我才意识到和AI交流其实也是一个需要门槛的事情。(尤其是跟5.4这个不说人话 还喜欢每次发几千字汇报的东西交流时) 用上AI之后,我和AI交流的次数和时长都越来越多,3月份时觉得自己打字速度拖慢了交流效率,开始尝试双拼,现在打字速度比之前快多了,和AI交流不再依靠语音来输入。 目前我一周时间里现实和网络中人发言的字数加起来,还没和AI交流半天打字来的多,如果要用比例的话,近一个月里大概是30:1,也就是我97%的发言都是和AI在交流,和人说的不多 那么佬们近一个月里与AI和人对话的字数比值是多少呢? 0~10 10~20 20~40 40~80 80~160 160以上(选这个你是完全不跟人交流吗) 点击以查看投票。 2 个帖子 - 2 位参与者 阅读完整话题
是这样的,之前做一个智能 Banner 生成项目,其中有一步需要根据用户输入生成多个字段,如“权益”“标题”等。这些字段的长度必须严格控制,以保证 Banner 的排版正常。然而,在实践中遇到了如下问题: 问题1:字段长度不可控,尽管我尝试了多种提示词策略,模型生成的文本长度仍然无法稳定控制,导致 Banner 排版偶尔异常。 问题2:无论是中文还是英文内容,都存在长度超出或不足的情况。 问题3:我曾要求模型在返回结构化 JSON 时增加一个“思考过程”字段,让模型计算每个字段的字符长度。然而发现 AI 给出的计算过程经常出错,甚至简单的加法也可能错误。 至今未找到一种可靠的方法,能够在生成文本的同时严格控制每个字段的字符长度,以满足 Banner 排版的精确要求。项目技术栈:springAI 大模型:千问 很纳闷浏览器网页端输入一段描述让AI返回精准技术就可以呢,他们是怎么做的呢。我通过代码api就很难,有佬遇过吗?这个bug一直挂在那里 1 个帖子 - 1 位参与者 阅读完整话题
从 20字限制两周后… 继续讨论: 今天距开启20字限制整一个月(4.7 - 5.7),具体好处从上面的前情帖子就可以看出来,不再赘述。 最近这两周我们做了什么: 继续清扫凑字数回复 调整网站架构 大幅服务器扩容 相信以上措施(包括两周前情帖里的)佬友们从体感上,是有明显感受的。昨天节后第一天,访问量再创新高,但社区访问很流畅。 有佬友担心,20字限制后,社区访问量会大幅下滑。其实并没有,可以看看四月份 Similarweb 数据: 通过以上数据可以看到,L站各方面指标仍然大幅领跑。 事实上我们关注的数据会更多,这有助于我们进行决策。通过近一个月的回复数据分析,综合了服务器压力、用户行为习惯等多方面因素考量,我们觉得 16 字回复限制会是更符合当前现状。 自本公告发出时,我们已将最小回帖字数调整为 16 ,与 Boost 更好地配合。主题最小字数仍旧保持 20 不变。 重申一下社区新的互动逻辑: 调整帖子最小长度 我们应该遵循这样一个逻辑进行社区互动: 能点赞的不 Boost ,能 Boost 的不回帖,回帖必有信息量。 再说说抽奖。 其置顶公告尚未撤下,这充分表明我们对此调整的决心,接下来仍将大力清洗闭眼抽奖的账号。 102 个帖子 - 101 位参与者 阅读完整话题
把快捷回复的字数用尽, 你能写出怎样精彩的快捷回复? 在<快捷回复秀>里秀快捷回复吧! 2 个帖子 - 1 位参与者 阅读完整话题
微信输入法的剪切板同步似乎有字数限制啥的,超过一定字数就同步不了了 1 个帖子 - 1 位参与者 阅读完整话题
佬友们,我觉得限制字数回帖没什么意义,20字不一定就有营养。 反而是逼着佬友凑字数,然后被举报又被删,很打击佬友热情的。 没评论,佬友就没兴趣发帖, 其实做这些限制,都是担心论坛质量下降和服务器顶不住压力吧? 那不如按等级限制发帖数量, 比如1级用户一天最多发2贴,回帖必须20字。 2级最多4贴,2级最小10字。 3级6贴。3级不限字数。 这样有没有搞头? 1 个帖子 - 1 位参与者 阅读完整话题