youtube-rb 0.2.0 → 0.3.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 +4 -4
- data/README.md +149 -429
- data/lib/youtube-rb/client.rb +5 -2
- data/lib/youtube-rb/downloader.rb +64 -456
- data/lib/youtube-rb/options.rb +3 -7
- data/lib/youtube-rb/version.rb +1 -1
- data/lib/youtube-rb.rb +0 -2
- data/spec/client_spec.rb +9 -16
- data/spec/download_with_mocks_spec.rb +8 -18
- data/spec/downloader_spec.rb +21 -114
- data/spec/fixtures/rickroll_full_info.json +785 -23
- data/spec/integration/ytdlp_integration_spec.rb +1 -12
- data/spec/real_download_spec.rb +1 -30
- data/spec/support/mocking_helper.rb +103 -8
- data/spec/youtube_rb_spec.rb +2 -18
- data/youtube-rb.gemspec +2 -2
- metadata +4 -5
- data/lib/youtube-rb/extractor.rb +0 -425
|
@@ -50,17 +50,7 @@ RSpec.describe "YoutubeRb + yt-dlp Integration", :integration do
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
describe "
|
|
54
|
-
it "creates client with yt-dlp backend enabled" do
|
|
55
|
-
client = YoutubeRb::Client.new(use_ytdlp: true, verbose: false)
|
|
56
|
-
expect(client.options.use_ytdlp).to be true
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
it "creates client with fallback mode" do
|
|
60
|
-
client = YoutubeRb::Client.new(ytdlp_fallback: true, verbose: false)
|
|
61
|
-
expect(client.options.ytdlp_fallback).to be true
|
|
62
|
-
end
|
|
63
|
-
|
|
53
|
+
describe "client configuration" do
|
|
64
54
|
it "creates client with verbose mode" do
|
|
65
55
|
client = YoutubeRb::Client.new(verbose: true)
|
|
66
56
|
expect(client.options.verbose).to be true
|
|
@@ -94,7 +84,6 @@ RSpec.describe "YoutubeRb + yt-dlp Integration", :integration do
|
|
|
94
84
|
it "downloads video segment using yt-dlp backend" do
|
|
95
85
|
client = YoutubeRb::Client.new(
|
|
96
86
|
output_path: output_dir,
|
|
97
|
-
use_ytdlp: true,
|
|
98
87
|
verbose: false
|
|
99
88
|
)
|
|
100
89
|
|
data/spec/real_download_spec.rb
CHANGED
|
@@ -63,7 +63,6 @@ RSpec.describe "Real video downloads", :real_download do
|
|
|
63
63
|
|
|
64
64
|
client = YoutubeRb::Client.new(
|
|
65
65
|
output_path: output_dir,
|
|
66
|
-
use_ytdlp: true,
|
|
67
66
|
verbose: true
|
|
68
67
|
)
|
|
69
68
|
|
|
@@ -112,7 +111,6 @@ RSpec.describe "Real video downloads", :real_download do
|
|
|
112
111
|
|
|
113
112
|
client = YoutubeRb::Client.new(
|
|
114
113
|
output_path: output_dir,
|
|
115
|
-
use_ytdlp: true,
|
|
116
114
|
verbose: true
|
|
117
115
|
)
|
|
118
116
|
|
|
@@ -128,39 +126,12 @@ RSpec.describe "Real video downloads", :real_download do
|
|
|
128
126
|
end
|
|
129
127
|
end
|
|
130
128
|
|
|
131
|
-
describe "fallback mechanism" do
|
|
132
|
-
let(:test_url) { 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' }
|
|
133
|
-
|
|
134
|
-
it "falls back to yt-dlp when pure Ruby fails", :very_slow do
|
|
135
|
-
puts "\n → Testing automatic fallback to yt-dlp"
|
|
136
|
-
puts " → This may download ~240MB of video data..."
|
|
137
|
-
|
|
138
|
-
client = YoutubeRb::Client.new(
|
|
139
|
-
output_path: output_dir,
|
|
140
|
-
use_ytdlp: false, # Start with pure Ruby
|
|
141
|
-
ytdlp_fallback: true, # Enable fallback
|
|
142
|
-
verbose: true
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
# This should try pure Ruby first, then fall back to yt-dlp
|
|
146
|
-
output_file = client.download(test_url)
|
|
147
|
-
|
|
148
|
-
expect(output_file).to be_a(String)
|
|
149
|
-
expect(File.exist?(output_file)).to be true
|
|
150
|
-
expect(File.size(output_file)).to be > 10_000_000 # At least 10MB
|
|
151
|
-
|
|
152
|
-
puts " ✓ Fallback worked, file downloaded"
|
|
153
|
-
puts " ✓ File size: #{(File.size(output_file) / 1024.0 / 1024.0).round(2)} MB"
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
129
|
describe "error handling" do
|
|
158
130
|
it "raises error for invalid video ID" do
|
|
159
131
|
puts "\n → Testing error handling for invalid video"
|
|
160
132
|
|
|
161
133
|
client = YoutubeRb::Client.new(
|
|
162
|
-
output_path: output_dir
|
|
163
|
-
use_ytdlp: true
|
|
134
|
+
output_path: output_dir
|
|
164
135
|
)
|
|
165
136
|
|
|
166
137
|
invalid_url = 'https://www.youtube.com/watch?v=invalidvideoid'
|
|
@@ -1,18 +1,113 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
1
4
|
module MockingHelper
|
|
2
|
-
# Mock the
|
|
3
|
-
def
|
|
5
|
+
# Mock the YtdlpWrapper to return video info without making actual yt-dlp calls
|
|
6
|
+
def mock_ytdlp(video_data)
|
|
4
7
|
video_info = YoutubeRb::VideoInfo.new(video_data)
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
# Mock YtdlpWrapper availability
|
|
10
|
+
allow(YoutubeRb::YtdlpWrapper).to receive(:available?).and_return(true)
|
|
11
|
+
|
|
12
|
+
# Mock extract_info to return video data
|
|
13
|
+
allow_any_instance_of(YoutubeRb::YtdlpWrapper).to receive(:extract_info).and_return(video_data)
|
|
14
|
+
|
|
15
|
+
# Mock download methods - create actual files
|
|
16
|
+
allow_any_instance_of(YoutubeRb::YtdlpWrapper).to receive(:download) do |instance, url, output_path|
|
|
17
|
+
# If output_path is provided, use it; otherwise generate from options
|
|
18
|
+
if output_path
|
|
19
|
+
output_file = output_path
|
|
20
|
+
else
|
|
21
|
+
options = instance.instance_variable_get(:@options)
|
|
22
|
+
output_dir = options.output_path || (@test_output_dir || './test_downloads')
|
|
23
|
+
template = options.output_template || '%(title)s-%(id)s.%(ext)s'
|
|
24
|
+
|
|
25
|
+
# Simple template replacement
|
|
26
|
+
filename = template
|
|
27
|
+
.gsub('%(title)s', (video_data['title'] || 'video').gsub(/[\/\\:*?"<>|]/, '_'))
|
|
28
|
+
.gsub('%(id)s', video_data['id'] || 'unknown')
|
|
29
|
+
.gsub('%(ext)s', video_data['ext'] || 'mp4')
|
|
30
|
+
|
|
31
|
+
output_file = File.join(output_dir, filename)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Ensure directory exists
|
|
35
|
+
FileUtils.mkdir_p(File.dirname(output_file))
|
|
36
|
+
|
|
37
|
+
# Create a fake video file
|
|
38
|
+
File.write(output_file, "fake video data" * 1000)
|
|
39
|
+
|
|
40
|
+
# Create subtitle files if requested
|
|
41
|
+
options = instance.instance_variable_get(:@options)
|
|
42
|
+
if options && (options.write_subtitles || options.write_auto_sub)
|
|
43
|
+
base_name = File.basename(output_file, File.extname(output_file))
|
|
44
|
+
subtitle_langs = options.subtitle_langs || ['en']
|
|
45
|
+
subtitle_format = options.subtitle_format || 'srt'
|
|
46
|
+
available_subtitles = video_data['subtitles'] || {}
|
|
47
|
+
|
|
48
|
+
subtitle_langs.each do |lang|
|
|
49
|
+
# Only create subtitle file if language is available in video data
|
|
50
|
+
next unless available_subtitles.key?(lang)
|
|
51
|
+
|
|
52
|
+
subtitle_file = File.join(File.dirname(output_file), "#{base_name}.#{lang}.#{subtitle_format}")
|
|
53
|
+
File.write(subtitle_file, "WEBVTT\n\n00:00:00.000 --> 00:00:10.000\nTest subtitle")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Create metadata files if requested
|
|
58
|
+
if options
|
|
59
|
+
base_name = File.basename(output_file, File.extname(output_file))
|
|
60
|
+
dir = File.dirname(output_file)
|
|
61
|
+
|
|
62
|
+
if options.write_info_json
|
|
63
|
+
info_file = File.join(dir, "#{base_name}.info.json")
|
|
64
|
+
File.write(info_file, JSON.generate(video_data))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if options.write_thumbnail && video_data['thumbnail']
|
|
68
|
+
ext = File.extname(video_data['thumbnail']).split('?').first || '.jpg'
|
|
69
|
+
thumbnail_file = File.join(dir, "#{base_name}#{ext}")
|
|
70
|
+
File.write(thumbnail_file, "fake thumbnail data")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
if options.write_description && video_data['description']
|
|
74
|
+
desc_file = File.join(dir, "#{base_name}.description")
|
|
75
|
+
File.write(desc_file, video_data['description'])
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
output_file
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Mock download_segment
|
|
83
|
+
allow_any_instance_of(YoutubeRb::YtdlpWrapper).to receive(:download_segment) do |_, url, start_time, end_time, output_path|
|
|
84
|
+
output_file = output_path || File.join(@test_output_dir || './test_downloads', "segment-#{start_time}-#{end_time}.mp4")
|
|
85
|
+
|
|
86
|
+
# Ensure directory exists
|
|
87
|
+
FileUtils.mkdir_p(File.dirname(output_file))
|
|
88
|
+
|
|
89
|
+
# Create a fake segment file
|
|
90
|
+
File.write(output_file, "fake segment data" * 1000)
|
|
91
|
+
|
|
92
|
+
output_file
|
|
93
|
+
end
|
|
9
94
|
|
|
10
95
|
video_info
|
|
11
96
|
end
|
|
12
97
|
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
98
|
+
# For backward compatibility with tests that use mock_extractor
|
|
99
|
+
alias_method :mock_extractor, :mock_ytdlp
|
|
100
|
+
|
|
101
|
+
# Mock YtdlpWrapper to raise error
|
|
102
|
+
def mock_ytdlp_error(message = 'Test error')
|
|
103
|
+
allow(YoutubeRb::YtdlpWrapper).to receive(:available?).and_return(true)
|
|
104
|
+
allow_any_instance_of(YoutubeRb::YtdlpWrapper).to receive(:extract_info)
|
|
105
|
+
.and_raise(YoutubeRb::YtdlpWrapper::YtdlpError, message)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# For backward compatibility
|
|
109
|
+
def mock_extractor_error(error_class = nil, message = 'Test error')
|
|
110
|
+
mock_ytdlp_error(message)
|
|
16
111
|
end
|
|
17
112
|
end
|
|
18
113
|
|
data/spec/youtube_rb_spec.rb
CHANGED
|
@@ -59,8 +59,6 @@ RSpec.describe YoutubeRb do
|
|
|
59
59
|
expect(options.format).to eq('best')
|
|
60
60
|
expect(options.quality).to eq('best')
|
|
61
61
|
expect(options.output_path).to eq('./downloads')
|
|
62
|
-
expect(options.use_ytdlp).to eq(false)
|
|
63
|
-
expect(options.ytdlp_fallback).to eq(true)
|
|
64
62
|
expect(options.verbose).to eq(false)
|
|
65
63
|
end
|
|
66
64
|
|
|
@@ -69,26 +67,22 @@ RSpec.describe YoutubeRb do
|
|
|
69
67
|
format: '1080p',
|
|
70
68
|
output_path: '/tmp',
|
|
71
69
|
write_subtitles: true,
|
|
72
|
-
use_ytdlp: true,
|
|
73
70
|
verbose: true
|
|
74
71
|
)
|
|
75
72
|
expect(options.format).to eq('1080p')
|
|
76
73
|
expect(options.output_path).to eq('/tmp')
|
|
77
74
|
expect(options.write_subtitles).to eq(true)
|
|
78
|
-
expect(options.use_ytdlp).to eq(true)
|
|
79
75
|
expect(options.verbose).to eq(true)
|
|
80
76
|
end
|
|
81
77
|
end
|
|
82
78
|
|
|
83
79
|
describe "#to_h" do
|
|
84
80
|
it "returns hash representation" do
|
|
85
|
-
options = YoutubeRb::Options.new(format: 'best',
|
|
81
|
+
options = YoutubeRb::Options.new(format: 'best', verbose: true)
|
|
86
82
|
hash = options.to_h
|
|
87
83
|
expect(hash).to be_a(Hash)
|
|
88
84
|
expect(hash[:format]).to eq('best')
|
|
89
|
-
expect(hash[:
|
|
90
|
-
expect(hash).to have_key(:ytdlp_fallback)
|
|
91
|
-
expect(hash).to have_key(:verbose)
|
|
85
|
+
expect(hash[:verbose]).to eq(true)
|
|
92
86
|
end
|
|
93
87
|
end
|
|
94
88
|
|
|
@@ -169,16 +163,6 @@ RSpec.describe YoutubeRb do
|
|
|
169
163
|
end
|
|
170
164
|
end
|
|
171
165
|
|
|
172
|
-
describe YoutubeRb::Extractor do
|
|
173
|
-
let(:extractor) { YoutubeRb::Extractor.new('https://www.youtube.com/watch?v=test') }
|
|
174
|
-
|
|
175
|
-
describe "#initialize" do
|
|
176
|
-
it "creates extractor with URL" do
|
|
177
|
-
expect(extractor.url).to eq('https://www.youtube.com/watch?v=test')
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
|
|
182
166
|
describe YoutubeRb::Downloader do
|
|
183
167
|
let(:url) { 'https://www.youtube.com/watch?v=test' }
|
|
184
168
|
let(:options) { YoutubeRb::Options.new(output_path: './test_downloads') }
|
data/youtube-rb.gemspec
CHANGED
|
@@ -12,9 +12,9 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.license = "MIT"
|
|
13
13
|
spec.required_ruby_version = ">= 3.4.0"
|
|
14
14
|
|
|
15
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
|
16
15
|
spec.metadata["source_code_uri"] = "https://github.com/Qew7/youtube-rb"
|
|
17
16
|
spec.metadata["changelog_uri"] = "https://github.com/Qew7/youtube-rb/blob/master/CHANGELOG.md"
|
|
17
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/Qew7/youtube-rb/issues"
|
|
18
18
|
|
|
19
19
|
# Specify which files should be added to the gem when it is released.
|
|
20
20
|
spec.files = Dir.glob("{lib,bin,spec}/**/*") + %w[README.md LICENSE Rakefile youtube-rb.gemspec]
|
|
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
|
|
|
31
31
|
spec.add_dependency "base64", "~> 0.2"
|
|
32
32
|
|
|
33
33
|
# Development dependencies
|
|
34
|
-
spec.add_development_dependency "bundler", ">= 2.
|
|
34
|
+
spec.add_development_dependency "bundler", ">= 2.0"
|
|
35
35
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
36
36
|
spec.add_development_dependency "rspec", "~> 3.13"
|
|
37
37
|
spec.add_development_dependency "webmock", "~> 3.26"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: youtube-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Maxim Veysgeym
|
|
@@ -99,14 +99,14 @@ dependencies:
|
|
|
99
99
|
requirements:
|
|
100
100
|
- - ">="
|
|
101
101
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '2.
|
|
102
|
+
version: '2.0'
|
|
103
103
|
type: :development
|
|
104
104
|
prerelease: false
|
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
106
|
requirements:
|
|
107
107
|
- - ">="
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '2.
|
|
109
|
+
version: '2.0'
|
|
110
110
|
- !ruby/object:Gem::Dependency
|
|
111
111
|
name: rake
|
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -181,7 +181,6 @@ files:
|
|
|
181
181
|
- lib/youtube-rb.rb
|
|
182
182
|
- lib/youtube-rb/client.rb
|
|
183
183
|
- lib/youtube-rb/downloader.rb
|
|
184
|
-
- lib/youtube-rb/extractor.rb
|
|
185
184
|
- lib/youtube-rb/options.rb
|
|
186
185
|
- lib/youtube-rb/version.rb
|
|
187
186
|
- lib/youtube-rb/video_info.rb
|
|
@@ -206,9 +205,9 @@ homepage: https://github.com/Qew7/youtube-rb
|
|
|
206
205
|
licenses:
|
|
207
206
|
- MIT
|
|
208
207
|
metadata:
|
|
209
|
-
homepage_uri: https://github.com/Qew7/youtube-rb
|
|
210
208
|
source_code_uri: https://github.com/Qew7/youtube-rb
|
|
211
209
|
changelog_uri: https://github.com/Qew7/youtube-rb/blob/master/CHANGELOG.md
|
|
210
|
+
bug_tracker_uri: https://github.com/Qew7/youtube-rb/issues
|
|
212
211
|
rdoc_options: []
|
|
213
212
|
require_paths:
|
|
214
213
|
- lib
|