import {Op, html, watch} from "@benev/slate" import {VideoEffect} from "../../../../context/types.js" import {Tooltip} from "../../../../views/tooltip/view.js" import xMarkSvg from "../../../../icons/gravity-ui/x-mark.svg.js" import {tooltipStyles} from "../../../../views/tooltip/styles.js" import exportSvg from "../../../../icons/gravity-ui/export.svg.js" import {StateHandler} from "../../../../views/state-handler/view.js" import {collaboration, shadow_view} from "../../../../context/context.js" import circleInfoSvg from "../../../../icons/gravity-ui/circle-info.svg.js" import {confirmModalStyles, exportOverlayStyles, styles} from "./styles.js" export const Export = shadow_view(use => () => { use.styles([styles, tooltipStyles]) use.watch(() => use.context.state) const state = use.context.state const video_export = use.context.controllers.video_export const [logs, setLogs, getLogs] = use.state([]) use.mount(() => { const dispose = collaboration.onChange(() => use.rerender()) return () => dispose() }) const isClient = collaboration.client use.mount(() => { const dispose = watch.track(() => use.context.state.log, (log) => { if(getLogs().length === 20) { const new_arr = getLogs().slice(1) setLogs(new_arr) } setLogs([...getLogs(), log]) }) return () => dispose() }) const renderAspectRatio = () => { const aspectRatio = state.settings.aspectRatio return html` Aspect Ratio ${aspectRatio === "21/9" ? "Ultra-Wide" : aspectRatio === "9/16" ? "Vertical" : aspectRatio === "16/9" ? "Wide" : aspectRatio === "1/1" ? "Square" : aspectRatio === "4/3" ? "Standard" : aspectRatio === "3/2" ? "Balanced" : null} ${aspectRatio} ` } return StateHandler(Op.all( use.context.helpers.ffmpeg.is_loading.value, use.context.is_webcodecs_supported.value), () => html` Export ${circleInfoSvg} Your video will export with the following settings: ${renderAspectRatio()} Resolution ${state.settings.width}x${state.settings.height} (${state.settings.standard}) Timebase ${state.timebase} fps Bitrate ${state.settings.bitrate} kbps ${Tooltip( html` video_export.export_start(use.context.state, state.settings.bitrate)} > ${exportSvg}Export `, html`${isClient ? "Only host can export" : null}` )} `) }) export const ExportInProgressOverlay = shadow_view((use) => () => { use.styles([exportOverlayStyles]) use.watch(() => use.context.state) const state = use.context.state const videoExport = use.context.controllers.video_export // Format progress to 2 decimal places const formattedProgress = state.export_status === "complete" || state.export_status === "flushing" ? 100 : Number.parseFloat(state.export_progress.toFixed(2)) // Get status text for display const getStatusText = () => { switch (state.export_status) { // case "preparing": // return "Preparing export..." case "composing": return "Exporting video..." case "flushing": return "Finalizing export..." case "complete": return "Export complete!" case "error": return "Export failed" default: return "Processing..." } } // Get variant for progress bar const getProgressVariant = () => { if (state.export_status === "error") return "danger" if (state.export_status === "complete") return "success" return "primary" } const handleSaveButtonMouseOver = () => { if (state.export_status === "complete") { use.rerender() } } const handleSaveButtonMouseOut = () => { use.rerender() } // Update visibility class and body scroll if (state.is_exporting) { use.element.classList.add("visible") document.body.style.overflow = "hidden" } else { use.element.classList.remove("visible") document.body.style.overflow = "" } return html` ${ state.is_exporting ? html` ${use.context.controllers.compositor.app.view} ` : "" } ${ state.export_status === "complete" ? html` Export Complete ` : state.export_status === "error" ? html` Export Failed ` : html` Exporting Video ` } ${formattedProgress}% ${getStatusText()} { videoExport.resetExporter(use.context.state) }} > ${state.export_status === "complete" ? "Continue Editing" : "Cancel Export"} videoExport.save_file()} class="save-button" @mouseover=${handleSaveButtonMouseOver} @mouseout=${handleSaveButtonMouseOut} > Save Video ` }) export const ExportConfirmModal = shadow_view(use => (showModal: boolean, setShowModal: (v: boolean) => void) => { use.watch(() => use.context.state) use.styles([confirmModalStyles]) const mediaController = use.context.controllers.media const [_, setFilesProgess, getFileProgress] = use.state>(new Map()) // get proxy videos on timeline const getProxies = () => { const files = [...mediaController.values()] return files.filter(file => file.kind === "video" && file.proxy && use.context.state.effects.some(e => e.kind === "video" && e.file_hash === file.hash)) } use.mount(() => { const dispose = mediaController.on_media_change(() => use.rerender()) const dispose1 = collaboration.onFileProgress(({hash, progress}) => { setFilesProgess(new Map(getFileProgress()).set(hash, progress)) }) return () => {dispose(); dispose1()} }) const dialog = use.defer(() => use.shadow.querySelector("dialog")) if(showModal) { dialog?.showModal() } else { dialog?.close() } const getProxyPreview = (hash: string) => { return (use.context.state.effects.find(e => e.kind === "video" && e.file_hash === hash) as VideoEffect).thumbnail } const getFileMetadata = (hash: string) => { return collaboration.filesMetada.find(([fileHash]) => hash === fileHash)?.[1] } const renderProxyFilesInProgressModal = () => { return html` Export Unavailable setShowModal(false)} class="close-modal">${xMarkSvg} We're still transferring the following files to you. Please wait until all transfers are complete before exporting the project. Pending files: ${getProxyFilesInProgress.map(([hash, file]) => html` ${getFileMetadata(hash)?.name} Progress: ${((file.received/file.total) * 100).toFixed()}% `)} ` } const renderProjectReadyModal = () => { return html` Your project is ready setShowModal(false)} class="close-modal">${xMarkSvg} All files have been transferred without proxies. You can now export your project in the highest quality. { use.context.controllers.video_export.export_start(use.context.state, use.context.state.settings.bitrate) setShowModal(false) }} > Export ` } const renderRequestOriginalFilesModal = () => { return html` Proxy Videos Detected setShowModal(false)} class="close-modal">${xMarkSvg} To ensure faster transfers within collaborative environment, some of your videos are proxies (same resolution, lower bitrate): Export options: Export with proxies { setShowModal(false) use.context.controllers.video_export.export_start(use.context.state, use.context.state.settings.bitrate) }} > Export Request original videos: ${getProxies().map(proxy => html` ${proxy?.file.name} collaboration.requestOriginalVideoFile(proxy!.hash)}> ${getFileProgress().get(proxy.hash) === 100 ? "Request" : `${getFileProgress().get(proxy.hash)?.toFixed()}%`} `)} ` } const getProxyFilesInProgress = collaboration.filesInProgress.filter(([_, file]) => file.proxy === true) return html` ${getProxyFilesInProgress.length !== 0 ? renderProxyFilesInProgressModal() : getProxies().length !== 0 ? renderRequestOriginalFilesModal() : renderProjectReadyModal() } ` })
${circleInfoSvg} Your video will export with the following settings:
We're still transferring the following files to you. Please wait until all transfers are complete before exporting the project.
All files have been transferred without proxies. You can now export your project in the highest quality.
To ensure faster transfers within collaborative environment, some of your videos are proxies (same resolution, lower bitrate):