+
+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 2
+
+
+## Part 3
+
+
+## Part 4
+
\ 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