简单实现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
上一篇 2024年 6月 28日 上午10:36
下一篇 2024年 7月 2日 下午2:47

相关推荐

  • 简单实现suno-api账号保活

    本文由 ChatMoney团队出品 简介 之前的一个简易的项目suno-api。是使用cookie来获取suno-token发起请求的,之前写的简单,并没有做cookie保活,在运行一段时间后cookie会失效,api便失效了。那现在就来实现一个简单的账号保活。 保活原理 账号保活的实现原理比较简单,其实就是每隔一段时间去获取一次token。当然有其他保活方…

    2024年 6月 5日
    205
  • 讲讲前端工程化

    本文由 ChatMoney团队出品 前言 在2010年前,前端只是一个项目的“附赠品”,对于整个项目来说他显得无关紧要,甚至没有前后端之分,但后来为了提升用户体验,工程师们不得不把界面和交互做的更加优美和便捷,于是前端慢慢地脱离出来变成了一个单独地岗位和方向。 随着前端项目复杂度的提升,传统的前端开发方式(html+css+js)已经无法满足复杂多变的开发需…

    2024年 8月 3日
    216
  • 浅谈ChatGPT模型中的惩罚机制

    本文由ChatMoney团队出品 在探讨ChatGPT模型的文本生成能力时,除了采样算法,惩罚机制同样扮演着至关重要的角色。这些机制不仅影响生成文本的多样性和创意性,还为我们提供了调整文本风格和质量的灵活手段。本文将深入探讨ChatGPT中的两种惩罚机制:频率惩罚(frequency_penalty)和存在惩罚(presence_penalty),并解释它们…

    2024年 6月 5日
    356
  • 大语言模型中上下文窗口理解和实现原理

    本文由 ChatMoney团队出品 上下文窗口含义及其作用 上下文窗口就像是语言模型在阅读和写作时使用的一个“记忆窗口”。想象一下你在读一本书的时候,为了理解某个句子,你可能需要回顾前面的一两句话来抓住它们之间的联系。同样,语言模型在预测或生成文本时,也需要查看前面的一定数量的词元或文本片段,这个范围就是上下文窗口。用大白话说,就是在大模型对话中,将你要提前…

    2024年 6月 18日
    197
  • Android Studio下载Gradle超时解决方案

    本文由 ChatMoney团队出品 Android Studio 找到项目中 gradle 配置文件的路径,我的路径为 /你的项目路径/gradle/wrapper/gradle-wrapper.properties,里面对应内容应该如下所示: 超时原因是因为as(Android Studio,此后简称 as)中默认是从gradle官网去下载,此时我们有一个…

    2024年 7月 17日
    157

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

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

关注微信