Remove standalone services (ai-compliance-sdk root, developer-portal, dsms-gateway, dsms-node, night-scheduler) and legacy compliance/dsgvo pages. Add new SDK pipeline modules (academy, document-crawler, dsb-portal, incidents, whistleblower, reporting, sso, multi-tenant, industry-templates). Add drafting engine, legal corpus files (AT/CH/DE), pitch-deck, blog and Förderantrag pages. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
185 lines
4.4 KiB
Go
185 lines
4.4 KiB
Go
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
|
|
}
|