fix: 时间显示精度问题

This commit is contained in:
imeepos 2025-07-15 09:08:37 +08:00
parent 0a0c281ef6
commit be8c032158
2 changed files with 117 additions and 38 deletions

View File

@ -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());

View File

@ -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,31 +469,88 @@ 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) => {
<div key={segment.id} className="bg-white p-3 rounded border"> const segmentProps = parseSegmentProperties(segment.properties || null);
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-900">{segment.name}</span> return (
<span className="text-xs text-gray-500"> <div key={segment.id} className="bg-white p-3 rounded border">
{formatDuration(segment.start_time)} - {formatDuration(segment.end_time)} <div className="flex items-center justify-between mb-2">
</span> <span className="text-sm text-gray-900">{segment.name}</span>
</div> <span className="text-xs text-gray-500">
<div className="text-xs text-gray-500 space-y-1"> {formatDurationWithMs(segment.start_time)} - {formatDurationWithMs(segment.end_time)}
<div> </span>
<span>ID: <span className="font-mono text-orange-600">{segment.id}</span></span>
<span className="ml-3">: {segment.segment_index}</span>
</div> </div>
<div>: {formatDuration(segment.duration)}</div> <div className="text-xs text-gray-500 space-y-1">
{segment.template_material_id && (
<div> <div>
使ID: <span className="font-mono text-blue-600">{segment.template_material_id}</span> <span>ID: <span className="font-mono text-orange-600">{segment.id}</span></span>
<span className="ml-3">: {segment.segment_index}</span>
</div> </div>
)} <div className="flex items-center space-x-4">
{!segment.template_material_id && ( <span>: {formatDurationWithMs(segment.duration)}</span>
<div className="text-gray-400"></div> {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 && (
<div>
使ID: <span className="font-mono text-blue-600">{segment.template_material_id}</span>
</div>
)}
{!segment.template_material_id && (
<div className="text-gray-400"></div>
)}
</div>
</div> </div>
</div> );
))} })}
</div> </div>
)} )}
</div> </div>