Guide指南ガイド

What the library does, section by section, with a live example in each. Everything here is also in the repository's guide, docs/guide.md; the API page has the full surface.

这个库做的事,一节一节讲,每节都有活的示例。这些内容仓库里的指南 docs/guide.md 都有;完整接口见 API 页。

このライブラリができることを、節ごとに動く例つきで説明します。内容はすべてリポジトリのガイド docs/guide.md にもあり、API の全体は API ページにあります。

時Input and timing输入与时间入力とタイミング

The sentence comes in already cut into timed segments, and tsubu lights the one being spoken.

句子传进来时已经切好段、标好时间,tsubu 把正在读的那一段点亮。

文は文節に分けられ、時刻が付いた状態で渡され、tsubu は今読まれている文節を光らせます。

A segment is one unit of reading, made of parts; a part with ruby is drawn as <ruby>, so a word with a reading is never split. kind: "punct" marks punctuation: no underline, never the segment being spoken, and no time. Times are seconds on your clock, and t uses the same clock.

段是一个朗读单位,由片段组成;带 ruby 的片段画成 <ruby>,所以带读音的词不会被拆开。kind: "punct" 表示标点:没有下划线,永远不是正在读的段,也不带时间。时间以秒为单位,用你的时钟;t 用同一个时钟。

文節(segment)は読みの単位で、パーツの並びです。ruby を持つパーツは <ruby> として描かれるので、読みを持つ語が途中で切れることはありません。kind: "punct" は句読点で、下線がなく、読まれている文節にはならず、時刻も持ちません。時刻は秒単位で呼び出し側の時計を使い、t も同じ時計です。

The times decide the timing: "segments" when every non-punctuation segment has start and end (one segment lights at a time); "sentence" when only the sentence has them (the whole sentence lights, and the span is not divided among segments); "none" when there are no times (a static preview; stateAt and apply throw).

时间决定 timing:每个非标点段都有 start 和 end 时是 "segments"(一次亮一段);只有整句有时间时是 "sentence"(整句一起亮,时间段不会分给各段);哪里都没有时间时是 "none"(静态预览;stateAt 和 apply 会抛错)。

時刻が timing を決めます。句読点以外のすべての文節に start と end があれば "segments"(一度に一文節が光る)。文全体にだけ時刻があれば "sentence"(文全体が光り、時間は文節に配分されません)。どこにも時刻がなければ "none"(静的なプレビュー。stateAt と apply は例外を投げます)。

Every span is half-open, [start, end). In a gap between two segments the one before is past, the one after is future, and none is current. tsubu never guesses: only some segments timed, overlapping times, or punctuation with a time are refused with an error whose message starts with tsubu:.

每个时间段都是左闭右开的 [start, end)。两段之间的空隙里,前一段是 past,后一段是 future,没有 current。tsubu 从不猜测:只有部分段带时间、时间重叠、标点带时间,都会抛出以 tsubu: 开头的错误。

区間はすべて半開区間 [start, end) です。文節と文節の隙間では、前の文節が past、後の文節が future で、current はありません。tsubu は推測しません。一部の文節にだけ時刻がある、時刻が重なる、句読点に時刻があるといった入力は、tsubu: で始まるメッセージのエラーで拒否されます。

const s = Tsubu.sentence({ segments: [...] });
s.timing            // "segments", "sentence" or "none"
s.stateAt(1.0)      // { timing, state, progress, current, segments: [{ state, progress }] }
s.apply(root, 1.0)  // writes that state onto the page as attributes, and returns it
s.span(2)           // segment 2's own { start, end }, or null: where to seek on a tap
0.00 s

読Romaji over and under上下的罗马字上と下のローマ字

Romaji, the caller's own, over or under the text: you choose what goes on each side, and either side switches without moving a character.

由调用方提供的罗马字,标在字的上方或下方:每一侧标什么由你决定,任何一侧开关都不会让字移动。

呼び出し側が用意したローマ字を、文字の上か下に。どちらの側に何を置くかを選べ、どちらの側を切り替えても文字は動きません。

A segment may carry romaji, the romanised reading of the whole segment; tsubu never makes it from kana. options.over and options.under each name "ruby" (the furigana), "romaji" or "none"; the defaults are furigana over and nothing under, and the two may not name the same layer. Tsubu.setAnnotation(root, "over" | "under", on) switches a side (setRuby is its "over" form). Hidden readings keep their room, so nothing moves. Readings under the text sit below the dotted underline, and the lines make room for both sides.

