pull/2366/head
v1.4.1-beta.14
parent
0807bd99b9
commit
0e648b5588
@ -0,0 +1,183 @@
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
import {
|
||||
useRefTracksByEpisodeId,
|
||||
useRefTracksByMovieId,
|
||||
useSubtitleAction,
|
||||
} from "@/apis/hooks";
|
||||
import { useModals, withModal } from "@/modules/modals";
|
||||
import { task } from "@/modules/task";
|
||||
import { syncMaxOffsetSecondsOptions } from "@/pages/Settings/Subtitles/options";
|
||||
import { toPython } from "@/utilities";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Alert, Button, Checkbox, Divider, Stack, Text } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { FunctionComponent } from "react";
|
||||
import { Selector, SelectorOption } from "../inputs";
|
||||
|
||||
const TaskName = "Syncing Subtitle";
|
||||
|
||||
function useReferencedSubtitles(
|
||||
mediaType: "episode" | "movie",
|
||||
mediaId: number,
|
||||
subtitlesPath: string
|
||||
) {
|
||||
// We cannot call hooks conditionally, we rely on useQuery "enabled" option to do only the required API call
|
||||
const episodeData = useRefTracksByEpisodeId(
|
||||
subtitlesPath,
|
||||
mediaId,
|
||||
mediaType === "episode"
|
||||
);
|
||||
const movieData = useRefTracksByMovieId(
|
||||
subtitlesPath,
|
||||
mediaId,
|
||||
mediaType === "movie"
|
||||
);
|
||||
|
||||
const mediaData = mediaType === "episode" ? episodeData : movieData;
|
||||
|
||||
const subtitles: { group: string; value: string; label: string }[] = [];
|
||||
|
||||
if (!mediaData.data) {
|
||||
return [];
|
||||
} else {
|
||||
if (mediaData.data.audio_tracks.length > 0) {
|
||||
mediaData.data.audio_tracks.forEach((item) => {
|
||||
subtitles.push({
|
||||
group: "Embedded audio tracks",
|
||||
value: item.stream,
|
||||
label: `${item.name || item.language} (${item.stream})`,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (mediaData.data.embedded_subtitles_tracks.length > 0) {
|
||||
mediaData.data.embedded_subtitles_tracks.forEach((item) => {
|
||||
subtitles.push({
|
||||
group: "Embedded subtitles tracks",
|
||||
value: item.stream,
|
||||
label: `${item.name || item.language} (${item.stream})`,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (mediaData.data.external_subtitles_tracks.length > 0) {
|
||||
mediaData.data.external_subtitles_tracks.forEach((item) => {
|
||||
if (item) {
|
||||
subtitles.push({
|
||||
group: "External Subtitles files",
|
||||
value: item.path,
|
||||
label: item.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return subtitles;
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
selections: FormType.ModifySubtitle[];
|
||||
onSubmit?: VoidFunction;
|
||||
}
|
||||
|
||||
interface FormValues {
|
||||
reference?: string;
|
||||
maxOffsetSeconds?: string;
|
||||
noFixFramerate: boolean;
|
||||
gss: boolean;
|
||||
}
|
||||
|
||||
const SyncSubtitleForm: FunctionComponent<Props> = ({
|
||||
selections,
|
||||
onSubmit,
|
||||
}) => {
|
||||
if (selections.length === 0) {
|
||||
throw new Error("You need to select at least 1 media to sync");
|
||||
}
|
||||
|
||||
const { mutateAsync } = useSubtitleAction();
|
||||
const modals = useModals();
|
||||
|
||||
const mediaType = selections[0].type;
|
||||
const mediaId = selections[0].id;
|
||||
const subtitlesPath = selections[0].path;
|
||||
|
||||
const subtitles: SelectorOption<string>[] = useReferencedSubtitles(
|
||||
mediaType,
|
||||
mediaId,
|
||||
subtitlesPath
|
||||
);
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
initialValues: {
|
||||
noFixFramerate: false,
|
||||
gss: false,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit((parameters) => {
|
||||
selections.forEach((s) => {
|
||||
const form: FormType.ModifySubtitle = {
|
||||
...s,
|
||||
reference: parameters.reference,
|
||||
max_offset_seconds: parameters.maxOffsetSeconds,
|
||||
no_fix_framerate: toPython(parameters.noFixFramerate),
|
||||
gss: toPython(parameters.gss),
|
||||
};
|
||||
|
||||
task.create(s.path, TaskName, mutateAsync, { action: "sync", form });
|
||||
});
|
||||
|
||||
onSubmit?.();
|
||||
modals.closeSelf();
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<Alert
|
||||
title="Subtitles"
|
||||
color="gray"
|
||||
icon={<FontAwesomeIcon icon={faInfoCircle}></FontAwesomeIcon>}
|
||||
>
|
||||
<Text size="sm">{selections.length} subtitles selected</Text>
|
||||
</Alert>
|
||||
<Selector
|
||||
clearable
|
||||
disabled={subtitles.length === 0 || selections.length !== 1}
|
||||
label="Reference"
|
||||
placeholder="Default: choose automatically within video file"
|
||||
options={subtitles}
|
||||
{...form.getInputProps("reference")}
|
||||
></Selector>
|
||||
<Selector
|
||||
clearable
|
||||
label="Max Offset Seconds"
|
||||
options={syncMaxOffsetSecondsOptions}
|
||||
placeholder="Select..."
|
||||
{...form.getInputProps("maxOffsetSeconds")}
|
||||
></Selector>
|
||||
<Checkbox
|
||||
label="No Fix Framerate"
|
||||
{...form.getInputProps("noFixFramerate")}
|
||||
></Checkbox>
|
||||
<Checkbox
|
||||
label="Golden-Section Search"
|
||||
{...form.getInputProps("gss")}
|
||||
></Checkbox>
|
||||
<Divider></Divider>
|
||||
<Button type="submit">Sync</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export const SyncSubtitleModal = withModal(SyncSubtitleForm, "sync-subtitle", {
|
||||
title: "Sync Subtitle Options",
|
||||
size: "lg",
|
||||
});
|
||||
|
||||
export default SyncSubtitleForm;
|
@ -0,0 +1,12 @@
|
||||
import { SelectorOption } from "@/components";
|
||||
|
||||
export const antiCaptchaOption: SelectorOption<string>[] = [
|
||||
{
|
||||
label: "Anti-Captcha",
|
||||
value: "anti-captcha",
|
||||
},
|
||||
{
|
||||
label: "Death by Captcha",
|
||||
value: "death-by-captcha",
|
||||
},
|
||||
];
|
Loading…
Reference in new issue