rget 4.11.0 → 4.13.2

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 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