refactored formatters to use format_transcript and format_transcripts
This commit is contained in:
		
							parent
							
								
									d314139329
								
							
						
					
					
						commit
						cf585e2175
					
				|  | @ -166,6 +166,7 @@ The `formatters` submodule provides a few basic formatters to wrap around you tr | |||
| We provided a few subclasses of formatters to use: | ||||
| 
 | ||||
| - JSONFormatter | ||||
| - PrettyPrintFormatter | ||||
| - TextFormatter | ||||
| - WebVTTFormatter (a basic implementation) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,11 +1,9 @@ | |||
| import json | ||||
| 
 | ||||
| import pprint | ||||
| 
 | ||||
| import argparse | ||||
| 
 | ||||
| from ._api import YouTubeTranscriptApi | ||||
| 
 | ||||
| from .formatters import FormatterLoader | ||||
| 
 | ||||
| 
 | ||||
| class YouTubeTranscriptCli(object): | ||||
|     def __init__(self, args): | ||||
|  | @ -34,7 +32,7 @@ class YouTubeTranscriptCli(object): | |||
| 
 | ||||
|         return '\n\n'.join( | ||||
|             [str(exception) for exception in exceptions] | ||||
|             + ([json.dumps(transcripts) if parsed_args.json else pprint.pformat(transcripts)] if transcripts else []) | ||||
|             + ([FormatterLoader().load(parsed_args.format).format_transcripts(transcripts)] if transcripts else []) | ||||
|         ) | ||||
| 
 | ||||
|     def _fetch_transcript(self, parsed_args, proxies, cookies, video_id): | ||||
|  | @ -98,11 +96,10 @@ class YouTubeTranscriptCli(object): | |||
|             help='If this flag is set transcripts which have been manually created will not be retrieved.', | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--json', | ||||
|             action='store_const', | ||||
|             const=True, | ||||
|             default=False, | ||||
|             help='If this flag is set the output will be JSON formatted.', | ||||
|             '--format', | ||||
|             type=str, | ||||
|             default='pretty', | ||||
|             choices=tuple(FormatterLoader.TYPES.keys()), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--translate', | ||||
|  |  | |||
|  | @ -9,49 +9,75 @@ class Formatter(object): | |||
|     Formatter classes should inherit from this class and implement | ||||
|     their own .format() method which should return a string. A  | ||||
|     transcript is represented by a List of Dictionary items. | ||||
| 
 | ||||
|     :param transcript: list representing 1 or more transcripts | ||||
|     :type transcript: list | ||||
|     """ | ||||
|     def __init__(self, transcript): | ||||
|         if not isinstance(transcript, list): | ||||
|             raise TypeError("'transcript' must be of type: List") | ||||
| 
 | ||||
|         self._transcript = transcript | ||||
|      | ||||
|     def format(self, **kwargs): | ||||
|     def format_transcript(self, transcript, **kwargs): | ||||
|         raise NotImplementedError('A subclass of Formatter must implement ' \ | ||||
|             'their own .format() method.') | ||||
|             'their own .format_transcript() method.') | ||||
| 
 | ||||
|     def format_transcripts(self, transcripts, **kwargs): | ||||
|         raise NotImplementedError('A subclass of Formatter must implement ' \ | ||||
|                                   'their own .format_transcripts() method.') | ||||
| 
 | ||||
| 
 | ||||
| class PrettyPrintFormatter(Formatter): | ||||
|     def format(self, **kwargs): | ||||
|     def format_transcript(self, transcript, **kwargs): | ||||
|         """Pretty prints a transcript. | ||||
| 
 | ||||
|         :return: A pretty printed string representation of the transcript dict.' | ||||
|         :param transcript: | ||||
|         :return: A pretty printed string representation of the transcript.' | ||||
|         :rtype str | ||||
|         """ | ||||
|         return pprint.pformat(self._transcript, **kwargs) | ||||
|         return pprint.pformat(transcript, **kwargs) | ||||
| 
 | ||||
|     def format_transcripts(self, transcripts, **kwargs): | ||||
|         """Pretty prints a list of transcripts. | ||||
| 
 | ||||
