package runner import ( "context" "encoding/json" "fmt" "math" "os/exec" "strconv" "strings" "time" ) const generatedVideoMetadataProbeTimeout = 8 * time.Second type generatedVideoMetadata struct { Duration float64 HasAudio bool HasAudioKnown bool } type ffprobeVideoMetadata struct { Format struct { Duration string `json:"duration"` } `json:"format"` Streams []struct { CodecType string `json:"codec_type"` } `json:"streams"` } func (s *Service) enrichGeneratedVideoMetadata(ctx context.Context, taskKind string, result map[string]any) map[string]any { if taskKind != "videos.generations" { return result } data, _ := result["data"].([]any) if len(data) == 0 { return result } for _, raw := range data { item, _ := raw.(map[string]any) if len(item) == 0 || !isGeneratedVideoItem(item) { continue } needsDuration := floatFromAny(item["duration"]) <= 0 _, hasAudioMetadata := boolishOptional(firstPresentValue(item, "has_audio", "hasAudio")) if !needsDuration && hasAudioMetadata { continue } urlValue := firstNonEmptyStringValue(item, "video_url", "videoUrl", "url") if urlValue == "" { continue } metadata, err := s.probeVideoMetadata(ctx, urlValue) if err != nil { if s.logger != nil { s.logger.Debug("probe generated video metadata failed", "url", trimForLog(urlValue), "error", err) } continue } if needsDuration && metadata.Duration > 0 { item["duration"] = metadata.Duration } if !hasAudioMetadata && metadata.HasAudioKnown { item["has_audio"] = metadata.HasAudio } } return result } func isGeneratedVideoItem(item map[string]any) bool { itemType := strings.TrimSpace(stringFromAny(item["type"])) if itemType == "video" { return true } if firstNonEmptyStringValue(item, "video_url", "videoUrl") != "" { return true } urlValue := strings.ToLower(firstNonEmptyStringValue(item, "url")) return strings.Contains(urlValue, ".mp4") || strings.Contains(urlValue, ".mov") || strings.Contains(urlValue, ".webm") || strings.Contains(urlValue, ".m3u8") } func (s *Service) probeVideoMetadata(ctx context.Context, rawURL string) (generatedVideoMetadata, error) { if _, err := exec.LookPath("ffprobe"); err != nil { return generatedVideoMetadata{}, err } probeURL := rawURL if s != nil { if resolved, err := s.generatedAssetFetchURL(rawURL); err == nil && strings.TrimSpace(resolved) != "" { probeURL = resolved } } probeCtx, cancel := context.WithTimeout(ctx, generatedVideoMetadataProbeTimeout) defer cancel() cmd := exec.CommandContext( probeCtx, "ffprobe", "-v", "error", "-show_entries", "format=duration:stream=codec_type", "-of", "json", probeURL, ) output, err := cmd.Output() if err != nil { return generatedVideoMetadata{}, err } var probed ffprobeVideoMetadata if err := json.Unmarshal(output, &probed); err != nil { return generatedVideoMetadata{}, err } metadata := generatedVideoMetadata{} if durationText := strings.TrimSpace(probed.Format.Duration); durationText != "" { if duration, err := strconv.ParseFloat(durationText, 64); err == nil && duration > 0 && !math.IsNaN(duration) && !math.IsInf(duration, 0) { rounded := math.Round(duration) if rounded <= 0 { rounded = 1 } metadata.Duration = rounded } } if probed.Streams != nil { metadata.HasAudioKnown = true for _, stream := range probed.Streams { if strings.TrimSpace(stream.CodecType) == "audio" { metadata.HasAudio = true break } } } if metadata.Duration <= 0 && !metadata.HasAudioKnown { return metadata, fmt.Errorf("invalid video metadata: %q", trimForLog(string(output))) } return metadata, nil } func trimForLog(value string) string { value = strings.TrimSpace(value) if len(value) <= 120 { return value } return value[:120] + "..." }