Skip to content

refactor: migrate to WXT and overhaul codebase - #1906

Open
NullVerdict wants to merge 15 commits into
devfrom
wxt
Open

NullVerdict wants to merge 15 commits into
devfrom
wxt

Conversation

@NullVerdict

Copy link
Copy Markdown
Collaborator

Move extension builds to WXT and improve translation reliability.

  • Improve YouTube audio track selection and handling of temporary download failures.
  • Make translation status polling adaptive and prevent duplicate requests for completed translations.
  • Update auto source-language handling for Live Voice and improve translation restarts.
  • Improve reconnection after the extension’s background process restarts.
  • Optimize subtitle processing and improve screen-reader accessibility for decorative icons.
  • Refine redaction of JSON parsing errors in diagnostic messages.

@NullVerdict

NullVerdict commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator Author

@ilyhalight В целом всё готово. Единственный вопрос по стратегиям от @Acobat12: они ломаются только в очень старых версиях Chrome (в 104 версии точно не работают).

@ilyhalight

Copy link
Copy Markdown
Owner

в коде в большинстве случаев используется import.meta.env.FIREFOX, но при этом есть isFirefoxBuild() в src/extension/background/telemetry-guard.ts, почему бы не использовать что-то одно

из .output пропала папка sourcemaps из-за чего sourcemap не загружаются в Bugsink.
stashSourcemaps кладёт карты расширения в dist-ext/sourcemaps/{chrome,firefox}, карты userscript лежат в dist/sourcemaps.

Лишний zip в артефакте workflow. autobuild оставляет в .output неподписанный vot-extension--firefox.zip, и он попадает в артефакт .output/.zip.

