简单实现Ai音乐suno-api

本文由 ChatMoney团队出品

前言

在科技与艺术的交汇处,AI音乐创作正以其独特的魅力,引领着音乐产业的一次革命。不久前,AI音乐的浪潮席卷了整个创意领域,激发了无数音乐爱好者和技术开发者的无限想象。在这场音乐与科技的盛宴中,主流的AI音乐平台suno无疑成为了焦点,尽管它尚未对外开放API服务,但这并未阻止我们探索的脚步。

今天,我们将踏上一段奇妙的旅程,用Go语言这把精准而强大的工具,尝试构建一个简易的suno-api。

开发前准备

  1. golang开发环境。

  2. golang版本:1.21.0

  3. 可登录suno的环境

  4. 获取suno平台cookie

开发过程

1.安装gin框架

go get -u github.com/gin-gonic/gin

2.封装suno请求

  1. 相关结构体
package internal

import "time"

// GenerateReq generate-Req
type GenerateReq struct {
    GptDescriptionPrompt string `json:"gpt_description_prompt"`
    Prompt               string `json:"prompt"`
    Mv                   string `json:"mv"`
    Title                string `json:"title"`
    Tags                 string `json:"tags"`
}

// GenerateResp generate-Resp
type GenerateResp struct {
    BatchSize         int       `json:"batch_size"`
    Clips             []Clips   `json:"clips"`
    CreatedAt         time.Time `json:"created_at"`
    ID                string    `json:"id"`
    Status            string    `json:"status"`
    Metadata          `json:"metadata"`
    MajorModelVersion string `json:"major_model_version"`
}

// TokenResp token-Response
type TokenResp struct {
    Jwt    string
    Object string
}

// SidResp session-resp
type SidResp struct {
    Response struct {
       Object              string      `json:"object"`
       ID                  string      `json:"id"`
       Sessions            []Session   `json:"sessions"`
       SignIn              interface{} `json:"sign_in"`
       SignUp              interface{} `json:"sign_up"`
       LastActiveSessionID string      `json:"last_active_session_id"`
       CreatedAt           time.Time   `json:"created_at"`
       UpdatedAt           time.Time   `json:"updated_at"`
    } `json:"response"`
    Client interface{} `json:"client"`
}

// Clips clips
type Clips struct {
    Detail            string      `json:"detail"`
    Id                string      `json:"id"`
    VideoUrl          string      `json:"video_url"`
    AudioUrl          string      `json:"audio_url"`
    ImageUrl          string      `json:"image_url"`
    ImageLargeUrl     string      `json:"image_large_url"`
    MajorModelVersion string      `json:"major_model_version"`
    ModelName         string      `json:"model_name"`
    Metadata          *Metadata   `json:"metadata"`
    IsLiked           bool        `json:"is_liked"`
    UserId            string      `json:"user_id"`
    IsTrashed         bool        `json:"is_trashed"`
    Reaction          interface{} `json:"reaction"`
    CreatedAt         time.Time   `json:"created_at"`
    Status            string      `json:"status"`
    Title             string      `json:"title"`
    PlayCount         int         `json:"play_count"`
    UpvoteCount       int         `json:"upvote_count"`
    IsPublic          bool        `json:"is_public"`
}

// Metadata Metadata
type Metadata struct {
    Tags                 string      `json:"tags"`
    Prompt               string      `json:"prompt"`
    GptDescriptionPrompt string      `json:"gpt_description_prompt"`
    AudioPromptId        interface{} `json:"audio_prompt_id"`
    History              interface{} `json:"history"`
    ConcatHistory        interface{} `json:"concat_history"`
    Type                 string      `json:"type"`
    Duration             float64     `json:"duration"`
    RefundCredits        bool        `json:"refund_credits"`
    Stream               bool        `json:"stream"`
    ErrorType            interface{} `json:"error_type"`
    ErrorMessage         interface{} `json:"error_message"`
}

// Session sessionId
type Session struct {
    Object string `json:"object"`
    ID     string `json:"id"`
}
获取suno的token
  1. 实现逻辑为通过suno的cookie获取到sessionid,后通过sessionid获取token.