|         :param transcripts: | ||||
|         :return: A pretty printed string representation of the transcripts.' | ||||
|         :rtype str | ||||
|         """ | ||||
|         return self.format_transcript(transcripts, **kwargs) | ||||
| 
 | ||||
| 
 | ||||
| class JSONFormatter(Formatter): | ||||
|     def format(self, **kwargs): | ||||
|     def format_transcript(self, transcript, **kwargs): | ||||
|         """Converts a transcript into a JSON string. | ||||
| 
 | ||||
|         :param transcript: | ||||
|         :return: A JSON string representation of the transcript.' | ||||
|         :rtype str | ||||
|         """ | ||||
|         return json.dumps(self._transcript, **kwargs) | ||||
|         return json.dumps(transcript, **kwargs) | ||||
| 
 | ||||
|     def format_transcripts(self, transcripts, **kwargs): | ||||
|         """Converts a list of transcripts into a JSON string. | ||||
| 
 | ||||
|         :param transcripts: | ||||
|         :return: A JSON string representation of the transcript.' | ||||
|         :rtype str | ||||
|         """ | ||||
|         return self.format_transcript(transcripts, **kwargs) | ||||
| 
 | ||||
| 
 | ||||
| class TextFormatter(Formatter): | ||||
|     def format(self, **kwargs): | ||||
|     def format_transcript(self, transcript, **kwargs): | ||||
|         """Converts a transcript into plain text with no timestamps. | ||||
| 
 | ||||
|         :param transcript: | ||||
|         :return: all transcript text lines separated by newline breaks.' | ||||
|         :rtype str | ||||
|         """ | ||||
|         return "\n".join(line['text'] for line in self._transcript) | ||||
|         return '\n'.join(line['text'] for line in transcript) | ||||
| 
 | ||||
|     def format_transcripts(self, transcripts, **kwargs): | ||||
|         """Converts a list of transcripts into plain text with no timestamps. | ||||
| 
 | ||||
|         :param transcripts: | ||||
|         :return: all transcript text lines separated by newline breaks.' | ||||
|         :rtype str | ||||
|         """ | ||||
|         return '\n\n\n'.join([self.format_transcript(transcript, **kwargs) for transcript in transcripts]) | ||||
| 
 | ||||
| 
 | ||||
| class WebVTTFormatter(Formatter): | ||||
|  | @ -77,19 +103,20 @@ class WebVTTFormatter(Formatter): | |||
|         ms = int(round((time - int(time))*1000, 2)) | ||||
|         return "{:02d}:{:02d}:{:02d}.{:03d}".format(hours, mins, secs, ms) | ||||
|      | ||||
|     def format(self, **kwargs): | ||||
|     def format_transcript(self, transcript, **kwargs): | ||||
|         """A basic implementation of WEBVTT formatting. | ||||
| 
 | ||||
|         :param transcript: | ||||
|         :reference: https://www.w3.org/TR/webvtt1/#introduction-caption | ||||
|         """ | ||||
|         lines = [] | ||||
|         for i, line in enumerate(self._transcript): | ||||
|             if i < len(self._transcript) - 1: | ||||
|         for i, line in enumerate(transcript): | ||||
|             if i < len(transcript) - 1: | ||||
|                 # Looks ahead, use next start time since duration value | ||||
|                 # would create an overlap between start times. | ||||
|                 time_text = "{} --> {}".format( | ||||
|                     self._seconds_to_timestamp(line['start']), | ||||
|                     self._seconds_to_timestamp(self._transcript[i + 1]['start']) | ||||
|                     self._seconds_to_timestamp(transcript[i + 1]['start']) | ||||
|                 ) | ||||
|             else: | ||||
|                 # Reached the end, cannot look ahead, use duration now. | ||||
|  | @ -102,6 +129,14 @@ class WebVTTFormatter(Formatter): | |||
|          | ||||
|         return "WEBVTT\n\n" + "\n\n".join(lines) + "\n" | ||||
| 
 | ||||
|     def format_transcripts(self, transcripts, **kwargs): | ||||
|         """A basic implementation of WEBVTT formatting for a list of transcripts. | ||||
| 
 | ||||
