fix: store source video coordinates in pause points for correct re-rendering
The re-render task was using pause point coordinates from the accessible video timeline (which includes freeze frame durations) instead of the original source video coordinates. This caused pause points to exceed the source video duration and get clamped incorrectly. Changes: - Add source_ms field to PausePointData model to store source video cut point - Update video_renderer.py to populate source_ms when building pause points - Update rerender_accessible_video.py to use source_ms for placement calculations - Apply user adjustments as relative offsets (delta-based adjustment) - Update API responses and TypeScript types to include source_ms - Add backward compatibility fallback for jobs without source_ms Note: Existing jobs need to be re-processed from initial render to populate the new source_ms field. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a59dbb60ac
commit
a6cd4cde07
6 changed files with 41 additions and 11 deletions
|
|
@ -1532,6 +1532,7 @@ async def get_accessible_video_edit_state(
|
|||
PausePointResponse(
|
||||
cue_index=pp.get("cue_index"),
|
||||
original_ms=pp.get("original_ms"),
|
||||
source_ms=pp.get("source_ms", pp.get("original_ms")), # Fallback for old data
|
||||
adjusted_ms=pp.get("adjusted_ms"),
|
||||
min_bound_ms=pp.get("min_bound_ms"),
|
||||
max_bound_ms=pp.get("max_bound_ms")
|
||||
|
|
@ -1648,6 +1649,7 @@ async def update_pause_point(
|
|||
return PausePointResponse(
|
||||
cue_index=pause_point["cue_index"],
|
||||
original_ms=pause_point["original_ms"],
|
||||
source_ms=pause_point.get("source_ms", pause_point["original_ms"]), # Fallback for old data
|
||||
adjusted_ms=pause_point["adjusted_ms"],
|
||||
min_bound_ms=pause_point["min_bound_ms"],
|
||||
max_bound_ms=pause_point["max_bound_ms"]
|
||||
|
|
|
|||
|
|
@ -68,7 +68,8 @@ class RequestedOutputs(BaseModel):
|
|||
class PausePointData(BaseModel):
|
||||
"""Pause point timing data for accessible video editing during QC."""
|
||||
cue_index: int # AD cue index this pause point belongs to
|
||||
original_ms: float # Original pause point timestamp (ms)
|
||||
original_ms: float # Rendered timeline position (ms) - for UI display
|
||||
source_ms: float # Source video cut point (ms) - for re-rendering
|
||||
adjusted_ms: Optional[float] = None # User-adjusted timestamp (ms), None = use original
|
||||
min_bound_ms: float # Minimum allowed value (end of previous AD segment)
|
||||
max_bound_ms: float # Maximum allowed value (start of next AD segment)
|
||||
|
|
|
|||
|
|
@ -130,7 +130,8 @@ class AccessibleVideoProgress(BaseModel):
|
|||
class PausePointResponse(BaseModel):
|
||||
"""Pause point timing data for QC editing."""
|
||||
cue_index: int = Field(..., description="AD cue index this pause point belongs to")
|
||||
original_ms: float = Field(..., description="Original pause point timestamp (ms)")
|
||||
original_ms: float = Field(..., description="Rendered timeline position (ms) - for display")
|
||||
source_ms: float = Field(..., description="Source video cut point (ms) - for re-rendering")
|
||||
adjusted_ms: Optional[float] = Field(None, description="User-adjusted timestamp (ms)")
|
||||
min_bound_ms: float = Field(..., description="Minimum allowed value (ms)")
|
||||
max_bound_ms: float = Field(..., description="Maximum allowed value (ms)")
|
||||
|
|
|
|||
|
|
@ -874,7 +874,8 @@ class VideoRendererService:
|
|||
|
||||
pause_point_data_list.append(PausePointData(
|
||||
cue_index=cue_index,
|
||||
original_ms=pause_ms,
|
||||
original_ms=pause_ms, # Rendered timeline position
|
||||
source_ms=p["pause_point"] * 1000, # Source video cut point
|
||||
adjusted_ms=None,
|
||||
min_bound_ms=min_bound_ms,
|
||||
max_bound_ms=max_bound_ms
|
||||
|
|
|
|||
|
|
@ -418,24 +418,48 @@ def _build_placements_with_adjustments(
|
|||
"""
|
||||
Build placement instructions using adjusted pause points from QC edits.
|
||||
|
||||
Uses source_ms (source video coordinates) for pause point calculations,
|
||||
applying user adjustments as relative offsets.
|
||||
|
||||
Args:
|
||||
ad_vtt_content: AD VTT content
|
||||
cue_durations: TTS durations per cue
|
||||
pause_points: Pause point data with original and adjusted values
|
||||
pause_points: Pause point data with source_ms, original_ms, and adjusted values
|
||||
|
||||
Returns:
|
||||
List of placement dicts
|
||||
"""
|
||||
cues = VTTParser.parse(ad_vtt_content)
|
||||
|
||||
# Build lookup of adjusted pause points by cue index
|
||||
# Build lookup of pause points by cue index using SOURCE coordinates
|
||||
adjusted_pause_by_cue = {}
|
||||
for pp in pause_points:
|
||||
cue_idx = pp.get("cue_index")
|
||||
adjusted = pp.get("adjusted_ms")
|
||||
original = pp.get("original_ms")
|
||||
# Use adjusted if set, otherwise original (in seconds)
|
||||
pause_time_s = (adjusted if adjusted is not None else original) / 1000.0
|
||||
source_ms = pp.get("source_ms")
|
||||
original_ms = pp.get("original_ms")
|
||||
adjusted_ms = pp.get("adjusted_ms")
|
||||
|
||||
# Fallback for data without source_ms (backward compatibility)
|
||||
if source_ms is None:
|
||||
logger.warning(
|
||||
f"Cue {cue_idx}: No source_ms found, falling back to original_ms. "
|
||||
"Job may need to be re-processed from initial render."
|
||||
)
|
||||
source_ms = original_ms
|
||||
|
||||
# Apply user adjustment as relative offset
|
||||
if adjusted_ms is not None and original_ms is not None:
|
||||
# User adjusted in rendered timeline - apply same delta to source
|
||||
adjustment_delta = adjusted_ms - original_ms
|
||||
source_ms = source_ms + adjustment_delta
|
||||
logger.info(
|
||||
f"Cue {cue_idx}: Applying adjustment delta {adjustment_delta:.1f}ms "
|
||||
f"(rendered: {original_ms:.1f} -> {adjusted_ms:.1f}, "
|
||||
f"source: {source_ms - adjustment_delta:.1f} -> {source_ms:.1f})"
|
||||
)
|
||||
|
||||
# Convert to seconds for placement
|
||||
pause_time_s = source_ms / 1000.0
|
||||
adjusted_pause_by_cue[cue_idx] = pause_time_s
|
||||
|
||||
placements = []
|
||||
|
|
@ -443,7 +467,7 @@ def _build_placements_with_adjustments(
|
|||
if i >= len(cue_durations):
|
||||
break
|
||||
|
||||
# Get pause point: use adjusted value if available
|
||||
# Get pause point: use source-based value if available, otherwise fall back to VTT
|
||||
pause_point = adjusted_pause_by_cue.get(i, cue.start_time)
|
||||
|
||||
placements.append({
|
||||
|
|
|
|||
|
|
@ -336,7 +336,8 @@ export interface ReviewNotesListResponse {
|
|||
|
||||
export interface PausePointData {
|
||||
cue_index: number;
|
||||
original_ms: number;
|
||||
original_ms: number; // Rendered timeline position (for display)
|
||||
source_ms: number; // Source video cut point (for re-rendering)
|
||||
adjusted_ms: number | null;
|
||||
min_bound_ms: number;
|
||||
max_bound_ms: number;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue