-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathaudio_downloader.rb
116 lines (104 loc) · 3.96 KB
/
audio_downloader.rb
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
require 'faraday'
require 'logger'
require_relative 'fileid_decoder'
require_relative 'params_decryptor'
class AudioDownloader
LOGGER = Logger.new(STDOUT)
ILLEGAL_FILENAME_CHARS = %r([|/?*:"<>\\])
def initialize(uid, token)
@decryptor = ParamsDecryptor.new
@uid = uid
@token = token
end
def batch_download(audio_ids, dir)
audio_ids.each do |audio_id|
download(audio_id, dir)
end
end
def download(audio_id, dir)
audio_desc = get_audio_desc(audio_id)
audio = get_audio_resp(audio_desc)
total_length = audio_desc['totalLength']
content_length = audio.headers['content-length']
filename = sanitize_file_name(audio_desc['title'])
if total_length and content_length and total_length == content_length.to_i
open "/#{dir}/#{filename}.m4a", 'wb' do |io|
io << audio.body
end
LOGGER.info("[#{audio_id}]_#{filename} with length #{total_length} download successful.")
yield "#{audio_id}_#{filename}", true if block_given?
else
if audio.status == 206
processed_length = content_length.to_i
open "/#{dir}/#{filename}.m4a", 'wb' do |io|
io << audio.body
while processed_length < total_length
partial_audio = get_audio_resp(audio_desc) do |req|
req.headers['Range'] = "bytes=#{processed_length}-#{total_length - 1}"
end
partial_content_length = partial_audio.headers['content-length'].to_i
processed_length += partial_content_length
io << partial_audio.body
end
end
LOGGER.info("[#{audio_id}]_#{filename} with length #{processed_length} download successful.")
yield "#{audio_id}_#{filename}", true if block_given?
else
LOGGER.error("[#{audio_id}] download failure with response (#{audio.status}, #{audio.headers}).")
yield "#{audio_id}_#{filename}", false if block_given?
end
end
rescue Exception => e
LOGGER.error("[#{audio_id}] download failure with error (#{e.message}).")
yield "#{audio_id}_#{e.message}", false if block_given?
end
private
def get_audio_resp(audio_desc)
decoder = FileidDecoder.new(audio_desc['seed'])
url = "#{audio_desc['domain']}/download/#{audio_desc['apiVersion']}/#{decoder.decode(audio_desc['fileId'])}"
params = @decryptor.decrypt(audio_desc['ep']).split('-')
connection.get do |req|
req.url url, {
sign: params[1],
buy_key: params[0],
token: params[2],
timestamp: params[3],
duration: audio_desc['duration']
}
req.headers['Accept-Encoding'] = 'identity;q=1, *;q=0'
req.headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.59 Safari/537.36'
yield req if block_given?
end
end
def get_audio_desc(audio_id)
resp = mpay_connection.get do |req|
req.url "/mobile/track/pay/#{audio_id}", {
device: 'pc',
uid: @uid,
token: @token,
isBackend: false
}
req.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01'
req.headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.59 Safari/537.36'
req.headers['Host'] = 'mpay.ximalaya.com'
end
JSON.parse(resp.body)
end
def mpay_connection
@mpay_connection ||= Faraday.new(url: 'https://mpay.ximalaya.com') do |faraday|
faraday.request :url_encoded # form-encode POST params
faraday.response :logger # log requests to STDOUT
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
end
end
def connection
@connection ||= Faraday.new(url: 'http://audio.pay.xmcdn.com') do |faraday|
faraday.request :url_encoded
faraday.response :logger
faraday.adapter Faraday.default_adapter
end
end
def sanitize_file_name(name)
name.gsub(ILLEGAL_FILENAME_CHARS, '_').gsub(/\s/, '')
end
end