diff --git a/README.md b/README.md new file mode 100644 index 0000000..406c245 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +

+ UOL + test +
+ 2021 Bright Network Internship +
+ Google Youtube Coding Challenge +
+ Challenge Completed! +

+ +Written by [Zach CHAN](https://zachan.dev/) +(Links: [Website](https://zachan.dev/) +[LinkedIn](https://www.linkedin.com/in/zach-chan-hk/) +[Email](mailto:zach@zachan.dev)) + +Participant ID: [shc39@student.london.ac.uk](mailto:zach@zachan.dev) + +## Table of Contents + + + +- [Coding Challenge Introduction](#coding-challenge-introduction) +- [Programming Language Chosen](#programming-language-chosen) +- [Passed Tests Screenshot](#passed-tests-screenshot) + - [Part 1](#part-1) + - [Part 2](#part-2) + - [Part 3](#part-3) + - [Part 4](#part-4) + + + +
+ +# Coding Challenge Introduction +It is my honour to become part of the Bright Network Internship, July 2021. I have chosen to take part in Google’s Coding Challenge for Bright Network. And this is my repository for submission! + +# Programming Language Chosen +Python, I used [PyCharm](https://www.jetbrains.com/pycharm/) for easier coding and debugging. + +[Click to see the Python coding](./python/) + +# Passed Tests Screenshot +## Part 1 +![Part 1 Tests Screenshot](./python/test_results/Part_1_tests.png) + +## Part 2 +![Part 2 Tests Screenshot](./python/test_results/Part_2_tests.png) + +## Part 3 +![Part 3 Tests Screenshot](./python/test_results/Part_3_tests.png) + +## Part 4 +![Part 4 Tests Screenshot](./python/test_results/Part_4_tests.png) \ No newline at end of file diff --git a/README_imgs/bright_network_logo.svg b/README_imgs/bright_network_logo.svg new file mode 100644 index 0000000..09b2692 --- /dev/null +++ b/README_imgs/bright_network_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/README_imgs/google_logo.svg b/README_imgs/google_logo.svg new file mode 100644 index 0000000..c652a5a --- /dev/null +++ b/README_imgs/google_logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/python/README.md b/python/README.md index d259b4a..c26bc90 100644 --- a/python/README.md +++ b/python/README.md @@ -1,3 +1,6 @@ +# Click [**here**](../README.md) to view my Submission Report +---------------------------------------------------------------- + # Youtube Challenge - Python The Python Youtube Challenge uses Python3. The below commands use Python3 specifically, to account for users that might diff --git a/python/src/video.py b/python/src/video.py index 52da908..c498815 100644 --- a/python/src/video.py +++ b/python/src/video.py @@ -15,6 +15,13 @@ def __init__(self, video_title: str, video_id: str, video_tags: Sequence[str]): # in case the caller changes the 'video_tags' they passed to us self._tags = tuple(video_tags) + # custom property: flag + self._flag = None + + def __str__(self): + """Custom String Representation""" + return "{title} ({id}) [{tags}]".format(title=self._title, id=self._video_id, tags=" ".join(self._tags)) + @property def title(self) -> str: """Returns the title of a video.""" @@ -29,3 +36,17 @@ def video_id(self) -> str: def tags(self) -> Sequence[str]: """Returns the list of tags of a video.""" return self._tags + + # Custom property: flag + @property + def flag(self) -> str: + """Returns the flag of a video.""" + return self._flag + + # Custom flag video method + def action_flag(self, reason): + self._flag = reason + + # Custom flag removal method + def remove_flag(self): + self._flag = None diff --git a/python/src/video_player.py b/python/src/video_player.py index bb79af8..7517935 100644 --- a/python/src/video_player.py +++ b/python/src/video_player.py @@ -1,13 +1,21 @@ """A video player class.""" from .video_library import VideoLibrary - +from .video_playlist import Playlist +import random +import re class VideoPlayer: """A class used to represent a Video Player.""" def __init__(self): self._video_library = VideoLibrary() + self._video_playing = None + self._video_paused = False + self._playlists = list() + + def get_video_library(self): + return self._video_library def number_of_videos(self): num_videos = len(self._video_library.get_all_videos()) @@ -15,8 +23,12 @@ def number_of_videos(self): def show_all_videos(self): """Returns all videos.""" - - print("show_all_videos needs implementation") + print("Here's a list of all available videos:") + videos = self._video_library.get_all_videos() + for vid in sorted(videos, key=lambda v: v.title[0]): + # Part 4 FLAG_VIDEO task: add flag when showing flagged videos + print("\t{0}{1}".format(vid, " - FLAGGED (reason: {0})".format(vid.flag) if vid.flag else "")) + # print("show_all_videos needs implementation") def play_video(self, video_id): """Plays the respective video. @@ -24,32 +36,88 @@ def play_video(self, video_id): Args: video_id: The video_id to be played. """ - print("play_video needs implementation") + # print("play_video needs implementation") + # 1. Search video by id + video_result = self._video_library.get_video(video_id) + if video_result is None: + print("Cannot play video: Video does not exist") + return + # 2. Part 4 FLAG_VIDEO task: Check if the video is flagged + if video_result.flag: + print("Cannot play video: Video is currently flagged (reason: {0})".format(video_result.flag)) + return + # 3. Check and Try stop playing video + if self._video_playing is not None: + self.stop_video() + # 4. Play video + print("Playing video: {0}".format(video_result.title)) + self._video_playing = video_result + self._video_paused = False def stop_video(self): """Stops the current video.""" - - print("stop_video needs implementation") + # print("stop_video needs implementation") + # 1. Check if any video is playing + if self._video_playing is None: + print("Cannot stop video: No video is currently playing") + return + # 2. Stop playing video + print("Stopping video: {0}".format(self._video_playing.title)) + self._video_playing = None + self._video_paused = False def play_random_video(self): """Plays a random video from the video library.""" - - print("play_random_video needs implementation") + videos = self._video_library.get_all_videos() + # 1. Part 4 FLAG_VIDEO task: filter all flagged video(s) out + videos = list(filter(lambda vid: not vid.flag, videos)) + # 2. Check if any videos available + if len(videos) == 0: + print("No videos available") + return + # 3. shuffle and play first vid + random.shuffle(videos) + self.play_video(videos[0].video_id) + # print("play_random_video needs implementation") def pause_video(self): """Pauses the current video.""" - - print("pause_video needs implementation") + # 1. Check if a video is playing + if self._video_playing is None: + print("Cannot pause video: No video is currently playing") + return + # 2. Check if the video is already paused + if self._video_paused: + print("Video already paused: {0}".format(self._video_playing.title)) + return + # 3. Pause the video + print("Pausing video: {0}".format(self._video_playing.title)) + self._video_paused = True + # print("pause_video needs implementation") def continue_video(self): """Resumes playing the current video.""" - - print("continue_video needs implementation") + # 1. Check pause-ability + if not self._video_playing: + print("Cannot continue video: No video is currently playing") + return + if not self._video_paused: + print("Cannot continue video: Video is not paused") + return + # 2. Continue paused video + print("Continuing video: {0}".format(self._video_playing.title)) + self._video_paused = False + # print("continue_video needs implementation") def show_playing(self): """Displays video currently playing.""" - - print("show_playing needs implementation") + # 1. Check any video playing + if not self._video_playing: + print("No video is currently playing") + return + # 2. Show the video playing + print("Currently playing: {0}{1}".format(self._video_playing, " - PAUSED" if self._video_paused else "")) + # print("show_playing needs implementation") def create_playlist(self, playlist_name): """Creates a playlist with a given name. @@ -57,7 +125,16 @@ def create_playlist(self, playlist_name): Args: playlist_name: The playlist name. """ - print("create_playlist needs implementation") + # 1. find if there is any playlist with same name exists + exist_playlist = self.is_playlist_exist(playlist_name) + if exist_playlist: + print("Cannot create playlist: A playlist with the same name already exists") + return + # 2. Create playlist and append to the list of playlists + new_playlist = Playlist(self, playlist_name) + self._playlists.append(new_playlist) + print("Successfully created new playlist: {0}".format(new_playlist.get_name())) + # print("create_playlist needs implementation") def add_to_playlist(self, playlist_name, video_id): """Adds a video to a playlist with a given name. @@ -66,12 +143,26 @@ def add_to_playlist(self, playlist_name, video_id): playlist_name: The playlist name. video_id: The video_id to be added. """ - print("add_to_playlist needs implementation") + # 1. Check if playlist exists + exist_playlist = self.is_playlist_exist(playlist_name) + if not exist_playlist: + print("Cannot add video to {0}: Playlist does not exist".format(playlist_name)) + return + # 2. Pass the job to the playlist to add + exist_playlist.add_video(playlist_name, video_id) + # print("add_to_playlist needs implementation") def show_all_playlists(self): """Display all playlists.""" + if len(self._playlists) == 0: + print("No playlists exist yet") + return + + print("Showing all playlists:") + for playlist in sorted(self._playlists, key=lambda pl: pl.get_name()): + print("\t{0}".format(playlist.get_name())) - print("show_all_playlists needs implementation") + # print("show_all_playlists needs implementation") def show_playlist(self, playlist_name): """Display all videos in a playlist with a given name. @@ -79,7 +170,21 @@ def show_playlist(self, playlist_name): Args: playlist_name: The playlist name. """ - print("show_playlist needs implementation") + # 1. Check if playlist exists + exist_playlist = self.is_playlist_exist(playlist_name) + if not exist_playlist: + print("Cannot show playlist {0}: Playlist does not exist".format(playlist_name)) + return + # 2. Show videos + print("Showing playlist: {0}".format(playlist_name)) + exist_playlist_videos = exist_playlist.get_videos() + if len(exist_playlist_videos) == 0: + print("\tNo videos here yet") + return + for vid in exist_playlist_videos.values(): + # Part 4 FLAG_VIDEO task: add flag when showing flagged videos + print("\t{0}{1}".format(vid, " - FLAGGED (reason: {0})".format(vid.flag) if vid.flag else "")) + # print("show_playlist needs implementation") def remove_from_playlist(self, playlist_name, video_id): """Removes a video to a playlist with a given name. @@ -88,7 +193,14 @@ def remove_from_playlist(self, playlist_name, video_id): playlist_name: The playlist name. video_id: The video_id to be removed. """ - print("remove_from_playlist needs implementation") + # 1. Check if playlist exists + exist_playlist = self.is_playlist_exist(playlist_name) + if not exist_playlist: + print("Cannot remove video from {0}: Playlist does not exist".format(playlist_name)) + return + # 2. Pass the job to the playlist + exist_playlist.remove_video(playlist_name, video_id) + # print("remove_from_playlist needs implementation") def clear_playlist(self, playlist_name): """Removes all videos from a playlist with a given name. @@ -96,7 +208,14 @@ def clear_playlist(self, playlist_name): Args: playlist_name: The playlist name. """ - print("clears_playlist needs implementation") + # 1. Check if playlist exists + exist_playlist = self.is_playlist_exist(playlist_name) + if not exist_playlist: + print("Cannot clear playlist {0}: Playlist does not exist".format(playlist_name)) + return + # 2. Pass the job to the playlist + exist_playlist.clear(playlist_name) + # print("clears_playlist needs implementation") def delete_playlist(self, playlist_name): """Deletes a playlist with a given name. @@ -104,7 +223,15 @@ def delete_playlist(self, playlist_name): Args: playlist_name: The playlist name. """ - print("deletes_playlist needs implementation") + # 1. Check if playlist exists + exist_playlist = self.is_playlist_exist(playlist_name) + if not exist_playlist: + print("Cannot delete playlist {0}: Playlist does not exist".format(playlist_name)) + return + # 2. Delete playlist + self._playlists = [i for i in self._playlists if i.get_name() != playlist_name] + print("Deleted playlist: {0}".format(playlist_name)) + # print("deletes_playlist needs implementation") def search_videos(self, search_term): """Display all the videos whose titles contain the search_term. @@ -112,7 +239,9 @@ def search_videos(self, search_term): Args: search_term: The query to be used in search. """ - print("search_videos needs implementation") + # Pass to Helper Function + self.search_videos_general(search_term, lambda vid: search_term.lower() in vid.title.lower()) + # print("search_videos needs implementation") def search_videos_tag(self, video_tag): """Display all videos whose tags contains the provided tag. @@ -120,7 +249,9 @@ def search_videos_tag(self, video_tag): Args: video_tag: The video tag to be used in search. """ - print("search_videos_tag needs implementation") + # Pass to Helper Function + self.search_videos_general(video_tag, lambda vid: video_tag.lower() in ";".join(vid.tags).lower().split(";")) + # print("search_videos_tag needs implementation") def flag_video(self, video_id, flag_reason=""): """Mark a video as flagged. @@ -129,7 +260,24 @@ def flag_video(self, video_id, flag_reason=""): video_id: The video_id to be flagged. flag_reason: Reason for flagging the video. """ - print("flag_video needs implementation") + # 1. Search video by id + video_result = self._video_library.get_video(video_id) + if video_result is None: + print("Cannot flag video: Video does not exist") + return + # 2. Check if the video is flagged + if video_result.flag: + print("Cannot flag video: Video is already flagged") + return + # 3. Stop playing-video and Flag the video + # 3.1. check if the playing vid is the one to flag + if self._video_playing and self._video_playing.video_id == video_id: + self.stop_video() + if flag_reason == "": + flag_reason = "Not supplied" + video_result.action_flag(flag_reason) + print("Successfully flagged video: {0} (reason: {1})".format(video_result.title, video_result.flag)) + # print("flag_video needs implementation") def allow_video(self, video_id): """Removes a flag from a video. @@ -137,4 +285,61 @@ def allow_video(self, video_id): Args: video_id: The video_id to be allowed again. """ - print("allow_video needs implementation") + # 1. Search video by id + video_result = self._video_library.get_video(video_id) + if video_result is None: + print("Cannot remove flag from video: Video does not exist") + return + # 2. Check if the video is not flagged + if not video_result.flag: + print("Cannot remove flag from video: Video is not flagged") + return + # 3. Remove the flag + video_result.remove_flag() + print("Successfully removed flag from video: {0}".format(video_result.title)) + # print("allow_video needs implementation") + + # ===========HELPER FUNCTIONS======================================================================================= + # Custom Helper Function for Part 2 + def is_playlist_exist(self, playlist_name): + """ + Return if requested playlist exists + :param playlist_name: + :return: + """ + return next((pl for pl in self._playlists if pl.get_name().lower() == playlist_name.lower()), None); + + # Custom Helper Function for Part 3 + def search_videos_general(self, search_keyword, search_filter): + """ + General Helper Function for search videos + :param search_keyword: + :param search_filter: + :return: + """ + # 1. Search matching results + search_results = sorted( + list(filter(search_filter, self._video_library.get_all_videos())), + key=lambda vid: vid.title) + # 2. Part 4 FLAG_VIDEO task: Filter out flagged video + search_results = list(filter(lambda vid: not vid.flag, search_results)) + # 2. Return if no search results + if len(search_results) == 0: + print("No search results for {0}".format(search_keyword)) + return + # 3. Print results + print("Here are the results for {0}:".format(search_keyword)) + for i, vid in enumerate(search_results, start=1): + print("\t{0}) {1}".format(i, vid)) + # 4. Ask user to play or not + print("Would you like to play any of the above? If yes, specify the number of the video.") + print("If your answer is not a valid number, we will assume it's a no.") + # 5. Check and Use regex to get valid number + is_numeric = re.search(r'^\d+$', input()) + if not is_numeric: + return + user_input = int(is_numeric.group()) + if not (1 <= user_input <= len(search_results)): + return + # 6. Play the selected video + self.play_video(search_results[user_input - 1].video_id) \ No newline at end of file diff --git a/python/src/video_playlist.py b/python/src/video_playlist.py index 5836da4..b0b2702 100644 --- a/python/src/video_playlist.py +++ b/python/src/video_playlist.py @@ -1,5 +1,79 @@ """A video playlist class.""" +from .video_library import VideoLibrary class Playlist: """A class used to represent a Playlist.""" + def __init__(self, _player, _name): + self._player = _player + self._name = _name + self._videos = {} + + def get_name(self): + """ + Returns name of the playlist + :return: playlist_name + """ + return self._name + + def get_videos(self): + """ + Returns videos in the playlist + :return: playlist_videos + """ + return self._videos + + def add_video(self, playlist_name, video_id): + """ + Custom method to add video to the playlist + :param playlist_name: + :param video_id: + :return: + """ + # 1. Check if video exists + video_result = self._player.get_video_library().get_video(video_id) + if video_result is None: + print("Cannot add video to {0}: Video does not exist".format(playlist_name)) + return + # 2. Part 4 FLAG_VIDEO task: Check if video is flagged + if video_result.flag: + print("Cannot add video to {0}: Video is currently flagged (reason: {1})" + .format(playlist_name, video_result.flag)) + return + # 3. Check if video is already added + if video_id in self._videos: + print("Cannot add video to {0}: Video already added".format(playlist_name)) + return + # 4. Add the video + self._videos[video_id] = video_result + print("Added video to {0}: {1}".format(playlist_name, video_result.title)) + + def remove_video(self, playlist_name, video_id): + """ + Custom method to remove video from the playlist + :param playlist_name: + :param video_id: + :return: + """ + # 1. Check if video exists + exist_video = self._player.get_video_library().get_video(video_id) + if not exist_video: + print("Cannot remove video from {0}: Video does not exist".format(playlist_name)) + return + # 2. Check if video in playlist + is_video_in_playlist = video_id in self._videos + if not is_video_in_playlist: + print("Cannot remove video from {0}: Video is not in playlist".format(playlist_name)) + return + # 3. Remove the video + self._videos.pop(video_id) + print("Removed video from {0}: {1}".format(playlist_name, exist_video.title)) + + def clear(self, playlist_name): + """ + Custom method to remove video from the playlist + :param playlist_name: + :return: + """ + self._videos = {} + print("Successfully removed all videos from {0}".format(playlist_name)) \ No newline at end of file diff --git a/python/test_results/Part_1_tests.png b/python/test_results/Part_1_tests.png new file mode 100644 index 0000000..ecd9aba Binary files /dev/null and b/python/test_results/Part_1_tests.png differ diff --git a/python/test_results/Part_2_tests.png b/python/test_results/Part_2_tests.png new file mode 100644 index 0000000..13c3918 Binary files /dev/null and b/python/test_results/Part_2_tests.png differ diff --git a/python/test_results/Part_3_tests.png b/python/test_results/Part_3_tests.png new file mode 100644 index 0000000..49e476f Binary files /dev/null and b/python/test_results/Part_3_tests.png differ diff --git a/python/test_results/Part_4_tests.png b/python/test_results/Part_4_tests.png new file mode 100644 index 0000000..044b380 Binary files /dev/null and b/python/test_results/Part_4_tests.png differ