|         :param transcripts: | ||||
|         :reference: https://www.w3.org/TR/webvtt1/#introduction-caption | ||||
|         """ | ||||
|         return '\n\n\n'.join([self.format_transcript(transcript, **kwargs) for transcript in transcripts]) | ||||
| 
 | ||||
| 
 | ||||
| class FormatterLoader(object): | ||||
|     TYPES = { | ||||
|  | @ -118,10 +153,13 @@ class FormatterLoader(object): | |||
|                 f'Choose one of the following formats: {", ".join(FormatterLoader.TYPES.keys())}' | ||||
|             ) | ||||
| 
 | ||||
|     def __init__(self, formatter_type='pretty'): | ||||
|     def load(self, formatter_type='pretty'): | ||||
|         """ | ||||
|         Loads the Formatter for the given formatter type. | ||||
| 
 | ||||
|         :param formatter_type: | ||||
|         :return: Formatter object | ||||
|         """ | ||||
|         if formatter_type not in FormatterLoader.TYPES.keys(): | ||||
|             raise FormatterLoader.UnknownFormatterType(formatter_type) | ||||
|         self._formatter = FormatterLoader.TYPES[formatter_type] | ||||
| 
 | ||||
|     def load(self, transcript): | ||||
|         return self._formatter(transcript) | ||||
|         return FormatterLoader.TYPES[formatter_type]() | ||||
|  |  | |||
|  | @ -25,50 +25,52 @@ class TestYouTubeTranscriptCli(TestCase): | |||
|         YouTubeTranscriptApi.list_transcripts = MagicMock(return_value=self.transcript_list_mock) | ||||
| 
 | ||||
|     def test_argument_parsing(self): | ||||
|         parsed_args = YouTubeTranscriptCli('v1 v2 --json --languages de en'.split())._parse_args() | ||||
|         parsed_args = YouTubeTranscriptCli('v1 v2 --format 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.format, 'json') | ||||
|         self.assertEqual(parsed_args.languages, ['de', 'en']) | ||||
|         self.assertEqual(parsed_args.http_proxy, '') | ||||
|         self.assertEqual(parsed_args.https_proxy, '') | ||||
| 
 | ||||
|         parsed_args = YouTubeTranscriptCli('v1 v2 --languages de en --json'.split())._parse_args() | ||||
|         parsed_args = YouTubeTranscriptCli('v1 v2 --languages de en --format json'.split())._parse_args() | ||||
|         self.assertEqual(parsed_args.video_ids, ['v1', 'v2']) | ||||
|         self.assertEqual(parsed_args.json, True) | ||||
|         self.assertEqual(parsed_args.format, 'json') | ||||
|         self.assertEqual(parsed_args.languages, ['de', 'en']) | ||||
|         self.assertEqual(parsed_args.http_proxy, '') | ||||
|         self.assertEqual(parsed_args.https_proxy, '') | ||||
| 
 | ||||
|         parsed_args = YouTubeTranscriptCli(' --json v1 v2 --languages de en'.split())._parse_args() | ||||
|         parsed_args = YouTubeTranscriptCli(' --format 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.format, 'json') | ||||
|         self.assertEqual(parsed_args.languages, ['de', 'en']) | ||||
|         self.assertEqual(parsed_args.http_proxy, '') | ||||
|         self.assertEqual(parsed_args.https_proxy, '') | ||||
| 
 | ||||
|         parsed_args = YouTubeTranscriptCli( | ||||
|             'v1 v2 --languages de en --json --http-proxy http://user:pass@domain:port --https-proxy https://user:pass@domain:port'.split() | ||||
|             'v1 v2 --languages de en --format json ' | ||||
|             '--http-proxy http://user:pass@domain:port ' | ||||
|             '--https-proxy https://user:pass@domain:port'.split() | ||||
|         )._parse_args() | ||||
|         self.assertEqual(parsed_args.video_ids, ['v1', 'v2']) | ||||
|         self.assertEqual(parsed_args.json, True) | ||||
|         self.assertEqual(parsed_args.format, 'json') | ||||
|         self.assertEqual(parsed_args.languages, ['de', 'en']) | ||||
|         self.assertEqual(parsed_args.http_proxy, 'http://user:pass@domain:port') | ||||
|         self.assertEqual(parsed_args.https_proxy, 'https://user:pass@domain:port') | ||||
| 
 | ||||
|         parsed_args = YouTubeTranscriptCli( | ||||
|             'v1 v2 --languages de en --json --http-proxy http://user:pass@domain:port'.split() | ||||
|             'v1 v2 --languages de en --format json --http-proxy http://user:pass@domain:port'.split() | ||||
|         )._parse_args() | ||||
|         self.assertEqual(parsed_args.video_ids, ['v1', 'v2']) | ||||
|         self.assertEqual(parsed_args.json, True) | ||||
|         self.assertEqual(parsed_args.format, 'json') | ||||
|         self.assertEqual(parsed_args.languages, ['de', 'en']) | ||||
|         self.assertEqual(parsed_args.http_proxy, 'http://user:pass@domain:port') | ||||
|         self.assertEqual(parsed_args.https_proxy, '') | ||||
| 
 | ||||
|         parsed_args = YouTubeTranscriptCli( | ||||
|             'v1 v2 --languages de en --json --https-proxy https://user:pass@domain:port'.split() | ||||
|             'v1 v2 --languages de en --format json --https-proxy https://user:pass@domain:port'.split() | ||||
|         )._parse_args() | ||||
|         self.assertEqual(parsed_args.video_ids, ['v1', 'v2']) | ||||
|         self.assertEqual(parsed_args.json, True) | ||||
|         self.assertEqual(parsed_args.format, 'json') | ||||
|         self.assertEqual(parsed_args.languages, ['de', 'en']) | ||||
|         self.assertEqual(parsed_args.https_proxy, 'https://user:pass@domain:port') | ||||
|         self.assertEqual(parsed_args.http_proxy, '') | ||||
|  | @ -76,28 +78,28 @@ class TestYouTubeTranscriptCli(TestCase): | |||
|     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.format, 'pretty') | ||||
|         self.assertEqual(parsed_args.languages, ['en']) | ||||
| 
 | ||||
|     def test_argument_parsing__fail_without_video_ids(self): | ||||
|         with self.assertRaises(SystemExit): | ||||
|             YouTubeTranscriptCli('--json'.split())._parse_args() | ||||
|             YouTubeTranscriptCli('--format json'.split())._parse_args() | ||||
| 
 | ||||
|     def test_argument_parsing__json(self): | ||||
|         parsed_args = YouTubeTranscriptCli('v1 v2 --json'.split())._parse_args() | ||||
|         parsed_args = YouTubeTranscriptCli('v1 v2 --format json'.split())._parse_args() | ||||
|         self.assertEqual(parsed_args.video_ids, ['v1', 'v2']) | ||||
|         self.assertEqual(parsed_args.json, True) | ||||
|         self.assertEqual(parsed_args.format, 'json') | ||||
|         self.assertEqual(parsed_args.languages, ['en']) | ||||
| 
 | ||||
|         parsed_args = YouTubeTranscriptCli('--json v1 v2'.split())._parse_args() | ||||
|         parsed_args = YouTubeTranscriptCli('--format json v1 v2'.split())._parse_args() | ||||
|         self.assertEqual(parsed_args.video_ids, ['v1', 'v2']) | ||||
|         self.assertEqual(parsed_args.json, True) | ||||
|         self.assertEqual(parsed_args.format, 'json') | ||||
|         self.assertEqual(parsed_args.languages, ['en']) | ||||
| 
 | ||||
|     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.format, 'pretty') | ||||
|         self.assertEqual(parsed_args.languages, ['de', 'en']) | ||||
| 
 | ||||
|     def test_argument_parsing__proxies(self): | ||||
|  | @ -135,13 +137,13 @@ class TestYouTubeTranscriptCli(TestCase): | |||
|     def test_argument_parsing__translate(self): | ||||
|         parsed_args = YouTubeTranscriptCli('v1 v2 --languages de en --translate cz'.split())._parse_args() | ||||
|         self.assertEqual(parsed_args.video_ids, ['v1', 'v2']) | ||||
|         self.assertEqual(parsed_args.json, False) | ||||
|         self.assertEqual(parsed_args.format, 'pretty') | ||||
|         self.assertEqual(parsed_args.languages, ['de', 'en']) | ||||
|         self.assertEqual(parsed_args.translate, 'cz') | ||||
| 
 | ||||
|         parsed_args = YouTubeTranscriptCli('v1 v2 --translate cz --languages de en'.split())._parse_args() | ||||
|         self.assertEqual(parsed_args.video_ids, ['v1', 'v2']) | ||||
|         self.assertEqual(parsed_args.json, False) | ||||
|         self.assertEqual(parsed_args.format, 'pretty') | ||||
|         self.assertEqual(parsed_args.languages, ['de', 'en']) | ||||
|         self.assertEqual(parsed_args.translate, 'cz') | ||||
| 
 | ||||
|  | @ -188,7 +190,9 @@ class TestYouTubeTranscriptCli(TestCase): | |||
| 
 | ||||
|     def test_run__exclude_manually_created_and_generated(self): | ||||
|         self.assertEqual( | ||||
|             YouTubeTranscriptCli('v1 v2 --languages de en --exclude-manually-created --exclude-generated'.split()).run(), | ||||
|             YouTubeTranscriptCli( | ||||
|                 'v1 v2 --languages de en --exclude-manually-created --exclude-generated'.split() | ||||
|             ).run(), | ||||
|             '' | ||||
|         ) | ||||
| 
 | ||||
|  | @ -204,7 +208,7 @@ class TestYouTubeTranscriptCli(TestCase): | |||
|         YouTubeTranscriptApi.list_transcripts.assert_any_call('v2', proxies=None, cookies=None) | ||||
| 
 | ||||
|     def test_run__json_output(self): | ||||
|         output = YouTubeTranscriptCli('v1 v2 --languages de en --json'.split()).run() | ||||
|         output = YouTubeTranscriptCli('v1 v2 --languages de en --format json'.split()).run() | ||||
| 
 | ||||
|         # will fail if output is not valid json | ||||
|         json.loads(output) | ||||
|  |  | |||
|  | @ -20,23 +20,16 @@ class TestFormatters(TestCase): | |||
|             {'text': 'line between', 'start': 1.5, 'duration': 2.0}, | ||||
|             {'text': 'testing the end line', 'start': 2.5, 'duration': 3.25} | ||||
|         ] | ||||
|      | ||||
|     def test_base_formatter_valid_type(self): | ||||
|         with self.assertRaises(TypeError) as err: | ||||
|             Formatter({"test": []}) | ||||
|         expected_err = "'transcript' must be of type: List" | ||||
|         self.assertEqual(expected_err, str(err.exception)) | ||||
|      | ||||
|         self.transcripts = [self.transcript, self.transcript] | ||||
| 
 | ||||
|     def test_base_formatter_format_call(self): | ||||
|         with self.assertRaises(NotImplementedError) as err: | ||||
|             Formatter(self.transcript).format() | ||||
|          | ||||
|         expected_err = "A subclass of Formatter must implement their own " \ | ||||
|             ".format() method." | ||||
|         self.assertEqual(expected_err, str(err.exception)) | ||||
|         with self.assertRaises(NotImplementedError): | ||||
|             Formatter().format_transcript(self.transcript) | ||||
|         with self.assertRaises(NotImplementedError): | ||||
|             Formatter().format_transcripts([self.transcript]) | ||||
| 
 | ||||
|     def test_webvtt_formatter_starting(self): | ||||
|         content = WebVTTFormatter(self.transcript).format() | ||||
|         content = WebVTTFormatter().format_transcript(self.transcript) | ||||
|         lines = content.split('\n') | ||||
| 
 | ||||
|         # test starting lines | ||||
|  | @ -44,42 +37,66 @@ class TestFormatters(TestCase): | |||
|         self.assertEqual(lines[1], "") | ||||
|      | ||||
|     def test_webvtt_formatter_ending(self): | ||||
|         content = WebVTTFormatter(self.transcript).format() | ||||
|         content = WebVTTFormatter().format_transcript(self.transcript) | ||||
|         lines = content.split('\n') | ||||
| 
 | ||||
|         # test ending lines | ||||
|         self.assertEqual(lines[-2], self.transcript[-1]['text']) | ||||
|         self.assertEqual(lines[-1], "") | ||||
| 
 | ||||
|     def test_webvtt_formatter_many(self): | ||||
|         formatter = WebVTTFormatter() | ||||
|         content = formatter.format_transcripts(self.transcripts) | ||||
|         formatted_single_transcript = formatter.format_transcript(self.transcript) | ||||
| 
 | ||||
|         self.assertEqual(content, formatted_single_transcript + '\n\n\n' + formatted_single_transcript) | ||||
| 
 | ||||
|     def test_pretty_print_formatter(self): | ||||
|         content = PrettyPrintFormatter(self.transcript).format() | ||||
|         content = PrettyPrintFormatter().format_transcript(self.transcript) | ||||
| 
 | ||||
|         self.assertEqual(content, pprint.pformat(self.transcript)) | ||||
| 
 | ||||
|     def test_pretty_print_formatter_many(self): | ||||
|         content = PrettyPrintFormatter().format_transcripts(self.transcripts) | ||||
| 
 | ||||
|         self.assertEqual(content, pprint.pformat(self.transcripts)) | ||||
| 
 | ||||
|     def test_json_formatter(self): | ||||
|         content = JSONFormatter(self.transcript).format() | ||||
|         content = JSONFormatter().format_transcript(self.transcript) | ||||
| 
 | ||||
|         self.assertEqual(json.loads(content), self.transcript) | ||||
| 
 | ||||
|     def test_json_formatter_many(self): | ||||
|         content = JSONFormatter().format_transcripts(self.transcripts) | ||||
| 
 | ||||
|         self.assertEqual(json.loads(content), self.transcripts) | ||||
| 
 | ||||
|     def test_text_formatter(self): | ||||
|         content = TextFormatter(self.transcript).format() | ||||
|         content = TextFormatter().format_transcript(self.transcript) | ||||
|         lines = content.split('\n') | ||||
| 
 | ||||
|         self.assertEqual(lines[0], self.transcript[0]["text"]) | ||||
|         self.assertEqual(lines[-1], self.transcript[-1]["text"]) | ||||
| 
 | ||||
|     def test_text_formatter_many(self): | ||||
|         formatter = TextFormatter() | ||||
|         content = formatter.format_transcripts(self.transcripts) | ||||
|         formatted_single_transcript = formatter.format_transcript(self.transcript) | ||||
| 
 | ||||
|         self.assertEqual(content, formatted_single_transcript + '\n\n\n' + formatted_single_transcript) | ||||
| 
 | ||||
|     def test_formatter_loader(self): | ||||
|         loader = FormatterLoader('json') | ||||
|         formatter = loader.load(self.transcript) | ||||
|         loader = FormatterLoader() | ||||
|         formatter = loader.load('json') | ||||
| 
 | ||||
|         self.assertTrue(isinstance(formatter, JSONFormatter)) | ||||
| 
 | ||||
|     def test_formatter_loader__default_formatter(self): | ||||
|         loader = FormatterLoader() | ||||
|         formatter = loader.load(self.transcript) | ||||
|         formatter = loader.load() | ||||
| 
 | ||||
|         self.assertTrue(isinstance(formatter, PrettyPrintFormatter)) | ||||
| 
 | ||||
|     def test_formatter_loader__unknown_format(self): | ||||
|         with self.assertRaises(FormatterLoader.UnknownFormatterType): | ||||
|             FormatterLoader('png') | ||||
|             FormatterLoader().load('png') | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue