pax_global_header00006660000000000000000000000064135416332610014516gustar00rootroot0000000000000052 comment=a7844c9acf22c4c0a01eacba37bf7f186d83b16a python-mpegdash-0.2.0/000077500000000000000000000000001354163326100146245ustar00rootroot00000000000000python-mpegdash-0.2.0/.gitignore000066400000000000000000000000751354163326100166160ustar00rootroot00000000000000*.py[co] *.sw[op] *.ropeproject tests/mpd-samples/output.mpd python-mpegdash-0.2.0/.travis.yml000066400000000000000000000001241354163326100167320ustar00rootroot00000000000000language: python python: - "2.7" - "3.4" - "3.5" script: python setup.py test python-mpegdash-0.2.0/LICENSE000066400000000000000000000020671354163326100156360ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 supercast-tv Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-mpegdash-0.2.0/README.md000066400000000000000000000027521354163326100161110ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/sangwonl/python-mpegdash.svg?branch=master)](https://travis-ci.org/sangwonl/python-mpegdash) # python-mpegdash MPEG-DASH MPD(Media Presentation Description) Parser compatible with Python2.6+ and Python3 ## Install ```bash $ pip install mpegdash ``` ## Test ```bash $ python -m unittest discover $ python3 -m unittest discover ``` ## Usage ```python from mpegdash.parser import MPEGDASHParser # Parse from file path mpd_path = './tests/mpd-samples/sample-001.mpd' mpd = MPEGDASHParser.parse(mpd_path) # Parse from url mpd_url = 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/motion-20120802-manifest.mpd' mpd = MPEGDASHParser.parse(mpd_url) # Parse from string mpd_string = ''' motion-20120802-89.mp4 ''' mpd = MPEGDASHParser.parse(mpd_string) # Write to xml file MPEGDASHParser.write(mpd, './tests/mpd-samples/output.mpd') ``` python-mpegdash-0.2.0/mpegdash/000077500000000000000000000000001354163326100164145ustar00rootroot00000000000000python-mpegdash-0.2.0/mpegdash/__init__.py000066400000000000000000000000001354163326100205130ustar00rootroot00000000000000python-mpegdash-0.2.0/mpegdash/nodes.py000066400000000000000000001143651354163326100201100ustar00rootroot00000000000000from mpegdash.utils import ( parse_attr_value, parse_child_nodes, parse_node_value, write_attr_value, write_child_node, write_node_value ) class XMLNode(object): def parse(self, xmlnode): raise NotImplementedError('Should have implemented this') def write(self, xmlnode): raise NotImplementedError('Should have implemented this') class Subset(XMLNode): def __init__(self): self.id = None # xs:string self.contains = [] # UIntVectorType (required) def parse(self, xmlnode): self.id = parse_attr_value(xmlnode, 'id', str) self.contains = parse_attr_value(xmlnode, 'contains', [int]) def write(self, xmlnode): write_attr_value(xmlnode, 'id', self.id) write_attr_value(xmlnode, 'contains', self.contains) class URL(XMLNode): def __init__(self): self.source_url = None # xs:anyURI self.range = None # xs:string def parse(self, xmlnode): self.source_url = parse_attr_value(xmlnode, 'sourceURL', str) self.range = parse_attr_value(xmlnode, 'range', str) def write(self, xmlnode): write_attr_value(xmlnode, 'sourceURL', self.source_url) write_attr_value(xmlnode, 'range', self.range) class BaseURL(XMLNode): def __init__(self): self.base_url_value = None # xs:anyURI self.service_location = None # xs:string self.byte_range = None # xs:string self.availability_time_offset = None # xs:double self.availability_time_complete = None # xs:boolean def parse(self, xmlnode): self.base_url_value = parse_node_value(xmlnode, str) self.service_location = parse_attr_value(xmlnode, 'serviceLocation', str) self.byte_range = parse_attr_value(xmlnode, 'byteRange', str) self.availability_time_offset = parse_attr_value(xmlnode, 'availabilityTimeOffset', float) self.availability_time_complete = parse_attr_value(xmlnode, 'availabilityTimeComplete', bool) def write(self, xmlnode): write_node_value(xmlnode, self.base_url_value) write_attr_value(xmlnode, 'serviceLocation', self.service_location) write_attr_value(xmlnode, 'byteRange', self.byte_range) write_attr_value(xmlnode, 'availabilityTimeOffset', self.availability_time_offset) write_attr_value(xmlnode, 'availabilityTimeComplete', self.availability_time_complete) class XsStringElement(XMLNode): def __init__(self): self.text = None def parse(self, xmlnode): self.text = parse_node_value(xmlnode, str) def write(self, xmlnode): write_node_value(xmlnode, self.text) class ProgramInformation(XMLNode): def __init__(self): self.lang = None # xs:language self.more_information_url = None # xs:anyURI self.titles = None # xs:string* self.sources = None # xs:string* self.copyrights = None # xs:string* def parse(self, xmlnode): self.lang = parse_attr_value(xmlnode, 'lang', str) self.more_information_url = parse_attr_value(xmlnode, 'moreInformationURL', str) self.titles = parse_child_nodes(xmlnode, 'Title', XsStringElement) self.sources = parse_child_nodes(xmlnode, 'Source', XsStringElement) self.copyrights = parse_child_nodes(xmlnode, 'Copyright', XsStringElement) def write(self, xmlnode): write_attr_value(xmlnode, 'lang', self.lang) write_attr_value(xmlnode, 'moreInformationURL', self.more_information_url) write_child_node(xmlnode, 'Title', self.titles) write_child_node(xmlnode, 'Source', self.sources) write_child_node(xmlnode, 'Copyright', self.copyrights) class Metrics(XMLNode): def __init__(self): self.metrics = '' # xs:string (required) self.reportings = None # DescriptorType* self.ranges = None # RangeType* def parse(self, xmlnode): self.metrics = parse_attr_value(xmlnode, 'metrics', str) self.reportings = parse_child_nodes(xmlnode, 'Reporting', Descriptor) self.ranges = parse_child_nodes(xmlnode, 'Range', Range) def write(self, xmlnode): write_attr_value(xmlnode, 'metrics', self.metrics) write_child_node(xmlnode, 'Reporting', self.reportings) write_child_node(xmlnode, 'Range', self.ranges) class Range(XMLNode): def __init__(self): self.starttime = None # xs:duration self.duration = None # xs:duration def parse(self, xmlnode): self.starttime = parse_attr_value(xmlnode, 'starttime', str) self.duration = parse_attr_value(xmlnode, 'duration', str) def write(self, xmlnode): write_attr_value(xmlnode, 'starttime', self.starttime) write_attr_value(xmlnode, 'duration', self.duration) class SegmentURL(XMLNode): def __init__(self): self.media = None # xs:anyURI self.media_range = None # xs:string self.index = None # xs:anyURI self.index_range = None # xs:string def parse(self, xmlnode): self.media = parse_attr_value(xmlnode, 'media', str) self.media_range = parse_attr_value(xmlnode, 'mediaRange', str) self.index = parse_attr_value(xmlnode, 'index', str) self.index_range = parse_attr_value(xmlnode, 'indexRange', str) def write(self, xmlnode): write_attr_value(xmlnode, 'media', self.media) write_attr_value(xmlnode, 'mediaRange', self.media_range) write_attr_value(xmlnode, 'index', self.index) write_attr_value(xmlnode, 'indexRange', self.index_range) class S(XMLNode): def __init__(self): self.t = None # xs:unsignedLong self.d = 0 # xs:unsignedLong (required) self.r = None # xml:integer def parse(self, xmlnode): self.t = parse_attr_value(xmlnode, 't', int) self.d = parse_attr_value(xmlnode, 'd', int) self.r = parse_attr_value(xmlnode, 'r', int) def write(self, xmlnode): write_attr_value(xmlnode, 't', self.t) write_attr_value(xmlnode, 'd', self.d) write_attr_value(xmlnode, 'r', self.r) class SegmentTimeline(XMLNode): def __init__(self): self.Ss = None # xs:complexType+ def parse(self, xmlnode): self.Ss = parse_child_nodes(xmlnode, 'S', S) def write(self, xmlnode): write_child_node(xmlnode, 'S', self.Ss) class SegmentBase(XMLNode): def __init__(self): self.timescale = None # xs:unsignedInt self.index_range = None # xs:string self.index_range_exact = None # xs:boolean self.presentation_time_offset = None # xs:unsignedLong self.availability_time_offset = None # xs:double self.availability_time_complete = None # xs:boolean self.initializations = None # URLType* self.representation_indexes = None # URLType* def parse(self, xmlnode): self.timescale = parse_attr_value(xmlnode, 'timescale', int) self.index_range = parse_attr_value(xmlnode, 'indexRange', str) self.index_range_exact = parse_attr_value(xmlnode, 'indexRangeExact', bool) self.presentation_time_offset = parse_attr_value(xmlnode, 'presentationTimeOffset', int) self.availability_time_offset = parse_attr_value(xmlnode, 'availabilityTimeOffset', float) self.availability_time_complete = parse_attr_value(xmlnode, 'availabilityTimeComplete', bool) self.initializations = parse_child_nodes(xmlnode, 'Initialization', URL) self.representation_indexes = parse_child_nodes(xmlnode, 'RepresentationIndex', URL) def write(self, xmlnode): write_attr_value(xmlnode, 'timescale', self.timescale) write_attr_value(xmlnode, 'indexRange', self.index_range) write_attr_value(xmlnode, 'indexRangeExact', self.index_range_exact) write_attr_value(xmlnode, 'presentationTimeOffset', self.presentation_time_offset) write_attr_value(xmlnode, 'availabilityTimeOffset', self.availability_time_offset) write_attr_value(xmlnode, 'availabilityTimeComplete', self.availability_time_complete) write_child_node(xmlnode, 'Initialization', self.initializations) write_child_node(xmlnode, 'RepresentationIndex', self.representation_indexes) class MultipleSegmentBase(SegmentBase): def __init__(self): SegmentBase.__init__(self) self.duration = None # xs:unsignedInt self.start_number = None # xs:unsignedInt self.segment_timelines = None # SegmentTimelineType* self.bitstream_switchings = None # URLType* def parse(self, xmlnode): SegmentBase.parse(self, xmlnode) self.duration = parse_attr_value(xmlnode, 'duration', int) self.start_number = parse_attr_value(xmlnode, 'startNumber', int) self.segment_timelines = parse_child_nodes(xmlnode, 'SegmentTimeline', SegmentTimeline) self.bitstream_switchings = parse_child_nodes(xmlnode, 'BitstreamSwitching', URL) def write(self, xmlnode): SegmentBase.write(self, xmlnode) write_attr_value(xmlnode, 'duration', self.duration) write_attr_value(xmlnode, 'startNumber', self.start_number) write_child_node(xmlnode, 'SegmentTimeline', self.segment_timelines) write_child_node(xmlnode, 'BitstreamSwitching', self.bitstream_switchings) class SegmentTemplate(MultipleSegmentBase): def __init__(self): MultipleSegmentBase.__init__(self) self.media = None # xs:string self.index = None # xs:string self.initialization = None # xs:string self.bitstream_switching = None # xs:string def parse(self, xmlnode): MultipleSegmentBase.parse(self, xmlnode) self.media = parse_attr_value(xmlnode, 'media', str) self.index = parse_attr_value(xmlnode, 'index', str) self.initialization = parse_attr_value(xmlnode, 'initialization', str) self.bitstream_switching = parse_attr_value(xmlnode, 'bitstreamSwitching', str) def write(self, xmlnode): MultipleSegmentBase.write(self, xmlnode) write_attr_value(xmlnode, 'media', self.media) write_attr_value(xmlnode, 'index', self.index) write_attr_value(xmlnode, 'initialization', self.initialization) write_attr_value(xmlnode, 'bitstreamSwitching', self.bitstream_switching) class SegmentList(MultipleSegmentBase): def __init__(self): MultipleSegmentBase.__init__(self) self.segment_urls = None # SegmentURLType def parse(self, xmlnode): MultipleSegmentBase.parse(self, xmlnode) self.segment_urls = parse_child_nodes(xmlnode, 'SegmentURL', SegmentURL) def write(self, xmlnode): MultipleSegmentBase.write(self, xmlnode) write_child_node(xmlnode, 'SegmentURL', self.segment_urls) class Event(XMLNode): def __init__(self): self.event_value = None # xs:string self.message_data = None # xs:string self.presentation_time = None # xs:unsignedLong self.duration = None # xs:unsignedLong self.id = None # xs:unsignedInt def parse(self, xmlnode): self.event_value = parse_node_value(xmlnode, str) self.message_data = parse_attr_value(xmlnode, 'messageData', str) self.presentation_time = parse_attr_value(xmlnode, 'presentationTime', int) self.duration = parse_attr_value(xmlnode, 'duration', int) self.id = parse_attr_value(xmlnode, 'id', int) def write(self, xmlnode): write_node_value(xmlnode, self.event_value) write_attr_value(xmlnode, 'messageData', self.message_data) write_attr_value(xmlnode, 'presentationTime', self.presentation_time) write_attr_value(xmlnode, 'duration', self.duration) write_attr_value(xmlnode, 'id', self.id) class Descriptor(XMLNode): def __init__(self): self.scheme_id_uri = '' # xs:anyURI (required) self.value = None # xs:string self.id = None # xs:string def parse(self, xmlnode): self.scheme_id_uri = parse_attr_value(xmlnode, 'schemeIdUri', str) self.value = parse_attr_value(xmlnode, 'value', str) self.id = parse_attr_value(xmlnode, 'id', str) def write(self, xmlnode): write_attr_value(xmlnode, 'schemeIdUri', self.scheme_id_uri) write_attr_value(xmlnode, 'value', self.value) write_attr_value(xmlnode, 'id', self.id) class ContentComponent(XMLNode): def __init__(self): self.id = None # xs:unsigendInt self.lang = None # xs:language self.content_type = None # xs:string self.par = None # RatioType self.accessibilities = None # DescriptorType* self.roles = None # DescriptorType* self.ratings = None # DescriptorType* self.viewpoints = None # DescriptorType* def parse(self, xmlnode): self.id = parse_attr_value(xmlnode, 'id', int) self.lang = parse_attr_value(xmlnode, 'lang', str) self.content_type = parse_attr_value(xmlnode, 'contentType', str) self.par = parse_attr_value(xmlnode, 'par', str) self.accessibilities = parse_child_nodes(xmlnode, 'Accessibility', Descriptor) self.roles = parse_child_nodes(xmlnode, 'Role', Descriptor) self.ratings = parse_child_nodes(xmlnode, 'Rating', Descriptor) self.viewpoints = parse_child_nodes(xmlnode, 'Viewpoint', Descriptor) def write(self, xmlnode): write_attr_value(xmlnode, 'id', self.id) write_attr_value(xmlnode, 'lang', self.lang) write_attr_value(xmlnode, 'contentType', self.content_type) write_attr_value(xmlnode, 'par', self.par) write_child_node(xmlnode, 'Accessibility', self.accessibilities) write_child_node(xmlnode, 'Role', self.roles) write_child_node(xmlnode, 'Rating', self.ratings) write_child_node(xmlnode, 'Viewpoint', self.viewpoints) class RepresentationBase(XMLNode): def __init__(self): self.profiles = None # xs:string self.width = None # xs:unsigendInt self.height = None # xs:unsigendInt self.sar = None # RatioType self.frame_rate = None # FrameRateType self.audio_sampling_rate = None # xs:string self.mime_type = None # xs:string self.segment_profiles = None # xs:string self.codecs = None # xs:string self.maximum_sap_period = None # xs:double self.start_with_sap = None # SAPType self.max_playout_rate = None # xs:double self.coding_dependency = None # xs:boolean self.scan_type = None # VideoScanType self.frame_packings = None # DescriptorType* self.audio_channel_configurations = None # DescriptorType* self.content_protections = None # DescriptorType* self.essential_properties = None # DescriptorType* self.supplemental_properties = None # DescriptorType* self.inband_event_streams = None # DescriptorType* def parse(self, xmlnode): self.profiles = parse_attr_value(xmlnode, 'profile', str) self.width = parse_attr_value(xmlnode, 'width', int) self.height = parse_attr_value(xmlnode, 'height', int) self.sar = parse_attr_value(xmlnode, 'sar', str) self.frame_rate = parse_attr_value(xmlnode, 'frameRate', str) self.audio_sampling_rate = parse_attr_value(xmlnode, 'audioSamplingRate', str) self.mime_type = parse_attr_value(xmlnode, 'mimeType', str) self.segment_profiles = parse_attr_value(xmlnode, 'segmentProfiles', str) self.codecs = parse_attr_value(xmlnode, 'codecs', str) self.maximum_sap_period = parse_attr_value(xmlnode, 'maximumSAPPeriod', float) self.start_with_sap = parse_attr_value(xmlnode, 'startWithSAP', int) self.max_playout_rate = parse_attr_value(xmlnode, 'maxPlayoutRate', float) self.coding_dependency = parse_attr_value(xmlnode, 'codingDependency', bool) self.scan_type = parse_attr_value(xmlnode, 'scanType', str) self.frame_packings = parse_child_nodes(xmlnode, 'FramePacking', Descriptor) self.audio_channel_configurations = parse_child_nodes(xmlnode, 'AudioChannelConfiguration', Descriptor) self.content_protections = parse_child_nodes(xmlnode, 'ContentProtection', Descriptor) self.essential_properties = parse_child_nodes(xmlnode, 'EssentialProperty', Descriptor) self.supplemental_properties = parse_child_nodes(xmlnode, 'SupplementalProperty', Descriptor) self.inband_event_streams = parse_child_nodes(xmlnode, 'InbandEventStream', Descriptor) def write(self, xmlnode): write_attr_value(xmlnode, 'profile', self.profiles) write_attr_value(xmlnode, 'width', self.width) write_attr_value(xmlnode, 'height', self.height) write_attr_value(xmlnode, 'sar', self.sar) write_attr_value(xmlnode, 'frameRate', self.frame_rate) write_attr_value(xmlnode, 'audioSamplingRate', self.audio_sampling_rate) write_attr_value(xmlnode, 'mimeType', self.mime_type) write_attr_value(xmlnode, 'segmentProfiles', self.segment_profiles) write_attr_value(xmlnode, 'codecs', self.codecs) write_attr_value(xmlnode, 'maximumSAPPeriod', self.maximum_sap_period) write_attr_value(xmlnode, 'startWithSAP', self.start_with_sap) write_attr_value(xmlnode, 'maxPlayoutRate', self.max_playout_rate) write_attr_value(xmlnode, 'codingDependency', self.coding_dependency) write_attr_value(xmlnode, 'scanType', self.scan_type) write_child_node(xmlnode, 'FramePacking', self.frame_packings) write_child_node(xmlnode, 'AudioChannelConfiguration', self.audio_channel_configurations) write_child_node(xmlnode, 'ContentProtection', self.content_protections) write_child_node(xmlnode, 'EssentialProperty', self.essential_properties) write_child_node(xmlnode, 'SupplementalProperty', self.supplemental_properties) write_child_node(xmlnode, 'InbandEventStream', self.inband_event_streams) class Representation(RepresentationBase): def __init__(self): RepresentationBase.__init__(self) self.id = '' # StringNoWhitespaceType (Required) self.bandwidth = 0 # xs:unsignedInt (required) self.quality_ranking = None # xs:unsignedInt self.dependency_id = None # StringVectorType self.num_channels = None # xs:unsignedInt self.sample_rate = None # xs:unsignedLong self.base_urls = None # BaseURLType* self.segment_bases = None # SegmentBaseType* self.segment_lists = None # SegmentListType* self.segment_templates = None # SegmentTemplateType* self.sub_representations = None # SubRepresentationType* def parse(self, xmlnode): RepresentationBase.parse(self, xmlnode) self.id = parse_attr_value(xmlnode, 'id', str) self.bandwidth = parse_attr_value(xmlnode, 'bandwidth', int) self.quality_ranking = parse_attr_value(xmlnode, 'qualityRanking', int) self.dependency_id = parse_attr_value(xmlnode, 'dependencyId', [str]) self.num_channels = parse_attr_value(xmlnode, 'numChannels', int) self.sample_rate = parse_attr_value(xmlnode, 'sampleRate', int) self.base_urls = parse_child_nodes(xmlnode, 'BaseURL', BaseURL) self.segment_bases = parse_child_nodes(xmlnode, 'SegmentBase', SegmentBase) self.segment_lists = parse_child_nodes(xmlnode, 'SegmentList', SegmentList) self.segment_templates = parse_child_nodes(xmlnode, 'SegmentTemplate', SegmentTemplate) self.sub_representations = parse_child_nodes(xmlnode, 'SubRepresentation', SubRepresentation) def write(self, xmlnode): RepresentationBase.write(self, xmlnode) write_attr_value(xmlnode, 'id', self.id) write_attr_value(xmlnode, 'width', self.width) write_attr_value(xmlnode, 'height', self.height) write_attr_value(xmlnode, 'bandwidth', self.bandwidth) write_attr_value(xmlnode, 'mimeType', self.mime_type) write_attr_value(xmlnode, 'codecs', self.codecs) write_child_node(xmlnode, 'BaseURL', self.base_urls) write_child_node(xmlnode, 'SegmentBase', self.segment_bases) write_child_node(xmlnode, 'SegmentList', self.segment_lists) write_child_node(xmlnode, 'SegmentTemplate', self.segment_templates) write_child_node(xmlnode, 'SubRepresentation', self.sub_representations) class SubRepresentation(RepresentationBase): def __init__(self): RepresentationBase.__init__(self) self.level = None # xs:unsigendInt self.bandwidth = None # xs:unsignedInt self.dependency_level = None # UIntVectorType self.content_component = None # StringVectorType def parse(self, xmlnode): RepresentationBase.parse(self, xmlnode) self.level = parse_attr_value(xmlnode, 'level', int) self.bandwidth = parse_attr_value(xmlnode, 'bandwidth', int) self.dependency_level = parse_attr_value(xmlnode, 'dependencyLevel', [int]) self.content_component = parse_attr_value(xmlnode, 'contentComponent', [str]) def write(self, xmlnode): RepresentationBase.write(self, xmlnode) write_attr_value(xmlnode, 'level', self.level) write_attr_value(xmlnode, 'bandwidth', self.bandwidth) write_attr_value(xmlnode, 'dependencyLevel', self.dependency_level) write_attr_value(xmlnode, 'contentComponent', self.content_component) class AdaptationSet(RepresentationBase): def __init__(self): RepresentationBase.__init__(self) self.id = None # xs:unsignedInt self.group = None # xs:unsignedInt self.lang = None # xs:language self.content_type = None # xs:string self.par = None # RatioType self.min_bandwidth = None # xs:unsignedInt self.max_bandwidth = None # xs:unsignedInt self.min_width = None # xs:unsignedInt self.max_width = None # xs:unsignedInt self.min_height = None # xs:unsignedInt self.max_height = None # xs:unsignedInt self.min_frame_rate = None # FrameRateType self.max_frame_rate = None # FrameRateType self.segment_alignment = None # ConditionalUintType self.subsegment_alignment = None # ConditionalUintType self.subsegment_starts_with_sap = None # SAPType self.bitstream_switching = None # xs:boolean self.accessibilities = None # DescriptorType* self.roles = None # DescriptorType* self.ratings = None # DescriptorType* self.viewpoints = None # DescriptorType* self.content_components = None # DescriptorType* self.base_urls = None # BaseURLType* self.segment_bases = None # SegmentBase* self.segment_lists = None # SegmentListType* self.segment_templates = None # SegmentTemplateType* self.representations = None # RepresentationType* def parse(self, xmlnode): RepresentationBase.parse(self, xmlnode) self.id = parse_attr_value(xmlnode, 'id', int) self.group = parse_attr_value(xmlnode, 'group', int) self.lang = parse_attr_value(xmlnode, 'lang', str) self.content_type = parse_attr_value(xmlnode, 'contentType', str) self.par = parse_attr_value(xmlnode, 'par', str) self.min_bandwidth = parse_attr_value(xmlnode, 'minBandwidth', int) self.max_bandwidth = parse_attr_value(xmlnode, 'maxBandwidth', int) self.min_width = parse_attr_value(xmlnode, 'minWidth', int) self.max_width = parse_attr_value(xmlnode, 'maxWidth', int) self.min_height = parse_attr_value(xmlnode, 'minHeight', int) self.max_height = parse_attr_value(xmlnode, 'maxHeight', int) self.min_frame_rate = parse_attr_value(xmlnode, 'minFrameRate', str) self.max_frame_rate = parse_attr_value(xmlnode, 'maxFrameRate', str) self.segment_alignment = parse_attr_value(xmlnode, 'segmentAlignment', bool) self.subsegment_alignment = parse_attr_value(xmlnode, 'subsegmentAlignment', bool) self.subsegment_starts_with_sap = parse_attr_value(xmlnode, 'subsegmentStartsWithSAP', int) self.bitstream_switching = parse_attr_value(xmlnode, 'bitstreamSwitching', bool) self.accessibilities = parse_child_nodes(xmlnode, 'Accessibility', Descriptor) self.roles = parse_child_nodes(xmlnode, 'Role', Descriptor) self.ratings = parse_child_nodes(xmlnode, 'Rating', Descriptor) self.viewpoints = parse_child_nodes(xmlnode, 'Viewpoint', Descriptor) self.content_components = parse_child_nodes(xmlnode, 'ContentComponent', ContentComponent) self.base_urls = parse_child_nodes(xmlnode, 'BaseURL', BaseURL) self.segment_bases = parse_child_nodes(xmlnode, 'SegmentBase', SegmentBase) self.segment_lists = parse_child_nodes(xmlnode, 'SegmentList', SegmentList) self.segment_templates = parse_child_nodes(xmlnode, 'SegmentTemplate', SegmentTemplate) self.representations = parse_child_nodes(xmlnode, 'Representation', Representation) def write(self, xmlnode): RepresentationBase.write(self, xmlnode) write_attr_value(xmlnode, 'id', self.id) write_attr_value(xmlnode, 'group', self.group) write_attr_value(xmlnode, 'lang', self.lang) write_attr_value(xmlnode, 'contentType', self.content_type) write_attr_value(xmlnode, 'par', self.par) write_attr_value(xmlnode, 'minBandwidth', self.min_bandwidth) write_attr_value(xmlnode, 'maxBandwidth', self.max_bandwidth) write_attr_value(xmlnode, 'minWidth', self.min_width) write_attr_value(xmlnode, 'maxWidth', self.max_width) write_attr_value(xmlnode, 'minHeight', self.min_height) write_attr_value(xmlnode, 'maxHeight', self.max_height) write_attr_value(xmlnode, 'minFrameRate', self.min_frame_rate) write_attr_value(xmlnode, 'maxFrameRate', self.max_frame_rate) write_attr_value(xmlnode, 'segmentAlignment', self.segment_alignment) write_attr_value(xmlnode, 'subsegmentAlignment', self.subsegment_alignment) write_attr_value(xmlnode, 'subsegmentStartsWithSAP', self.subsegment_starts_with_sap) write_attr_value(xmlnode, 'bitstreamSwitching', self.bitstream_switching) write_child_node(xmlnode, 'Accessibility', self.accessibilities) write_child_node(xmlnode, 'Role', self.roles) write_child_node(xmlnode, 'Rating', self.ratings) write_child_node(xmlnode, 'Viewpoint', self.viewpoints) write_child_node(xmlnode, 'ContentComponent', self.content_components) write_child_node(xmlnode, 'BaseURL', self.base_urls) write_child_node(xmlnode, 'SegmentBase', self.segment_bases) write_child_node(xmlnode, 'SegmentList', self.segment_lists) write_child_node(xmlnode, 'SegmentTemplate', self.segment_templates) write_child_node(xmlnode, 'Representation', self.representations) class EventStream(XMLNode): def __init__(self): self.scheme_id_uri = None # xs:anyURI (required) self.value = None # xs:string self.timescale = None # xs:unsignedInt self.events = None # EventType* def parse(self, xmlnode): self.scheme_id_uri = parse_attr_value(xmlnode, 'schemeIdUri', str) self.value = parse_attr_value(xmlnode, 'value', str) self.timescale = parse_attr_value(xmlnode, 'timescale', int) self.events = parse_child_nodes(xmlnode, 'Event', Event) def write(self, xmlnode): write_attr_value(xmlnode, 'schemeIdUri', self.scheme_id_uri) write_attr_value(xmlnode, 'value', self.value) write_attr_value(xmlnode, 'timescale', self.timescale) write_child_node(xmlnode, 'Event', self.events) class Period(XMLNode): def __init__(self): self.id = None # xs:string self.start = None # xs:duration self.duration = None # xs:duration self.bitstream_switching = None # xs:boolean self.base_urls = None # BaseURLType* self.segment_bases = None # SegmentBaseType* self.segment_lists = None # SegmentListType* self.segment_templates = None # SegmentTemplateType* self.asset_identifiers = None # DescriptorType* self.event_streams = None # EventStreamType* self.adaptation_sets = None # AdaptationSetType* self.subsets = None # SubsetType* def parse(self, xmlnode): self.id = parse_attr_value(xmlnode, 'id', str) self.start = parse_attr_value(xmlnode, 'start', str) self.duration = parse_attr_value(xmlnode, 'duration', str) self.bitstream_switching = parse_attr_value(xmlnode, 'bitstreamSwitching', bool) self.base_urls = parse_child_nodes(xmlnode, 'BaseURL', BaseURL) self.segment_bases = parse_child_nodes(xmlnode, 'SegmentBase', SegmentBase) self.segment_lists = parse_child_nodes(xmlnode, 'SegmentList', SegmentList) self.segment_templates = parse_child_nodes(xmlnode, 'SegmentTemplate', SegmentTemplate) self.asset_identifiers = parse_child_nodes(xmlnode, 'AssetIdentifier', Descriptor) self.event_streams = parse_child_nodes(xmlnode, 'EventStream', EventStream) self.adaptation_sets = parse_child_nodes(xmlnode, 'AdaptationSet', AdaptationSet) self.subsets = parse_child_nodes(xmlnode, 'Subset', Subset) def write(self, xmlnode): write_attr_value(xmlnode, 'id', self.id) write_attr_value(xmlnode, 'start', self.start) write_attr_value(xmlnode, 'duration', self.duration) write_attr_value(xmlnode, 'bitstreamSwitching', self.bitstream_switching) write_child_node(xmlnode, 'BaseURL', self.base_urls) write_child_node(xmlnode, 'SegmentBase', self.segment_bases) write_child_node(xmlnode, 'SegmentList', self.segment_lists) write_child_node(xmlnode, 'SegmentTemplate', self.segment_templates) write_child_node(xmlnode, 'AssetIdentifier', self.asset_identifiers) write_child_node(xmlnode, 'EventStream', self.event_streams) write_child_node(xmlnode, 'AdaptationSet', self.adaptation_sets) write_child_node(xmlnode, 'Subset', self.subsets) class MPEGDASH(XMLNode): def __init__(self): self.xmlns = None # xmlns self.id = None # xs:string self.type = None # PresentationType self.profiles = '' # xs:string (required) self.availability_start_time = None # xs:dateTime self.availability_end_time = None # xs:dateTime self.publish_time = None # xs:dateTime self.media_presentation_duration = None # xs:duration self.minimum_update_period = None # xs:duration self.min_buffer_time = None # xs:duration self.time_shift_buffer_depth = None # xs:duration self.suggested_presentation_delay = None # xs:duration self.max_segment_duration = None # xs:duration self.max_subsegment_duration = None # xs:duration self.program_informations = None # ProgramInformationType* self.base_urls = None # BaseURLType* self.locations = None # xs:anyURI* self.periods = None # PeriodType+ self.metrics = None # MetricsType* self.utc_timings = None # DescriptorType* def parse(self, xmlnode): self.xmlns = parse_attr_value(xmlnode, 'xmlns', str) self.id = parse_attr_value(xmlnode, 'id', str) self.type = parse_attr_value(xmlnode, 'type', str) self.profiles = parse_attr_value(xmlnode, 'profiles', str) self.availability_start_time = parse_attr_value(xmlnode, 'availabilityStartTime', str) self.availability_end_time = parse_attr_value(xmlnode, 'availabilityEndTime', str) self.publish_time = parse_attr_value(xmlnode, 'publishTime', str) self.media_presentation_duration = parse_attr_value(xmlnode, 'mediaPresentationDuration', str) self.minimum_update_period = parse_attr_value(xmlnode, 'minimumUpdatePeriod', str) self.min_buffer_time = parse_attr_value(xmlnode, 'minBufferTime', str) self.time_shift_buffer_depth = parse_attr_value(xmlnode, 'timeShiftBufferDepth', str) self.suggested_presentation_delay = parse_attr_value(xmlnode, 'suggestedPresentationDelay', str) self.max_segment_duration = parse_attr_value(xmlnode, 'maxSegmentDuration', str) self.max_subsegment_duration = parse_attr_value(xmlnode, 'maxSubsegmentDuration', str) self.program_informations = parse_child_nodes(xmlnode, 'ProgramInformation', ProgramInformation) self.base_urls = parse_child_nodes(xmlnode, 'BaseURL', BaseURL) self.locations = parse_child_nodes(xmlnode, 'Location', XsStringElement) self.periods = parse_child_nodes(xmlnode, 'Period', Period) self.metrics = parse_child_nodes(xmlnode, 'Metrics', Metrics) self.utc_timings = parse_child_nodes(xmlnode, 'UTCTiming', Descriptor) def write(self, xmlnode): write_attr_value(xmlnode, 'xmlns', self.xmlns) write_attr_value(xmlnode, 'id', self.id) write_attr_value(xmlnode, 'type', self.type) write_attr_value(xmlnode, 'profiles', self.profiles) write_attr_value(xmlnode, 'availabilityStartTime', self.availability_start_time) write_attr_value(xmlnode, 'availabilityEndTime', self.availability_end_time) write_attr_value(xmlnode, 'publishTime', self.publish_time) write_attr_value(xmlnode, 'mediaPresentationDuration', self.media_presentation_duration) write_attr_value(xmlnode, 'minimumUpdatePeriod', self.minimum_update_period) write_attr_value(xmlnode, 'minBufferTime', self.min_buffer_time) write_attr_value(xmlnode, 'timeShiftBufferDepth', self.time_shift_buffer_depth) write_attr_value(xmlnode, 'suggestedPresentationDelay', self.suggested_presentation_delay) write_attr_value(xmlnode, 'maxSegmentDuration', self.max_segment_duration) write_attr_value(xmlnode, 'maxSubsegmentDuration', self.max_subsegment_duration) write_child_node(xmlnode, 'ProgramInformation', self.program_informations) write_child_node(xmlnode, 'BaseURL', self.base_urls) write_child_node(xmlnode, 'Location', self.locations) write_child_node(xmlnode, 'Period', self.periods) write_child_node(xmlnode, 'Metrics', self.metrics) write_child_node(xmlnode, 'UTCTiming', self.utc_timings) python-mpegdash-0.2.0/mpegdash/parser.py000066400000000000000000000024531354163326100202660ustar00rootroot00000000000000from xml.dom import minidom # python3 support try: from urllib2 import urlopen except: from urllib.request import urlopen from mpegdash.nodes import MPEGDASH from mpegdash.utils import parse_child_nodes, write_child_node from mpegdash.prettyprinter import pretty_print class MPEGDASHParser(object): @classmethod def load_xmldom(cls, string_or_url): if ']*>$', line): # single line text element, don't change indentation addition = 0 elif re.match('^<\/\w', line) and current[0] > 0: # end of element and have padding, decrement identation by one current[0] -= 1 elif re.match('^<\w[^>]*[^\/]>.*$', line): # start of element, increment indentation by one addition = 1 else: # single line element, don't change indentation addition = 0 # update and store current indentation in outer function current[0] += addition # pad the line and return return (indent * (current[0] - addition)) + line # split the document into line, indent each line, then rejoin lines return (newl).join( map( indentline, re.sub('(>)(<)(\/*)', r'\1\n\2\3', xmlstr).split('\n') ) ) python-mpegdash-0.2.0/mpegdash/schema/000077500000000000000000000000001354163326100176545ustar00rootroot00000000000000python-mpegdash-0.2.0/mpegdash/schema/dash-mpd.xsd000066400000000000000000000472501354163326100221010ustar00rootroot00000000000000 Media Presentation Description This Schema defines the Media Presentation Description for MPEG-DASH. python-mpegdash-0.2.0/mpegdash/utils.py000066400000000000000000000045321354163326100201320ustar00rootroot00000000000000from past.builtins import unicode # python3 compat from xml.dom import minidom import re def _find_child_nodes_by_name(parent, name): nodes = [] for node in parent.childNodes: if node.nodeType == node.ELEMENT_NODE and node.localName == name: nodes.append(node) return nodes def parse_child_nodes(xmlnode, tag_name, node_type): elements = _find_child_nodes_by_name(xmlnode, tag_name) if not elements: return None nodes = [] for elem in elements: if node_type in (unicode, str): node = xmlnode.firstChild.nodeValue else: node = node_type() node.parse(elem) nodes.append(node) return nodes def parse_node_value(xmlnode, value_type): node_val = xmlnode.firstChild.nodeValue if xmlnode.firstChild else None if node_val: return value_type(node_val) return None def parse_attr_value(xmlnode, attr_name, value_type): if attr_name not in xmlnode.attributes.keys(): return None attr_val = xmlnode.attributes[attr_name].nodeValue if isinstance(value_type, list): attr_type = type(value_type[0]) if len(value_type) > 0 else str return [attr_type(elem) for elem in re.split(r',| ', attr_val)] return value_type(attr_val) def write_child_node(xmlnode, tag_name, node): if node: xmldoc = xmlnode if isinstance(xmlnode, minidom.Document) else xmlnode.ownerDocument if isinstance(node, list): for n in node: new_elem = xmldoc.createElement(tag_name) n.write(new_elem) xmlnode.appendChild(new_elem) else: new_elem = xmldoc.createElement(tag_name) node.write(new_elem) xmlnode.appendChild(new_elem) def write_node_value(xmlnode, node_val): if node_val: xmldoc = xmlnode if isinstance(xmlnode, minidom.Document) else xmlnode.ownerDocument text_node = xmldoc.createTextNode(str(node_val)) xmlnode.appendChild(text_node) def write_attr_value(xmlnode, attr_name, attr_val): if attr_name and attr_val is not None: if isinstance(type(attr_val), list): attr_val = ' '.join([str(val) for val in attr_val]) val = str(attr_val) if type(attr_val) is bool: val = val.lower() xmlnode.setAttribute(attr_name, val) python-mpegdash-0.2.0/requirements.txt000066400000000000000000000000071354163326100201050ustar00rootroot00000000000000future python-mpegdash-0.2.0/setup.py000066400000000000000000000017741354163326100163470ustar00rootroot00000000000000from os.path import dirname, abspath, join, exists from setuptools import setup long_description = None if exists("README.md"): long_description = open("README.md").read() setup( name="mpegdash", packages=["mpegdash"], description="MPEG-DASH MPD(Media Presentation Description) Parser", long_description=long_description, long_description_content_type='text/markdown', author="sangwonl", author_email="gamzabaw@gmail.com", version="0.2.0", license="MIT", zip_safe=False, include_package_data=True, install_requires=["future"], url="https://github.com/sangwonl/python-mpegdash", tests_require=["unittest2"], test_suite="tests.my_module_suite", classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Environment :: Other Environment", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries :: Python Modules", ], ) python-mpegdash-0.2.0/tests/000077500000000000000000000000001354163326100157665ustar00rootroot00000000000000python-mpegdash-0.2.0/tests/__init__.py000066400000000000000000000003111354163326100200720ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest def my_module_suite(): loader = unittest.TestLoader() suite = loader.discover('tests', pattern='test_*.py') return suite python-mpegdash-0.2.0/tests/mpd-samples/000077500000000000000000000000001354163326100202105ustar00rootroot00000000000000python-mpegdash-0.2.0/tests/mpd-samples/360p_speciment_dash.mpd000066400000000000000000000027021354163326100244510ustar00rootroot00000000000000 360p_speciment_dash.mpd generated by GPAC 360p_speciment_dashinit.mp4 python-mpegdash-0.2.0/tests/mpd-samples/motion-20120802-manifest.mpd000066400000000000000000000054761354163326100247330ustar00rootroot00000000000000 motion-20120802-89.mp4 motion-20120802-88.mp4 motion-20120802-87.mp4 motion-20120802-86.mp4 motion-20120802-85.mp4 motion-20120802-8c.mp4 motion-20120802-8d.mp4 motion-20120802-8b.mp4 python-mpegdash-0.2.0/tests/mpd-samples/oops-20120802-manifest.mpd000066400000000000000000000054611354163326100244000ustar00rootroot00000000000000 oops-20120802-89.mp4 oops-20120802-88.mp4 oops-20120802-87.mp4 oops-20120802-86.mp4 oops-20120802-85.mp4 oops-20120802-8c.mp4 oops-20120802-8d.mp4 oops-20120802-8b.mp4 python-mpegdash-0.2.0/tests/mpd-samples/sample-001.mpd000066400000000000000000000100061354163326100224660ustar00rootroot00000000000000 ad/ 720p.ts 1080p.ts main/ video/ 720p/ 1080/ audio/ python-mpegdash-0.2.0/tests/mpd-samples/utc_timing.mpd000066400000000000000000000013031354163326100230510ustar00rootroot00000000000000 http://www.example.com/location.mpd Test Title Test Source python-mpegdash-0.2.0/tests/mpd-samples/with_event_message_data.mpd000066400000000000000000000037571354163326100255770ustar00rootroot00000000000000 Some Random Event Text python-mpegdash-0.2.0/tests/test_mpdtoxml.py000066400000000000000000000041171354163326100212460ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest from sys import version_info from mpegdash.parser import MPEGDASHParser class MPD2XMLTestCase(unittest.TestCase): def test_mpd2xml(self): mpd = MPEGDASHParser.parse('./tests/mpd-samples/sample-001.mpd') MPEGDASHParser.write(mpd, './tests/mpd-samples/output.mpd') mpd2 = MPEGDASHParser.parse('./tests/mpd-samples/output.mpd') all_reprs = [] for period in mpd.periods: for adapt_set in period.adaptation_sets: for repr in adapt_set.representations: all_reprs.append(repr) all_reprs2 = [] for period in mpd2.periods: for adapt_set in period.adaptation_sets: for repr in adapt_set.representations: all_reprs2.append(repr) self.assertTrue(len(all_reprs) == 5) self.assertTrue(len(all_reprs) == len(all_reprs2)) def test_mpd2xml_boolean_casing(self): mpd = MPEGDASHParser.parse('./tests/mpd-samples/with_event_message_data.mpd') MPEGDASHParser.write(mpd, './tests/mpd-samples/output.mpd') with open('./tests/mpd-samples/output.mpd') as f: regex = r'segmentAlignment=\"true\"' # assertRegexpMatches is deprecated in 3, assertRegex not in 2 if version_info > (3, 1,): self.assertRegex(f.read(), regex) else: self.assertRegexpMatches(f.read(), regex) def test_mpd2xmlstr(self): # set maxDiff to None for Python2.6 self.maxDiff = None with open('./tests/mpd-samples/sample-001.mpd') as f: # read the test MPD mpd = MPEGDASHParser.parse(f.read()) # get the MPD as an XML string xmlstrout = MPEGDASHParser.toprettyxml(mpd) # then parse that string mpd2 = MPEGDASHParser.parse(xmlstrout) # get the reparsed MPD as a string xmlstrout2 = MPEGDASHParser.toprettyxml(mpd2) # and check the are equal self.assertEqual(xmlstrout, xmlstrout2) python-mpegdash-0.2.0/tests/test_xmltompd.py000066400000000000000000000056631354163326100212550ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest from mpegdash.parser import MPEGDASHParser class XML2MPDTestCase(unittest.TestCase): def test_xml2mpd_from_string(self): mpd_string = ''' motion-20120802-89.mp4 ''' self.assert_mpd(MPEGDASHParser.parse(mpd_string)) def test_xml2mpd_from_file(self): self.assert_mpd(MPEGDASHParser.parse('./tests/mpd-samples/sample-001.mpd')) self.assert_mpd(MPEGDASHParser.parse('./tests/mpd-samples/motion-20120802-manifest.mpd')) self.assert_mpd(MPEGDASHParser.parse('./tests/mpd-samples/oops-20120802-manifest.mpd')) self.assert_mpd(MPEGDASHParser.parse('./tests/mpd-samples/360p_speciment_dash.mpd')) def test_xml2mpd_from_url(self): mpd_url = 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/motion-20120802-manifest.mpd' self.assert_mpd(MPEGDASHParser.parse(mpd_url)) def test_xml2mpd_from_file_with_utc_timing(self): mpd = MPEGDASHParser.parse('./tests/mpd-samples/utc_timing.mpd') self.assertEqual(mpd.utc_timings[0].scheme_id_uri, 'urn:mpeg:dash:utc:http-iso:2014') self.assertEqual(mpd.utc_timings[0].value, 'https://time.akamai.com/?iso') def test_xml2mpd_from_file_with_event_messagedata(self): mpd = MPEGDASHParser.parse('./tests/mpd-samples/with_event_message_data.mpd') self.assertTrue(mpd.periods[0].event_streams[0].events[0].message_data is not None) self.assertTrue(mpd.periods[0].event_streams[0].events[0].event_value is None) self.assertTrue(mpd.periods[0].event_streams[0].events[1].message_data is None) self.assertEqual(mpd.periods[0].event_streams[0].events[1].event_value, "Some Random Event Text") def assert_mpd(self, mpd): self.assertTrue(mpd is not None) self.assertTrue(len(mpd.periods) > 0) self.assertTrue(mpd.periods[0].adaptation_sets is not None) self.assertTrue(len(mpd.periods[0].adaptation_sets) > 0) self.assertTrue(mpd.periods[0].adaptation_sets[0].representations is not None) self.assertTrue(len(mpd.periods[0].adaptation_sets[0].representations) > 0) self.assertTrue(len(mpd.periods[0].adaptation_sets[0].representations[0].id) > 0)