Create standalone browser port of LTX Athena with authenticated native API transport
This commit is contained in:
commit
1333b0c36b
202 files changed
+63054
No files matched your search
@@ -0,0 +1,109 @@
|
||||
import { useCallback } from 'react'
|
||||
import type { Asset, TimelineClip } from '../../types/project-model'
|
||||
import {
|
||||
selectActiveTimeline,
|
||||
selectAssets,
|
||||
selectClips,
|
||||
} from './editor-selectors'
|
||||
import { exportFcp7Xml } from '../../lib/timeline-import'
|
||||
import { useEditorStore } from './editor-store'
|
||||
|
||||
interface TimelineXmlExportClip {
|
||||
name: string
|
||||
filePath: string
|
||||
trackIndex: number
|
||||
type: 'video' | 'audio' | 'image'
|
||||
startTime: number
|
||||
duration: number
|
||||
trimStart: number
|
||||
sourceDuration: number
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
|
||||
function parseExportDimensions(resolution?: string): { width: number; height: number } {
|
||||
const match = resolution?.match(/(\d+)x(\d+)/)
|
||||
if (!match) return { width: 1920, height: 1080 }
|
||||
return {
|
||||
width: parseInt(match[1], 10),
|
||||
height: parseInt(match[2], 10),
|
||||
}
|
||||
}
|
||||
|
||||
function resolveLiveAsset(assets: Asset[], clip: TimelineClip): Asset {
|
||||
if (!clip.assetId) return clip.asset!
|
||||
return assets.find(asset => asset.id === clip.assetId) || clip.asset!
|
||||
}
|
||||
|
||||
function buildTimelineXmlExportClips(clips: TimelineClip[], assets: Asset[]): TimelineXmlExportClip[] {
|
||||
return clips
|
||||
.filter(clip => clip.asset && clip.type !== 'adjustment')
|
||||
.map(clip => {
|
||||
const asset = resolveLiveAsset(assets, clip)
|
||||
let filePath = asset.path
|
||||
|
||||
if (asset.takes && asset.takes.length > 0) {
|
||||
const takeIndex = clip.takeIndex ?? asset.activeTakeIndex ?? 0
|
||||
const take = asset.takes[Math.min(takeIndex, asset.takes.length - 1)]
|
||||
filePath = take.path
|
||||
}
|
||||
|
||||
const { width, height } = parseExportDimensions(asset.resolution)
|
||||
|
||||
return {
|
||||
name: clip.importedName || asset.path?.split(/[/\\]/).pop() || 'clip',
|
||||
filePath,
|
||||
trackIndex: clip.trackIndex,
|
||||
type: clip.type as 'video' | 'image' | 'audio',
|
||||
startTime: clip.startTime,
|
||||
duration: clip.duration,
|
||||
trimStart: clip.trimStart,
|
||||
sourceDuration: asset.duration || clip.duration,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function useTimelineXmlExport() {
|
||||
const activeTimeline = useEditorStore(selectActiveTimeline)
|
||||
const clips = useEditorStore(selectClips)
|
||||
const assets = useEditorStore(selectAssets)
|
||||
|
||||
const handleExportTimelineXml = useCallback(async () => {
|
||||
const timeline = activeTimeline
|
||||
if (!timeline) return
|
||||
|
||||
const xmlContent = exportFcp7Xml({
|
||||
name: timeline.name,
|
||||
fps: 24,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
clips: buildTimelineXmlExportClips(clips, assets),
|
||||
})
|
||||
|
||||
if (window.electronAPI?.showSaveDialog) {
|
||||
const filePath = await window.electronAPI.showSaveDialog({
|
||||
title: 'Export Timeline as FCP 7 XML',
|
||||
defaultPath: `${timeline.name}.xml`,
|
||||
filters: [{ name: 'FCP 7 XML', extensions: ['xml'] }],
|
||||
})
|
||||
if (filePath) {
|
||||
await window.electronAPI.saveFile({ filePath, data: xmlContent })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const blob = new Blob([xmlContent], { type: 'text/xml' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const anchor = document.createElement('a')
|
||||
anchor.href = url
|
||||
anchor.download = `${timeline.name}.xml`
|
||||
anchor.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}, [activeTimeline, assets, clips])
|
||||
|
||||
return {
|
||||
handleExportTimelineXml,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user