/***************************************************************************** * Copyright (C) 2013 VLC authors and VideoLAN * * Authors: * Nicolas Bertrand * Jean-Baptiste Kempf * Guillaume Gonnaud * Valentin Vetter * Anthony Giniers * Ludovic Hoareau * Loukmane Dessai * Simona-Marinela Prodea * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ /** * @file dcpparser.cpp * @brief Parsing of DCP XML files */ #ifdef HAVE_CONFIG_H # include "config.h" #endif /* VLC core API headers */ #include #include #include #include #include #include #include #include #include "dcpparser.h" using namespace std; typedef enum { CHUNK_UNKNOWN = 0, CHUNK_PATH, CHUNK_VOL_INDEX, CHUNK_OFFSET, CHUNK_LENGTH } ChunkTag_t; typedef enum { ASSET_UNKNOWN = 0, ASSET_ID, ASSET_ANNOTATION_TEXT, ASSET_PACKING_LIST, ASSET_CHUNK_LIST, ASSET_HASH, ASSET_SIZE, ASSET_TYPE, ASSET_ORIGINAL_FILENAME } AssetTag_t; static const string g_asset_names[] = { "Id", "AnnotationText", "PackingList", "ChunkList", "Hash", "Size", "Type", "OriginalFileName" }; typedef enum { PKL_UNKNOWN = 0, PKL_ID, PKL_ISSUE_DATE, PKL_ISSUER, PKL_CREATOR, PKL_ASSET_LIST, PKL_ANNOTATION_TEXT, /* start of optional tags */ PKL_ICON_ID, PKL_GROUP_ID, PKL_SIGNER, PKL_SIGNATURE, } PKLTag_t; typedef enum { CPL_UNKNOWN = 0, CPL_ID, CPL_ANNOTATION_TEXT, /* optional */ CPL_ICON_ID, /* optional */ CPL_ISSUE_DATE, CPL_ISSUER, /* optional */ CPL_CREATOR, /* optional */ CPL_CONTENT_TITLE, CPL_CONTENT_KIND, CPL_CONTENT_VERSION, /* not optional, but not always present*/ CPL_RATING_LIST, /* not handled */ CPL_REEL_LIST, CPL_SIGNER, /* optional - not handled */ CPL_SIGNATURE /* optional - not handled */ } CPLTag_t; class ChunkList: public std::list { public : ChunkList(); ~ChunkList(); }; /* * Chunk Class */ int Chunk::Parse( xml_reader_t *p_xmlReader, string p_node, int p_type){ string node; int type; string s_value; static const string names[] = {"Path", "VolumeIndex", "Offset", "Length"}; if (p_type != XML_READER_STARTELEM) return -1; if( p_node != "Chunk") return -1; /* loop on Chunks Node */ while( ( type = XmlFile::ReadNextNode( this->p_demux, p_xmlReader, node ) ) > 0 ) { switch (type) { case XML_READER_STARTELEM: { ChunkTag_t chunk_tag = CHUNK_UNKNOWN; for(ChunkTag_t i = CHUNK_PATH; i <= CHUNK_LENGTH; i = ChunkTag_t(i+1)) { if( node == names[i-1]) { chunk_tag = i; if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) return -1; switch (chunk_tag) { case CHUNK_PATH: this->s_path = s_value; break; case CHUNK_VOL_INDEX: this->i_vol_index = atoi(s_value.c_str()); break; case CHUNK_OFFSET: this->i_offset = atoi(s_value.c_str()); break; case CHUNK_LENGTH: this->i_length = atoi(s_value.c_str()); break; case CHUNK_UNKNOWN: default: break; } /* break the for loop as a tag is found*/ break; } } if(chunk_tag == CHUNK_UNKNOWN) return -1; break; } case XML_READER_TEXT: s_value = node; if (unlikely(node.empty())) return -1; break; case XML_READER_ENDELEM: /* Verify if we reach Chuk endelem */ if ( node == p_node) { /* verify path */ if ( this->s_path.empty() ) { msg_Err(this->p_demux, "Chunk::Parse No path found"); return -1; } if ( this->i_vol_index != 1 ) { msg_Err(this->p_demux, "Only one VOLINDEX supported. Patch welcome."); return -1; } /* end of chunk tag parse */ return 0; } break; } } return -1; } /* * AssetMap Class */ AssetMap::~AssetMap() { } int AssetMap::Parse ( ) { int type = 0; int retval; int reel_nbr = 0; int index = 0; int sum_duration_vid = 0; int sum_duration_aud = 0; string node; char *psz_kdm_path; CPL *cpl; Reel *reel; PKL *pkl; AssetList *_p_asset_list = NULL; vector pklfiles; /* init XML parser */ if( this->OpenXml() ) { msg_Err( p_demux, "Failed to initialize Assetmap XML parser" ); return -1; } /* reading ASSETMAP file to get the asset_list */ msg_Dbg( p_demux, "reading ASSETMAP file..." ); while( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) ) { if( type == -1 ) { this->CloseXml(); return -1; } if ( (type == XML_READER_STARTELEM) && ( node =="AssetList")) { _p_asset_list = new (nothrow) AssetList(); if ( unlikely(_p_asset_list == NULL) ) { this->CloseXml(); return -1; } p_dcp->p_asset_list = _p_asset_list; if (this->ParseAssetList(p_xmlReader, node, type )) { this->CloseXml(); return -1; } /* asset list found so break*/ break; } } /* Look for PKLs path */ if ( (_p_asset_list == NULL) || (_p_asset_list->size() == 0) ) { msg_Err( p_demux, "Asset list empty" ); this->CloseXml(); return -1; } for (AssetList::iterator iter = _p_asset_list->begin(); iter != _p_asset_list->end() ; ++iter) { string s_filepath; s_filepath = (*iter)->getPath(); if (s_filepath.empty()) { msg_Err( p_demux, "No path element for asset" ); continue; } /* set an absolute file path */ s_filepath = p_dcp->path + s_filepath; /* case of packing list */ if ((*iter)->isPackingList()) { pklfiles.push_back( s_filepath ); } } /* TODO: case of only on PKL managed. * Future work needed for managing severals * the PKL vector will be used to select the required PKL */ if( (pklfiles.size() == 0) || (pklfiles[0].empty()) ) { msg_Err( p_demux, "Could not find PKL file in ASSETMAP" ); this->CloseXml(); return -1; } /* Create the first PKL */ pkl = new (nothrow) PKL(p_demux, pklfiles[0], _p_asset_list, p_dcp->path); if ( unlikely(pkl == NULL) ) { this->CloseXml(); return -1; } if (pkl->Parse()) { delete pkl; this->CloseXml(); return -1; } p_dcp->pkls.push_back( pkl ); /* Now, CPL */ if ( pkl->FindCPLs() <= 0 ) { msg_Err(p_demux, " No CPL found"); this->CloseXml(); return -1; } /* TODO: Only one CPL managed. * Future work needed for managing severals */ cpl = pkl->getCPL(0); if( cpl == NULL ) { msg_Err(p_demux, " No CPL found"); this->CloseXml(); return -1; } if ( cpl->Parse() ) { this->CloseXml(); return -1; } /* KDM, if needed */ for( AssetList::iterator iter = _p_asset_list->begin(); iter != _p_asset_list->end(); ++iter ) if( ! (*iter)->getKeyId().empty() ) { msg_Dbg( p_demux, "DCP is encrypted, searching KDM file..."); psz_kdm_path = var_InheritString( p_demux, "kdm" ); if( !psz_kdm_path || !*psz_kdm_path ) { msg_Err( p_demux, "cryptographic key IDs found in CPL and no path to KDM given"); free( psz_kdm_path ); this->CloseXml(); return VLC_EGENERIC; } KDM p_kdm( p_demux, psz_kdm_path, p_dcp ); free( psz_kdm_path ); if( ( retval = p_kdm.Parse() ) ) { this->CloseXml(); return retval; } break; } reel_nbr = cpl->getReelList().size(); for(index = 0; index != reel_nbr; ++index) { reel = cpl->getReel(index); Asset *asset; struct info_reel video; struct info_reel audio; /* Get picture */ asset = reel->getTrack(TRACK_PICTURE); if (asset != NULL) { sum_duration_vid += asset->getDuration(); video.filename = p_dcp->path + asset->getPath(); video.i_entrypoint = asset->getEntryPoint(); video.i_duration = asset->getDuration(); video.i_correction = video.i_entrypoint - sum_duration_vid + video.i_duration; video.i_absolute_end = sum_duration_vid; video.p_key = asset->getAESKeyById( p_dcp->p_key_list, asset->getKeyId() ); p_dcp->video_reels.push_back(video); msg_Dbg( this->p_demux, "Video Track: %s",asset->getPath().c_str()); msg_Dbg( this->p_demux, "Entry point: %i",asset->getEntryPoint()); } /* Get audio */ asset = reel->getTrack(TRACK_SOUND); if (asset != NULL) { /*if (!p_dcp->audio_reels.empty()) { sum_duration_aud = 0; for (int i = 0; i != p_dcp->audio_reels.size(); ++i) { sum_duration_aud += p_dcp->audio_reels(i).i_duration; } }*/ sum_duration_aud += asset->getDuration(); audio.filename = p_dcp->path + asset->getPath(); audio.i_entrypoint = asset->getEntryPoint(); audio.i_duration = asset->getDuration(); audio.i_correction = audio.i_entrypoint - sum_duration_aud + audio.i_duration; audio.i_absolute_end = sum_duration_aud; audio.p_key = asset->getAESKeyById( p_dcp->p_key_list, asset->getKeyId() ); p_dcp->audio_reels.push_back(audio); msg_Dbg( this->p_demux, "Audio Track: %s",asset->getPath().c_str()); msg_Dbg( this->p_demux, "Entry point: %i",asset->getEntryPoint()); } } /* free memory */ this->CloseXml(); return VLC_SUCCESS; } /* * Asset Class */ Asset::~Asset() {} int Asset::Parse( xml_reader_t *p_xmlReader, string p_node, int p_type) { string node; int type; string s_value; const string s_root_node = "Asset"; if (p_type != XML_READER_STARTELEM) return -1; if( p_node != s_root_node) return -1; /* loop on Assets Node */ while( ( type = XmlFile::ReadNextNode( this->p_demux, p_xmlReader, node ) ) > 0 ) { switch (type) { case XML_READER_STARTELEM: { AssetTag_t _tag = ASSET_UNKNOWN; for(AssetTag_t i = ASSET_ID; i <= ASSET_ORIGINAL_FILENAME; i = AssetTag_t(i+1)) { if( node == g_asset_names[i-1]) { _tag = i; switch(_tag) { /* Case of complex nodes */ case ASSET_PACKING_LIST: /* case of tag, bur not compliant with SMPTE-429-9 2007*/ if (xml_ReaderIsEmptyElement( p_xmlReader)) { this->b_is_packing_list = true; } else if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) { msg_Err(this->p_demux, "Missing end node in %s", node.c_str()); return -1; } if ( s_value == "true" ) this->b_is_packing_list = true; break; case ASSET_CHUNK_LIST: if ( this->parseChunkList(p_xmlReader, node, type ) ) { msg_Err(this->p_demux, "Error parsing chunk list: %s", node.c_str()); return -1; } this->s_path = this->chunk_vec[0].getPath(); break; case ASSET_ID: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) { msg_Err(this->p_demux, "Missing end node in %s", node.c_str()); return -1; } this->s_id = s_value; break; case ASSET_ANNOTATION_TEXT: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) { msg_Err(this->p_demux, "Missing end node in %s", node.c_str()); return -1; } this->s_annotation = s_value; break; case ASSET_ORIGINAL_FILENAME: case ASSET_HASH: case ASSET_TYPE: case ASSET_SIZE: /* Asset tags not in AssetMap */ break; default: msg_Warn(this->p_demux, "Unknown ASSET_TAG: %i", _tag ); break; } /* break the for loop as a tag is found*/ break; } } if( _tag == ASSET_UNKNOWN ) { msg_Err(this->p_demux, "Unknown ASSET_TAG: %s", node.c_str()); return -1; } break; } case XML_READER_TEXT: msg_Err(this->p_demux, " Text element found in Asset"); return -1; case XML_READER_ENDELEM: if ( node != s_root_node) { msg_Err(this->p_demux, "Something goes wrong in Asset parsing on Assetmap (node %s)", node.c_str()); return -1; } else { /*Check Presence of Id and Chunklist */ if ( this->s_id.empty() ) { msg_Err(this->p_demux, " No Id element found in Asset"); return -1; } if ( this->s_path.empty() ) { msg_Err(this->p_demux, " No path element found in Asset"); return -1; } return 0; } break; } } return -1; } int Asset::ParsePKL( xml_reader_t *p_xmlReader) { string node; int type; string s_value; const string s_root_node = "Asset"; while( ( type = XmlFile::ReadNextNode( this->p_demux, p_xmlReader, node ) ) > 0 ) { switch (type) { case XML_READER_STARTELEM: { AssetTag_t _tag = ASSET_UNKNOWN; for(AssetTag_t i = ASSET_ID; i <= ASSET_ORIGINAL_FILENAME; i = AssetTag_t(i+1)) { if( node == g_asset_names[i-1]) { _tag = i; switch(_tag) { case ASSET_ANNOTATION_TEXT: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) return -1; if ( this->s_annotation.empty() ) this->s_annotation = s_value; else this->s_annotation = this->s_annotation + "--" + s_value; break; case ASSET_HASH: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) return -1; this->s_hash = s_value; break; case ASSET_SIZE: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) return -1; this->ui_size = atol(s_value.c_str()); break; case ASSET_TYPE: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) return -1; this->s_type = s_value; break; case ASSET_ORIGINAL_FILENAME: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) return -1; this->s_original_filename = s_value; break; case ASSET_ID: /* already verified */ case ASSET_PACKING_LIST: case ASSET_CHUNK_LIST: /* Asset tags not in PKL */ break; default: msg_Warn(this->p_demux, "Unknown ASSET_TAG: %i", _tag ); break; } /* break the for loop as a tag is found*/ break; } } if( _tag == ASSET_UNKNOWN ) return -1; break; } case XML_READER_TEXT: return -1; case XML_READER_ENDELEM: if ( node != s_root_node) { msg_Err(this->p_demux, "Something goes wrong in Asset parsing on PKL (node %s)", node.c_str()); return -1; } else { /* Verify that mandatory attributes are filled */ if (this->s_hash.empty()) { msg_Err(this->p_demux,"Asset Hash tag invalid"); return -1; } if (this->ui_size == 0) { msg_Err(this->p_demux,"Asset Size tag invalid"); return -1; } if (this->s_type.empty()) { msg_Err(this->p_demux,"Asset Type tag invalid"); return -1; } return 0; } break; } } return -1; } void Asset::Dump() { msg_Dbg(this->p_demux,"Id = %s", this->s_id.c_str()); msg_Dbg(this->p_demux,"Path = %s", this->s_path.c_str()); msg_Dbg(this->p_demux,"Is PKL = %s", this->b_is_packing_list ? "True" : "False"); msg_Dbg(this->p_demux,"Hash = %s", this->s_hash.c_str()); msg_Dbg(this->p_demux,"Size = %i", this->ui_size); msg_Dbg(this->p_demux,"Type = %s", this->s_type.c_str()); msg_Dbg(this->p_demux,"OrignalFileName = %s", this->s_original_filename.c_str()); msg_Dbg(this->p_demux,"AnnotationText = %s", this->s_annotation.c_str()); } int Asset::parseChunkList( xml_reader_t *p_xmlReader, string p_node, int p_type) { string node; int type; string s_value; std::vector chunk_vec; if (p_type != XML_READER_STARTELEM) return -1; if( p_node != "ChunkList" ) return -1; /* loop on Assets Node */ while( ( type = XmlFile::ReadNextNode( this->p_demux, p_xmlReader, node ) ) > 0 ) { switch (type) { case XML_READER_STARTELEM: { Chunk chunk(this->p_demux); if (node != "Chunk" ) return -1; if ( chunk.Parse(p_xmlReader, node, type) ) return -1; chunk_vec.push_back(chunk); break; } case XML_READER_ENDELEM: if ( node == p_node) { if (chunk_vec.size() != 1 ) { msg_Err(this->p_demux, "chunklist of size greater than one not supported"); return -1; } this->chunk_vec = chunk_vec; return 0; } break; } } return -1; } AESKey * Asset::getAESKeyById( AESKeyList* p_key_list, const string s_id ) { /* return NULL if DCP is not encrypted */ if( !p_key_list || s_id.empty() ) return NULL; for( AESKeyList::iterator index = p_key_list->begin(); index != p_key_list->end(); ++index ) if( (*index)->getKeyId() == s_id ) return *index; return NULL; } int AssetMap::ParseAssetList (xml_reader_t *p_xmlReader, const string p_node, int p_type) { string node; int type; Asset *asset; if (p_type != XML_READER_STARTELEM) return -1; if( p_node != "AssetList" ) return -1; /* loop on AssetList nodes */ while( ( type = XmlFile::ReadNextNode( this->p_demux, p_xmlReader, node ) ) > 0 ) { switch (type) { case XML_READER_STARTELEM: if (node != "Asset" ) return -1; asset = new (nothrow) Asset(this->p_demux); if ( unlikely(asset == NULL) ) return -1; if (asset->Parse(p_xmlReader, node, type)){ msg_Err(this->p_demux, "Error parsing Asset in AssetMap"); delete asset; return -1; } p_dcp->p_asset_list->push_back(asset); break; case XML_READER_ENDELEM: if (node == p_node ) return 0; break; default: case XML_READER_TEXT: msg_Err(this->p_demux, "Error parsing AssetList in AssetMap"); return -1; } } return -1; } Asset * AssetMap::getAssetById(AssetList *asset_list, const string p_id) { AssetList::iterator index = asset_list->begin() ; for (index = asset_list->begin(); index != asset_list->end(); ++index) if ((*index)->getId() == p_id ) return *index; return NULL; } /* * XmlFile Class */ XmlFile::~XmlFile() {} int XmlFile::OpenXml() { char *psz_uri; psz_uri = vlc_path2uri( this->s_path.c_str(), "file" ); this->p_stream = vlc_stream_NewURL(this->p_demux, psz_uri ); free(psz_uri); if( ! this->p_stream ) { return -1; } this->p_xmlReader = xml_ReaderCreate( this->p_demux, this->p_stream); if( ! this->p_xmlReader ) { vlc_stream_Delete( this->p_stream ); return -1; } return 0; } int XmlFile::ReadNextNode( demux_t *p_demux, xml_reader_t *p_xmlReader, string& p_node ) { const char * c_node; int i = xml_ReaderNextNode( p_xmlReader, &c_node ); if( i <= XML_READER_NONE ) return i; /* remove namespaces, if there are any */ string s_node = c_node; size_t ui_pos = s_node.find( ":" ); if( ( i == XML_READER_STARTELEM || i == XML_READER_ENDELEM ) && ( ui_pos != string::npos ) ) { try { p_node = s_node.substr( ui_pos + 1 ); } catch( ... ) { msg_Err( p_demux, "error while handling string" ); return -1; } } else p_node = s_node; return i; } int XmlFile::ReadEndNode( demux_t *p_demux, xml_reader_t *p_xmlReader, string p_node, int p_type, string &s_value) { string node; if ( xml_ReaderIsEmptyElement( p_xmlReader) ) return 0; if (p_type != XML_READER_STARTELEM) return -1; int n = XmlFile::ReadNextNode( p_demux, p_xmlReader, node ); if( n == XML_READER_TEXT ) { s_value = node; n = XmlFile::ReadNextNode( p_demux, p_xmlReader, node ); if( ( n == XML_READER_ENDELEM ) && node == p_node) return 0; } return n == XML_READER_ENDELEM ? 0 : -1; } /* * Reads first node in XML and returns * 1 if XML is CPL, * 0 if not * -1 on error */ int XmlFile::isCPL() { string node; int type, ret = 0; if( this->OpenXml() ) { msg_Err( this->p_demux, "Failed to open CPL XML file" ); return -1; } /* read 1st node and verify that is a CPL */ type = XmlFile::ReadNextNode( this->p_demux, p_xmlReader, node ); if( type == -1 ) /* error */ ret = -1; if( type == XML_READER_STARTELEM && node == "CompositionPlaylist" ) ret = 1; /* close xml */ this->CloseXml(); return ret; } void XmlFile::CloseXml() { if( this->p_stream ) vlc_stream_Delete( this->p_stream ); if( this->p_xmlReader ) xml_ReaderDelete( this->p_xmlReader ); } /* * PKL Class */ PKL::~PKL() { vlc_delete_all(vec_cpl); } int PKL::Parse() { string node; int type; string s_value; const string s_root_node = "PackingList"; static const string names[] = { "Id", "IssueDate", "Issuer", "Creator", "AssetList", "AnnotationText", "IconId", "GroupId", "Signer", "Signature" }; if (this->OpenXml()) return -1; /* read 1st node and verify that is a PKL*/ if ( ! ( ( XML_READER_STARTELEM == XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) && (node == s_root_node) ) ) { msg_Err( this->p_demux, "Not a valid XML Packing List"); goto error; } while( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) ) { switch (type) { case XML_READER_STARTELEM: { PKLTag_t _tag = PKL_UNKNOWN; for(PKLTag_t i = PKL_ID; i <= PKL_SIGNATURE; i = PKLTag_t(i+1)) { if( node == names[i-1]) { _tag = i; switch (_tag) { /* case for parsing non terminal nodes */ case PKL_ASSET_LIST: if ( this->ParseAssetList(node, type) ) goto error; break; case PKL_SIGNER: if ( this->ParseSigner(node, type) ) goto error; break; case PKL_SIGNATURE: if ( this->ParseSignature(node, type) ) goto error; break; /* Parse simple/end nodes */ case PKL_ID: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_id = s_value; break; case PKL_ISSUE_DATE: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_issue_date = s_value; break; case PKL_ISSUER: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_issuer = s_value; break; case PKL_CREATOR: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_creator = s_value; break; case PKL_ANNOTATION_TEXT: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_annotation = s_value; break; case PKL_ICON_ID: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_icon_id = s_value; break; case PKL_GROUP_ID: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_group_id = s_value; break; default: msg_Warn(this->p_demux, "Unknown PKG_TAG: %i", _tag ); break; } /* break the for loop as a tag is found*/ break; } } if( _tag == PKL_UNKNOWN ) goto error; break; } case XML_READER_TEXT: case -1: goto error; case XML_READER_ENDELEM: if ( node != s_root_node) { msg_Err(this->p_demux, "Something goes wrong in PKL parsing (node %s)", node.c_str()); goto error; } break; } } /* TODO verify presence of mandatory fields*/ /* Close PKL XML*/ this->CloseXml(); return 0; error: msg_Err( this->p_demux, "PKL parsing failed"); this->CloseXml(); return -1; } int PKL::FindCPLs() { if ( this->vec_cpl.size() != 0 ) { msg_Err(this->p_demux, "CPLs already checked"); return -1; } for (AssetList::iterator index = this->asset_list->begin(); index != this->asset_list->end(); ++index) { Asset *asset = *index; if ( asset->getType().find("text/xml") == string::npos) { /* not an xml file */ continue; } CPL *cpl = new (nothrow) CPL(this->p_demux, this->s_dcp_path + asset->getPath(), this->asset_list); if ( unlikely(cpl == NULL) ) return -1; switch( cpl->isCPL() ) { case 1: /* CPL Found */ this->vec_cpl.push_back(cpl); break; case -1: /* error */ delete cpl; return -1; case 0: default: delete cpl; break; } } return this->vec_cpl.size(); } int PKL::ParseAssetList(string p_node, int p_type) { string node; int type; if (p_type != XML_READER_STARTELEM) return -1; if( p_node != "AssetList") return -1; while( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) ) { switch (type) { case XML_READER_STARTELEM: if( node =="Asset") { if ( this->ParseAsset(node, type) ) return -1; } else return -1; break; case XML_READER_ENDELEM: if ( node == p_node) { /* parse of chunklist finished */ goto end; } break; case -1: /* error */ return -1; } } end: return 0; } int PKL::ParseAsset(string p_node, int p_type) { string node; int type; string s_value; Asset *asset = NULL; if (p_type != XML_READER_STARTELEM) return -1; if( p_node != "Asset") return -1; /* 1st node shall be Id" */ if( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) ) if ( ! ( ( type == XML_READER_STARTELEM ) && ( node == "Id" ) ) || type == -1 ) return -1; if( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) != -1 ) { if( type == XML_READER_TEXT ) { s_value = node; if (unlikely(node.empty())) return -1; } } else return -1; if( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) != -1 ) { if( type == XML_READER_ENDELEM ) { asset = AssetMap::getAssetById(this->asset_list, s_value); if (asset == NULL) return -1; } } else return -1; if (asset == NULL) return -1; if ( asset->ParsePKL(this->p_xmlReader) ) return -1; return 0; } int PKL::ParseSigner(string p_node, int p_type) { string node; int type; if (p_type != XML_READER_STARTELEM) return -1; if( p_node != "Signer") return -1; while( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) > 0 ) { /* TODO not implemented. Just parse until end of Signer node */ if ((node == p_node) && (type == XML_READER_ENDELEM)) return 0; } msg_Err(this->p_demux, "Parse of Signer finished bad"); return -1; } int PKL::ParseSignature(string p_node, int p_type) { string node; int type; if (p_type != XML_READER_STARTELEM) return -1; if( p_node != "Signature") return -1; while( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) > 0 ) { /* TODO not implemented. Just parse until end of Signature node */ if ((node == p_node) && (type == XML_READER_ENDELEM)) return 0; } msg_Err(this->p_demux, "Parse of Signature finished bad"); return -1; } /* * Reel Class */ int Reel::Parse(string p_node, int p_type) { string node; int type; string s_value; if (p_type != XML_READER_STARTELEM) return -1; if( p_node != "Reel") return -1; while( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) > 0 ) { switch (type) { case XML_READER_STARTELEM: if (node =="Id") { if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; this->s_id = s_value; } else if (node == "AnnotationText") { if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; this->s_annotation = s_value; } else if ( node =="AssetList" ) { if (this->ParseAssetList(node, type)) return -1; } else { /* unknown tag */ msg_Err(this->p_demux, "Reel::Parse, unknown tag:%s", node.c_str()); return -1; } break; case XML_READER_TEXT: /* Error */ msg_Err(this->p_demux, "Reel parsing error"); return -1; case XML_READER_ENDELEM: /* verify correctness of end node */ if ( node == p_node) { /* TODO : verify Reel id */ return 0; } break; } } return -1; } Asset * Reel::getTrack(TrackType_t e_track) { switch (e_track) { case TRACK_PICTURE: return this->p_picture_track; case TRACK_SOUND: return this->p_sound_track; case TRACK_SUBTITLE: return this->p_subtitle_track; case TRACK_UNKNOWN: default: break; } return NULL; } int Reel::ParseAssetList(string p_node, int p_type) { string node; int type; string s_value; if (p_type != XML_READER_STARTELEM) return -1; if( p_node != "AssetList") return -1; while( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) > 0 ) { switch (type) { case XML_READER_STARTELEM: if (node =="MainPicture") { if ( this->ParseAsset(node, type, TRACK_PICTURE) ) return -1; } else if (node =="MainSound") { if ( this->ParseAsset(node, type, TRACK_SOUND) ) return -1; } else if (node =="MainSubtitle") { if ( this->ParseAsset(node, type, TRACK_SUBTITLE) ) return -1; } else { /* unknown tag */ msg_Err(this->p_demux, "Reel::ParseAssetList, unknown tag:%s", node.c_str()); return -1; } break; case XML_READER_TEXT: /* Parsing error */ msg_Err(this->p_demux, "AssetList parsing error"); return -1; case XML_READER_ENDELEM: /* verify correctness of end node */ if ( node == p_node) { /* TODO : verify id */ return 0; } break; } } return -1; } int Reel::ParseAsset(string p_node, int p_type, TrackType_t e_track) { string node; int type; string s_value; bool b_stop_parse = false; Asset *asset = NULL; if (p_type != XML_READER_STARTELEM) return -1; /* 1st node shall be Id */ if( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) ) if( ! ( ( type == XML_READER_STARTELEM ) && ( node == "Id" ) ) || type == -1 ) return -1; if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; asset = AssetMap::getAssetById(this->p_asset_list, s_value); if (asset == NULL) return -1; while( (! b_stop_parse) && ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) ) { switch (type) { case XML_READER_STARTELEM: if (node =="EditRate") { if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; } else if (node == "AnnotationText") { if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; asset->setAnnotation(s_value); } else if (node == "IntrinsicDuration") { if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; asset->setIntrinsicDuration(atoi(s_value.c_str())); } else if (node == "EntryPoint") { if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; asset->setEntryPoint(atoi(s_value.c_str())); } else if (node == "Duration") { if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; asset->setDuration(atoi(s_value.c_str())); } else if (node == "KeyId") { if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; asset->setKeyId( s_value ); } else if (node == "Hash") { if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; } else if (node == "FrameRate") { if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; } else if (node == "ScreenAspectRatio") { if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; } else if (node == "Language") { if ( XmlFile::ReadEndNode( this->p_demux, this->p_xmlReader, node, type, s_value ) ) return -1; } else { /* unknown tag */ msg_Err(this->p_demux, "Reel::ParseAsset unknown tag:%s", node.c_str()); return -1; } break; case XML_READER_TEXT: case -1: /* error */ return -1; break; case XML_READER_ENDELEM: /* verify correctness of end node */ if ( node == p_node) { /* TODO : verify id */ b_stop_parse = true; } } } /* store by track */ switch (e_track) { case TRACK_PICTURE: this->p_picture_track = asset; break; case TRACK_SOUND: this->p_sound_track = asset; break; case TRACK_SUBTITLE: this->p_subtitle_track = asset; break; case TRACK_UNKNOWN: default: break; } return 0; } /* * CPL Class */ CPL::~CPL() { vlc_delete_all(vec_reel); } int CPL::Parse() { string node; int type; string s_value; const string s_root_node = "CompositionPlaylist"; static const string names[] = { "Id", "AnnotationText", "IconId", "IssueDate", "Issuer", "Creator", "ContentTitleText", "ContentKind", "ContentVersion", "RatingList", "ReelList", "Signer", "Signature" }; if (this->OpenXml()) return -1; /* read 1st node and verify that is a CPL*/ if( ! ( ( XML_READER_STARTELEM == XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) && (node == s_root_node) ) ) { msg_Err( this->p_demux, "Not a valid XML Packing List"); goto error; } while( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) ) { switch (type) { case XML_READER_STARTELEM: { CPLTag_t _tag = CPL_UNKNOWN; for(CPLTag_t i = CPL_ID; i <= CPL_SIGNATURE; i = CPLTag_t(i+1)) { if( node == names[i-1]) { _tag = i; switch (_tag) { /* case for parsing non terminal nodes */ case CPL_REEL_LIST: if ( this->ParseReelList(node, type) ) goto error; break; case CPL_CONTENT_VERSION: case CPL_SIGNER: case CPL_SIGNATURE: case CPL_RATING_LIST: if ( this->DummyParse(node,type) ) goto error; break; /* Parse simple/end nodes */ case CPL_ID: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_id = s_value; break; case CPL_ANNOTATION_TEXT: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_annotation = s_value; break; case CPL_ICON_ID: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_icon_id = s_value; break; case CPL_ISSUE_DATE: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_issue_date= s_value; break; case CPL_ISSUER: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_issuer = s_value; break; case CPL_CREATOR: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_creator = s_value; break; case CPL_CONTENT_TITLE: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_content_title = s_value; break; case CPL_CONTENT_KIND: if ( XmlFile::ReadEndNode( this->p_demux, p_xmlReader, node, type, s_value ) ) goto error; this->s_content_kind = s_value; break; default: msg_Warn(this->p_demux, "Unknown CPL_TAG: %i", _tag ); break; } /* break the for loop as a tag is found*/ break; } } if( _tag == CPL_UNKNOWN ) goto error; break; } case XML_READER_TEXT: case -1: goto error; case XML_READER_ENDELEM: if ( node != s_root_node) { msg_Err(this->p_demux, "Something goes wrong in CKL parsing (node %s)", node.c_str()); goto error; } break; } } /* TODO verify presence of mandatory fields*/ /* Close CPL XML*/ this->CloseXml(); return 0; error: this->CloseXml(); return -1; } int CPL::ParseReelList(string p_node, int p_type) { string node; int type; if (p_type != XML_READER_STARTELEM) return -1; if( p_node != "ReelList") return -1; while( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) > 0 ) { switch (type) { case XML_READER_STARTELEM: { Reel *p_reel = new (nothrow) Reel( this->p_demux, this->asset_list, this->p_xmlReader); if ( unlikely(p_reel == NULL) ) return -1; if( node =="Reel") { if ( p_reel->Parse(node, type) ) { delete p_reel; return -1; } } else { delete p_reel; return -1; } this->vec_reel.push_back(p_reel); break; } case XML_READER_TEXT: /* impossible */ break; case XML_READER_ENDELEM: if ( node == p_node) return 0; break; } } return -1; } int CPL::DummyParse(string p_node, int p_type) { string node; int type; if (p_type != XML_READER_STARTELEM) return -1; if (xml_ReaderIsEmptyElement( this->p_xmlReader)) return 0; while( ( type = XmlFile::ReadNextNode( this->p_demux, this->p_xmlReader, node ) ) > 0 ) { /* TODO not implemented. Just pase until end of input node */ if ((node == p_node) && (type == XML_READER_ENDELEM)) return 0; } return -1; }