ca4a0b1303
- 用户数据隔离:所有用户数据按 users/{user_id}/ 隔离,Rust IPC 命令自治读取 auth.json
- 安全加固:delete_local_product/rename_local_product/export_product 增加前缀校验
- 移除音调(pitch)功能:从 VoiceSynthesis、projectStore、types 等完全移除
- 动态视频分辨率:根据素材最小高度自动选择 720p/1080p,9:16 比例强校验
- ASS 字幕按目标分辨率等比例缩放(720p 和 1080p 视觉一致)
- Canvas 预览支持参数化 playResY,预览与压制效果一致
- 配音合成增加台词字数校验弹窗(语速>1.0时要求更多字)
- BGM 默认音量从 25% 调至 15%
- 素材选择提示文案更新(9:16 比例,5-60秒)
- 视频校验从严格 1080x1920 改为 9:16 比例判断
- 背景图片弹窗宽度从 440px 放大到 560px
99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
|
||
interface AvatarMaterial {
|
||
id: string;
|
||
name: string;
|
||
duration: number;
|
||
path: string;
|
||
}
|
||
|
||
interface AvatarMaterialSelectorProps {
|
||
selectedAvatarMaterial: AvatarMaterial | null;
|
||
onSelectLocalVideo: () => void;
|
||
}
|
||
|
||
/**
|
||
* 人物素材选择卡片
|
||
* - 已选择时展示素材信息 + 更换按钮
|
||
* - 未选择时展示空状态 + 选择按钮
|
||
*/
|
||
const AvatarMaterialSelector: React.FC<AvatarMaterialSelectorProps> = ({
|
||
selectedAvatarMaterial,
|
||
onSelectLocalVideo,
|
||
}) => {
|
||
return (
|
||
<div className="panel-section">
|
||
<label className="panel-label">形象选择</label>
|
||
{selectedAvatarMaterial ? (
|
||
<div
|
||
style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
padding: 'var(--spacing-sm) var(--spacing-md)',
|
||
border: '1px solid var(--border-color)',
|
||
borderRadius: '8px',
|
||
background: 'var(--bg-secondary)',
|
||
}}
|
||
>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-sm)' }}>
|
||
<div
|
||
style={{
|
||
width: '36px',
|
||
height: '36px',
|
||
borderRadius: '50%',
|
||
background: 'var(--primary-light)',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
color: 'var(--primary)',
|
||
}}
|
||
>
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
|
||
<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2" />
|
||
<circle cx="12" cy="7" r="4" />
|
||
</svg>
|
||
</div>
|
||
<div>
|
||
<div style={{ fontSize: 'var(--font-sm)', fontWeight: 500 }}>{selectedAvatarMaterial.name}</div>
|
||
<div style={{ fontSize: 'var(--font-xs)', color: 'var(--text-secondary)' }}>
|
||
{selectedAvatarMaterial.duration.toFixed(1)}s
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<button className="btn btn-ghost btn-sm" onClick={onSelectLocalVideo}>
|
||
更换
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<div
|
||
style={{
|
||
padding: 'var(--spacing-md)',
|
||
border: '1px dashed var(--border-color)',
|
||
borderRadius: '8px',
|
||
textAlign: 'center',
|
||
color: 'var(--text-secondary)',
|
||
fontSize: 'var(--font-sm)',
|
||
}}
|
||
>
|
||
<p>未选择形象素材</p>
|
||
<p style={{ marginTop: '4px', fontSize: 'var(--font-xs)', opacity: 0.7 }}>
|
||
请选择人物出镜视频用于生成
|
||
</p>
|
||
<p style={{ marginTop: '4px', fontSize: 'var(--font-xs)', color: 'var(--text-tertiary)' }}>
|
||
格式:MP4 / MOV · 时长:5-60秒 · 比例:9:16(720×1280 或 1080×1920)
|
||
</p>
|
||
<button
|
||
className="btn btn-primary btn-sm"
|
||
style={{ marginTop: 'var(--spacing-sm)' }}
|
||
onClick={onSelectLocalVideo}
|
||
>
|
||
选择素材
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default AvatarMaterialSelector;
|