Пример #1
0
    def copy(self):
        title = 'Copy files to %s' % Config.tv_dir
        choice, data = self.display_list(title, table_type='copy')
        click.echo()

        if choice == 'copy_all':
            copied_all = True
            for episode in data[1]:
                torrent_hash = episode[3]
                torrent_dir, torrent_name = os.path.split(episode[2])
                click.echo('Copying: %s... ' % episode[1], nl=False)
                try:
                    DownloadManager(torrent_hash, torrent_dir, torrent_name)
                except OSError as e:
                    copied_all = False
                    click.echo(tvu.style(str(e), fg='red'))
                else:
                    click.echo(tvu.style('Done', fg='green'))
            if not copied_all:
                click.echo()
                click.echo('Error: Some files could not be copied.')

        else:
            selected = [i for i in data[1] if choice in i][0]
            torrent_hash = selected[3]
            torrent_dir, torrent_name = os.path.split(selected[2])
            click.echo('Copying: %s... ' % selected[1], nl=False)
            try:
                DownloadManager(torrent_hash, torrent_dir, torrent_name)
            except OSError as e:
                click.echo(tvu.style(str(e), fg='red'))
                sys.exit(1)
            click.echo('Done')
Пример #2
0
def list_missing(today):
    """List episodes that are ready to download.
    """
    send(te, v)
    fp = tvu.FancyPrint()

    shows = Shows()

    with click.progressbar(
            shows,
            item_show_func=tfunct,
            show_percent=False,
            show_eta=False,
            width=Config.pb.width,
            empty_char=tvu.style(Config.pb.empty_char,
                                 fg=Config.pb.dark,
                                 bg=Config.pb.dark),
            fill_char=tvu.style(Config.pb.fill_char,
                                fg=Config.pb.light,
                                bg=Config.pb.light),
            bar_template=Config.pb.template,
    ) as bar:
        for series in bar:
            if series.is_missing(today):
                fp.standard_print(series.show_missing())
    fp.done()
Пример #3
0
def list_missing(today):
    """List episodes that are ready to download.
    """
    send(te, v)
    fp = tvu.FancyPrint()

    shows = Shows()

    with click.progressbar(
            shows,
            item_show_func=tfunct,
            show_percent=False,
            show_eta=False,
            width=Config.pb.width,
            empty_char=tvu.style(Config.pb.empty_char,
                                 fg=Config.pb.dark,
                                 bg=Config.pb.dark),
            fill_char=tvu.style(Config.pb.fill_char,
                                fg=Config.pb.light,
                                bg=Config.pb.light),
            bar_template=Config.pb.template,
    ) as bar:
        for series in bar:
            if series.is_missing(today):
                fp.standard_print(series.show_missing())
    fp.done()
Пример #4
0
    def notify(self, *msg):
        msg = ', '.join([str(i) for i in msg])
        base_pad = (math.ceil(len(msg) / Config.console_columns)
                    * Config.console_columns)
        padding = ' ' * (base_pad - (len(msg) + len(self.name) + 2))
        msg = tvu.style(msg, fg='green')
        sitename = tvu.style(self.name, fg='yellow')
        fullmsg = '%s: %s%s' % (sitename, msg, padding)

        click.echo('%s' % fullmsg, err=True)
Пример #5
0
 def display_error(self, message):
     colors = Config.color
     click.echo()
     click.echo('%s %s' % (
         style('[!]',
               fg=colors.warn.fg,
               bg=colors.warn.bg),
         style(message,
               fg=colors.warn.fg))
     )
Пример #6
0
    def notify(self, *msg):
        msg = ', '.join([str(i) for i in msg])
        base_pad = (math.ceil(len(msg) / Config.console_columns) *
                    Config.console_columns)
        padding = ' ' * (base_pad - (len(msg) + len(self.name) + 2))
        msg = tvu.style(msg, fg='green')
        sitename = tvu.style(self.name, fg='yellow')
        fullmsg = '%s: %s%s' % (sitename, msg, padding)

        click.echo('%s' % fullmsg, err=True)
Пример #7
0
def tvol(no_cache, config_name):
    """Download and manage tv shows.

    Use `tvol COMMAND -h` to get help for each command.

    \b
       \/    TVOverlord source code is available at:
      [. ]   https://github.com/8cylinder/tv-overlord
       ^
      /^\\    Any feature requests or bug reports should go there.
     //^\\\\
    -^-._.--.-^^-.____._^-.^._
    """
    Config.get_config_data(config_name)
    DB.configure()

    if Config.version_notification:
        if v.new_version():
            msg = tvu.style(v.message, fg='green')
            click.secho(msg, err=True)

    te.ask()

    if no_cache:
        Config.use_cache = False
    else:
        Config.use_cache = True
Пример #8
0
def tvol(no_cache, config_name):
    """Download and manage tv shows.

    Use `tvol COMMAND -h` to get help for each command.

    \b
       \/    TVOverlord source code is available at:
      [. ]   https://github.com/8cylinder/tv-overlord
       ^
      /^\\    Any feature requests or bug reports should go there.
     //^\\\\
    -^-._.--.-^^-.____._^-.^._
    """
    Config.get_config_data(config_name)
    DB.configure()

    if Config.version_notification:
        if v.new_version():
            msg = tvu.style(v.message, fg='green')
            click.secho(msg, err=True)

    te.ask()

    if no_cache:
        Config.use_cache = False
    else:
        Config.use_cache = True
Пример #9
0
 def exists(self, filename):
     if filename is None:
         return ''
     elif os.path.exists(filename):
         filename = filename
     else:
         filename = style(filename, fg='black', strike=True)
     return filename
Пример #10
0
    def test_each(self, search_string):
        engines = self.torrent_engines + self.newsgroup_engines
        indent = '  '
        click.echo('Searching for: %s' % search_string)
        for engine in engines:
            search = engine.Provider()
            name = '%s (%s)' % (search.name, search.shortname)
            click.echo(style(name, bold=True))

            results = search.search(search_string)

            click.echo(indent + style(search.url, fg='blue', ul=True))
            results_count = str(len(results))
            if results_count == '0':
                results_count = style(results_count, fg='red')
            else:
                results_count = style(results_count, fg='green')
            click.echo(indent + 'Search results: %s' % results_count)