@@ -63,38 +105,34 @@ export function bytesToBase64(bytes: Uint8Array): string {
if (typeof btoaFn !== "function") {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

может быть еще стоит btoaFn / atobFn просто на btoa / atob заменить, они вроде и так везде есть

* (field 1 = ClientAbrState). Existing field 69 entries are replaced, every
* other field is copied byte-for-byte.
*/
export function setGeneratedSabrAudioTrackId(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

нигде кроме тестов самой этой функции не используется

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ещё вот

Мёртвый код, четыре предупреждения lint:

  • youtubeSabrPlayer.ts:8: импорт WebEmbeddedFormat;
  • youtubeSabrSupport.ts:5: импорт debug;
  • youtubeSabrSupport.ts:437: CurrentYouTubeAudioEvidence;
  • youtubeSabrSupport.ts:447: isVotSupportedAudioLanguage. Сама проверка поддерживаемых языков не потеряна, она переехала в resolveSabrAudioTrackId.

@NullVerdict NullVerdict Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поправил. Есть ещё какие-то моменты?

@ilyhalight ilyhalight Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

вроде нет, единственное из-за typescript 7, возможно, в ноде будет падать. Если там проблема только в моих либах, то обновлю их попозже

@NullVerdict
NullVerdict marked this pull request as ready for review October 26, 2026 21:51
@Acobat12

Copy link
Copy Markdown
Contributor

Привет! Раз уж вы сейчас перерабатываете логику выбора аудиодорожек, возможно, стоит учесть ещё один момент, о котором я ранее писал.

Иногда YouTube показывает несколько дорожек одного языка, например en.4 и en.10. Сейчас в resolveSabrAudioTrackId() используется .find(), поэтому выбор зависит от порядка, в котором YouTube вернул дорожки.

Из-за этого может быть выбрана en.10, хотя также доступна en.4. При этом наличие дорожки в метаданных ещё не гарантирует, что её получится фактически скачать через SABR. В результате перевод потенциально может завершиться ошибкой, хотя другая дорожка того же языка могла бы работать.

Возможно, стоит немного изменить выбор: если среди дорожек нужного языка есть .4, отдавать ей приоритет, а если нет, оставлять существующую логику.

Сейчас:

const exact = availableTrackIds.find(
  (trackId) => sabrTrackLanguage(trackId) === requested,
);

Можно заменить на:

const exactMatches = availableTrackIds.filter(
  (trackId) => sabrTrackLanguage(trackId) === requested,
);

const exact =
  exactMatches.find((trackId) => trackId.endsWith(".4")) ??
  exactMatches[0];

Таким образом, если среди дорожек нужного языка присутствует .4, будет выбрана именно она как оригинальная дорожка. Если .4 отсутствует, сохранится прежнее поведение, выбор первой подходящей дорожки.

Это небольшое изменение позволит отдавать приоритет оригинальному аудио, не ломая существующую логику загрузки и резервные варианты.

Разумеется, решение за вами. Просто предлагаю небольшое улучшение, поскольку, когда я изначально работал над SABR, ещё не до конца разобрался с особенностями выбора аудиодорожек.

@Acobat12

Copy link
Copy Markdown
Contributor

@ilyhalight В целом всё готово. Единственный вопрос по стратегиям от @Acobat12: они ломаются только в очень старых версиях Chrome (в 104 версии точно не работают).

Старые версии Chrome я не тестировал. Проверял стратегии в разных браузерах, даже в Safari специально для этого создавал виртуальную машину с macOS. Также проверял Supermium.

Времени всё детально протестировать не было, хотя хотелось, чтобы потом пользователи не жаловались на ошибки :)

Что делать со старыми версиями Chrome, я даже сейчас не знаю. Другого решения у меня пока нет.

Что касается Supermium, то в нём стратегии работают. Это форк Chromium с поддержкой даже Windows XP: https://github.com/win32ss/supermium

Поэтому пользователи старых систем, которым нужен перевод, могут установить Supermium вместо устаревшего Chrome.

@NullVerdict

NullVerdict commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator Author

@Acobat12 Интересное предложение, но хардкодить .4 нет смысла, эти цифры всегда разные. Единственный нормальный вариант это фильтровать по метаданным. Но пока никто на проблемы не жалуется, думаю, трогать это не стоит

@ilyhalight

Copy link
Copy Markdown
Owner

Но пока никто на проблемы не жалуется

вообще странно, что никто не жалуется. Судя по логам, относительно много неудачных попыток загрузки через web_sabr, хоть и примерно 2/3 из них, судя по всему, по итогу, успешно загружают аудио через обычный web_abr. В будущем нужно будет обязательно расширить этот лог, чтобы понимать в чем причина т.к. сейчас ~90% audio_failure из них это просто unknown

@ilyhalight

Copy link
Copy Markdown
Owner

можешь в vot.js дофиксить pr, чтобы я оба замержил сразу?

@Acobat12

Copy link
Copy Markdown
Contributor

Тяжело объяснить на словах, как это будет работать, но логика довольно простая.
Других цифр нету только .4 и .10

Если пользователь выбрал английский, сначала ищется именно английская дорожка, и только среди совпадений отдаётся приоритет .4.

Например, если пользователь выбрал испанский, а у видео доступны es-US.10 и оригинальная en-US.4, будет выбрана испанская es-US.10, а не английская.

Не бойтесь, этим изменением вы не сломаете переводы :) Наоборот, оно защищает от конкретной ошибки выбора аудиодорожки, о которой я ещё писал в PR #1873. В остальных случаях логика остаётся прежней.

@Acobat12

Acobat12 commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

вообще странно, что никто не жалуется. Судя по логам, относительно много неудачных попыток загрузки через web_sabr, хоть и примерно 2/3 из них, судя по всему, по итогу, успешно загружают аудио через обычный web_abr. В будущем нужно будет обязательно расширить этот лог, чтобы понимать в чем причина т.к. сейчас ~90% audio_failure из них это просто unknown

Кроме уже предложенной замены выше .find() на .filter() с приоритетом .4, для исправления unknown в WXT нужны следующие изменения:

1. youtubeSabrSupport.ts обработка unknown

В resolveSabrAudioTrackId() найди:

if (!requested || requested === "auto") {
  return {
    availableTrackIds,
    supportedAlternativeTrackIds,
    usedSupportedAlternative: false,
  };
}

Замени на:

if (!requested || ["auto", "unknown", "und"].includes(requested)) {
  const supportedTracks = availableTrackIds.filter(
    (trackId) =>
      supportedAlternativeTrackIds.includes(trackId),
  );

  const originalTrack = availableTrackIds.find(
    (trackId) => trackId.endsWith(".4"),
  );

  const trackId =
    originalTrack && supportedTracks.includes(originalTrack)
      ? originalTrack
      : supportedTracks[0];

  return {
    trackId,
    availableTrackIds,
    supportedAlternativeTrackIds,
    usedSupportedAlternative:
      Boolean(trackId && trackId !== originalTrack),
  };
}

Теперь при unknown:

  • Если есть .4 и её язык поддерживается VOT, выбирается она.
  • Если .4 отсутствует или её язык не поддерживается VOT, выбирается первая дорожка с поддерживаемым языком.
  • Если ID отсутствуют, сохраняется существующий fallback.
  • Если среди доступных дорожек нет ни одной с поддерживаемым языком, trackId остаётся undefined. Дальнейшее поведение определяется существующей логикой загрузчика.

2. youtubeSabrSupport.ts ожидание дорожки

В waitForSabrAudioTrackId() найди:

if (!requested || requested === "auto") {

Замени на:

if (!requested || ["auto", "unknown", "und"].includes(requested)) {

Так unknown будет обрабатываться сразу, без бессмысленного ожидания совпадения с языком "unknown".

3. youtubeSabrSupport.ts фильтрация форматов

В selectSabrAudioFormat() найди:

const language = normalizeAudioLanguage(
  trackId ? sabrTrackLanguage(trackId) : requestedLanguage,
);

Замени на:

const normalizedLanguage = normalizeAudioLanguage(
  trackId ? sabrTrackLanguage(trackId) : requestedLanguage,
);

const language =
  normalizedLanguage &&
  !["auto", "unknown", "und"].includes(normalizedLanguage)
    ? normalizedLanguage
    : undefined;

Это необходимо, чтобы "unknown" не использовался как реальный язык при фильтрации аудиоформатов.

4. youtubeSabr.ts классификация fallback

Найди:

requestedSabrLanguage && requestedSabrLanguage !== "auto"
  ? "native-only"
  : "native-auto";

Замени на:

requestedSabrLanguage &&
!["auto", "unknown", "und"].includes(requestedSabrLanguage)
  ? "native-only"
  : "native-auto";

Это позволит правильно классифицировать загрузку с неизвестным языком как native-auto.

Итого нужно сделать всего 5 изменений: одно для приоритета .4 над .10, которое я уже описал выше и не стал повторять, и ещё четыре для обработки unknown.

Эти изменения направлены на улучшение определения аудиодорожек, а не на изменение существующей логики переводов. Не бойтесь, переводы от этого не должны сломаться :) Наоборот, это поможет избежать некоторых ошибок при выборе аудио. 💯

@Acobat12

Copy link
Copy Markdown
Contributor

После этих изменений, останется только один возможный сценарий, который в будущем может решиться сам собой, причём изменять код для этого не потребуется.

Например, оригинальный язык видео португальский (pt), но он ошибочно определился как unknown. В таком случае скрипт может отправить оригинальную португальскую дорожку в Яндекс. Если Яндекс не сможет обработать этот язык, перевод не получится.

Однако если YouTube позже добавит к этому видео автоматический дубляж на английский или другой поддерживаемый язык, при повторной попытке скрипт сможет выбрать эту дорожку и отправить её на перевод уже без каких-либо изменений в коде.

А то, что пользователи сейчас особо не жалуются, вполне объяснимо. Когда VOT предлагает авторизоваться на YouTube, пользователь просто следует инструкции и пробует снова. Для него это выглядит как обычный шаг для продолжения перевода, а не как ошибка загрузки аудио. Поэтому отсутствие жалоб ещё не означает, что проблем с web_sabr нет :)

Так что тишина в комментариях не обязательно признак того, что всё работает идеально. Но, согласитесь, такая тишина тоже немного настораживает?)) У меня в форке такая же история тишина, блин! 😄

