diff --git a/common/src/message.ts b/common/src/message.ts index 56339afa..4bfd7e18 100755 --- a/common/src/message.ts +++ b/common/src/message.ts @@ -6,12 +6,13 @@ import { AudioModel, AudioTrackModel, AnkiUiSavedState, - ConfirmedVideoDataSubtitleTrack, + SubtitleTrack, PostMineAction, PlayMode, CardModel, CardTextFieldValues, MobileOverlayModel, + SerializedSubtitleFile, } from './model'; import { AsbPlayerToVideoCommandV2 } from './command'; @@ -234,11 +235,6 @@ export interface ShowAnkiUiAfterRerecordMessage extends Message { readonly uiState: AnkiUiSavedState; } -export interface SerializedSubtitleFile { - name: string; - base64: string; -} - export interface LegacyPlayerSyncMessage extends Message { readonly command: 'sync'; readonly subtitles: SerializedSubtitleFile; @@ -420,7 +416,7 @@ export interface AnkiUiBridgeRerecordMessage extends Message { export interface VideoDataUiBridgeConfirmMessage extends Message { readonly command: 'confirm'; - readonly data: ConfirmedVideoDataSubtitleTrack[]; + readonly data: SubtitleTrack[]; readonly shouldRememberTrackChoices: boolean; } diff --git a/common/src/model.ts b/common/src/model.ts index 05c7e74c..91a55169 100755 --- a/common/src/model.ts +++ b/common/src/model.ts @@ -133,18 +133,29 @@ export interface AnkiUiSavedState { dialogRequestedTimestamp: number; } -export interface VideoDataSubtitleTrack { - label: string; - language: string; - url: string; - m3U8BaseUrl?: string; - extension: string; +export type SubtitleTrack = SerializedSubtitleFile | EmbeddedSubtitle; + +// typeguard +export const isSubtitleFile = (b:SubtitleTrack): b is SerializedSubtitleFile => { + return (b as SerializedSubtitleFile).type === "file"; } -export interface ConfirmedVideoDataSubtitleTrack { +// typeguard +export const isEmbeddedSubtitle = (b:SubtitleTrack): b is EmbeddedSubtitle => { + return (b as EmbeddedSubtitle).type === "url"; +} + +export interface SerializedSubtitleFile { + type: "file"; name: string; + base64: string; +} + +export interface EmbeddedSubtitle { + type: "url"; + label: string; language: string; - subtitleUrl: string; + url: string; m3U8BaseUrl?: string; extension: string; } @@ -152,14 +163,14 @@ export interface ConfirmedVideoDataSubtitleTrack { export interface VideoData { basename: string; error?: string; - subtitles?: VideoDataSubtitleTrack[]; + subtitles?: EmbeddedSubtitle[]; } export interface VideoDataUiState { open?: boolean; isLoading?: boolean; suggestedName?: string; - subtitles?: VideoDataSubtitleTrack[]; + subtitles?: EmbeddedSubtitle[]; error?: string; themeType?: string; selectedSubtitle?: string[]; diff --git a/extension/src/controllers/video-data-sync-controller.ts b/extension/src/controllers/video-data-sync-controller.ts index dd13aea5..b13f7cc6 100644 --- a/extension/src/controllers/video-data-sync-controller.ts +++ b/extension/src/controllers/video-data-sync-controller.ts @@ -1,13 +1,14 @@ import { - ConfirmedVideoDataSubtitleTrack, ExtensionSyncMessage, SerializedSubtitleFile, VideoData, - VideoDataSubtitleTrack, + EmbeddedSubtitle, VideoDataUiBridgeConfirmMessage, VideoDataUiBridgeOpenFileMessage, VideoDataUiState, VideoToExtensionCommand, + isEmbeddedSubtitle, + SubtitleTrack, } from '@project/common'; import { AsbplayerSettings, SettingsProvider, SubtitleListPreference } from '@project/common/settings'; import { bufferToBase64 } from '../services/base64'; @@ -54,7 +55,7 @@ export default class VideoDataSyncController { private _doneListener?: () => void; private _autoSync?: boolean; private _lastLanguagesSynced: { [key: string]: string[] }; - private _emptySubtitle: VideoDataSubtitleTrack; + private _emptySubtitle: EmbeddedSubtitle; private _boundFunction?: (event: Event) => void; private _syncedData?: VideoData; private _wasPaused?: boolean; @@ -72,6 +73,7 @@ export default class VideoDataSyncController { this._autoSync = false; this._lastLanguagesSynced = {}; this._emptySubtitle = { + type: 'url', language: '', url: '-', label: i18n.t('extension.videoDataSync.emptySubtitleTrack'), @@ -167,7 +169,7 @@ export default class VideoDataSyncController { const subtitleTrackChoices = this._syncedData?.subtitles ?? []; const subs = this._matchLastSyncedWithAvailableTracks(); - const selectedSub: VideoDataSubtitleTrack[] = subs.autoSelectedTracks; + const selectedSub: EmbeddedSubtitle[] = subs.autoSelectedTracks; if (subs.completeMatch && !userRequested && !this._syncedData?.error) { // Instead of showing, auto-sync @@ -187,28 +189,28 @@ export default class VideoDataSyncController { const themeType = await this._context.settings.getSingle('themeType'); let state: VideoDataUiState = this._syncedData ? { - open: true, - isLoading: this._syncedData.subtitles === undefined, - suggestedName: this._syncedData.basename, - selectedSubtitle: ['-'], - subtitles: subtitleTrackChoices, - error: this._syncedData.error, - themeType: themeType, - openedFromMiningCommand, - defaultCheckboxState: defaultCheckboxState, - } + open: true, + isLoading: this._syncedData.subtitles === undefined, + suggestedName: this._syncedData.basename, + selectedSubtitle: ['-'], + subtitles: subtitleTrackChoices, + error: this._syncedData.error, + themeType: themeType, + openedFromMiningCommand, + defaultCheckboxState: defaultCheckboxState, + } : { - open: true, - isLoading: this._context.subSyncAvailable && this._waitingForSubtitles, - suggestedName: document.title, - selectedSubtitle: ['-'], - error: '', - showSubSelect: true, - subtitles: subtitleTrackChoices, - themeType: themeType, - openedFromMiningCommand, - defaultCheckboxState: defaultCheckboxState, - }; + open: true, + isLoading: this._context.subSyncAvailable && this._waitingForSubtitles, + suggestedName: document.title, + selectedSubtitle: ['-'], + error: '', + showSubSelect: true, + subtitles: subtitleTrackChoices, + themeType: themeType, + openedFromMiningCommand, + defaultCheckboxState: defaultCheckboxState, + }; state.selectedSubtitle = selectedSub.map((subtitle) => subtitle.url || '-'); const client = await this._client(); this._prepareShow(); @@ -248,7 +250,7 @@ export default class VideoDataSyncController { return tracks; } - private _defaultVideoName(basename: string | undefined, subtitleTrack: VideoDataSubtitleTrack) { + private _defaultVideoName(basename: string | undefined, subtitleTrack: EmbeddedSubtitle) { if (subtitleTrack.url === '-') { return basename ?? ''; } @@ -291,15 +293,15 @@ export default class VideoDataSyncController { const confirmMessage = message as VideoDataUiBridgeConfirmMessage; if (confirmMessage.shouldRememberTrackChoices) { - this.lastLanguageSynced = confirmMessage.data.map((track) => track.language); + this.lastLanguageSynced = confirmMessage.data.map((track) => isEmbeddedSubtitle(track) ? track.language : ''); await this._context.settings .set({ streamingLastLanguagesSynced: this._lastLanguagesSynced }) - .catch(() => {}); + .catch(() => { }); } - const data = confirmMessage.data as ConfirmedVideoDataSubtitleTrack[]; + const data = confirmMessage.data as SubtitleTrack[]; - shallUpdate = await this._syncDataArray(data); + shallUpdate = await this._syncData(data); } else if ('openFile' === message.command) { const openFileMessage = message as VideoDataUiBridgeOpenFileMessage; const subtitles = openFileMessage.subtitles as SerializedSubtitleFile[]; @@ -367,52 +369,32 @@ export default class VideoDataSyncController { this._context.mobileVideoOverlayController.forceHide = true; } - private async _syncData(data: VideoDataSubtitleTrack[]) { + private async _syncData(data: SubtitleTrack[]) { try { let subtitles: SerializedSubtitleFile[] = []; for (let i = 0; i < data.length; i++) { - const { extension, url, m3U8BaseUrl } = data[i]; - const subtitleFiles = await this._subtitlesForUrl( - this._defaultVideoName(this._syncedData?.basename, data[i]), - extension, - url, - m3U8BaseUrl - ); - if (subtitleFiles !== undefined) { - subtitles.push(...subtitleFiles); - } - } - - this._syncSubtitles( - subtitles, - data.some((track) => track.m3U8BaseUrl !== undefined) - ); - return true; - } catch (error) { - if (typeof (error as Error).message !== 'undefined') { - this._reportError(`Data Sync failed: ${(error as Error).message}`); - } - - return false; - } - } - - private async _syncDataArray(data: ConfirmedVideoDataSubtitleTrack[]) { - try { - let subtitles: SerializedSubtitleFile[] = []; - - for (let i = 0; i < data.length; i++) { - const { name, extension, subtitleUrl, m3U8BaseUrl } = data[i]; - const subtitleFiles = await this._subtitlesForUrl(name, extension, subtitleUrl, m3U8BaseUrl); - if (subtitleFiles !== undefined) { - subtitles.push(...subtitleFiles); + if (isEmbeddedSubtitle(data[i])) { + const embeddedSubtitle = data[i] as EmbeddedSubtitle; + const { extension, url, m3U8BaseUrl } = embeddedSubtitle; + const subtitleFiles = await this._subtitlesForUrl( + this._defaultVideoName(this._syncedData?.basename, embeddedSubtitle), + extension, + url, + m3U8BaseUrl + ); + if (subtitleFiles !== undefined) { + subtitles.push(...subtitleFiles); + } + } else { + // isSubtitleFile + subtitles.push(data[i] as SerializedSubtitleFile) } } this._syncSubtitles( subtitles, - data.some((track) => track.m3U8BaseUrl !== undefined) + data.some((track) => isEmbeddedSubtitle(track) && track.m3U8BaseUrl !== undefined) ); return true; } catch (error) { @@ -455,6 +437,7 @@ export default class VideoDataSyncController { if (url === '-') { return [ { + type: "file", name: `${name}.${extension}`, base64: '', }, @@ -484,7 +467,7 @@ export default class VideoDataSyncController { const promises = parser.manifest.segments .filter((s: any) => !s.discontinuity && s.uri) .map((s: any) => fetch(`${m3U8BaseUrl}/${s.uri}`)); - const tracks = []; + const tracks: SerializedSubtitleFile[] = []; for (const p of promises) { const response = await p; @@ -496,6 +479,7 @@ export default class VideoDataSyncController { } tracks.push({ + type: "file", name: `${name}.${partExtension}`, base64: bufferToBase64(await response.arrayBuffer()), }); @@ -510,6 +494,7 @@ export default class VideoDataSyncController { return [ { + type: 'file', name: `${name}.${extension}`, base64: response ? bufferToBase64(await response.arrayBuffer()) : '', }, @@ -523,7 +508,7 @@ export default class VideoDataSyncController { this._prepareShow(); const subtitleTrackChoices = this._syncedData?.subtitles ?? []; - let selectedSub: VideoDataSubtitleTrack[] = [this._emptySubtitle, this._emptySubtitle, this._emptySubtitle]; + let selectedSub: EmbeddedSubtitle[] = [this._emptySubtitle, this._emptySubtitle, this._emptySubtitle]; for (let i = 0; i < this.lastLanguageSynced.length; i++) { const language = this.lastLanguageSynced[i]; for (let j = 0; j < subtitleTrackChoices.length; j++) { diff --git a/extension/src/pages/amazon-prime-page.ts b/extension/src/pages/amazon-prime-page.ts index 389ea469..9860e6a4 100644 --- a/extension/src/pages/amazon-prime-page.ts +++ b/extension/src/pages/amazon-prime-page.ts @@ -13,6 +13,7 @@ inferTracks({ const label = `${value.catalogMetadata.catalog.title} ${track.displayName}`; addTrack({ + type: "url", label: label, language: track.languageCode.toLowerCase(), url: track.url, diff --git a/extension/src/pages/apps-disney-plus-page.ts b/extension/src/pages/apps-disney-plus-page.ts index d72b147d..48dbe69a 100644 --- a/extension/src/pages/apps-disney-plus-page.ts +++ b/extension/src/pages/apps-disney-plus-page.ts @@ -17,6 +17,7 @@ inferTracksFromInterceptedMpd(/https:\/\/.+\.apps\.disneyplus\..+\.mpd/, (playli } return { + type: "url", label, language, url: playlist.resolvedUri, diff --git a/extension/src/pages/bandai-channel-page.ts b/extension/src/pages/bandai-channel-page.ts index a62872ba..26f32e4c 100644 --- a/extension/src/pages/bandai-channel-page.ts +++ b/extension/src/pages/bandai-channel-page.ts @@ -37,6 +37,7 @@ inferTracks({ typeof track.label === 'string' ? `${track.srclang} - ${track?.label}` : track.srclang; const url = track.sources[0].src.replace(/^http:\/\//, 'https://'); addTrack({ + type: "url", label: label, language: track.srclang.toLowerCase(), url: url, diff --git a/extension/src/pages/bilibili-page.ts b/extension/src/pages/bilibili-page.ts index 33aab94f..ccefb020 100644 --- a/extension/src/pages/bilibili-page.ts +++ b/extension/src/pages/bilibili-page.ts @@ -14,6 +14,7 @@ inferTracks({ const extension = extractExtension(url, 'srt'); addTrack({ + type: "url", label: track.lang, language: track.lang_key, url, diff --git a/extension/src/pages/disney-plus-page.ts b/extension/src/pages/disney-plus-page.ts index 2cfd2eba..246d3e0b 100644 --- a/extension/src/pages/disney-plus-page.ts +++ b/extension/src/pages/disney-plus-page.ts @@ -1,4 +1,4 @@ -import { VideoDataSubtitleTrack } from '@project/common'; +import { EmbeddedSubtitle } from '@project/common'; import { Parser } from 'm3u8-parser'; setTimeout(() => { @@ -64,7 +64,7 @@ setTimeout(() => { }); } - function completeM3U8(url: string): Promise { + function completeM3U8(url: string): Promise { return new Promise((resolve, reject) => { setTimeout(async () => { try { @@ -75,7 +75,7 @@ setTimeout(() => { if (subtitleGroup && subtitleGroup['sub-main']) { const tracks = subtitleGroup['sub-main']; - const subtitles: VideoDataSubtitleTrack[] = []; + const subtitles: EmbeddedSubtitle[] = []; for (const label of Object.keys(tracks)) { const track = tracks[label]; @@ -84,6 +84,7 @@ setTimeout(() => { const baseUrl = baseUrlForUrl(url); const subtitleM3U8Url = `${baseUrl}/${track.uri}`; subtitles.push({ + type: 'url', label: label, language: track.language, url: subtitleM3U8Url, @@ -106,7 +107,7 @@ setTimeout(() => { }); } - let subtitlesPromise: Promise | undefined; + let subtitlesPromise: Promise | undefined; const originalParse = JSON.parse; JSON.parse = function () { diff --git a/extension/src/pages/emby-page.ts b/extension/src/pages/emby-page.ts index 8e6bc0c1..b22d19ef 100644 --- a/extension/src/pages/emby-page.ts +++ b/extension/src/pages/emby-page.ts @@ -1,4 +1,4 @@ -import { VideoDataSubtitleTrack } from '@project/common'; +import { EmbeddedSubtitle } from '@project/common'; import { VideoData } from '@project/common'; declare const ApiClient: any | undefined; @@ -26,7 +26,7 @@ document.addEventListener( var mediaID = session.PlayState.MediaSourceId; var nowPlayingItem = session.NowPlayingItem; response.basename = nowPlayingItem.FileName; - const subtitles: VideoDataSubtitleTrack[] = []; + const subtitles: EmbeddedSubtitle[] = []; nowPlayingItem.MediaStreams.filter( (stream: { IsTextSubtitleStream: any }) => stream.IsTextSubtitleStream ).forEach((sub: { Codec: string; DisplayTitle: any; Language: any; Index: number }) => { @@ -42,6 +42,7 @@ document.addEventListener( '?api_key=' + apikey; subtitles.push({ + type: 'url', label: sub.DisplayTitle, language: sub.Language, url: url, diff --git a/extension/src/pages/hulu-page.ts b/extension/src/pages/hulu-page.ts index bc520ab0..74b53753 100644 --- a/extension/src/pages/hulu-page.ts +++ b/extension/src/pages/hulu-page.ts @@ -1,4 +1,4 @@ -import { VideoDataSubtitleTrack } from '@project/common'; +import { EmbeddedSubtitle } from '@project/common'; import { extractExtension } from './util'; setTimeout(() => { @@ -7,7 +7,7 @@ setTimeout(() => { } function extractSubtitleTracks(value: any) { - const subtitles = []; + const subtitles: EmbeddedSubtitle[] = []; if (isObject(value.transcripts_urls?.webvtt)) { const urls = value.transcripts_urls.webvtt; @@ -17,6 +17,7 @@ setTimeout(() => { if (typeof url === 'string') { if (subtitles.find((s) => s.label === s.language) === undefined) { subtitles.push({ + type: "url", label: language, language: language.toLowerCase(), url: url, @@ -32,7 +33,7 @@ setTimeout(() => { let playlistController: AbortController | undefined; - function fetchPlaylistAndExtractSubtitles(payload: any): Promise { + function fetchPlaylistAndExtractSubtitles(payload: any): Promise { playlistController?.abort(); return new Promise((resolve, reject) => { setTimeout(() => { @@ -86,7 +87,7 @@ setTimeout(() => { }); } - let subtitlesPromise: Promise | undefined; + let subtitlesPromise: Promise | undefined; let basenamePromise: Promise | undefined; const originalStringify = JSON.stringify; @@ -109,7 +110,7 @@ setTimeout(() => { 'asbplayer-get-synced-data', async () => { let basename = ''; - let subtitles: VideoDataSubtitleTrack[] = []; + let subtitles: EmbeddedSubtitle[] = []; let error = ''; try { diff --git a/extension/src/pages/mpd-util.ts b/extension/src/pages/mpd-util.ts index ec2ed2bd..0fc8d3d3 100644 --- a/extension/src/pages/mpd-util.ts +++ b/extension/src/pages/mpd-util.ts @@ -1,4 +1,4 @@ -import { VideoDataSubtitleTrack } from '@project/common'; +import { EmbeddedSubtitle } from '@project/common'; import { extractExtension, inferTracks } from './util'; import { parse } from 'mpd-parser'; @@ -9,15 +9,15 @@ export interface Playlist { export const inferTracksFromInterceptedMpd = ( mpdUrlRegex: RegExp, - trackExtractor: (playlist: Playlist, language: string) => VideoDataSubtitleTrack | undefined + trackExtractor: (playlist: Playlist, language: string) => EmbeddedSubtitle | undefined ) => { const originalFetch = window.fetch; - const tryExtractSubtitleTracks = async (mpdUrl: string): Promise => { + const tryExtractSubtitleTracks = async (mpdUrl: string): Promise => { const manifest = await (await originalFetch(mpdUrl)).text(); const parsedManifest = parse(manifest, { manifestUri: mpdUrl }); const subGroups = parsedManifest.mediaGroups?.SUBTITLES?.subs ?? {}; - const tracks: VideoDataSubtitleTrack[] = []; + const tracks: EmbeddedSubtitle[] = []; if (typeof subGroups !== 'object') { return []; diff --git a/extension/src/pages/osnplus-page.ts b/extension/src/pages/osnplus-page.ts index 78904fad..b1c8722d 100644 --- a/extension/src/pages/osnplus-page.ts +++ b/extension/src/pages/osnplus-page.ts @@ -4,6 +4,7 @@ import { extractExtension } from './util'; inferTracksFromInterceptedMpd(/https:\/\/(.+\.)?osn\.com.+\.mpd/, (playlist, language) => { const name = playlist.attributes?.NAME; return { + type: "url", label: name === undefined ? language : `${language} - ${name}`, language, url: playlist.resolvedUri, diff --git a/extension/src/pages/tver-page.ts b/extension/src/pages/tver-page.ts index ef70ad74..467441ca 100644 --- a/extension/src/pages/tver-page.ts +++ b/extension/src/pages/tver-page.ts @@ -18,6 +18,7 @@ inferTracks({ const url = track.sources[0].src; addTrack({ + type: "url", label: label, language: language, url: url.replace(/^http:\/\//, 'https://'), diff --git a/extension/src/pages/util.ts b/extension/src/pages/util.ts index 064d9a48..6f9be317 100644 --- a/extension/src/pages/util.ts +++ b/extension/src/pages/util.ts @@ -1,4 +1,4 @@ -import { VideoDataSubtitleTrack } from '@project/common'; +import { EmbeddedSubtitle } from '@project/common'; export function extractExtension(url: string, fallback: string) { const dotIndex = url.lastIndexOf('.'); @@ -40,15 +40,15 @@ export function poll(test: () => boolean, timeout: number = 10000): Promise void, + addTrack: (track: EmbeddedSubtitle) => void, setBasename: (basename: string) => void ) => void; - onRequest?: (addTrack: (track: VideoDataSubtitleTrack) => void, setBasename: (basename: string) => void) => void; + onRequest?: (addTrack: (track: EmbeddedSubtitle) => void, setBasename: (basename: string) => void) => void; waitForBasename: boolean; } diff --git a/extension/src/pages/viki-page.ts b/extension/src/pages/viki-page.ts index faa77dbf..e70da875 100644 --- a/extension/src/pages/viki-page.ts +++ b/extension/src/pages/viki-page.ts @@ -4,6 +4,7 @@ import { extractExtension } from './util'; inferTracksFromInterceptedMpd(/https:\/\/.+\.viki\..+manifest\.mpd/, (playlist, language) => { const name = playlist.attributes?.NAME; return { + type: "url", label: name === undefined ? language : `${language} - ${name}`, language, url: playlist.resolvedUri, diff --git a/extension/src/services/binding.ts b/extension/src/services/binding.ts index 56f7d6af..f8492d96 100755 --- a/extension/src/services/binding.ts +++ b/extension/src/services/binding.ts @@ -1224,6 +1224,7 @@ export default class Binding { const base64 = await bufferToBase64(await f.arrayBuffer()); return { + type: "file", name: f.name, base64: base64, }; diff --git a/extension/src/ui/components/VideoDataSyncDialog.tsx b/extension/src/ui/components/VideoDataSyncDialog.tsx index a6dfed84..cfd4b374 100644 --- a/extension/src/ui/components/VideoDataSyncDialog.tsx +++ b/extension/src/ui/components/VideoDataSyncDialog.tsx @@ -14,7 +14,7 @@ import Typography from '@material-ui/core/Typography'; import makeStyles from '@material-ui/styles/makeStyles'; import Switch from '@material-ui/core/Switch'; import LabelWithHoverEffect from '@project/common/components/LabelWithHoverEffect'; -import { ConfirmedVideoDataSubtitleTrack, VideoDataSubtitleTrack } from '@project/common'; +import { EmbeddedSubtitle, SubtitleTrack, isEmbeddedSubtitle, isSubtitleFile } from '@project/common'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -51,14 +51,14 @@ interface Props { isLoading: boolean; suggestedName: string; showSubSelect: boolean; - subtitles: VideoDataSubtitleTrack[]; + subtitles: SubtitleTrack[]; selectedSubtitle: string[]; defaultCheckboxState: boolean; error: string; openedFromMiningCommand: boolean; onCancel: () => void; onOpenFile: () => void; - onConfirm: (track: ConfirmedVideoDataSubtitleTrack[], shouldRememberTrackChoices: boolean) => void; + onConfirm: (track: SubtitleTrack[], shouldRememberTrackChoices: boolean) => void; } export default function VideoDataSyncDialog({ @@ -86,8 +86,8 @@ export default function VideoDataSyncDialog({ useEffect(() => { if (open) { setSelectedSubtitles( - selectedSubtitle.map((url) => { - return url !== undefined ? url : '-'; + selectedSubtitle.map((key) => { + return key !== undefined ? key : '-'; }) ); } else if (!open) { @@ -114,9 +114,10 @@ export default function VideoDataSyncDialog({ if ( !name || name === suggestedName || - subtitles.find((track) => track.url !== '-' && name === calculateName(suggestedName, track.label)) + subtitles.find((track) => isEmbeddedSubtitle(track) && track.url !== '-' && name === calculateName(suggestedName, track.label)) ) { - const selectedTrack = subtitles.find((track) => track.url === selectedSubtitles[0])!; + // TODO(xpire): make this logic work for SerializedSubtitleFile as well + const selectedTrack = subtitles.find((track) => (track as EmbeddedSubtitle).url === selectedSubtitles[0])! as EmbeddedSubtitle; if (selectedTrack.url === '-') { return suggestedName; @@ -131,7 +132,7 @@ export default function VideoDataSyncDialog({ }, [suggestedName, selectedSubtitles, subtitles]); function handleOkButtonClick() { - const selectedSubtitleTracks: ConfirmedVideoDataSubtitleTrack[] = allSelectedSubtitleTracks(); + const selectedSubtitleTracks: SubtitleTrack[] = allSelectedSubtitleTracks(); onConfirm(selectedSubtitleTracks, shouldRememberTrackChoices); } @@ -140,25 +141,35 @@ export default function VideoDataSyncDialog({ } function allSelectedSubtitleTracks() { - const selectedSubtitleTracks: ConfirmedVideoDataSubtitleTrack[] = selectedSubtitles - .map((selected): ConfirmedVideoDataSubtitleTrack | undefined => { - const subtitle = subtitles.find((subtitle) => subtitle.url === selected); + const selectedSubtitleTracks: SubtitleTrack[] = selectedSubtitles + .map((selected): SubtitleTrack | undefined => { + const subtitle = subtitles.find((subtitle) => isEmbeddedSubtitle(subtitle) && subtitle.url === selected || isSubtitleFile(subtitle) && subtitle.name == selected); if (subtitle) { - const { language, extension, m3U8BaseUrl } = subtitle; - return { - name: suggestedName.trim() + language.trim(), - extension: extension, - subtitleUrl: selected, - language: language, - m3U8BaseUrl: m3U8BaseUrl, - }; + if (isEmbeddedSubtitle(subtitle)) { + const { language, extension, m3U8BaseUrl } = subtitle; + return { + type: "url", + label: suggestedName.trim() + language.trim(), + extension: extension, + url: selected, + language: language, + m3U8BaseUrl: m3U8BaseUrl, + }; + } else { + // isSubtitleFile + return subtitle; + } + } }) - .filter((track): track is ConfirmedVideoDataSubtitleTrack => track !== undefined); + .filter((track): track is EmbeddedSubtitle => track !== undefined); // Give the first track the trimmed name from the name field in case it has been changed by the user - selectedSubtitleTracks[0].name = trimmedName; - + if (isEmbeddedSubtitle(selectedSubtitleTracks[0])) + selectedSubtitleTracks[0].label = trimmedName; + else { + selectedSubtitleTracks[0].name = trimmedName; + } return selectedSubtitleTracks; } @@ -187,10 +198,14 @@ export default function VideoDataSyncDialog({ }) } > - {subtitles.map((subtitle) => ( + {subtitles.map((subtitle) => (isEmbeddedSubtitle(subtitle) ? {subtitle.label} + : + + {subtitle.name} + ))} {isLoading && ( diff --git a/extension/src/ui/components/VideoDataSyncUi.tsx b/extension/src/ui/components/VideoDataSyncUi.tsx index 4771f971..0158483d 100644 --- a/extension/src/ui/components/VideoDataSyncUi.tsx +++ b/extension/src/ui/components/VideoDataSyncUi.tsx @@ -5,11 +5,10 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import VideoDataSyncDialog from './VideoDataSyncDialog'; import Bridge from '../bridge'; import { - ConfirmedVideoDataSubtitleTrack, Message, SerializedSubtitleFile, UpdateStateMessage, - VideoDataSubtitleTrack, + SubtitleTrack, VideoDataUiBridgeConfirmMessage, VideoDataUiBridgeOpenFileMessage, } from '@project/common'; @@ -29,8 +28,8 @@ export default function VideoDataSyncUi({ bridge }: Props) { const [isLoading, setIsLoading] = useState(true); const [suggestedName, setSuggestedName] = useState(''); const [showSubSelect, setShowSubSelect] = useState(true); - const [subtitles, setSubtitles] = useState([ - { language: '', url: '-', label: t('extension.videoDataSync.emptySubtitleTrack'), extension: 'srt' }, + const [subtitles, setSubtitles] = useState([ + { type: "url", language: '', url: '-', label: t('extension.videoDataSync.emptySubtitleTrack'), extension: 'srt' }, ]); const [selectedSubtitle, setSelectedSubtitle] = useState(['-', '-', '-']); const [defaultCheckboxState, setDefaultCheckboxState] = useState(false); @@ -45,7 +44,7 @@ export default function VideoDataSyncUi({ bridge }: Props) { bridge.sendMessageFromServer({ command: 'cancel' }); }, [bridge]); const handleConfirm = useCallback( - (data: ConfirmedVideoDataSubtitleTrack[], shouldRememberTrackChoices: boolean) => { + (data: SubtitleTrack[], shouldRememberTrackChoices: boolean) => { setOpen(false); const message: VideoDataUiBridgeConfirmMessage = { command: 'confirm', data, shouldRememberTrackChoices }; bridge.sendMessageFromServer(message); @@ -119,21 +118,23 @@ export default function VideoDataSyncUi({ bridge }: Props) { if (files && files.length > 0) { try { setDisabled(true); - const subtitles: SerializedSubtitleFile[] = []; + const subtitleFiles: SerializedSubtitleFile[] = []; for (let i = 0; i < files.length; ++i) { const f = files[i]; const base64 = await bufferToBase64(await f.arrayBuffer()); - subtitles.push({ + subtitleFiles.push({ + type: "file", name: f.name, base64: base64, }); } setOpen(false); - const message: VideoDataUiBridgeOpenFileMessage = { command: 'openFile', subtitles }; - bridge.sendMessageFromServer(message); + // setSubtitles([...subtitles, ...subtitleFiles]) + // const message: VideoDataUiBridgeOpenFileMessage = { command: 'openFile', subtitles }; + // bridge.sendMessageFromServer(message); } finally { setDisabled(false); }