rget 4.11.0 → 4.13.2

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: 399f9407dcfa7a2c99884c4c7c1cac60fabaae386622bac006896d877e2f1c71
4
- data.tar.gz: 5ba2047c907946753452437b51ecf3a55f3e7fa97e247c6ffab04f10badda4fc
3
+ metadata.gz: bef9d92d8791dfd9e2e4cf6852a30d07bf4bcd8a46a492a443d1d7223ac7877d
4
+ data.tar.gz: d03997be575932a0cc303ba44848c5178bd3802efcccfa223507eb0e3b417165
5
5
  SHA512:
6
- metadata.gz: f0d57aff2683b09e53fbd663a11db02e862430d51ed97588c317e1432aa0b5d13625fc6747a8c83b210b1b394a726c48662af5c2b22ba24bbd08d8ddebf08487
7
- data.tar.gz: de34fca3119bbb1a78704131e0a825fe3f2d0b77d1426dbe08bb0cf94eed751f6961b1c34e37a55f2907dfafed803877f692aeafffe780f9513b1b239270ba03
6
+ metadata.gz: 208553638033e5d34e9367c7a44da64620bd8f3306b91e70d831a087607a22d5e3467a6ae769678e59ca687b7fb55539ff6b24720e0c2944976b86a2110c09b1
7
+ data.tar.gz: d7e6f651fb7c91df44f5fe0947f627bbcef767019719e4405c288bcad668eb3262178526100cb11b49a303720fb0873456c2964cb7cdd2a879ff2be1e6ba6d0c
@@ -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/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 only)
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/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/nicovideo.rb CHANGED
@@ -31,7 +31,7 @@ class Nicovideo < WebRadio
31
31
  serial = tmp if tmp > 0
32
32
  end
33
33
  appendix = title =~ /おまけ|アフタートーク/ ? 'a' : ''
34
- @file = "#{@label}##{'%02d' % serial}#{appendix}.#{video.type}"
34
+ @file = "#{@label}##{'%02d' % serial}#{appendix}.#{video.type || 'mp4'}"
35
35
  @mp3_file = @file.sub(/\....$/, '.mp3')
36
36
  mp3nize(@file, @mp3_file) do
37
37
  loop do
@@ -39,7 +39,7 @@ class Nicovideo < WebRadio
39
39
  _, err, status = Open3.capture3("youtube-dl -f mp4 -o #{@file} --netrc #{video.url}")
40
40
  break if status == 0
41
41
  next if err =~ /403: Forbidden/
42
- raise ForbiddenError.new("Could not access to #{video.url}") if err =~ /TypeError|AssertionError/
42
+ raise ForbiddenError.new("Could not access to #{video.url}") if err =~ /TypeError|AssertionError|The video can't be downloaded/
43
43
  raise DownloadError.new(err)
44
44
  end
45
45
  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/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
- Youtube.new(params, options)
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
- def initialize(params, options)
6
- super
7
- @offset = 0
8
- end
6
+ def initialize(params, options)
7
+ super
8
+ @offset = 0
9
+ @target_content = []
10
+ end
9
11
 
10
- def download
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
- mp4_file = "#{@label}##{serial}.mp4"
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
- rescue ForbiddenError
33
- puts "#{$!.message}, try next."
34
- @offset += 1
35
- retry
36
- rescue NotFoundError
37
- raise DownloadError.new('video not found')
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/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.11.0"
7
+ spec.version = "4.13.2"
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 playlist.}
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
@@ -30,4 +29,8 @@ programs:
30
29
  active:
31
30
  desc: 愛美とはるかの2年A組青春アクティ部!
32
31
  url: https://www.youtube.com/playlist?list=PLI5zma0g_n8_y5Xk5eFwX8osz4Q2a3cqU
33
- label: アクティ部
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.11.0
4
+ version: 4.13.2
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-03 00:00:00.000000000 Z
11
+ date: 2022-02-15 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, himalaya, asobi store, stand.fm and youtube playlist.
168
+ are hibiki, onsen, niconico, himalaya, asobi store, stand.fm and youtube.
155
169
  email:
156
170
  - t@tdtds.jp
157
171
  executables:
@@ -174,7 +188,6 @@ 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
@@ -205,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
205
218
  - !ruby/object:Gem::Version
206
219
  version: '0'
207
220
  requirements: []
208
- rubygems_version: 3.0.8
221
+ rubygems_version: 3.3.4
209
222
  signing_key:
210
223
  specification_version: 4
211
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