Пример #11
0
    def test_each(self, search_string, show_results):
        """Do a test search for each search engine"""
        engines = self.torrent_engines + self.newsgroup_engines
        indent = '  '
        click.echo()
        click.echo('Searching for: %s' % search_string)
        for engine in engines:
            search = engine
            name = '%s (%s)' % (search.name, search.shortname)
            click.echo(tu.style(name, bold=True))

            results = search.search(search_string)

            click.echo(indent + tu.style(search.url, fg='blue', ul=True))
            results_count = str(len(results))
            if results_count == '0':
                results_count = tu.style(results_count, fg='red')
            else:
                results_count = tu.style(results_count, fg='green')
            click.echo(indent + 'Search results: %s' % results_count)

            if show_results:
                for result in results:
                    click.echo('%s* %s' % (indent, result[0]))
Пример #12
0
    def test_each(self, search_string, show_results):
        """Do a test search for each search engine"""
        engines = self.torrent_engines + self.newsgroup_engines
        indent = '  '
        click.echo()
        click.echo('Searching for: %s' % search_string)
        for engine in engines:
            search = engine
            name = '%s (%s)' % (search.name, search.shortname)
            click.echo(tu.style(name, bold=True))

            results = search.search(search_string)

            click.echo(indent + tu.style(search.url, fg='blue', ul=True))
            results_count = str(len(results))
            if results_count == '0':
                results_count = tu.style(results_count, fg='red')
            else:
                results_count = tu.style(results_count, fg='green')
            click.echo(indent + 'Search results: %s' % results_count)

            if show_results:
                for result in results:
                    click.echo('%s* %s' % (indent, result[0]))
Пример #13
0
 def show_missing(self):
     missing = self.missing
     if len(missing) == 0:
         return False
     ret = style(self.db_name, fg='green', bold=True)
     ret += ' - %s, %s' % (self.airsDayOfWeek, self.airsTime)
     ret += '\n'
     indent = '    '
     missing_list = []
     for s in missing:
         se = 'S%sE%s' % (s['season'].rjust(2, '0'), s['episode'].rjust(
             2, '0'))
         missing_list.append(se)
     ret += textwrap.fill(', '.join(missing_list),
                          width=int(self.console_columns),
                          initial_indent=indent,
                          subsequent_indent=indent)
     return ret
Пример #14
0
 def show_missing(self):
     missing = self.missing
     if len(missing) == 0:
         return False
     ret = style(self.db_name, fg='green', bold=True)
     ret += ' - %s, %s' % (self.airs_dayofweek, self.airs_time)
     ret += '\n'
     indent = '    '
     missing_list = []
     for s in missing:
         se = 'S%sE%s' % (s['season'].rjust(2, '0'),
                          s['episode'].rjust(2, '0'))
         missing_list.append(se)
     ret += textwrap.fill(', '.join(missing_list),
                          width=int(self.console_columns),
                          initial_indent=indent,
                          subsequent_indent=indent)
     return ret
Пример #15
0
    def generate(self):
        colors = Config.color.table
        title_bar = style(
            '|',
            fg=colors.bar.fg,
            bg=colors.header.bg,)
        bar = style(
            '|',
            fg=colors.bar.fg,
        )

        # TITLE --------------------------------------------
        title = '  %s' % self.table.title.text
        title = title.ljust(Config.console_columns)
        title = style(title,
                      bold=True,
                      fg=colors.title.fg,
                      bg=colors.title.bg)
        click.echo(title)

        # HEADER ROW ---------------------------------------
        header = self.table.header
        header_row = [style(' ', bg=colors.header.bg,
                                 fg=colors.header.fg)]
        NUMBER_SPACE = 1
        BAR_COUNT = len(header.widths)
        flex_width = (Config.console_columns - sum(header.widths) -
                      NUMBER_SPACE - BAR_COUNT)

        for title, width in zip(header.titles,
                                header.widths):
            if width == 0:
                width = flex_width
            title = title[:width].ljust(width)
            header_row.append(
                style(title,
                      bg=colors.header.bg,
                      fg=colors.header.fg,
                )
            )

        header_row = title_bar.join(header_row)

        click.echo(header_row)

        # BODY ROWS -----------------------------------------

        key = """abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW"""
        key += """XYZ0123456789!#$%&()*+-./:;<=>?@[\\]^_`|}{"'~"""
        if self.table_type == 'download':
            options = '\nLetter, [s]kip, skip [r]est of show, [q]uit or [m]ark as downloaded: '
            key = re.sub('[srqm]', '', key)

        elif self.table_type == 'nondb':
            options = '\nLetter or [q]uit: '
            key = re.sub('[q]', '', key)

        elif self.table_type == 'copy':
            options = '\nLetter, [a]ll or [q]uit: '
            key = re.sub('[aq]', '', key)

        elif self.table_type == 'redownload':
            options = '\nLetter or [q]uit: '
            key = re.sub('[q]', '', key)


        self.table.body = self.table.body[:self.display_count]
        for row, counter in zip(self.table.body, key):
            # look through the title cell to see if any have 720 or
            # 1080 in the string and mark this row as high def.
            is_hidef = False
            if '720p' in row[0] or '1080p' in row[0]:
                is_hidef = True

            row_arr = [counter]
            for i, width, align in zip(row, header.widths, header.alignments):
                i = str(i)
                if width == 0:
                    width = flex_width
                row_item = i
                row_item = U.snip(row_item, width)
                row_item = row_item.strip()

                if align == '<':
                    row_item = row_item.ljust(width)
                if align == '>':
                    row_item = row_item.rjust(width)
                if align == '=':
                    row_item = row_item.center(width)
                else:
                    row_item = row_item.ljust(width)

                # if hi def, set the foreground to green
                if is_hidef:
                    row_item = style(row_item, fg=colors.hidef.fg)

                row_arr.append(row_item)
            click.echo(bar.join(row_arr))

        # USER INPUT ---------------------------------------
        choice = False
        while not choice:
            choice = self.ask(options, key)
        return choice
