commit
9fa8bb0d70
|
@ -0,0 +1,30 @@
|
||||||
|
[run]
|
||||||
|
source = youtube_transcript_api
|
||||||
|
|
||||||
|
|
||||||
|
[report]
|
||||||
|
omit =
|
||||||
|
*/__main__.py
|
||||||
|
|
||||||
|
exclude_lines =
|
||||||
|
pragma: no cover
|
||||||
|
|
||||||
|
# Don't complain about missing debug-only code:
|
||||||
|
def __unicode__
|
||||||
|
def __repr__
|
||||||
|
if self\.debug
|
||||||
|
|
||||||
|
# Don't complain if tests don't hit defensive assertion code:
|
||||||
|
raise AssertionError
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# Don't complain if non-runnable code isn't run:
|
||||||
|
if 0:
|
||||||
|
if __name__ == .__main__.:
|
||||||
|
|
||||||
|
# Don't complain about empty stubs of abstract methods
|
||||||
|
@abstractmethod
|
||||||
|
@abstractclassmethod
|
||||||
|
@abstractstaticmethod
|
||||||
|
|
||||||
|
show_missing = True
|
|
@ -6,3 +6,4 @@ dist
|
||||||
build
|
build
|
||||||
*.egg-info
|
*.egg-info
|
||||||
upload_new_version.sh
|
upload_new_version.sh
|
||||||
|
.coverage
|
|
@ -0,0 +1,18 @@
|
||||||
|
language: python
|
||||||
|
python:
|
||||||
|
- "2.7"
|
||||||
|
- "3.3"
|
||||||
|
- "3.4"
|
||||||
|
- "3.5"
|
||||||
|
- "3.6"
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- python: 3.7
|
||||||
|
dist: xenial
|
||||||
|
sudo: true
|
||||||
|
install:
|
||||||
|
- pip install -r requirements.txt
|
||||||
|
script:
|
||||||
|
- coverage run -m unittest discover
|
||||||
|
after_success:
|
||||||
|
- coveralls
|
|
@ -1,5 +1,11 @@
|
||||||
# YouTube Transcript/Subtitle API (including automatically generated subtitles)
|
# YouTube Transcript/Subtitle API (including automatically generated subtitles)
|
||||||
|
|
||||||
|
[](https://travis-ci.org/jdepoix/youtube-transcript-api)
|
||||||
|
[](https://coveralls.io/github/jdepoix/youtube-transcript-api?branch=master)
|
||||||
|
[](http://opensource.org/licenses/MIT)
|
||||||
|
[](https://pypi.org/project/youtube-transcript-api/)
|
||||||
|
[](https://pypi.org/project/youtube-transcript-api/)
|
||||||
|
|
||||||
This is an python API which allows you to get the transcripts/subtitles for a given YouTube video. It also works for automatically generated subtitles and it does not require a headless browser, like other selenium based solutions do!
|
This is an python API which allows you to get the transcripts/subtitles for a given YouTube video. It also works for automatically generated subtitles and it does not require a headless browser, like other selenium based solutions do!
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
.venv/bin/coverage run -m unittest discover && .venv/bin/coverage report
|
|
@ -1 +1,7 @@
|
||||||
requests
|
requests
|
||||||
|
|
||||||
|
# testing
|
||||||
|
mock
|
||||||
|
httpretty
|
||||||
|
coverage
|
||||||
|
coveralls
|
20
setup.py
20
setup.py
|
@ -1,3 +1,7 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
import setuptools
|
import setuptools
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +13,15 @@ def get_long_description():
|
||||||
return _get_file_content('README.md')
|
return _get_file_content('README.md')
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_suite():
|
||||||
|
test_loader = unittest.TestLoader()
|
||||||
|
test_suite = test_loader.discover(
|
||||||
|
'test', pattern='test_*.py',
|
||||||
|
top_level_dir='{dirname}/youtube_transcript_api'.format(dirname=os.path.dirname(__file__))
|
||||||
|
)
|
||||||
|
return test_suite
|
||||||
|
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
name="youtube_transcript_api",
|
name="youtube_transcript_api",
|
||||||
version="0.1.3",
|
version="0.1.3",
|
||||||
|
@ -29,6 +42,13 @@ setuptools.setup(
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'requests',
|
'requests',
|
||||||
],
|
],
|
||||||
|
tests_require=[
|
||||||
|
'mock',
|
||||||
|
'httpretty',
|
||||||
|
'coverage',
|
||||||
|
'coveralls',
|
||||||
|
],
|
||||||
|
test_suite='setup.get_test_suite',
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
'youtube_transcript_api = youtube_transcript_api.__main__:main',
|
'youtube_transcript_api = youtube_transcript_api.__main__:main',
|
||||||
|
|
|
@ -1,62 +1,14 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from pprint import pprint
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import argparse
|
from ._cli import YouTubeTranscriptCli
|
||||||
|
|
||||||
from ._api import YouTubeTranscriptApi
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args(args):
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=(
|
|
||||||
'This is an python API which allows you to get the transcripts/subtitles for a given YouTube video. '
|
|
||||||
'It also works for automatically generated subtitles and it does not require a headless browser, like '
|
|
||||||
'other selenium based solutions do!'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
parser.add_argument('video_ids', nargs='*', type=str, help='List of YouTube video IDs.')
|
|
||||||
parser.add_argument(
|
|
||||||
'--languages',
|
|
||||||
nargs='*',
|
|
||||||
default=[],
|
|
||||||
type=str,
|
|
||||||
help=(
|
|
||||||
'A list of language codes in a descending priority. For example, if this is set to "de en" it will first '
|
|
||||||
'try to fetch the german transcript (de) and then fetch the english transcipt (en) if it fails to do so. '
|
|
||||||
'As I can\'t provide a complete list of all working language codes with full certainty, you may have to '
|
|
||||||
'play around with the language codes a bit, to find the one which is working for you!'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--json',
|
|
||||||
action='store_const',
|
|
||||||
const=True,
|
|
||||||
default=False,
|
|
||||||
help='If this flag is set the output will be JSON formatted.',
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser.parse_args(args)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
|
|
||||||
parsed_args = parse_args(sys.argv[1:])
|
print(YouTubeTranscriptCli(sys.argv[1:]).run())
|
||||||
transcripts, _ = YouTubeTranscriptApi.get_transcripts(
|
|
||||||
parsed_args.video_ids,
|
|
||||||
languages=parsed_args.languages,
|
|
||||||
continue_after_error=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if parsed_args.json:
|
|
||||||
print(json.dumps(transcripts))
|
|
||||||
else:
|
|
||||||
pprint(transcripts)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if sys.version_info.major == 2:
|
# This can only be tested by using different python versions, therefore it is not covered by coverage.py
|
||||||
|
if sys.version_info.major == 2: # pragma: no cover
|
||||||
reload(sys)
|
reload(sys)
|
||||||
sys.setdefaultencoding('utf-8')
|
sys.setdefaultencoding('utf-8')
|
||||||
|
|
||||||
|
@ -36,8 +37,8 @@ class YouTubeTranscriptApi():
|
||||||
)
|
)
|
||||||
self.video_id = video_id
|
self.video_id = video_id
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def get_transcripts(video_ids, languages=None, continue_after_error=False):
|
def get_transcripts(cls, video_ids, languages=None, continue_after_error=False):
|
||||||
"""
|
"""
|
||||||
Retrieves the transcripts for a list of videos.
|
Retrieves the transcripts for a list of videos.
|
||||||
|
|
||||||
|
@ -60,7 +61,7 @@ class YouTubeTranscriptApi():
|
||||||
|
|
||||||
for video_id in video_ids:
|
for video_id in video_ids:
|
||||||
try:
|
try:
|
||||||
data[video_id] = YouTubeTranscriptApi.get_transcript(video_id, languages)
|
data[video_id] = cls.get_transcript(video_id, languages)
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
if not continue_after_error:
|
if not continue_after_error:
|
||||||
raise exception
|
raise exception
|
||||||
|
@ -69,15 +70,15 @@ class YouTubeTranscriptApi():
|
||||||
|
|
||||||
return data, unretrievable_videos
|
return data, unretrievable_videos
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def get_transcript(video_id, languages=None):
|
def get_transcript(cls, video_id, languages=None):
|
||||||
"""
|
"""
|
||||||
Retrieves the transcript for a single video.
|
Retrieves the transcript for a single video.
|
||||||
|
|
||||||
:param video_id: the youtube video id
|
:param video_id: the youtube video id
|
||||||
:type video_id: str
|
:type video_id: str
|
||||||
:param languages: A list of language codes in a descending priority. For example, if this is set to ['de', 'en']
|
:param languages: A list of language codes in a descending priority. For example, if this is set to ['de', 'en']
|
||||||
it will first try to fetch the german transcript (de) and then fetch the english transcipt (en) if it fails to
|
it will first try to fetch the german transcript (de) and then fetch the english transcript (en) if it fails to
|
||||||
do so. As I can't provide a complete list of all working language codes with full certainty, you may have to
|
do so. As I can't provide a complete list of all working language codes with full certainty, you may have to
|
||||||
play around with the language codes a bit, to find the one which is working for you!
|
play around with the language codes a bit, to find the one which is working for you!
|
||||||
:type languages: [str]
|
:type languages: [str]
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
import pprint
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from ._api import YouTubeTranscriptApi
|
||||||
|
|
||||||
|
|
||||||
|
class YouTubeTranscriptCli():
|
||||||
|
def __init__(self, args):
|
||||||
|
self._args = args
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
parsed_args = self._parse_args()
|
||||||
|
|
||||||
|
transcripts, _ = YouTubeTranscriptApi.get_transcripts(
|
||||||
|
parsed_args.video_ids,
|
||||||
|
languages=parsed_args.languages,
|
||||||
|
continue_after_error=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if parsed_args.json:
|
||||||
|
return json.dumps(transcripts)
|
||||||
|
else:
|
||||||
|
return pprint.pformat(transcripts)
|
||||||
|
|
||||||
|
def _parse_args(self):
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=(
|
||||||
|
'This is an python API which allows you to get the transcripts/subtitles for a given YouTube video. '
|
||||||
|
'It also works for automatically generated subtitles and it does not require a headless browser, like '
|
||||||
|
'other selenium based solutions do!'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
parser.add_argument('video_ids', nargs='+', type=str, help='List of YouTube video IDs.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--languages',
|
||||||
|
nargs='*',
|
||||||
|
default=[],
|
||||||
|
type=str,
|
||||||
|
help=(
|
||||||
|
'A list of language codes in a descending priority. For example, if this is set to "de en" it will '
|
||||||
|
'first try to fetch the german transcript (de) and then fetch the english transcipt (en) if it fails '
|
||||||
|
'to do so. As I can\'t provide a complete list of all working language codes with full certainty, you '
|
||||||
|
'may have to play around with the language codes a bit, to find the one which is working for you!'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--json',
|
||||||
|
action='store_const',
|
||||||
|
const=True,
|
||||||
|
default=False,
|
||||||
|
help='If this flag is set the output will be JSON formatted.',
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args(self._args)
|
|
@ -1,9 +1,11 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if sys.version_info.major == 3 and sys.version_info.minor >= 4:
|
|
||||||
|
# This can only be tested by using different python versions, therefore it is not covered by coverage.py
|
||||||
|
if sys.version_info.major == 3 and sys.version_info.minor >= 4: # pragma: no cover
|
||||||
# Python 3.4+
|
# Python 3.4+
|
||||||
from html import unescape
|
from html import unescape
|
||||||
else:
|
else: # pragma: no cover
|
||||||
if sys.version_info.major <= 2:
|
if sys.version_info.major <= 2:
|
||||||
# Python 2
|
# Python 2
|
||||||
import HTMLParser
|
import HTMLParser
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<transcript>
|
||||||
|
<text start="0" dur="1.54">Hey, this is just a test</text>
|
||||||
|
<text start="1.54" dur="4.16">this is not the original transcript</text>
|
||||||
|
<text start="5.7" dur="3.239">just something shorter, I made up for testing</text>
|
||||||
|
</transcript>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,103 @@
|
||||||
|
from unittest import TestCase
|
||||||
|
from mock import MagicMock
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import httpretty
|
||||||
|
|
||||||
|
from youtube_transcript_api._api import YouTubeTranscriptApi
|
||||||
|
|
||||||
|
|
||||||
|
def load_asset(filename):
|
||||||
|
with open('{dirname}/assets/{filename}'.format(dirname=os.path.dirname(__file__), filename=filename)) as file:
|
||||||
|
return file.read()
|
||||||
|
|
||||||
|
|
||||||
|
class TestYouTubeTranscriptApi(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
httpretty.enable()
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET,
|
||||||
|
'https://www.youtube.com/watch',
|
||||||
|
body=load_asset('youtube.html')
|
||||||
|
)
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET,
|
||||||
|
'https://www.youtube.com/api/timedtext',
|
||||||
|
body=load_asset('transcript.xml')
|
||||||
|
)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
httpretty.disable()
|
||||||
|
|
||||||
|
def test_get_transcript(self):
|
||||||
|
transcript = YouTubeTranscriptApi.get_transcript('GJLlxj_dtq8')
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
transcript,
|
||||||
|
[
|
||||||
|
{'text': 'Hey, this is just a test', 'start': 0.0, 'duration': 1.54},
|
||||||
|
{'text': 'this is not the original transcript', 'start': 1.54, 'duration': 4.16},
|
||||||
|
{'text': 'just something shorter, I made up for testing', 'start': 5.7, 'duration': 3.239}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_transcript__correct_language_is_used(self):
|
||||||
|
YouTubeTranscriptApi.get_transcript('GJLlxj_dtq8', ['de', 'en'])
|
||||||
|
query_string = httpretty.last_request().querystring
|
||||||
|
|
||||||
|
self.assertIn('lang', query_string)
|
||||||
|
self.assertEqual(len(query_string['lang']), 1)
|
||||||
|
self.assertEqual(query_string['lang'][0], 'de')
|
||||||
|
|
||||||
|
def test_get_transcript__fallback_language_is_used(self):
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET,
|
||||||
|
'https://www.youtube.com/api/timedtext',
|
||||||
|
body=''
|
||||||
|
)
|
||||||
|
|
||||||
|
YouTubeTranscriptApi.get_transcript('GJLlxj_dtq8', ['de', 'en'])
|
||||||
|
query_string = httpretty.last_request().querystring
|
||||||
|
|
||||||
|
self.assertIn('lang', query_string)
|
||||||
|
self.assertEqual(len(query_string['lang']), 1)
|
||||||
|
self.assertEqual(query_string['lang'][0], 'en')
|
||||||
|
|
||||||
|
def test_get_transcript__exception_is_raised_when_not_available(self):
|
||||||
|
httpretty.register_uri(
|
||||||
|
httpretty.GET,
|
||||||
|
'https://www.youtube.com/api/timedtext',
|
||||||
|
body=''
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.assertRaises(YouTubeTranscriptApi.CouldNotRetrieveTranscript):
|
||||||
|
YouTubeTranscriptApi.get_transcript('GJLlxj_dtq8')
|
||||||
|
|
||||||
|
def test_get_transcripts(self):
|
||||||
|
video_id_1 = 'video_id_1'
|
||||||
|
video_id_2 = 'video_id_2'
|
||||||
|
languages = ['de', 'en']
|
||||||
|
YouTubeTranscriptApi.get_transcript = MagicMock()
|
||||||
|
|
||||||
|
YouTubeTranscriptApi.get_transcripts([video_id_1, video_id_2], languages=languages)
|
||||||
|
|
||||||
|
YouTubeTranscriptApi.get_transcript.assert_any_call(video_id_1, languages)
|
||||||
|
YouTubeTranscriptApi.get_transcript.assert_any_call(video_id_2, languages)
|
||||||
|
self.assertEqual(YouTubeTranscriptApi.get_transcript.call_count, 2)
|
||||||
|
|
||||||
|
def test_get_transcripts__stop_on_error(self):
|
||||||
|
YouTubeTranscriptApi.get_transcript = MagicMock(side_effect=Exception('Error'))
|
||||||
|
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
YouTubeTranscriptApi.get_transcripts(['video_id_1', 'video_id_2'])
|
||||||
|
|
||||||
|
def test_get_transcripts__continue_on_error(self):
|
||||||
|
video_id_1 = 'video_id_1'
|
||||||
|
video_id_2 = 'video_id_2'
|
||||||
|
YouTubeTranscriptApi.get_transcript = MagicMock(side_effect=Exception('Error'))
|
||||||
|
|
||||||
|
YouTubeTranscriptApi.get_transcripts(['video_id_1', 'video_id_2'], continue_after_error=True)
|
||||||
|
|
||||||
|
YouTubeTranscriptApi.get_transcript.assert_any_call(video_id_1, None)
|
||||||
|
YouTubeTranscriptApi.get_transcript.assert_any_call(video_id_2, None)
|
|
@ -0,0 +1,68 @@
|
||||||
|
from unittest import TestCase
|
||||||
|
from mock import MagicMock
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from youtube_transcript_api._cli import YouTubeTranscriptCli, YouTubeTranscriptApi
|
||||||
|
|
||||||
|
|
||||||
|
class TestYouTubeTranscriptCli(TestCase):
|
||||||
|
def test_argument_parsing(self):
|
||||||
|
parsed_args = YouTubeTranscriptCli('v1 v2 --json --languages de en'.split())._parse_args()
|
||||||
|
self.assertEqual(parsed_args.video_ids, ['v1', 'v2'])
|
||||||
|
self.assertEqual(parsed_args.json, True)
|
||||||
|
self.assertEqual(parsed_args.languages, ['de', 'en'])
|
||||||
|
|
||||||
|
parsed_args = YouTubeTranscriptCli('v1 v2 --languages de en --json'.split())._parse_args()
|
||||||
|
self.assertEqual(parsed_args.video_ids, ['v1', 'v2'])
|
||||||
|
self.assertEqual(parsed_args.json, True)
|
||||||
|
self.assertEqual(parsed_args.languages, ['de', 'en'])
|
||||||
|
|
||||||
|
parsed_args = YouTubeTranscriptCli(' --json v1 v2 --languages de en'.split())._parse_args()
|
||||||
|
self.assertEqual(parsed_args.video_ids, ['v1', 'v2'])
|
||||||
|
self.assertEqual(parsed_args.json, True)
|
||||||
|
self.assertEqual(parsed_args.languages, ['de', 'en'])
|
||||||
|
|
||||||
|
def test_argument_parsing__only_video_ids(self):
|
||||||
|
parsed_args = YouTubeTranscriptCli('v1 v2'.split())._parse_args()
|
||||||
|
self.assertEqual(parsed_args.video_ids, ['v1', 'v2'])
|
||||||
|
self.assertEqual(parsed_args.json, False)
|
||||||
|
self.assertEqual(parsed_args.languages, [])
|
||||||
|
|
||||||
|
def test_argument_parsing__fail_without_video_ids(self):
|
||||||
|
with self.assertRaises(SystemExit):
|
||||||
|
YouTubeTranscriptCli('--json'.split())._parse_args()
|
||||||
|
|
||||||
|
def test_argument_parsing__json(self):
|
||||||
|
parsed_args = YouTubeTranscriptCli('v1 v2 --json'.split())._parse_args()
|
||||||
|
self.assertEqual(parsed_args.video_ids, ['v1', 'v2'])
|
||||||
|
self.assertEqual(parsed_args.json, True)
|
||||||
|
self.assertEqual(parsed_args.languages, [])
|
||||||
|
|
||||||
|
parsed_args = YouTubeTranscriptCli('--json v1 v2'.split())._parse_args()
|
||||||
|
self.assertEqual(parsed_args.video_ids, ['v1', 'v2'])
|
||||||
|
self.assertEqual(parsed_args.json, True)
|
||||||
|
self.assertEqual(parsed_args.languages, [])
|
||||||
|
|
||||||
|
def test_argument_parsing__languages(self):
|
||||||
|
parsed_args = YouTubeTranscriptCli('v1 v2 --languages de en'.split())._parse_args()
|
||||||
|
self.assertEqual(parsed_args.video_ids, ['v1', 'v2'])
|
||||||
|
self.assertEqual(parsed_args.json, False)
|
||||||
|
self.assertEqual(parsed_args.languages, ['de', 'en'])
|
||||||
|
|
||||||
|
def test_run(self):
|
||||||
|
YouTubeTranscriptApi.get_transcripts = MagicMock(return_value=([], []))
|
||||||
|
YouTubeTranscriptCli('v1 v2 --languages de en'.split()).run()
|
||||||
|
|
||||||
|
YouTubeTranscriptApi.get_transcripts.assert_called_once_with(
|
||||||
|
['v1', 'v2'],
|
||||||
|
languages=['de', 'en'],
|
||||||
|
continue_after_error=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_run__json_output(self):
|
||||||
|
YouTubeTranscriptApi.get_transcripts = MagicMock(return_value=([{'boolean': True}], []))
|
||||||
|
output = YouTubeTranscriptCli('v1 v2 --languages de en --json'.split()).run()
|
||||||
|
|
||||||
|
# will fail if output is not valid json
|
||||||
|
json.loads(output)
|
Loading…
Reference in New Issue