diff --git a/recommendation_engine.py b/recommendation_engine.py index 4a8b075..618298f 100644 --- a/recommendation_engine.py +++ b/recommendation_engine.py @@ -112,7 +112,7 @@ class Engine: for event in user.get('events', []): if event.get('event') == "grid" and event.get('data').get('index') not in grid_events: grid_events[event.get('data').get('index')] = event.get('data') - if event.get('event') == "play" and not play_index: + if event.get('event') == "play" and event["data"].get("type") == "video" and not play_index: play_index = event.get('data').get('index') if len(grid_events) == video_num and play_index: break @@ -181,8 +181,7 @@ class Engine: return [e[1] for e in rec_list] -# NOTE for future improvement: vids_exclude element unit could be clip or in/out time pairs, rather than playlist. -# The same playlist could be played in the grid view as long as these are differenct clips or separate times. +# Current assumption: Avoid the same playlist in the grid view. In the future, the same playlist could be played in the grid view as long as these are differenct clips or separate times? def get_recommendations(self, user, vids_exclude = []): channels = {k: v.get('value', 0) for k, v in self.state['channels'].items()} sliders = {k: v.get('value', 0) for k, v in self.state['globalKeywords'].items()} @@ -247,6 +246,54 @@ class Engine: } for video in videos] +# Output: playlists with updated in/out time of clips that have been watched. +# Watched is defined as a video being played in full screen. +# "watch_cutoff" parameter: the portion of the clip duration to be determined as watched the whole clip. should be [0,1] +# + check (play, pause) pairs and eliminate unusual cases most likely due to a bug. +# + If (play, pause) pairs exceed XX(80-90?) percent of the clip length, eliminate the clip from the playlist. +# + Otherwise, find the last pause position of a clip and record it as "in" position of the clip. +# + If the clips are all eliminated from a playlist, eliminate the playlist. + def update_user_playlists(playlists, user, watch_cutoff = 0.8): + play = {} + watched = [] + clip_max_dur = 10800 # = 3 hours; arbitrary max duration allowed for (pause time - play time) to detect outlier/bugs + # The current max time of a clip duration is 10379.383333377269 from "DDLaunch: Erik Verlinde, Gravity as an emergent force (1956)" + for event in user["events"][::-1]: + if event["event"] == "play" and event["data"].get("type") == "video": + play = event + elif event["event"] == "pause" and play!={} and event["data"].get("type") == "video": + if "position" not in play["data"]: + play = {} + break + if play["data"].get("playlist") == event["data"].get("playlist"): + if event["data"]["position"] - play["data"]["position"] > 0 and event["data"]["position"] - play["data"]["position"] < clip_max_dur and event["data"].get("playlistPosition") == play["data"].get("playlistPosition") and event["data"].get("playlistPosition") is not None: + i = event["data"]["playlistPosition"] + for playlist in playlists: + if playlist["name"] == event["data"]["playlist"] and i < len(playlist["clips"]): + if play["data"]["position"] >= max(playlist["clips"][i]["in"] - 30, 0) and event["data"]["position"] <= playlist["clips"][i]["out"] + 30: + # This assumes the (play, pause) fits inside the clip's (in, out) segment with +/- 30secs buffer. Check if there are instances where this might not be the case. + # i.e. clip in/out may be edited (before after edit inconsistency); skip may trigger jump to a wrong clip (bug) + if event["data"]["position"] >= ((playlist["clips"][i]["out"]-playlist["clips"][i]["in"])*watch_cutoff + playlist["clips"][i]["in"]): + watched.append((playlist["name"],i)) + else: + playlist["clips"][i]["in"] = event["data"]["position"] + break + play = {} + + d_watched = defaultdict(set) + for k, v in watched: + d_watched[k].add(v) + for k, v in d_watched.items(): + for playlist in playlists: + if playlist["name"] == k: + if len(v) == len(playlist["clips"]): + playlists.remove(playlist) + else: + playlist["clips"] = [playlist["clips"][i] for i in range(len(playlist["clips"])) if i not in v] + break + return(playlists) + + def get_next(self, user, position): grid_events = {} video_num = 16