按《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。
98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
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;
|
||
}
|