code

music

最近很少用 deepin 在线或者使用客户段听音乐了,喜欢下载到本地听那些熟悉的歌曲。

很多歌不是这个平台没版权就是那个要付费。也是顾虑到网易云9级用户的面子,不经常去其他平台听歌,在 GitHub 上有个项目,项目地址:https://github.com/nondanee/UnblockNeteaseMusic。可以只使用网易云的客户端听大部分平台的音乐,对于没有版权的歌曲会使用其他平台的 api 来播放。用着总算有个解决方案了。但是在 linux 客户端上,网易云程序没有设置代理的功能,而linux 上又要弄证书什么的,一个客户端就懒的折腾了,主要是折腾了几天没有很好的解决方案,就一直搁置了。

也是经常使用 you-get 的缘故,会经常把电影或视频下载下来,同时也支持很多国内视频与音乐平台,you-get 项目地址:https://github.com/soimort/you-get

you-get 使用起来也比较简单,将链接放进去就好了,但是国内如果不使用代理,速度十分的慢。关于代理的使用

1
you-get -x 127.0.0.1:7890 'https://www.youtube.com/watch?v=jNQXAC9IVRw'

对于视频来说每次找到一个视频,复制链接,下载。还算可以接受,但是对于音乐,只有短短的几分钟,每次要下新的歌曲都要进行打开浏览器搜索,复制链接。干脆自己写一个程序。

由于是打算做命令行程序,就是用 Go 来写吧,一个小程序,就不使用 go mod 来做了,直接一个 main 搞定。此项目地址:https://github.com/ayuayue/kugou

本来打算通过命令行来查询歌曲,返回歌曲的链接,然后配合 you-get 来进行下载,这样就多了一步,类似于使用管道符的方式。

又打算直接通过 go 来调用 you-get 来下载,但是这样前提需要安装 you-get 并且代理不好在程序中配置,只好配置临时或全局的终端代理。

最后打算直接在程序中完成歌曲的搜索,歌词及歌曲下载。这样就不依赖于外部的东西。

对于歌曲下载api 的获取,使用 you-get 是通过 -d 参数,开启调试。会显示出请求的地址以及音乐的地址,由此可以的到歌曲详情的 api

Api

  1. 查询歌曲 http://msearchcdn.kugou.com/api/v3/search/song

    eg: http://msearchcdn.kugou.com/api/v3/search/song?keyword=花海

    参数 值类型
    keyword 字符串
  2. 歌曲详情 https://www.kugou.com/yy/index.php?r=play/getdata

    eg: https://www.kugou.com/yy/index.php?r=play/getdata?hash=xxxx&album_id=xxx&mid=123

    参数 值类型
    hash 字符串
    album_id 字符串
    mid 123

有了这两个 API 剩下的就简单了。

开始编码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
)

type Req struct {
	Status  int    `json:"status"`
	Error   string `json:"error"`
	Data    Data   `json:"data"`
	Errcode int    `json:"errcode"`
}
type Data struct {
	Info []Info `json:"info"`
}
type Info struct {
	Hash       string `json:"hash"`
	AlbumID    string `json:"album_id"`
	SingerName string `josn:"singername"`
	SongName   string `json:"songname"`
}
type Song struct {
	Status  int      `json:"status"`
	ErrCode int      `json:"err_code"`
	Data    SongData `json:"data"`
}
type SongData struct {
	AudioName string `json:"audio_name"`
	Lyrics    string `json:"lyrics"`
	PlayURL   string `json:"play_url"`
}

var SearchApi = "http://msearchcdn.kugou.com/api/v3/search/song"
var SongApi = "https://www.kugou.com/yy/index.php?r=play"

func query() Req {
	var name string
	flag.StringVar(&name, "name", "", "歌曲名")
	flag.Parse()
	//fmt.Println(name)
	client, err := http.NewRequest(http.MethodGet, SearchApi, nil)

	if err != nil {
		panic("请求失败")
	}
	params := make(url.Values)
	params.Add("keyword", name)
	client.URL.RawQuery = params.Encode()
	//fmt.Println(client)
	req, err := http.DefaultClient.Do(client)

	defer func() {
		req.Body.Close()
	}()
	if req.StatusCode != 200 {
		panic("返回错误响应")
	}
	body, _ := ioutil.ReadAll(req.Body)
	r := Req{}
	json.Unmarshal(body, &r)
	//fmt.Printf("%#v",r)
	return r
}

func (r *Req) List() {
	if r.Status != 1 {
		panic(r.Error)
	}
	songs := r.Data.Info
	for k, song := range songs {
		fmt.Println(k+1, song.SongName, "--", song.SingerName)
	}
}
func (r *Req) GetLink(num int) []string {
	songApi := "https://www.kugou.com/song"
	songs := r.Data.Info
	hash := songs[num-1].Hash
	album_id := songs[num-1].AlbumID
	fmt.Printf("在线播放链接:%s/#hash=%s&album_id=%s\n", songApi, hash, album_id)
	return []string{
		hash, album_id,
	}
}
func (s *Song) Download() {
	if s.Status != 1 {
		panic("获取歌曲信息失败")
	}
	lyrics := s.Data.Lyrics
	fmt.Println("开始下载歌词")
	lrcFileName := fmt.Sprintf("%s.lrc", s.Data.AudioName)
	songName := fmt.Sprintf("%s.mp3", s.Data.AudioName)
	lf, err := os.Create(lrcFileName)
	if err != nil {
		panic(err)
	}
	defer func() { _ = lf.Close() }()
	_, err = lf.Write([]byte(lyrics))
	if err != nil {
		panic(err)
	}
	fmt.Println("下载歌词完成")
	fmt.Println("开始下载歌曲")
	req, err := http.Get(s.Data.PlayURL)
	if err != nil {
		panic("请求失败")
	}
	defer func() {
		req.Body.Close()
	}()
	if req.StatusCode != 200 {
		panic("返回错误响应")
	}
	sf, err := os.Create(songName)
	if err != nil {
		panic(err)
	}
	defer func() { _ = sf.Close() }()
	_, err = io.Copy(sf, req.Body)
	if err != nil {
		panic(err)
	}
}
func main() {
	var num int
	req := query()
	req.List()
	fmt.Println("请输入要选择的歌曲链接的序号:")
	fmt.Scanln(&num)
	params := req.GetLink(num)
	song := GetSong(params)
	song.Download()
}
func GetSong(params []string) Song {
	url := fmt.Sprintf("%s/getdata&hash=%s&album_id=%s&mid=123", SongApi, params[0], params[1])
	req, err := http.Get(url)
	if err != nil {
		panic("请求失败")
	}
	defer func() {
		req.Body.Close()
	}()
	if req.StatusCode != 200 {
		panic("返回错误响应")
	}
	body, _ := ioutil.ReadAll(req.Body)
	song := Song{}
	json.Unmarshal(body, &song)
	return song
}

编译(我本地是 linux 环境)

1
2
3
4
# linux
go build -o path ./main.go #path 路径
# windows 
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o path main.go