rget 4.9.3 → 4.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 222b83cd5f8c3cff4f1b3372ec7488cc2bc4643c66469be1259d123c54e53643
4
- data.tar.gz: d2eda18075858e1e78fe0a862140161edfab2ad886075ba80e8b1a450f0ec617
3
+ metadata.gz: 680138673b4a74e24fc3114cfefd7e4d7a1f4f56fe37ca70b7848c0256b95d63
4
+ data.tar.gz: fe7fdd1168af355285b5ec7ab4c9741f1acdd4fc496ea3a2bc9e59b5869f7011
5
5
  SHA512:
6
- metadata.gz: 54d7bcead0ea3ddbb7a88afad7e7b95bde817c7272d0c2c4c567925bc41cc5ce35dff780624be93b92a49ec0e74ce9ac6cacf22f4c61c109543d73dc6d320728
7
- data.tar.gz: f722aabcdaa8767d0fecaebedc400c26312ad44c5b8894f1cd3567c0eabbadee13aaeefbcc8a7211779b8427a386725e1a1406b788ae2c791183b2b07727e15a
6
+ metadata.gz: 394aac18d5099e78ef52c99896a132fdc805edc8670fa7e980e074ffd8ecd7fd02cf6ae423c059852c43253ab585b82d720d1400602c127a6dbd46ac80f3474e
7
+ data.tar.gz: d01d41849aa9e3ac8cef2e3e523b62445116bffe09952d7f91909a82c2e1b7d47de58429d537eb423a35c563ebe84ce29eecbdd42ed26c07629b0ee2a346fc88
@@ -1,4 +1,4 @@
1
- FROM ruby:2
1
+ FROM ruby:3
2
2
  ENV USER vscode
3
3
  LABEL maintainer "@tdtds <t@tdtds.jp>"
4
4
  RUN apt-get -y update && apt-get -y install ffmpeg && \
@@ -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
- "rebornix.ruby"
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/.travis.yml CHANGED
@@ -5,7 +5,7 @@ before_install:
5
5
  - gem install bundler -v '> 1.5.0'
6
6
 
7
7
  rvm:
8
- - 2.5.0
8
+ - 2.7.2
9
9
 
10
10
  deploy:
11
11
  provider: rubygems
data/README.md CHANGED
@@ -5,12 +5,13 @@ Downloading newest radio programs on the web. Supported radio stations are:
5
5
  * hibiki
6
6
  * onsen
7
7
  * niconico
8
- * freshlive.tv
9
8
  * himalaya.fm
10
9
  * Asobi Store
10
+ * stand.fm
11
+ * YouTube (playlist or channel)
11
12
 
12
13
  If you want to save files as MP3, needs `ffmpeg` command.
13
- To download niconico video, also needs latest `youtube-dl` command (you can get it from `https://yt-dl.org/`), then specify niconico user ID and password to `~/.netrc` as:
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:
14
15
 