Пример #16
0
    def search(self,
               search_string,
               season=False,
               episode=False,
               date_search=None,
               search_type='torrent'):
        """
        Return an array of values:

        [
          [
            ['Title string', 'search url'],
            [head1, head2, head3, id],
            [head1-width, head2-width, head3-width],
            [head1-alignment, head2-alignment, head3-alignment]
          ],
          [
            [<column 1 data>, <column 2 data>, <column 3 data>, <id>],
            [<column 1 data>, <column 2 data>, <column 3 data>, <id>],
            # etc...
          ]
        ]
        """

        self.season = season
        self.episode = episode
        self.show_name = search_string
        self.search_type = search_type

        click.echo()

        if self.search_type == 'torrent':
            header = [[search_string, ''],
                      ['Name', 'Size', 'Date', 'Seeds', 'SE'],
                      [0, 10, 12, 6, 2], ['<', '>', '<', '>', '<']]
        else:
            header = [[search_string, ''], ['Name', 'Size', 'Date', 'SE'],
                      [0, 10, 12, 3], ['<', '>', '<', '<']]

        if self.search_type == 'torrent':
            engines = self.torrent_engines
        elif self.search_type == 'nzb':
            engines = self.newsgroup_engines
        else:
            raise ValueError('search_type can only be "torrent" or "nzb"')

        socket.setdefaulttimeout(10)
        # socket.setdefaulttimeout(0.1)
        episodes = []
        with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
            # For nzb's, the idx is needed so Provider.download knows
            # which engine was used.  It's not needed for torrents
            # because there is no download method.
            res = {
                executor.submit(self.job, engine, search_string, season,
                                episode, date_search, idx): engine
                for idx, engine in enumerate(engines)
            }

            names = [i.name for i in engines]
            names.sort()
            names = [' %s ' % i for i in names]
            names = [tu.style(i, fg='white', bg='red') for i in names]
            for future in concurrent.futures.as_completed(res):

                results = future.result()
                finished_name = results[-1]
                for i, e in enumerate(names):
                    e = click.unstyle(e).strip()
                    if e == finished_name:
                        e = ' %s ' % e
                        names[i] = tu.style(e, fg='white', bg='green')

                if date_search:
                    title = '%s %s' % (search_string.strip(), date_search)
                else:
                    title = '%s %s' % (search_string.strip(),
                                       tu.sxxexx(season, episode))
                title = title.ljust(Config.console_columns)
                click.echo(tu.style(title, bold=True))
                click.echo(' '.join(names))
                # move up two lines
                click.echo('[%sA' % 3)

                episodes = episodes + results[:-1]

        # go up 3 lines to remove the progress bar
        click.echo('[%sA' % 2)

        if self.search_type == 'torrent':
            self.sort_torrents(episodes)

        if Config.filter_list:
            episodes = [i for i in episodes if self.filter_episode(i)]

        self.episodes = episodes
        # return search_results
        return [header] + [episodes]
Пример #17
0
    def search(self, search_string, season=False, episode=False,
               date_search=None, search_type='torrent'):
        """
        Return an array of values:

        [
          [
            ['Title string', 'search url'],
            [head1, head2, head3, id],
            [head1-width, head2-width, head3-width],
            [head1-alignment, head2-alignment, head3-alignment]
          ],
          [
            [<column 1 data>, <column 2 data>, <column 3 data>, <id>],
            [<column 1 data>, <column 2 data>, <column 3 data>, <id>],
            # etc...
          ]
        ]
        """

        self.season = season
        self.episode = episode
        self.show_name = search_string
        self.search_type = search_type

        click.echo()

        if self.search_type == 'torrent':
            header = [
                [search_string, ''],
                ['Name', 'Size', 'Date', 'Seeds', 'SE'],
                [0, 10, 12, 6, 2],
                ['<', '>', '<', '>', '<']]
        else:
            header = [
                [search_string, ''],
                ['Name', 'Size', 'Date', 'SE'],
                [0, 10, 12, 3],
                ['<', '>', '<', '<']]

        if self.search_type == 'torrent':
            engines = self.torrent_engines
        elif self.search_type == 'nzb':
            engines = self.newsgroup_engines
        else:
            raise ValueError('search_type can only be "torrent" or "nzb"')

        # socket.setdefaulttimeout(5)
        # socket.setdefaulttimeout(0.1)
        episodes = []
        with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
            # For nzb's, the idx is needed so Provider.download knows
            # which engine was used.  It's not needed for torrents
            # because there is no download method.
            res = {
                executor.submit(
                    self.job, engine, search_string, season, episode, date_search, idx
                ): engine for idx, engine in enumerate(engines)
            }

            names = [i.name for i in engines]
            names.sort()
            names = [' %s ' % i for i in names]
            names = [tu.style(i, fg='white', bg='red') for i in names]
            for future in concurrent.futures.as_completed(res):

                results = future.result()
                finished_name = results[-1]
                for i, e in enumerate(names):
                    e = click.unstyle(e).strip()
                    if e == finished_name:
                        e = ' %s ' % e
                        names[i] = tu.style(e, fg='white', bg='green')

                if date_search:
                    title = '%s %s' % (search_string.strip(),
                                       date_search)
                else:
                    title = '%s %s' % (search_string.strip(),
                                       tu.sxxexx(season, episode))
                title = title.ljust(Config.console_columns)
                click.echo(tu.style(title, bold=True))
                click.echo(' '.join(names))
                # move up two lines
                click.echo('[%sA' % 3)

                episodes = episodes + results[:-1]

        # go up 3 lines to remove the progress bar
        click.echo('[%sA' % 2)

        if self.search_type == 'torrent':
            self.sort_torrents(episodes)

        self.episodes = episodes
        # return search_results
        return [header] + [episodes]
Пример #18
0
def calendar(show_name, show_all, sort_by_next, no_color, days):
    """Display a calendar of upcoming episodes.

    If SHOW_NAME is used, it will display a calendar for any shows that
    match that string.

    --days can be one number or two numbers.  If one number is used, it will
    show that many days ahead.  If two numbers are used, the first number is
    where the calendar will start and the second number is where it will end.
    The two number must be seperated by a comma with no spaces.

    \b
    --days 10      will show from today to 10 days in the future
    --days 10,20   will start ten days from now and then show 20 days ahead.
    --days -20,10  will go back 20 days from today and then show ahead from there.
    """
    if no_color:
        use_color = False
    else:
        use_color = True

    # set colors for ui elements
    if Config.is_win:
        foreground = "white"
        header_color = "blue"
        date_color_1 = "blue"
        date_color_2 = 0
        title_color_1 = "blue"
        title_color_2 = 0
    else:
        foreground = 225
        header_color = 17
        date_color_1 = 17
        date_color_2 = 0
        title_color_1 = 18
        title_color_2 = 0

    title_width = 20  # width of show titles column
    spacer = " "  # can be any string, any length
    today = datetime.datetime.today()

    if days:
        days = days.split(",")
        days = [int(x) for x in days]
        if len(days) == 2:
            today = today + datetime.timedelta(days=days[0])
            calendar_columns = days[1]
        if len(days) == 1:
            calendar_columns = days[0]
    else:
        calendar_columns = Config.console_columns - (title_width + len(spacer))

    # Days_chars can be any string of seven chars. eg: 'mtwtfSS'
    days_chars = ".....::"  # first char is monday
    monthstart = "|"  # marker used to indicate the begining of month

    # build date title row
    months_row = today.strftime("%b") + (" " * calendar_columns)
    days_row = ""
    daybefore = today - datetime.timedelta(days=1)
    for days in range(calendar_columns):
        cur_date = today + datetime.timedelta(days=days)

        if cur_date.month != daybefore.month:
            days_row += monthstart
            month = cur_date.strftime("%b")
            month_len = len(month)
            months_row = months_row[:days] + month + months_row[(days + month_len) :]
        else:
            days_row += days_chars[cur_date.weekday()]

        daybefore = cur_date

    months_row = months_row[:calendar_columns]  # chop off any extra spaces created by adding the months
    if use_color:
        months_row = style(months_row, fg=foreground, bg=header_color)
        days_row = style(days_row, fg=foreground, bg=header_color)
    months_row = (" " * title_width) + (" " * len(spacer)) + months_row
    days_row = (" " * title_width) + (" " * len(spacer)) + days_row
    click.echo(months_row)
    click.echo(days_row)

    # build shows rows
    step = 3
    color_row = False
    counter = 1
    season_marker = "-"
    filter_date = ""
    filter_name = ""
    if sort_by_next:
        filter_date = True
    if show_name:
        filter_name = show_name

    show = Shows(name_filter=filter_name, by_date=filter_date)
    for show in show:
        broadcast_row = ""
        title = show.db_name[:title_width].ljust(title_width)
        has_episode = False
        first_display_date = True
        last_days_away = 0
        last_date = 0
        for i in show.series:  # season
            for j in show.series[i]:  # episode
                episode_number = show.series[i][j]["episodenumber"]
                b_date = show.series[i][j]["firstaired"]
                if not b_date:
                    continue  # some episode have no broadcast date?
                split_date = b_date.split("-")
                broadcast_date = datetime.datetime(int(split_date[0]), int(split_date[1]), int(split_date[2]))
                if broadcast_date == last_date:
                    continue  # sometimes multiple episodes have the same date, don't repeat them.
                last_date = broadcast_date
                if broadcast_date.date() < today.date():
                    continue  # don't include episodes before today
                days_away = (broadcast_date - today).days + 1
                if days_away >= calendar_columns:
                    continue  # don't include days after the width of the screen
                if show.series[i][j]["seasonnumber"] == "0":
                    continue  # not interested in season 0 episodes.

                if first_display_date:
                    if int(episode_number) > 1:
                        before_first = season_marker * days_away
                    else:
                        before_first = " " * days_away
                    broadcast_row = before_first + episode_number
                    first_display_date = False
                    # set the next episode date in the db while we're here:
                    show.set_next_episode(broadcast_date.date())
                else:
                    episode_char_len = len(str(int(episode_number) - 1))
                    broadcast_row = (
                        broadcast_row
                        + (season_marker * (days_away - last_days_away - episode_char_len))
                        + episode_number
                    )

                last_days_away = days_away

                has_episode = True

        broadcast_row = broadcast_row[:calendar_columns].ljust(calendar_columns)

        if has_episode or show_all:
            if use_color and color_row:
                title = style(title, fg=foreground, bg=title_color_1)
                broadcast_row = style(broadcast_row, fg=foreground, bg=date_color_1)
            elif use_color and not color_row:
                title = style(title, fg=foreground, bg=title_color_2)
                broadcast_row = style(broadcast_row, fg=foreground, bg=date_color_2)
            row = title + spacer + broadcast_row
            click.echo(row)

            if counter >= step:
                counter = 0
                color_row = True
            else:
                color_row = False
                counter += 1
Пример #19
0
    def add_new(self, name):
        try:
            result = self.tvapi.search(name)
        except KeyError as e:
            click.echo(
                'Show data returned from TheTVDB.com has an error in it.',
                err=True)
            sys.exit(1)

        if not result:
            click.echo('No show found.')
            return
        else:
            click.echo('Multiple shows found, type a number to select.')
            click.echo('Type "<ctrl> c" to cancel.')
            click.echo()
            indent = '     '
            for index, show in enumerate(result):
                title = show['seriesname']
                click.echo(' %2s. ' % (index + 1), nl=False)
                click.secho(title, bold=True)
                if 'overview' in show:
                    click.echo(format_paragraphs(
                        show['overview'], indent=indent))
                if 'firstaired' in show:
                    click.secho(
                        '%sFirst aired: %s' % (indent, show['firstaired']),
                        fg='green')
                click.echo()

            choice = click.prompt(
                'Choose number (or [enter] for #1)', default=1,
                type=click.IntRange(min=1, max=len(result)))
            idchoice = choice - 1
            show = result[idchoice]

        self.db_name = show['seriesname']  # name
        self._get_thetvdb_series_data()

        last_season = 1
        last_episode = 0
        for season in self.series:
            last_season = season
            for episode in self.series[season]:
                b_date = self.series[season][episode]['firstaired']
                if not b_date:
                    continue  # some episodes have no broadcast date
                split_date = b_date.split('-')
                broadcast_date = datetime.datetime(
                    int(split_date[0]), int(split_date[1]), int(split_date[2]))
                today = datetime.datetime.today()
                if broadcast_date > today:
                    break
                last_episode = episode

        last_sxxexx = style(sxxexx(last_season, last_episode), bold=True)

        if int(last_season) == 1 and int(last_episode) == 0:
            se = 0
            ep = 0
        else:
            click.echo()
            click.echo('The last episode broadcast was %s.' % last_sxxexx)
            msg = 'Start downloading the [f]irst (or [enter]), [l]atest or season and episode?'
            start = click.prompt(msg, default='f')

            if start == 'f':
                se = ep = 0
            elif start == 'l':
                se = last_season
                ep = last_episode
            else:
                try:
                    se, ep = [int(i) for i in start.split()]
                except ValueError:
                    sys.exit('Season and episode must be two numbers seperated by a space.')

            if ep > 0:
                ep -= 1  # episode in db is the NEXT episode

        msg = self._add_new_db(season=se, episode=ep)
        click.echo()
        click.echo(msg)
Пример #20
0
def info(show_name, show_all, sort_by_next, db_status,
         ask_inactive, show_links, synopsis):
    """Show information about your tv shows.

    SHOW_NAME can be a full or partial name of a show.  If used, it
    will show information about any shows that match that string, else
    it will show informaton about all your shows.
    """
    show_info = {}
    counter = 0
    status = 'active'

    # When the user specifies a single show, turn on --show-all
    # because the show they are asking for might an inactive show
    # and turn on --synopsis and --show-links since its only one
    # show we may as well show everything
    filter_name = ''
    if show_name:
        show_all = True
        synopsis = True
        show_links = True
        filter_name = show_name
        db_status = 'all'

    if Config.is_win:
        colors = {'links': 'blue', 'ended': 'red',
                  'last': 'cyan', 'future': 'green'}
    else:
        colors = {'links': 20, 'ended': 'red',
                  'last': 48, 'future': 22}

    shows = Shows(name_filter=filter_name, status=db_status)
    for show in shows:
        title = show.db_name

        if show.status == 'Ended':
            status = style(show.status, fg=colors['ended'])
        else:
            status = ''

        # build first row of info for each show
        se = 'Last downloaded: S%sE%s' % (
            str(show.db_current_season).rjust(2, '0'),
            str(show.db_last_episode).rjust(2, '0'),
        )
        se = style(se, fg=colors['last'])

        imdb_url = thetvdb_url = ''
        if show_links:
            imdb_url = style('\n    IMDB.com:    http://imdb.com/title/%s' % show.imdb_id,
                             fg=colors['links'])
            thetvdb_url = style('\n    TheTVDB.com: http://thetvdb.com/?tab=series&id=%s' % show.id,
                                fg=colors['links'])

        synopsis_text = None
        if synopsis and show.overview:
            paragraph = show.overview
            indent = '    '
            paragraph = format_paragraphs(paragraph, indent=indent)
            synopsis_text = '\n%s' % paragraph

        first_row_a = []
        fancy_title = style(title, bold=True)
        for i in [fancy_title + ',', se, status, imdb_url, thetvdb_url, synopsis_text]:
            if i: first_row_a.append(i)
        first_row = ' '.join(first_row_a)

        # build 'upcoming episodes' list
        today = datetime.datetime.today()
        first_time = True
        episodes_list = []
        counter += 1
        for i in show.series:  # season
            for j in show.series[i]:  # episode
                b_date = show.series[i][j]['firstaired']
                if not b_date: continue  # some episode have no broadcast date?

                split_date = b_date.split('-')
                broadcast_date = datetime.datetime(
                    int(split_date[0]), int(split_date[1]), int(split_date[2]))

                if not show_all:
                    if broadcast_date < today:
                        continue

                future_date = date_parser.parse(b_date)
                diff = future_date - today
                fancy_date = future_date.strftime('%b %d')
                if broadcast_date >= today:
                    episodes_list.append('S%sE%s, %s (%s)' % (
                        show.series[i][j]['seasonnumber'].rjust(2, '0'),
                        show.series[i][j]['episodenumber'].rjust(2, '0'),
                        fancy_date,
                        diff.days + 1,
                    ))

                if first_time:
                    first_time = False
                    if sort_by_next:
                        sort_key = str(diff.days).rjust(5, '0') + str(counter)
                    else:
                        sort_key = show.db_name.replace('The ', '')

        if not first_time:
            if episodes_list:
                indent = '    '
                episode_list = 'Future episodes: ' + ' - '.join(episodes_list)
                episodes = format_paragraphs(episode_list, indent=indent)
                show_info[sort_key] = first_row + '\n' + episodes
            else:
                show_info[sort_key] = first_row

        if ask_inactive:
            if show.status == 'Ended' and first_time:
                click.echo(
                    '%s has ended, and all have been downloaded. Set as inactive? [y/n]: ' %
                    title)
                set_status = click.getchar()
                click.echo(set_status)
                if set_status == 'y':
                    show.set_inactive()

    keys = list(show_info.keys())
    keys.sort()
    full_output = ''
    for i in keys:
        full_output = full_output + show_info[i] + '\n\n'

    # remove the last '\n\n'
    full_output = full_output.strip()

    if len(keys) < 4:
        click.echo(full_output)
    elif Config.is_win:
        click.echo(full_output)
    else:
        click.echo_via_pager(full_output)
Пример #21
0
def edit_db(search_str):
    try:
        import readline
    except ImportError:
        pass

    sql = 'SELECT * FROM shows WHERE name like :search'
    conn = sqlite3.connect(Config.db_file)
    conn.row_factory = tvu.dict_factory
    curs = conn.cursor()
    values = {'search': '%{}%'.format(search_str)}
    results = curs.execute(sql, values)

    data = []
    for i in results:
        data.append(i)

    if len(data) == 0:
        sys.exit('"%s" not found' % search_str)
    elif len(data) > 1:
        click.echo('Multiple shows found, type a number to edit.')
        click.echo('Type "<ctrl> c" to cancel.')
        click.echo()
        for index, show in enumerate(data):
            click.echo('  %s. %s' % (index + 1, show['name']))
        click.echo()
        choice = click.prompt(
            'Choose number', default=1,
            type=click.IntRange(min=1, max=len(data)))
        idchoice = choice - 1
        if idchoice not in range(len(data)):
            sys.exit('Invalid choice: %s' % choice)
        row = data[idchoice]
    else:
        row = data[0]

    editcolor = 'green' if Config.is_win else 31
    dirty = False
    is_error = False

    click.echo()
    click.echo(tvu.format_paragraphs('''
      While editing a field, hit <enter> in an empty field to leave it
      unchanged and skip to the next one.  Type "<ctrl> c" to cancel all
      edits.  The current value is shown in ()\'s beside the field
      name.'''))
    click.echo()

    title = '%s' % row['name']
    click.echo(tvu.style(title, bold=True, ul=True))
    click.echo()
    try:
        msg = tvu.style('Search engine name (%s): ', fg=editcolor)
        new_search_engine_name = input(msg % (row['search_engine_name']))
        if not new_search_engine_name:
            new_search_engine_name = row['search_engine_name']
        else:
            dirty = True

        msg = tvu.style('Current season (%s): ', fg=editcolor)
        new_season = input(msg % (row['season']))
        if not new_season:
            new_season = str(row['season'])
        else:
            dirty = True

        msg = tvu.style('Last episode (%s): ', fg=editcolor)
        new_episode = input(msg % (row['episode']))
        if not new_episode:
            new_episode = str(row['episode'])
        else:
            dirty = True

        msg = tvu.style('Status (%s): ', fg=editcolor)
        new_status = input(msg % (row['status']))
        if not new_status:
            new_status = row['status']
        else:
            dirty = True

        date_pretty = 'y' if row['search_by_date'] == 1 else 'n'
        msg = tvu.style('Search by date (%s) [y/n]: ', fg=editcolor)
        new_date = input(msg % (date_pretty))
        if not new_date:
            new_date = date_pretty
        else:
            dirty = True

        new_format = row['date_format']
        if new_date == 'y':
            click.echo('  The optional date format string can be any valid Python format string.')
            click.echo('  https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior')
            msg = tvu.style('  Date format string (%s): ', fg=editcolor)
            new_format = input(msg % (row['date_format']))
            if not new_format:
                new_format = row['date_format']
            else:
                dirty = True

    except KeyboardInterrupt:
        click.echo('\n\nDatabase edit cancelled.')
        sys.exit(0)

    click.echo()

    if dirty is False:
        click.echo('No changes made.')
        sys.exit(0)

    if not new_season.isdigit():
        click.echo('Error: Season must be a number')
        is_error = True

    if not new_episode.isdigit():
        click.echo('Error: Episode must be a number')
        is_error = True

    if new_status not in ['active', 'inactive']:
        click.echo('Error: Status must be either "active" or "inactive"')
        is_error = True

    if new_date not in ['y', 'n']:
        click.echo('Error: Search by date must be either "y" or "n"')
        is_error = True

    if is_error:
        sys.exit(1)

    if not click.confirm('Are these changes correct? (you can always change it back)', default='Y'):
        click.echo('\nDatabase edit cancelled.')
        sys.exit()

    sql = '''UPDATE shows SET
                season=:season,
                episode=:episode,
                status=:status,
                search_engine_name=:search_engine_name,
                search_by_date=:search_by_date,
                date_format=:date_format
             WHERE thetvdb_series_id=:tvdb_id'''

    row_values = {
        'season': new_season,
        'episode': new_episode,
        'status': new_status,
        'search_engine_name': new_search_engine_name,
        'tvdb_id': row['thetvdb_series_id'],
        'search_by_date': 1 if new_date == 'y' else 0,
        'date_format': new_format,
    }

    curs.execute(sql, row_values)

    conn.commit()
    conn.close()
Пример #22
0
    def add_new(self, name):
        try:
            result = self.tvapi.search(name)
        except KeyError as e:
            click.echo(
                'Show data returned from TheTVDB.com has an error in it.',
                err=True)
            sys.exit(1)

        if not result:
            click.echo('No show found.')
            return
        else:
            click.echo('Multiple shows found, type a number to select.')
            click.echo('Type "<ctrl> c" to cancel.')
            click.echo()
            indent = '     '
            for index, show in enumerate(result):
                title = show['seriesName']
                click.echo(' %2s. ' % (index + 1), nl=False)
                click.secho(title, bold=True)
                if 'overview' in show:
                    click.echo(
                        format_paragraphs(show['overview'], indent=indent))
                if 'firstaired' in show:
                    click.secho('%sFirst aired: %s' %
                                (indent, show['firstaired']),
                                fg='green')
                click.echo()

            choice = click.prompt('Choose number (or [enter] for #1)',
                                  default=1,
                                  type=click.IntRange(min=1, max=len(result)))
            idchoice = choice - 1
            show = result[idchoice]

        self.db_name = show['seriesName']  # name
        self._get_thetvdb_series_data()

        last_season = 1
        last_episode = 0
        for season in self.series:
            last_season = season
            for episode in self.series[season]:
                b_date = self.series[season][episode]['firstaired']
                if not b_date:
                    continue  # some episodes have no broadcast date
                split_date = b_date.split('-')
                broadcast_date = datetime.datetime(int(split_date[0]),
                                                   int(split_date[1]),
                                                   int(split_date[2]))
                today = datetime.datetime.today()
                if broadcast_date > today:
                    break
                last_episode = episode

        last_sxxexx = style(sxxexx(last_season, last_episode), bold=True)

        if int(last_season) == 1 and int(last_episode) == 0:
            se = 0
            ep = 0
        else:
            click.echo()
            click.echo('The last episode broadcast was %s.' % last_sxxexx)
            msg = 'Start downloading the [f]irst (or [enter]), [l]atest or season and episode?'
            start = click.prompt(msg, default='f')

            if start == 'f':
                se = ep = 0
            elif start == 'l':
                se = last_season
                ep = last_episode
            else:
                try:
                    se, ep = [int(i) for i in start.split()]
                except ValueError:
                    sys.exit(
                        'Season and episode must be two numbers seperated by a space.'
                    )

            if ep > 0:
                ep -= 1  # episode in db is the NEXT episode

        msg = self._add_new_db(season=se, episode=ep)
        click.echo()
        click.echo(msg)
Пример #23
0
def edit_db(search_str):
    try:
        import readline
    except ImportError:
        pass

    sql = 'SELECT * FROM shows WHERE name like :search'
    conn = sqlite3.connect(Config.db_file)
    conn.row_factory = tvu.dict_factory
    curs = conn.cursor()
    values = {'search': '%{}%'.format(search_str)}
    results = curs.execute(sql, values)

    data = []
    for i in results:
        data.append(i)

    if len(data) == 0:
        sys.exit('"%s" not found' % search_str)
    elif len(data) > 1:
        click.echo('Multiple shows found, type a number to edit.')
        click.echo('Type "<ctrl> c" to cancel.')
        click.echo()
        for index, show in enumerate(data):
            click.echo('  %s. %s' % (index + 1, show['name']))
        click.echo()
        choice = click.prompt(
            'Choose number', default=1,
            type=click.IntRange(min=1, max=len(data)))
        idchoice = choice - 1
        if idchoice not in range(len(data)):
            sys.exit('Invalid choice: %s' % choice)
        row = data[idchoice]
    else:
        row = data[0]

    editcolor = 'green' if Config.is_win else 31
    dirty = False
    is_error = False

    click.echo()
    click.echo(tvu.format_paragraphs('''
      While editing a field, hit <enter> in an empty field to leave it
      unchanged and skip to the next one.  Type "<ctrl> c" to cancel all
      edits.  The current value is shown in ()\'s beside the field
      name.'''))
    click.echo()

    title = '%s' % row['name']
    click.echo(tvu.style(title, bold=True, ul=True))
    click.echo()
    try:
        msg = tvu.style('Search engine name (%s): ', fg=editcolor)
        new_search_engine_name = input(msg % (row['search_engine_name']))
        if not new_search_engine_name:
            new_search_engine_name = row['search_engine_name']
        else:
            dirty = True

        msg = tvu.style('Current season (%s): ', fg=editcolor)
        new_season = input(msg % (row['season']))
        if not new_season:
            new_season = str(row['season'])
        else:
            dirty = True

        msg = tvu.style('Last episode (%s): ', fg=editcolor)
        new_episode = input(msg % (row['episode']))
        if not new_episode:
            new_episode = str(row['episode'])
        else:
            dirty = True

        msg = tvu.style('Status (%s): ', fg=editcolor)
        new_status = input(msg % (row['status']))
        if not new_status:
            new_status = row['status']
        else:
            dirty = True

        date_pretty = 'y' if row['search_by_date'] == 1 else 'n'
        msg = tvu.style('Search by date (%s) [y/n]: ', fg=editcolor)
        new_date = input(msg % (date_pretty))
        if not new_date:
            new_date = date_pretty
        else:
            dirty = True

        new_format = row['date_format']
        if new_date == 'y':
            click.echo('  The optional date format string can be any valid Python format string.')
            click.echo('  https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior')
            msg = tvu.style('  Date format string (%s): ', fg=editcolor)
            new_format = input(msg % (row['date_format']))
            if not new_format:
                new_format = row['date_format']
            else:
                dirty = True

    except KeyboardInterrupt:
        click.echo('\n\nDatabase edit cancelled.')
        sys.exit(0)

    click.echo()

    if dirty is False:
        click.echo('No changes made.')
        sys.exit(0)

    if not new_season.isdigit():
        click.echo('Error: Season must be a number')
        is_error = True

    if not new_episode.isdigit():
        click.echo('Error: Episode must be a number')
        is_error = True

    if new_status not in ['active', 'inactive']:
        click.echo('Error: Status must be either "active" or "inactive"')
        is_error = True

    if new_date not in ['y', 'n']:
        click.echo('Error: Search by date must be either "y" or "n"')
        is_error = True

    if is_error:
        sys.exit(1)

    if not click.confirm('Are these changes correct? (you can always change it back)', default='Y'):
        click.echo('\nDatabase edit cancelled.')
        sys.exit()

    sql = '''UPDATE shows SET
                season=:season,
                episode=:episode,
                status=:status,
                search_engine_name=:search_engine_name,
                search_by_date=:search_by_date,
                date_format=:date_format
             WHERE thetvdb_series_id=:tvdb_id'''

    row_values = {
        'season': new_season,
        'episode': new_episode,
        'status': new_status,
        'search_engine_name': new_search_engine_name,
        'tvdb_id': row['thetvdb_series_id'],
        'search_by_date': 1 if new_date == 'y' else 0,
        'date_format': new_format,
    }

    curs.execute(sql, row_values)

    conn.commit()
    conn.close()
Пример #24
0
    def generate(self):
        colors = Config.color.table
        title_bar = style(
            '|',
            fg=colors.bar.fg,
            bg=colors.header.bg,
        )
        bar = style(
            '|',
            fg=colors.bar.fg,
        )

        # TITLE --------------------------------------------
        title = '  %s' % self.table.title.text
        title = title.ljust(Config.console_columns)
        title = style(title, bold=True, fg=colors.title.fg, bg=colors.title.bg)
        click.echo(title)

        # HEADER ROW ---------------------------------------
        header = self.table.header
        header_row = [style(' ', bg=colors.header.bg, fg=colors.header.fg)]
        NUMBER_SPACE = 1
        BAR_COUNT = len(header.widths)
        flex_width = (Config.console_columns - sum(header.widths) -
                      NUMBER_SPACE - BAR_COUNT)

        for title, width, alignment in zip(header.titles, header.widths,
                                           header.alignments):
            if width == 0:
                width = flex_width
            if alignment == '<':
                title = title[:width].ljust(width)
            elif alignment == '>':
                title = title[:width].rjust(width)
            elif alignment == '=':
                title = title[:width].center(width)
            else:
                title = title[:width].ljust(width)

            header_row.append(
                style(
                    title,
                    bg=colors.header.bg,
                    fg=colors.header.fg,
                ))

        header_row = title_bar.join(header_row)

        click.echo(header_row)

        # BODY ROWS -----------------------------------------

        # key has the s, r, q, m removed to not interfere with the
        # ask_user options.  This list can have anything, as long as
        # they are single characters.  This is aprox 90 characters.
        key = """abcdefghijklnoptuvwxyzABCDEFGHIJKLMNOPQRSTUVW"""
        key += """XYZ0123456789!#$%&()*+-./:;<=>?@[\\]^_`{|}"'~"""
        key = list(key)

        self.table.body = self.table.body[:self.display_count]
        for row, counter in zip(self.table.body, key):
            # look through the title cell to see if any have 720 or
            # 1080 in the string and mark this row as high def.
            is_hidef = False
            if '720p' in row[0] or '1080p' in row[0]:
                is_hidef = True

            row_arr = [counter]
            for i, width, align in zip(row, header.widths, header.alignments):
                i = str(i)
                if width == 0:
                    width = flex_width
                row_item = i
                row_item = U.snip(row_item, width)
                row_item = row_item.strip()

                if align == '<':
                    row_item = row_item.ljust(width)
                if align == '>':
                    row_item = row_item.rjust(width)
                if align == '=':
                    row_item = row_item.center(width)
                else:
                    row_item = row_item.ljust(width)

                # if hi def, set the foreground to green
                if is_hidef:
                    row_item = style(row_item, fg=colors.hidef.fg)

                row_arr.append(row_item)
            click.echo(bar.join(row_arr))

        # USER INPUT ---------------------------------------
        choice = False
        while not choice:
            if self.is_postdownload:
                choice = self.ask_simple(key)
            else:
                if self.nondb:
                    choice = self.ask_simple(key)
                else:
                    choice = self.ask(key)
        return choice
Пример #25
0
    def search(self,
               search_string,
               season=False,
               episode=False,
               search_type='torrent'):
        """
        Return an array of values:

        [
          [
            ['Title string', 'search url'],
            [head1, head2, head3, id],
            [head1-width, head2-width, head3-width],
            [head1-alignment, head2-alignment, head3-alignment]
          ],
          [
            [<column 1 data>, <column 2 data>, <column 3 data>, <id>],
            [<column 1 data>, <column 2 data>, <column 3 data>, <id>],
            # etc...
          ]
        ]
        """

        self.season = season
        self.episode = episode
        self.show_name = search_string
        self.search_type = search_type

        click.echo()

        if self.search_type == 'torrent':
            header = [[search_string, ''],
                      ['Name', 'Size', 'Date', 'Seeds', 'SE'],
                      [0, 10, 12, 6, 2], ['<', '>', '<', '>', '<']]
        else:
            header = [[search_string, ''], ['Name', 'Size', 'Date', 'SE'],
                      [0, 10, 12, 2], ['<', '>', '<', '<']]

        if self.search_type == 'torrent':
            engines = self.torrent_engines
        elif self.search_type == 'nzb':
            engines = self.newsgroup_engines
        else:
            raise ValueError('search_type can only be "torrent" or "nzb"')

        # socket.setdefaulttimeout(3.05)
        socket.setdefaulttimeout(1)
        episodes = []
        with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
            res = {
                executor.submit(self.job, engine, search_string, season,
                                episode): engine
                for engine in engines
            }
            with click.progressbar(
                    concurrent.futures.as_completed(res),
                    label=U.style('  %s' % search_string, bold=True),
                    empty_char=style(Config.pb.empty_char,
                                     fg=Config.pb.dark,
                                     bg=Config.pb.dark),
                    fill_char=style(Config.pb.fill_char,
                                    fg=Config.pb.light,
                                    bg=Config.pb.light),
                    length=len(engines),
                    show_percent=False,
                    show_eta=False,
                    item_show_func=self.progress_title,
                    width=Config.pb.width,
                    bar_template=Config.pb.template,
                    show_pos=True,
            ) as bar:
                for future in bar:
                    results = future.result()
                    # remove the search engine name from the end of
                    # the results array that was added in self.job()
                    # so progress_title() could make use of it.
                    results = results[:-1]
                    episodes = episodes + results

        # go up 3 lines to remove the progress bar
        click.echo('[%sA' % 3)

        if self.search_type == 'torrent':
            self.sort_torrents(episodes)

        # return search_results
        return [header] + [episodes]
Пример #26
0
def calendar(show_name, show_all, sort_by_next, no_color, days):
    """Display a calendar of upcoming episodes.

    If SHOW_NAME is used, it will display a calendar for any shows that
    match that string.

    --days can be one number or two numbers.  If one number is used, it will
    show that many days ahead.  If two numbers are used, the first number is
    where the calendar will start and the second number is where it will end.
    The two number must be seperated by a comma with no spaces.

    \b
    --days 10      will show from today to 10 days in the future
    --days 10,20   will start ten days from now and then show 20 days ahead.
    --days -20,10  will go back 20 days from today and then show ahead from there.
    """
    if no_color:
        use_color = False
    else:
        use_color = True

    # set colors for ui elements
    if Config.is_win:
        foreground = 'white'
        header_color = 'blue'
        date_color_1 = 'blue'
        date_color_2 = 0
        title_color_1 = 'blue'
        title_color_2 = 0
    else:
        foreground = 225
        header_color = 17
        date_color_1 = 17
        date_color_2 = 0
        title_color_1 = 18
        title_color_2 = 0

    title_width = 20  # width of show titles column
    spacer = ' '  # can be any string, any length
    today = datetime.datetime.today()

    if days:
        days = days.split(',')
        days = [int(x) for x in days]
        if len(days) == 2:
            today = today + datetime.timedelta(days=days[0])
            calendar_columns = days[1]
        if len(days) == 1:
            calendar_columns = days[0]
    else:
        calendar_columns = Config.console_columns - (title_width + len(spacer))

    # Days_chars can be any string of seven chars. eg: 'mtwtfSS'
    days_chars = '.....::'  # first char is monday
    monthstart = '|'  # marker used to indicate the begining of month

    # build date title row
    months_row = today.strftime('%b') + (' ' * calendar_columns)
    days_row = ''
    daybefore = today - datetime.timedelta(days=1)
    for days in range(calendar_columns):
        cur_date = today + datetime.timedelta(days=days)

        if cur_date.month != daybefore.month:
            days_row += monthstart
            month = cur_date.strftime('%b')
            month_len = len(month)
            months_row = months_row[:days] + month + months_row[(days +
                                                                 month_len):]
        else:
            days_row += days_chars[cur_date.weekday()]

        daybefore = cur_date

    months_row = months_row[:
                            calendar_columns]  # chop off any extra spaces created by adding the months
    if use_color:
        months_row = style(months_row, fg=foreground, bg=header_color)
        days_row = style(days_row, fg=foreground, bg=header_color)
    months_row = (' ' * title_width) + (' ' * len(spacer)) + months_row
    days_row = (' ' * title_width) + (' ' * len(spacer)) + days_row
    click.echo(months_row)
    click.echo(days_row)

    # build shows rows
    step = 3
    color_row = False
    counter = 1
    season_marker = '-'
    filter_date = ''
    filter_name = ''
    if sort_by_next:
        filter_date = True
    if show_name:
        filter_name = show_name

    show = Shows(name_filter=filter_name, by_date=filter_date)
    for show in show:
        broadcast_row = ''
        title = show.db_name[:title_width].ljust(title_width)
        has_episode = False
        first_display_date = True
        last_days_away = 0
        last_date = 0
        for i in show.series:  # season
            for j in show.series[i]:  # episode
                episode_number = show.series[i][j]['episodenumber']
                b_date = show.series[i][j]['firstaired']
                if not b_date:
                    continue  # some episode have no broadcast date?
                split_date = b_date.split('-')
                broadcast_date = datetime.datetime(int(split_date[0]),
                                                   int(split_date[1]),
                                                   int(split_date[2]))
                if broadcast_date == last_date:
                    continue  # sometimes multiple episodes have the same date, don't repeat them.
                last_date = broadcast_date
                if broadcast_date.date() < today.date():
                    continue  # don't include episodes before today
                days_away = (broadcast_date - today).days + 1
                if days_away >= calendar_columns:
                    continue  # don't include days after the width of the screen
                if show.series[i][j]['seasonnumber'] == '0':
                    continue  # not interested in season 0 episodes.

                if first_display_date:
                    if int(episode_number) > 1:
                        before_first = season_marker * days_away
                    else:
                        before_first = ' ' * days_away
                    broadcast_row = before_first + episode_number
                    first_display_date = False
                    # set the next episode date in the db while we're here:
                    show.set_next_episode(broadcast_date.date())
                else:
                    episode_char_len = len(str(int(episode_number) - 1))
                    broadcast_row = broadcast_row + (
                        season_marker * (days_away - last_days_away -
                                         episode_char_len)) + episode_number

                last_days_away = days_away

                has_episode = True

        broadcast_row = broadcast_row[:calendar_columns].ljust(
            calendar_columns)

        if has_episode or show_all:
            if use_color and color_row:
                title = style(title, fg=foreground, bg=title_color_1)
                broadcast_row = style(broadcast_row,
                                      fg=foreground,
                                      bg=date_color_1)
            elif use_color and not color_row:
                title = style(title, fg=foreground, bg=title_color_2)
                broadcast_row = style(broadcast_row,
                                      fg=foreground,
                                      bg=date_color_2)
            row = title + spacer + broadcast_row
            click.echo(row)

            if counter >= step:
                counter = 0
                color_row = True
            else:
                color_row = False
                counter += 1