def test_datetime_000_21(self):
        """Test method 'MythTV.asnaiveutc()"""

        dt24 = self.ut.split(" ", 1)[1]
        dt24 = dt24.rsplit(" ", 1)[0]
        dtiso = datetime.fromIso(dt24, tz='UTC')
        autiso = dtiso.asnaiveutc()

        dt25 = self.lt.split(" ", 1)[1]
        dt25 = dt25.rsplit(" ", 1)[0]
        dtiso = datetime.fromIso(dt25)
        autiso1 = dtiso.asnaiveutc()

        #print(autiso)
        #print(autiso1)
        self.assertEqual(autiso, autiso1,
                         "Wrong time calculated from 'asnaiveutc()'")
    def test_datetime_000_20(self):
        """Test method 'MythTV.datetime.utcisoformat()"""

        dt24 = self.ut.split(" ", 1)[1]
        dt24 = dt24.rsplit(" ", 1)[0]
        dtiso = datetime.fromIso(dt24, tz='UTC')
        utiso = dtiso.utcisoformat()

        dt25 = self.lt.split(" ", 1)[1]
        dt25 = dt25.rsplit(" ", 1)[0]
        dtiso = datetime.fromIso(dt25)
        utiso1 = dtiso.utcisoformat()

        #print(dtiso)
        #print(dtiso1)
        self.assertEqual(utiso, utiso1,
                         "Wrong time calculated from 'utcisoformat()'")
    def test_datetime_000_17(self):
        """Test method 'MythTV.datetime.timestamp()"""

        dt20 = self.ut.split(" ", 1)[1]
        dt20 = dt20.rsplit(" ", 1)[0]
        dtiso = datetime.fromIso(dt20, tz='UTC')
        tstamp = dtiso.timestamp()
        #print(tstamp)
        self.assertTrue((abs(tstamp - self.cts) < 1.0),
                        "'datetime.timestamp()' delivers wrong stamp")

        dt21 = self.lt.split(" ", 1)[1]
        dt21 = dt21.rsplit(" ", 1)[0]
        dtiso1 = datetime.fromIso(dt21)
        tstamp1 = dtiso1.timestamp()
        #print(tstamp1)
        self.assertTrue((abs(tstamp1 - self.cts) < 1.0),
                        "'datetime.timestamp()' delivers wrong stamp")
    def test_datetime_000_08(self):
        """Test method 'MythTV.datetime.utcnow()' against output of 'timedatectl'"""

        t6 = datetime.utcnow()
        dt7 = self.ut.split(" ", 1)[1]
        dt7 = dt7.rsplit(" ", 1)[0]
        dtiso = datetime.fromIso(dt7, tz='UTC')
        #print(dtiso)
        self.assertTrue(((t6 - dtiso) < timedelta(seconds=1)),
                        "Wrong implementation of 'utc(now)'")
    def test_datetime_002_03(self):
        """Test 'ISO' to 'UTC' conversion in respect to daylight saving time of 'America/Anchorage'
           using 'fromIso(,tz='UTC') method.
        """

        for i, t in enumerate(self.t_iso_list):
            t_fromiso = datetime.fromIso(t, tz='UTC')
            t_org = t_fromiso.isoformat()
            self.assertTrue(isinstance(t_fromiso, datetime))
            # check if conversion works:
            self.assertEqual(t_org, self.t_iso_utc_list[i])
            # check for correct types:
            self.assertTrue(isinstance(t_fromiso, datetime))
            self.assertTrue('posixtzinfo' in repr(t_fromiso.tzinfo))
