rget 4.11.0 → 4.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.devcontainer/devcontainer.json +4 -1
- data/README.md +1 -1
- data/lib/webradio.rb +4 -4
- data/lib/youtube.rb +102 -21
- data/rget.gemspec +2 -2
- data/rget.yaml +5 -1
- metadata +3 -4
- data/lib/freshlive.rb +0 -113
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 313f0120701854057599c157f85971584cd70eed1dd45f6d8cc5ea1444cc714c
|
4
|
+
data.tar.gz: 0f998f200f67b6a28186d3070b4b3f03e50b76d14e9c6def56a39afbdfda7a52
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ce23bc480247bcf370528ac3f08191846516d5746f0a075862f21eb7e863caf2b6e89ecb297766018b248e8ddcb76b0ce477cd98ec5e58939a15f38da27930d
|
7
|
+
data.tar.gz: 7cdfbc35c482db64f26899523d0338d69ca5faa82b70b84f187d4824766c97e2dc86f06d89565c9f4d54586a8449453a9aa3384cad17465a1446e251f8c19e28
|
@@ -13,7 +13,10 @@
|
|
13
13
|
|
14
14
|
// Add the IDs of extensions you want installed when the container is created.
|
15
15
|
"extensions": [
|
16
|
-
|
16
|
+
"rebornix.ruby"
|
17
|
+
,"github.vscode-pull-request-github"
|
18
|
+
,"eamodio.gitlens"
|
19
|
+
,"donjayamanne.githistory"
|
17
20
|
],
|
18
21
|
|
19
22
|
// Uncomment the next line if you want start specific services in your Docker Compose config.
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ Downloading newest radio programs on the web. Supported radio stations are:
|
|
8
8
|
* himalaya.fm
|
9
9
|
* Asobi Store
|
10
10
|
* stand.fm
|
11
|
-
* YouTube (playlist
|
11
|
+
* YouTube (playlist or channel)
|
12
12
|
|
13
13
|
If you want to save files as MP3, needs `ffmpeg` command.
|
14
14
|
To download niconico video or YouTube, also needs latest `youtube-dl` command (you can get it from `https://yt-dl.org/`), then specify user ID and password to `~/.netrc` for niconico as:
|
data/lib/webradio.rb
CHANGED
@@ -20,9 +20,6 @@ class WebRadio
|
|
20
20
|
when %r[nicovideo\.jp]
|
21
21
|
require 'nicovideo'
|
22
22
|
Nicovideo.new(params, options)
|
23
|
-
when %r[^https://freshlive\.tv/]
|
24
|
-
require 'freshlive'
|
25
|
-
FreshLive.new(params, options)
|
26
23
|
when %r[^http://m\.himalaya\.fm/]
|
27
24
|
require 'himalaya'
|
28
25
|
Himalaya.new(params, options)
|
@@ -34,7 +31,10 @@ class WebRadio
|
|
34
31
|
StandFm.new(params, options)
|
35
32
|
when %r[^https://www\.youtube\.com/playlist]
|
36
33
|
require 'youtube'
|
37
|
-
|
34
|
+
YoutubePlaylist.new(params, options)
|
35
|
+
when %r[^https://www\.youtube\.com/c/]
|
36
|
+
require 'youtube'
|
37
|
+
YoutubeChannel.new(params, options)
|
38
38
|
else
|
39
39
|
raise UnsupportError, "unsupported url: #{params['url']}"
|
40
40
|
end
|
data/lib/youtube.rb
CHANGED
@@ -1,40 +1,121 @@
|
|
1
1
|
require 'open-uri'
|
2
2
|
require 'json'
|
3
|
+
require 'nokogiri'
|
3
4
|
|
4
5
|
class Youtube < WebRadio
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
def initialize(params, options)
|
7
|
+
super
|
8
|
+
@offset = 0
|
9
|
+
@target_content = []
|
10
|
+
end
|
9
11
|
|
10
|
-
|
11
|
-
html = URI.open(@url).read
|
12
|
-
json_str = html.scan(/ytInitialData = (.*);<\/script>/).flatten.first
|
13
|
-
json = JSON.parse(json_str)
|
14
|
-
title = json["contents"]["twoColumnBrowseResultsRenderer"]["tabs"][0]["tabRenderer"]["content"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]["contents"][0]["playlistVideoListRenderer"]["contents"][0]["playlistVideoRenderer"]["title"]["runs"][0]["text"]
|
15
|
-
@cover = json["contents"]["twoColumnBrowseResultsRenderer"]["tabs"][0]["tabRenderer"]["content"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]["contents"][0]["playlistVideoListRenderer"]["contents"][0]["playlistVideoRenderer"]["thumbnail"]["thumbnails"].last["url"]
|
16
|
-
mp4_url = "https://www.youtube.com#{json["contents"]["twoColumnBrowseResultsRenderer"]["tabs"][0]["tabRenderer"]["content"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]["contents"][0]["playlistVideoListRenderer"]["contents"][0]["playlistVideoRenderer"]["navigationEndpoint"]["commandMetadata"]["webCommandMetadata"]["url"]}"
|
17
|
-
serial = title.scan(/[0-9]+/).first
|
12
|
+
private
|
18
13
|
|
19
|
-
|
20
|
-
mp3_file = "#{@label}##{serial}.mp3"
|
14
|
+
def youtube_download(mp4_url, mp4_file, mp3_file)
|
21
15
|
begin
|
22
16
|
mp3nize(mp4_file, mp3_file) do
|
23
17
|
loop do
|
24
18
|
print '.'
|
25
|
-
_, err, status = Open3.capture3("youtube-dl -f mp4 -o #{mp4_file} --netrc '#{mp4_url}'")
|
19
|
+
_, err, status = Open3.capture3("youtube-dl -f mp4 -o '#{mp4_file}' --netrc '#{mp4_url}'")
|
26
20
|
break if status == 0
|
27
21
|
next if err =~ /403: Forbidden/
|
28
22
|
raise ForbiddenError.new("Could not access to #{mp4_url}") if err =~ /TypeError|AssertionError/
|
29
23
|
raise DownloadError.new(err)
|
30
24
|
end
|
31
25
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
26
|
+
rescue ForbiddenError
|
27
|
+
puts "#{$!.message}, try next."
|
28
|
+
@offset += 1
|
29
|
+
retry
|
30
|
+
rescue NotFoundError
|
31
|
+
raise DownloadError.new('video not found')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def first_video(html)
|
36
|
+
json_str = html.scan(/ytInitialData = (.*);<\/script>/).flatten.first
|
37
|
+
json = JSON.parse(json_str)
|
38
|
+
contents = json.dig(*@target_content)
|
39
|
+
yield contents.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def serial_file(title, label, serial, ext)
|
43
|
+
if serial > 0
|
44
|
+
"#{@label}##{serial}.#{ext}"
|
45
|
+
else
|
46
|
+
"#{title}.#{ext}"
|
38
47
|
end
|
39
48
|
end
|
40
49
|
end
|
50
|
+
|
51
|
+
class YoutubePlaylist < Youtube
|
52
|
+
|
53
|
+
def initialize(params, options)
|
54
|
+
super
|
55
|
+
@target_content = ["contents", "twoColumnBrowseResultsRenderer", "tabs", 0, "tabRenderer", "content", "sectionListRenderer", "contents", 0, "itemSectionRenderer", "contents", 0, "playlistVideoListRenderer", "contents"].freeze
|
56
|
+
end
|
57
|
+
|
58
|
+
def download
|
59
|
+
first_video(URI.open(@url).read) do |content|
|
60
|
+
item = content['playlistVideoRenderer']
|
61
|
+
@cover = item['thumbnail']['thumbnails'].last['url']
|
62
|
+
title = item['title']['runs'][0]['text']
|
63
|
+
serial = title.scan(/\d+/).first.to_i
|
64
|
+
url = "https://www.youtube.com#{item["navigationEndpoint"]["commandMetadata"]["webCommandMetadata"]["url"]}"
|
65
|
+
|
66
|
+
mp4_file = serial_file title, @label, serial, 'mp4'
|
67
|
+
mp3_file = serial_file title, @label, serial, 'mp3'
|
68
|
+
|
69
|
+
youtube_download url, mp4_file, mp3_file
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def dump
|
74
|
+
uri = URI(@url)
|
75
|
+
tag = Hash[URI.decode_www_form(File.basename(uri.query))]['list']
|
76
|
+
html = Nokogiri(uri.open.read)
|
77
|
+
label, = html.css('title').text.split(/ - .*\Z/)
|
78
|
+
return {
|
79
|
+
tag => {
|
80
|
+
'desc' => label,
|
81
|
+
'url' => @url,
|
82
|
+
'label' => label
|
83
|
+
}
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class YoutubeChannel < Youtube
|
89
|
+
def initialize(params, options)
|
90
|
+
super
|
91
|
+
@target_content = ['contents', 'twoColumnBrowseResultsRenderer', 'tabs', 1, 'tabRenderer', 'content', 'sectionListRenderer', 'contents', 0, 'itemSectionRenderer', 'contents', 0, 'gridRenderer', 'items'].freeze
|
92
|
+
end
|
93
|
+
|
94
|
+
def download
|
95
|
+
first_video(URI.open(@url).read) do |content|
|
96
|
+
item = content['gridVideoRenderer']
|
97
|
+
@cover = item['thumbnail']['thumbnails'].last['url']
|
98
|
+
title = item['title']['runs'][0]['text']
|
99
|
+
serial = title.scan(/\d+/).first.to_i
|
100
|
+
url = "https://www.youtube.com#{item['navigationEndpoint']['commandMetadata']['webCommandMetadata']['url']}"
|
101
|
+
|
102
|
+
mp4_file = serial_file title, @label, serial, 'mp4'
|
103
|
+
mp3_file = serial_file title, @label, serial, 'mp3'
|
104
|
+
|
105
|
+
youtube_download url, mp4_file, mp3_file
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def dump
|
110
|
+
tag = @url.scan(%r|/c/([^/]*)|).flatten.first
|
111
|
+
html = Nokogiri(URI(@url).open.read)
|
112
|
+
label = html.css('meta[property="og:title"]').attr('content').text
|
113
|
+
return {
|
114
|
+
tag => {
|
115
|
+
'desc' => label,
|
116
|
+
'url' => "#{@url.scan(%r|(.*?/c/[^/]*)|).flatten.first}/videos?view=0&sort=dd&flow=grid",
|
117
|
+
'label' => label
|
118
|
+
}
|
119
|
+
}
|
120
|
+
end
|
121
|
+
end
|
data/rget.gemspec
CHANGED
@@ -4,10 +4,10 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "rget"
|
7
|
-
spec.version = "4.
|
7
|
+
spec.version = "4.12.0"
|
8
8
|
spec.authors = ["Tada, Tadashi"]
|
9
9
|
spec.email = ["t@tdtds.jp"]
|
10
|
-
spec.description = %q{Downloading newest radio programs on the web. Supported radio stations are hibiki, onsen, niconico, himalaya, asobi store, stand.fm and youtube
|
10
|
+
spec.description = %q{Downloading newest radio programs on the web. Supported radio stations are hibiki, onsen, niconico, himalaya, asobi store, stand.fm and youtube.}
|
11
11
|
spec.summary = %q{Downloading newest radio programs on the web.}
|
12
12
|
spec.homepage = "https://github.com/wasamas/rget"
|
13
13
|
spec.license = "GPL"
|
data/rget.yaml
CHANGED
@@ -30,4 +30,8 @@ programs:
|
|
30
30
|
active:
|
31
31
|
desc: 愛美とはるかの2年A組青春アクティ部!
|
32
32
|
url: https://www.youtube.com/playlist?list=PLI5zma0g_n8_y5Xk5eFwX8osz4Q2a3cqU
|
33
|
-
label: アクティ部
|
33
|
+
label: アクティ部
|
34
|
+
shimotsukin:
|
35
|
+
desc: 霜月はるかのしもつきんチャンネル
|
36
|
+
url: https://www.youtube.com/c/shimotsukinchannel/videos?view=0&sort=dd&flow=grid
|
37
|
+
label: しもつきん
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rget
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tada, Tadashi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-01-
|
11
|
+
date: 2021-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -151,7 +151,7 @@ dependencies:
|
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '0'
|
153
153
|
description: Downloading newest radio programs on the web. Supported radio stations
|
154
|
-
are hibiki, onsen, niconico, himalaya, asobi store, stand.fm and youtube
|
154
|
+
are hibiki, onsen, niconico, himalaya, asobi store, stand.fm and youtube.
|
155
155
|
email:
|
156
156
|
- t@tdtds.jp
|
157
157
|
executables:
|
@@ -174,7 +174,6 @@ files:
|
|
174
174
|
- bin/rget
|
175
175
|
- lib/asobistore.rb
|
176
176
|
- lib/dropbox.rb
|
177
|
-
- lib/freshlive.rb
|
178
177
|
- lib/hibiki.rb
|
179
178
|
- lib/himalaya.rb
|
180
179
|
- lib/hls.rb
|
data/lib/freshlive.rb
DELETED
@@ -1,113 +0,0 @@
|
|
1
|
-
require 'webradio'
|
2
|
-
require 'nokogiri'
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
class FreshLive < WebRadio
|
6
|
-
def download
|
7
|
-
if URI(@url).path =~ %r|/search/|
|
8
|
-
archive = @url
|
9
|
-
else
|
10
|
-
archive = URI(File.join(@url + '/programs/archive'))
|
11
|
-
end
|
12
|
-
|
13
|
-
each_programs(Nokogiri(URI.open(archive).read)) do |meta|
|
14
|
-
begin
|
15
|
-
serial = meta['data']['title'].scan(/\d+$/).first.to_i
|
16
|
-
src = "#{@label}##{'%02d' % serial}.ts"
|
17
|
-
dst = src.sub(/\.ts$/, @options.mp3 ? '.mp3' : '.mp4')
|
18
|
-
if exist?(dst)
|
19
|
-
puts "#{dst} is existent, skipped."
|
20
|
-
return
|
21
|
-
end
|
22
|
-
unless exist?(src)
|
23
|
-
open(src, 'wb') do |w|
|
24
|
-
print "getting #{src}..."
|
25
|
-
ts_list(meta['data']['archiveStreamUrl']).each_with_index do |u, i|
|
26
|
-
print '.' if i % 50 == 0
|
27
|
-
w.write(URI.open(u, 'rb').read)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
if @options.mp3
|
32
|
-
dst = to_mp3(src)
|
33
|
-
else
|
34
|
-
dst = to_mp4(src)
|
35
|
-
end
|
36
|
-
puts 'done.'
|
37
|
-
move(dst)
|
38
|
-
return
|
39
|
-
rescue OpenURI::HTTPError
|
40
|
-
puts 'try next.'
|
41
|
-
next
|
42
|
-
rescue
|
43
|
-
puts 'fail.'
|
44
|
-
$stderr.puts 'faild to convert .ts => .mp4'
|
45
|
-
return
|
46
|
-
end
|
47
|
-
end
|
48
|
-
puts 'fail.'
|
49
|
-
$stderr.puts 'free program not found.'
|
50
|
-
end
|
51
|
-
|
52
|
-
def dump
|
53
|
-
u = URI(@url)
|
54
|
-
if u.path =~ %r|/search/|
|
55
|
-
desc = URI.decode_www_form_component(Pathname(u.path).basename.to_s)
|
56
|
-
return {
|
57
|
-
'freshlive_search' => {
|
58
|
-
'desc' => desc,
|
59
|
-
'url' => @url,
|
60
|
-
'label' => desc
|
61
|
-
}
|
62
|
-
}
|
63
|
-
else
|
64
|
-
tag = Pathname(u.path).basename.to_s
|
65
|
-
meta = JSON.parse(Nokogiri(URI.open(@url, &:read)).css('script').first)
|
66
|
-
return {
|
67
|
-
tag => {
|
68
|
-
'desc' => meta['name'],
|
69
|
-
'url' => @url,
|
70
|
-
'label' => tag
|
71
|
-
}
|
72
|
-
}
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
private
|
77
|
-
def each_programs(html)
|
78
|
-
x = "//section[descendant::h1[contains(text(),'アーカイブ')]]//*[contains(@class,'ProgramTitle')]/a/@href"
|
79
|
-
html.xpath(x).each do |href|
|
80
|
-
id = Pathname(href.value).basename.to_s
|
81
|
-
yield JSON.parse(URI.open("https://freshlive.tv/proxy/Programs;id=#{id}", &:read))
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def ts_list(rate_m3u8)
|
86
|
-
ts_m3u8 = URI.open(rate_m3u8).read.each_line.grep_v(/^#/)[1].chomp
|
87
|
-
URI.open(URI(rate_m3u8) + ts_m3u8).read.each_line.grep_v(/^#/).map{|u|URI(rate_m3u8) + u.chomp}
|
88
|
-
end
|
89
|
-
|
90
|
-
def to_mp3(src)
|
91
|
-
dst = src.sub(/ts$/, 'mp3')
|
92
|
-
command = "ffmpeg -i '#{src}' -vn -ab 64k '#{dst}'"
|
93
|
-
result = Open3.capture3(command)
|
94
|
-
if result[2].to_i == 0
|
95
|
-
File.delete(src)
|
96
|
-
else
|
97
|
-
File.delete(dst) if File.exist?(dst)
|
98
|
-
end
|
99
|
-
return dst
|
100
|
-
end
|
101
|
-
|
102
|
-
def to_mp4(src)
|
103
|
-
dst = src.sub(/ts$/, 'mp4')
|
104
|
-
command = "ffmpeg -i '#{src}' -vcodec copy -strict -2 '#{dst}'"
|
105
|
-
result = Open3.capture3(command)
|
106
|
-
if result[2].to_i == 0
|
107
|
-
File.delete(src)
|
108
|
-
else
|
109
|
-
File.delete(dst) if File.exist?(dst)
|
110
|
-
end
|
111
|
-
return dst
|
112
|
-
end
|
113
|
-
end
|