package academy import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "time" ) // HeyGenClient handles avatar video generation via the HeyGen API type HeyGenClient struct { apiKey string avatarID string client *http.Client } // NewHeyGenClient creates a new HeyGen client func NewHeyGenClient() *HeyGenClient { apiKey := os.Getenv("HEYGEN_API_KEY") avatarID := os.Getenv("HEYGEN_AVATAR_ID") if avatarID == "" { avatarID = "josh_lite3_20230714" // Default avatar } return &HeyGenClient{ apiKey: apiKey, avatarID: avatarID, client: &http.Client{ Timeout: 300 * time.Second, // Video generation can take time }, } } // IsConfigured returns true if API key is set func (c *HeyGenClient) IsConfigured() bool { return c.apiKey != "" } // CreateVideoRequest represents the HeyGen API request type CreateVideoRequest struct { VideoInputs []VideoInput `json:"video_inputs"` Dimension Dimension `json:"dimension"` } // VideoInput represents a single video segment type VideoInput struct { Character Character `json:"character"` Voice VideoVoice `json:"voice"` } // Character represents the avatar type Character struct { Type string `json:"type"` AvatarID string `json:"avatar_id"` } // VideoVoice represents the voice/audio source type VideoVoice struct { Type string `json:"type"` // "audio" for pre-generated audio AudioURL string `json:"audio_url,omitempty"` InputText string `json:"input_text,omitempty"` } // Dimension represents video dimensions type Dimension struct { Width int `json:"width"` Height int `json:"height"` } // CreateVideoResponse represents the HeyGen API response type CreateVideoResponse struct { Data struct { VideoID string `json:"video_id"` } `json:"data"` Error interface{} `json:"error"` } // HeyGenVideoStatus represents video status from HeyGen type HeyGenVideoStatus struct { Data struct { Status string `json:"status"` // processing, completed, failed VideoURL string `json:"video_url"` } `json:"data"` } // CreateVideo creates a video with the avatar and audio func (c *HeyGenClient) CreateVideo(audioURL string) (*CreateVideoResponse, error) { if !c.IsConfigured() { return nil, fmt.Errorf("HeyGen API key not configured") } url := "https://api.heygen.com/v2/video/generate" reqBody := CreateVideoRequest{ VideoInputs: []VideoInput{ { Character: Character{ Type: "avatar", AvatarID: c.avatarID, }, Voice: VideoVoice{ Type: "audio", AudioURL: audioURL, }, }, }, Dimension: Dimension{ Width: 1920, Height: 1080, }, } jsonBody, err := json.Marshal(reqBody) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } req, err := http.NewRequest("POST", url, bytes.NewReader(jsonBody)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Api-Key", c.apiKey) resp, err := c.client.Do(req) if err != nil { return nil, fmt.Errorf("HeyGen API request failed: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { return nil, fmt.Errorf("HeyGen API error %d: %s", resp.StatusCode, string(body)) } var result CreateVideoResponse if err := json.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &result, nil } // GetVideoStatus checks the status of a video generation job func (c *HeyGenClient) GetVideoStatus(videoID string) (*HeyGenVideoStatus, error) { if !c.IsConfigured() { return nil, fmt.Errorf("HeyGen API key not configured") } url := fmt.Sprintf("https://api.heygen.com/v1/video_status.get?video_id=%s", videoID) req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("X-Api-Key", c.apiKey) resp, err := c.client.Do(req) if err != nil { return nil, fmt.Errorf("HeyGen API request failed: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } var status HeyGenVideoStatus if err := json.Unmarshal(body, &status); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &status, nil }