| import argparse |
| import logging |
| import os |
| from unittest import mock |
| from unittest.mock import MagicMock, patch |
|
|
| import pytest |
|
|
| from pytube import Caption, CaptionQuery, cli, StreamQuery |
| from pytube.exceptions import PytubeError |
|
|
| parse_args = cli._parse_args |
|
|
|
|
| @mock.patch("pytube.cli._parse_args") |
| def test_main_invalid_url(_parse_args): |
| parser = argparse.ArgumentParser() |
| args = parse_args(parser, ["crikey",],) |
| _parse_args.return_value = args |
| with pytest.raises(SystemExit): |
| cli.main() |
|
|
|
|
| @mock.patch("pytube.cli.display_streams") |
| @mock.patch("pytube.cli.YouTube") |
| def test_download_when_itag_not_found(youtube, display_streams): |
| |
| youtube.streams = mock.Mock() |
| youtube.streams.get_by_itag.return_value = None |
| |
| with pytest.raises(SystemExit): |
| cli.download_by_itag(youtube, 123) |
| |
| youtube.streams.get_by_itag.assert_called_with(123) |
| display_streams.assert_called_with(youtube) |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| @mock.patch("pytube.Stream") |
| def test_download_when_itag_is_found(youtube, stream): |
| stream.itag = 123 |
| stream.exists_at_path.return_value = False |
| youtube.streams = StreamQuery([stream]) |
| with patch.object( |
| youtube.streams, "get_by_itag", wraps=youtube.streams.get_by_itag |
| ) as wrapped_itag: |
| cli.download_by_itag(youtube, 123) |
| wrapped_itag.assert_called_with(123) |
| youtube.register_on_progress_callback.assert_called_with(cli.on_progress) |
| stream.download.assert_called() |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| @mock.patch("pytube.Stream") |
| def test_display_stream(youtube, stream): |
| |
| stream.itag = 123 |
| stream.__repr__ = MagicMock(return_value="") |
| youtube.streams = StreamQuery([stream]) |
| |
| cli.display_streams(youtube) |
| |
| stream.__repr__.assert_called() |
|
|
|
|
| @mock.patch("pytube.cli._print_available_captions") |
| @mock.patch("pytube.cli.YouTube") |
| def test_download_caption_with_none(youtube, print_available): |
| |
| caption = Caption( |
| {"url": "url1", "name": {"simpleText": "name1"}, "languageCode": "en", "vssId": ".en"} |
| ) |
| youtube.captions = CaptionQuery([caption]) |
| |
| cli.download_caption(youtube, None) |
| |
| print_available.assert_called_with(youtube.captions) |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| def test_download_caption_with_language_found(youtube): |
| youtube.title = "video title" |
| caption = Caption( |
| {"url": "url1", "name": {"simpleText": "name1"}, "languageCode": "en", "vssId": ".en"} |
| ) |
| caption.download = MagicMock(return_value="file_path") |
| youtube.captions = CaptionQuery([caption]) |
| cli.download_caption(youtube, "en") |
| caption.download.assert_called_with(title="video title", output_path=None) |
|
|
|
|
| @mock.patch("pytube.cli._print_available_captions") |
| @mock.patch("pytube.cli.YouTube") |
| def test_download_caption_with_lang_not_found(youtube, print_available): |
| |
| caption = Caption( |
| {"url": "url1", "name": {"simpleText": "name1"}, "languageCode": "en", "vssId": ".en"} |
| ) |
| youtube.captions = CaptionQuery([caption]) |
| |
| cli.download_caption(youtube, "blah") |
| |
| print_available.assert_called_with(youtube.captions) |
|
|
|
|
| def test_print_available_captions(capsys): |
| |
| caption1 = Caption( |
| {"url": "url1", "name": {"simpleText": "name1"}, "languageCode": "en", "vssId": ".en"} |
| ) |
| caption2 = Caption( |
| {"url": "url2", "name": {"simpleText": "name2"}, "languageCode": "fr", "vssId": ".fr"} |
| ) |
| query = CaptionQuery([caption1, caption2]) |
| |
| cli._print_available_captions(query) |
| |
| captured = capsys.readouterr() |
| assert captured.out == "Available caption codes are: en, fr\n" |
|
|
|
|
| def test_display_progress_bar(capsys): |
| cli.display_progress_bar(bytes_received=25, filesize=100, scale=0.55) |
| out, _ = capsys.readouterr() |
| assert "25.0%" in out |
|
|
|
|
| @mock.patch("pytube.Stream") |
| def test_on_progress(stream): |
| stream.filesize = 10 |
| cli.display_progress_bar = MagicMock() |
| cli.on_progress(stream, "", 7) |
| cli.display_progress_bar.assert_called_once_with(3, 10) |
|
|
|
|
| def test_parse_args_falsey(): |
| parser = argparse.ArgumentParser() |
| args = cli._parse_args(parser, ["http://youtube.com/watch?v=9bZkp7q19f0"]) |
| assert args.url == "http://youtube.com/watch?v=9bZkp7q19f0" |
| assert args.build_playback_report is False |
| assert args.itag is None |
| assert args.list is False |
| assert args.verbose is False |
|
|
|
|
| def test_parse_args_truthy(): |
| parser = argparse.ArgumentParser() |
| args = cli._parse_args( |
| parser, |
| [ |
| "http://youtube.com/watch?v=9bZkp7q19f0", |
| "--build-playback-report", |
| "-c", |
| "en", |
| "-l", |
| "--itag=10", |
| "-vvv", |
| ], |
| ) |
| assert args.url == "http://youtube.com/watch?v=9bZkp7q19f0" |
| assert args.build_playback_report is True |
| assert args.itag == 10 |
| assert args.list is True |
| assert args.verbose is True |
|
|
|
|
| @mock.patch("pytube.cli.setup_logger", return_value=None) |
| def test_main_logging_setup(setup_logger): |
| |
| parser = argparse.ArgumentParser() |
| args = parse_args(parser, ["http://fakeurl", "-v"]) |
| cli._parse_args = MagicMock(return_value=args) |
| |
| with pytest.raises(SystemExit): |
| cli.main() |
| |
| setup_logger.assert_called_with(logging.DEBUG, log_filename=None) |
|
|
|
|
| @mock.patch("pytube.cli.YouTube", return_value=None) |
| def test_main_download_by_itag(youtube): |
| parser = argparse.ArgumentParser() |
| args = parse_args( |
| parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "--itag=10"] |
| ) |
| cli._parse_args = MagicMock(return_value=args) |
| cli.download_by_itag = MagicMock() |
| cli.main() |
| youtube.assert_called() |
| cli.download_by_itag.assert_called() |
|
|
|
|
| @mock.patch("pytube.cli.YouTube", return_value=None) |
| def test_main_build_playback_report(youtube): |
| parser = argparse.ArgumentParser() |
| args = parse_args( |
| parser, |
| ["http://youtube.com/watch?v=9bZkp7q19f0", "--build-playback-report"], |
| ) |
| cli._parse_args = MagicMock(return_value=args) |
| cli.build_playback_report = MagicMock() |
| cli.main() |
| youtube.assert_called() |
| cli.build_playback_report.assert_called() |
|
|
|
|
| @mock.patch("pytube.cli.YouTube", return_value=None) |
| def test_main_display_streams(youtube): |
| parser = argparse.ArgumentParser() |
| args = parse_args(parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "-l"]) |
| cli._parse_args = MagicMock(return_value=args) |
| cli.display_streams = MagicMock() |
| cli.main() |
| youtube.assert_called() |
| cli.display_streams.assert_called() |
|
|
|
|
| @mock.patch("pytube.cli.YouTube", return_value=None) |
| def test_main_download_caption(youtube): |
| parser = argparse.ArgumentParser() |
| args = parse_args(parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "-c", "en"]) |
| cli._parse_args = MagicMock(return_value=args) |
| cli.download_caption = MagicMock() |
| cli.main() |
| youtube.assert_called() |
| cli.download_caption.assert_called() |
|
|
|
|
| @mock.patch("pytube.cli.YouTube", return_value=None) |
| @mock.patch("pytube.cli.download_by_resolution") |
| def test_download_by_resolution_flag(youtube, download_by_resolution): |
| parser = argparse.ArgumentParser() |
| args = parse_args( |
| parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "-r", "320p"] |
| ) |
| cli._parse_args = MagicMock(return_value=args) |
| cli.main() |
| youtube.assert_called() |
| download_by_resolution.assert_called() |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| @mock.patch("pytube.cli.Playlist") |
| @mock.patch("pytube.cli._perform_args_on_youtube") |
| def test_download_with_playlist(perform_args_on_youtube, playlist, youtube): |
| |
| cli.safe_filename = MagicMock(return_value="safe_title") |
| parser = argparse.ArgumentParser() |
| args = parse_args(parser, ["https://www.youtube.com/playlist?list=PLyn"]) |
| cli._parse_args = MagicMock(return_value=args) |
| videos = [youtube] |
| playlist_instance = playlist.return_value |
| playlist_instance.videos = videos |
| |
| cli.main() |
| |
| playlist.assert_called() |
| perform_args_on_youtube.assert_called_with(youtube, args) |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| @mock.patch("pytube.cli.Playlist") |
| @mock.patch("pytube.cli._perform_args_on_youtube") |
| def test_download_with_playlist_video_error( |
| perform_args_on_youtube, playlist, youtube, capsys |
| ): |
| |
| cli.safe_filename = MagicMock(return_value="safe_title") |
| parser = argparse.ArgumentParser() |
| args = parse_args(parser, ["https://www.youtube.com/playlist?list=PLyn"]) |
| cli._parse_args = MagicMock(return_value=args) |
| videos = [youtube] |
| playlist_instance = playlist.return_value |
| playlist_instance.videos = videos |
| perform_args_on_youtube.side_effect = PytubeError() |
| |
| cli.main() |
| |
| playlist.assert_called() |
| captured = capsys.readouterr() |
| assert "There was an error with video" in captured.out |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| @mock.patch("pytube.StreamQuery") |
| @mock.patch("pytube.Stream") |
| @mock.patch("pytube.cli._download") |
| def test_download_by_resolution(download, stream, stream_query, youtube): |
| |
| stream_query.get_by_resolution.return_value = stream |
| youtube.streams = stream_query |
| |
| cli.download_by_resolution( |
| youtube=youtube, resolution="320p", target="test_target" |
| ) |
| |
| download.assert_called_with(stream, target="test_target") |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| @mock.patch("pytube.StreamQuery") |
| @mock.patch("pytube.cli._download") |
| def test_download_by_resolution_not_exists(download, stream_query, youtube): |
| stream_query.get_by_resolution.return_value = None |
| youtube.streams = stream_query |
| with pytest.raises(SystemExit): |
| cli.download_by_resolution( |
| youtube=youtube, resolution="DOESNT EXIST", target="test_target" |
| ) |
| download.assert_not_called() |
|
|
|
|
| @mock.patch("pytube.Stream") |
| def test_download_stream_file_exists(stream, capsys): |
| |
| stream.exists_at_path.return_value = True |
| |
| cli._download(stream=stream) |
| |
| captured = capsys.readouterr() |
| assert "Already downloaded at" in captured.out |
| stream.download.assert_not_called() |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| @mock.patch("pytube.cli.ffmpeg_process") |
| def test_perform_args_should_ffmpeg_process(ffmpeg_process, youtube): |
| |
| parser = argparse.ArgumentParser() |
| args = parse_args( |
| parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "-f", "best"] |
| ) |
| cli._parse_args = MagicMock(return_value=args) |
| |
| cli._perform_args_on_youtube(youtube, args) |
| |
| ffmpeg_process.assert_called_with( |
| youtube=youtube, resolution="best", target=None |
| ) |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| @mock.patch("pytube.cli._ffmpeg_downloader") |
| def test_ffmpeg_process_best_should_download( |
| _ffmpeg_downloader, youtube |
| ): |
| |
| target = "/target" |
| streams = MagicMock() |
| youtube.streams = streams |
| video_stream = MagicMock() |
| streams.filter.return_value.order_by.return_value.last.return_value = ( |
| video_stream |
| ) |
| audio_stream = MagicMock() |
| streams.get_audio_only.return_value = audio_stream |
| |
| cli.ffmpeg_process(youtube, "best", target) |
| |
| _ffmpeg_downloader.assert_called_with( |
| audio_stream=audio_stream, video_stream=video_stream, target=target |
| ) |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| @mock.patch("pytube.cli._ffmpeg_downloader") |
| def test_ffmpeg_process_res_should_download( |
| _ffmpeg_downloader, youtube |
| ): |
| |
| target = "/target" |
| streams = MagicMock() |
| youtube.streams = streams |
| video_stream = MagicMock() |
| streams.filter.return_value.first.return_value = video_stream |
| audio_stream = MagicMock() |
| streams.get_audio_only.return_value = audio_stream |
| |
| cli.ffmpeg_process(youtube, "XYZp", target) |
| |
| _ffmpeg_downloader.assert_called_with( |
| audio_stream=audio_stream, video_stream=video_stream, target=target |
| ) |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| @mock.patch("pytube.cli._ffmpeg_downloader") |
| def test_ffmpeg_process_res_none_should_not_download( |
| _ffmpeg_downloader, youtube |
| ): |
| |
| target = "/target" |
| streams = MagicMock() |
| youtube.streams = streams |
| streams.filter.return_value.first.return_value = None |
| audio_stream = MagicMock() |
| streams.get_audio_only.return_value = audio_stream |
| |
| with pytest.raises(SystemExit): |
| cli.ffmpeg_process(youtube, "XYZp", target) |
| |
| _ffmpeg_downloader.assert_not_called() |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| @mock.patch("pytube.cli._ffmpeg_downloader") |
| def test_ffmpeg_process_audio_none_should_fallback_download( |
| _ffmpeg_downloader, youtube |
| ): |
| |
| target = "/target" |
| streams = MagicMock() |
| youtube.streams = streams |
| stream = MagicMock() |
| streams.filter.return_value.order_by.return_value.last.return_value = stream |
| streams.get_audio_only.return_value = None |
| |
| cli.ffmpeg_process(youtube, "best", target) |
| |
| _ffmpeg_downloader.assert_called_with( |
| audio_stream=stream, video_stream=stream, target=target |
| ) |
|
|
|
|
| @mock.patch("pytube.cli.YouTube") |
| @mock.patch("pytube.cli._ffmpeg_downloader") |
| def test_ffmpeg_process_audio_fallback_none_should_exit( |
| _ffmpeg_downloader, youtube |
| ): |
| |
| target = "/target" |
| streams = MagicMock() |
| youtube.streams = streams |
| stream = MagicMock() |
| streams.filter.return_value.order_by.return_value.last.side_effect = [ |
| stream, |
| stream, |
| None, |
| ] |
| streams.get_audio_only.return_value = None |
| |
| with pytest.raises(SystemExit): |
| cli.ffmpeg_process(youtube, "best", target) |
| |
| _ffmpeg_downloader.assert_not_called() |
|
|
|
|
| @mock.patch("pytube.cli.os.unlink", return_value=None) |
| @mock.patch("pytube.cli.subprocess.run", return_value=None) |
| @mock.patch("pytube.cli._download", return_value=None) |
| @mock.patch("pytube.cli._unique_name", return_value=None) |
| def test_ffmpeg_downloader(unique_name, download, run, unlink): |
| |
| target = "target" |
| audio_stream = MagicMock() |
| video_stream = MagicMock() |
| video_stream.id = "video_id" |
| audio_stream.subtype = "audio_subtype" |
| video_stream.subtype = "video_subtype" |
| unique_name.side_effect = ["video_name", "audio_name"] |
|
|
| |
| cli._ffmpeg_downloader( |
| audio_stream=audio_stream, video_stream=video_stream, target=target |
| ) |
| |
| download.assert_called() |
| run.assert_called_with( |
| [ |
| "ffmpeg", |
| "-i", |
| os.path.join("target", "video_name.video_subtype"), |
| "-i", |
| os.path.join("target", "audio_name.audio_subtype"), |
| "-codec", |
| "copy", |
| os.path.join("target", "safe_title.video_subtype"), |
| ] |
| ) |
| unlink.assert_called() |
|
|
|
|
| @mock.patch("pytube.cli.download_audio") |
| @mock.patch("pytube.cli.YouTube.__init__", return_value=None) |
| def test_download_audio_args(youtube, download_audio): |
| |
| parser = argparse.ArgumentParser() |
| args = parse_args( |
| parser, ["http://youtube.com/watch?v=9bZkp7q19f0", "-a", "mp4"] |
| ) |
| cli._parse_args = MagicMock(return_value=args) |
| |
| cli.main() |
| |
| youtube.assert_called() |
| download_audio.assert_called() |
|
|
|
|
| @mock.patch("pytube.cli._download") |
| @mock.patch("pytube.cli.YouTube") |
| def test_download_audio(youtube, download): |
| |
| youtube_instance = youtube.return_value |
| audio_stream = MagicMock() |
| youtube_instance.streams.filter.return_value.order_by.return_value.last.return_value = ( |
| audio_stream |
| ) |
| |
| cli.download_audio(youtube_instance, "filetype", "target") |
| |
| download.assert_called_with(audio_stream, target="target") |
|
|
|
|
| @mock.patch("pytube.cli._download") |
| @mock.patch("pytube.cli.YouTube") |
| def test_download_audio_none(youtube, download): |
| |
| youtube_instance = youtube.return_value |
| youtube_instance.streams.filter.return_value.order_by.return_value.last.return_value = ( |
| None |
| ) |
| |
| with pytest.raises(SystemExit): |
| cli.download_audio(youtube_instance, "filetype", "target") |
| |
| download.assert_not_called() |
|
|
|
|
| @mock.patch("pytube.cli.YouTube.__init__", return_value=None) |
| def test_perform_args_on_youtube(youtube): |
| parser = argparse.ArgumentParser() |
| args = parse_args(parser, ["http://youtube.com/watch?v=9bZkp7q19f0"]) |
| cli._parse_args = MagicMock(return_value=args) |
| cli._perform_args_on_youtube = MagicMock() |
| cli.main() |
| youtube.assert_called() |
| cli._perform_args_on_youtube.assert_called() |
|
|
|
|
| @mock.patch("pytube.cli.os.path.exists", return_value=False) |
| def test_unique_name(path_exists): |
| assert ( |
| cli._unique_name("base", "subtype", "video", "target") == "base_video_0" |
| ) |
|
|
|
|
| @mock.patch("pytube.cli.os.path.exists") |
| def test_unique_name_counter(path_exists): |
| path_exists.side_effect = [True, False] |
| assert ( |
| cli._unique_name("base", "subtype", "video", "target") == "base_video_1" |
| ) |
|
|