每段可以带 romaji,即整段的罗马字读音;tsubu 从不自己把假名转成罗马字。options.over 和 options.under 各自指定 "ruby"(注音)、"romaji" 或 "none";默认上方是注音、下方什么都没有,两侧不能指定同一层。Tsubu.setAnnotation(root, "over" | "under", on) 开关某一侧(setRuby 就是它的 "over" 形式)。隐藏的读音照旧占位,所以什么都不会动。下方的读音在点状下划线之下,各行会为两侧都留出空间。

文節には romaji(文節全体のローマ字読み)を付けられます。tsubu が仮名からローマ字を作ることはありません。options.over と options.under はそれぞれ "ruby"(ふりがな)、"romaji"、"none" を指定します。既定は上にふりがな、下には何もなしで、両側に同じ層は指定できません。Tsubu.setAnnotation(root, "over" | "under", on) で片側を切り替えます(setRuby はその "over" 版です)。隠した読みも場所を保つので、何も動きません。下側の読みは点線の下線の下に置かれ、行は両側の分の余白を取ります。

In vertical text over is at the right and under at the left, and romaji lie on their side. The motion presets, the contrast check, forced colours and announceRuby treat romaji as they treat furigana. The exporters and importers accept romaji in their input but carry none.

竖排时上方在字的右侧、下方在左侧,罗马字侧躺。动效预设、对比度检查、强制颜色和 announceRuby 对罗马字的处理和注音一样。导出和导入函数接受带罗马字的输入,但不会带上罗马字。

縦書きでは上が右側、下が左側になり、ローマ字は横倒しになります。モーションプリセット、コントラスト検査、強制カラー、announceRuby はローマ字をふりがなと同じように扱います。書き出しと読み込みの関数はローマ字を含む入力を受け付けますが、ローマ字は運びません。

0.00 s

源Loanword origins外来语词源外来語の由来

Where a loanword comes from, over the word: パン from Portuguese pão. They switch on their own, and nothing moves.

外来语从哪里来,标在词的上方:パン 来自葡萄牙语 pão。词源单独开关,什么都不会动。

外来語がどこから来たかを、その語の上に。パンはポルトガル語の pão から。由来は単独で切り替えられ、何も動きません。

A part may carry origin: { text, lang, wasei? }: the source word, its language as a BCP 47 tag, and wasei: true for a pseudo-loanword coined in Japanese, which is drawn with 和製 before it. With options.origin: "over" each origin sits over its part, in the furigana's row, which a loanword in katakana leaves free. A loanword written in kanji (煙草, たばこ, from tabaco) keeps its furigana and has no origin drawn. Tsubu.setAnnotation(root, "origin", on) switches the origins; the over side's switch leaves them alone. Romaji over the text take the same row, so origin: "over" is refused with over: "romaji"; put the romaji under the text.

片段可以带 origin: { text, lang, wasei? }:原词、用 BCP 47 标签表示的语言,日语自造的和制外来语加 wasei: true,画的时候前面加「和製」。options.origin: "over" 时每个词源画在它的片段上方、注音那一行;片假名写的外来语没有注音,那一行正好空着。用汉字写的外来语(煙草,たばこ,来自 tabaco)保留注音,不画词源。Tsubu.setAnnotation(root, "origin", on) 开关词源;上方的开关不影响词源。上方的罗马字也用这一行,所以 origin: "over" 不能和 over: "romaji" 一起用;把罗马字放到下方即可。

パーツには origin: { text, lang, wasei? } を付けられます。原語、BCP 47 タグで表した言語、日本で作られた和製語には wasei: true で、その前に「和製」と描かれます。options.origin: "over" のとき、由来はパーツの上、ふりがなの行に置かれます。カタカナの外来語にはふりがながないので、その行は空いています。漢字で書く外来語(煙草、たばこ、tabaco から)はふりがなを残し、由来は描きません。Tsubu.setAnnotation(root, "origin", on) で由来を切り替え、上側の切り替えは由来に影響しません。上側のローマ字も同じ行を使うので、origin: "over" と over: "romaji" は一緒に使えません。ローマ字は下側に置いてください。

tsubu never guesses an origin and bundles no dictionary. JMdict's lsource is one source; it is licensed CC BY-SA 4.0 by the Electronic Dictionary Research and Development Group, and whoever uses that data must give attribution. Each origin's <rt> carries its lang, so screen readers say it in its language, and without ruby it reads パン(pão, pt).

