TracksEditor.js
672 lines
| 1 | /** |
| 2 | * WordPress dependencies |
| 3 | */ |
| 4 | const { __, sprintf } = wp.i18n; |
| 5 | import "./TracksEditor.scss"; |
| 6 | const { |
| 7 | NavigableMenu, |
| 8 | MenuItem, |
| 9 | FormFileUpload, |
| 10 | MenuGroup, |
| 11 | ToolbarGroup, |
| 12 | ToolbarButton, |
| 13 | Dropdown, |
| 14 | Button, |
| 15 | TextControl, |
| 16 | Icon, |
| 17 | Modal, |
| 18 | } = wp.components; |
| 19 | const { MediaUpload, MediaUploadCheck } = wp.blockEditor; |
| 20 | const { useSelect } = wp.data; |
| 21 | const { useState, useRef } = wp.element; |
| 22 | import { TranscriptionLanguageTokenField } from "../components/TranscriptionLanguageSelect"; |
| 23 | |
| 24 | const ALLOWED_TYPES = ["text/vtt"]; |
| 25 | |
| 26 | const DEFAULT_KIND = "subtitles"; |
| 27 | |
| 28 | // const KIND_OPTIONS = [ |
| 29 | // { label: __("Subtitles"), value: "subtitles" }, |
| 30 | // { label: __("Captions"), value: "captions" }, |
| 31 | // { label: __("Descriptions"), value: "descriptions" }, |
| 32 | // { label: __("Chapters"), value: "chapters" }, |
| 33 | // { label: __("Metadata"), value: "metadata" }, |
| 34 | // ]; |
| 35 | |
| 36 | const captionIcon = ( |
| 37 | <svg viewBox="0 0 29 25" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| 38 | <path |
| 39 | fillRule="evenodd" |
| 40 | clipRule="evenodd" |
| 41 | d="M17.5014 20.2854H28.6316V0.764648H0.110825V20.2854H11.241L14.3712 24.2854L17.5014 20.2854ZM14.3712 21.0401L16.5269 18.2854H26.6316V2.76465H2.11082V18.2854H12.2155L14.3712 21.0401Z" |
| 42 | /> |
| 43 | <path d="M10.4503 14.9446C9.56226 14.9446 8.76226 14.7606 8.05026 14.3926C7.33826 14.0166 6.77826 13.4966 6.37026 12.8326C5.97026 12.1606 5.77026 11.4006 5.77026 10.5526C5.77026 9.70464 5.97026 8.94864 6.37026 8.28464C6.77826 7.61264 7.33826 7.09264 8.05026 6.72464C8.76226 6.34864 9.56226 6.16064 10.4503 6.16064C11.2663 6.16064 11.9943 6.30464 12.6343 6.59264C13.2743 6.88064 13.8023 7.29664 14.2183 7.84064L12.4303 9.43664C11.9103 8.78064 11.2983 8.45264 10.5943 8.45264C10.0023 8.45264 9.52626 8.64464 9.16626 9.02864C8.80626 9.40464 8.62626 9.91264 8.62626 10.5526C8.62626 11.1926 8.80626 11.7046 9.16626 12.0886C9.52626 12.4646 10.0023 12.6526 10.5943 12.6526C11.2983 12.6526 11.9103 12.3246 12.4303 11.6686L14.2183 13.2646C13.8023 13.8086 13.2743 14.2246 12.6343 14.5126C11.9943 14.8006 11.2663 14.9446 10.4503 14.9446Z" /> |
| 44 | <path d="M19.2042 14.9446C18.3162 14.9446 17.5162 14.7606 16.8042 14.3926C16.0922 14.0166 15.5322 13.4966 15.1242 12.8326C14.7242 12.1606 14.5242 11.4006 14.5242 10.5526C14.5242 9.70464 14.7242 8.94864 15.1242 8.28464C15.5322 7.61264 16.0922 7.09264 16.8042 6.72464C17.5162 6.34864 18.3162 6.16064 19.2042 6.16064C20.0202 6.16064 20.7482 6.30464 21.3882 6.59264C22.0282 6.88064 22.5562 7.29664 22.9722 7.84064L21.1842 9.43664C20.6642 8.78064 20.0522 8.45264 19.3482 8.45264C18.7562 8.45264 18.2802 8.64464 17.9202 9.02864C17.5602 9.40464 17.3802 9.91264 17.3802 10.5526C17.3802 11.1926 17.5602 11.7046 17.9202 12.0886C18.2802 12.4646 18.7562 12.6526 19.3482 12.6526C20.0522 12.6526 20.6642 12.3246 21.1842 11.6686L22.9722 13.2646C22.5562 13.8086 22.0282 14.2246 21.3882 14.5126C20.7482 14.8006 20.0202 14.9446 19.2042 14.9446Z" /> |
| 45 | </svg> |
| 46 | ); |
| 47 | |
| 48 | function TrackList({ tracks, onEditPress }) { |
| 49 | let content; |
| 50 | if (tracks.length === 0) { |
| 51 | content = ( |
| 52 | <p className="block-library-video-tracks-editor__tracks-informative-message"> |
| 53 | {__( |
| 54 | "Captions are .vtt files that help make your content more accesible to a wider range of users.", |
| 55 | "presto-player" |
| 56 | )} |
| 57 | </p> |
| 58 | ); |
| 59 | } else { |
| 60 | content = tracks.map((track, index) => { |
| 61 | return ( |
| 62 | <div |
| 63 | key={index} |
| 64 | className="block-library-video-tracks-editor__track-list-track" |
| 65 | > |
| 66 | <span> |
| 67 | {track.label} |
| 68 | {track.srcLang ? ` (${track.srcLang})` : ""} |
| 69 | </span> |
| 70 | <Button |
| 71 | isTertiary |
| 72 | onClick={() => onEditPress(index)} |
| 73 | aria-label={sprintf( |
| 74 | /* translators: %s: Label of the video text track e.g: "French subtitles" */ |
| 75 | __("Edit %s", "presto-player"), |
| 76 | track.label |
| 77 | )} |
| 78 | > |
| 79 | {__("Edit", "presto-player")} |
| 80 | </Button> |
| 81 | </div> |
| 82 | ); |
| 83 | }); |
| 84 | } |
| 85 | return ( |
| 86 | <MenuGroup |
| 87 | label={__("Captions", "presto-player")} |
| 88 | className="block-library-video-tracks-editor__track-list" |
| 89 | > |
| 90 | {content} |
| 91 | </MenuGroup> |
| 92 | ); |
| 93 | } |
| 94 | |
| 95 | function SingleTrackEditor({ |
| 96 | track, |
| 97 | onChange, |
| 98 | onClose, |
| 99 | onRemove, |
| 100 | isBunnyNet = false, |
| 101 | }) { |
| 102 | const { src = "", label = "", srcLang = "", kind = DEFAULT_KIND } = track; |
| 103 | const fileName = src.startsWith("blob:") |
| 104 | ? "" |
| 105 | : src.substring(src.lastIndexOf("/") + 1); |
| 106 | return ( |
| 107 | <NavigableMenu> |
| 108 | <div className="block-library-video-tracks-editor__single-track-editor"> |
| 109 | <span className="block-library-video-tracks-editor__single-track-editor-edit-track-label"> |
| 110 | {__("Edit caption track", "presto-player")} |
| 111 | </span> |
| 112 | <span> |
| 113 | {__("File", "presto-player")}: <b>{fileName}</b> |
| 114 | </span> |
| 115 | <div className="block-library-video-tracks-editor__single-track-editor-label-language"> |
| 116 | <TextControl |
| 117 | /* eslint-disable jsx-a11y/no-autofocus */ |
| 118 | autoFocus |
| 119 | /* eslint-enable jsx-a11y/no-autofocus */ |
| 120 | onChange={(newLabel) => |
| 121 | onChange({ |
| 122 | ...track, |
| 123 | label: newLabel, |
| 124 | }) |
| 125 | } |
| 126 | label={__("Label", "presto-player")} |
| 127 | value={label} |
| 128 | help={__("Title of track", "presto-player")} |
| 129 | disabled={isBunnyNet} |
| 130 | /> |
| 131 | <TextControl |
| 132 | onChange={(newSrcLang) => |
| 133 | onChange({ |
| 134 | ...track, |
| 135 | srcLang: newSrcLang, |
| 136 | }) |
| 137 | } |
| 138 | label={__("Source language", "presto-player")} |
| 139 | value={srcLang} |
| 140 | help={__("Language tag (en, fr, etc.)", "presto-player")} |
| 141 | disabled={isBunnyNet} |
| 142 | /> |
| 143 | </div> |
| 144 | {/* <SelectControl |
| 145 | className="block-library-video-tracks-editor__single-track-editor-kind-select" |
| 146 | options={KIND_OPTIONS} |
| 147 | value={kind} |
| 148 | label={__("Kind")} |
| 149 | onChange={(newKind) => { |
| 150 | if (newKind === DEFAULT_KIND) { |
| 151 | newKind = undefined; |
| 152 | } |
| 153 | onChange({ |
| 154 | ...track, |
| 155 | kind: newKind, |
| 156 | }); |
| 157 | }} |
| 158 | /> */} |
| 159 | <div className="block-library-video-tracks-editor__single-track-editor-buttons-container"> |
| 160 | <Button |
| 161 | isSecondary |
| 162 | onClick={() => { |
| 163 | const changes = {}; |
| 164 | let hasChanges = false; |
| 165 | if (label === "") { |
| 166 | changes.label = __("English", "presto-player"); |
| 167 | hasChanges = true; |
| 168 | } |
| 169 | if (srcLang === "") { |
| 170 | changes.srcLang = "en"; |
| 171 | hasChanges = true; |
| 172 | } |
| 173 | if (hasChanges) { |
| 174 | onChange({ |
| 175 | ...track, |
| 176 | ...changes, |
| 177 | }); |
| 178 | } |
| 179 | onClose(); |
| 180 | }} |
| 181 | > |
| 182 | {__("Close", "presto-player")} |
| 183 | </Button> |
| 184 | <Button isDestructive isLink onClick={onRemove}> |
| 185 | {__("Remove track", "presto-player")} |
| 186 | </Button> |
| 187 | </div> |
| 188 | </div> |
| 189 | </NavigableMenu> |
| 190 | ); |
| 191 | } |
| 192 | |
| 193 | export default function TracksEditor({ |
| 194 | tracks = [], |
| 195 | onChange, |
| 196 | transcribeLanguages = [], |
| 197 | onTranscribeLanguagesChange, |
| 198 | videoId, |
| 199 | videoType = "public", |
| 200 | }) { |
| 201 | const mediaUpload = useSelect((select) => { |
| 202 | return select("core/block-editor").getSettings().mediaUpload; |
| 203 | }, []); |
| 204 | const [trackBeingEdited, setTrackBeingEdited] = useState(null); |
| 205 | const [isTranscribing, setIsTranscribing] = useState(false); |
| 206 | const [trackToRemove, setTrackToRemove] = useState(null); |
| 207 | const [isDeletingTrack, setIsDeletingTrack] = useState(false); |
| 208 | const [showTranscribeConfirm, setShowTranscribeConfirm] = useState(false); |
| 209 | const [transcribeSuccess, setTranscribeSuccess] = useState(false); |
| 210 | const [transcribeError, setTranscribeError] = useState(false); |
| 211 | const dropdownOnCloseRef = useRef(null); |
| 212 | |
| 213 | /** |
| 214 | * Check if a track is from Bunny.net stream library |
| 215 | * Uses explicit source flag set by server-side code for robust detection |
| 216 | */ |
| 217 | const isBunnyNetCaption = (track) => { |
| 218 | if (!track || !track.source) { |
| 219 | return false; |
| 220 | } |
| 221 | return track.source === "bunny"; |
| 222 | }; |
| 223 | |
| 224 | /** |
| 225 | * Delete a caption from Bunny.net stream library |
| 226 | */ |
| 227 | const deleteBunnyNetCaption = async (track) => { |
| 228 | if (!videoId || !track) { |
| 229 | return; |
| 230 | } |
| 231 | |
| 232 | try { |
| 233 | // First, fetch the video data to get the Bunny.net GUID |
| 234 | const videoData = await wp.apiFetch({ |
| 235 | path: `presto-player/v1/videos/${videoId}`, |
| 236 | }); |
| 237 | |
| 238 | const bunnyGuid = videoData?.external_id; |
| 239 | if (!bunnyGuid) { |
| 240 | console.warn( |
| 241 | "Could not find Bunny.net video GUID for caption deletion" |
| 242 | ); |
| 243 | return; |
| 244 | } |
| 245 | |
| 246 | // Get the language code from the track |
| 247 | const language = |
| 248 | track.srcLang || track.src?.match(/\/captions\/([^/]+)\.vtt/)?.[1]; |
| 249 | if (!language) { |
| 250 | console.warn("Could not determine language code for caption deletion"); |
| 251 | return; |
| 252 | } |
| 253 | |
| 254 | // Delete the caption from Bunny.net |
| 255 | const deleteCaptionBase = |
| 256 | prestoPlayerAdmin?.transcriptionEndpoints?.deleteCaption; |
| 257 | const deletePath = `${deleteCaptionBase}/${bunnyGuid}/${language}`; |
| 258 | await wp.apiFetch({ |
| 259 | path: wp.url.addQueryArgs(deletePath, { |
| 260 | type: videoType, |
| 261 | }), |
| 262 | method: "DELETE", |
| 263 | }); |
| 264 | } catch (error) { |
| 265 | console.error("Error deleting caption from Bunny.net:", error); |
| 266 | // Show error notice |
| 267 | wp.data |
| 268 | .dispatch("core/notices") |
| 269 | .createNotice( |
| 270 | "error", |
| 271 | __( |
| 272 | "Failed to delete caption from Bunny.net. Please try again.", |
| 273 | "presto-player" |
| 274 | ), |
| 275 | { |
| 276 | type: "snackbar", |
| 277 | isDismissible: true, |
| 278 | } |
| 279 | ); |
| 280 | } |
| 281 | }; |
| 282 | |
| 283 | /** |
| 284 | * Handle track removal - show confirmation dialog if Bunny.net caption |
| 285 | */ |
| 286 | const handleTrackRemove = (trackIndex) => { |
| 287 | const track = tracks[trackIndex]; |
| 288 | |
| 289 | // Close the editor first to prevent accessing undefined track |
| 290 | setTrackBeingEdited(null); |
| 291 | |
| 292 | // If it's a Bunny.net caption, show confirmation dialog |
| 293 | if (isBunnyNetCaption(track)) { |
| 294 | setTrackToRemove({ track, index: trackIndex }); |
| 295 | } else { |
| 296 | // For non-Bunny.net tracks, remove directly |
| 297 | onChange(tracks.filter((_track, index) => index !== trackIndex)); |
| 298 | } |
| 299 | }; |
| 300 | |
| 301 | /** |
| 302 | * Confirm track removal - delete from Bunny.net if applicable |
| 303 | */ |
| 304 | const confirmTrackRemove = async () => { |
| 305 | if (!trackToRemove || isDeletingTrack) { |
| 306 | return; |
| 307 | } |
| 308 | |
| 309 | const { track, index } = trackToRemove; |
| 310 | |
| 311 | setIsDeletingTrack(true); |
| 312 | try { |
| 313 | // If it's a Bunny.net caption, delete it first |
| 314 | if (isBunnyNetCaption(track)) { |
| 315 | await deleteBunnyNetCaption(track); |
| 316 | } |
| 317 | |
| 318 | // Remove from tracks array |
| 319 | onChange(tracks.filter((_track, i) => i !== index)); |
| 320 | |
| 321 | // Close the dialog |
| 322 | setTrackToRemove(null); |
| 323 | } finally { |
| 324 | setIsDeletingTrack(false); |
| 325 | } |
| 326 | }; |
| 327 | |
| 328 | /** |
| 329 | * Handle Bunny.net caption generation confirmation |
| 330 | */ |
| 331 | const handleTranscribe = async () => { |
| 332 | if (!videoId || !transcribeLanguages.length) { |
| 333 | return; |
| 334 | } |
| 335 | |
| 336 | setIsTranscribing(true); |
| 337 | try { |
| 338 | const videoData = await wp.apiFetch({ |
| 339 | path: `presto-player/v1/videos/${videoId}`, |
| 340 | }); |
| 341 | const bunnyGuid = videoData?.external_id; |
| 342 | if (!bunnyGuid) { |
| 343 | throw new Error("Could not find Bunny.net video GUID"); |
| 344 | } |
| 345 | const transcribeEndpoint = |
| 346 | prestoPlayerAdmin?.transcriptionEndpoints?.transcribe; |
| 347 | await wp.apiFetch({ |
| 348 | path: transcribeEndpoint, |
| 349 | method: "POST", |
| 350 | data: { |
| 351 | guid: bunnyGuid, |
| 352 | type: videoType, |
| 353 | targetLanguages: transcribeLanguages, |
| 354 | }, |
| 355 | }); |
| 356 | setTranscribeSuccess(true); |
| 357 | } catch (error) { |
| 358 | const rawErrorMessage = |
| 359 | error?.message || |
| 360 | __("Failed to generate captions. Please try again.", "presto-player"); |
| 361 | console.error("Bunny transcription API error:", { |
| 362 | message: rawErrorMessage, |
| 363 | error: error, |
| 364 | }); |
| 365 | setTranscribeError(true); |
| 366 | } finally { |
| 367 | setIsTranscribing(false); |
| 368 | } |
| 369 | }; |
| 370 | |
| 371 | /** |
| 372 | * Get the appropriate modal title based on current state |
| 373 | */ |
| 374 | const getTranscribeModalTitle = () => { |
| 375 | if (transcribeSuccess) { |
| 376 | return __("Caption Generation Started!", "presto-player"); |
| 377 | } |
| 378 | if (transcribeError) { |
| 379 | return __("Caption Generation Failed", "presto-player"); |
| 380 | } |
| 381 | return __("Caption Generation Charges", "presto-player"); |
| 382 | }; |
| 383 | |
| 384 | if (!mediaUpload) { |
| 385 | return null; |
| 386 | } |
| 387 | return ( |
| 388 | <> |
| 389 | {trackToRemove && ( |
| 390 | <> |
| 391 | <Modal |
| 392 | className="presto-player__modal-confirm" |
| 393 | overlayClassName="presto-player__modal-confirm-overlay" |
| 394 | title={__("Remove Caption Track", "presto-player")} |
| 395 | onRequestClose={() => setTrackToRemove(null)} |
| 396 | > |
| 397 | <p> |
| 398 | {__( |
| 399 | "Removing this caption track will also delete it from the Bunny.net Stream Library. This action cannot be undone.", |
| 400 | "presto-player" |
| 401 | )} |
| 402 | </p> |
| 403 | <div className="presto-player__modal-actions"> |
| 404 | <Button |
| 405 | onClick={() => setTrackToRemove(null)} |
| 406 | disabled={isDeletingTrack} |
| 407 | > |
| 408 | {__("Cancel", "presto-player")} |
| 409 | </Button> |
| 410 | <Button |
| 411 | isDestructive |
| 412 | onClick={confirmTrackRemove} |
| 413 | isBusy={isDeletingTrack} |
| 414 | disabled={isDeletingTrack} |
| 415 | > |
| 416 | {__("Remove Track", "presto-player")} |
| 417 | </Button> |
| 418 | </div> |
| 419 | </Modal> |
| 420 | </> |
| 421 | )} |
| 422 | {showTranscribeConfirm && ( |
| 423 | <> |
| 424 | <Modal |
| 425 | className="presto-player__modal-transcribe" |
| 426 | overlayClassName="presto-player__modal-transcribe-overlay" |
| 427 | title={getTranscribeModalTitle()} |
| 428 | onRequestClose={() => { |
| 429 | setShowTranscribeConfirm(false); |
| 430 | setTranscribeSuccess(false); |
| 431 | setTranscribeError(false); |
| 432 | dropdownOnCloseRef.current?.(); |
| 433 | }} |
| 434 | > |
| 435 | {transcribeSuccess ? ( |
| 436 | <> |
| 437 | <p> |
| 438 | {__( |
| 439 | "Captions are being generated and will automatically appear on the player when ready. Depending on the length of the video, it may take few minutes. Please check back later.", |
| 440 | "presto-player" |
| 441 | )} |
| 442 | </p> |
| 443 | <div className="presto-player__modal-actions"> |
| 444 | <Button |
| 445 | isPrimary |
| 446 | onClick={() => { |
| 447 | setShowTranscribeConfirm(false); |
| 448 | setTranscribeSuccess(false); |
| 449 | dropdownOnCloseRef.current?.(); |
| 450 | }} |
| 451 | > |
| 452 | {__("Close", "presto-player")} |
| 453 | </Button> |
| 454 | </div> |
| 455 | </> |
| 456 | ) : transcribeError ? ( |
| 457 | <> |
| 458 | <p> |
| 459 | {__( |
| 460 | "We couldn't process your caption generation request. Please try again. If the issue persists, please re-upload the video to Bunny.net Stream Library or contact support@prestomade.com.", |
| 461 | "presto-player" |
| 462 | )} |
| 463 | </p> |
| 464 | <div className="presto-player__modal-actions"> |
| 465 | <Button |
| 466 | onClick={() => { |
| 467 | setShowTranscribeConfirm(false); |
| 468 | setTranscribeError(false); |
| 469 | dropdownOnCloseRef.current?.(); |
| 470 | }} |
| 471 | > |
| 472 | {__("Cancel", "presto-player")} |
| 473 | </Button> |
| 474 | <Button |
| 475 | isPrimary |
| 476 | onClick={() => { |
| 477 | setTranscribeError(false); |
| 478 | handleTranscribe(); |
| 479 | }} |
| 480 | > |
| 481 | {__("Try Again", "presto-player")} |
| 482 | </Button> |
| 483 | </div> |
| 484 | </> |
| 485 | ) : ( |
| 486 | <> |
| 487 | <p> |
| 488 | {__( |
| 489 | "This will automatically generate captions for the selected languages. You will be charged $0.10 per minute for each language from your Bunny.net account balance.", |
| 490 | "presto-player" |
| 491 | )} |
| 492 | </p> |
| 493 | <p className="presto-player__modal-note"> |
| 494 | <a |
| 495 | href="https://docs.bunny.net/docs/stream-pricing#transcribing" |
| 496 | target="_blank" |
| 497 | rel="noopener noreferrer" |
| 498 | className="presto-player__transcribe-pricing-link" |
| 499 | > |
| 500 | {__("View Bunny.net pricing details", "presto-player")} → |
| 501 | </a> |
| 502 | </p> |
| 503 | <div className="presto-player__modal-actions"> |
| 504 | <Button |
| 505 | onClick={() => { |
| 506 | setShowTranscribeConfirm(false); |
| 507 | setTranscribeSuccess(false); |
| 508 | setTranscribeError(false); |
| 509 | dropdownOnCloseRef.current?.(); |
| 510 | }} |
| 511 | disabled={isTranscribing} |
| 512 | > |
| 513 | {__("Cancel", "presto-player")} |
| 514 | </Button> |
| 515 | <Button |
| 516 | isPrimary |
| 517 | onClick={handleTranscribe} |
| 518 | isBusy={isTranscribing} |
| 519 | disabled={isTranscribing} |
| 520 | > |
| 521 | {__("Confirm & Generate", "presto-player")} |
| 522 | </Button> |
| 523 | </div> |
| 524 | </> |
| 525 | )} |
| 526 | </Modal> |
| 527 | </> |
| 528 | )} |
| 529 | <Dropdown |
| 530 | contentClassName="block-library-video-tracks-editor" |
| 531 | renderToggle={({ isOpen, onToggle }) => ( |
| 532 | <ToolbarGroup> |
| 533 | <ToolbarButton |
| 534 | label={__("Captions", "presto-player")} |
| 535 | showTooltip |
| 536 | aria-expanded={isOpen} |
| 537 | aria-haspopup="true" |
| 538 | onClick={onToggle} |
| 539 | icon={captionIcon} |
| 540 | /> |
| 541 | </ToolbarGroup> |
| 542 | )} |
| 543 | renderContent={({ onClose: closeDropdown }) => { |
| 544 | dropdownOnCloseRef.current = closeDropdown; |
| 545 | if (trackBeingEdited !== null && tracks[trackBeingEdited]) { |
| 546 | const currentTrack = tracks[trackBeingEdited]; |
| 547 | return ( |
| 548 | <SingleTrackEditor |
| 549 | track={currentTrack} |
| 550 | onChange={(newTrack) => { |
| 551 | const newTracks = [...tracks]; |
| 552 | newTracks[trackBeingEdited] = newTrack; |
| 553 | onChange(newTracks); |
| 554 | }} |
| 555 | onClose={() => setTrackBeingEdited(null)} |
| 556 | onRemove={() => { |
| 557 | handleTrackRemove(trackBeingEdited); |
| 558 | }} |
| 559 | isBunnyNet={isBunnyNetCaption(currentTrack)} |
| 560 | /> |
| 561 | ); |
| 562 | } |
| 563 | return ( |
| 564 | <> |
| 565 | <NavigableMenu> |
| 566 | <TrackList tracks={tracks} onEditPress={setTrackBeingEdited} /> |
| 567 | <MenuGroup |
| 568 | className="block-library-video-tracks-editor__add-tracks-container" |
| 569 | label={__("Add caption languages", "presto-player")} |
| 570 | > |
| 571 | <MediaUpload |
| 572 | onSelect={({ url }) => { |
| 573 | const trackIndex = tracks.length; |
| 574 | onChange([...tracks, { src: url }]); |
| 575 | setTrackBeingEdited(trackIndex); |
| 576 | }} |
| 577 | allowedTypes={ALLOWED_TYPES} |
| 578 | render={({ open }) => ( |
| 579 | <MenuItem icon={"media"} onClick={open}> |
| 580 | {__("Open Media Library", "presto-player")} |
| 581 | </MenuItem> |
| 582 | )} |
| 583 | /> |
| 584 | <MediaUploadCheck> |
| 585 | <FormFileUpload |
| 586 | onChange={(event) => { |
| 587 | const files = event.target.files; |
| 588 | const trackIndex = tracks.length; |
| 589 | mediaUpload({ |
| 590 | allowedTypes: ALLOWED_TYPES, |
| 591 | filesList: files, |
| 592 | onFileChange: ([{ url }]) => { |
| 593 | const newTracks = [...tracks]; |
| 594 | if (!newTracks[trackIndex]) { |
| 595 | newTracks[trackIndex] = {}; |
| 596 | } |
| 597 | newTracks[trackIndex] = { |
| 598 | ...tracks[trackIndex], |
| 599 | src: url, |
| 600 | }; |
| 601 | onChange(newTracks); |
| 602 | setTrackBeingEdited(trackIndex); |
| 603 | }, |
| 604 | }); |
| 605 | }} |
| 606 | accept=".vtt,text/vtt" |
| 607 | render={({ openFileDialog }) => { |
| 608 | return ( |
| 609 | <MenuItem |
| 610 | icon={"upload"} |
| 611 | onClick={() => { |
| 612 | openFileDialog(); |
| 613 | }} |
| 614 | > |
| 615 | {__("Upload", "presto-player")} |
| 616 | </MenuItem> |
| 617 | ); |
| 618 | }} |
| 619 | /> |
| 620 | </MediaUploadCheck> |
| 621 | </MenuGroup> |
| 622 | |
| 623 | {/* Automatic Caption Settings - Only show for Bunny.net videos */} |
| 624 | {onTranscribeLanguagesChange && ( |
| 625 | <MenuGroup |
| 626 | className="block-library-video-tracks-editor__transcription-container" |
| 627 | label={__("Automatic Captions", "presto-player")} |
| 628 | > |
| 629 | <div style={{ padding: "6px 12px" }}> |
| 630 | <TranscriptionLanguageTokenField |
| 631 | value={transcribeLanguages} |
| 632 | onChange={onTranscribeLanguagesChange} |
| 633 | showWarning={true} |
| 634 | /> |
| 635 | <div |
| 636 | style={{ |
| 637 | marginTop: "12px", |
| 638 | display: "flex", |
| 639 | gap: "8px", |
| 640 | }} |
| 641 | > |
| 642 | <Button |
| 643 | isPrimary |
| 644 | disabled={ |
| 645 | !transcribeLanguages || |
| 646 | transcribeLanguages.length === 0 || |
| 647 | isTranscribing || |
| 648 | !videoId |
| 649 | } |
| 650 | isBusy={isTranscribing} |
| 651 | onClick={() => { |
| 652 | if (!videoId || !transcribeLanguages.length) { |
| 653 | return; |
| 654 | } |
| 655 | setShowTranscribeConfirm(true); |
| 656 | }} |
| 657 | > |
| 658 | {__("Generate", "presto-player")} |
| 659 | </Button> |
| 660 | </div> |
| 661 | </div> |
| 662 | </MenuGroup> |
| 663 | )} |
| 664 | </NavigableMenu> |
| 665 | </> |
| 666 | ); |
| 667 | }} |
| 668 | /> |
| 669 | </> |
| 670 | ); |
| 671 | } |
| 672 |