简单实现Ai音乐suno-api

本文由 ChatMoney团队出品

前言

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

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

开发前准备

  1. golang开发环境。
  2. golang版本:1.21.0
  3. 可登录suno的环境
  4. 获取suno平台cookie
简单实现Ai音乐suno-api

开发过程

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"`
}
  1. 获取suno的token
  2. 实现逻辑为通过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: %vnResponse body: %sn", err, bodyBytes)
      fmt.Printf("请求失败,状态码: %d, 响应内容: %sn", tokenResp.StatusCode, bodyBytes)
      return "", errors.New(fmt.Sprintf("请求失败,状态码: %d, 响应内容: %sn", 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
}
  1. 生成歌曲提交
// 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
}
  1. 查询歌曲详情
// 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: %vnResponse body: %sn", 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

生成音乐

简单实现Ai音乐suno-api

查询音乐详情

简单实现Ai音乐suno-api

结语

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

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

完整代码

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

关于我们

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

ChatMoney的头像ChatMoney
Previous 2024年 6月 28日 上午10:36
Next 2024年 7月 2日 下午2:47

相关推荐

  • 面向对象设计基本原则

    本文由 ChatMoney团队出品 引言 在软件开发过程中,随着系统复杂度的增加和业务变更,程序员面临诸多挑战,如耦合性、内聚性、可维护性、可扩展性和可重用性。设计模式能有效地解决这些问题。设计模式蕴含了面向对象的精髓,掌握面向对象设计和分析是掌握设计模式的基础。它能帮助我们优化代码结构,提高代码的可维护性、可扩展性和可读性。 设计模式遵循一定的原则,这些原…

    2024年 8月 5日
    244
  • Vue3中组件使用ref时获取组件类型推导

    本文由 ChatMoney团队出品 我们在使用Vue3+ts开发时,常常会用到一些第三方组件库,比如Element-Plus UI、Navie UI等,这些UI框架中有些组件常常会暴露一些方法给我们便捷的去实现各种复杂的交互,我们经常会像下面这样去给组件定义一个ref去获取组件的实例: 这个方法可以正常使用,但是没有任何的ts类型推导,这也就丧失了一部分我们…

    2024年 7月 12日
    212
  • TypeScript中never类型的妙用

       本文由 ChatMoney团队出品 妙用一   当我们在一个项目中,可能会去改动一个在整个项目中应用很广泛的函数的参数类型,但是可能由于代码量比较庞大,我们不好排查改了之后哪些地方会出现问题,此时我们可以使用never类型来辅助我们的函数,当我们在原有的类型基础上添加了新的类型时,可能会导致else分支中的代码逻辑出现问题,此时我们…

    2024年 6月 12日
    243
  • 从GPT-1到GPT-3 预训练语言模型的演进与突破

    本文由 ChatMoney团队出品 前言 Generative Pre-trained Transformer(GPT)系列是由OpenAI开发的预训练语言模型,它们在多种NLP任务中取得了令人瞩目的成绩,包括文章生成、代码生成、机器翻译和问答等。GPT系列模型的核心思想是通过无监督学习在大规模语料库上进行预训练,再通过少量数据进行微调以适应特定任务。随着模…

    2024年 7月 4日
    940
  • 简单实现限流中间件

      本文由 ChatMoney团队出品 引言 在现代Web应用开发中,限流是一个重要的概念,它能够保护服务器免受流量攻击,确保服务的稳定性和可用性。Go语言以其高性能和并发处理能力在后端服务开发中广受欢迎。Gin是一个使用Go语言编写的Web框架,以其简洁和高效著称。在Gin框架中,通过中间件实现限流功能是一种常见的做法。 限流中间件的作用 限流中…

    2024年 6月 5日
    286

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信