Exemple #1
0
    def check_subtitle_cpl_max_elements(self, playlist, asset, folder):
        """ Maximum number of subtitle Text or Image elements.

            A subtitle instance shall contain no more than six (6) Text
            elements or three (3) Image elements.

            Reference :
                SMPTE 429-2-2013 8.4.4
        """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        subtitles = keys_by_name_dict(st_dict, 'Subtitle')
        if not subtitles:
            return

        for idx, st in enumerate(subtitles[0]):
            text_count = len(keys_by_name_dict(st, 'Text'))
            if text_count > 6:
                self.error(
                    "Too many Text elements ({}) for subtitle {}".format(
                        text_count, st['Subtitle@SpotNumber']))
            img_count = len(keys_by_name_dict(st, 'Image'))
            if img_count > 6:
                self.error(
                    "Too many Image elements ({}) for subtitle {}".format(
                        img_count, st['Subtitle@SpotNumber']))
Exemple #2
0
    def check_subtitle_cpl_loadfont(self, playlist, asset, folder):
        """ Text subtitle must contains one and only one LoadFont element.

            As specified in SMPTE 429-2 8.4.1, only exception is PNG based
            subtitles. SMPTE 428-7-2014 5.11.1 also specify that the LoadFont ID
            attribute shall be a string of one or more character. This is
            not enforced at the XSD schema level so we explicitly check it
            here.

            On the other hand, Interop spec doesn't have strict requirement
            on the presence of LoadFont tag.

            References:
                TI Subtitle Specification for DLP Cinema (v1.1) 2.7
                https://web.archive.org/web/20140924175755/http://dlp.com/downloads/pdf_dlp_cinema_CineCanvas_Rev_C.pdf
                SMPTE ST 428-7:2014 5.11.1
                SMPTE ST 429-2:2013 8.4.1
        """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return
        if self.dcp.schema == 'Interop':
            return

        loadfont_attribute = "LoadFont@ID"
        text_elems = keys_by_name_dict(st_dict, 'Text')
        loadfont_elems = keys_by_name_dict(st_dict, loadfont_attribute)
        if text_elems and len(loadfont_elems) != 1:
            self.error("Text based subtitle shall contain one and only one "
                       "LoadFont element, found {}".format(
                           len(loadfont_elems)))
        if text_elems and not loadfont_elems[0]:
            self.error("LoadFont element with an empty ID attribute")
Exemple #3
0
    def check_subtitle_cpl_loadfont(self, playlist, asset, folder):
        """ Text subtitle must contains one and only one LoadFont element.

            As specified in SMPTE 429-2 8.4.1, only exception is PNG based
            subtitles. SMPTE 428-7-2014 5.11.1 also specify that the LoadFont ID
            attribute shall be a string of one or more character. This is
            not enforced at the XSD schema level so we explicitly check it
            here.

            Reference :
                SMPTE ST 428-7-2014 5.11.1
                SMPTE ST 429-2-2013 8.4.1

        """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        if self.dcp.schema == 'SMPTE':
            loadfont_attribute = "LoadFont@ID"
        else:
            loadfont_attribute = "LoadFont@Id"  # Interop

        text_elems = keys_by_name_dict(st_dict, 'Text')
        loadfont_elems = keys_by_name_dict(st_dict, loadfont_attribute)
        if text_elems and len(loadfont_elems) != 1:
            raise CheckException(
                "Text based subtitle shall contain one and only one "
                "LoadFont element, found {}".format(len(loadfont_elems)))
        if text_elems and not loadfont_elems[0]:
            raise CheckException("LoadFont element with an empty ID attribute")
    def check_subtitle_cpl_content(self, playlist, asset, folder):
        """ Subtitle individual structure check. """
        st_dict = self.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        subtitles = keys_by_name_dict(st_dict, 'Subtitle')
        for st in subtitles[0]:
            has_image = keys_by_name_dict(st, 'Image')
            has_text = keys_by_name_dict(st, 'Text')
            if not has_image and not has_text:
                raise CheckException(
                    "Subtitle {} element must define one Text or Image"
                    "".format(st['Subtitle@SpotNumber']))
Exemple #5
0
    def check_subtitle_cpl_position(self, playlist, asset, folder):
        """ Subtitles vertical position (out of screen) check.

            VAlign="top", VPosition="0" : out of the top of the screen
            VAlign="bottom", VPosition="0" : some char like 'g' will be cut

            References:
                TI Subtitle Specification for DLP Cinema (v1.1) 2.10
                https://web.archive.org/web/20140924175755/http://dlp.com/downloads/pdf_dlp_cinema_CineCanvas_Rev_C.pdf
                SMPTE ST 428-7:2014 6.2.4
        """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        subs = keys_by_name_dict(st_dict, 'Subtitle')
        flat_subs = [item for sublist in subs for item in sublist]

        for st in flat_subs:
            st_idx = st['Subtitle@SpotNumber']
            valign = keys_by_pattern_dict(st, ['@VAlign'])
            vpos = keys_by_pattern_dict(st, ['@VPosition'])

            for a, p in zip(valign, vpos):
                if a == 'top' and p == 0:
                    self.error(
                        "Subtitle {} is out of screen (top)".format(st_idx))
                if a == 'bottom' and p == 0:
                    self.error(
                        "Subtitle {} is nearly out of screen (bottom), some "
                        "characters will be cut".format(st_idx))
    def check_subtitle_cpl_st_timing(self, playlist, asset, folder):
        """ Subtitle individual duration / fade time check. """
        st_dict = self.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        subtitles = keys_by_name_dict(st_dict, 'Subtitle')
        editrate = self.get_subtitle_editrate(asset, st_dict)

        for st in subtitles[0]:
            st_idx = st['Subtitle@SpotNumber']
            st_in, st_out = st['Subtitle@TimeIn'], st['Subtitle@TimeOut']
            dur = (self.st_tc_frames(st_out, editrate) -
                   self.st_tc_frames(st_in, editrate))

            if dur <= 0:
                raise CheckException(
                    "Subtitle {} null or negative duration".format(st_idx))

            f_s, f_d = self.get_subtitle_fade_io(st, editrate)
            if f_s and f_s > dur:
                raise CheckException(
                    "Subtitle {} FadeUpTime longer than duration".format(
                        st_idx))
            if f_d and f_d > dur:
                raise CheckException(
                    "Subtitle {} FadeDownTime longer than duration".format(
                        st_idx))
    def check_subtitle_cpl_duration(self, playlist, asset, folder):
        """ Subtitle duration coherence with CPL. """
        st_dict = self.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        st_rate = self.get_subtitle_editrate(asset, st_dict)
        subtitles = keys_by_name_dict(st_dict, 'Subtitle')
        _, asset = asset

        last_tc = 0
        for st in subtitles[0]:
            st_out = self.st_tc_frames(st['Subtitle@TimeOut'], st_rate)
            if st_out > last_tc:
                last_tc = st_out

        cpl_rate = asset['EditRate']
        cpl_dur = asset['Duration']
        ratio_editrate = st_rate / cpl_rate
        last_tc_st = last_tc / ratio_editrate

        if last_tc_st > cpl_dur:
            reel_cpl = get_reel_for_asset(playlist, asset['Id'])['Position']
            raise CheckException(
                "Subtitle exceed track duration. Subtitle {} - Track {} "
                "- Reel {}".format(frame_to_tc(last_tc_st, cpl_rate),
                                   frame_to_tc(cpl_dur, cpl_rate), reel_cpl))
    def check_subtitle_cpl_position(self, playlist, asset, folder):
        """ Subtitles vertical position (out of screen) check.

            VAlign="top", VPosition="0" : out of the top of the screen
            VAlign="bottom", VPosition="0" : some char like 'g' will be cut
        """
        st_dict = self.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        subs = keys_by_name_dict(st_dict, 'Subtitle')
        flat_subs = [item for sublist in subs for item in sublist]

        for st in flat_subs:
            st_idx = st['Subtitle@SpotNumber']
            valign = keys_by_pattern_dict(st, ['@VAlign'])
            vpos = keys_by_pattern_dict(st, ['@VPosition'])

            for a, p in zip(valign, vpos):
                if a == 'top' and p == 0:
                    raise CheckException(
                        "Subtitle {} is out of screen (top)".format(st_idx))
                if a == 'bottom' and p == 0:
                    raise CheckException(
                        "Subtitle {} is nearly out of screen (bottom), some "
                        "characters will be cut".format(st_idx))
Exemple #9
0
    def check_subtitle_cpl_first_tt_event(self, playlist, asset, folder):
        """ First Timed Text Event of Composition TimeIn greater than 4s.

            The composition's first Timed Text event's TimeIn attribute
            should be greater than or equal to 4 seconds.

            Reference:
                SMPTE RDD 52:2020 7.2.4
        """
        reel_cpl = get_reel_for_asset(playlist, asset[1]['Id'])['Position']
        first_reel_of_st = get_first_reel_for_asset_type(playlist, 'Subtitle')
        # We are probably checking a Caption track
        if not first_reel_of_st:
            return None
        first_reel_of_st = first_reel_of_st['Position']
        if not first_reel_of_st or reel_cpl != first_reel_of_st:
            return

        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        subtitles = keys_by_name_dict(st_dict, 'Subtitle')
        if not subtitles:
            return

        st_editrate = self.st_util.get_subtitle_editrate(asset, st_dict)
        first_tc = subtitles[0][0]['Subtitle@TimeIn']
        first_tc_frames = self.st_util.st_tc_frames(first_tc, st_editrate)

        if (first_tc_frames < 4 * st_editrate):
            self.error("First Timed Text event of CPL happens "
                       "earlier than 4 seconds: {}".format(first_tc))