// GetToken getToken
func (s *service) GetToken(cookieString string) (token string, err error) {
   var cookies []*http.Cookie
   cookies = s.parseCookieString(cookieString)

   jar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
   sidUrl, _ := url.Parse(GetSidUrl)
   jar.SetCookies(sidUrl, cookies)

   client := &http.Client{
      Jar: jar,
   }

   sidResp, err := client.Get(sidUrl.String())
   if err != nil {
      return "", err
   }
   defer sidResp.Body.Close()

   var sidResponse SidResp
   _ = json.NewDecoder(sidResp.Body).Decode(&sidResponse)

   sid := ""
   if len(sidResponse.Response.Sessions) > 0 {
      sid = sidResponse.Response.Sessions[0].ID
      fmt.Println(sidResponse.Response.Sessions[0].ID)
   } else {
      err = errors.New("获取sessionId失败:Response.Sessions为空")
      return "", err
   }

   getTokenUrl := fmt.Sprintf("%s/%s/tokens?_clerk_js_version=4.72.0-snapshot.vc141245", GetTokenUrl, sid)
   tokenResp, err := client.Post(getTokenUrl, ContentType, nil)
   if err != nil {
      return "", err
   }
   defer tokenResp.Body.Close()

   var tokenResponse TokenResp
   bodyBytes, err := io.ReadAll(tokenResp.Body)
   if err != nil {
      return "", fmt.Errorf("error reading response body: %v", err)
   }

   if tokenResp.StatusCode != http.StatusOK {
      fmt.Printf("Error decoding JSON response: %v\nResponse body: %s\n", err, bodyBytes)
      fmt.Printf("请求失败,状态码: %d, 响应内容: %s\n", tokenResp.StatusCode, bodyBytes)
      return "", errors.New(fmt.Sprintf("请求失败,状态码: %d, 响应内容: %s\n", tokenResp.StatusCode, bodyBytes))
   }

   err = json.Unmarshal(bodyBytes, &tokenResponse)
   if err != nil {
      return "", err
   }

   if tokenResponse.Jwt == "" {
      return "", errors.New("获取token失败:token为空")
   }

   return tokenResponse.Jwt, nil
}

// parseCookieString 格式化cookie
func (s *service) parseCookieString(cookieString string) []*http.Cookie {
   var cookies []*http.Cookie
   for _, cookiePair := range strings.Split(cookieString, "; ") {
      parts := strings.Split(cookiePair, "=")
      if len(parts) == 2 {
         cookies = append(cookies, &http.Cookie{Name: parts[0], Value: parts[1]})
      }
   }
   return cookies
}
生成歌曲提交
// Generate 发起生成请求
func (s *service) Generate(prompt string) (response GenerateResp, err error) {
   // token校验
   if err = s.checkToken(); err != nil {
      return
   }

   // 携带token发起请求
   genReq := GenerateReq{
      GptDescriptionPrompt: prompt,
      Mv:                   "chirp-v3-0",
   }
   reqData, _ := json.Marshal(genReq)
   genMusicUrl := BaseUrl + "/api/generate/v2/"
   req, _ := http.NewRequest("POST", genMusicUrl, bytes.NewBuffer(reqData))
   req.Header.Set("Authorization", "Bearer "+Token)
   req.Header.Set("Content-Type", "application/json")

   client := &http.Client{}
   resp, err := client.Do(req)
   if err != nil {
      return
   }

   defer resp.Body.Close()

   var genResponse GenerateResp
   err = json.NewDecoder(resp.Body).Decode(&genResponse)
   if err != nil {
      return
   }

   return genResponse, nil
}
查询歌曲详情
// GetFeed 查询详情
func (s *service) GetFeed(ids []string) (result []Clips, err error) {
   // token校验
   if err = s.checkToken(); err != nil {
      return
   }

   idsFormat := url.PathEscape(strings.Join(ids, ","))
   feedUrl := fmt.Sprintf("%s/api/feed/?ids=%s", BaseUrl, idsFormat)
   req, _ := http.NewRequest("GET", feedUrl, nil)
   req.Header.Set("Authorization", "Bearer "+Token)
   req.Header.Set("Content-Type", "application/json")

   client := &http.Client{
      Timeout: 10 * time.Second,
   }

   resp, err := client.Do(req)
   if err != nil {
      return nil, err
   }
   defer resp.Body.Close()

   bodyBytes, err := io.ReadAll(resp.Body)
   if err != nil {
      return nil, fmt.Errorf("error reading response body: %v", err)
   }

   err = json.Unmarshal(bodyBytes, &result)
   if err != nil {
      fmt.Printf("Error decoding JSON response: %v\nResponse body: %s\n", err, bodyBytes)
      return nil, err
   }
   return result, nil
}
3.控制器
// GenMusic 提交音乐请求
func (h *Handler) GenMusic(c *gin.Context) {
   var genReq genReq
   if err := c.ShouldBindJSON(&genReq); err != nil {
      errResponse(c, err.Error(), "Params Fail")
      return
   }

   token, err := SunoService.GetToken(cookieString)
   if err != nil {
      errResponse(c, err.Error(), "Request Failed")
      return
   }

   SunoService.SetToken(token)

   res, err := SunoService.Generate(genReq.Prompt)
   if err != nil {
      errResponse(c, err.Error(), "Request Failed")
      return
   }

   okResponse(c, res)
   return
}

// GetFeed 查询详情
func (h *Handler) GetFeed(c *gin.Context) {
   var feedReq feedReq
   if err := c.ShouldBindJSON(&feedReq); err != nil {
      errResponse(c, err.Error(), "Params Fail")
      return
   }

   token, err := SunoService.GetToken(cookieString)
   if err != nil {
      errResponse(c, err.Error(), "Request Failed")
      return
   }
   SunoService.SetToken(token)

   data, err := SunoService.GetFeed(feedReq.ClipsIds)
   if err != nil {
      errResponse(c, err.Error(), "Request Failed")
      return
   }
   okResponse(c, data)
   return
}

postman测试

项目启动

cd cmd
go run main.go

生成音乐

查询音乐详情

结语

虽然目前这个suno-api仅揭开了其神秘的面纱,实现了基础的生成及详情查询功能,但它所蕴含的潜力与未来的发展空间却是无限的。如果我们能够为这个API增添更多的功能和优化,它将变得更加强大和完善。比如,通过配置文件来管理cookie,让它们更加安全和持久;增加cookie的保活机制,让用户体验更加流畅;记录每一次的请求日志,让问题追踪和系统监控变得更加简单;实施接口限流策略,保障系统在高并发情况下的稳定性等等。希望后续能够激发更多的创意火花,创造出更多动人心弦的旋律。

感谢这段旅程有你的陪伴,让我们一起期待下一次更加精彩的启程。

完整代码

https://gitee.com/mofung1/go-suno

关于我们

本文由ChatMoney团队出品,ChatMoney专注于AI应用落地与变现,我们提供全套、持续更新的AI源码系统与可执行的变现方案,致力于帮助更多人利用AI来变现,欢迎进入ChatMoney获取更多AI变现方案!

PyCharm是一个集成开发环境(IDE),主要用于Python语言开发。如果你想利用它创建一个程序来通过Suno API生成音乐并将其下载,你可以按照以下步骤操作: 1. **安装所需库**: 首先,你需要安装`requests`库来发送HTTP请求以及处理API响应,如果尚未安装,可以在命令行中输入 `pip install requests`. 2. **设置Suno API**: 确保你有Suno API的访问权限,并获取到必要的访问密钥或令牌。通常,这涉及到注册一个开发者账号并在文档中找到对应的API端点和下载音乐的URL。 3. **编写代码**: 使用PyCharm编写一个脚本,示例如下: ```python import requests import os def download_sunny_music(api_key, song_id): # 创建请求头 headers = { 'Authorization': f'Token {api_key}', 'Content-Type': 'application/json' } # 构造下载URL base_url = "https://api.suno.com/music/download" url = f"{base_url}/{song_id}" # 发送GET请求 response = requests.get(url, headers=headers) # 检查请求状态码 if response.status_code == 200: # 下载文件 file_path = f'music/{song_id}.mp3' # 根据需求定制保存路径 with open(file_path, 'wb') as file: file.write(response.content) print(f"Music downloaded: {file_path}") else: print(f"Error downloading music: {response.text}") # 使用API密钥和歌单ID替换这里的变量 api_key = "your_api_key" song_id = "your_song_id" download_sunny_music(api_key, song_id) ``` 4. **运行和调试**: 在PyCharm中设置好项目结构,将上述代码放在适当的位置,然后运行这段代码。记得将`api_key`和`song_id`替换为你实际的API密钥和要下载的歌曲ID。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值