implemented the lower priority features; haven't tested them
This commit is contained in:
@ -14,6 +14,9 @@ import type {
|
||||
SilenceTrimGroup,
|
||||
TimelineMarker,
|
||||
Chapter,
|
||||
ZoomConfig,
|
||||
ClipInfo,
|
||||
BackgroundMusicConfig,
|
||||
} from '../types/project';
|
||||
|
||||
interface EditorState {
|
||||
@ -50,6 +53,10 @@ interface EditorState {
|
||||
|
||||
backendUrl: string;
|
||||
zonePreviewPaddingSeconds: number;
|
||||
|
||||
zoomConfig: ZoomConfig;
|
||||
additionalClips: ClipInfo[];
|
||||
backgroundMusic: BackgroundMusicConfig | null;
|
||||
}
|
||||
|
||||
interface EditorActions {
|
||||
@ -104,6 +111,12 @@ interface EditorActions {
|
||||
getWordAtTime: (time: number) => number;
|
||||
loadProject: (projectData: any) => void;
|
||||
reset: () => void;
|
||||
setZoomConfig: (config: Partial<ZoomConfig>) => void;
|
||||
addAdditionalClip: (path: string, label?: string) => void;
|
||||
removeAdditionalClip: (id: string) => void;
|
||||
reorderAdditionalClip: (id: string, direction: -1 | 1) => void;
|
||||
setBackgroundMusic: (config: BackgroundMusicConfig | null) => void;
|
||||
updateBackgroundMusic: (updates: Partial<BackgroundMusicConfig>) => void;
|
||||
}
|
||||
|
||||
const ZONE_PREVIEW_PADDING_KEY = 'talkedit-zone-preview-padding-seconds';
|
||||
@ -146,6 +159,9 @@ const initialState: EditorState = {
|
||||
exportProgress: 0,
|
||||
backendUrl: 'http://127.0.0.1:8000',
|
||||
zonePreviewPaddingSeconds: getStoredZonePreviewPaddingSeconds(),
|
||||
zoomConfig: { enabled: false, zoomFactor: 1, panX: 0, panY: 0 },
|
||||
additionalClips: [],
|
||||
backgroundMusic: null,
|
||||
};
|
||||
|
||||
let nextRangeId = 1;
|
||||
@ -190,7 +206,7 @@ export const useEditorStore = create<EditorState & EditorActions>()(
|
||||
setTranscriptionModel: (model) => set({ transcriptionModel: model }),
|
||||
|
||||
saveProject: (): ProjectFile => {
|
||||
const { videoPath, words, segments, cutRanges, muteRanges, gainRanges, speedRanges, globalGainDb, silenceTrimGroups, timelineMarkers, transcriptionModel, language, exportedAudioPath } = get();
|
||||
const { videoPath, words, segments, cutRanges, muteRanges, gainRanges, speedRanges, globalGainDb, silenceTrimGroups, timelineMarkers, transcriptionModel, language, exportedAudioPath, zoomConfig, additionalClips, backgroundMusic } = get();
|
||||
if (!videoPath) throw new Error('No video loaded');
|
||||
const now = new Date().toISOString();
|
||||
// Strip globalStartIndex (runtime-only field) before persisting.
|
||||
@ -214,8 +230,11 @@ export const useEditorStore = create<EditorState & EditorActions>()(
|
||||
silenceTrimGroups,
|
||||
timelineMarkers,
|
||||
language,
|
||||
createdAt: now, // will be overwritten if we track original creation time later
|
||||
createdAt: now,
|
||||
modifiedAt: now,
|
||||
zoomConfig,
|
||||
additionalClips,
|
||||
backgroundMusic: backgroundMusic ?? undefined,
|
||||
};
|
||||
},
|
||||
|
||||
@ -600,6 +619,43 @@ export const useEditorStore = create<EditorState & EditorActions>()(
|
||||
return lo < words.length ? lo : words.length - 1;
|
||||
},
|
||||
|
||||
setZoomConfig: (config) => {
|
||||
const { zoomConfig } = get();
|
||||
set({ zoomConfig: { ...zoomConfig, ...config } });
|
||||
},
|
||||
|
||||
addAdditionalClip: (path, label) => {
|
||||
const { additionalClips } = get();
|
||||
const id = `clip_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
||||
set({ additionalClips: [...additionalClips, { id, path, label: label || path.split(/[/\\]/).pop() || 'Clip' }] });
|
||||
},
|
||||
|
||||
removeAdditionalClip: (id) => {
|
||||
const { additionalClips } = get();
|
||||
set({ additionalClips: additionalClips.filter((c) => c.id !== id) });
|
||||
},
|
||||
|
||||
reorderAdditionalClip: (id, direction) => {
|
||||
const { additionalClips } = get();
|
||||
const idx = additionalClips.findIndex((c) => c.id === id);
|
||||
if (idx === -1) return;
|
||||
const target = idx + direction;
|
||||
if (target < 0 || target >= additionalClips.length) return;
|
||||
const reordered = [...additionalClips];
|
||||
[reordered[idx], reordered[target]] = [reordered[target], reordered[idx]];
|
||||
set({ additionalClips: reordered });
|
||||
},
|
||||
|
||||
setBackgroundMusic: (config) => {
|
||||
set({ backgroundMusic: config });
|
||||
},
|
||||
|
||||
updateBackgroundMusic: (updates) => {
|
||||
const { backgroundMusic } = get();
|
||||
if (!backgroundMusic) return;
|
||||
set({ backgroundMusic: { ...backgroundMusic, ...updates } });
|
||||
},
|
||||
|
||||
loadProject: (data) => {
|
||||
const { backendUrl, zonePreviewPaddingSeconds, projectFilePath } = get();
|
||||
const url = `${backendUrl}/file?path=${encodeURIComponent(data.videoPath)}`;
|
||||
@ -634,6 +690,9 @@ export const useEditorStore = create<EditorState & EditorActions>()(
|
||||
transcriptionModel: data.transcriptionModel ?? null,
|
||||
language: data.language || '',
|
||||
exportedAudioPath: data.exportedAudioPath ?? null,
|
||||
zoomConfig: data.zoomConfig || { enabled: false, zoomFactor: 1, panX: 0, panY: 0 },
|
||||
additionalClips: data.additionalClips || [],
|
||||
backgroundMusic: data.backgroundMusic || null,
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user