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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 399f9407dcfa7a2c99884c4c7c1cac60fabaae386622bac006896d877e2f1c71
4
- data.tar.gz: 5ba2047c907946753452437b51ecf3a55f3e7fa97e247c6ffab04f10badda4fc
3
+ metadata.gz: 313f0120701854057599c157f85971584cd70eed1dd45f6d8cc5ea1444cc714c
4
+ data.tar.gz: 0f998f200f67b6a28186d3070b4b3f03e50b76d14e9c6def56a39afbdfda7a52
5
5
  SHA512:
6
- metadata.gz: f0d57aff2683b09e53fbd663a11db02e862430d51ed97588c317e1432aa0b5d13625fc6747a8c83b210b1b394a726c48662af5c2b22ba24bbd08d8ddebf08487
7
- data.tar.gz: de34fca3119bbb1a78704131e0a825fe3f2d0b77d1426dbe08bb0cf94eed751f6961b1c34e37a55f2907dfafed803877f692aeafffe780f9513b1b239270ba03
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
- "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:
@@ -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
@@ -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#{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
@@ -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.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 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"
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.11.0
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-03 00:00:00.000000000 Z
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 playlist.
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
@@ -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