feat(course): add per-module narrated video pipeline
Each course module now carries a video record, the generator embeds a module video block, and the checker requires 15 H.264/AAC 720p videos between 3 and 8 minutes. The recorder prepares module narration, runs the module actions with the lesson overlay, records/mixes the video, and writes per-module reports; it also uses OPFS save/reopen paths. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import {applyAreaOperation} from "./area-layout";
|
||||
export const BLENDER_WORKSPACE_IDS = [
|
||||
"Layout",
|
||||
"Modeling",
|
||||
@@ -87,9 +86,7 @@ export type UICommand =
|
||||
| { type: "setActiveArea"; areaId: string }
|
||||
| { type: "toggleOperatorSearch"; open?: boolean }
|
||||
| { type: "toggleMenu"; menu?: string }
|
||||
| { type: "toggleSidebar"; visible?: boolean }
|
||||
| { type: "areaOperation"; workspaceId?: WorkspaceId; operation: import("./area-layout").AreaOperation; measured?: AreaIR[] }
|
||||
| { type: "setWorkspaceLayout"; workspaceId: WorkspaceId; areas: AreaIR[] };
|
||||
| { type: "toggleSidebar"; visible?: boolean };
|
||||
|
||||
function regions(): RegionIR[] {
|
||||
return [
|
||||
@@ -233,31 +230,5 @@ export function reduceUICommand(state: WebWorkspaceState, command: UICommand): W
|
||||
return { ...state, openMenu: state.openMenu === command.menu ? null : command.menu ?? null, operatorSearchOpen: null === command.menu ? state.operatorSearchOpen : false };
|
||||
case "toggleSidebar":
|
||||
return { ...state, sidebarVisible: command.visible ?? !state.sidebarVisible };
|
||||
case "areaOperation": {
|
||||
const workspaceId = command.workspaceId ?? state.context.workspaceId;
|
||||
const workspace = state.workspaces[workspaceId];
|
||||
if (!workspace) return state;
|
||||
const base = command.measured && command.measured.length > 0
|
||||
? { ...workspace, areas: command.measured, layoutMode: "custom" as const, revision: workspace.revision + 1 }
|
||||
: workspace;
|
||||
const next = applyAreaOperation({ ...base, layoutMode: "custom" }, command.operation);
|
||||
return {
|
||||
...state,
|
||||
workspaces: { ...state.workspaces, [workspaceId]: next },
|
||||
context: {
|
||||
...state.context,
|
||||
workspaceId,
|
||||
activeAreaId: next.activeAreaId,
|
||||
activeEditor: next.areas.find((area) => area.id === next.activeAreaId)?.editor ?? state.context.activeEditor,
|
||||
revision: state.context.revision + 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
case "setWorkspaceLayout": {
|
||||
const workspace = state.workspaces[command.workspaceId];
|
||||
if (!workspace) return state;
|
||||
const next = { ...workspace, areas: command.areas, layoutMode: "custom" as const, revision: workspace.revision + 1 };
|
||||
return { ...state, workspaces: { ...state.workspaces, [command.workspaceId]: next } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user