Example #1
0
    def test_method_calc_points_no_point_structure(self):
        driver_name = 'Kobernulf_Monnur'
        position = 1
        driver_best_lap = 42.0
        race_best_lap = 11.0

        mock_driver = MagicMock(spec=Driver)
        type(mock_driver).laps_complete = PropertyMock(return_value=6)
        type(mock_driver).race_time = PropertyMock(return_value=42.0)
        type(mock_driver).stops = PropertyMock(return_value=0)

        mock_classification_entry = MagicMock(spec=ClassificationEntry)
        type(mock_classification_entry).driver = PropertyMock(
            return_value=mock_driver)
        type(mock_classification_entry).driver_name = PropertyMock(
            return_value=driver_name)
        type(mock_classification_entry).position = PropertyMock(
            return_value=position)
        type(mock_classification_entry).best_lap = PropertyMock(
            return_value=driver_best_lap)

        mock_starting_grid_entry = MagicMock(spec=StartingGridEntry)
        type(mock_starting_grid_entry).driver_index = PropertyMock(
            return_value=0)
        type(mock_starting_grid_entry).driver_name = PropertyMock(
            return_value=driver_name)
        type(mock_starting_grid_entry).position = PropertyMock(return_value=2)

        instance = RaceResultsWithChange([mock_classification_entry],
                                         [mock_starting_grid_entry])
        expected_result = '0'
        self.assertEqual(
            instance.calc_points((driver_name, position, race_best_lap)),
            expected_result)
Example #2
0
    def test_method_calc_points_best_lap(self):
        driver_name = 'Kobernulf_Monnur'
        position = 1
        best_lap = 42.0

        mock_driver = MagicMock(spec=Driver)
        type(mock_driver).laps_complete = PropertyMock(return_value=6)
        type(mock_driver).race_time = PropertyMock(return_value=42.0)
        type(mock_driver).stops = PropertyMock(return_value=0)

        mock_classification_entry = MagicMock(spec=ClassificationEntry)
        type(mock_classification_entry).driver = PropertyMock(
            return_value=mock_driver)
        type(mock_classification_entry).driver_name = PropertyMock(
            return_value='Kobernulf Monnur')
        type(mock_classification_entry).position = PropertyMock(return_value=1)
        type(mock_classification_entry).best_lap = PropertyMock(
            return_value=best_lap)

        mock_starting_grid_entry = MagicMock(spec=StartingGridEntry)
        type(mock_starting_grid_entry).driver_index = PropertyMock(
            return_value=0)
        type(mock_starting_grid_entry).driver_name = PropertyMock(
            return_value='Kobernulf Monnur')
        type(mock_starting_grid_entry).position = PropertyMock(return_value=2)

        configuration = {'point_structure': [5, 15, 12, 10, 8, 6, 4, 2, 1]}

        instance = RaceResultsWithChange([mock_classification_entry],
                                         [mock_starting_grid_entry],
                                         **configuration)
        expected_result = '20'
        self.assertEqual(
            instance.calc_points((driver_name, position, best_lap),
                                 **configuration), expected_result)
Example #3
0
    def test_method_to_frame_with_changes(self):
        mock_driver_1 = MagicMock(spec=Driver)
        type(mock_driver_1).laps_complete = PropertyMock(return_value=6)
        type(mock_driver_1).race_time = PropertyMock(return_value=42.0)
        type(mock_driver_1).stops = PropertyMock(return_value=0)

        mock_driver_2 = MagicMock(spec=Driver)
        type(mock_driver_2).laps_complete = PropertyMock(return_value=6)
        type(mock_driver_2).race_time = PropertyMock(return_value=45.0)
        type(mock_driver_2).stops = PropertyMock(return_value=0)

        mock_classification_entry_1 = MagicMock(spec=ClassificationEntry)
        type(mock_classification_entry_1).driver = PropertyMock(
            return_value=mock_driver_1)
        type(mock_classification_entry_1).driver_name = PropertyMock(
            return_value='Kobernulf Monnur')
        type(mock_classification_entry_1).position = PropertyMock(
            return_value=1)

        mock_classification_entry_2 = MagicMock(spec=ClassificationEntry)
        type(mock_classification_entry_2).driver = PropertyMock(
            return_value=mock_driver_2)
        type(mock_classification_entry_2).driver_name = PropertyMock(
            return_value='Testy McTest')
        type(mock_classification_entry_2).position = PropertyMock(
            return_value=2)

        mock_starting_grid_entry_1 = MagicMock(spec=StartingGridEntry)
        type(mock_starting_grid_entry_1).driver_index = PropertyMock(
            return_value=0)
        type(mock_starting_grid_entry_1).driver_name = PropertyMock(
            return_value='Kobernulf Monnur')
        type(mock_starting_grid_entry_1).position = PropertyMock(
            return_value=2)

        mock_starting_grid_entry_2 = MagicMock(spec=StartingGridEntry)
        type(mock_starting_grid_entry_2).driver_index = PropertyMock(
            return_value=1)
        type(mock_starting_grid_entry_2).driver_name = PropertyMock(
            return_value='Testy McTest')
        type(mock_starting_grid_entry_2).position = PropertyMock(
            return_value=1)

        instance = RaceResultsWithChange(
            [mock_classification_entry_1, mock_classification_entry_2],
            [mock_starting_grid_entry_2, mock_starting_grid_entry_1])
        expected_result = numpy.ndarray
        self.assertIsInstance(instance.to_frame(), expected_result)
Example #4
0
    def test_init_config(self):
        configuration = {
            'participant_config': {
                'Kobernulf Monnur': {
                    'display': 'Senor Pez',
                    'car': '125cc Shifter Kart',
                    'team': 'DarkNitro',
                }
            },
            'point_structure': [5, 15, 12, 10, 8, 6, 4, 2, 1]
        }
        mock_driver = MagicMock(spec=Driver)
        type(mock_driver).laps_complete = PropertyMock(return_value=6)
        type(mock_driver).race_time = PropertyMock(return_value=42.0)
        type(mock_driver).stops = PropertyMock(return_value=0)

        mock_classification_entry = MagicMock(spec=ClassificationEntry)
        type(mock_classification_entry).driver = PropertyMock(
            return_value=mock_driver)
        type(mock_classification_entry).driver_name = PropertyMock(
            return_value='Kobernulf Monnur')
        type(mock_classification_entry).position = PropertyMock(return_value=1)

        mock_starting_grid_entry = MagicMock(spec=StartingGridEntry)
        type(mock_starting_grid_entry).driver_index = PropertyMock(
            return_value=0)
        type(mock_starting_grid_entry).driver_name = PropertyMock(
            return_value='Kobernulf Monnur')
        type(mock_starting_grid_entry).position = PropertyMock(return_value=2)

        instance = RaceResultsWithChange([mock_classification_entry],
                                         [mock_starting_grid_entry],
                                         **configuration)
        expected_result = RaceResultsWithChange
        self.assertIsInstance(instance, expected_result)
Example #5
0
    def test_init_no_config(self):
        mock_driver = MagicMock(spec=Driver)
        type(mock_driver).laps_complete = PropertyMock(return_value=6)
        type(mock_driver).race_time = PropertyMock(return_value=42.0)
        type(mock_driver).stops = PropertyMock(return_value=0)

        mock_classification_entry = MagicMock(spec=ClassificationEntry)
        type(mock_classification_entry).driver = PropertyMock(
            return_value=mock_driver)
        type(mock_classification_entry).driver_name = PropertyMock(
            return_value='Kobernulf Monnur')
        type(mock_classification_entry).position = PropertyMock(return_value=1)

        mock_starting_grid_entry = MagicMock(spec=StartingGridEntry)
        type(mock_starting_grid_entry).driver_index = PropertyMock(
            return_value=0)
        type(mock_starting_grid_entry).driver_name = PropertyMock(
            return_value='Kobernulf Monnur')
        type(mock_starting_grid_entry).position = PropertyMock(return_value=2)

        instance = RaceResultsWithChange([mock_classification_entry],
                                         [mock_starting_grid_entry])

        expected_result = RaceResultsWithChange
        self.assertIsInstance(instance, expected_result)
Example #6
0
    def test_property_row_colors(self):
        mock_driver = MagicMock(spec=Driver)
        type(mock_driver).laps_complete = PropertyMock(return_value=6)
        type(mock_driver).race_time = PropertyMock(return_value=42.0)
        type(mock_driver).stops = PropertyMock(return_value=0)

        mock_classification_entry = MagicMock(spec=ClassificationEntry)
        type(mock_classification_entry).driver = PropertyMock(
            return_value=mock_driver)
        type(mock_classification_entry).driver_name = PropertyMock(
            return_value='Kobernulf Monnur')
        type(mock_classification_entry).position = PropertyMock(return_value=1)

        mock_starting_grid_entry = MagicMock(spec=StartingGridEntry)
        type(mock_starting_grid_entry).driver_index = PropertyMock(
            return_value=0)
        type(mock_starting_grid_entry).driver_name = PropertyMock(
            return_value='Kobernulf Monnur')
        type(mock_starting_grid_entry).position = PropertyMock(return_value=2)

        instance = RaceResultsWithChange([mock_classification_entry],
                                         [mock_starting_grid_entry])
        expected_result = [(192, 192, 192, 255), (255, 255, 255, 255)]
        self.assertListEqual(instance.row_colors, expected_result)
def make_video(config_file):
    """
    Test of race data.
    """
    configuration = json.load(open(config_file))
    race_data = RaceData(configuration['source_telemetry'])
    result_data = RaceData(configuration['source_telemetry'])
    output_prefix = re.match(r'(.*)\.json$',
                             os.path.basename(config_file)).group(1)

    if os.environ.get('HEADINGFONTOVERRIDE') is not None:
        configuration['heading_font'] = \
            os.environ['HEADINGFONTOVERRIDE']
    if os.environ.get('DISPLAYFONTOVERRIDE') is not None:
        configuration['font'] = os.environ['DISPLAYFONTOVERRIDE']

    framerate = 30

    source_video = mpy.VideoFileClip(configuration['source_video']).subclip(
        configuration['video_skipstart'], configuration['video_skipend'])

    pcre_standings = GTStandings(race_data, ups=framerate, **configuration)
    standings_clip_mask = mpy.VideoClip(
        make_frame=pcre_standings.make_mask_frame, ismask=True)
    standings_clip = mpy.VideoClip(
        make_frame=pcre_standings.make_frame).set_mask(standings_clip_mask)

    try:
        if sys.argv[2] == "sync":

            def timecode_frame(time):
                """
                Custom make frame for timecode.
                """
                timecode_image = Image.new('RGB', (200, 50))
                draw = ImageDraw.Draw(timecode_image)
                draw.text((50, 25), "%.02f" % (time))
                return PIL_to_npimage(timecode_image)

            timecode_clip = mpy.VideoClip(
                timecode_frame, duration=source_video.duration).set_position(
                    ('center', 'top'))

            first_lap_data = RaceData(configuration['source_telemetry'])
            while first_lap_data.drivers_by_index[0].laps_complete < 1:
                _ = first_lap_data.get_data()

            main_event = mpy.CompositeVideoClip(
                [source_video, standings_clip,
                 timecode_clip]).set_duration(source_video.duration).subclip(
                     first_lap_data.elapsed_time - 5,
                     first_lap_data.elapsed_time + 5)
        else:
            raise IndexError

    except IndexError:
        main_event = mpy.CompositeVideoClip(
            [source_video, standings_clip]).set_duration(source_video.duration)

    pcre_starting_grid = StartingGrid(sorted(race_data.starting_grid,
                                             key=lambda x: x.position),
                                      size=source_video.size,
                                      **configuration)
    Image.fromarray(pcre_starting_grid.to_frame()).save(output_prefix +
                                                        "_starting_grid.png")
    starting_grid = mpy.ImageClip(
        pcre_starting_grid.to_frame()).set_duration(5)

    while True:
        try:
            result_data.get_data()
        except StopIteration:
            break

    pcre_results = RaceResultsWithChange(sorted(result_data.classification,
                                                key=lambda x: x.position),
                                         result_data.starting_grid,
                                         size=source_video.size,
                                         **configuration)
    Image.fromarray(pcre_results.to_frame()).save(output_prefix +
                                                  '_results.png')
    results = mpy.ImageClip(pcre_results.to_frame()).set_duration(20)

    if not any(
        [x['points'] for x in configuration['participant_config'].values()]):
        pcre_series_standings = SeriesStandings(result_data.classification,
                                                size=source_video.size,
                                                **configuration)
    else:
        pcre_series_standings = SeriesStandingsWithChange(
            result_data.classification,
            size=source_video.size,
            **configuration)
    Image.fromarray(
        pcre_series_standings.to_frame()).save(output_prefix +
                                               '_series_standings.png')
    series_standings = mpy.ImageClip(
        pcre_series_standings.to_frame()).set_duration(20)

    output = mpy.concatenate_videoclips([
        starting_grid.fadeout(1), main_event,
        results.fadein(1).fadeout(1),
        series_standings.fadein(1)
    ],
                                        method="compose")
    output.write_videofile(configuration['output_video'], fps=framerate)
Example #8
0
 def test_method_format_time_passed_string(self):
     time = "ERROR"
     expected_result = ""
     self.assertEqual(RaceResultsWithChange.format_time(time),
                      expected_result)
Example #9
0
 def test_method_format_time_passed_none(self):
     time = None
     expected_result = ""
     self.assertEqual(RaceResultsWithChange.format_time(time),
                      expected_result)
Example #10
0
 def test_method_format_time_round(self):
     time = 3702.9876
     expected_result = '1:01:42.988'
     self.assertEqual(RaceResultsWithChange.format_time(time),
                      expected_result)
Example #11
0
 def test_method_format_time_truncate(self):
     time = 3702.1234
     expected_result = '1:01:42.123'
     self.assertEqual(RaceResultsWithChange.format_time(time),
                      expected_result)
Example #12
0
 def test_method_format_time(self):
     time = 3702
     expected_result = '1:01:42.000'
     self.assertEqual(RaceResultsWithChange.format_time(time),
                      expected_result)
Example #13
0
 def test_method_format_time_below_hour_round(self):
     time = 84.9876
     expected_result = '1:24.988'
     self.assertEqual(RaceResultsWithChange.format_time(time),
                      expected_result)
Example #14
0
 def test_method_format_time_below_hour_truncate(self):
     time = 84.1234
     expected_result = '1:24.123'
     self.assertEqual(RaceResultsWithChange.format_time(time),
                      expected_result)
Example #15
0
 def test_method_format_time_below_min(self):
     time = 42
     expected_result = '42.000'
     self.assertEqual(RaceResultsWithChange.format_time(time),
                      expected_result)
def make_video(config_file, *, framerate=None, sync=False):
    configuration = json.load(open(config_file))
    try:
        race_data = RaceData(configuration['source_telemetry'])
        result_data = RaceData(configuration['source_telemetry'])
    except KeyError:
        sys.exit("Configuration Error: Source Telemetry not found.")

    try:
        output_prefix = os.path.splitext(configuration['output_video'])[0]
    except KeyError:
        sys.exit("Configuration Error: Output Video not found.")

    try:
        champion = configuration['show_champion']
    except KeyError:
        champion = False

    if os.environ.get('HEADINGFONTOVERRIDE') is not None:
        configuration['heading_font'] = \
            os.environ['HEADINGFONTOVERRIDE']
    if os.environ.get('DISPLAYFONTOVERRIDE') is not None:
        configuration['font'] = os.environ['DISPLAYFONTOVERRIDE']

    try:
        video_skipstart = configuration['video_skipstart']
    except KeyError:
        video_skipstart = 0

    try:
        video_skipend = configuration['video_skipend']
    except KeyError:
        video_skipend = None

    if 'source_video' in configuration \
            and configuration['source_video'] is not None:
        source_video = mpy.VideoFileClip(
            configuration['source_video']
        ).subclip(
            video_skipstart,
            video_skipend)
        if framerate is None:
            framerate = source_video.fps
    else:
        time_data = RaceData(configuration['source_telemetry'])
        with tqdm(desc="Detecting Telemetry Duration") as progress:
            while True:
                try:
                    _ = time_data.get_data()
                    progress.update()
                except StopIteration:
                    break
        source_video = mpy.ColorClip((1280, 1024)).set_duration(
            time_data.elapsed_time)

        if framerate is None:
            framerate = 30

    pcre_standings = GTStandings(
        race_data,
        ups=framerate,
        **configuration)
    standings_clip_mask = mpy.VideoClip(
        make_frame=pcre_standings.make_mask_frame,
        ismask=True)
    standings_clip = mpy.VideoClip(
        make_frame=pcre_standings.make_frame
    ).set_mask(standings_clip_mask)

    if sync:
        def timecode_frame(time):
            """
            Custom make frame for timecode.
            """
            timecode_image = Image.new('RGB', (100, 40))
            draw = ImageDraw.Draw(timecode_image)
            draw.text((10, 10), "%.02f" % time)
            return PIL_to_npimage(timecode_image)

        timecode_clip = mpy.VideoClip(
            timecode_frame,
            duration=source_video.duration
        ).set_position(('center', 'top'))

        first_lap_data = RaceData(configuration['source_telemetry'])
        with tqdm(desc="Detecting Video Start") as progress:
            while not any(
                    [x.laps_complete > 0
                     for x in first_lap_data.drivers.values()]):
                _ = first_lap_data.get_data()
                progress.update()

        start_time = first_lap_data.elapsed_time - 10
        end_time = None

        with tqdm(desc="Detecting Video End") as progress:
            while not all(
                    [x.laps_complete > 0
                     for x in first_lap_data.drivers.values()]):
                try:
                    _ = first_lap_data.get_data()
                    progress.update()
                except StopIteration:
                    end_time = start_time + 60
                    break

        if end_time is None:
            end_time = first_lap_data.elapsed_time + 10

        main_event = mpy.CompositeVideoClip(
            [source_video, standings_clip, timecode_clip]
        ).set_duration(
            source_video.duration
        ).subclip(start_time, end_time)

    else:
        main_event = mpy.CompositeVideoClip(
            [source_video, standings_clip]
        ).set_duration(source_video.duration)

    pcre_starting_grid = StartingGrid(
        sorted(race_data.starting_grid, key=lambda x: x.position),
        size=source_video.size,
        **configuration)
    Image.fromarray(pcre_starting_grid.to_frame()).save(
        output_prefix + "_starting_grid.png")
    starting_grid = mpy.ImageClip(
        pcre_starting_grid.to_frame()).set_duration(5)

    while True:
        try:
            result_data.get_data()
        except StopIteration:
            break

    end_titles = list()

    if 'car_classes' in configuration and len(configuration['car_classes']):
        no_points_config = copy(configuration)
        try:
            del no_points_config['point_structure']
        except KeyError:
            pass

        pcre_results = RaceResultsWithChange(
            sorted(result_data.all_driver_classification,
                   key=lambda x: x.position),
            result_data.starting_grid,
            size=source_video.size,
            **no_points_config)
        Image.fromarray(pcre_results.to_frame()).save(
            output_prefix + '_results.png')
        results = mpy.ImageClip(pcre_results.to_frame()).set_duration(20)

        end_titles.append(results)

        for car_class_filter in [
                car_class for car_class in configuration['car_classes']
                if car_class != ""]:
            class_cars = [
                car for car_class, car_class_data
                in configuration['car_classes'].items()
                if car_class == car_class_filter
                for car in car_class_data['cars']]
            class_drivers = [
                driver for driver, data
                in configuration['participant_config'].items()
                if data['car'] in class_cars]

            class_classification = [
                classification for classification
                in result_data.all_driver_classification
                if classification.driver_name in class_drivers]
            for position, classification in enumerate(sorted(
                    class_classification,
                    key=lambda x: x.position), 1):
                classification.position = position

            class_starting_grid = [
                grid for grid
                in result_data.starting_grid
                if grid.driver_name in class_drivers]
            for position, grid in enumerate(sorted(
                    class_starting_grid,
                    key=lambda x: x.position), 1):
                grid.position = position

            pcre_results = RaceResultsWithChange(
                sorted(class_classification, key=lambda x: x.position),
                class_starting_grid,
                size=source_video.size,
                **configuration)
            Image.fromarray(pcre_results.to_frame()).save(
                output_prefix + '_' + car_class_filter + '_results.png')
            results = mpy.ImageClip(pcre_results.to_frame()).set_duration(20)
            end_titles.append(results)
    else:
        pcre_results = RaceResultsWithChange(
            sorted(result_data.all_driver_classification,
                   key=lambda x: x.position),
            result_data.starting_grid,
            size=source_video.size,
            **configuration)
        Image.fromarray(pcre_results.to_frame()).save(
            output_prefix + '_results.png')
        results = mpy.ImageClip(pcre_results.to_frame()).set_duration(20)
        end_titles.append(results)

    try:
        if any(configuration['point_structure']):
            if not any([
                    x['points'] for x
                    in configuration['participant_config'].values()]):
                if 'car_classes' in configuration \
                        and len(configuration['car_classes']):
                    for car_class_filter in [car_class for car_class in
                                             configuration['car_classes'] if
                                             car_class != ""]:
                        class_cars = [
                            car for car_class, car_class_data
                            in configuration['car_classes'].items()
                            if car_class == car_class_filter
                            for car in car_class_data['cars']]
                        class_drivers = [
                            driver for driver, data
                            in configuration['participant_config'].items()
                            if data['car'] in class_cars]

                        class_classification = [
                            classification for classification
                            in result_data.all_driver_classification
                            if classification.driver_name in class_drivers]
                        for position, classification in enumerate(sorted(
                                class_classification,
                                key=lambda x: x.position), 1):
                            classification.position = position

                        pcre_series_standings = SeriesStandings(
                            class_classification,
                            size=source_video.size,
                            **configuration)
                        Image.fromarray(pcre_series_standings.to_frame()).save(
                            output_prefix
                            + '_'
                            + car_class_filter
                            + '_series_standings.png')
                        series_standings = mpy.ImageClip(
                            pcre_series_standings.to_frame()).set_duration(20)
                        end_titles.append(series_standings)
                else:
                    pcre_series_standings = SeriesStandings(
                        result_data.all_driver_classification,
                        size=source_video.size,
                        **configuration)
                    Image.fromarray(pcre_series_standings.to_frame()).save(
                        output_prefix + '_series_standings.png')
                    series_standings = mpy.ImageClip(
                        pcre_series_standings.to_frame()).set_duration(20)
                    end_titles.append(series_standings)
            else:
                if 'car_classes' in configuration \
                        and len(configuration['car_classes']):
                    for car_class_filter in [car_class for car_class in
                                             configuration['car_classes'] if
                                             car_class != ""]:
                        class_cars = [
                            car for car_class, car_class_data
                            in configuration['car_classes'].items()
                            if car_class == car_class_filter
                            for car in car_class_data['cars']]
                        class_drivers = [
                            driver for driver, data
                            in configuration['participant_config'].items()
                            if data['car'] in class_cars]

                        class_classification = [
                            classification for classification
                            in result_data.all_driver_classification
                            if classification.driver_name in class_drivers]
                        for position, classification in enumerate(sorted(
                                class_classification,
                                key=lambda x: x.position), 1):
                            classification.position = position

                        pcre_series_standings = SeriesStandingsWithChange(
                            class_classification,
                            size=source_video.size,
                            **configuration)
                        Image.fromarray(pcre_series_standings.to_frame()).save(
                            output_prefix
                            + '_'
                            + car_class_filter
                            + '_series_standings.png')
                        series_standings = mpy.ImageClip(
                            pcre_series_standings.to_frame()).set_duration(20)
                        end_titles.append(series_standings)

                else:
                    pcre_series_standings = SeriesStandingsWithChange(
                        result_data.all_driver_classification,
                        size=source_video.size,
                        **configuration)
                    Image.fromarray(pcre_series_standings.to_frame()).save(
                        output_prefix + '_series_standings.png')
                    series_standings = mpy.ImageClip(
                        pcre_series_standings.to_frame()).set_duration(20)
                    end_titles.append(series_standings)
    except KeyError:
        try:
            _ = configuration['point_structure']
            pcre_series_standings = SeriesStandings(
                result_data.all_driver_classification,
                size=source_video.size,
                **configuration)

            Image.fromarray(pcre_series_standings.to_frame()).save(
                output_prefix + '_series_standings.png')
            series_standings = mpy.ImageClip(
                pcre_series_standings.to_frame()).set_duration(20)

            end_titles.append(series_standings)
        except KeyError:
            pass

    if champion:
        if 'car_classes' in configuration and len(configuration['car_classes']):
            for car_class_filter in [car_class for car_class in
                                     configuration['car_classes'] if
                                     car_class != ""]:
                class_cars = [
                    car for car_class, car_class_data
                    in configuration['car_classes'].items()
                    if car_class == car_class_filter
                    for car in car_class_data['cars']]
                class_drivers = [
                    driver for driver, data
                    in configuration['participant_config'].items()
                    if data['car'] in class_cars]

                class_classification = [
                    classification for classification
                    in result_data.all_driver_classification
                    if classification.driver_name in class_drivers]
                for position, classification in enumerate(sorted(
                        class_classification,
                        key=lambda x: x.position), 1):
                    classification.position = position

                pcre_series_standings = SeriesChampion(
                    class_classification,
                    size=source_video.size,
                    **configuration)
                Image.fromarray(pcre_series_standings.to_frame()).save(
                    output_prefix
                    + '_'
                    + car_class_filter
                    + '_series_champion.png')
                series_standings = mpy.ImageClip(
                    pcre_series_standings.to_frame()).set_duration(20)
                end_titles.append(series_standings)
        else:
            pcre_series_champion = SeriesChampion(
                result_data.all_driver_classification,
                size=source_video.size,
                **configuration)
            Image.fromarray(pcre_series_champion.to_frame()).save(
                output_prefix + '_series_champion.png')
            series_champion = mpy.ImageClip(
                pcre_series_champion.to_frame()).set_duration(20)
            end_titles.append(series_champion)

    output = mpy.concatenate_videoclips(
        [starting_grid.fadeout(1), main_event]
        + [clip.fadein(1).fadeout(1) for clip in end_titles[:-1]]
        + [end_titles[-1].fadein(1)], method="compose")

    output.write_videofile(configuration['output_video'], fps=float(framerate))