From c70ec1ce1aff6072934add1f543576d5200c1b02 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 30 Aug 2024 18:45:49 +0100 Subject: [Video] Captions and alt text (#5009) * video settings modal in composer * show done button on web * rm download options * fix logic for showing settings button * add language picker (wip) * subtitle list with language select * send captions & alt text with video when posting * style "ensure you have selected a language" text * include aspect ratio with video * filter out captions where the lang is not set * rm log * fix label and add hint * minor scrubber fix --- src/view/com/composer/videos/SubtitleDialog.tsx | 265 ++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 src/view/com/composer/videos/SubtitleDialog.tsx (limited to 'src/view/com/composer/videos/SubtitleDialog.tsx') diff --git a/src/view/com/composer/videos/SubtitleDialog.tsx b/src/view/com/composer/videos/SubtitleDialog.tsx new file mode 100644 index 000000000..90a29b25d --- /dev/null +++ b/src/view/com/composer/videos/SubtitleDialog.tsx @@ -0,0 +1,265 @@ +import React, {useCallback} from 'react' +import {StyleProp, View, ViewStyle} from 'react-native' +import RNPickerSelect from 'react-native-picker-select' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {MAX_ALT_TEXT} from '#/lib/constants' +import {useEnforceMaxGraphemeCount} from '#/lib/strings/helpers' +import {LANGUAGES} from '#/locale/languages' +import {isWeb} from '#/platform/detection' +import {useLanguagePrefs} from '#/state/preferences' +import {atoms as a, useTheme, web} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import * as TextField from '#/components/forms/TextField' +import {CC_Stroke2_Corner0_Rounded as CCIcon} from '#/components/icons/CC' +import {PageText_Stroke2_Corner0_Rounded as PageTextIcon} from '#/components/icons/PageText' +import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' +import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' +import {Text} from '#/components/Typography' +import {SubtitleFilePicker} from './SubtitleFilePicker' + +interface Props { + altText: string + captions: {lang: string; file: File}[] + setAltText: (altText: string) => void + setCaptions: React.Dispatch< + React.SetStateAction<{lang: string; file: File}[]> + > +} + +export function SubtitleDialogBtn(props: Props) { + const control = Dialog.useDialogControl() + const {_} = useLingui() + + return ( + + + + + + + + ) +} + +function SubtitleDialogInner({ + altText, + setAltText, + captions, + setCaptions, +}: Props) { + const control = Dialog.useDialogContext() + const {_} = useLingui() + const t = useTheme() + const enforceLen = useEnforceMaxGraphemeCount() + const {primaryLanguage} = useLanguagePrefs() + + const handleSelectFile = useCallback( + (file: File) => { + setCaptions(subs => [ + ...subs, + { + lang: subs.some(s => s.lang === primaryLanguage) + ? '' + : primaryLanguage, + file, + }, + ]) + }, + [setCaptions, primaryLanguage], + ) + + const subtitleMissingLanguage = captions.some(sub => sub.lang === '') + + return ( + + + + Alt text + + + setAltText(enforceLen(evt, MAX_ALT_TEXT))} + maxLength={MAX_ALT_TEXT * 10} + multiline + numberOfLines={3} + onKeyPress={({nativeEvent}) => { + if (nativeEvent.key === 'Escape') { + control.close() + } + }} + /> + + + {isWeb && ( + <> + + + Captions (.vtt) + + = 4} + /> + + {captions.map((subtitle, i) => ( + + langCode(lang) === subtitle.lang || + !captions.some(s => s.lang === langCode(lang)), + )} + style={[i % 2 === 0 && t.atoms.bg_contrast_25]} + /> + ))} + + + )} + + {subtitleMissingLanguage && ( + + Ensure you have selected a language for each subtitle file. + + )} + + + + + + + + ) +} + +function SubtitleFileRow({ + language, + file, + otherLanguages, + setCaptions, + style, +}: { + language: string + file: File + otherLanguages: {code2: string; code3: string; name: string}[] + setCaptions: React.Dispatch< + React.SetStateAction<{lang: string; file: File}[]> + > + style: StyleProp +}) { + const {_} = useLingui() + const t = useTheme() + + const handleValueChange = useCallback( + (lang: string) => { + if (lang) { + setCaptions(subs => + subs.map(s => (s.lang === language ? {lang, file: s.file} : s)), + ) + } + }, + [setCaptions, language], + ) + + return ( + + + + {language === '' ? ( + + ) : ( + + )} + + {file.name} + + ({ + label: `${lang.name} (${langCode(lang)})`, + value: langCode(lang), + }))} + style={{viewContainer: {maxWidth: 200, flex: 1}}} + /> + + + + + + ) +} + +function langCode(lang: {code2: string; code3: string}) { + return lang.code2 || lang.code3 +} -- cgit 1.4.1