Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple file upload button implementation #380 #434

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions common/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import {
AudioModel,
AudioTrackModel,
AnkiUiSavedState,
ConfirmedVideoDataSubtitleTrack,
SubtitleTrack,
PostMineAction,
PlayMode,
CardModel,
CardTextFieldValues,
MobileOverlayModel,
SerializedSubtitleFile,
} from './model';
import { AsbPlayerToVideoCommandV2 } from './command';

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down
31 changes: 21 additions & 10 deletions common/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,33 +133,44 @@ 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;
}

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[];
Expand Down
123 changes: 54 additions & 69 deletions extension/src/controllers/video-data-sync-controller.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
Expand All @@ -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'),
Expand Down Expand Up @@ -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
Expand All @@ -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();
Expand Down Expand Up @@ -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 ?? '';
}
Expand Down Expand Up @@ -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[];
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -455,6 +437,7 @@ export default class VideoDataSyncController {
if (url === '-') {
return [
{
type: "file",
name: `${name}.${extension}`,
base64: '',
},
Expand Down Expand Up @@ -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;
Expand All @@ -496,6 +479,7 @@ export default class VideoDataSyncController {
}

tracks.push({
type: "file",
name: `${name}.${partExtension}`,
base64: bufferToBase64(await response.arrayBuffer()),
});
Expand All @@ -510,6 +494,7 @@ export default class VideoDataSyncController {

return [
{
type: 'file',
name: `${name}.${extension}`,
base64: response ? bufferToBase64(await response.arrayBuffer()) : '',
},
Expand All @@ -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++) {
Expand Down
1 change: 1 addition & 0 deletions extension/src/pages/amazon-prime-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions extension/src/pages/apps-disney-plus-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ inferTracksFromInterceptedMpd(/https:\/\/.+\.apps\.disneyplus\..+\.mpd/, (playli
}

return {
type: "url",
label,
language,
url: playlist.resolvedUri,
Expand Down
1 change: 1 addition & 0 deletions extension/src/pages/bandai-channel-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions extension/src/pages/bilibili-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ inferTracks({
const extension = extractExtension(url, 'srt');

addTrack({
type: "url",
label: track.lang,
language: track.lang_key,
url,
Expand Down
9 changes: 5 additions & 4 deletions extension/src/pages/disney-plus-page.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { VideoDataSubtitleTrack } from '@project/common';
import { EmbeddedSubtitle } from '@project/common';
import { Parser } from 'm3u8-parser';

setTimeout(() => {
Expand Down Expand Up @@ -64,7 +64,7 @@ setTimeout(() => {
});
}

function completeM3U8(url: string): Promise<VideoDataSubtitleTrack[]> {
function completeM3U8(url: string): Promise<EmbeddedSubtitle[]> {
return new Promise((resolve, reject) => {
setTimeout(async () => {
try {
Expand All @@ -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];
Expand All @@ -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,
Expand All @@ -106,7 +107,7 @@ setTimeout(() => {
});
}

let subtitlesPromise: Promise<VideoDataSubtitleTrack[]> | undefined;
let subtitlesPromise: Promise<EmbeddedSubtitle[]> | undefined;

const originalParse = JSON.parse;
JSON.parse = function () {
Expand Down
Loading