@Acobat12

Copy link
Copy Markdown
Contributor

Небольшое дополнение к моему предыдущему комментарию. Кажется, я всё-таки упустил один важный момент.

Даже если YouTube со временем добавит к видео автодубляж на поддерживаемый язык, это ещё не гарантирует, что скрипт сможет отправить новую дорожку на перевод.

Проблема в том, что Яндекс может вернуть уже существующий результат перевода и не запросить аудио повторно. В таком случае логика выбора дорожек в web_sabr просто не будет задействована.

Ранее я уже описывал способ принудительно запросить новый перевод в issue #1828.

В моих тестах для этого помогала передача wasStream: true вместе с изменённой длительностью видео:

videoData: {
  ...videoData,
  duration: videoData.duration + 300,
},
extraOpts: {
  wasStream: true,
},

Поэтому, возможно, стоит добавить отдельный режим принудительного запроса нового перевода.

Как вариант, эту логику можно реализовать в src/core/translationHandler.ts, в методе requestTranslationWithLivelyFallback(), непосредственно перед вызовом votClient.translateVideo(request), где формируется запрос к Яндексу.

При активации такого режима можно изменять videoData.duration и передавать extraOpts.wasStream: true. При этом для обычных запросов параметры лучше оставлять без изменений, чтобы не мешать получению уже готовых переводов.

Не уверен, что это самое подходящее место с точки зрения текущей архитектуры, но, возможно, такой вариант будет проще реализовать.

Тогда сценарий, который я описывал ранее, действительно сможет работать: если YouTube позже добавит поддерживаемую дорожку, можно будет принудительно запустить повторную обработку и попытаться отправить Яндексу уже её.

В моих тестах такой подход позволял обходить получение уже существующего перевода.

Получается, с фразой "изменять код не потребуется" я немного поспешил. 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants