Files
opendance-web/web-server/src/entities/motion.entity.ts
T
opendance aad60cbd2e feat(motions): 动作预览——骨架缩略图 + 管理端 3D 回放(契约 data-models 0.6.0)
按《DSH 开发文档 v1.6》4.11(T-WEB-014,用户标注「重要」):

服务端:
- 抽出公共 PNG 编码器 src/common/png.ts(模型缩略图改为复用,零原生依赖不变)
- 新增 src/motions/motion-thumbnail.ts:关键点序列 -> 正视图骨架 PNG
  (复用 33 点 PARENT_TABLE 连线,包围盒按整段计算,低置信度点不画)
- motions 增 thumbnail_asset_id(迁移 AddMotionThumbnail)+ AssetKind.MotionThumbnail
- /preprocess 生成缩略图并落库,失败只记 warning;MotionDto 增 thumbnailUrl(契约 0.6.0)

管理端:
- 新增 MotionPreview:懒加载 NDJSON -> 骨架线框 + 关节实例球体
  支持播放/暂停、上一帧/下一帧、时间轴拖动、0.25x-2x 变速、循环
  叠加帧号/当前时间/总时长,按整段包围盒自动取景
  three.js 单独分包(lazy + Suspense),主包 241 kB 不涨
- 动作列表新增 40px 骨架缩略图列(点击开预览)+ 预览按钮 + 内联预览面板
- 新增 admin/src/lib/keypoint-sequence.ts 与 lib/skeleton-topology.ts

测试:新增 8 项;规模 18 文件 / 237 项。版本 0.5.0。
2026-09-17 00:21:09 +08:00

98 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { bigintNumber, decimalNumber } from '../common/transformers';
import { MotionFormat, PreprocessState, PublishStatus, SourceType } from '../common/enums';
import { Song } from './song.entity';
/** 动作数据 —— 契约 data-models.md §3 */
@Entity('motions')
@Index('idx_motions_song_id', ['songId'])
@Index('idx_motions_status', ['status'])
@Index('idx_motions_format', ['format'])
export class Motion {
@PrimaryGeneratedColumn({ type: 'bigint' })
id!: number;
@Column({ name: 'song_id', type: 'bigint', nullable: true, transformer: bigintNumber })
songId!: number | null;
@ManyToOne(() => Song, { nullable: true, onDelete: 'SET NULL' })
@JoinColumn({ name: 'song_id' })
song?: Song | null;
@Column({ type: 'varchar', length: 200 })
name!: string;
@Column({ type: 'varchar', length: 24 })
format!: MotionFormat;
@Column({ name: 'file_asset_id', type: 'bigint', nullable: true, transformer: bigintNumber })
fileAssetId!: number | null;
@Column({ type: 'decimal', precision: 8, scale: 3, nullable: true, transformer: decimalNumber })
fps!: number | null;
@Column({ name: 'frame_count', type: 'int', nullable: true })
frameCount!: number | null;
@Column({ name: 'duration_ms', type: 'int', nullable: true })
durationMs!: number | null;
// ---- 预处理产物(T-WEB-006)----
@Column({ name: 'keypoint_format', type: 'varchar', length: 32, nullable: true })
keypointFormat!: string | null;
@Column({ name: 'keypoint_asset_id', type: 'bigint', nullable: true, transformer: bigintNumber })
keypointAssetId!: number | null;
@Column({ name: 'keypoint_frames', type: 'int', nullable: true })
keypointFrames!: number | null;
/** 骨架缩略图资产(预处理时生成;失败不影响预处理) */
@Column({ name: 'thumbnail_asset_id', type: 'bigint', nullable: true, transformer: bigintNumber })
thumbnailAssetId!: number | null;
@Column({ name: 'preprocess_state', type: 'varchar', length: 16, default: PreprocessState.None })
preprocessState!: PreprocessState;
@Column({ name: 'preprocess_error', type: 'text', nullable: true })
preprocessError!: string | null;
// ---- 来源五元组(必填)----
@Column({ name: 'source_type', type: 'varchar', length: 32 })
sourceType!: SourceType;
@Column({ name: 'source_name', type: 'varchar', length: 200 })
sourceName!: string;
@Column({ name: 'source_url', type: 'varchar', length: 500 })
sourceUrl!: string;
@Column({ type: 'varchar', length: 100 })
license!: string;
@Column({ type: 'varchar', length: 200 })
author!: string;
@Column({ type: 'varchar', length: 16, default: PublishStatus.Draft })
status!: PublishStatus;
@CreateDateColumn({ name: 'created_at', type: 'datetime' })
createdAt!: Date;
@UpdateDateColumn({ name: 'updated_at', type: 'datetime' })
updatedAt!: Date;
@DeleteDateColumn({ name: 'deleted_at', type: 'datetime', nullable: true })
deletedAt!: Date | null;
}