rubytube 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +19 -0
- data/Rakefile +10 -0
- data/lib/rubytube/cipher.rb +369 -0
- data/lib/rubytube/client.rb +165 -0
- data/lib/rubytube/extractor.rb +177 -0
- data/lib/rubytube/innertube.rb +105 -0
- data/lib/rubytube/monostate.rb +5 -0
- data/lib/rubytube/parser.rb +164 -0
- data/lib/rubytube/request.rb +75 -0
- data/lib/rubytube/stream.rb +81 -0
- data/lib/rubytube/stream_query.rb +33 -0
- data/lib/rubytube/utils.rb +24 -0
- data/lib/rubytube/version.rb +5 -0
- data/lib/rubytube.rb +67 -0
- data/sig/rubytube.rbs +4 -0
- metadata +95 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
module RubyTube
|
2
|
+
class Stream
|
3
|
+
attr_accessor(
|
4
|
+
:monostate,
|
5
|
+
:url,
|
6
|
+
:itag,
|
7
|
+
:mime_type,
|
8
|
+
:codecs,
|
9
|
+
:type,
|
10
|
+
:subtype,
|
11
|
+
:file_size,
|
12
|
+
:is_otf,
|
13
|
+
:bitrate
|
14
|
+
)
|
15
|
+
|
16
|
+
def initialize(stream, monostate)
|
17
|
+
self.monostate = monostate
|
18
|
+
|
19
|
+
self.url = stream['url']
|
20
|
+
self.itag = stream['itag'].to_i
|
21
|
+
|
22
|
+
self.mime_type, self.codecs = Extractor.mime_type_codec(stream['mimeType'])
|
23
|
+
self.type, self.subtype = mime_type.split('/')
|
24
|
+
|
25
|
+
self.is_otf = stream['is_otf']
|
26
|
+
self.bitrate = stream['bitrate']
|
27
|
+
|
28
|
+
self.file_size = stream.fetch('contentLength', 0).to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
def download(filename: nil, output_dir: nil)
|
32
|
+
file_path = get_file_path(filename, output_dir)
|
33
|
+
|
34
|
+
return file_path if File.exist?(file_path)
|
35
|
+
|
36
|
+
bytes_remaining = file_size
|
37
|
+
|
38
|
+
File.open(file_path, 'wb') do |f|
|
39
|
+
begin
|
40
|
+
Request.stream(url) do |chunk|
|
41
|
+
bytes_remaining -= chunk.bytesize
|
42
|
+
f.write(chunk)
|
43
|
+
end
|
44
|
+
rescue HTTPError => e
|
45
|
+
raise e if e.code != 404
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
file_path
|
50
|
+
end
|
51
|
+
|
52
|
+
def is_audio?
|
53
|
+
type == 'audio'
|
54
|
+
end
|
55
|
+
|
56
|
+
def is_video?
|
57
|
+
type == 'video'
|
58
|
+
end
|
59
|
+
|
60
|
+
def title
|
61
|
+
monostate.title
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def get_file_path(filename, output_dir, prefix = '')
|
67
|
+
filename = default_filename unless filename
|
68
|
+
|
69
|
+
if prefix
|
70
|
+
filename = "#{prefix}#{filename}"
|
71
|
+
end
|
72
|
+
|
73
|
+
output_path = Utils.target_directory(output_dir)
|
74
|
+
File.join(output_path, filename)
|
75
|
+
end
|
76
|
+
|
77
|
+
def default_filename
|
78
|
+
"#{monostate.title}.#{subtype}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module RubyTube
|
2
|
+
class StreamQuery < Array
|
3
|
+
attr_reader :streams
|
4
|
+
|
5
|
+
def initialize(fmt_streams)
|
6
|
+
super
|
7
|
+
@streams = fmt_streams
|
8
|
+
end
|
9
|
+
|
10
|
+
def filter(file_extension: nil, only_audio: false, only_video: false)
|
11
|
+
filters = []
|
12
|
+
|
13
|
+
filters << ->(stream) { stream.subtype == file_extension } if file_extension
|
14
|
+
filters << ->(stream) { stream.is_audio? } if only_audio
|
15
|
+
filters << ->(stream) { stream.is_video? } if only_video
|
16
|
+
|
17
|
+
r = streams
|
18
|
+
filters.each do |f|
|
19
|
+
r = r.select(&f)
|
20
|
+
end
|
21
|
+
|
22
|
+
r
|
23
|
+
end
|
24
|
+
|
25
|
+
def first
|
26
|
+
streams.first
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_by_itag(itag)
|
30
|
+
streams.find { |s| s.itag == itag }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RubyTube
|
2
|
+
module Utils
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def regex_search(pattern, string, group)
|
6
|
+
match = string.match(pattern)
|
7
|
+
if match
|
8
|
+
return match[group]
|
9
|
+
end
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def target_directory(output_path = nil)
|
14
|
+
if output_path
|
15
|
+
result = File.join(Dir.pwd, output_path) unless File.absolute_path?(output_path)
|
16
|
+
else
|
17
|
+
result = Dir.pwd
|
18
|
+
end
|
19
|
+
|
20
|
+
FileUtils.mkdir_p(result) unless Dir.exist?(result)
|
21
|
+
result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/rubytube.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'faraday/follow_redirects'
|
5
|
+
|
6
|
+
require_relative 'rubytube/version'
|
7
|
+
|
8
|
+
require_relative 'rubytube/cipher'
|
9
|
+
require_relative 'rubytube/client'
|
10
|
+
require_relative 'rubytube/extractor'
|
11
|
+
require_relative 'rubytube/innertube'
|
12
|
+
require_relative 'rubytube/monostate'
|
13
|
+
require_relative 'rubytube/parser'
|
14
|
+
require_relative 'rubytube/request'
|
15
|
+
require_relative 'rubytube/stream_query'
|
16
|
+
require_relative 'rubytube/stream'
|
17
|
+
require_relative 'rubytube/utils'
|
18
|
+
|
19
|
+
module RubyTube
|
20
|
+
class Error < StandardError; end
|
21
|
+
class HTMLParseError < StandardError; end
|
22
|
+
class ExtractError < StandardError; end
|
23
|
+
class MaxRetriesExceeded < StandardError; end
|
24
|
+
class VideoUnavailable < StandardError; end
|
25
|
+
|
26
|
+
class RegexMatchError < StandardError
|
27
|
+
def initialize(caller, pattern)
|
28
|
+
super("Regex match error in #{caller} for pattern #{pattern}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class MembersOnly < StandardError
|
33
|
+
def initialize(video_id)
|
34
|
+
super("Members only video: #{video_id}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class RecordingUnavailable < StandardError
|
39
|
+
def initialize(video_id)
|
40
|
+
super("Recording unavailable: #{video_id}")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class VideoUnavailable < StandardError
|
45
|
+
def initialize(video_id)
|
46
|
+
super("Video unavailable: #{video_id}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class VideoPrivate < StandardError
|
51
|
+
def initialize(video_id)
|
52
|
+
super("Video is private: #{video_id}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class LiveStreamError < StandardError
|
57
|
+
def initialize(video_id)
|
58
|
+
super("Video is a live stream: #{video_id}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class << self
|
63
|
+
def new(url)
|
64
|
+
Client.new(url)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/sig/rubytube.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubytube
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- nightswinger
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-09-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.0.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.0.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday-follow_redirects
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Write a longer description or delete this line.
|
42
|
+
email:
|
43
|
+
- stardustkids83@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".rspec"
|
49
|
+
- ".standard.yml"
|
50
|
+
- CHANGELOG.md
|
51
|
+
- CODE_OF_CONDUCT.md
|
52
|
+
- Gemfile
|
53
|
+
- LICENSE.txt
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- lib/rubytube.rb
|
57
|
+
- lib/rubytube/cipher.rb
|
58
|
+
- lib/rubytube/client.rb
|
59
|
+
- lib/rubytube/extractor.rb
|
60
|
+
- lib/rubytube/innertube.rb
|
61
|
+
- lib/rubytube/monostate.rb
|
62
|
+
- lib/rubytube/parser.rb
|
63
|
+
- lib/rubytube/request.rb
|
64
|
+
- lib/rubytube/stream.rb
|
65
|
+
- lib/rubytube/stream_query.rb
|
66
|
+
- lib/rubytube/utils.rb
|
67
|
+
- lib/rubytube/version.rb
|
68
|
+
- sig/rubytube.rbs
|
69
|
+
homepage: https://github.com/nightswinger/rubytube
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
metadata:
|
73
|
+
homepage_uri: https://github.com/nightswinger/rubytube
|
74
|
+
source_code_uri: https://github.com/nightswinger/rubytube
|
75
|
+
changelog_uri: https://github.com/nightswinger/rubytube
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 2.6.0
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubygems_version: 3.4.10
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: Write a short summary, because RubyGems requires one.
|
95
|
+
test_files: []
|