Example #6
0
def buildNumbers(args, opts):
    # either option -N <inetref> <subtitle>   e.g. -N 69 "Elizabeth Keen"
    #    or  option -N <inetref> <date time>  e.g. -N 69 "2021-01-29 19:00:00"
    #    or  option -N <title> <subtitle>     e.g. -N "The Blacklist" "Elizabeth Keen"
    #    or  option -N <title> <date time>    e.g. -N "The Blacklist" "2021-01-29 19:00:00"
    from MythTV.utility import levenshtein
    from MythTV.utility.dt import posixtzinfo
    from MythTV.tvmaze import tvmaze_api as tvmaze
    from MythTV import datetime
    from lxml import etree
    from datetime import timedelta

    if opts.debug:
        print("Function 'buildNumbers' called with arguments: " +
              (" ".join(["'%s'" % i for i in args])))

    # set the session
    if opts.session:
        tvmaze.set_session(opts.session)

    dtInLocalZone = None
    # ToDo:
    # below check shows a deficiency of the MythTV grabber API itself:
    # TV-Shows or Movies with an integer as title are not recognized correctly.
    # see https://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format
    # and https://code.mythtv.org/trac/ticket/11850
    try:
        inetref = int(args[0])
        tvsubtitle = args[1]
        inetrefList = [inetref]

    except ValueError:
        tvtitle = args[0]
        tvsubtitle = args[1]
        inetrefList = []  # inetrefs for shows with title matches
        best_show_quality = 0.5  # require at least this quality on string match

        showlist = tvmaze.search_show(tvtitle)

        # It's problematic to make decisions solely upon the Levenshtein distance.
        # If the strings are really long or really short, a simple rule, such as
        # "accept any distance < 6" can provide misleading results.
        # To establish a more useful measurement, we'll use the Levenshtein
        # distance to figure out the ratio (0 - 1) of matching characters in the
        # longer string, and call this 'match_quality'.
        #    "Risk", "Call" -> distance = 4
        #           match_quality = (4 - 4) / 4 = 0
        #    "In Sickness and in Health", "Sickness and Health" -> distance = 6
        #           match_quality = (25 - 6)/25 = .76

        for show_info in showlist:
            try:
                inetref = int(show_info.id)
                distance = levenshtein(show_info.name.lower(), tvtitle.lower())
                if len(tvtitle) > len(show_info.name):
                    match_quality = float(len(tvtitle) -
                                          distance) / len(tvtitle)
                else:
                    match_quality = float(len(show_info.name) -
                                          distance) / len(show_info.name)
                if match_quality >= best_show_quality:
                    #if opts.debug:
                    #print ('show_info =', show_info, ', match_quality =', match_quality)
                    if match_quality == best_show_quality:
                        inetrefList.append(inetref)
                    else:
                        # Any items previously appended for a lesser match need to be eliminated
                        inetrefList = [inetref]
                        best_show_quality = match_quality
            except (TypeError, ValueError):
                pass

    # check whether the 'subtitle' is really a timestamp
    try:
        dtInLocalZone = datetime.strptime(
            tvsubtitle, "%Y-%m-%d %H:%M:%S")  # defaults to local timezone
    except ValueError:
        dtInLocalZone = None

    matchesFound = 0
    best_ep_quality = 0.5  # require at least this quality on string match
    tree = etree.XML(u'<metadata></metadata>')
    for inetref in inetrefList:
        dtInTgtZone = None
        if dtInLocalZone:
            try:
                show_info = tvmaze.get_show(inetref)
                # Some cases have 'network' = None, but webChannel != None. If we
                # find such a case, we'll set show_network to the webChannel.
                show_network = show_info.network
                if show_network is None:
                    show_network = show_info.streaming_service
                show_country = show_network.get('country')
                show_tz = show_country.get('timezone')
                dtInTgtZone = dtInLocalZone.astimezone(posixtzinfo(show_tz))

            except (ValueError, AttributeError) as e:
                dtInTgtZone = None

        if dtInTgtZone:
            # get episode info based on inetref and datetime in target zone
            try:
                #print('get_show_episodes_by_date(', inetref, ',', dtInTgtZone, ')')
                episodes = tvmaze.get_show_episodes_by_date(
                    inetref, dtInTgtZone)
            except SystemExit:
                episodes = []
            time_match_list = []
            early_match_list = []
            minTimeDelta = timedelta(minutes=60)
            for i, ep in enumerate(episodes):
                epInTgtZone = datetime.fromIso(ep.timestamp,
                                               tz=posixtzinfo(show_tz))
                durationDelta = timedelta(minutes=ep.duration)

                # Consider it a match if the recording starts late, but within the duration of the show.
                if epInTgtZone <= dtInTgtZone < epInTgtZone + durationDelta:
                    # Recording start time is within the range of this episode
                    if opts.debug:
                        print('Recording in range of inetref %d, season %d, episode %d (%s ... %s)' \
                                % (inetref, ep.season, ep.number, epInTgtZone, epInTgtZone+durationDelta))
                    time_match_list.append(i)
                    minTimeDelta = timedelta(minutes=0)
                # Consider it a match if the recording is a little bit early. This helps cases
                # where you set up a rule to record, at say 9:00, and the broadcaster uses a
                # slightly odd start time, like 9:05.
                elif epInTgtZone - minTimeDelta <= dtInTgtZone < epInTgtZone:
                    # Recording started earlier than this episode, so see if it's the closest match
                    if epInTgtZone - dtInTgtZone == minTimeDelta:
                        if opts.debug:
                            print('adding episode to closest list',
                                  epInTgtZone - dtInTgtZone, '\n')
                        early_match_list.append(i)
                    elif epInTgtZone - dtInTgtZone < minTimeDelta:
                        if opts.debug:
                            print('this episode is new closest',
                                  epInTgtZone - dtInTgtZone, '\n')
                        minTimeDelta = epInTgtZone - dtInTgtZone
                        early_match_list = [i]

            if not time_match_list:
                # No exact matches found, so use the list of the closest episode(s)
                time_match_list = early_match_list

            if time_match_list:
                for ep_index in time_match_list:
                    season_nr = str(episodes[ep_index].season)
                    episode_id = episodes[ep_index].id
                    item = buildSingleItem(inetref, season_nr, episode_id)
                    if item is not None:
                        tree.append(item.toXML())
                        matchesFound += 1
        else:
            # get episode based on subtitle
            episodes = tvmaze.get_show_episode_list(inetref)

            min_dist_list = []
            for i, ep in enumerate(episodes):
                if 0 and opts.debug:
                    print("tvmaze.get_show_episode_list(%s) returned :" %
                          inetref)
                    for k, v in ep.__dict__.items():
                        print(k, " : ", v)
                distance = levenshtein(ep.name, tvsubtitle)
                if len(tvsubtitle) >= len(ep.name):
                    match_quality = float(len(tvsubtitle) -
                                          distance) / len(tvsubtitle)
                else:
                    match_quality = float(len(ep.name) - distance) / len(
                        ep.name)
                #if opts.debug:
                #print('inetref', inetref, 'episode =', ep.name, ', distance =', distance, ', match_quality =', match_quality)
                if match_quality >= best_ep_quality:
                    if match_quality == best_ep_quality:
                        min_dist_list.append(i)
                        if opts.debug:
                            print(
                                '"%s" added to best list, match_quality = %g' %
                                (ep.name, match_quality))
                    else:
                        # Any items previously appended for a lesser match need to be eliminated
                        tree = etree.XML(u'<metadata></metadata>')
                        min_dist_list = [i]
                        best_ep_quality = match_quality
                        if opts.debug:
                            print('"%s" is new best match_quality = %g' %
                                  (ep.name, match_quality))

            # The list is constructed in order of oldest season to newest.
            # If episodes with equivalent match quality show up in multiple
            # seasons, we want to list the most recent first. To accomplish
            # this, we'll process items starting at the end of the list, and
            # proceed to the beginning.
            while min_dist_list:
                ep_index = min_dist_list.pop()
                season_nr = str(episodes[ep_index].season)
                episode_id = episodes[ep_index].id
                if opts.debug:
                    episode_nr = str(episodes[ep_index].number)
                    print("tvmaze.get_show_episode_list(%s) returned :" %
                          inetref)
                    print("with season : %s and episode %s" %
                          (season_nr, episode_nr))
                    print(
                        "Chosen episode index '%d' based on match quality %g" %
                        (ep_index, best_ep_quality))

                # we have now inetref, season, episode_id
                item = buildSingleItem(inetref, season_nr, episode_id)
                if item is not None:
                    tree.append(item.toXML())
                    matchesFound += 1

    if matchesFound > 0:
        print_etree(
            etree.tostring(tree,
                           encoding='UTF-8',
                           pretty_print=True,
                           xml_declaration=True))
    else:
        if dtInLocalZone:
            raise Exception(
                "Cannot find any episode with timestamp matching '%s'." %
                tvsubtitle)
        else:
            # tvmaze.py -N 4711 "Episode 42"
            raise Exception("Cannot find any episode with subtitle '%s'." %
                            tvsubtitle)