tsubu 从不猜测词源,也不附带词典。JMdict 的 lsource 是一个来源,由 Electronic Dictionary Research and Development Group 以 CC BY-SA 4.0 授权,使用这些数据的人必须署名。每个词源的 <rt> 带着自己的 lang,读屏软件会用那种语言读;画不出 ruby 时显示为 パン(pão, pt)。

tsubu は由来を推測せず、辞書も同梱しません。JMdict の lsource が出典の一つで、Electronic Dictionary Research and Development Group により CC BY-SA 4.0 で提供されており、そのデータを使う人は帰属を表示しなければなりません。由来の <rt> はそれぞれの lang を持つので、スクリーンリーダーはその言語で読み上げます。ルビを描けない環境ではパン(pão, pt)と表示されます。

0.00 s

行Several lines and paging多行与翻页複数行とページ送り

A long sentence breaks by Japanese rules, keeps its lines even, sizes itself to its box and turns pages as the reading goes on.

长句按日语规则断行,各行长度均衡,字号随容器变化,读到哪儿就翻到哪一页。

長い文は日本語の禁則で改行され、行の長さは揃い、文字サイズはコンテナに追従し、読みの進みに合わせてページが送られます。

A line breaks only between segments, and never where Japanese line-breaking rules (kinsoku) forbid it: no closing bracket, punctuation, ー or small kana at the start of a line, no opening bracket at its end. Segments that may not be parted share a nowrap unit. Lines are balanced with text-wrap: balance where the browser does it: Chromium balances up to six lines, so the last line does not hold a lone segment; Firefox breaks tsubu's lines greedily; WebKit balances but may add a line, so a paged sentence keeps its greedy lines there.

行只在段与段之间断开,而且不在日语禁则不允许的地方断:闭合括号、标点、ー、小假名不出现在行首,开括号不出现在行尾。不能分开的段放在同一个 nowrap 单元里。在浏览器支持的地方各行用 text-wrap: balance 均衡:Chromium 最多均衡六行,所以最后一行不会只剩一段;Firefox 对 tsubu 的行按贪心方式断行;WebKit 会均衡,但可能多出一行,所以翻页的句子在 WebKit 里保留贪心断行。

改行は文節の間でだけ起こり、禁則処理に反する位置では起こりません。閉じ括弧、句読点、ー、小書き仮名は行頭に来ず、開き括弧は行末に来ません。分けられない文節は同じ nowrap のユニットにまとめられます。行はブラウザが対応する範囲で text-wrap: balance により均されます。Chromium は 6 行まで均すので、最終行に文節が一つだけ残ることはありません。Firefox は tsubu の行を貪欲に折り返します。WebKit は均しますが行が一つ増えることがあるため、ページ送りする文は WebKit では貪欲な折り返しのままにします。

Inside an element with class tsubu-fluid, the font size is the container's width divided by a number of characters per line, in three steps (under 480px, to 960px, and over), each configurable, between --tsubu-size-min and --tsubu-size-max. With --tsubu-max-lines set, a longer sentence is paged by t: apply shows the page that holds the segment being spoken, and returns page and pages.

放在带 tsubu-fluid 类的元素里时,字号等于容器宽度除以每行字数,分三档(480px 以下、到 960px、以上),每档都可配置,并限制在 --tsubu-size-min 和 --tsubu-size-max 之间。设了 --tsubu-max-lines 时,更长的句子按 t 翻页:apply 显示正在读的段所在的页,并返回 page 和 pages。

クラス tsubu-fluid の要素の中では、文字サイズはコンテナ幅を一行あたりの文字数で割った値になります。三つの段階(480px 未満、960px まで、それ以上)はそれぞれ設定でき、--tsubu-size-min と --tsubu-size-max の範囲に収まります。--tsubu-max-lines を設定すると、それより長い文は t でページ送りされます。apply は読まれている文節のあるページを表示し、page と pages を返します。

<div class="tsubu-fluid" style="width: 100%; --tsubu-max-lines: 2">
  <!-- the .tsubu element made by Tsubu.sentence() -->
</div>

Because paging reads the layout, call apply after document.fonts.ready, and again when the container changes size.

翻页要读版面,所以请在 document.fonts.ready 之后调用 apply,容器尺寸变化后再调用一次。

ページ送りはレイアウトを読むので、apply は document.fonts.ready の後に呼び、コンテナのサイズが変わったらもう一度呼んでください。

0.00 s

色Themes and contrast主题与对比度テーマとコントラスト

Three measured colour presets, and a function that checks any colours you set against WCAG thresholds.

三套实测过的配色预设,外加一个按 WCAG 阈值检查你自己配色的函数。

