tangled
alpha
login
or
join now
dunkirk.sh
/
pstream-ng
1
fork
atom
pstream is dead; long live pstream
taciturnaxolotl.github.io/pstream-ng/
1
fork
atom
overview
issues
pulls
pipelines
persist caption across sources
Pas
1 week ago
314f9574
9009bf81
+47
-33
4 changed files
expand all
collapse all
unified
split
src
components
player
atoms
settings
CaptionsView.tsx
hooks
useCaptions.ts
useInitializePlayer.ts
stores
subtitles
index.ts
+5
-6
src/components/player/atoms/settings/CaptionsView.tsx
···
294
294
const { t } = useTranslation();
295
295
const lang = usePlayerStore((s) => s.caption.selected?.language);
296
296
const setCaption = usePlayerStore((s) => s.setCaption);
297
297
-
const setCustomSubs = useSubtitleStore((s) => s.setCustomSubs);
297
297
+
const setSubtitle = useSubtitleStore((s) => s.setSubtitle);
298
298
const fileInput = useRef<HTMLInputElement>(null);
299
299
const [error, setError] = useState<string | null>(null);
300
300
···
315
315
srtData: converted,
316
316
id: "custom-caption",
317
317
});
318
318
-
setCustomSubs();
318
318
+
setSubtitle(true, "custom", "custom-caption");
319
319
} catch (err) {
320
320
setError(
321
321
err instanceof Error
···
370
370
export function PasteCaptionOption(props: { selected?: boolean }) {
371
371
const { t } = useTranslation();
372
372
const setCaption = usePlayerStore((s) => s.setCaption);
373
373
-
const setCustomSubs = useSubtitleStore((s) => s.setCustomSubs);
373
373
+
const setSubtitle = useSubtitleStore((s) => s.setSubtitle);
374
374
const setDelay = useSubtitleStore((s) => s.setDelay);
375
375
const [isLoading, setIsLoading] = useState(false);
376
376
const [error, setError] = useState<string | null>(null);
···
409
409
srtData: converted,
410
410
id: "pasted-caption",
411
411
});
412
412
-
setCustomSubs();
412
412
+
setSubtitle(true, parsedData.language, "pasted-caption");
413
413
414
414
// Set delay if included in the pasted data, otherwise reset to 0
415
415
if (parsedData.delay !== undefined) {
···
476
476
);
477
477
const delay = useSubtitleStore((s) => s.delay);
478
478
const appLanguage = useLanguageStore((s) => s.language);
479
479
-
const setCustomSubs = useSubtitleStore((s) => s.setCustomSubs);
479
479
+
const setSubtitle = useSubtitleStore((s) => s.setSubtitle);
480
480
const matchScore = useCaptionMatchScore();
481
481
482
482
// Get combined caption list
···
580
580
srtData: converted,
581
581
id: "custom-caption",
582
582
});
583
583
-
setCustomSubs();
584
583
} catch (err) {
585
584
// Silently fail on drop - user can use the upload button for better error feedback
586
585
}
+20
-6
src/components/player/hooks/useCaptions.ts
···
13
13
} from "../utils/captions";
14
14
15
15
export function useCaptions() {
16
16
-
const setLanguage = useSubtitleStore((s) => s.setLanguage);
16
16
+
const setSubtitle = useSubtitleStore((s) => s.setSubtitle);
17
17
const enabled = useSubtitleStore((s) => s.enabled);
18
18
const resetSubtitleSpecificSettings = useSubtitleStore(
19
19
(s) => s.resetSubtitleSpecificSettings,
···
21
21
const setCaption = usePlayerStore((s) => s.setCaption);
22
22
const currentTranslateTask = usePlayerStore((s) => s.caption.translateTask);
23
23
const lastSelectedLanguage = useSubtitleStore((s) => s.lastSelectedLanguage);
24
24
+
const lastSelectedSubtitleId = useSubtitleStore(
25
25
+
(s) => s.lastSelectedSubtitleId,
26
26
+
);
24
27
const setIsOpenSubtitles = useSubtitleStore((s) => s.setIsOpenSubtitles);
25
28
26
29
const captionList = usePlayerStore((s) => s.captionList);
···
53
56
resetSubtitleSpecificSettings();
54
57
}
55
58
56
56
-
setLanguage(caption.language);
59
59
+
setSubtitle(true, caption.language, caption.id);
57
60
58
61
// Use native tracks for MP4 streams instead of custom rendering
59
62
if (source?.type === "file" && enableNativeSubtitles) {
···
65
68
},
66
69
[
67
70
setIsOpenSubtitles,
68
68
-
setLanguage,
71
71
+
setSubtitle,
69
72
setCaption,
70
73
resetSubtitleSpecificSettings,
71
74
source,
···
135
138
const disable = useCallback(async () => {
136
139
setIsOpenSubtitles(false);
137
140
setCaption(null);
138
138
-
setLanguage(null);
139
139
-
}, [setCaption, setLanguage, setIsOpenSubtitles]);
141
141
+
setSubtitle(false);
142
142
+
}, [setCaption, setSubtitle, setIsOpenSubtitles]);
140
143
141
144
const selectLastUsedLanguage = useCallback(async () => {
145
145
+
if (lastSelectedSubtitleId) {
146
146
+
const caption = captions.find((v) => v.id === lastSelectedSubtitleId);
147
147
+
if (caption) return selectCaptionById(caption.id);
148
148
+
}
149
149
+
142
150
const language = lastSelectedLanguage ?? "en";
143
151
await selectLanguage(language);
144
152
return true;
145
145
-
}, [lastSelectedLanguage, selectLanguage]);
153
153
+
}, [
154
154
+
lastSelectedLanguage,
155
155
+
selectLanguage,
156
156
+
lastSelectedSubtitleId,
157
157
+
captions,
158
158
+
selectCaptionById,
159
159
+
]);
146
160
147
161
const toggleLastUsed = useCallback(async () => {
148
162
if (enabled) disable();
+1
-5
src/components/player/hooks/useInitializePlayer.ts
···
26
26
);
27
27
const { selectLastUsedLanguageIfEnabled } = useCaptions();
28
28
29
29
-
// Only select subtitles on initial load, not when source changes
30
30
-
const hasInitializedRef = useRef(false);
31
31
-
32
29
useEffect(() => {
33
33
-
if (sourceIdentifier && !hasInitializedRef.current) {
34
34
-
hasInitializedRef.current = true;
30
30
+
if (sourceIdentifier) {
35
31
selectLastUsedLanguageIfEnabled();
36
32
}
37
33
}, [sourceIdentifier, selectLastUsedLanguageIfEnabled]);
+21
-16
src/stores/subtitles/index.ts
···
59
59
};
60
60
enabled: boolean;
61
61
lastSelectedLanguage: string | null;
62
62
+
lastSelectedSubtitleId: string | null;
62
63
isOpenSubtitles: boolean;
63
64
styling: SubtitleStyling;
64
65
overrideCasing: boolean;
···
66
67
showDelayIndicator: boolean;
67
68
updateStyling(newStyling: Partial<SubtitleStyling>): void;
68
69
resetStyling(): void;
69
69
-
setLanguage(language: string | null): void;
70
70
+
setSubtitle(
71
71
+
enabled: boolean,
72
72
+
language?: string | null,
73
73
+
subtitleId?: string | null,
74
74
+
): void;
70
75
setIsOpenSubtitles(isOpenSubtitles: boolean): void;
71
71
-
setCustomSubs(): void;
72
76
setOverrideCasing(enabled: boolean): void;
73
77
setDelay(delay: number): void;
74
78
importSubtitleLanguage(lang: string | null): void;
···
84
88
lastSelectedLanguage: null,
85
89
},
86
90
lastSelectedLanguage: null,
91
91
+
lastSelectedSubtitleId: null,
87
92
isOpenSubtitles: false,
88
93
overrideCasing: false,
89
94
delay: 0,
···
105
110
s.overrideCasing = false;
106
111
});
107
112
},
113
113
+
setIsOpenSubtitles(isOpenSubtitles) {
114
114
+
set((s) => {
115
115
+
s.isOpenSubtitles = isOpenSubtitles;
116
116
+
});
117
117
+
},
108
118
updateStyling(newStyling) {
109
119
set((s) => {
110
120
if (newStyling.backgroundOpacity !== undefined)
···
153
163
};
154
164
});
155
165
},
156
156
-
setLanguage(lang) {
166
166
+
setSubtitle(enabled, language, subtitleId) {
157
167
set((s) => {
158
158
-
s.enabled = !!lang;
159
159
-
if (lang) s.lastSelectedLanguage = lang;
160
160
-
});
161
161
-
},
162
162
-
setIsOpenSubtitles(isOpenSubtitles) {
163
163
-
set((s) => {
164
164
-
s.isOpenSubtitles = isOpenSubtitles;
165
165
-
});
166
166
-
},
167
167
-
setCustomSubs() {
168
168
-
set((s) => {
169
169
-
s.enabled = true;
170
170
-
s.lastSelectedLanguage = null;
168
168
+
s.enabled = enabled;
169
169
+
if (enabled) {
170
170
+
s.lastSelectedLanguage = language ?? null;
171
171
+
s.lastSelectedSubtitleId = subtitleId ?? null;
172
172
+
} else {
173
173
+
s.lastSelectedLanguage = null;
174
174
+
s.lastSelectedSubtitleId = null;
175
175
+
}
171
176
});
172
177
},
173
178
setOverrideCasing(enabled) {