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.
data/lib/youtube-rb.rb CHANGED
@@ -1,14 +1,12 @@
1
1
  require_relative "youtube-rb/version"
2
2
  require_relative "youtube-rb/options"
3
3
  require_relative "youtube-rb/video_info"
4
- require_relative "youtube-rb/extractor"
5
4
  require_relative "youtube-rb/ytdlp_wrapper"
6
5
  require_relative "youtube-rb/downloader"
7
6
  require_relative "youtube-rb/client"
8
7
 
9
8
  module YoutubeRb
10
9
  class Error < StandardError; end
11
- class ExtractionError < Error; end
12
10
  class DownloadError < Error; end
13
11
  class ValidationError < Error; end
14
12
 
data/spec/client_spec.rb CHANGED
@@ -4,7 +4,7 @@ RSpec.describe YoutubeRb::Client do
4
4
  let(:client) { described_class.new(output_path: @test_output_dir) }
5
5
 
6
6
  before do
7
- mock_extractor(video_data)
7
+ mock_ytdlp(video_data)
8
8
  end
9
9
 
10
10
  describe '#initialize' do
@@ -62,11 +62,11 @@ RSpec.describe YoutubeRb::Client do
62
62
  end
63
63
 
64
64
  it 'raises error for invalid URL' do
65
- mock_extractor_error
65
+ mock_ytdlp_error
66
66
 
67
67
  expect {
68
68
  client.info('https://www.youtube.com/watch?v=invalid')
69
- }.to raise_error(YoutubeRb::Extractor::ExtractionError)
69
+ }.to raise_error(StandardError)
70
70
  end
71
71
  end
72
72
 
@@ -96,7 +96,7 @@ RSpec.describe YoutubeRb::Client do
96
96
  custom_dir = File.join(@test_output_dir, 'nested', 'path')
97
97
  custom_client = described_class.new(output_path: custom_dir)
98
98
 
99
- mock_extractor(video_data)
99
+ mock_ytdlp(video_data)
100
100
  stub_video_download(video_url)
101
101
 
102
102
  output_file = custom_client.download(test_url)
@@ -275,7 +275,7 @@ RSpec.describe YoutubeRb::Client do
275
275
  subtitle_format: 'vtt'
276
276
  )
277
277
 
278
- mock_extractor(video_data)
278
+ mock_ytdlp(video_data)
279
279
  video_data['subtitles'].each do |lang, subs|
280
280
  subs.each { |sub| stub_subtitle_download(sub['url']) }
281
281
  end
@@ -366,13 +366,6 @@ RSpec.describe YoutubeRb::Client do
366
366
  expect(output_file).to be_a(String)
367
367
  end
368
368
 
369
- it 'raises error if ffmpeg not available' do
370
- allow_any_instance_of(YoutubeRb::Downloader).to receive(:ffmpeg_available?).and_return(false)
371
-
372
- expect {
373
- client.extract_audio(test_url)
374
- }.to raise_error(YoutubeRb::Downloader::DownloadError, /FFmpeg is required/)
375
- end
376
369
  end
377
370
 
378
371
  describe '#valid_url?' do
@@ -381,7 +374,7 @@ RSpec.describe YoutubeRb::Client do
381
374
  end
382
375
 
383
376
  it 'returns false for invalid URL' do
384
- mock_extractor_error
377
+ mock_ytdlp_error
385
378
 
386
379
  expect(client.valid_url?('https://www.youtube.com/watch?v=invalid')).to be false
387
380
  end
@@ -395,7 +388,7 @@ RSpec.describe YoutubeRb::Client do
395
388
  end
396
389
 
397
390
  it 'returns false for non-YouTube URL' do
398
- mock_extractor_error(YoutubeRb::Extractor::ExtractionError, 'Not a YouTube URL')
391
+ mock_ytdlp_error('Not a YouTube URL')
399
392
 
400
393
  expect(client.valid_url?('https://example.com')).to be false
401
394
  end
@@ -416,7 +409,7 @@ RSpec.describe YoutubeRb::Client do
416
409
  no_formats_data = video_data.dup
417
410
  no_formats_data['formats'] = []
418
411
 
419
- mock_extractor(no_formats_data)
412
+ mock_ytdlp(no_formats_data)
420
413
 
421
414
  formats = client.formats('https://www.youtube.com/watch?v=noformats')
422
415
  expect(formats).to eq([])
@@ -437,7 +430,7 @@ RSpec.describe YoutubeRb::Client do
437
430
  no_subs_data = video_data.dup
438
431
  no_subs_data['subtitles'] = {}
439
432
 
440
- mock_extractor(no_subs_data)
433
+ mock_ytdlp(no_subs_data)
441
434
 