15
16
  ```
16
17
  machine niconico
data/lib/hls.rb CHANGED
@@ -3,13 +3,14 @@ require 'open3'
3
3
 
4
4
  class HLS < WebRadio
5
5
  private
6
- def hls_download(name, serial, m3u_meta)
6
+ def hls_download(name, serial, m3u_meta, headers = {})
7
7
  mp4_file = "#{name}##{serial}.mp4"
8
8
  mp3_file = "#{name}##{serial}.mp3"
9
- m3u = URI.open(m3u_meta, &:read).scan(/^[^#].*/).first
9
+ m3u = URI.open(m3u_meta, headers, &:read).scan(/^[^#].*/).first
10
10
  m3u = Pathname(m3u_meta).dirname.join(m3u) if Pathname(m3u).relative?
11
+ ffmpeg_headers = headers.empty? ? '' : "-headers '" + headers.map{|k, v| "#{k}: #{v}"}.join("\r\n") + "'"
11
12
  mp3nize(mp4_file, mp3_file) do
12
- result = Open3.capture3(%Q[ffmpeg -loglevel error -protocol_whitelist file,http,https,tcp,tls,crypto -i "#{m3u}" "#{mp4_file}"])
13
+ result = Open3.capture3(%Q[ffmpeg -loglevel error -protocol_whitelist file,http,https,tcp,tls,crypto #{ffmpeg_headers} -i "#{m3u}" "#{mp4_file}"])
13
14
  unless result[2].to_i == 0
14
15
  raise WebRadio::DownloadError.new("failed download the radio program (#{@label}).")
15
16
  end
data/lib/onsen.rb CHANGED
@@ -5,21 +5,25 @@ require 'nokogiri'
5
5
  require 'json'
6
6
 
7
7
  class Onsen < HLS
8
+ HEADERS = {
9
+ "Referer" => 'https://www.onsen.ag/'
10
+ }
11
+
8
12
  def initialize(params, options)
9
13
  super
10
14
  @cover = "//*[@class='newest-content--left']//img[1]/@src" unless @cover
11
15
  end
12
16
 
13
17
  def download
14
- html = URI.open(@url, &:read)
18
+ html = URI.open(@url, HEADERS, &:read)
15
19
  serial = Nokogiri(html).css('.play-video-info td')[0].text.scan(/\d+/)[0].to_i
16
20
  m3u8 = JSON.parse(html.scan(%r|streaming_url:("https:.*?.m3u8")|).flatten.sort.last)
17
- hls_download(@label, serial, m3u8)
21
+ hls_download(@label, serial, m3u8, HEADERS)
18
22
  end
19
23
 
20
24
  def dump
21
25
  tag = Pathname(@url).basename.to_s.gsub(%r|[-/]|, '_')
22
- html = Nokogiri(URI.open(@url, &:read))
26
+ html = Nokogiri(URI.open(@url, HEADERS, &:read))
23
27
  title = html.css('h3')[0].text
24
28
  return {
25
29
  tag => {
data/lib/standfm.rb ADDED
@@ -0,0 +1,62 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+ require 'mechanize'
4
+ require 'json'
5
+
6
+ class StandFm < WebRadio
7
+ def initialize(params, options)
8
+ super
9
+ @offset = 0
10
+ end
11
+
12
+ def download
13
+ uri = URI(@url)
14
+ html = Nokogiri(uri.open.read)
15
+ episode = uri + html.css('#root a[href^="/episodes/"]').map{|e|e.attr('href')}.uniq[@offset]
16
+
17
+ html = episode.open.read
18
+ json = JSON.parse(html.scan(%r[<script>window.__SERVER_STATE__=(.*)</script>]).flatten[0])
19
+ m4a = json['topics'].find{|k,v|v['episodeId'] == File.basename(episode.path)}.last['downloadUrl']
20
+
21
+ doc = Nokogiri(html)
22
+ serial = doc.title.scan(/#(\d+)/).flatten.first.to_i
23
+ @cover = doc.css('meta[property="og:image"]').attr('content').text unless @cover
24
+ m4a_file = "#{@label}##{serial}.m4a"
25
+ mp3_file = "#{@label}##{serial}.mp3"
26
+
27
+ mp3nize(m4a_file, mp3_file) do
28
+ open(m4a_file, 'wb:ASCII-8BIT') do |w|
29
+ w.write(URI(m4a).open.read)
30
+ end
31
+ end
32
+ end
33
+
34
+ def dump
35
+ uri = URI(@url)
36
+ tag = File.basename(uri.path)
37
+ html = Nokogiri(uri.open.read)
38
+ label, = html.css('title').text.split(/ \| /)
39
+ cover = html.css('meta[property="og:image"]').attr('content').text
40
+ return {
41
+ tag => {
42
+ 'desc' => label,
43
+ 'url' => @url,
44
+ 'label' => label,
45
+ 'cover' => cover
46
+ }
47
+ }
48
+ end
49
+
50
+ private
51
+ def find_player(url)
52
+ programs = Nokogiri(URI.open(url).read)
53
+ programs.css('.list-main-product a.wrap').each do |program|
54
+ begin
55
+ return Nokogiri(URI.open("https://asobistore.jp#{program.attr('href')}").read).css('iframe').last.attr('src')
56
+ rescue # access denied because only access by premium members
57
+ next
58
+ end
59
+ end
60
+ raise StandardError.new('movie not found.')
61
+ end
62
+ end
data/lib/webradio.rb CHANGED
@@ -20,15 +20,21 @@ 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)
29
26
  when %r[^https://asobistore\.jp/]
30
27
  require 'asobistore'
31
28
  AsobiStore.new(params, options)
29
+ when %r[^https://stand\.fm/channels/]
30
+ require 'standfm'
31
+ StandFm.new(params, options)
32
+ when %r[^https://www\.youtube\.com/playlist]
33
+ require 'youtube'
34
+ YoutubePlaylist.new(params, options)
35
+ when %r[^https://www\.youtube\.com/c/]
36
+ require 'youtube'
37
+ YoutubeChannel.new(params, options)
32
38
  else
33
39
  raise UnsupportError, "unsupported url: #{params['url']}"
34
40
  end
data/lib/youtube.rb ADDED
@@ -0,0 +1,121 @@
1
+ require 'open-uri'
2
+ require 'json'
3
+ require 'nokogiri'
4
+
5
+ class Youtube < WebRadio
6
+ def initialize(params, options)
7
+ super
8
+ @offset = 0
9
+ @target_content = []
10
+ end
11
+
12
+ private
13
+
14
+ def youtube_download(mp4_url, mp4_file, mp3_file)
15
+ begin
16
+ mp3nize(mp4_file, mp3_file) do
17
+ loop do
18
+ print '.'
19
+ _, err, status = Open3.capture3("youtube-dl -f mp4 -o '#{mp4_file}' --netrc '#{mp4_url}'")
20
+ break if status == 0
21
+ next if err =~ /403: Forbidden/
22
+ raise ForbiddenError.new("Could not access to #{mp4_url}") if err =~ /TypeError|AssertionError/
23
+ raise DownloadError.new(err)
24
+ end
25
+ end
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}"
47
+ end
48
+ end
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/watch?v=#{item["videoId"]}"
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.9.3"
8
- spec.authors = ["TADA Tadashi"]
7
+ spec.version = "4.13.0"
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, freshlive, himalaya and asobi store.}
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"
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.add_runtime_dependency "pit"
24
24
  spec.add_runtime_dependency "dropbox_api"
25
25
  spec.add_runtime_dependency "mp3info"
26
+ spec.add_runtime_dependency "rss"
26
27
 
27
28
  spec.add_development_dependency "bundler"
28
29
  spec.add_development_dependency "rake"
data/rget.yaml CHANGED
@@ -9,7 +9,6 @@ programs:
9
9
  desc: ラジオ「松井恵理子のにじらじっ!」
10
10
  url: http://www.onsen.ag/program/matsui/
11
11
  label: 松井恵理子のにじらじ
12
- cover: //*[@id='newProgramWrap']//img[1]/@src
13
12
  suzakinishi:
14
13
  desc: 洲崎西
15
14
  url: http://ch.nicovideo.jp/search/%E6%B4%B2%E5%B4%8E%E8%A5%BF?channel_id=ch2589908&mode=s&sort=f&order=d&type=video
@@ -23,3 +22,15 @@ programs:
23
22
  desc: ル美子さん
24
23
  url: https://ch.nicovideo.jp/luminesan/video?rss=2.0
25
24
  label: ル美子さん
25
+ ranacha:
26
+ desc: らなちゃのひとりごと
27
+ url: https://stand.fm/channels/5f7d9011f04555115d2170ef
28
+ label: らなちゃのひとりごと
29
+ active:
30
+ desc: 愛美とはるかの2年A組青春アクティ部!
31
+ url: https://www.youtube.com/playlist?list=PLI5zma0g_n8_y5Xk5eFwX8osz4Q2a3cqU
32
+ label: アクティ部
33
+ shimotsukin:
34
+ desc: 霜月はるかのしもつきんチャンネル
35
+ url: https://www.youtube.com/c/shimotsukinchannel/videos?view=0&sort=dd&flow=grid
36
+ 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.9.3
4
+ version: 4.13.0
5
5
  platform: ruby
6
6
  authors:
7
- - TADA Tadashi
7
+ - Tada, Tadashi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-29 00:00:00.000000000 Z
11
+ date: 2021-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rss
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: bundler
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -151,7 +165,7 @@ dependencies:
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0'
153
167
  description: Downloading newest radio programs on the web. Supported radio stations
154
- are hibiki, onsen, niconico, freshlive, himalaya and asobi store.
168
+ are hibiki, onsen, niconico, himalaya, asobi store, stand.fm and youtube.
155
169
  email:
156
170
  - t@tdtds.jp
157
171
  executables:
@@ -174,14 +188,15 @@ files:
174
188
  - bin/rget
175
189
  - lib/asobistore.rb
176
190
  - lib/dropbox.rb
177
- - lib/freshlive.rb
178
191
  - lib/hibiki.rb
179
192
  - lib/himalaya.rb
180
193
  - lib/hls.rb
181
194
  - lib/nicovideo.rb
182
195
  - lib/onsen.rb
183
196
  - lib/podcast.rb
197
+ - lib/standfm.rb
184
198
  - lib/webradio.rb
199
+ - lib/youtube.rb
185
200
  - rget.gemspec
186
201
  - rget.yaml
187
202
  homepage: https://github.com/wasamas/rget
@@ -203,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
203
218
  - !ruby/object:Gem::Version
204
219
  version: '0'
205
220
  requirements: []
206
- rubygems_version: 3.0.8
221
+ rubygems_version: 3.2.32
207
222
  signing_key:
208
223
  specification_version: 4
209
224
  summary: Downloading newest radio programs on the web.
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