rget 4.10.0 → 4.13.1

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: 002a0d55ff230dabdb3c046e67cf09a56f28aae9bbbb44c3bd7de3a4490cc770
4
- data.tar.gz: 33f86195c750fb5cad0f133fe199fc8a94fe65f6a5bae2b0eb372eb7a34aed6f
3
+ metadata.gz: 61e6f81e8c57cc0ffdee5a2455b37a22d9eb012b07ac0106faba68c5a628a063
4
+ data.tar.gz: dc9c5a901981b16a765664062ba3fe135d9bb46cbaa56f49b7637902d705aba2
5
5
  SHA512:
6
- metadata.gz: f144e5b3623c29342f278b22de551b3e535f8ce1a699d6454116718a4653bc4425b49ce3f4e8062cc98ca08e4940e6ce77462456161a99144008e9c5a36a7972
7
- data.tar.gz: cf9a018da02a9651e554cff2237bebbe744aff02073799b10f02806fd55079e051fc123f2c79802694efc030682126b52eb2f128041d553468a8cdc6564a0e0b
6
+ metadata.gz: a976800fbf484379d64b9728e8d5feae3a16ffa04ec8d8298f74ed3914d0022d1ea9a6f19cb8d888cafed34d68df99916f02572adf4c44d0ecda8756c202ff05
7
+ data.tar.gz: 7536109c0076f84727db67e00ae36cdabb82219413a2d3fc928501ced572efc06d978557056547634642721883f41efc4790b67eb129ff441745d2e009f379f0
@@ -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,9 +8,10 @@ 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 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/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
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)
@@ -32,6 +29,12 @@ class WebRadio
32
29
  when %r[^https://stand\.fm/channels/]
33
30
  require 'standfm'
34
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)
35
38
  else
36
39
  raise UnsupportError, "unsupported url: #{params['url']}"
37
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.10.0"
7
+ spec.version = "4.13.1"
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 and stand.fm.}
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
@@ -27,3 +26,11 @@ programs:
27
26
  desc: らなちゃのひとりごと
28
27
  url: https://stand.fm/channels/5f7d9011f04555115d2170ef
29
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.10.0
4
+ version: 4.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tada, Tadashi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-13 00:00:00.000000000 Z
11
+ date: 2022-02-09 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 and stand.fm.
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
@@ -183,6 +196,7 @@ files:
183
196
  - lib/podcast.rb
184
197
  - lib/standfm.rb
185
198
  - lib/webradio.rb
199
+ - lib/youtube.rb
186
200
  - rget.gemspec
187
201
  - rget.yaml
188
202
  homepage: https://github.com/wasamas/rget
@@ -204,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
218
  - !ruby/object:Gem::Version
205
219
  version: '0'
206
220
  requirements: []
207
- rubygems_version: 3.0.8
221
+ rubygems_version: 3.3.4
208
222
  signing_key:
209
223
  specification_version: 4
210
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