Repository navigation
- No key needed to start (optional): SceneVoice Cloud. A welcome page (
welcome.html) opens after install with focus on its heading: Hear a sample reads three sample descriptions through your screen reader (or the built-in voice), then one choice: Start free or I have my own key. Start free asks the SceneVoice Cloud relay for an anonymous device token (no account, no e-mail), gives a free daily allowance of described minutes and has Cloud describe both sites; the allowance, plan, reset time, Stop using Cloud and a licence-key field for Plus or organisation plans live at the top of the settings page. A video someone with Cloud already had described is loaded from the shared cache instead of being made again (Share finished descriptions, on with Cloud, off otherwise; only finished description text is shared, never pictures, questions or your own key); used-up allowance says when it comes back. Users with their own key send nothing to SceneVoice; the key path is never degraded. New host permissionhttps://api.scenevoice.app/*, justified inPRIVACY.md(new SceneVoice Cloud section) anddocs/store-listing.md. The relay itself is a reference implementation underserver/(Cloudflare Worker: devices, allowance, Gemini- and DashScope-compatible proxies that never store a request, script cache, licences, Stripe webhook, 47 tests, deployment and cost notes inserver/README.md); it is not deployed yet, so Start free reports the failure plainly and points to the own-key path until it is. - Descriptions keep out of the dialogue's way. A pause the model only estimated is now used with half a second of margin (a pause measured from captions is used in full), and the model is told never to invent a pause when it cannot tell. A description may only cut off one less important than itself; otherwise it is held for up to 5 seconds of video for a real pause, re-fitted at that moment, and dropped if none comes. A visual description that lands on speech waits up to 5 seconds for a pause its brief version fits at your reading speed. In Keep playing mode, after more than three descriptions have been skipped for want of a pause, SceneVoice says so once per video and names the shortcut that switches to Pause the video. In Pause the video mode with a screen reader the video is held 1.5 seconds longer before it resumes, so the last words are not lost. Jumping in the video stops the sentence being read. A description from pictures said more than 6 seconds after its moment begins Earlier: / 刚才:, and very late ones are dropped. Each section's request carries the last four descriptions of the one before, so a section no longer re-describes the scene it opens in. The built-in voice waits for a hidden tab only when the video is silent too.
- What it costs, and a limit you set. A Usage today part of the settings page counts, per model (Gemini, Qwen, Cloud), the requests, tokens, minutes of video and an estimate in money, from a price table checked on 2026-11-04 (counted on this computer only, never sent anywhere); the popup says what the current video cost. A new Daily limit (none, 30 minutes, 1, 2 or 4 hours of described video) refuses new jobs for the rest of the day once reached and says so; asks and corrections still work, and nothing starts by itself again that day. Rate limits are told apart from used-up quotas: a minute's limit is retried after the wait the API asks for (at most 90 seconds), a day's quota switches once to another model family and is otherwise reported as such, and the Cloud allowance says when it resets. At most two picture requests per model are in flight at once; the rest queue in order.
- Any language. Descriptions can be written in any language the browser is set to, not only the ones with their own tables; twelve more languages have tables of their own (Italian, Dutch, Polish, Turkish, Vietnamese, Thai, Indonesian, Bengali, Urdu, Persian, Ukrainian, Swahili), with the hedging examples in the prompt built from the description language's own words. The live regions, the text shown on the video and the answer in the ask dialog carry the description language (and
dir="auto"), so the screen reader switches voice; the popup says when the interface language and the description language differ; right-to-left language names are shown right to left. The video type is read from YouTube's numeric category id when the page has it, not only the name. - Interface in 14 languages. Besides English and Simplified Chinese, the interface is available in Traditional Chinese, Spanish, Portuguese (Brazil), French, German, Japanese, Korean, Russian, Arabic, Hindi, Indonesian and Vietnamese. These twelve were drafted by an AI and are marked so at the top of each file until a native speaker who uses a screen reader has read them through; a missing string falls back to English. A browser set to Portuguese (Portugal) or to Chinese for Hong Kong or Macau gets the Portuguese (Brazil) or Simplified Chinese interface rather than English, and each page declares the language its strings are actually in (pt-BR, zh-CN). The store name is now SceneVoice: AI Audio Description for Video (the old one did not fit the 45-character limit), and the summary names both sites in every language.
- Settings page: when the second key is saved, a site that had the first key's model only for want of the other goes back to its usual one (Gemini on YouTube, Qwen on Bilibili), and the key status says so. Voices can now be saved for any description language, not only the ones with tables.
- Ask dialog: the Send button in dark mode now has enough contrast (5.2:1; it was 3.9:1).
- A project website under
site/(English and Simplified Chinese, written for screen-reader users first): hear a sample through the screen reader or the browser's voice, how it works, install links, who it is for, privacy, and the plans. No scripts or fonts from elsewhere, no analytics;site/check.mjsfails the build on any axe violation, missing link target or contrast below 7:1, and.github/workflows/pages.ymlpublishes it to GitHub Pages once that is switched on. Stores that are not open yet are plain text, not links to#; the Hear it buttons stay hidden until the script that drives them has loaded, with a<noscript>note otherwise; the share preview card is a PNG (site/assets/og.png, rendered from the SVG byscripts/make_og.py); the Chinese page, and the welcome page in a Chinese interface, link the Chinese privacy policy; the install text no longer claims a store review or a Gitee release that do not exist yet. - Release engineering.
npm run release -- <version>cuts the CHANGELOG, sets the version, runs the tests and build, commits and tags (nothing is pushed). Av*tag builds, tests, runs the end-to-end suite, checks that tag, package and CHANGELOG agree, and publishes a GitHub Release with the extension zip, external source maps andSHA256SUMS; with a store's secrets all set (five for the Chrome Web Store,CWS_PUBLISHER_IDincluded, three for Edge) it also uploads a draft to that store (never publishes), each store independently of the other, and the store credentials reach only the upload steps. CI pins every action to a commit, caches npm and pip, fails when the content script passes 120 KB (in CI, on a tag, and innpm run release), and refuses a package with dotfiles or source maps inside. NewSECURITY.md,PRIVACY.zh-CN.md,docs/release.md, issue and pull-request templates, Dependabot and CODEOWNERS;docs/store-listing.mdanswers the Chrome Web Store data-usage form andnpm run screenshotscaptures the store screenshots from the real pages. - After an adversarial review of all of the above (75 findings, 37 confirmed by three independent checks each), the fixes: screen readers that switch voice by language hear status, errors and notices in the interface language and only the descriptions in the description language; in pause mode with the built-in voice a description is spoken in a hidden tab too, instead of the video silently pausing and resuming; the speed shortcuts re-fit descriptions to the dialogue like the settings page does; a description that waited for a pause gets that pause's room; a picture description the model could not time is said in a pause the subtitles show whatever its priority; billed replies that could not be read still count in Usage today; a video already being described when the daily limit is reached keeps reporting its progress. Welcome page: My own key is preselected and Cloud is not called recommended until the relay is live; the built-in voice can be stopped, is stopped before the screen reader path starts, and says so when it cannot speak; Start free failures are one sentence with one remedy, and the per-network device limit is named. Settings: a device token the relay no longer knows is removed on sight and Start free comes back. Sharing sends the corrected descriptions, never one marked wrong. Relay: output and thinking tokens are metered too, free-plan models are anchored to the flash text models on both routes, a shared description is uploaded only by a device that had video described, at most 20 a day, and the first stored is never overwritten; deleting a licence record revokes the plan; one device may start at most 60 calls a minute. Extension pages declare the language actually shown when a translation is missing; Traditional Chinese has every string, and the build refuses a language that lacks a string spoken mid-video; the Chinese product names left Spanish, Hindi, Russian and Vietnamese labels. Release: the store secrets reach only the upload steps, the Chrome Web Store step needs all five secrets or is skipped, a failed Chrome upload no longer skips Edge, and the 120 KB gate really fails the build. Documents: the privacy policy in both languages now describes both paths and carries the same date; README, the website, the store listing's data-usage answers and the settings-page privacy notes no longer claim no server unconditionally; the website's install steps describe the welcome page; pre-release tags, which the scripts could not honour, left
docs/release.md. - Second review pass over the low-severity notes (27 confirmed by two independent checks each): a model the relay switched a Cloud job to no longer overwrites the Gemini model chosen for your own key; questions and corrections count against the daily limit at the length of the clip looked at, not a token estimate three times too high; Qwen reasoning tokens are counted once; the offscreen document closes after a Cloud cache lookup like after any request; the model list is fetched once per sitting through Cloud, not before every stretch; a priority-1 cue is no longer moved to a pause a hair too short once rounded; changing the video's playback speed re-fits descriptions like the speed shortcuts do; each welcome-page sample stays in the live region as long as it needs at the chosen reading speed; the transcript page names a language outside the built-in list in itself. Browsers set to Portuguese (Portugal) or to Chinese for Hong Kong or Macau get the Portuguese (Brazil) or Simplified Chinese interface instead of English. Relay: an opaque
nullOrigin is refused like any unlisted web origin, a model body without Content-Length is dropped the moment it passes 20 MB, licence keys are derived from the Stripe session so retried webhooks yield one key. Website: stores not open yet are plain text rather than links to#, the Hear it buttons appear only once their script has loaded (with a<noscript>note), the share card is a PNG, JSON-LD usessameAs, the Chinese page and the welcome page in Chinese link the Chinese privacy policy, and the install text no longer says the listings are under review. SECURITY.md states where the device token travels and that every workflow pins its actions to commit SHAs (a test fails on a floating tag); both READMEs' layout sections name the new modules. - End-to-end suite: the welcome page is the first-run check (install, focus, sample, both paths), the popup is checked in its three states as a real popup, every page is checked in dark mode too, a fake Bilibili page with a mock DashScope server covers the Qwen path, and a second run uses the Chinese interface.
- The first minute no longer misleads. Describe now and Ask a question without a key open the settings page, as their message says, and the page opens at the key field the site needs (
options.html#gemini/#qwen). With only one key saved, that key's model describes every site (Gemini on Bilibili from pictures, Qwen on YouTube from pictures) and SceneVoice says so once per video, instead of asking for a key you cannot get; saving the first key also sets it as the model for both sites. After Save and test the page says what to do next (open a video and press the shortcut). The Qwen model hint names the real default,qwen3-vl-plus. - The popup no longer talks over you while descriptions are being created: its status says the state once, and the numbers that change every second live in the progress box (and its bar). After Create descriptions or Stop, keyboard focus moves to the button that took the other's place instead of falling to the page. Away from a video, the key banner appears only when there is no key at all, and asks for the Qwen key in a Chinese browser.
- Two status messages said in the same moment (Descriptions loaded, then what the video type changed) are now both read: live-region writes are queued instead of the second cancelling the first.
- Nothing hangs in silence: a worker in the offscreen document that never answers (its script failed to load, it was terminated) is given 15 seconds for a command and 10 minutes for a model request, then reported as an error.
- Your key cannot be spent by a page you are not watching. Embedded YouTube players that have not been played never start generation by themselves (automatic creation, or carrying on with a saved run); the service worker accepts jobs, lookups and stops only for videos of the sender's own site, in a language SceneVoice writes, no longer than 12 hours; at most two description jobs run at once (
busy); and a tab may send at most 30 picture requests a minute. - Interface strings moved to one file per language under
scripts/locales/(en.mjsis the reference). English and Simplified Chinese must stay complete; other languages may be partial and fall back to English, and the build prints their coverage.CONTRIBUTING.mdsays how to translate. - Turning descriptions off now means no more requests. Off from anywhere (shortcut, popup, settings) also stops this video's description job, keeping what is done. During generation the shortcut still says how far it has got, and adds that pressing it again within 3 seconds stops and turns descriptions off.
- Store listing: permission reasons for the Qwen API and the Bilibili content script, and a single purpose that names Bilibili. New
docs/distribution-china.md: step by step for the Microsoft Edge Add-ons store and a Gitee mirror with a zip and install instructions in Chinese. - China site checks. Bilibili's undocumented web APIs (
x/web-interface/view,x/player/v2) are no longer called: title, uploader, description, section and parts come from the data the page is delivered with (window.__INITIAL_STATE__) or its text, and dialogue timing from the subtitle file the player itself loads when the viewer turns CC subtitles on. Model Studio was checked against its documentation (2026-10-31): the default model is nowqwen3-vl-plus; the default address still works but is the legacy one, and the settings page says how to use the workspace address Alibaba Cloud now recommends; picture format, count and JSON mode are confirmed. The settings page says what Qwen costs (about 5 requests per minute of video, roughly ¥2 per hour, on the user's own account). A test covers moving to another part (分P). - PRIVACY.md now names the request header each key travels in (
x-goog-api-keyfor Gemini,Authorization: Bearerfor Qwen, never the address), says what reaches Alibaba Cloud (pictures and text, never the video file or its sound), and lists the new things sent with a question (people labels, the last three questions about the video) and kept in synced settings (whether the create-descriptions hint was given; the videos whose AI notice was heard, when it is set to once per video). - Shorter by default. A visual description that runs well past what the model was asked for (about 1.5 times: brief 25, standard 60, detailed 135 characters in Chinese; about 12, 33 and 75 words in other languages) is cut at a sentence end instead of being read in full; on-screen text keeps the old, longer limits. The instructions now forbid filler such as We see…, The video shows…, 画面中可以看到….
- People keep one label. The instructions now ask for a label that tells people apart (two visible features: the woman host in red, 穿红衣服的女主持人), never a bare a man / 一个男人, and forbid introducing someone already labelled as a new, unnamed person, with English and Chinese examples. The labels so far also go with questions (Gemini and models sent pictures), so answers call people the same as the descriptions.
- Questions carry their context. The last three questions about the same video and their answers go with a new question, so a follow-up (and what is he holding?) is understood; the answers are marked as possibly wrong. Models sent pictures now get the last 20 seconds (as many pictures as one request takes, 6 by default) instead of 8 seconds and 4 pictures; Gemini still gets a 24-second clip.
- Going back to a video in the same tab no longer repeats what its type changed: every video's notice is remembered for the tab, not only the last one.
- Descriptions are labelled as AI-generated. The first message when descriptions start on a video (loaded, creating, carrying on, describing as you watch) begins AI-generated audio description follows. / 以下为 AI 生成的口述影像。, as China's rules for labelling AI-generated content and ACB's text-to-speech guidelines ask. A new setting, Say “AI-generated audio description follows” every time (on by default), can limit it to the first time for each video (remembered in
storage.syncunderaiNoticeSeen, the last 300 videos). Descriptions shown on the video are always tagged AI description / AI 口述. - First use. A step-by-step key guide (
guide.html, English and Chinese), written to be followed with a screen reader, is linked next to each key field; the key fields are described by a sentence saying what to paste, where to get it and what is free; links to AI Studio and Model Studio say where they go. In a Chinese browser the settings page starts at the Qwen key. Gemini not being available where you are now has its own message (region), separate from a key without access to a model (forbidden). The "press the shortcut to create descriptions" hint is given once, ever, instead of on every new video (remembered asgenerateHintGiven). The popup lists every shortcut under All keyboard shortcuts, with Change shortcuts; both pages name chrome://extensions/shortcuts and say the shortcuts work on Bilibili too. - The popup's progress matches the box on the player: both are written by one function (
generationProgressLabels/liveProgressLabelsinshared/labels.js), so the section, percentage and time left read the same, and the popup no longer rounds 0% up to 2% (only its bar shows a sliver). While describing as you watch, the popup now shows the progress bar too, with Described up to …. - Failures say what to do next. When generation stops part-way, the message says how many minutes are saved and that the shortcut carries on from there, and pressing it does (it no longer switches descriptions off after an error). A lost connection is resumed by itself when the computer is back online (Back online. Carrying on with the descriptions.). Clearer next steps for a used-up quota (wait a few minutes, a daily limit resets the next day, or pick another model, which has its own quota), for a part the model cannot take in even split into smaller pieces (pick another model), and for live streams (try the recording once it is up). Describing from pictures now notices a protected (DRM) video, which the browser draws as solid black without an error: after about 12 seconds of black pictures in a row it stops and says so (error
frames-protected). - Describing as you watch (Bilibili, and Qwen on YouTube) now shows its progress. A box above the player's controls says Describing as you watch / 正在边看边生成 and Described up to 1:35 / 已描述到 1:35 — how far the part being watched is described — with the share of the video described; it goes away when the picture taking stops. The popup says how far it has got, and the same is said at 25%, 50% and 75% of the video under Announce generation progress. The tab state carries
describedUpTo. - Opening a video that was described before is no longer silent. SceneVoice says the saved descriptions are loaded and how many there are (已有口述,已加载,共 N 条); when descriptions are off, it says so and which key turns them on; when the saved run found nothing to describe, it says that. A run that stopped part-way says how many minutes were loaded in the same sentence that says the rest is being created.
- Coming back to a video whose descriptions are still on their first section (nothing saved yet) now follows that job: the progress box appears, the shortcut says the percentage, section and time left, and the "no descriptions yet" hint is no longer given. Pressing the shortcut during generation never starts it again.
- Progress is said as generation goes, not only shown. When the first minutes are ready you hear You can start watching (可以开始看了); after that, the percentage and time left are said at 25%, 50% and 75%, never within 20 seconds of the last generation message (a quarter passed too soon is said once, later), instead of after every five-minute section. The end still says how many descriptions there are. A new setting, Announce generation progress (on by default), turns the quarterly messages off; Announce progress and status is now Announce status.
- The progress box is easier to see. It now sits just above the player's control bar (measured, so it clears the taller bar in fullscreen) instead of the top-left corner, where YouTube draws the video title in fullscreen; on Shorts, which have no control bar there, it stays at the top. Text is 16px (time left 15px) in white on near-black with a yellow border, and the box shows the percentage done. It attaches itself to the player if generation began before the player existed. It stays up while the controls hide and goes away when the descriptions are ready.
- China site / Bilibili (experimental). SceneVoice now also runs on Bilibili video pages (
www.bilibili.com/video/*, top frame only), and can use Qwen (通义千问, through Alibaba Cloud Model Studio / DashScope's OpenAI-compatible API) as well as Gemini. The site and the model are two separate layers: a platform adapter per site (src/content/youtube.js,src/content/bilibili.js) and a provider per model (src/shared/providers.js,qwen.js,describe.js). The settings page has a model for each site (YouTube: Gemini by default; Bilibili: Qwen by default) and a Qwen key section, stored and kept from content scripts like the Gemini key. Gemini on YouTube works exactly as before. Any other combination is sent pictures instead of a link: the page takes a scaled-down frame of the playing<video>every 2 seconds and sends up to 6 per 12-second stretch; descriptions are added to the saved script, the newest important one is said at the next pause (a few seconds late), and the rest are heard on the next viewing. Questions and Last description is wrong send pictures of that moment. A video whose picture the browser protects (a cross-origin, "tainted" canvas) or that plays outside a<video>element gets a clear message instead. Bilibili's CC subtitles (written, then AI-generated) give the dialogue timing when they can be read; otherwise the model's estimate is used. On Bilibili, a description language that follows the browser means Chinese. Interface strings in English and Simplified Chinese. API details not yet confirmed are settings or constants marked 待确认 (see README). - Fewer made-up details, and a way to correct the ones that remain. Gemini is now told to describe only what is visible: no guessed feelings or motives, no praise or judgement, and no names, jobs or relationships unless on-screen text or the dialogue gives them (the video title alone no longer counts), with wrong-and-right examples in English and Chinese. Blurred, small or partly read things are described as such ("appears to", "hard to see", "可能", "看不清"), and partly readable on-screen text is transcribed only as far as it can be read. Every description comes with a
confidence; one marked low counts one step less important (so Essentials only leaves it out) and is led by "Not certain." / "不太确定。" (in the description language) unless its own words already say so. Scripts saved before this change keep working. - Answers to questions say which moment they are about ("At 2:15: …", "在 2:15 处:…"), are based on the picture, and say so when Gemini is not sure. The recent descriptions sent with a question are marked as possibly wrong. Below the answer, Go to 2:15 jumps there. Gemini now returns the answer as structured output (
time,confidence,answer); a time it gives from the start of the clip is moved to the video, and one outside the clip is not trusted. - Last description is wrong (popup button, and a new shortcut, unset by default) marks the description just heard, or the one for the moment on screen. Only that moment (about 20 seconds around it) is described again; the new description replaces the old one in the saved script, or the old one is removed if there is nothing there to describe. Each step is announced: what was marked, then the new words, or what went wrong. The marks are stored in the extension's local storage next to the script, re-applied when a generation still running saves a later section, and deleted with the script. The transcript page labels corrected descriptions.
- On-screen text comes first and is read word for word. Gemini now transcribes it in a field of its own (
onScreenText), exactly as written, before writing the spoken versions, which quote it rather than paraphrase it; the instructions name what must not be missed (titles, names, slides, chart figures, prices, contact details, code, menus). Text gets one density step more than other descriptions, wins over a visual description of the same importance, is not cut off by a less important one, and, when it appears while someone is speaking, waits up to 8 seconds for a pause. When the video has captions, text that is also spoken within a few seconds (a slide read aloud, subtitles that match the captions) is left out. The transcript page shows the exact text where a description translates or shortens it. Scripts saved before this change keep working; create them again to get transcriptions. - Quiet moments stay quiet. Gemini is no longer asked for a description every 10 to 20 seconds: it describes only what is important and new, lets music, scenery and long takes play with a few words at a real change, and may return nothing for a section. How much to describe is now Essentials only, Moderate or As much as possible (仅关键, 适中, 尽量多), and follows the video type like mode and detail: entertainment describes only the essentials by default, and a density the user picks always wins (one saved by an earlier version that differs from the default counts as picked). Each type's density can be changed on the settings page; Adjust mode and detail to the video type is now Adjust descriptions to the video type.
- Mode and detail follow the video type. Entertainment (films, music, vlogs, games) keeps playing with brief descriptions; tutorials and explainers (lessons, how-to, science, news with charts) pause for detailed ones. The type comes from the YouTube category and title, read from the watch page SceneVoice already fetches for caption timing, and from Gemini, which now names the type in the same structured answer as the descriptions, so there is no extra request. It applies only to a mode or detail the user has not picked; a choice made in the popup, on the settings page or with a shortcut always wins (a mode or detail saved by an earlier version that differs from the default counts as picked). The page says once per video what changed and, the first time, how to change it back; the popup shows the values in use. On the settings page, Adjust mode and detail to the video type (on by default) turns it off, each type's mode and detail can be changed, and Let the video type decide again hands back a mode or detail the user picked. The video's type changes playback only; settings in storage are not touched, so tabs showing different videos do not affect each other.
- Descriptions no longer talk over dialogue. When the video has captions, their timing corrects the pauses the model estimated, and a description that starts at the tail of a sentence waits for it to end.
- In Keep playing mode a description that does not fit is now skipped, even an important one (previously its brief version was spoken anyway). In Pause the video mode it pauses the video instead.
- Built-in voice: pitch can now be set alongside voice and speaking rate. Voices for the description language are listed first (Chinese voices for Chinese descriptions), with their language spelled out, and each description language remembers its own voice. Test voice reads a sentence in the description language rather than the interface language. A synced voice that this computer does not have is replaced by an automatic voice for the description language.
- The popup has a speed control (built-in voice rate, or screen reader speed) next to detail and mode, a Test voice button, and names the shortcuts for these settings.
- New Speak faster and Speak slower shortcuts (unset by default, as Chrome allows only four suggested keys). Shortcuts that change a setting say the new value, and say so if it could not be saved.
- Restore default settings on the settings page. It keeps the API key, model, saved descriptions and keyboard shortcuts.
- Sliders are read out as values ("1.2×", "35%", "Normal") and are tied to their labels; the volume slider used to be read as "0.35", and the label around the rate and volume sliders belonged to the value shown next to them rather than to the slider.
- Settings are checked when read, so a bad or out-of-range value synced from another browser falls back to its default. Two quick changes on the same page no longer overwrite each other.
Install: unzip and load the folder as an unpacked extension, or wait for the store listing. Verify the download with sha256sum -c SHA256SUMS. The -sourcemaps zip is for reading stack traces; it is not part of the extension.