-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path3-download.go
More file actions
111 lines (98 loc) · 2.54 KB
/
3-download.go
File metadata and controls
111 lines (98 loc) · 2.54 KB
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
/*
Downloading tracks
Usage:
gmusic download [id...]
Download downloads the tracks identified by the IDs to the current
directory and prints the filenames they are saved under to standard
output. Progress and error information is additionally written to
standard error. If no IDs are given, a newline-separated list is read
from standard input.
If a track fails to download, download moves on to the next one. The
exit status is 0 only if all tracks downloaded successfully.
The filename under which each track is saved is generated server-side
from the track's title and track number. Download clobbers existing
files without asking, so be careful with it.
*/
package main
import (
"errors"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/lxr/go.google.musicmanager"
)
func init() {
cmds["download"] = download
}
func download() error {
client, err := loadClient()
if err != nil {
return err
}
success := true
scanner := getScanner()
for scanner.Scan() {
id := scanner.Text()
logf("downloading %s: ", id)
name, err := downloadTrack(client, id)
if err != nil {
logf("%v\n", err)
success = false
} else {
println(name)
}
}
switch {
case scanner.Err() != nil:
return scanner.Err()
case !success:
return errors.New("not all tracks were downloaded")
default:
return nil
}
}
func downloadTrack(client *musicmanager.Client, id string) (string, error) {
url, err := client.ExportTrack(id)
if err != nil {
return "", err
}
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
size, err := strconv.ParseFloat(resp.Header.Get("Content-Length"), 64)
if err == nil {
logf("(%.2f MiB) ", size/(1<<20))
}
// Google probably won't put malicious paths in
// Content-Disposition, but this will prevent the file from
// being written outside the current directory.
name, _ := getName(resp.Header.Get("Content-Disposition"))
name = filepath.Base(name)
if name == "." {
name = id + ".mp3"
}
f, err := os.Create(name)
if err != nil {
return "", err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return name, err
}
// getName extracts a UTF-8-encoded filename from a Content-Disposition
// header. (The default mime.ParseMediaType function cannot be used,
// because it seems to disagree with the server on how to encode/decode
// parens.)
func getName(v string) (string, error) {
parts := strings.SplitN(v, "filename*=UTF-8''", 2)
if len(parts) < 2 {
return "", errors.New("media type value lacks UTF-8 filename")
}
return url.QueryUnescape(parts[1])
}