Exemple #10
0
    def check_subtitle_cpl_loadfont(self, playlist, asset, folder):
        """ Text subtitle must contains one and only one LoadFont element.

            As specified in SMPTE 429-2 8.4.1, only exception is PNG based
            subtitles.
        """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        if self.dcp.schema == 'SMPTE':
            text_elems = keys_by_name_dict(st_dict, 'Text')
            loadfont_elems = keys_by_name_dict(st_dict, 'LoadFont@ID')
            if text_elems and len(loadfont_elems) != 1:
                raise CheckException(
                    "Text based subtitle shall contain one and only one "
                    "LoadFont element, found {}".format(len(loadfont_elems)))
    def check_subtitle_cpl_font_ref(self, playlist, asset, folder):
        """ Subtitle font references check. """
        st_dict = self.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        if self.dcp.schema == 'SMPTE':
            font_id = self.get_subtitle_elem(st_dict, 'LoadFont@ID')
            font_ref = keys_by_name_dict(st_dict, 'Font@ID')
        else:
            font_id = self.get_subtitle_elem(st_dict, 'LoadFont@Id')
            font_ref = keys_by_name_dict(st_dict, 'Font@Id')

        for ref in font_ref:
            if ref != font_id:
                raise CheckException(
                    "Subtitle reference unknown font {} (loaded {})".format(
                        ref, font_id))
Exemple #12
0
    def check_subtitle_cpl_empty(self, playlist, asset, folder):
        """ Empty Subtitle file check. """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        subtitles = keys_by_name_dict(st_dict, 'Subtitle')
        if not subtitles:
            raise CheckException("Subtitle file is empty")
Exemple #13
0
    def check_subtitle_cpl_empty(self, playlist, asset, folder):
        """ Empty Subtitle file check.

            References: N/A
        """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        subtitles = keys_by_name_dict(st_dict, 'Subtitle')
        if not subtitles:
            self.error("Subtitle file is empty")
Exemple #14
0
    def check_subtitle_cpl_content(self, playlist, asset, folder):
        """ Subtitle individual structure check.

            Reference :
                Interop TI Subtitle Spec 1.1 2.9
                SMPTE 428-7-2014 6
        """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        subtitles = keys_by_name_dict(st_dict, 'Subtitle')
        if not subtitles:
            return

        for st in subtitles[0]:
            has_image = keys_by_name_dict(st, 'Image')
            has_text = keys_by_name_dict(st, 'Text')
            if not has_image and not has_text:
                raise CheckException(
                    "Subtitle {} element must define one Text or Image"
                    "".format(st['Subtitle@SpotNumber']))
Exemple #15
0
    def check_subtitle_cpl_content(self, playlist, asset, folder):
        """ Subtitle individual structure check.

            References:
                TI Subtitle Specification for DLP Cinema (v1.1) 2.9
                https://web.archive.org/web/20140924175755/http://dlp.com/downloads/pdf_dlp_cinema_CineCanvas_Rev_C.pdf
                SMPTE ST 428-7:2014 6
        """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        subtitles = keys_by_name_dict(st_dict, 'Subtitle')
        if not subtitles:
            return

        for st in subtitles[0]:
            has_image = keys_by_name_dict(st, 'Image')
            has_text = keys_by_name_dict(st, 'Text')
            if not has_image and not has_text:
                self.error(
                    "Subtitle {} element must define one Text or Image".format(
                        st['Subtitle@SpotNumber']))
    def check_subtitle_cpl_image(self, playlist, asset, folder):
        """ Subtitle image element must reference a valid PNG file. """
        st_dict = self.get_subtitle_xml(asset, folder)
        if not st_dict:
            return
        # TODO : Implement the test for SMPTE
        if self.dcp.schema != 'Interop':
            return

        imgs = keys_by_name_dict(st_dict, 'Image')
        for img in imgs:
            if not os.path.exists(os.path.join(folder, img)):
                raise CheckException(
                    "Subtitle image reference {} not found in folder {}"
                    "".format(img, os.path.relpath(folder, self.dcp.path)))
Exemple #17
0
    def check_subtitle_cpl_font_ref(self, playlist, asset, folder):
        """ Subtitle font references check.

            References:
                TI Subtitle Specification for DLP Cinema (v1.1) 2.7
                https://web.archive.org/web/20140924175755/http://dlp.com/downloads/pdf_dlp_cinema_CineCanvas_Rev_C.pdf
                SMPTE ST 428-7:2014 5.11.1
        """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        if self.dcp.schema == 'SMPTE':
            font_id = self.st_util.get_subtitle_elem(st_dict, 'LoadFont@ID')
            font_ref = keys_by_name_dict(st_dict, 'Font@ID')
        else:
            font_id = self.st_util.get_subtitle_elem(st_dict, 'LoadFont@Id')
            font_ref = keys_by_name_dict(st_dict, 'Font@Id')

        for ref in font_ref:
            if ref != font_id:
                self.error(
                    "Subtitle reference unknown font {} (loaded {})".format(
                        ref, font_id))
Exemple #18
0
    def check_subtitle_cpl_concurrent_visibility(self, playlist, asset,
                                                 folder):
        """ Maximum number of subtitle visible on screen at once.

            Up to two (2) subtitle instances may be visible on screen
            at any time. The visibility period of an instance shall include
            fade-in and fade-out times.

            Reference :
                SMPTE 429-2-2013 8.4.4
        """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        subtitles = keys_by_name_dict(st_dict, 'Subtitle')
        editrate = self.st_util.get_subtitle_editrate(asset, st_dict)
        if not subtitles:
            return

        st_list = []
        for idx, st in enumerate(subtitles[0]):
            st_in, st_out = st['Subtitle@TimeIn'], st['Subtitle@TimeOut']
            st_list.append((idx, self.st_util.st_tc_frames(st_in,
                                                           editrate), 0))
            st_list.append((idx, self.st_util.st_tc_frames(st_out,
                                                           editrate), 1))
        st_list = sorted(st_list, key=lambda x: (x[1], x[0], x[2]))

        vis_list = []
        current_vis = 0
        for idx, st in enumerate(st_list):
            current_vis += 1 if st[2] == 0 else -1
            vis_list.append(current_vis)

        for idx, v in enumerate(vis_list):
            if v > 2:
                st = subtitles[0][st_list[idx][0]]
                st_in, st_out = st['Subtitle@TimeIn'], st['Subtitle@TimeOut']
                self.error(
                    "Too many subtitles ({}) visible at once between {} and {}"
                    .format(v, st_in, st_out))
Exemple #19
0
    def check_subtitle_cpl_image(self, playlist, asset, folder):
        """ Subtitle image element must reference a valid PNG file.

            References:
                TI Subtitle Specification for DLP Cinema (v1.1) 2.17
                https://web.archive.org/web/20140924175755/http://dlp.com/downloads/pdf_dlp_cinema_CineCanvas_Rev_C.pdf
        """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return
        # TODO : Implement the test for SMPTE
        if self.dcp.schema != 'Interop':
            return

        imgs = keys_by_name_dict(st_dict, 'Image')
        for img in imgs:
            if not os.path.exists(os.path.join(folder, img)):
                self.error("Subtitle image reference {} not found in folder {}"
                           "".format(img,
                                     os.path.relpath(folder, self.dcp.path)))
Exemple #20
0
    def check_subtitle_cpl_font_glyph(self, playlist, asset, folder):
        """ Check for missing font glyphs.

            Reference : N/A
        """
        st_dict = self.st_util.get_subtitle_xml(asset, folder)
        if not st_dict:
            return

        subtitles = keys_by_name_dict(st_dict, 'Subtitle')
        if not subtitles:
            return

        # See SMPTE ST 428-7-2014 sections 6.3 and 6.4 for possible
        # Subtitle Text and Font hierarchy. Note that here we just
        # recursively iterate to extract all relevant childs whitout
        # checking if the specific hierarchy is valid or not.
        all_text = self.st_util.extract_subtitle_text(subtitles[0])
        unique_chars = set()
        for text in all_text:
            for char in text:
                unique_chars.add(char)

        path, uri = self.st_util.get_font_path(st_dict, folder)
        if not path:
            return
        if not os.path.exists(path):
            return

        face = freetype.Face(path)
        font_chars = [six.unichr(c) for c, n in face.get_chars()]

        missing_glyphs = []
        for char in unique_chars:
            if char not in font_chars:
                missing_glyphs.append(char)

        if missing_glyphs:
            raise CheckException(
                "Font ({}) is missing required glyphs : {}".format(
                    os.path.basename(path), ", ".join(missing_glyphs)))