def test_detailed_radiofrance_diffusion_step_first_child(monkeypatch_apple_podcast): """Test radiofrance'step parsing: Detailed step with child precision (first child programme). """ parsed_step = FranceInter()._get_radiofrance_programme_step( api_data=API_DATA["data"]["grid"][0], dt=datetime.fromtimestamp(1602738010), child_precision=True, detailed=True) expected_step = Step( start=1602738010, end=1602738780, broadcast=Broadcast(title="Retour au bercail à 21 heures pour 20 millions de français !", summary="Le chef de l'Etat a annoncé hier soir l'instauration d'un couvre feu en Ile de France et dans 8 métropoles à partir de samedi et pour 6 semaines. Emmanuel Macron en appelle à la responsabilité de chacun pour lutter contre l'épidémie qui a fait plus de 33 000 morts.", type=BroadcastType.PROGRAMME, station=FranceInter().station_info, thumbnail_src="https://is3-ssl.mzstatic.com/image/thumb/Podcasts113/v4/fc/74/e8/fc74e883-1a69-3b9d-70f4-43e185f57db6/mza_6895079223603784045.jpg/626x0w.webp", link="https://www.franceinter.fr/emissions/journal-de-7h/journal-de-7h-15-octobre-2020", show_title="Journal de 7h", show_link="https://www.franceinter.fr/emissions/le-journal-de-7h", parent_show_title="Le 7/9", parent_show_link="https://www.franceinter.fr/emissions/le-7-9")) assert parsed_step == expected_step
def get_next_step(self, logger: Logger, dt: datetime, channel: "Channel") -> Step: if self == channel.station_at: return Step.none() return Step(start=int(dt.timestamp()), end=int(dt.timestamp()), broadcast=Broadcast.empty(self))
def test_detailed_radiofrance_diffusion_step_second_child(monkeypatch_apple_podcast): """Test radiofrance'step parsing: Detailed step with child precision (second). """ parsed_step = FranceInter()._get_radiofrance_programme_step( api_data=API_DATA["data"]["grid"][0], dt=datetime.fromtimestamp(1602738880), child_precision=True, detailed=True) expected_step = Step( start=1602738880, end=1602738960, broadcast=Broadcast(title="\"Bordel, cette américanisation !\"", summary="Le thème, et l’anathème, de \"l’américanisation\" de la France sont anciens. Autrefois on parlait de \"coca-colonisation\" des modes de vie.", type=BroadcastType.PROGRAMME, station=FranceInter().station_info, thumbnail_src="https://is3-ssl.mzstatic.com/image/thumb/Podcasts113/v4/9f/7c/81/9f7c81de-7d7d-6d54-27a2-c0e52509cb43/mza_3524174555840859685.jpg/626x0w.webp", link="https://www.franceinter.fr/emissions/les-80-de/les-80-de-15-octobre-2020", show_title="Les 80\" de...", show_link="https://www.franceinter.fr/emissions/les-80-de-nicolas-demorand", parent_show_title="Le 7/9", parent_show_link="https://www.franceinter.fr/emissions/le-7-9")) assert parsed_step == expected_step
def get_schedule(self, logger: Logger, start: datetime, end: datetime) -> List[Step]: return [ Step(start=int(start.timestamp()), end=int(end.timestamp()), broadcast=Broadcast.empty(self)) ]
def _step_from_show_data(self, show_data: dict): return Step(start=show_data.get("show_start"), end=show_data.get("show_end"), broadcast=Broadcast(title=show_data.get( "show_title", self.station_slogan), type=BroadcastType.PROGRAMME, station=self.station_info, thumbnail_src=self.station_thumbnail, summary=show_data.get("summary", "")))
def get_next_step(self, logger: Logger, dt: datetime, channel: "Channel") -> Step: if self == channel.station_at(dt): return Step.none() return Step(start=int(dt.timestamp()), end=int(dt.timestamp()), broadcast=Broadcast(title="La Playlist Pycolore", type=BroadcastType.PROGRAMME, station=self.station_info, thumbnail_src=self.station_thumbnail))
def get_schedule(self, logger: Logger, start: datetime, end: datetime) -> List[Step]: return [ Step(start=int(start.timestamp()), end=int(end.timestamp()), broadcast=Broadcast(title="La Playlist Pycolore", type=BroadcastType.PROGRAMME, station=self.station_info, thumbnail_src=self.station_thumbnail)) ]
def get_schedule(self, logger: Logger, start: datetime, end: datetime) -> List[Step]: temp_end, end = start, end steps = [] while temp_end <= end: temp_start = temp_end title, temp_end = self._get_franceinfo_slot(temp_end) steps.append( Step(start=int(temp_start.timestamp()), end=int(temp_end.timestamp()), broadcast=Broadcast( title=title, type=BroadcastType.PROGRAMME, station=self.station_info, thumbnail_src=self.station_thumbnail))) return steps
def get_step(self, logger: Logger, dt: datetime, channel) -> UpdateInfo: """Returns mapping containing info about current song. If music: {"type": BroadcastType.MUSIC, "artist": artist, "title": title} If ads: "type": BroadcastType.ADS Else: "type": BroadcastType.NONE Moreover, returns other metadata for postprocessing. end datetime object To sum up, here are the keys of returned mapping: - type: BroadcastType object - end: timestamp in sec - artist: str (optional) - title: str (optional) - thumbnail_src: url to thumbnail """ start = int(dt.timestamp()) try: fetched_data = self._fetch_song_metadata() except requests.exceptions.Timeout: return self._update_info(Step.empty_until(start, start + 90, self)) end = fetched_data["end"] if start > end: if not self._get_show_metadata(dt): return self._update_info(Step.empty(start, self)) end = 0 broadcast_data = { "thumbnail_src": self.station_thumbnail, "type": BroadcastType.PROGRAMME, "title": self.station_slogan, } else: title = fetched_data['title'] artist = fetched_data['singer'] thumbnail = fetched_data.get("cover") or self.station_thumbnail broadcast_data = { "title": f"{artist} • {title}", "type": BroadcastType.MUSIC, "thumbnail_src": thumbnail, "metadata": SongPayload(title=title, artist=artist) } broadcast_data.update(station=self.station_info, **self._get_show_metadata(dt)) return self._update_info( Step(start=start, end=end, broadcast=Broadcast(**broadcast_data)))
def _get_radiofrance_programme_step(self, api_data: dict, dt: datetime, child_precision: bool, detailed: bool): """Return radio france step starting at dt. Parameters: child_precision: bool -- if True, search current child if current broadcast contains any detailed: bool -- if True, return more info in step such as summary, external links, parent broadcast """ start = max(int(api_data["start"]), int(dt.timestamp())) metadata = { "station": self.station_info, "type": BroadcastType.PROGRAMME } children = (api_data.get("children") or []) if child_precision else [] broadcast, broadcast_end, is_child = (self._find_current_child_show( children, api_data, start) if any(children) else (api_data, int(api_data["end"]), False)) diffusion = broadcast.get("diffusion") if diffusion is None: title = broadcast["title"] show_title = "" thumbnail_src = self.station_thumbnail else: show = diffusion.get("show", {}) or {} title = diffusion.get("title") or show.get("title", "") show_title = show.get("title", "") if title != show.get("title", "") else "" podcast_link = (show.get("podcast") or {}).get("itunes") thumbnail_src = music.fetch_apple_podcast_cover( podcast_link, self.station_thumbnail) metadata.update({ "title": title, "show_title": show_title, "thumbnail_src": thumbnail_src, }) if detailed: metadata = self._get_detailed_metadata(metadata, api_data, broadcast, is_child) return Step(start=start, end=broadcast_end, broadcast=Broadcast(**metadata))
def test_basic_radiofrance_diffusion_step(monkeypatch_apple_podcast): """Test radiofrance'step parsing: Undetailed step without child precision. """ parsed_step = FranceInter()._get_radiofrance_programme_step( api_data=API_DATA["data"]["grid"][0], dt=datetime.fromtimestamp(1602738000), child_precision=False, detailed=False) expected_step = Step( start=1602738000, end=1602745200, broadcast=Broadcast(title="Gaël Perdriau - Aurélien Rousseau", type=BroadcastType.PROGRAMME, station=FranceInter().station_info, thumbnail_src="https://is3-ssl.mzstatic.com/image/thumb/Podcasts113/v4/88/31/cb/8831cb22-ff5f-03fa-7815-1c57552ea7d7/mza_5059723156060763498.jpg/626x0w.webp", show_title="Le 7/9",)) assert parsed_step == expected_step
def _get_radiofrance_track_step(self, api_data: dict, dt: datetime): start = max(int(api_data["start"]), int(dt.timestamp())) track_data = api_data["track"] artists = ", ".join( track_data.get("mainArtists") or track_data.get("performers")) cover_link, deezer_link = music.fetch_cover_and_link_on_deezer( self.station_thumbnail, artists, track_data.get("albumTitle")) return Step(start=start, end=api_data["end"], broadcast=Broadcast( title=f"{artists} • {track_data['title']}", type=BroadcastType.MUSIC, station=self.station_info, link=deezer_link, summary=self.station_slogan, thumbnail_src=cover_link, metadata=SongPayload( title=track_data["title"], artist=artists, album=track_data.get("album", ""), base64_cover_art=url_to_base64(cover_link))))
def test_radiofrance_blank_step(): """Test radiofrance step parsing: Blank step """ parsed_step = FranceInfo()._get_radiofrance_programme_step( api_data=API_DATA["data"]["grid"][3], dt=datetime.fromtimestamp(1610662290), child_precision=True, detailed=True) expected_step = Step( start=1610662290, end=1610662800, broadcast=Broadcast( title="Les nouvelles mesures sanitaires pour les écoles", type=BroadcastType.PROGRAMME, station=FranceInfo().station_info, thumbnail_src=FranceInfo.station_thumbnail)) assert parsed_step == expected_step
def process(self, step: Step, logger: Logger, dt: datetime) -> Step: """Play backup songs if advertising is detected on currently broadcasted station.""" if step.broadcast.type != BroadcastType.ADS: return step logger.debug( f"channel={self.channel.id} station={self.channel.station_at(dt).formatted_station_name} Ads detected." ) if not self.backup_songs: logger.debug( f"channel={self.channel.id} Backup songs list must be generated." ) self.backup_songs = self._parse_songs() backup_song = self.backup_songs.pop(0) # tell liquidsoap to play backup song with liquidsoap_telnet_session() as session: session.write( f"{self.channel.id}_custom_songs.push {backup_song.path}\n". encode()) broadcast = step.broadcast thumbnail, url = fetch_cover_and_link_on_deezer( self.channel.station_at(dt).station_thumbnail, backup_song.artist, backup_song.album, backup_song.title) # and update metadata return Step( start=step.start, end=step.start + int(backup_song.length), broadcast=Broadcast( title=f"{backup_song.artist} • {backup_song.title}", type=BroadcastType.MUSIC, station=broadcast.station, thumbnail_src=thumbnail, summary= (f"Publicité en cours sur {broadcast.station.name}. En attendant, voici une chanson de la " "playlist Pycolore."), show_title="La playlist Pycolore", show_link="/pycolore/playlist/", ))
def test_radiofrance_track_step(): """Test radiofrance step parsing: Track step """ parsed_step = FranceInterParis()._get_radiofrance_track_step( api_data=API_DATA["data"]["grid"][1], dt=datetime.fromtimestamp(1610662504)) expected_step = Step( start=1610662504, end=1610662753, broadcast=Broadcast( title="Ihsan Al-Munzer • Jamileh", type=BroadcastType.MUSIC, station=FranceInterParis().station_info, thumbnail_src="https://cdns-images.dzcdn.net/images/artist/a1a23844fed57b71fd1f18a9f633636e/500x500-000000-80-0-0.jpg", summary=FranceInterParis.station_slogan, metadata=SongPayload(title='Jamileh', artist='Ihsan Al-Munzer', album=''), link="https://www.deezer.com/artist/65281352")) assert parsed_step == expected_step
def test_radiofrance_track_step_without_artist(): """Test radiofrance step parsing: Track step without mainArtists field """ parsed_step = FranceInterParis()._get_radiofrance_track_step( api_data=API_DATA["data"]["grid"][2], dt=datetime.fromtimestamp(1610307944)) expected_step = Step( start=1610307944, end=1610308075, broadcast=Broadcast( title="HENRI TEXIER • ENFANT LIVRE", type=BroadcastType.MUSIC, station=FranceInterParis().station_info, thumbnail_src="https://cdns-images.dzcdn.net/images/artist/345f1b012b55d907be32e0b80864fed2/500x500-000000-80-0-0.jpg", summary=FranceInterParis.station_slogan, metadata=SongPayload(title='ENFANT LIVRE', artist='HENRI TEXIER', album=''), link="https://www.deezer.com/artist/153756")) assert parsed_step == expected_step
def get_step(self, logger: Logger, dt: datetime, channel: Channel) -> UpdateInfo: dt_timestamp = int(dt.timestamp()) if self._current_song is None: next_station_name = channel.station_after(dt).name next_station_start = int(channel.station_end_at(dt).timestamp()) return UpdateInfo(should_notify_update=True, step=Step.waiting_for_next_station( dt_timestamp, next_station_start, self, next_station_name)) artists_list = tuple(self._artists) artists_str = ", ".join(artists_list[:-1]) + " et " + artists_list[-1] thumbnail_src, link = fetch_cover_and_link_on_deezer( self.station_thumbnail, self._current_song.artist, self._current_song.album, self._current_song.title) return UpdateInfo( should_notify_update=True, step=Step( start=dt_timestamp, end=int(self._current_song_end), broadcast=Broadcast( title= f"{self._current_song.artist} • {self._current_song.title}", link=link, thumbnail_src=thumbnail_src, station=self.station_info, type=BroadcastType.MUSIC, show_link= "https://radio.pycolore.fr/pages/playlist-pycolore", show_title="La playlist Pycolore", summary= (f"Une sélection aléatoire de chansons parmi les musiques stockées sur Pycolore. À suivre : " f"{artists_str}."), metadata=SongPayload( title=self._current_song.title, artist=self._current_song.artist, album="La Playlist Pycolore", base64_cover_art=url_to_base64(thumbnail_src)))))
def test_detailed_radiofrance_diffusion_step_after_last_child(monkeypatch_apple_podcast): """Test radiofrance'step parsing: Detailed step with child precision (after last child). """ parsed_step = FranceInter()._get_radiofrance_programme_step( api_data=API_DATA["data"]["grid"][0], dt=datetime.fromtimestamp(1602745050), child_precision=True, detailed=True) expected_step = Step( start=1602745050, end=1602745200, broadcast=Broadcast(title="Gaël Perdriau - Aurélien Rousseau", summary="Gaël Perdriau, maire LR de Saint-Etienne, et Aurélien Rousseau, directeur général de l'Agence régional de santéd'Île-de-France, sont les invités du 7/9 de France Inter.", type=BroadcastType.PROGRAMME, station=FranceInter().station_info, thumbnail_src="https://is3-ssl.mzstatic.com/image/thumb/Podcasts113/v4/88/31/cb/8831cb22-ff5f-03fa-7815-1c57552ea7d7/mza_5059723156060763498.jpg/626x0w.webp", link='https://www.franceinter.fr/emissions/le-7-9/le-7-9-15-octobre-2020', show_title="Le 7/9", show_link="https://www.franceinter.fr/emissions/le-7-9")) assert parsed_step == expected_step