You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go调用Youtube API v3上传视频返回200但处理失败求助

问题描述

使用Go语言调用YouTube API v3上传视频,接口返回200 OK,但YouTube Studio中视频显示「processing abandoned」(处理已放弃)。上传代码如下:

func UploadVideo(w http.ResponseWriter, r *http.Request) {
    youtubeAuthToken := r.Header.Get("Authorization")

    const maxFileSize = 50 * 1024 * 1024 * 1024 // 50 GB
    r.ParseMultipartForm(maxFileSize)

    // FOR TESTING
    filePath := "D:\\feed-prototype\\internal\\services\\video.mp4" // Specify the path to your video file
    file, err := os.Open(filePath)
    if err != nil {
        http.Error(w, "Failed to open the video file", http.StatusInternalServerError)
        return
    }
    defer file.Close()
    // FOR TESTING

    //  creating a buffer to store the form data
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)

    //  Adding video file to the request
    part, err := writer.CreateFormFile("video/*", "video.mp4") // use handler.FileName when formData
    if err != nil {
        http.Error(w, "Failed to create form file", http.StatusInternalServerError)
        return
    }
    _, err = io.Copy(part, file)
    if err != nil {
        http.Error(w, "Failed to copy file data", http.StatusInternalServerError)
        return
    }

    //  Video meta data
    var videoData utils.VideoData
    videoData.Snippet.Title = "My Video Title"
    videoData.Snippet.Description = "My Video Description"
    videoData.Snippet.Tags = []string{"tag1", "tag2", "tag3"}
    videoData.Snippet.CategoryId = "22" // Specify the category ID for your video
    videoData.Snippet.DefaultLanguage = "en-US"
    videoData.Status.PrivacyStatus = "private"

    metadata, err := json.Marshal(videoData)
    if err != nil {
        http.Error(w, "Failed to marshal video metadata", http.StatusInternalServerError)
        return
    }

    metaPart, err := writer.CreateFormField("json")
    if err != nil {
        http.Error(w, "Failed to create form field", http.StatusInternalServerError)
        return
    }

    _, err = metaPart.Write(metadata)
    if err != nil {
        http.Error(w, "Failed to write form field data", http.StatusInternalServerError)
        return
    }

    err = writer.Close()
    if err != nil {
        http.Error(w, "Failed to close multipart writer", http.StatusInternalServerError)
        return
    }

    //  Creating HTTP request
    req, err := http.NewRequest("POST", "https://www.googleapis.com/upload/youtube/v3/videos?part=snippet&part=status"+"&key="+apiKey+"&access_token="+youtubeAuthToken, body)
    if err != nil {
        http.Error(w, "Failed to create request", http.StatusInternalServerError)
        return
    }

    req.Header.Set("Content-Type", "application/octet-stream")
    req.Header.Set("Accept", "application/json")

    // Sending the request
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        http.Error(w, "Failed to send request", http.StatusInternalServerError)
        return
    }
    defer resp.Body.Close()

    responseBody, err := io.ReadAll(resp.Body)
    if err != nil {
        http.Error(w, "Failed to read response", http.StatusInternalServerError)
        return
    }

    // Handle the response
    if resp.StatusCode != http.StatusOK {
        fmt.Println(resp)
        http.Error(w, "Failed to upload video: "+resp.Status, http.StatusInternalServerError)
        return
    }

    // Process the response body as needed
    fmt.Println(string(responseBody))

    // Return a success message
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Video uploaded successfully"))
}

API返回响应如下:

{
  "kind": "youtube#video",
  "etag": "sZkdG8TxLSa-eDstdXTs9Iw887I",
  "id": "16wibcpe9ts",
  "snippet": {
    "publishedAt": "2023-06-06T14:00:05Z",
    "channelId": "UCKlDRAs3yyCLTqhVnrCrVBQ",
    "title": "unknown",
    "description": "",
    "license": "youtube",
    "embeddable": true,
    "publicStatsViewable": true
  }
}

视频为MP4格式,本地可正常播放,时长仅3秒。询问是否正确传递了视频文件和元数据,问题出在哪里。

问题分析与解决

你的代码存在几个关键错误,导致元数据未被API正确识别,视频处理失败:

  1. Content-Type设置错误
    你用multipart.NewWriter构建了多部分请求体,但却把请求头的Content-Type设为application/octet-stream,这会让YouTube API认为整个请求体是单一的视频文件,完全忽略你构建的多部分结构,导致元数据根本没被读取。
    正确做法是使用multipart/related类型(YouTube API要求的多部分格式),并指定生成的boundary。

  2. 表单字段名称错误

    • 视频文件的字段名必须是file,而非video/*
    • 元数据的字段名必须是metadata,而非json
      字段名不匹配会导致API无法识别对应数据,这也是响应中视频标题为unknown的核心原因——API没收到你设置的元数据。
  3. 令牌传递方式不规范
    你将access_token拼在URL参数中,规范做法是放在Authorization请求头里,格式为Bearer {token},避免潜在的参数解析问题。

修改后的关键代码片段

// 替换原multipart构建部分
body := &bytes.Buffer{}
boundary := "youtube_upload_boundary"
writer := multipart.NewWriter(body)
writer.SetBoundary(boundary)

// 先写入元数据部分
metaPart, err := writer.CreatePart(map[string][]string{
    "Content-Disposition": []string{"form-data; name=\"metadata\""},
    "Content-Type":        []string{"application/json"},
})
if err != nil {
    http.Error(w, "Failed to create metadata part", http.StatusInternalServerError)
    return
}
_, err = metaPart.Write(metadata)
if err != nil {
    http.Error(w, "Failed to write metadata", http.StatusInternalServerError)
    return
}

// 再写入视频文件部分
filePart, err := writer.CreatePart(map[string][]string{
    "Content-Disposition": []string{"form-data; name=\"file\"; filename=\"video.mp4\""},
    "Content-Type":        []string{"video/mp4"},
})
if err != nil {
    http.Error(w, "Failed to create file part", http.StatusInternalServerError)
    return
}
_, err = io.Copy(filePart, file)
if err != nil {
    http.Error(w, "Failed to copy file data", http.StatusInternalServerError)
    return
}

err = writer.Close()
if err != nil {
    http.Error(w, "Failed to close multipart writer", http.StatusInternalServerError)
    return
}

// 创建请求时修改URL和Header
req, err := http.NewRequest("POST", "https://www.googleapis.com/upload/youtube/v3/videos?part=snippet&part=status&key="+apiKey, body)
if err != nil {
    http.Error(w, "Failed to create request", http.StatusInternalServerError)
    return
}
// 设置正确的Content-Type
req.Header.Set("Content-Type", "multipart/related; boundary="+boundary)
req.Header.Set("Accept", "application/json")
// 用Header传递令牌,兼容原请求头可能带的Bearer前缀
req.Header.Set("Authorization", "Bearer "+strings.TrimPrefix(youtubeAuthToken, "Bearer "))

额外注意事项

  • 确认utils.VideoData结构体与YouTube API的Video结构完全匹配,特别是嵌套的snippet和status字段的JSON标签,避免序列化后字段名不匹配。
  • 虽然视频本地可播放,建议检查视频编码是否符合YouTube要求(如H.264视频编码+AAC音频编码),少数编码兼容问题也会导致处理失败。

内容的提问来源于stack exchange,提问作者vvspower

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 17:42:03