import { open } from "@tauri-apps/plugin-dialog" import { CheckSquare, FileVideo, Folder, Loader2, Pause, RefreshCw, Square, Trash2, X } from "lucide-react" import { useCallback, useEffect, useState } from "react" import { useTranslation } from "react-i18next" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Label } from "@/components/ui/label" import { Progress } from "@/components/ui/progress" import { ScrollArea } from "@/components/ui/scroll-area" import { cn } from "@/lib/utils" import { RenderStatus } from "@/types/video-compiler" import { useRenderQueue } from "../hooks/use-render-queue" import { ExportSettings } from "../types/export-types" import { ExportPresets } from "./export-presets" interface BatchExportTabProps { onClose: () => void defaultSettings: ExportSettings } // Временное хранилище для настроек проектов interface ProjectExportConfig { projectPath: string projectName: string outputPath: string settings: ExportSettings } export function BatchExportTab({ onClose, defaultSettings }: BatchExportTabProps) { const { t } = useTranslation() const { renderJobs, isProcessing, activeJobsCount, addProjectsToQueue, startRenderQueue, cancelJob, cancelAllJobs, clearCompleted, refreshQueue, } = useRenderQueue() const [pendingProjects, setPendingProjects] = useState([]) const [globalSettings, setGlobalSettings] = useState(defaultSettings) const [selectedPresetId, setSelectedPresetId] = useState("custom") const [outputFolder, setOutputFolder] = useState("") // Обновляем очередь при монтировании useEffect(() => { void refreshQueue() }, [refreshQueue]) // Выбор папки для вывода const handleChooseOutputFolder = useCallback(async () => { try { const selected = await open({ directory: true, title: t("dialogs.export.selectOutputFolder"), }) if (selected) { setOutputFolder(selected) // Обновляем пути для всех ожидающих проектов setPendingProjects((prev) => prev.map((project) => ({ ...project, outputPath: `${selected}/${project.projectName}_export.${project.settings.format}`, })), ) } } catch (error) { console.error("Failed to select output folder:", error) } }, [t]) // Добавление проектов const handleAddProjects = useCallback(async () => { try { const projectPaths = await addProjectsToQueue() if (projectPaths.length === 0) return const newProjects: ProjectExportConfig[] = projectPaths.map((path) => { const projectName = path.split("/").pop()?.replace(".tls", "") || "Untitled" const fileName = globalSettings.fileName.replace("{project_name}", projectName) return { projectPath: path, projectName, outputPath: outputFolder ? `${outputFolder}/${fileName}.${globalSettings.format}` : "", settings: { ...globalSettings, fileName }, } }) setPendingProjects((prev) => [...prev, ...newProjects]) } catch (error) { console.error("Failed to add projects:", error) } }, [addProjectsToQueue, globalSettings, outputFolder]) // Применение пресета const handlePresetSelect = useCallback((preset: any) => { setSelectedPresetId(preset.id) if (preset.id !== "custom" && preset.settings) { const updates: Partial = {} if (preset.settings.format) updates.format = preset.settings.format if (preset.settings.resolution) updates.resolution = preset.settings.resolution if (preset.settings.fps) updates.frameRate = preset.settings.fps if (preset.settings.bitrate) updates.bitrate = preset.settings.bitrate if (preset.settings.bitrateMode) updates.bitrateMode = preset.settings.bitrateMode if (preset.settings.useHardwareAcceleration !== undefined) { updates.enableGPU = preset.settings.useHardwareAcceleration } setGlobalSettings((prev) => ({ ...prev, ...updates })) // Применяем к ожидающим проектам setPendingProjects((prev) => prev.map((project) => ({ ...project, settings: { ...project.settings, ...updates }, outputPath: project.outputPath.replace(/\.[^.]+$/, `.${updates.format || project.settings.format}`), })), ) } }, []) // Удаление проекта из очереди const removePendingProject = useCallback((index: number) => { setPendingProjects((prev) => prev.filter((_, i) => i !== index)) }, []) // Запуск экспорта const handleStartExport = useCallback(async () => { if (pendingProjects.length === 0 || !outputFolder) return // Преобразуем в формат для рендер очереди const projectsToRender = pendingProjects.map((project) => ({ path: project.projectPath, outputPath: project.outputPath, })) await startRenderQueue(projectsToRender) // Очищаем список ожидающих проектов setPendingProjects([]) }, [pendingProjects, outputFolder, startRenderQueue]) // Получение статистики const getStats = () => { const completed = renderJobs.filter((job) => job.status === RenderStatus.Completed).length const failed = renderJobs.filter((job) => job.status === RenderStatus.Failed).length const queued = renderJobs.filter((job) => job.status === RenderStatus.Pending).length const processing = renderJobs.filter((job) => job.status === RenderStatus.Processing).length return { total: renderJobs.length, completed, failed, queued, processing } } const stats = getStats() // Получение иконки статуса const getStatusIcon = (status: RenderStatus) => { switch (status) { case RenderStatus.Pending: return case RenderStatus.Processing: return case RenderStatus.Completed: return case RenderStatus.Failed: return case RenderStatus.Cancelled: return default: return } } return (
{/* Настройки экспорта */} {t("dialogs.export.batchSettings")} {t("dialogs.export.batchSettingsDescription")} {/* Пресеты */} {/* Папка вывода */}
{outputFolder || t("dialogs.export.noFolderSelected")}
{/* Проекты для добавления */} {pendingProjects.length > 0 && ( {t("dialogs.export.pendingProjects")} {t("dialogs.export.pendingProjectsCount", { count: pendingProjects.length })}
{pendingProjects.map((project, index) => (
{project.projectName}
{project.outputPath || t("dialogs.export.noOutputPath")}
))}
)} {/* Очередь рендеринга */}
{t("dialogs.export.renderQueue")} {activeJobsCount > 0 ? t("dialogs.export.activeJobs", { count: activeJobsCount }) : t("dialogs.export.noActiveJobs")}
{stats.completed > 0 && ( )}
{renderJobs.length === 0 ? (
{t("dialogs.export.emptyQueue")}
) : ( renderJobs.map((job) => (
{getStatusIcon(job.status)}
{job.project_name}
{job.output_path}
{job.status === RenderStatus.Processing && job.progress && (
{job.progress.message || `${Math.round(job.progress.percentage)}%`}
)} {job.status === RenderStatus.Failed && job.progress?.message && (
{job.progress.message}
)}
{job.status === RenderStatus.Processing && ( )}
)) )}
{/* Статистика */} {renderJobs.length > 0 && (
{t("dialogs.export.queueStats", { total: stats.total, completed: stats.completed, failed: stats.failed, })}
)}
{/* Кнопки управления */}
{isProcessing ? ( <> ) : ( <> )}
) }