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正确识别,视频处理失败:
Content-Type设置错误
你用multipart.NewWriter构建了多部分请求体,但却把请求头的Content-Type设为application/octet-stream,这会让YouTube API认为整个请求体是单一的视频文件,完全忽略你构建的多部分结构,导致元数据根本没被读取。
正确做法是使用multipart/related类型(YouTube API要求的多部分格式),并指定生成的boundary。表单字段名称错误
- 视频文件的字段名必须是
file,而非video/* - 元数据的字段名必须是
metadata,而非json
字段名不匹配会导致API无法识别对应数据,这也是响应中视频标题为unknown的核心原因——API没收到你设置的元数据。
- 视频文件的字段名必须是
令牌传递方式不规范
你将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
相关产品推荐
相关产品推荐