442
435
  subtitles = client.subtitles('https://www.youtube.com/watch?v=nosubs')
443
436
  expect(subtitles).to eq({})
@@ -13,10 +13,9 @@ RSpec.describe "Download with mocks" do
13
13
  before(:each) do
14
14
  FileUtils.mkdir_p(output_dir)
15
15
 
16
- # Mock the Extractor to return real data
17
- allow_any_instance_of(YoutubeRb::Extractor).to receive(:extract_info) do
18
- YoutubeRb::VideoInfo.new(rickroll_info)
19
- end
16
+ # Mock YtdlpWrapper to return real data
17
+ allow(YoutubeRb::YtdlpWrapper).to receive(:available?).and_return(true)
18
+ allow_any_instance_of(YoutubeRb::YtdlpWrapper).to receive(:extract_info).and_return(rickroll_info)
20
19
  end
21
20
 
22
21
  after(:each) do
@@ -70,8 +69,7 @@ RSpec.describe "Download with mocks" do
70
69
  end
71
70
 
72
71
  client = YoutubeRb::Client.new(
73
- output_path: output_dir,
74
- use_ytdlp: true
72
+ output_path: output_dir
75
73
  )
76
74
 
77
75
  output_file = client.download(rickroll_url)
@@ -101,8 +99,7 @@ RSpec.describe "Download with mocks" do
101
99
  end
102
100
 
103
101
  client = YoutubeRb::Client.new(
104
- output_path: output_dir,
105
- use_ytdlp: true
102
+ output_path: output_dir
106
103
  )
107
104
 
108
105
  output_file = client.download_segment(rickroll_url, start_time, end_time)
@@ -146,7 +143,7 @@ RSpec.describe "Download with mocks" do
146
143
  # It's better tested in real_download_spec.rb with actual downloads
147
144
  end
148
145
 
149
- it "respects use_ytdlp: true option" do
146
+ it "always uses yt-dlp for downloads" do
150
147
  tried_ytdlp = false
151
148
 
152
149
  allow_any_instance_of(YoutubeRb::YtdlpWrapper).to receive(:download) do
@@ -157,8 +154,7 @@ RSpec.describe "Download with mocks" do
157
154
  end
158
155
 
159
156
  client = YoutubeRb::Client.new(
160
- output_path: output_dir,
161
- use_ytdlp: true
157
+ output_path: output_dir
162
158
  )
163
159
 
164
160
  client.download(rickroll_url)
@@ -173,14 +169,8 @@ RSpec.describe "Download with mocks" do
173
169
  raise YoutubeRb::YtdlpWrapper::YtdlpError, "Video unavailable"
174
170
  end
175
171
 
176
- allow_any_instance_of(YoutubeRb::Extractor).to receive(:extract_info) do
177
- raise YoutubeRb::Extractor::ExtractionError, "Failed to extract"
178
- end
179
-
180
172
  client = YoutubeRb::Client.new(
181
- output_path: output_dir,
182
- use_ytdlp: true,
183
- ytdlp_fallback: false
173
+ output_path: output_dir
184
174
  )
185
175
 
186
176
  expect {
@@ -5,7 +5,7 @@ RSpec.describe YoutubeRb::Downloader do
5
5
  let(:downloader) { described_class.new(test_url, options) }
6
6
 
7
7
  before do
8
- mock_extractor(video_data)
8
+ mock_ytdlp(video_data)
9
9
  end
10
10
 
11
11
  describe '#initialize' do
@@ -49,11 +49,9 @@ RSpec.describe YoutubeRb::Downloader do
49
49
  expect(info1).to equal(info2)
50
50
  end
51
51
 
52
- it 'calls extractor only once' do
53
- video_info = YoutubeRb::VideoInfo.new(video_data)
54
-
55
- expect_any_instance_of(YoutubeRb::Extractor)
56
- .to receive(:extract_info).once.and_return(video_info)
52
+ it 'calls ytdlp only once' do
53
+ expect_any_instance_of(YoutubeRb::YtdlpWrapper)
54
+ .to receive(:extract_info).once.and_return(video_data)
57
55
 
58
56
  downloader.info
59
57
  downloader.info
@@ -91,7 +89,7 @@ RSpec.describe YoutubeRb::Downloader do
91
89
  )
92
90
  dl = described_class.new(test_url, custom_options)
93
91
 
94
- mock_extractor(video_data)
92
+ mock_ytdlp(video_data)
95
93
  stub_video_download(video_url)
96
94
 
97
95
  output_file = dl.download
@@ -103,7 +101,7 @@ RSpec.describe YoutubeRb::Downloader do
103
101
  bad_title_data = video_data.dup
104
102
  bad_title_data['title'] = 'Test/Video:With*Bad?Chars'
105
103
 
106
- mock_extractor(bad_title_data)
104
+ mock_ytdlp(bad_title_data)
107
105
  stub_video_download(video_url)
108
106
 
109
107
  dl = described_class.new(
@@ -134,7 +132,7 @@ RSpec.describe YoutubeRb::Downloader do
134
132
  )
135
133
  dl = described_class.new(test_url, subtitle_options)
136
134
 
137
- mock_extractor(video_data)
135
+ mock_ytdlp(video_data)
138
136
  stub_video_download(video_url)
139
137
  video_data['subtitles'].each do |lang, subs|
140
138
  subs.each { |sub| stub_subtitle_download(sub['url']) }
@@ -149,7 +147,7 @@ RSpec.describe YoutubeRb::Downloader do
149
147
  it 'skips subtitles when disabled' do
150
148
  dl = described_class.new(test_url, options)
151
149
 
152
- mock_extractor(video_data)
150
+ mock_ytdlp(video_data)
153
151
  stub_video_download(video_url)
154
152
 
155
153
  dl.download
@@ -171,7 +169,7 @@ RSpec.describe YoutubeRb::Downloader do
171
169
  )
172
170
  dl = described_class.new(test_url, metadata_options)
173
171
 
174
- mock_extractor(video_data)
172
+ mock_ytdlp(video_data)
175
173
  stub_video_download(video_url)
176
174
  stub_thumbnail_download(video_data['thumbnail'])
177
175
 
@@ -191,7 +189,7 @@ RSpec.describe YoutubeRb::Downloader do
191
189
  )
192
190
  dl = described_class.new(test_url, thumbnail_options)
193
191
 
194
- mock_extractor(video_data)
192
+ mock_ytdlp(video_data)
195
193
  stub_video_download(video_url)
196
194
  stub_thumbnail_download(video_data['thumbnail'])
197
195
 
@@ -208,7 +206,7 @@ RSpec.describe YoutubeRb::Downloader do
208
206
  )
209
207
  dl = described_class.new(test_url, desc_options)
210
208
 
211
- mock_extractor(video_data)
209
+ mock_ytdlp(video_data)
212
210
  stub_video_download(video_url)
213
211
 
214
212
  dl.download
@@ -236,7 +234,7 @@ RSpec.describe YoutubeRb::Downloader do
236
234
  )
237
235
  dl = described_class.new(test_url, audio_options)
238
236
 
239
- mock_extractor(video_data)
237
+ mock_ytdlp(video_data)
240
238
  stub_video_download(video_url)
241
239
 
242
240
  output_file = dl.download
@@ -244,81 +242,16 @@ RSpec.describe YoutubeRb::Downloader do
244
242
  expect(output_file).to be_a(String)
245
243
  end
246
244
 
247
- it 'raises error if ffmpeg not available' do
248
- audio_options = YoutubeRb::Options.new(
249
- output_path: @test_output_dir,
250
- extract_audio: true
251
- )
252
- dl = described_class.new(test_url, audio_options)
253
-
254
- mock_extractor(video_data)
255
- stub_video_download(video_url)
256
-
257
- allow_any_instance_of(described_class).to receive(:ffmpeg_available?).and_return(false)
258
-
259
- expect {
260
- dl.download
261
- }.to raise_error(YoutubeRb::Downloader::DownloadError, /FFmpeg is required/)
262
- end
263
245
  end
264
246
 
265
247
  context 'error handling' do
266
- it 'raises error when no formats available' do
267
- no_format_data = video_data.dup
268
- no_format_data['formats'] = []
269
-
270
- mock_extractor(no_format_data)
271
-
272
- dl = described_class.new(
273
- 'https://www.youtube.com/watch?v=noformat',
274
- options
275
- )
276
-
277
- expect {
278
- dl.download
279
- }.to raise_error(YoutubeRb::Downloader::DownloadError, /No suitable format/)
280
- end
281
-
282
- it 'raises error when format has no URL' do
283
- bad_format_data = video_data.dup
284
- bad_format_data['formats'] = [{ 'format_id' => '18', 'ext' => 'mp4' }]
285
-
286
- mock_extractor(bad_format_data)
287
-
288
- dl = described_class.new(
289
- 'https://www.youtube.com/watch?v=badformat',
290
- options
291
- )
292
-
293
- expect {
294
- dl.download
295
- }.to raise_error(YoutubeRb::Downloader::DownloadError, /No URL found/)
296
- end
297
-
298
- it 'handles HTTP download errors' do
299
- # Disable yt-dlp fallback for this test
300
- dl = YoutubeRb::Downloader.new(
301
- test_url,
302
- output_path: @test_output_dir,
303
- use_ytdlp: false,
304
- ytdlp_fallback: false
305
- )
306
-
307
- mock_extractor(video_data)
308
- stub_request(:get, video_url).to_return(status: 403, body: 'Forbidden')
309
-
310
- expect {
311
- dl.download
312
- }.to raise_error(YoutubeRb::Downloader::DownloadError, /HTTP download failed/)
313
- end
314
-
315
- it 'handles network errors' do
316
- mock_extractor(video_data)
317
- stub_request(:get, video_url).to_raise(Faraday::ConnectionFailed)
248
+ it 'handles download errors' do
249
+ allow_any_instance_of(YoutubeRb::YtdlpWrapper).to receive(:download)
250
+ .and_raise(YoutubeRb::YtdlpWrapper::YtdlpError, 'Download failed')
318
251
 
319
252
  expect {
320
253
  downloader.download
321
- }.to raise_error(YoutubeRb::Downloader::DownloadError, /Network error/)
254
+ }.to raise_error(YoutubeRb::Downloader::DownloadError, /Download failed/)
322
255
  end
323
256
  end
324
257
  end
@@ -556,7 +489,7 @@ RSpec.describe YoutubeRb::Downloader do
556
489
  )
557
490
  dl = described_class.new(test_url, cache_options)
558
491
 
559
- mock_extractor(video_data)
492
+ mock_ytdlp(video_data)
560
493
 
561
494
  # Mock yt-dlp download
562
495
  allow_any_instance_of(YoutubeRb::YtdlpWrapper).to receive(:download) do |_, url, output_path|
@@ -583,7 +516,7 @@ RSpec.describe YoutubeRb::Downloader do
583
516
  )
584
517
  dl = described_class.new(test_url, no_cache_options)
585
518
 
586
- mock_extractor(video_data)
519
+ mock_ytdlp(video_data)
587
520
 
588
521
  # Mock yt-dlp download
589
522
  allow_any_instance_of(YoutubeRb::YtdlpWrapper).to receive(:download) do |_, url, output_path|
@@ -633,7 +566,7 @@ RSpec.describe YoutubeRb::Downloader do
633
566
  )
634
567
  dl = described_class.new(test_url, subtitle_options)
635
568
 
636
- mock_extractor(video_data)
569
+ mock_ytdlp(video_data)
637
570
  video_data['subtitles'].each do |lang, subs|
638
571
  subs.each { |sub| stub_subtitle_download(sub['url']) }
639
572
  end
@@ -649,7 +582,7 @@ RSpec.describe YoutubeRb::Downloader do
649
582
  subtitle_options = YoutubeRb::Options.new(output_path: custom_dir)
650
583
  dl = described_class.new(test_url, subtitle_options)
651
584
 
652
- mock_extractor(video_data)
585
+ mock_ytdlp(video_data)
653
586
  video_data['subtitles'].each do |lang, subs|
654
587
  subs.each { |sub| stub_subtitle_download(sub['url']) }
655
588
  end
@@ -701,32 +634,6 @@ RSpec.describe YoutubeRb::Downloader do
701
634
  end
702
635
  end
703
636
 
704
- describe '#audio_codec_for_format' do
705
- it 'returns correct codec for mp3' do
706
- dl = downloader
707
- expect(dl.send(:audio_codec_for_format, 'mp3')).to eq('libmp3lame')
708
- end
709
-
710
- it 'returns correct codec for aac' do
711
- dl = downloader
712
- expect(dl.send(:audio_codec_for_format, 'aac')).to eq('aac')
713
- end
714
-
715
- it 'returns correct codec for opus' do
716
- dl = downloader
717
- expect(dl.send(:audio_codec_for_format, 'opus')).to eq('libopus')
718
- end
719
-
720
- it 'returns correct codec for flac' do
721
- dl = downloader
722
- expect(dl.send(:audio_codec_for_format, 'flac')).to eq('flac')
723
- end
724
-
725
- it 'returns copy for unknown format' do
726
- dl = downloader
727
- expect(dl.send(:audio_codec_for_format, 'unknown')).to eq('copy')
728
- end
729
- end
730
637
 
731
638
  describe '#valid_segment_duration?' do
732
639
  it 'returns true for 10 seconds (default minimum)' do
@@ -762,7 +669,7 @@ RSpec.describe YoutubeRb::Downloader do
762
669
  )
763
670
  dl = described_class.new(test_url, custom_options)
764
671
 
765
- mock_extractor(video_data)
672
+ mock_ytdlp(video_data)
766
673
 
767
674
  expect(dl.send(:valid_segment_duration?, 5)).to be true # custom minimum
768
675
  expect(dl.send(:valid_segment_duration?, 120)).to be true # custom maximum