Merge branch 'p-stream:production' into substranslate

This commit is contained in:
vlOd 2025-12-26 21:28:44 +02:00 committed by GitHub
commit e02af4931c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 81 additions and 23 deletions

View file

@ -264,10 +264,46 @@ export function CustomCaptionOption() {
const setCaption = usePlayerStore((s) => s.setCaption); const setCaption = usePlayerStore((s) => s.setCaption);
const setCustomSubs = useSubtitleStore((s) => s.setCustomSubs); const setCustomSubs = useSubtitleStore((s) => s.setCustomSubs);
const fileInput = useRef<HTMLInputElement>(null); const fileInput = useRef<HTMLInputElement>(null);
const [error, setError] = useState<string | null>(null);
const handleFileSelect = (file: File) => {
setError(null);
const reader = new FileReader();
reader.addEventListener("load", (event) => {
if (!event.target || typeof event.target.result !== "string") {
setError("Failed to read file");
return;
}
try {
const converted = convert(event.target.result, "srt");
setCaption({
language: "custom",
srtData: converted,
id: "custom-caption",
});
setCustomSubs();
} catch (err) {
setError(
err instanceof Error
? err.message
: "Failed to convert subtitle file",
);
}
});
reader.addEventListener("error", () => {
setError("Failed to read file");
});
reader.readAsText(file, "utf-8");
};
return ( return (
<CaptionOption <CaptionOption
selected={lang === "custom"} selected={lang === "custom"}
error={error}
onClick={() => fileInput.current?.click()} onClick={() => fileInput.current?.click()}
> >
{t("player.menus.subtitles.customChoice")} {t("player.menus.subtitles.customChoice")}
@ -277,20 +313,22 @@ export function CustomCaptionOption() {
accept={subtitleTypeList.join(",")} accept={subtitleTypeList.join(",")}
type="file" type="file"
onChange={(e) => { onChange={(e) => {
if (!e.target.files) return; const files = e.target.files;
const reader = new FileReader(); if (!files || files.length === 0) return;
reader.addEventListener("load", (event) => {
if (!event.target || typeof event.target.result !== "string") const file = files[0];
const fileExtension = `.${file.name.split(".").pop()?.toLowerCase()}`;
if (!subtitleTypeList.includes(fileExtension)) {
setError(
`Unsupported file type. Supported: ${subtitleTypeList.join(", ")}`,
);
e.target.value = ""; // Reset input
return; return;
const converted = convert(event.target.result, "srt"); }
setCaption({
language: "custom", handleFileSelect(file);
srtData: converted, e.target.value = ""; // Reset input so same file can be selected again
id: "custom-caption",
});
setCustomSubs();
});
reader.readAsText(e.target.files[0], "utf-8");
}} }}
/> />
</CaptionOption> </CaptionOption>
@ -405,6 +443,7 @@ export function CaptionsView({
); );
const delay = useSubtitleStore((s) => s.delay); const delay = useSubtitleStore((s) => s.delay);
const appLanguage = useLanguageStore((s) => s.language); const appLanguage = useLanguageStore((s) => s.language);
const setCustomSubs = useSubtitleStore((s) => s.setCustomSubs);
// Get combined caption list // Get combined caption list
const captions = useMemo( const captions = useMemo(
@ -479,19 +518,23 @@ export function CaptionsView({
}, [srtData, selectedLanguage, delay, videoTime, selectedCaptionId]); }, [srtData, selectedLanguage, delay, videoTime, selectedCaptionId]);
function onDrop(event: DragEvent<HTMLDivElement>) { function onDrop(event: DragEvent<HTMLDivElement>) {
event.preventDefault();
const files = event.dataTransfer.files; const files = event.dataTransfer.files;
const firstFile = files[0]; const firstFile = files[0];
if (!files || !firstFile) return; if (!files || !firstFile) return;
const fileExtension = `.${firstFile.name.split(".").pop()}`; const fileExtension = `.${firstFile.name.split(".").pop()?.toLowerCase()}`;
if (!fileExtension || !subtitleTypeList.includes(fileExtension)) { if (!fileExtension || !subtitleTypeList.includes(fileExtension)) {
return; return;
} }
const reader = new FileReader(); const reader = new FileReader();
reader.addEventListener("load", (e) => { reader.addEventListener("load", (e) => {
if (!e.target || typeof e.target.result !== "string") return; if (!e.target || typeof e.target.result !== "string") {
return;
}
try {
const converted = convert(e.target.result, "srt"); const converted = convert(e.target.result, "srt");
setCaption({ setCaption({
@ -499,9 +542,17 @@ export function CaptionsView({
srtData: converted, srtData: converted,
id: "custom-caption", id: "custom-caption",
}); });
setCustomSubs();
} catch (err) {
// Silently fail on drop - user can use the upload button for better error feedback
}
}); });
reader.readAsText(firstFile); reader.addEventListener("error", () => {
// Silently fail on drop - user can use the upload button for better error feedback
});
reader.readAsText(firstFile, "utf-8");
} }
return ( return (

View file

@ -180,6 +180,13 @@ export function useCaptions() {
useEffect(() => { useEffect(() => {
if (!selectedCaption) return; if (!selectedCaption) return;
// Skip validation for custom/pasted captions that aren't in the caption list
const isCustomCaption =
selectedCaption.id === "custom-caption" ||
selectedCaption.id === "pasted-caption";
if (isCustomCaption) return;
const isSelectedCaptionStillAvailable = captions.some( const isSelectedCaptionStillAvailable = captions.some(
(caption) => caption.id === selectedCaption.id, (caption) => caption.id === selectedCaption.id,
); );