fix: 时间显示精度问题
This commit is contained in:
parent
0a0c281ef6
commit
be8c032158
|
|
@ -613,15 +613,9 @@ impl DraftContentParser {
|
||||||
let target_duration = segment_raw.target_timerange.duration;
|
let target_duration = segment_raw.target_timerange.duration;
|
||||||
let end_time = start_time + target_duration;
|
let end_time = start_time + target_duration;
|
||||||
|
|
||||||
// 计算实际的播放时长(考虑播放速度)
|
// 播放速度不影响片段在时间轴上的时长
|
||||||
|
// target_duration 就是片段的实际时长
|
||||||
let speed = segment_raw.speed.unwrap_or(1.0);
|
let speed = segment_raw.speed.unwrap_or(1.0);
|
||||||
let actual_duration = if speed != 0.0 {
|
|
||||||
// 如果有播放速度,计算实际播放时长
|
|
||||||
// 例如:speed = 1.28306,则实际播放时长 = target_duration / speed
|
|
||||||
(target_duration as f64 / speed) as u64
|
|
||||||
} else {
|
|
||||||
target_duration
|
|
||||||
};
|
|
||||||
|
|
||||||
let segment_name = format!("片段{}", segment_index + 1);
|
let segment_name = format!("片段{}", segment_index + 1);
|
||||||
let mut segment = TrackSegment::new(
|
let mut segment = TrackSegment::new(
|
||||||
|
|
@ -633,28 +627,27 @@ impl DraftContentParser {
|
||||||
segment_index,
|
segment_index,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 更新片段的实际持续时间(考虑播放速度)
|
// 保持时间轴上的时长,不要覆盖为 actual_duration
|
||||||
segment.duration = actual_duration;
|
// segment.duration 应该是 end_time - start_time (时间轴时长)
|
||||||
|
// actual_duration 存储在 properties 中供前端使用
|
||||||
|
|
||||||
// 关联素材
|
// 关联素材
|
||||||
if let Some(material_id) = material_map.get(&segment_raw.material_id) {
|
if let Some(material_id) = material_map.get(&segment_raw.material_id) {
|
||||||
segment.associate_material(material_id.clone());
|
segment.associate_material(material_id.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存储原始属性和计算后的时间信息
|
// 存储原始属性信息
|
||||||
let properties = serde_json::json!({
|
let properties = serde_json::json!({
|
||||||
"original_material_id": segment_raw.material_id,
|
"original_material_id": segment_raw.material_id,
|
||||||
"target_timerange": segment_raw.target_timerange,
|
"target_timerange": segment_raw.target_timerange,
|
||||||
"source_timerange": segment_raw.source_timerange,
|
"source_timerange": segment_raw.source_timerange,
|
||||||
"speed": segment_raw.speed,
|
"speed": segment_raw.speed,
|
||||||
"actual_duration": actual_duration,
|
|
||||||
"target_duration": target_duration,
|
|
||||||
"visible": segment_raw.visible,
|
"visible": segment_raw.visible,
|
||||||
"volume": segment_raw.volume,
|
"volume": segment_raw.volume,
|
||||||
"computed_info": {
|
"speed_info": {
|
||||||
"speed_applied": speed != 1.0,
|
"is_speed_modified": speed != 1.0,
|
||||||
"original_speed": speed,
|
"speed_value": speed,
|
||||||
"time_scaling_factor": if speed != 0.0 { 1.0 / speed } else { 1.0 }
|
"speed_description": if speed > 1.0 { "加速播放" } else if speed < 1.0 { "减速播放" } else { "正常播放" }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
segment.properties = Some(properties.to_string());
|
segment.properties = Some(properties.to_string());
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,16 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
|
||||||
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 格式化时长(精确到毫秒)
|
||||||
|
const formatDurationWithMs = (microseconds: number) => {
|
||||||
|
const totalMs = Math.floor(microseconds / 1000);
|
||||||
|
const seconds = Math.floor(totalMs / 1000);
|
||||||
|
const ms = totalMs % 1000;
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const remainingSeconds = seconds % 60;
|
||||||
|
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
// 格式化文件大小
|
// 格式化文件大小
|
||||||
const formatFileSize = (bytes?: number) => {
|
const formatFileSize = (bytes?: number) => {
|
||||||
if (!bytes) return '未知';
|
if (!bytes) return '未知';
|
||||||
|
|
@ -163,6 +173,25 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解析片段属性信息
|
||||||
|
const parseSegmentProperties = (properties: string | null) => {
|
||||||
|
if (!properties) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(properties);
|
||||||
|
return {
|
||||||
|
speed: parsed.speed || 1.0,
|
||||||
|
source_timerange: parsed.source_timerange || null,
|
||||||
|
target_timerange: parsed.target_timerange || null,
|
||||||
|
speed_info: parsed.speed_info || null,
|
||||||
|
visible: parsed.visible,
|
||||||
|
volume: parsed.volume
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: 'overview', label: '概览', icon: Monitor },
|
{ id: 'overview', label: '概览', icon: Monitor },
|
||||||
{ id: 'materials', label: '素材', icon: FileText },
|
{ id: 'materials', label: '素材', icon: FileText },
|
||||||
|
|
@ -440,12 +469,15 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
|
||||||
|
|
||||||
{track.segments.length > 0 && (
|
{track.segments.length > 0 && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{track.segments.map((segment) => (
|
{track.segments.map((segment) => {
|
||||||
|
const segmentProps = parseSegmentProperties(segment.properties || null);
|
||||||
|
|
||||||
|
return (
|
||||||
<div key={segment.id} className="bg-white p-3 rounded border">
|
<div key={segment.id} className="bg-white p-3 rounded border">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-sm text-gray-900">{segment.name}</span>
|
<span className="text-sm text-gray-900">{segment.name}</span>
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500">
|
||||||
{formatDuration(segment.start_time)} - {formatDuration(segment.end_time)}
|
{formatDurationWithMs(segment.start_time)} - {formatDurationWithMs(segment.end_time)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500 space-y-1">
|
<div className="text-xs text-gray-500 space-y-1">
|
||||||
|
|
@ -453,7 +485,60 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
|
||||||
<span>片段ID: <span className="font-mono text-orange-600">{segment.id}</span></span>
|
<span>片段ID: <span className="font-mono text-orange-600">{segment.id}</span></span>
|
||||||
<span className="ml-3">索引: {segment.segment_index}</span>
|
<span className="ml-3">索引: {segment.segment_index}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>时长: {formatDuration(segment.duration)}</div>
|
<div className="flex items-center space-x-4">
|
||||||
|
<span>时长: {formatDurationWithMs(segment.duration)}</span>
|
||||||
|
{segmentProps?.speed && segmentProps.speed !== 1.0 && (
|
||||||
|
<span className={`px-2 py-0.5 rounded text-xs ${
|
||||||
|
segmentProps.speed > 1.0
|
||||||
|
? 'bg-red-100 text-red-800'
|
||||||
|
: 'bg-green-100 text-green-800'
|
||||||
|
}`}>
|
||||||
|
速度: {segmentProps.speed.toFixed(3)}x {segmentProps.speed > 1.0 ? '(加速)' : '(减速)'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{segmentProps?.speed === 1.0 && (
|
||||||
|
<span className="bg-gray-100 text-gray-600 px-2 py-0.5 rounded text-xs">正常速度</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* 播放速度不影响片段时长,移除实际播放时长显示 */}
|
||||||
|
{/* 时间轴位置信息 */}
|
||||||
|
{segmentProps?.target_timerange && (
|
||||||
|
<div className="bg-blue-50 p-2 rounded mt-1">
|
||||||
|
<div className="text-xs font-medium text-blue-700 mb-1">时间轴位置:</div>
|
||||||
|
<div className="text-xs text-blue-600 space-y-0.5">
|
||||||
|
<div>开始: {formatDurationWithMs(segmentProps.target_timerange.start)}</div>
|
||||||
|
<div>时长: {formatDurationWithMs(segmentProps.target_timerange.duration)}</div>
|
||||||
|
<div>结束: {formatDurationWithMs(segmentProps.target_timerange.start + segmentProps.target_timerange.duration)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 源素材时间范围 */}
|
||||||
|
{segmentProps?.source_timerange && (
|
||||||
|
<div className="bg-purple-50 p-2 rounded mt-1">
|
||||||
|
<div className="text-xs font-medium text-purple-700 mb-1">源素材时间范围:</div>
|
||||||
|
<div className="text-xs text-purple-600 space-y-0.5">
|
||||||
|
<div>开始位置: {formatDurationWithMs(segmentProps.source_timerange.start)}</div>
|
||||||
|
<div>截取时长: {formatDurationWithMs(segmentProps.source_timerange.duration)}</div>
|
||||||
|
<div>结束位置: {formatDurationWithMs(segmentProps.source_timerange.start + segmentProps.source_timerange.duration)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* 其他属性信息 */}
|
||||||
|
{(segmentProps?.visible !== undefined || segmentProps?.volume !== undefined) && (
|
||||||
|
<div className="bg-gray-50 p-2 rounded mt-1">
|
||||||
|
<div className="text-xs font-medium text-gray-700 mb-1">其他属性:</div>
|
||||||
|
<div className="text-xs text-gray-600 space-y-0.5">
|
||||||
|
{segmentProps?.visible !== undefined && (
|
||||||
|
<div>可见性: {segmentProps.visible ? '可见' : '隐藏'}</div>
|
||||||
|
)}
|
||||||
|
{segmentProps?.volume !== undefined && (
|
||||||
|
<div>音量: {(segmentProps.volume * 100).toFixed(0)}%</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{segment.template_material_id && (
|
{segment.template_material_id && (
|
||||||
<div>
|
<div>
|
||||||
使用素材ID: <span className="font-mono text-blue-600">{segment.template_material_id}</span>
|
使用素材ID: <span className="font-mono text-blue-600">{segment.template_material_id}</span>
|
||||||
|
|
@ -464,7 +549,8 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue