Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
winterssy committed May 30, 2019
0 parents commit 900cd5d
Show file tree
Hide file tree
Showing 29 changed files with 2,427 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Editor directories and files
.idea
30 changes: 30 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
language: go

go:
- 1.12.x

# Note: If you're working inside $GOPATH/src, go modules are disabled by default and can be enabled by setting GO111MODULE=on.
env:
GO111MODULE=on

install:
- go mod download

script:
- go test -v ./...
- GOOS=windows GOARCH=amd64 go build -o music-get.exe
- GOOS=linux GOARCH=amd64 go build -o music-get

before_deploy:
- zip music-get_windows_amd64.zip music-get.exe README.md
- zip music-get_linux_amd64.zip music-get README.md

deploy:
provider: releases
api_key: $GITHUB_TOKEN
file:
- music-get_windows_amd64.zip
- music-get_linux_amd64.zip
skip_cleanup: true
on:
tags: true
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Music-Get

[![Build Status](https://travis-ci.org/winterssy/music-get.svg?branch=master)](https://travis-ci.org/winterssy/music-get)
[![License GPL-3.0](https://img.shields.io/badge/license-GPLv3.0-blue.svg)](https://github.com/winterssy/music-get/blob/master/LICENSE)

[网易云音乐](https://music.163.com) | [QQ音乐](https://y.qq.com) 下载助手,支持一键下载单曲/专辑/歌单以及歌手热门歌曲,并自动更新音乐标签。

>本项目仅供学习研究使用。如侵犯你的权益,请 [联系作者](mailto:[email protected]) 删除。
## 如何使用?

直接将音乐地址作为命令行参数传入即可,如:

- 下载单曲:
```
$ music-get https://music.163.com/#/song?id=553310243
$ music-get https://y.qq.com/n/yqq/song/002Zkt5S2z8JZx.html
```

- 下载专辑:
```
$ music-get https://music.163.com/#/album?id=38373053
$ music-get https://y.qq.com/n/yqq/song/002Zkt5S2z8JZx.html
```

- 下载歌单:
```
$ music-get https://music.163.com/#/playlist?id=156934569
$ music-get https://y.qq.com/n/yqq/album/002fRO0N4FftzY.html
```

- 下载歌手热门歌曲:
```
$ music-get https://music.163.com/#/artist?id=13193
$ music-get https://y.qq.com/n/yqq/singer/000Sp0Bz4JXH0o.html
```

命令选项:
- `-br`:优先下载音质,可选128/192/320,默认128。
- `-o`:下载保存目录,默认为 `/home/用户名/Music-Get` (Windows为 `C:\\Users\\用户名\\Music-Get` )。
- `-n`:并发下载任务数,最大值16,默认1,即单任务下载。
- `-h`:获取命令帮助。

**注:** 命令选项必须先于其它命令行参数输入。

## License

GPLv3.
196 changes: 196 additions & 0 deletions common/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package common

import (
"errors"
"github.com/bogem/id3v2"
"github.com/winterssy/music-get/utils"
"github.com/winterssy/music-get/utils/logger"
"gopkg.in/cheggaaa/pb.v1"
"io"
"io/ioutil"
"os"
"path/filepath"
"sync"
"time"
)

const (
NeteaseMusic = 1000 + iota
TencentMusic
)

const (
DownloadSuccess = 2000 + iota
DownloadNoCopyrightError
DownloadBuildPathError
DownloadHTTPRequestError
DownloadBuildFileError
DownloadFileTransferError
)

type Tag struct {
Title string
Artist string
Album string
Year string
Track string
CoverImage string
}

type MP3 struct {
FileName string
SavePath string
Playable bool
DownloadUrl string
Tag Tag
Origin int
}

type DownloadTask struct {
MP3 *MP3
Status int
}

func (m *MP3) UpdateTag(wg *sync.WaitGroup) {
var err error
defer func() {
if err != nil {
logger.Error.Printf("Update music tag error: %s: %s", m.FileName, err.Error())
} else {
logger.Info.Printf("Music tag updated: %s", m.FileName)
}
wg.Done()
}()

file := filepath.Join(m.SavePath, m.FileName)
resp, err := Request("GET", m.Tag.CoverImage, nil, nil, NeteaseMusic)
if err != nil {
return
}
defer resp.Body.Close()

data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}

tag, err := id3v2.Open(file, id3v2.Options{Parse: true})
if err != nil {
return
}
defer tag.Close()

tag.SetDefaultEncoding(id3v2.EncodingUTF8)
pic := id3v2.PictureFrame{
Encoding: id3v2.EncodingUTF8,
MimeType: "image/jpg",
PictureType: id3v2.PTOther,
Picture: data,
}
tag.AddAttachedPicture(pic)
tag.SetTitle(m.Tag.Title)
tag.SetArtist(m.Tag.Artist)
tag.SetAlbum(m.Tag.Album)
tag.SetYear(m.Tag.Year)
textFrame := id3v2.TextFrame{
Encoding: id3v2.EncodingUTF8,
Text: m.Tag.Track,
}
tag.AddFrame(tag.CommonID("Track number/Position in set"), textFrame)

err = tag.Save()
return
}

func (m *MP3) SingleDownload() error {
m.SavePath = filepath.Join(MP3DownloadDir, m.SavePath)
if err := utils.BuildPathIfNotExist(m.SavePath); err != nil {
return err
}

resp, err := Request("GET", m.DownloadUrl, nil, nil, m.Origin)
if err != nil {
return err
}
defer resp.Body.Close()

fPath := filepath.Join(m.SavePath, m.FileName)
f, err := os.Create(fPath)
if err != nil {
return err
}
defer f.Close()

bar := pb.New(int(resp.ContentLength)).SetUnits(pb.U_BYTES).SetRefreshRate(100 * time.Millisecond)
bar.ShowSpeed = true
bar.Start()
reader := bar.NewProxyReader(resp.Body)
n, err := io.Copy(f, reader)
if err != nil {
return err
}
if n != resp.ContentLength {
return errors.New("file transfer interrupted")
}

bar.Finish()
return nil
}

func (m *MP3) ConcurrentDownload(taskList chan DownloadTask, taskQueue chan struct{}, wg *sync.WaitGroup) {
var err error
task := DownloadTask{
MP3: m,
}

defer func() {
if err != nil {
logger.Error.Printf("Download error: %s: %s", m.FileName, err.Error())
}
wg.Done()
taskList <- task
<-taskQueue
}()

if !m.Playable {
logger.Info.Printf("Ignore no coypright music: %s", m.Tag.Title)
task.Status = DownloadNoCopyrightError
return
}

logger.Info.Printf("Downloading: %s", m.FileName)
m.SavePath = filepath.Join(MP3DownloadDir, m.SavePath)
if err = utils.BuildPathIfNotExist(m.SavePath); err != nil {
task.Status = DownloadBuildPathError
return
}

resp, err := Request("GET", m.DownloadUrl, nil, nil, m.Origin)
if err != nil {
task.Status = DownloadHTTPRequestError
return
}
defer resp.Body.Close()

fPath := filepath.Join(m.SavePath, m.FileName)
f, err := os.Create(fPath)
if err != nil {
task.Status = DownloadBuildFileError
return
}
defer f.Close()

n, err := io.Copy(f, resp.Body)
if err != nil {
task.Status = DownloadFileTransferError
return
}
if n != resp.ContentLength {
task.Status = DownloadFileTransferError
return
}

task.Status = DownloadSuccess
logger.Info.Printf("Download complete: %s", m.FileName)
return
}
29 changes: 29 additions & 0 deletions common/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package common

import (
"flag"
"os"
"path/filepath"
)

const (
MaxConcurrentDownloadTasksNumber = 16
)

var (
MP3DownloadDir string
MP3DownloadBr int
MP3ConcurrentDownloadTasksNumber int
)

func init() {
homedir, err := os.UserHomeDir()
if err != nil {
homedir = "."
}
downloadDir := filepath.Join(homedir, "Music-Get")
flag.StringVar(&MP3DownloadDir, "o", downloadDir, "MP3 download directory")
flag.IntVar(&MP3DownloadBr, "br", 128, "MP3 prior download bit rate, 128|192|320")
flag.IntVar(&MP3ConcurrentDownloadTasksNumber, "n", 1, "MP3 concurrent download tasks number, max 16")
flag.Parse()
}
Loading

0 comments on commit 900cd5d

Please sign in to comment.