From d72130fcb5711818aaad710c908da969b6405f27 Mon Sep 17 00:00:00 2001 From: trots Date: Mon, 3 Jun 2024 21:39:15 +0300 Subject: [PATCH 1/5] Add unit tests for engine functions --- engine.py | 4 ++-- test_engines.py | 24 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/engine.py b/engine.py index 7dd5eba..9a9a31e 100644 --- a/engine.py +++ b/engine.py @@ -28,7 +28,7 @@ ) -def view_count_to_int(count_str): +def view_count_to_int(count_str: str): if not count_str: return 0 parts = count_str.split() @@ -38,7 +38,7 @@ def view_count_to_int(count_str): return int(processed_count) if processed_count.isdigit() else 0 -def subcriber_count_to_int(count_str): +def subcriber_count_to_int(count_str: str): if not count_str: return 0 parts = count_str.split() diff --git a/test_engines.py b/test_engines.py index 27701d5..a9283c8 100644 --- a/test_engines.py +++ b/test_engines.py @@ -1,9 +1,31 @@ import unittest from datetime import timedelta -from engine import (timedelta_to_str, YoutubeGrepEngine) +from engine import (timedelta_to_str, view_count_to_int, subcriber_count_to_int, YoutubeGrepEngine) class TestStringMethods(unittest.TestCase): + def test_view_count_to_int(self): + self.assertEqual(view_count_to_int("123"), 123) + self.assertEqual(view_count_to_int("629 views"), 629) + self.assertEqual(view_count_to_int("578,614 views"), 578614) + self.assertEqual(view_count_to_int("22,112,475 views"), 22112475) + + self.assertEqual(view_count_to_int(None), 0) + self.assertEqual(view_count_to_int(""), 0) + + def test_subscriber_count_to_int(self): + self.assertEqual(subcriber_count_to_int("1 subscriber"), 1) + self.assertEqual(subcriber_count_to_int("640 subscribers"), 640) + self.assertEqual(subcriber_count_to_int("11K subscribers"), 11000) + self.assertEqual(subcriber_count_to_int("11.5K subscribers"), 11500) + self.assertEqual(subcriber_count_to_int("17M subscribers"), 17000000) + self.assertEqual(subcriber_count_to_int("17.3M subscribers"), 17300000) + self.assertEqual(subcriber_count_to_int("3B subscribers"), 3000000000) + self.assertEqual(subcriber_count_to_int("3.5B subscribers"), 3500000000) + + self.assertEqual(subcriber_count_to_int(None), 0) + self.assertEqual(subcriber_count_to_int(""), 0) + def test_timedelta_to_str(self): self.assertEqual(timedelta_to_str(timedelta(seconds=0)), "00:00:00") self.assertEqual(timedelta_to_str(timedelta(seconds=671)), "00:11:11") From 15de0d50b14fa1cebdeb156c41daa77c9701fae7 Mon Sep 17 00:00:00 2001 From: trots Date: Mon, 3 Jun 2024 21:40:48 +0300 Subject: [PATCH 2/5] Add coverage script --- .gitignore | 1 + coverage.bat | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 coverage.bat diff --git a/.gitignore b/.gitignore index 8620e6b..68495c0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ translations/*.qm youtube-analyzer.build youtube-analyzer.dist Output +.coverage diff --git a/coverage.bat b/coverage.bat new file mode 100644 index 0000000..438c5d4 --- /dev/null +++ b/coverage.bat @@ -0,0 +1,3 @@ +.venv\Scripts\coverage.exe run -m unittest discover +.venv\Scripts\coverage.exe html -i +htmlcov\index.html From d8ce7c6123b827cfd270f6e37e09f28823ebb56a Mon Sep 17 00:00:00 2001 From: trots Date: Thu, 6 Jun 2024 22:04:30 +0300 Subject: [PATCH 3/5] Add unit test for YoutubeGrepEngine logic --- engine.py | 15 ++++- test_engines.py | 166 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+), 3 deletions(-) diff --git a/engine.py b/engine.py index 9a9a31e..1c0a1ad 100644 --- a/engine.py +++ b/engine.py @@ -126,7 +126,7 @@ def __init__(self, model: ResultTableModel, request_limit: int): def search(self, request_text: str): try: self.error = "" - videos_search = VideosSearch(request_text, limit = self._request_limit) + videos_search = self._create_video_searcher(request_text) result = [] has_next_page = True counter = 0 @@ -134,8 +134,8 @@ def search(self, request_text: str): result_array = videos_search.result()["result"] for video in result_array: views = view_count_to_int(video["viewCount"]["text"]) - video_info = Video.getInfo(video["id"]) - channel_info = Channel.get(video["channel"]["id"]) + video_info = self._get_video_info(video["id"]) + channel_info = self._get_channel_info(video["channel"]["id"]) channel_views = view_count_to_int(channel_info["views"]) channel_subscribers = subcriber_count_to_int(channel_info["subscribers"]["simpleText"]) preview_link = video["thumbnails"][0]["url"] if len(video["thumbnails"]) > 0 else "" @@ -204,6 +204,15 @@ def published_time_sort_cast(published_time: str): return None return float(pb_timedelta.replace(":", "")) + def _create_video_searcher(self, request_text: str): + return VideosSearch(request_text, limit=self._request_limit) + + def _get_video_info(self, video_id: str): + return Video.getInfo(video_id) + + def _get_channel_info(self, channel_id: str): + return Channel.get(channel_id) + class YoutubeApiEngine(AbstractYoutubeEngine): def __init__(self, model: ResultTableModel, request_limit: int, api_key: str): diff --git a/test_engines.py b/test_engines.py index a9283c8..931a58f 100644 --- a/test_engines.py +++ b/test_engines.py @@ -1,7 +1,120 @@ import unittest from datetime import timedelta +from model import (ResultFields, ResultTableModel) from engine import (timedelta_to_str, view_count_to_int, subcriber_count_to_int, YoutubeGrepEngine) + +class MockVideosSearch: + def __init__(self, request_limit: int): + self._request_limit = min(request_limit, 3) + self._has_next = True if request_limit == 3 else False + self._result = { + "result": [] + } + if request_limit > 0: + self._result["result"].append({ + "title": "First video", + "id": "1", + "duration": "12:34", + "publishedTime": "8 hours ago", + "link": "https://video_1", + "viewCount": { + "text": "123 views" + }, + "channel": { + "id": "1" + }, + "thumbnails": [ + { + "url": "https://thumb_1.png" + } + ] + }) + if request_limit > 1: + self._result["result"].append({ + "title": "Second video", + "id": "2", + "duration": "23:12", + "publishedTime": "3 hours ago", + "link": "https://video_2", + "viewCount": { + "text": "800 views" + }, + "channel": { + "id": "2" + }, + "thumbnails": [ + { + "url": "https://thumb_2.png" + } + ] + }) + + def next(self): + has_next = self._has_next + self._has_next = False + + if has_next: + self._result = { + "result": [ + { + "title": "Third video", + "id": "3", + "duration": "36:47", + "publishedTime": "10 hours ago", + "link": "https://video_3", + "viewCount": { + "text": "409 views" + }, + "channel": { + "id": "3" + }, + "thumbnails": [ + { + "url": "https://thumb_3.png" + } + ] + } + ] + } + return has_next + + def result(self): + return self._result + + +class MockGrepEngine(YoutubeGrepEngine): + def __init__(self, request_limit: int): + super().__init__(ResultTableModel(None), request_limit) + + def model(self): + return self._model + + def _create_video_searcher(self, _request_text): + return MockVideosSearch(self._request_limit) + + def _get_video_info(self, _video_id: str): + return { + "keywords": ["word1", "word2"] + } + + def _get_channel_info(self, _channel_id: str): + return { + "title": "First channel title", + "url": "https://fchannel", + "views": "895 views", + "joinedDate": "2020-05-18", + "subscribers": { + "simpleText": "30 subscribers" + }, + "thumbnails": [ + { + "url": "https://logo_1.png" + } + ] + } + + class TestStringMethods(unittest.TestCase): def test_view_count_to_int(self): @@ -81,5 +194,58 @@ def test_youtube_grep_publish_time_sort_cast(self): self.assertIsNone(YoutubeGrepEngine.published_time_sort_cast("0")) self.assertIsNone(YoutubeGrepEngine.published_time_sort_cast("1 part ago")) + def test_youtube_grep_engine(self): + for count in range(4): + engine = MockGrepEngine(count) + self.assertTrue(engine.search("request")) + model = engine.model() + self.assertEqual(len(model.result), count) + + if count > 0: + self.assertEqual(model.result[0][ResultFields.VideoTitle], "First video") + self.assertEqual(model.result[0][ResultFields.VideoPublishedTime], "8 hours ago") + self.assertEqual(model.result[0][ResultFields.VideoDuration], "00:12:34") + self.assertEqual(model.result[0][ResultFields.VideoViews], 123) + self.assertEqual(model.result[0][ResultFields.VideoLink], "https://video_1") + self.assertEqual(model.result[0][ResultFields.ChannelTitle], "First channel title") + self.assertEqual(model.result[0][ResultFields.ChannelLink], "https://fchannel") + self.assertEqual(model.result[0][ResultFields.ChannelSubscribers], 30) + self.assertEqual(model.result[0][ResultFields.ChannelJoinedDate], "2020-05-18") + self.assertEqual(model.result[0][ResultFields.ViewRate], "410.0%") + self.assertEqual(model.result[0][ResultFields.VideoPreviewLink], "https://thumb_1.png") + self.assertEqual(model.result[0][ResultFields.ChannelLogoLink], "https://logo_1.png") + self.assertEqual(model.result[0][ResultFields.VideoTags], ["word1", "word2"]) + self.assertEqual(model.result[0][ResultFields.VideoDurationTimedelta], timedelta(seconds=754)) + if count > 1: + self.assertEqual(model.result[1][ResultFields.VideoTitle], "Second video") + self.assertEqual(model.result[1][ResultFields.VideoPublishedTime], "3 hours ago") + self.assertEqual(model.result[1][ResultFields.VideoDuration], "00:23:12") + self.assertEqual(model.result[1][ResultFields.VideoViews], 800) + self.assertEqual(model.result[1][ResultFields.VideoLink], "https://video_2") + self.assertEqual(model.result[1][ResultFields.ChannelTitle], "First channel title") + self.assertEqual(model.result[1][ResultFields.ChannelLink], "https://fchannel") + self.assertEqual(model.result[1][ResultFields.ChannelSubscribers], 30) + self.assertEqual(model.result[1][ResultFields.ChannelJoinedDate], "2020-05-18") + self.assertEqual(model.result[1][ResultFields.ViewRate], "2666.67%") + self.assertEqual(model.result[1][ResultFields.VideoPreviewLink], "https://thumb_2.png") + self.assertEqual(model.result[1][ResultFields.ChannelLogoLink], "https://logo_1.png") + self.assertEqual(model.result[1][ResultFields.VideoTags], ["word1", "word2"]) + self.assertEqual(model.result[1][ResultFields.VideoDurationTimedelta], timedelta(seconds=1392)) + if count > 2: + self.assertEqual(model.result[2][ResultFields.VideoTitle], "Third video") + self.assertEqual(model.result[2][ResultFields.VideoPublishedTime], "10 hours ago") + self.assertEqual(model.result[2][ResultFields.VideoDuration], "00:36:47") + self.assertEqual(model.result[2][ResultFields.VideoViews], 409) + self.assertEqual(model.result[2][ResultFields.VideoLink], "https://video_3") + self.assertEqual(model.result[2][ResultFields.ChannelTitle], "First channel title") + self.assertEqual(model.result[2][ResultFields.ChannelLink], "https://fchannel") + self.assertEqual(model.result[2][ResultFields.ChannelSubscribers], 30) + self.assertEqual(model.result[2][ResultFields.ChannelJoinedDate], "2020-05-18") + self.assertEqual(model.result[2][ResultFields.ViewRate], "1363.33%") + self.assertEqual(model.result[2][ResultFields.VideoPreviewLink], "https://thumb_3.png") + self.assertEqual(model.result[2][ResultFields.ChannelLogoLink], "https://logo_1.png") + self.assertEqual(model.result[2][ResultFields.VideoTags], ["word1", "word2"]) + self.assertEqual(model.result[2][ResultFields.VideoDurationTimedelta], timedelta(seconds=2207)) + if __name__ == "__main__": unittest.main() From b495292c0b852d068ce2b9ce144ff210f6e72899 Mon Sep 17 00:00:00 2001 From: trots Date: Sat, 8 Jun 2024 18:34:42 +0300 Subject: [PATCH 4/5] Add unit test for YoutubeApiEngine logic --- engine.py | 58 ++++++++------- test_engines.py | 184 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 215 insertions(+), 27 deletions(-) diff --git a/engine.py b/engine.py index 1c0a1ad..ce2d0fb 100644 --- a/engine.py +++ b/engine.py @@ -220,20 +220,9 @@ def __init__(self, model: ResultTableModel, request_limit: int, api_key: str): self._api_key = api_key def search(self, request_text: str): - api_service_name = "youtube" - api_version = "v3" - try: - youtube = googleapiclient.discovery.build(api_service_name, api_version, developerKey = self._api_key) - - request = youtube.search().list( - part = "snippet", - maxResults = self._request_limit, - q = request_text, - type="video" - ) - - search_response = request.execute() + youtube = self._create_youtube_client() + search_response = self._search_videos(youtube, request_text) video_ids = "" channel_ids = "" @@ -246,17 +235,8 @@ def search(self, request_text: str): video_ids = video_ids[1:] # Remove first comma channel_ids = channel_ids[1:] # Remove first comma - video_request = youtube.videos().list( - part = "contentDetails,statistics,snippet", - id = video_ids - ) - video_response = video_request.execute() - - channel_request = youtube.channels().list( - part="snippet,statistics", - id=channel_ids - ) - channel_response = channel_request.execute() + video_response = self._get_video_details(youtube, video_ids) + channel_response = self._get_channel_details(youtube, channel_ids) channels = {} for channel_item in channel_response["items"]: channels[channel_item["id"]] = channel_item @@ -283,7 +263,7 @@ def search(self, request_text: str): video_preview_link = search_snippet["thumbnails"]["high"]["url"] channel_snippet = channel_item["snippet"] channel_logo_link = channel_snippet["thumbnails"]["default"]["url"] - channel_logo_link = channel_logo_link.replace("https", "http") # https not working. I don't know why (2024.03.10) + channel_logo_link = channel_logo_link.replace("https", "http") # https is not working. I don't know why (2024.03.10) video_snippet = video_item["snippet"] tags = video_snippet["tags"] if "tags" in video_snippet else None count = count + 1 @@ -298,3 +278,31 @@ def search(self, request_text: str): except Exception as e: self.error = str(e) return False + + def _create_youtube_client(self): + api_service_name = "youtube" + api_version = "v3" + return googleapiclient.discovery.build(api_service_name, api_version, developerKey=self._api_key) + + def _search_videos(self, youtube, request_text: str): + request = youtube.search().list( + part="snippet", + maxResults=self._request_limit, + q=request_text, + type="video" + ) + return request.execute() + + def _get_video_details(self, youtube, video_ids): + video_request = youtube.videos().list( + part="contentDetails,statistics,snippet", + id=video_ids + ) + return video_request.execute() + + def _get_channel_details(self, youtube, channel_ids): + channel_request = youtube.channels().list( + part="snippet,statistics", + id=channel_ids + ) + return channel_request.execute() diff --git a/test_engines.py b/test_engines.py index 931a58f..84c5914 100644 --- a/test_engines.py +++ b/test_engines.py @@ -1,7 +1,9 @@ import unittest from datetime import timedelta from model import (ResultFields, ResultTableModel) -from engine import (timedelta_to_str, view_count_to_int, subcriber_count_to_int, YoutubeGrepEngine) +from engine import ( + timedelta_to_str, view_count_to_int, subcriber_count_to_int, + YoutubeGrepEngine, YoutubeApiEngine) class MockVideosSearch: @@ -84,13 +86,16 @@ def result(self): class MockGrepEngine(YoutubeGrepEngine): - def __init__(self, request_limit: int): + def __init__(self, request_limit: int, exception=False): super().__init__(ResultTableModel(None), request_limit) + self._exception = exception def model(self): return self._model def _create_video_searcher(self, _request_text): + if self._exception: + raise "Exception" return MockVideosSearch(self._request_limit) def _get_video_info(self, _video_id: str): @@ -115,6 +120,122 @@ def _get_channel_info(self, _channel_id: str): } +class MockApiEngine(YoutubeApiEngine): + def __init__(self, empty=False, exception=False): + super().__init__(ResultTableModel(None), 1, "") + self._exception = exception + self._search_responce = { + "items": [] + } + self._video_responce = { + "items": [] + } + self._channel_responce = { + "items": [] + } + if empty: + return + self._search_responce["items"].append({ + "id": { + "videoId": "video1" + }, + "snippet": { + "title": "First video", + "publishTime": "2024-06-05T13:01:03Z", + "channelId": "channel1", + "channelTitle": "First channel", + "thumbnails": { + "high": { + "url": "https://yt3.com/high1.png" + } + } + } + }) + self._search_responce["items"].append({ + "id": { + "videoId": "video2" + }, + "snippet": { + "title": "Second video", + "publishTime": "2023-04-12T15:12:43Z", + "channelId": "channel2", + "channelTitle": "Second channel", + "thumbnails": { + "high": { + "url": "https://yt3.com/high2.png" + } + } + } + }) + self._video_responce["items"].append({ + "contentDetails": { + "duration": "PT16M40S" + }, + "statistics": { + "viewCount": "589025" + }, + "snippet": { + "tags": ["word1", "word2"] + } + }) + self._video_responce["items"].append({ + "contentDetails": { + "duration": "PT24M31S" + }, + "statistics": { + "viewCount": "1598" + }, + "snippet": { + "tags": ["word2", "word3"] + } + }) + self._channel_responce["items"].append({ + "id": "channel1", + "statistics": { + "viewCount": "76177771", + "subscriberCount": "77900" + }, + "snippet": { + "thumbnails": { + "default": { + "url": "https://yt3.com/default1.png" + } + } + } + }) + self._channel_responce["items"].append({ + "id": "channel2", + "statistics": { + "viewCount": "789025", + "subscriberCount": "734" + }, + "snippet": { + "thumbnails": { + "default": { + "url": "https://yt3.com/default2.png" + } + } + } + }) + + def model(self): + return self._model + + def _create_youtube_client(self): + return None + + def _search_videos(self, _youtube, _request_text: str): + if self._exception: + raise "Exception" + return self._search_responce + + def _get_video_details(self, _youtube, _video_ids): + return self._video_responce + + def _get_channel_details(self, _youtube, _channel_ids): + return self._channel_responce + + class TestStringMethods(unittest.TestCase): def test_view_count_to_int(self): @@ -247,5 +368,64 @@ def test_youtube_grep_engine(self): self.assertEqual(model.result[2][ResultFields.VideoTags], ["word1", "word2"]) self.assertEqual(model.result[2][ResultFields.VideoDurationTimedelta], timedelta(seconds=2207)) + def test_youtube_grep_engine_failures(self): + engine = MockGrepEngine(0) + self.assertTrue(engine.search("request")) + model = engine.model() + self.assertEqual(len(model.result), 0) + + engine = MockGrepEngine(1, exception=True) + self.assertFalse(engine.search("request")) + model = engine.model() + self.assertEqual(len(model.result), 0) + + def test_youtube_api_engine(self): + engine = MockApiEngine() + self.assertTrue(engine.search("request")) + model = engine.model() + self.assertEqual(len(model.result), 2) + + self.assertEqual(model.result[0][ResultFields.VideoTitle], "First video") + self.assertEqual(model.result[0][ResultFields.VideoPublishedTime], "2024-06-05 13:01:03") + self.assertEqual(model.result[0][ResultFields.VideoDuration], "00:16:40") + self.assertEqual(model.result[0][ResultFields.VideoViews], 589025) + self.assertEqual(model.result[0][ResultFields.VideoLink], "https://www.youtube.com/watch?v=video1") + self.assertEqual(model.result[0][ResultFields.ChannelTitle], "First channel") + self.assertEqual(model.result[0][ResultFields.ChannelLink], "https://www.youtube.com/channel/channel1") + self.assertEqual(model.result[0][ResultFields.ChannelSubscribers], 77900) + self.assertEqual(model.result[0][ResultFields.ChannelJoinedDate], "") + self.assertEqual(model.result[0][ResultFields.ViewRate], "756.13%") + self.assertEqual(model.result[0][ResultFields.VideoPreviewLink], "https://yt3.com/high1.png") + self.assertEqual(model.result[0][ResultFields.ChannelLogoLink], "http://yt3.com/default1.png") + self.assertEqual(model.result[0][ResultFields.VideoTags], ["word1", "word2"]) + self.assertEqual(model.result[0][ResultFields.VideoDurationTimedelta], timedelta(seconds=1000)) + + self.assertEqual(model.result[1][ResultFields.VideoTitle], "Second video") + self.assertEqual(model.result[1][ResultFields.VideoPublishedTime], "2023-04-12 15:12:43") + self.assertEqual(model.result[1][ResultFields.VideoDuration], "00:24:31") + self.assertEqual(model.result[1][ResultFields.VideoViews], 1598) + self.assertEqual(model.result[1][ResultFields.VideoLink], "https://www.youtube.com/watch?v=video2") + self.assertEqual(model.result[1][ResultFields.ChannelTitle], "Second channel") + self.assertEqual(model.result[1][ResultFields.ChannelLink], "https://www.youtube.com/channel/channel2") + self.assertEqual(model.result[1][ResultFields.ChannelSubscribers], 734) + self.assertEqual(model.result[1][ResultFields.ChannelJoinedDate], "") + self.assertEqual(model.result[1][ResultFields.ViewRate], "217.71%") + self.assertEqual(model.result[1][ResultFields.VideoPreviewLink], "https://yt3.com/high2.png") + self.assertEqual(model.result[1][ResultFields.ChannelLogoLink], "http://yt3.com/default2.png") + self.assertEqual(model.result[1][ResultFields.VideoTags], ["word2", "word3"]) + self.assertEqual(model.result[1][ResultFields.VideoDurationTimedelta], timedelta(seconds=1471)) + + def test_youtube_api_engine_failures(self): + engine = MockApiEngine(empty=True) + self.assertTrue(engine.search("request")) + model = engine.model() + self.assertEqual(len(model.result), 0) + + engine = MockApiEngine(exception=True) + self.assertFalse(engine.search("request")) + model = engine.model() + self.assertEqual(len(model.result), 0) + + if __name__ == "__main__": unittest.main() From b1c510903c0ea395b5c1193479af3d157f4bfd66 Mon Sep 17 00:00:00 2001 From: trots Date: Sat, 8 Jun 2024 19:43:49 +0300 Subject: [PATCH 5/5] Fix flake8 warnings in the engine code --- engine.py | 50 +++++++++++++++++++++++++------------------------ test_engines.py | 3 ++- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/engine.py b/engine.py index ce2d0fb..950fac1 100644 --- a/engine.py +++ b/engine.py @@ -15,7 +15,7 @@ QNetworkReply ) from youtubesearchpython import ( - VideosSearch, + VideosSearch, Channel, Video ) @@ -60,16 +60,16 @@ def subcriber_count_to_int(count_str: str): def timedelta_to_str(td: timedelta): - if td is None: - return "" - mm, ss = divmod(td.seconds, 60) - hh, mm = divmod(mm, 60) - s = "%02d:%02d:%02d" % (hh, mm, ss) - if td.days: - s = ("%d:" % td.days) + s - if td.microseconds: - s = s + ".%06d" % td.microseconds - return s + if td is None: + return "" + mm, ss = divmod(td.seconds, 60) + hh, mm = divmod(mm, 60) + s = "%02d:%02d:%02d" % (hh, mm, ss) + if td.days: + s = ("%d:" % td.days) + s + if td.microseconds: + s = s + ".%06d" % td.microseconds + return s class ImageDownloader(QObject): @@ -140,13 +140,14 @@ def search(self, request_text: str): channel_subscribers = subcriber_count_to_int(channel_info["subscribers"]["simpleText"]) preview_link = video["thumbnails"][0]["url"] if len(video["thumbnails"]) > 0 else "" channel_logo_link = channel_info["thumbnails"][0]["url"] if len(channel_info["thumbnails"]) > 0 else "" - video_duration_td = YoutubeGrepEngine.duration_to_timedelta(video["duration"]) - video_duration = timedelta_to_str(video_duration_td) if video_duration_td is not None else video["duration"] + video_duration_d = YoutubeGrepEngine.duration_to_timedelta(video["duration"]) + video_duration = timedelta_to_str(video_duration_d) if video_duration_d is not None else video["duration"] result.append( - make_result_row(video["title"], video["publishedTime"], video_duration, - views, video["link"], channel_info["title"], channel_info["url"], channel_subscribers, - channel_views, channel_info["joinedDate"], preview_link, channel_logo_link, video_info["keywords"], - video_duration_td)) + make_result_row( + video["title"], video["publishedTime"], video_duration, + views, video["link"], channel_info["title"], channel_info["url"], + channel_subscribers, channel_views, channel_info["joinedDate"], preview_link, channel_logo_link, + video_info["keywords"], video_duration_d)) counter = counter + 1 if counter == self._request_limit: break @@ -156,10 +157,11 @@ def search(self, request_text: str): self._model.setData(result) self._model.set_sort_cast(ResultFields.VideoPublishedTime, YoutubeGrepEngine.published_time_sort_cast) return True - except Exception as _: - self.error = traceback.format_exc() + except Exception as exc: + print(exc) + self.error = traceback.format_exc() return False - + @staticmethod def duration_to_timedelta(duration: str): if not duration: @@ -232,8 +234,8 @@ def search(self, request_text: str): if channel_id in channel_ids: continue channel_ids = channel_ids + "," + channel_id - video_ids = video_ids[1:] # Remove first comma - channel_ids = channel_ids[1:] # Remove first comma + video_ids = video_ids[1:] # Remove first comma + channel_ids = channel_ids[1:] # Remove first comma video_response = self._get_video_details(youtube, video_ids) channel_response = self._get_channel_details(youtube, channel_ids) @@ -263,12 +265,12 @@ def search(self, request_text: str): video_preview_link = search_snippet["thumbnails"]["high"]["url"] channel_snippet = channel_item["snippet"] channel_logo_link = channel_snippet["thumbnails"]["default"]["url"] - channel_logo_link = channel_logo_link.replace("https", "http") # https is not working. I don't know why (2024.03.10) + channel_logo_link = channel_logo_link.replace("https", "http") # https is not working. I don't know why video_snippet = video_item["snippet"] tags = video_snippet["tags"] if "tags" in video_snippet else None count = count + 1 result.append( - make_result_row(video_title, video_published_time, video_duration, views, + make_result_row(video_title, video_published_time, video_duration, views, video_link, channel_title, channel_url, channel_subscribers, channel_views, channel_joined_date, video_preview_link, channel_logo_link, tags, video_duration_td)) diff --git a/test_engines.py b/test_engines.py index 84c5914..0930a0e 100644 --- a/test_engines.py +++ b/test_engines.py @@ -292,7 +292,8 @@ def test_youtube_grep_publish_time_converter(self): self.assertEqual(timedelta_to_str(YoutubeGrepEngine.published_time_to_timedelta("2 weeks ago")), "14:00:00:00") self.assertEqual(timedelta_to_str(YoutubeGrepEngine.published_time_to_timedelta("4 months ago")), "120:00:00:00") self.assertEqual(timedelta_to_str(YoutubeGrepEngine.published_time_to_timedelta("3 years ago")), "1080:00:00:00") - self.assertEqual(timedelta_to_str(YoutubeGrepEngine.published_time_to_timedelta("Streamed 3 years ago")), "1080:00:00:00") + self.assertEqual( + timedelta_to_str(YoutubeGrepEngine.published_time_to_timedelta("Streamed 3 years ago")), "1080:00:00:00") self.assertIsNone(YoutubeGrepEngine.published_time_to_timedelta("")) self.assertIsNone(YoutubeGrepEngine.published_time_to_timedelta(None))