計測済みの配色プリセットが三つ、そして自分で設定した色を WCAG のしきい値で検査する関数が一つ。

Colours are tokens in groups, one per state (future, current, past), each with text, furigana and underline colours, and the current group's highlight. Set them on .tsubu or any ancestor. Three presets, chosen with options.theme or data-tsubu-theme on any ancestor: light, dark and media (text over an image or a video, on a translucent plate). Each preset has colours for speakers a and b.

颜色是一组组的 token,每个状态(future、current、past)一组,各有文字、注音、下划线颜色,current 组还有高亮。设在 .tsubu 或任何祖先上。三套预设,用 options.theme 或任何祖先上的 data-tsubu-theme 选择:light、dark、media(叠在图片或视频上,带半透明底板)。每套预设都给说话人 a 和 b 定义了颜色。

色はグループ化されたトークンで、状態(future、current、past)ごとに文字、ふりがな、下線の色があり、current のグループにはハイライトもあります。.tsubu か任意の祖先に設定します。プリセットは三つで、options.theme か祖先の data-tsubu-theme で選びます。light、dark、media(画像や動画の上。半透明のプレート付き)。各プリセットには話者 a と b の色もあります。

Tsubu.checkContrast(root) measures what the browser computes, in every state, against WCAG-based thresholds: 4.5:1 for text and furigana, 3:1 for the underline and the highlight. The furigana above a highlighted segment are checked against the page, since the highlight covers only the line of text. It works on a hidden copy and leaves the page as it was.

Tsubu.checkContrast(root) 测量浏览器算出的颜色,覆盖每个状态,按基于 WCAG 的阈值检查:文字和注音 4.5:1,下划线和高亮 3:1。高亮段上方的注音是对着页面检查的,因为高亮只盖住那一行文字。它在隐藏副本上测量,页面保持原样。

Tsubu.checkContrast(root) はブラウザが算出した色を全状態で計測し、WCAG に基づくしきい値で検査します。文字とふりがなは 4.5:1、下線とハイライトは 3:1 です。ハイライトは本文の行だけを覆うので、その上のふりがなはページに対して検査されます。計測は非表示のコピーで行われ、ページはそのまま残ります。

const result = Tsubu.checkContrast(root);
// { ok, checks: [{ state, layer, foreground, background, backdrop, ratio, minimum, ok }], failures }
Tsubu.checkContrast(root, { backdrop: "#fdf6e3", thresholds: { text: 7 } });  // your page, AAA text

Tick the last box to paint the furigana in a pale grey and watch the check fail. States, themes and speakers change colours only, never weight, padding or size, so none of them can move a character.

勾上最后一个框,把注音涂成浅灰,看检查失败。状态、主题和说话人只改颜色,从不改字重、内边距或尺寸,所以都不会让字移动。

最後のチェックボックスを入れるとふりがなが薄いグレーになり、検査が失敗するのが見えます。状態、テーマ、話者が変えるのは色だけで、太さや余白やサイズは変えないので、文字は動きません。

0.00 s
layerlowest ratio最低对比度最低比率minstate

動Motion and revealing动效与揭示モーションと明かし

Motion presets hide or draw what has not been heard yet, and the caller can reveal segments on any event.

动效预设藏起或画出还没听到的部分,调用方可以在任何事件上揭示某一段。

モーションプリセットは、まだ聞いていない部分を隠したり描いたりします。呼び出し側はどんなイベントでも文節を明かせます。

Motion presets change how a sentence shows what has and has not been heard. Each is drawn by tsubu.css from the state apply writes, so every frame is still a function of t. appear draws nothing unheard; mask draws blanks with the underline, so the length of what is to come shows; blur blurs it; underline-draw draws the spoken segment's underline in over its time. The catalogue is Tsubu.MOTIONS.

动效预设改变句子显示"已听到"和"未听到"的方式。每一个都由 tsubu.css 根据 apply 写的状态画出,所以每一帧仍然只取决于 t。appear 不画未听到的;mask 画成带下划线的空格,能看出后面还有多长;blur 把它模糊;underline-draw 在正在读的段的时间里逐渐画出下划线。目录是 Tsubu.MOTIONS。

モーションプリセットは、聞いた部分とまだ聞いていない部分の見せ方を変えます。どれも apply が書く状態から tsubu.css が描くので、フレームは変わらず t だけで決まります。appear は未聴の部分を描きません。mask は下線だけの空白にして、この先の長さを見せます。blur はぼかします。underline-draw は読まれている文節の下線をその時間をかけて引きます。カタログは Tsubu.MOTIONS です。

Revealing on your events. Tsubu.setRevealed(root, [2, 3]) shows segments as if heard, whatever t is: a word the learner said right, a tap, a button. tsubu handles no events itself; Tsubu.segmentOf(event.target) tells a click handler which segment was hit. A sentence without times with mask is a fill-in exercise: every segment starts as a blank.

按你的事件揭示。Tsubu.setRevealed(root, [2, 3]) 让这些段像已听到一样显示,不管 t 是多少:学习者说对的词、一次点击、一个按钮。tsubu 自己不处理事件;Tsubu.segmentOf(event.target) 告诉点击处理函数点中了哪一段。没有时间的句子配上 mask 就是填空练习:每段一开始都是空格。

イベントで明かす。Tsubu.setRevealed(root, [2, 3]) は、t がいくつでも、それらの文節を聞いた後のように見せます。学習者が正しく言えた語、タップ、ボタンなどに。tsubu 自身はイベントを扱いません。Tsubu.segmentOf(event.target) がクリックハンドラにどの文節かを教えます。時刻のない文に mask を付ければ穴埋め練習です。すべての文節が空白から始まります。

const s = Tsubu.sentence(input, { motion: "mask" });   // no times: every segment starts as a blank
let shown = [];
root.addEventListener("click", (event) => {
  const i = Tsubu.segmentOf(event.target);
  if (i !== null) shown = Tsubu.setRevealed(root, [...shown, i]);
});

Use motion only to show a change in what is being read, never to decorate. Do not combine appear with mask or blur, nor mask with underline-draw. Text hidden by mask is still in the page for screen readers; for a test, keep the answer out of the page or use appear.

动效只用来表现"读到哪儿了"的变化,不用来装饰。不要把 appear 和 mask 或 blur 组合,也不要把 mask 和 underline-draw 组合。被 mask 藏起来的文字仍在页面里,读屏软件会读;用来考试时,不要把答案放进页面,或者用 appear。

モーションは「どこを読んでいるか」の変化を見せるためだけに使い、飾りには使いません。appear を mask や blur と組み合わせず、mask を underline-draw と組み合わせないでください。mask で隠した文字はページに残り、スクリーンリーダーは読み上げます。テストに使うときは答えをページに入れないか、appear を使ってください。

Tap each word you can hear听出一个词就点一个聞き取れた語をタップ · motion: "mask", no times没有时间時刻なし

revealed: []

motion: "underline-draw" on a clock走时钟時計で再生 ·

0.00 s

縦Vertical writing竖排縦書き

The same sentence set vertically: columns right to left, readings at the right, side lines instead of underlines.

同一句话竖排:列从右向左,注音在右侧,下划线变成边线。

同じ文を縦書きで。列は右から左へ、読みは右側に、下線は傍線になります。

Tsubu.sentence(input, { writing: "vertical" }), or data-tsubu-writing="vertical" on any ancestor. Columns run right to left, the furigana sit at the right of the characters, and each segment's underline becomes a dotted side line at its left. The highlight, its spread and the media plate are the same boxes turned with the text. Give the container a height: the column length is the sentence's height.

Tsubu.sentence(input, { writing: "vertical" }),或在任何祖先上写 data-tsubu-writing="vertical"。列从右向左排,注音在字的右侧,每段的下划线变成它左侧的点状边线。高亮、它的外扩和 media 底板是随文字一起转过来的同一组盒子。请给容器设高度:列长就是句子的高度。

Tsubu.sentence(input, { writing: "vertical" })、または任意の祖先に data-tsubu-writing="vertical"。列は右から左へ進み、ふりがなは文字の右側に付き、各文節の下線は左側の点線の傍線になります。ハイライト、その広がり、media のプレートは、文字と一緒に回転した同じ箱です。コンテナには高さを与えてください。列の長さが文の高さです。

A run of half-width digits or letters of at most options.combineUpright characters (0 to 4, default 2) stands upright across the column (10月, AI); longer runs lie on their side (2025年). Every horizontal guarantee holds with columns in place of lines, and paging counts columns from the right.

不超过 options.combineUpright 个(0 到 4,默认 2)的半角数字或字母在列中直立显示(10月、AI);更长的侧躺(2025年)。横排的每条保证在竖排里以列代替行同样成立,翻页从右边数列。

options.combineUpright 文字以内(0 から 4、既定 2)の半角の数字や英字は縦中横で立ちます(10月、AI)。それより長い並びは横倒しです(2025年)。横書きの保証はすべて、行を列に読み替えて縦書きでも成り立ち、ページ送りは右から列を数えます。

Vertical writing is measured in Chromium, Firefox and WebKit (Safari's engine); every guarantee holds in all three except balanced columns, as docs/testing.md records under Browser support.

竖排在 Chromium、Firefox 和 WebKit(Safari 的引擎)里都实测过;除了均衡分列,三个引擎里每条保证都成立,详见 docs/testing.md 的浏览器支持一节。

縦書きは Chromium、Firefox、WebKit(Safari のエンジン)で計測しています。列の均等化を除き、三つのエンジンすべてで各保証が成り立ちます。詳しくは docs/testing.md のブラウザ対応の節をご覧ください。

0.00 s

録Recording video录制视频動画の書き出し

Because every frame is a function of t, recording a video is a loop over frame times, with examples for a browser and for Node.

每一帧都是 t 的函数,所以录视频就是按每帧时间循环一遍,浏览器和 Node 各有示例。

フレームは t の関数なので、動画の書き出しはフレーム時刻のループです。ブラウザ向けと Node 向けの例があります。

Because each frame depends on t alone, a video is a loop over frame times. Two examples in the repository, both run by the tests, use examples/sentence.json:

每一帧只取决于 t,所以一段视频就是按每帧时间循环一遍。仓库里有两个示例,测试都会运行,用的是 examples/sentence.json:

フレームは t だけで決まるので、動画はフレーム時刻のループです。リポジトリには二つの例があり、どちらもテストが実行します。入力は examples/sentence.json です。

# PNG frames from headless Chromium, apply(root, t) at each frame's time; prints the ffmpeg command
python examples/capture-frames.py examples/sentence.json frames --fps 30 --theme media --motion appear

# HTML frames from Node, with htmlAt(t), for renderers that take markup
node examples/frames-from-node.mjs examples/sentence.json frames 30

The test renders every frame forward and then backward and requires the PNGs to be identical, byte for byte. The capture draws each frame on a fresh copy of the sentence (innerHTML = s.html, then apply), which makes frames identical in any order and lets them be rendered in parallel. The strip below is that idea on this page: each frame is htmlAt(t) rendered on its own.

测试先按顺序、再倒序渲染所有帧,要求 PNG 逐字节相同。截帧时每帧都用一份新的句子副本来画(innerHTML = s.html,再 apply),这样任何顺序画出的帧都相同,也可以并行渲染。下面这条胶片就是这个想法在本页的样子:每一帧都是单独渲染的 htmlAt(t)。

テストは全フレームを順方向と逆方向で描き、PNG がバイト単位で一致することを求めます。キャプチャは各フレームを文の新しいコピーに描く(innerHTML = s.html してから apply)ので、どの順番でもフレームは同じになり、並列にも描けます。下のフィルムはその考え方をこのページで示したもので、各コマは単独に描いた htmlAt(t) です。

幕Exporting subtitles导出字幕字幕の書き出し

The same input becomes WebVTT with readings and cue timestamps, an ASS karaoke line, or plain SRT.

同样的输入可以导出成带读音和时间戳的 WebVTT、一行 ASS 卡拉 OK,或纯文本 SRT。

同じ入力から、読みとタイムスタンプ付きの WebVTT、ASS のカラオケ行、またはプレーンテキストの SRT を書き出せます。

The optional tsubu-export.js turns the same input into subtitles. TsubuExport.toWebVTT(input, { ruby, offset, id }) returns one cue spanning the sentence, each segment a <c.tsubu-seg> span with <ruby> readings, and a cue timestamp at each spoken segment's start, so ::cue(:past) follows the reading. TsubuExport.toASS(input, { ruby, offset }) returns one karaoke Dialogue: line with \k durations, rounded to centiseconds so they add up to the line; toASSScript wraps it in a minimal script. A sentence without times cannot be exported.

可选的 tsubu-export.js 把同样的输入转成字幕。TsubuExport.toWebVTT(input, { ruby, offset, id }) 返回覆盖整句的一条 cue,每段是一个带 <ruby> 读音的 <c.tsubu-seg> span,每个非标点段开始处有一个 cue 时间戳,所以 ::cue(:past) 会跟着朗读走。TsubuExport.toASS(input, { ruby, offset }) 返回一行带 \k 时长的卡拉 OK Dialogue:,先取整到百分之一秒,所以各时长之和等于这一行;toASSScript 把它包成最小脚本。没有时间的句子无法导出。

省略可能な tsubu-export.js は同じ入力を字幕にします。TsubuExport.toWebVTT(input, { ruby, offset, id }) は文全体にわたる一つのキューを返します。各文節は <ruby> の読みを含む <c.tsubu-seg> スパンで、句読点以外の各文節の開始位置にキュータイムスタンプがあるので、::cue(:past) が読みに追従します。TsubuExport.toASS(input, { ruby, offset }) は \k の長さを持つカラオケ Dialogue: 行を返します。境界を先に百分の一秒へ丸めるので、長さの合計は行の長さと一致します。toASSScript はそれを最小限のスクリプトで包みます。時刻のない文は書き出せません。

TsubuExport.toSRT(input, { mode, ruby, offset }) writes plain SubRip, which nearly every video editor imports: one cue for the sentence, or with mode: "segments" one cue per spoken segment. It has no tags, so only ASS and WebVTT carry the per-segment highlight. To drive tsubu from an engine's own clock instead, turn its time into seconds (for a frame counter, t = from + frame / fps, computed from the frame number each time rather than added up) and write s.htmlAt(t) into an element. examples/tsubu-frames.js holds that arithmetic, and examples/remotion/ and examples/hyperframes/ are a Remotion and a HyperFrames composition built on it, written from each engine's documentation. The HyperFrames one has been rendered with HyperFrames 0.8.181, and every frame matched htmlAt(t); the Remotion one has not yet been run against Remotion.

TsubuExport.toSRT(input, { mode, ruby, offset }) 写出纯文本 SubRip,几乎所有视频编辑器都能导入:整句一条 cue,或用 mode: "segments" 每个非标点段一条。它没有标签,所以只有 ASS 和 WebVTT 带逐段高亮。要用引擎自己的时钟驱动 tsubu,就把它的时间换成秒(对帧计数器是 t = from + frame / fps,每次都从帧号算出,而不是累加),再把 s.htmlAt(t) 写进一个元素。examples/tsubu-frames.js 包含这段算术,examples/remotion/ 和 examples/hyperframes/ 是基于它的 Remotion 和 HyperFrames 合成,按各引擎的文档写成。HyperFrames 示例已用 HyperFrames 0.8.181 实际渲染,每一帧都和 htmlAt(t) 一致;Remotion 示例还没有在真实的 Remotion 里运行过。

TsubuExport.toSRT(input, { mode, ruby, offset }) はプレーンテキストの SubRip を書き出します。ほとんどの動画編集ソフトが読み込めます。文に一つのキュー、または mode: "segments" で句読点以外の文節ごとに一つ。タグがないので、文節ごとのハイライトを運べるのは ASS と WebVTT だけです。エンジン自身の時計で tsubu を動かすには、その時刻を秒に直し(フレームカウンタなら t = from + frame / fps。足し合わせず、毎回フレーム番号から計算します)、s.htmlAt(t) を要素に書き込みます。examples/tsubu-frames.js にはその計算があり、examples/remotion/ と examples/hyperframes/ はそれを使った Remotion と HyperFrames のコンポジションです。各エンジンのドキュメントに沿って書かれています。HyperFrames の例は HyperFrames 0.8.181 で実際にレンダリングし、すべてのフレームが htmlAt(t) と一致しました。Remotion の例は、実際の Remotion ではまだ動かしていません。

取Importing other formats导入其他格式他の形式の読み込み

Subtitles and other tools' output come back as tsubu's input, with no segment or time guessed.

字幕和其他工具的输出可以转回 tsubu 的输入,不猜任何段或时间。

字幕や他のツールの出力を tsubu の入力に戻せます。文節も時刻も推測しません。

The optional tsubu-import.js defines TsubuImport. fromWebVTT(text) gives a sentence per cue, its segments from <c.tsubu-seg> spans or cue timestamps; WebVTT records when a segment begins but not when it ends, so a gap between segments does not survive the round trip. fromSRT(text, { mode }) gives a sentence per cue, or with mode: "segments" one sentence with a segment per cue. fromCaptions(captions) takes Remotion's captions, a segment each, and fromKuroshiro(html) turns kuroshiro's furigana HTML into the parts of one segment. Every result is checked with Tsubu.sentence(), and a refusal names the cue or caption.

可选的 tsubu-import.js 定义 TsubuImport。fromWebVTT(text) 每条 cue 给出一句,段来自 <c.tsubu-seg> span 或 cue 时间戳;WebVTT 只记一段何时开始、不记何时结束,所以段与段之间的空档往返一次就没了。fromSRT(text, { mode }) 每条 cue 给出一句,或用 mode: "segments" 整个文件一句、每条 cue 一段。fromCaptions(captions) 接收 Remotion 的字幕,每条一段;fromKuroshiro(html) 把 kuroshiro 的注音 HTML 变成一个段的片段。每个结果都经 Tsubu.sentence() 检查,被拒绝时会指出是哪条 cue 或字幕。

省略可能な tsubu-import.js は TsubuImport を定義します。fromWebVTT(text) はキューごとに一文を返し、文節は <c.tsubu-seg> スパンかキュータイムスタンプから取ります。WebVTT は文節の始まりを記録しても終わりは記録しないので、文節の間の空きは往復で失われます。fromSRT(text, { mode }) はキューごとに一文、mode: "segments" ならファイル全体で一文、キューごとに一つの文節を返します。fromCaptions(captions) は Remotion のキャプションを一つずつ文節にし、fromKuroshiro(html) は kuroshiro のふりがな HTML を一つの文節のパーツにします。結果はすべて Tsubu.sentence() で検査され、拒否されたときはどのキューやキャプションかが示されます。

0.00 s

助Accessibility无障碍アクセシビリティ

The highlight survives forced colours, readings can be hidden from screen readers, and the spoken segment is marked for assistive technology.

强制颜色下高亮仍在,注音可以对读屏软件隐藏,正在读的段会标记给辅助技术。

強制カラーでもハイライトは残り、ふりがなはスクリーンリーダーから隠せ、読まれている文節は支援技術に伝わります。

Forced colours. In forced-colours mode browsers replace backgrounds, which would leave the segment being spoken looking like every other. tsubu keeps a highlight on that segment alone, in the user's own colours for selected text (Highlight and HighlightText); its furigana take CanvasText.

强制颜色。强制颜色模式下浏览器会替换背景,正在读的段就和别的段一样了。tsubu 只给这一段保留高亮,用用户自己的选中文字颜色(Highlight 和 HighlightText);它的注音用 CanvasText。

強制カラー。強制カラーモードではブラウザが背景を置き換えるので、読まれている文節が他と同じに見えてしまいます。tsubu はその文節にだけ、ユーザー自身の選択色(Highlight と HighlightText)でハイライトを残します。そのふりがなは CanvasText です。

Screen readers. A reading is read after its base by default (Chromium exposes 「雨 あめ が 降る」). With options.announceRuby: false every <rt> and <rp> gets aria-hidden="true", so the sentence is read once; the readings are still drawn and still switch. The segment being spoken carries aria-current="true", and segments on other pages are hidden from assistive technology along with the page.

读屏软件。默认会在汉字后面读出注音(Chromium 暴露的是「雨 あめ が 降る」)。设 options.announceRuby: false 时每个 <rt> 和 <rp> 都带 aria-hidden="true",整句只读一遍;注音照样显示、照样能开关。正在读的段带 aria-current="true",其他页的段和那一页一起对辅助技术隐藏。

スクリーンリーダー。既定では読みが漢字の後に読み上げられます(Chromium は「雨 あめ が 降る」と公開します)。options.announceRuby: false にすると各 <rt> と <rp> に aria-hidden="true" が付き、文は一度だけ読まれます。ふりがなは表示され、切り替えもできます。読まれている文節には aria-current="true" が付き、他のページの文節はそのページごと支援技術から隠されます。

Without ruby. Each reading sits between full-width <rp> parentheses. Browsers that draw ruby hide them, and tsubu.css keeps them hidden where a page's own styles would show them, so they move nothing and are not read; a reader view, a feed reader or a text browser shows 雨(あめ).

画不出 ruby 时。每个读音都夹在全角的 <rp> 括号里。能画 ruby 的浏览器会隐藏括号,页面自己的样式要显示它时 tsubu.css 也会让它保持隐藏,所以括号不移动任何字、也不会被朗读;阅读模式、RSS 阅读器、纯文本浏览器里显示为 雨(あめ)。

ルビが描けないとき。読みはそれぞれ全角の <rp> の括弧で囲まれます。ルビを描けるブラウザは括弧を隠し、ページ自身のスタイルが表示させようとしても tsubu.css が隠したままにするので、括弧は文字を動かさず、読み上げられもしません。リーダー表示、フィードリーダー、テキストブラウザでは 雨(あめ)と表示されます。

The HTML at t = 1.0, as htmlAt returns it:t = 1.0 时的 HTML,由 htmlAt 返回:t = 1.0 の HTML。htmlAt が返すもの: