Example #1
0
    def check(
        self, profile=DCP_CHECK_PROFILE, ov_path=None, hash_callback=None
    ):
        """ Check validity.

            Args:
                profile (dict): Checker profile.
                ov_path (str, optional): Absolute path of OriginalVersion DCP.

            Returns:
                Tuple (boolean, dict) of DCP check status and report. Report
                    is a Dictionary where check failure are grouped by
                    criticity.

        """
        if not self._parsed or not self._probeb:
            self.parse()

        self._checker = DCPChecker(
            self, profile=profile, ov_path=ov_path,
            hash_callback=hash_callback)
        return self._checker.check()
Example #2
0
class DCP(object):
    """ Digital Cinema Package abstraction. """
    def __init__(self, path):
        """ DCP constructor.

            Args:
                path (str): Absolute path to directory.

            Raises:
                ValueError: ``path`` directory not found.

        """

        if not os.path.isdir(path):
            raise ValueError("{} is not a valid folder".format(path))

        self.path = os.path.normpath(path)
        self.schema = 'Unknown'
        self.package_type = 'Unknown'
        self.foreign_files = []
        self.size = folder_size(path)
        self.log = get_log()

        self._probeb = False
        self._parsed = False

    def init_package_files(self):
        """ List all files present in DCP. """
        self._list_files = []
        for dirpath, dirnames, filenames in os.walk(self.path):
            for f in filenames:
                fullpath = os.path.join(dirpath, f)
                self._list_files.append(fullpath)

    def filter_files(self, filters):
        """ Build a list of package files matching specific names. """
        candidates = [os.path.join(self.path, c) for c in filters]
        return [f for f in self._list_files if f in candidates]

    def filter_xml_by_root(self, root_name):
        """ Build a list of package XML files having a specific root node. """
        xml_list = []
        candidates = [
            f for f in self._list_files if f.endswith('.xml')
            and not f.startswith('.') and os.path.dirname(f) == self.path
        ]

        for c in candidates:
            nodes = parse_xml(c, namespaces=DCP_SETTINGS['xmlns'])
            if root_name in nodes:
                xml_list.append(c)

        return xml_list

    def init_assetmap(self):
        """ Find DCP AssetMap and build Asset List. """
        self._list_am_path = self.filter_files(['ASSETMAP', 'ASSETMAP.xml'])
        self._list_am = [assetmap_parse(f) for f in self._list_am_path]

        # In the improbable case of multiple Assetmap found in the folder,
        # flatten asset list.
        self._list_asset = [{
            uuid: path
            for a in self._list_am for uuid, path, _ in list_am_assets(a)
        }]
        self._list_asset = {
            k: v
            for _list_asset in self._list_asset
            for k, v in six.iteritems(_list_asset)
        }

        # Schema (IOP or SMPTE) is assumed to be the one found for the Assetmap
        if self._list_am:
            self.schema = self._list_am[0]['Info']['AssetMap']['Schema']

    def init_volindex(self):
        """ Find DCP VolIndex. """
        self._list_vol_path = self.filter_files(['VOLINDEX', 'VOLINDEX.xml'])
        self._list_vol = [volindex_parse(f) for f in self._list_vol_path]

    def init_pkl(self):
        """ Find DCP PackingList. """
        self._list_pkl_path = self.filter_xml_by_root('PackingList')
        self._list_pkl = [pkl_parse(f) for f in self._list_pkl_path]

        self.pkl_find_path()

    def pkl_find_path(self):
        """ Find path for each PKL assets (using UUID and AssetMap). """
        for pkl in self._list_pkl:
            pkl_node = pkl['Info']['PackingList']
            for asset in pkl_node['AssetList']['Asset']:
                asset_id = asset['Id']
                if asset_id in self._list_asset:
                    asset['Path'] = os.path.join(self._list_asset[asset_id])

    def init_cpl(self):
        """ Find DCP CompositionPlayList. """
        self._list_cpl_path = self.filter_xml_by_root('CompositionPlaylist')
        self._list_cpl = [cpl_parse(f) for f in self._list_cpl_path]

        self.cpl_find_pkl()
        self.cpl_link_assets()

    def cpl_find_pkl(self):
        """ Find PKL that reference the CPL. """
        for cpl in self._list_cpl:
            cpl_node = cpl['Info']['CompositionPlaylist']
            cpl_id = cpl_node['Id']

            for pkl in self._list_pkl:
                assets = list(list_pkl_assets(pkl))
                uuids = [asset_id for asset_id, _, _ in assets]

                if cpl_id in uuids:
                    cpl_node['PKLId'] = pkl['Info']['PackingList']['Id']

    def cpl_link_assets(self):
        """ Link assets for each reel with actual files in the package. """
        # TODO : For Multi CPL package, what to choose if there are both
        # OV / VF?
        self.package_type = 'OV'

        for cpl in self._list_cpl:
            for _, asset in list_cpl_assets(cpl):
                asset_id = asset['Id']
                if asset_id in self._list_asset:
                    asset['Path'] = self._list_asset[asset_id]
                else:
                    self.package_type = 'VF'

    def cpl_probe_assets(self):
        """ Probe mxf assets for each reel. """
        for cpl in self._list_cpl:
            for essence, asset in list_cpl_assets(cpl):
                asset_path = os.path.join(self.path, asset.get('Path', ''))
                cpl_probe_asset(asset, essence, asset_path)

    def cpl_parse_metadata(self):
        """ Extract CPL common metadata. """
        for cpl in self._list_cpl:
            cpl_node = cpl['Info']['CompositionPlaylist']
            cpl_extract_characteristics(cpl_node)

    @property
    def list_assetmap(self):
        """ List of DCP AssetMap Dictionary. """
        return self._list_am

    @property
    def list_volindex(self):
        """ List of DCP VolIndex Dictionary. """
        return self._list_vol

    @property
    def list_pkl(self):
        """ List of DCP PackingList Dictionary. """
        return self._list_pkl

    @property
    def list_cpl(self):
        """ List of DCP CompositionPlayList Dictionary. """
        return self._list_cpl

    def parse(self, probe=True):
        """ Parse the DCP and Probe its assets. """
        if self._parsed:
            return self.probe_dict

        start = time.time()
        self.log.info("Probing DCP : {}".format(self.path))

        # Find and parse package components
        self.init_package_files()
        self.init_assetmap()
        self.init_volindex()
        self.init_pkl()
        self.init_cpl()

        if probe:
            self.cpl_probe_assets()
            self._probeb = True

        self.cpl_parse_metadata()

        seconds_elapsed = time.time() - start
        self.log.info("Total time : {:.2f} seconds".format(seconds_elapsed))

        self.probe_dict = {
            'asset_list': self._list_asset,
            'volindex_list': self._list_vol,
            'assetmap_list': self._list_am,
            'cpl_list': self._list_cpl,
            'pkl_list': self._list_pkl,
            'package_type': self.package_type,
            'path': self.path,
            'size': human_size(self.size),
            'count_file': len(self._list_asset),
            'schema': self.schema,
            'type': 'DCP'
        }

        # Remove namespace and attributes key from final result
        self.probe_dict = remove_key_dict(self.probe_dict,
                                          ['__xmlns__', '@xmlns'])
        self._parsed = True
        return self.probe_dict

    def check(self,
              profile=DCP_CHECK_PROFILE,
              ov_path=None,
              hash_callback=console_progress_bar):
        """ Check validity.

            Args:
                profile (dict): Checker profile.
                ov_path (str, optional): Absolute path of OriginalVersion DCP.

            Returns:
                Tuple (boolean, dict) of DCP check status and report. Report
                    is a Dictionary where check failure are grouped by
                    criticity.

        """
        if not self._parsed or not self._probeb:
            self.parse()

        self._checker = DCPChecker(self,
                                   profile=profile,
                                   ov_path=ov_path,
                                   hash_callback=hash_callback)
        return self._checker.check()