Beispiel #1
0
 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))
Beispiel #2
0
 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))
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
 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))
     ]
Beispiel #6
0
    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)))
Beispiel #7
0
 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", "")))
Beispiel #8
0
 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))
     ]
Beispiel #9
0
 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)))))
Beispiel #10
0
 def _handle_api_exception(self, api_data, logger, start) -> Optional[Step]:
     """Return a step if an error in API response was detected. Else return None."""
     error_message: Optional[str] = None
     if "API Timeout" in api_data.values():
         error_message = "API Timeout"
     elif "API rate limit exceeded" in api_data.values():
         error_message = "Radio France API rate limit exceeded"
     elif api_data.get("data") is None:
         error_message = "No data provided by Radio France API"
     elif not api_data["data"]["grid"]:
         error_message = "Grid provided by Radio France API is empty"
     if error_message:
         logger.error(error_message)
     return Step.empty_until(start, start +
                             90, self) if error_message else None
Beispiel #11
0
 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
Beispiel #12
0
    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))
Beispiel #13
0
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
Beispiel #14
0
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
Beispiel #15
0
 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))))
Beispiel #16
0
    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/",
            ))
Beispiel #17
0
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
Beispiel #18
0
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
Beispiel #19
0
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
Beispiel #20
0
 def schedule(self, data: List[Dict]):
     return [Step(**step_data) for step_data in data]
Beispiel #21
0
def test_update_info_unpacking():
    should_notify, step = UpdateInfo(should_notify_update=True, step=Step.none())
    assert (should_notify, step) == (True, Step.none())
Beispiel #22
0
 def __init__(self):
     super().__init__()
     self._current_show_data = {"show_end": datetime.now().timestamp()}
     self.current_step = Step.none()
Beispiel #23
0
class RadioFranceStation(URLStation):
    API_RATE_LIMIT_EXCEEDED = 1
    _station_api_name: str
    _grid_template = RADIO_FRANCE_GRID_TEMPLATE

    @property
    def token(self):
        if os.getenv("TOKEN") is None:  # in case of development server
            load_dotenv()
            if os.getenv("TOKEN") is None:
                raise RuntimeError("No token for Radio France API found.")
        return os.getenv("TOKEN")

    @staticmethod
    def _notifying_update_info(step):
        return UpdateInfo(should_notify_update=True, step=step)

    def _fetch_metadata(self,
                        start: datetime,
                        end: datetime,
                        retry=0,
                        current=0,
                        raise_exc=False) -> Dict[Any, Any]:
        """Fetch metadata from radiofrance open API."""
        query = self._grid_template.format(start=int(start.timestamp()),
                                           end=int(end.timestamp()),
                                           station=self._station_api_name)
        try:
            rep = requests.post(
                url="https://openapi.radiofrance.fr/v1/graphql?x-token={}".
                format(self.token),
                json={"query": query},
                timeout=4,
            )
        except requests.exceptions.Timeout:
            if current < retry:
                return self._fetch_metadata(start, end, retry, current + 1,
                                            raise_exc)
            if raise_exc:
                raise TimeoutError("Radio France API Timeout")
            return {"message": "API Timeout"}
        data = json.loads(rep.text)
        return data

    @staticmethod
    def _find_current_child_show(children: List[Any], parent: Dict[str, Any],
                                 start: int) -> Tuple[Dict, int, bool]:
        """Return current show among children and its end timestamp.

        Sometimes, current timestamp is between 2 children. In this case,
        return parent show and next child start as end.

        Parameters:
        - children: list of dict representing radiofrance steps
        - parent: dict representing radiofrance step
        - start: datetime object representing asked timestamp

        Return a tuple containing:
        - dict representing a step
        - end timestamp
        - True if the current show is child or not
        """

        # on enlève les enfants vides (les TrackStep que l'on ne prend pas en compte)
        children = filter(bool, children)
        # on trie dans l'ordre inverse
        children = sorted(children, key=lambda x: x.get("start"), reverse=True)

        # on initialise l'enfant suivant (par défaut le dernier)
        next_child = children[0]
        # et on parcourt la liste des enfants à l'envers
        for child in children:
            # dans certains cas, le type de step ne nous intéresse pas
            # et est donc vide, on passe directement au suivant
            # (c'est le cas des TrackSteps)
            if child.get("start") is None:
                continue

            # si le début du programme est à venir, on passe au précédent
            if child["start"] > start:
                next_child = child
                continue

            # au premier programme dont le début est avant la date courante
            # on sait qu'on est potentiellement dans le programme courant.
            # Il faut vérifier que l'on est encore dedans en vérifiant :
            if child["end"] > start:
                return child, int(child["end"]), True

            # sinon, on est dans un "trou" : on utilise donc le parent
            # et le début de l'enfant suivant. Cas particulier : si on est
            # entre la fin du dernier enfant et la fin du parent (càd l'enfant
            # suivant est égal à l'enfant courant), on prend la fin du parent.
            elif next_child == child:
                return parent, int(parent["end"]), False
            else:
                return parent, int(next_child["start"]), False
        else:
            # si on est ici, c'est que la boucle a parcouru tous les enfants
            # sans valider child["start"] < now. Autrement dit, le premier
            # enfant n'a pas encore commencé. On renvoie donc le parent et le
            # début du premier enfant (stocké dans next_child) comme end
            return parent, int(next_child.get("start")) or parent["end"], False

    def _handle_api_exception(self, api_data, logger, start) -> Optional[Step]:
        """Return a step if an error in API response was detected. Else return None."""
        error_message: Optional[str] = None
        if "API Timeout" in api_data.values():
            error_message = "API Timeout"
        elif "API rate limit exceeded" in api_data.values():
            error_message = "Radio France API rate limit exceeded"
        elif api_data.get("data") is None:
            error_message = "No data provided by Radio France API"
        elif not api_data["data"]["grid"]:
            error_message = "Grid provided by Radio France API is empty"
        if error_message:
            logger.error(error_message)
        return Step.empty_until(start, start +
                                90, self) if error_message else None

    @staticmethod
    def _get_detailed_metadata(metadata: dict, parent: dict, child: dict,
                               is_child: bool) -> dict:
        """Alter (add detailed information to) a copy of metadata and return it

        :param metadata: metadata to update (this method creates a copy and alter it)
        :param parent: parent broadcast
        :param child: child broadcast (may be identical to parent)
        :param is_child: if True, add parent_show_title and parent_show_link info
        :return: updated copy of metadata input
        """
        detailed_metadata = metadata.copy()
        diffusion = child.get("diffusion") or {}
        show = diffusion.get("show") or {}
        if not is_child:
            diffusion_summary = diffusion.get("standFirst", "") or ""
            if len(diffusion_summary.strip()) == 1:
                diffusion_summary = ""
            detailed_metadata.update({
                "show_link": show.get("url", ""),
                "link": diffusion.get("url", ""),
                "summary": diffusion_summary.strip(),
            })
            return detailed_metadata
        parent_diffusion = parent.get("diffusion") or {}
        parent_show = parent_diffusion.get("show") or {}
        diffusion_summary = diffusion.get(
            "standFirst", "") or parent_diffusion.get("standFirst", "") or ""
        child_show_link = diffusion.get("url", "") or parent_diffusion.get(
            "url", "")
        parent_show_link = parent_show.get("url") or ""
        parent_show_title = parent_show.get("title") or parent["title"]
        # on vérifie que les infos parents ne sont pas redondantes avec les infos enfantes
        if (parent_show_link == child_show_link
                or parent_show_title.upper() in (metadata.get(
                    "title", "").upper(), metadata.get("show_title",
                                                       "").upper())):
            parent_show_link = ""
            parent_show_title = ""
        if len(diffusion_summary.strip()) == 1:
            diffusion_summary = ""
        detailed_metadata.update({
            "show_link":
            show.get("url", ""),
            "link":
            diffusion.get("url", "") or parent_diffusion.get("url", ""),
            "summary":
            diffusion_summary.strip(),
            "parent_show_title":
            parent_show_title,
            "parent_show_link":
            parent_show_link,
        })
        return detailed_metadata

    def _get_radiofrance_step(self, api_data: dict, dt: datetime,
                              child_precision: bool, detailed: bool) -> Step:
        """Return a track step or a programme step from Radio France depending of contained metadata"""
        return (self._get_radiofrance_track_step(api_data, dt)
                if api_data.get("track")
                else self._get_radiofrance_programme_step(
                    api_data, dt, child_precision, detailed))

    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 _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 get_step(self, logger: Logger, dt: datetime, channel) -> UpdateInfo:
        start = int(dt.timestamp())
        fetched_data = self._fetch_metadata(dt, dt + timedelta(minutes=120))
        if (error_step := self._handle_api_exception(fetched_data, logger,
                                                     start)) is not None:
            return self._notifying_update_info(error_step)
        try:
            # on récupère la première émission trouvée
            first_show_in_grid = fetched_data["data"]["grid"][0]
            # si celle-ci est terminée et la suivante n'a pas encore démarrée
            # alors on RENVOIE une métadonnées neutre jusqu'au démarrage de l'émission
            # suivante
            if first_show_in_grid["end"] < start:
                next_show = fetched_data["data"]["grid"][1]
                return self._notifying_update_info(
                    Step.empty_until(start, int(next_show["start"]), self))
            # si l'émission n'est pas encore démarrée, on RENVOIE une métaonnée neutre
            # jusqu'au démarrage de celle-ci
            if first_show_in_grid["start"] > dt.timestamp():
                return self._notifying_update_info(
                    Step.empty_until(start, int(first_show_in_grid['start']),
                                     self))
            # cas où on est dans un programme, on RENVOIE les métadonnées de ce programme
            return self._notifying_update_info(
                self._get_radiofrance_step(first_show_in_grid,
                                           dt,
                                           child_precision=True,
                                           detailed=True))

        except Exception as err:
            logger.error(traceback.format_exc())
            logger.error("Données récupérées avant l'exception : {}".format(
                fetched_data))
            return self._notifying_update_info(
                Step.empty_until(start, start + 90, self))
Beispiel #24
0
        if (error_step :=
                self._handle_api_exception(api_data, logger,
                                           int(dt.timestamp()))) is not None:
            return error_step
        try:
            first_show_in_grid = api_data["data"]["grid"][0]
            return self._get_radiofrance_step(first_show_in_grid,
                                              dt,
                                              child_precision=True,
                                              detailed=False)
        except Exception as err:
            start = int(dt.timestamp())
            logger.error(traceback.format_exc())
            logger.error(
                "Données récupérées avant l'exception : {}".format(api_data))
            return Step.empty_until(start, start + 90, self)

    def get_schedule(self, logger: Logger, start: datetime,
                     end: datetime) -> List[Step]:
        api_data = self._fetch_metadata(start, end, retry=5, raise_exc=True)
        temp_end, end = start, end
        steps = []
        grid = api_data["data"]["grid"]
        while grid:
            step_data = grid.pop(0)
            new_step = self._get_radiofrance_programme_step(
                step_data, temp_end, child_precision=False, detailed=False)
            steps.append(new_step)
            temp_end = datetime.fromtimestamp(new_step.end)
        return steps
Beispiel #25
0
 def _post_get_hook_step(self, data: dict):
     try:
         return Step(**data)
     except (TypeError, ValidationError) as err:
         return Step.none()