dash_timeline_validator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 30a521b9bae3596ae9e89cc90d388a722ad5d3ec4f9871d9747af1f46ad94fd1
4
+ data.tar.gz: 0b5b89822ad89fac58a53a927a4fa257c331de9762c40fa2e304de12ac307f15
5
+ SHA512:
6
+ metadata.gz: ea0620b668d4b5803ecba5ce47811cc584eb8b07c757213ea72bf922bc774531da6e524e0971f06f858d50b138d2cbe5f00348c85578888ac4f5898c7b2c72e0
7
+ data.tar.gz: 90722730b66e4127aa8d26031a62d6eeab56adb7cee875f8d43291e796f353455716f6fd521db3edf9dced3ae386ad22201e7125c193e1a775d58dd6c4e8863a
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /*.gem
10
+ /data
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.0
7
+ before_install: gem install bundler -v 2.0.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dash_timeline_validator.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dash_timeline_validator (0.1.0)
5
+ awesome_print (~> 1.8.0)
6
+ iso8601 (~> 0.10.1)
7
+ ox (~> 2.9.0)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ awesome_print (1.8.0)
13
+ diff-lcs (1.3)
14
+ iso8601 (0.10.1)
15
+ ox (2.9.4)
16
+ rake (10.5.0)
17
+ rspec (3.8.0)
18
+ rspec-core (~> 3.8.0)
19
+ rspec-expectations (~> 3.8.0)
20
+ rspec-mocks (~> 3.8.0)
21
+ rspec-core (3.8.0)
22
+ rspec-support (~> 3.8.0)
23
+ rspec-expectations (3.8.2)
24
+ diff-lcs (>= 1.2.0, < 2.0)
25
+ rspec-support (~> 3.8.0)
26
+ rspec-mocks (3.8.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.8.0)
29
+ rspec-support (3.8.0)
30
+
31
+ PLATFORMS
32
+ ruby
33
+
34
+ DEPENDENCIES
35
+ bundler (~> 2.0)
36
+ dash_timeline_validator!
37
+ rake (~> 10.0)
38
+ rspec (~> 3.0)
39
+
40
+ BUNDLED WITH
41
+ 2.0.1
data/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2019, Globo.com
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # Dash Timeline Validator
2
+
3
+ An MPEG Dash timeline validator. The validator parses the given MPD file (local or from the web) and shows information and errors of the timeline.
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'dash_timeline_validator'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install dash_timeline_validator
21
+
22
+ ## Usage
23
+
24
+ Run this program passing the manifest path.
25
+
26
+ ```
27
+ dash_timeline_validator https://storage.googleapis.com/shaka-live-assets/player-source.mpd
28
+ ```
29
+
30
+ To download and check the duration of every chunk, use the environment variable `VERIFY_SEGMENTS_DURATION` as `true`.
31
+
32
+ ```
33
+ VERIFY_SEGMENTS_DURATION=true dash_timeline_validator https://storage.googleapis.com/shaka-live-assets/player-source.mpd
34
+ ```
35
+
36
+ ## Development
37
+
38
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
39
+
40
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
41
+
42
+ ## Contributing
43
+
44
+ Bug reports and pull requests are welcome on GitHub at https://github.com/globocom/dash_timeline_validator.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dash_timeline_validator"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,43 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "dash_timeline_validator/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dash_timeline_validator"
8
+ spec.version = DashTimelineValidator::VERSION
9
+ spec.authors = ["Ana Carolina Castro"]
10
+ spec.email = ["ana.castro@corp.globo.com"]
11
+
12
+ spec.summary = %q{MPEG-Dash Timeline Validator}
13
+ spec.description = %q{MPEG-Dash Timeline Validator.}
14
+ spec.homepage = "https://github.com/globocom/dash_timeline_validator"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/globocom/dash_timeline_validator"
21
+ spec.metadata["changelog_uri"] = "https://github.com/globocom/dash_timeline_validator"
22
+ else
23
+ raise "RubyGems 2.0 or newer is required to protect against " \
24
+ "public gem pushes."
25
+ end
26
+
27
+ # Specify which files should be added to the gem when it is released.
28
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
29
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
30
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
31
+ end
32
+ spec.bindir = "exe"
33
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
+ spec.require_paths = ["lib"]
35
+
36
+ spec.add_dependency "iso8601", "~> 0.10.1"
37
+ spec.add_dependency "ox", "~> 2.9.0"
38
+ spec.add_dependency "awesome_print", "~> 1.8.0"
39
+
40
+ spec.add_development_dependency "bundler", "~> 2.0"
41
+ spec.add_development_dependency "rake", "~> 10.0"
42
+ spec.add_development_dependency "rspec", "~> 3.0"
43
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "dash_timeline_validator"
4
+
5
+ if ARGV.length == 0
6
+ p "You must pass the manifest!"
7
+ return -1
8
+ end
9
+
10
+ DashTimelineValidator::CLI.main(ARGV[0])
@@ -0,0 +1,14 @@
1
+ require "dash_timeline_validator/adaptation_set"
2
+ require "dash_timeline_validator/cli"
3
+ require "dash_timeline_validator/file"
4
+ require "dash_timeline_validator/log"
5
+ require "dash_timeline_validator/options"
6
+ require "dash_timeline_validator/period"
7
+ require "dash_timeline_validator/report"
8
+ require "dash_timeline_validator/representation"
9
+ require "dash_timeline_validator/segment"
10
+ require "dash_timeline_validator/validator"
11
+
12
+ module DashTimelineValidator
13
+ class Error < StandardError; end
14
+ end
@@ -0,0 +1,17 @@
1
+ module DashTimelineValidator
2
+ class AdaptationSet
3
+ def self.process(context, adaptation_set, index)
4
+ as_result = {}
5
+ as_result["name"] = "AdaptationSet-#{index}"
6
+ DashTimelineValidator::Report.fill_report(as_result, adaptation_set, "mimeType")
7
+ DashTimelineValidator::Report.fill_report(as_result, adaptation_set, "contentType")
8
+ all_representations = adaptation_set.nodes.select { |n| n.name == "Representation" }
9
+
10
+ as_result["representations"] = all_representations.each_with_index.map do |representation, i|
11
+ DashTimelineValidator::Representation.process({root: context[:root], previous: as_result}, representation, i)
12
+ end
13
+
14
+ as_result
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ require "fileutils"
2
+
3
+ module DashTimelineValidator
4
+ class CLI
5
+ def self.error_exit(report)
6
+ DashTimelineValidator::Log.info(report)
7
+ exit(-1)
8
+ end
9
+
10
+ def self.main(manifest)
11
+ begin
12
+ FileUtils.mkdir_p DashTimelineValidator::Options::ANALYZER_FOLDER
13
+ DashTimelineValidator::Log.info("The manifest #{manifest} will be processed at #{DashTimelineValidator::Options::ANALYZER_FOLDER} folder.")
14
+
15
+ mpd_content = DashTimelineValidator::DashFile.fetch_file(manifest)
16
+
17
+ DashTimelineValidator::Log.info(DashTimelineValidator::Validator.analyze(manifest, mpd_content))
18
+ rescue StandardError => e
19
+ DashTimelineValidator::Log.warn("There was an error: #{e.inspect}")
20
+ DashTimelineValidator::Log.warn(e.backtrace.join("\n\t"))
21
+ DashTimelineValidator::Log.warn("Removing the folder #{DashTimelineValidator::Options::ANALYZER_FOLDER}")
22
+ FileUtils.rm_rf DashTimelineValidator::Options::ANALYZER_FOLDER
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ require "net/http"
2
+
3
+ module DashTimelineValidator
4
+ class DashFile
5
+ def self.fetch_file(origin, file_path = Options::ANALYZER_MANIFEST_PATH)
6
+ dirname = File.dirname(file_path)
7
+ unless File.directory? dirname
8
+ FileUtils.mkdir_p(dirname)
9
+ end
10
+
11
+ if uri? origin
12
+ download_and_save(origin, file_path)
13
+ else
14
+ FileUtils.cp origin, file_path
15
+ File.read file_path
16
+ end
17
+ end
18
+
19
+ def self.download_and_save(uri, path)
20
+ content = Net::HTTP.get(URI.parse(uri))
21
+ File.write(path, content)
22
+ content
23
+ end
24
+
25
+ def self.uri?(string)
26
+ uri = URI.parse(string)
27
+ %w( http https ).include?(uri.scheme)
28
+ rescue URI::BadURIError
29
+ false
30
+ rescue URI::InvalidURIError
31
+ false
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ require "awesome_print"
2
+
3
+ module DashTimelineValidator
4
+ class Log
5
+ def self.info(msg)
6
+ ap msg
7
+ end
8
+
9
+ def self.warn(msg)
10
+ ap msg
11
+ end
12
+
13
+ def self.error(msg)
14
+ ap msg
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ require "securerandom"
2
+
3
+ module DashTimelineValidator
4
+ class Options
5
+ ACCEPTABLE_DRIFT = ENV["ACCEPTABLE_DRIFT"] || 2
6
+ DEFAULT_PRESENTATION_DELAY = ENV["DEFAULT_PRESENTATION_DELAY"] || 10
7
+ BUFFERED_SEGMENTS = ENV["BUFFERED_SEGMENTS"] || 2
8
+ VERIFY_SEGMENTS_DURATION = ENV["VERIFY_SEGMENTS_DURATION"] || false
9
+ ANALYZER_FOLDER = ENV["ANALYZER_FOLDER"] || "data/#{SecureRandom.uuid}".freeze
10
+ ANALYZER_MANIFEST_PATH = ENV["ANALYZER_MANIFEST_PATH"] || "#{ANALYZER_FOLDER}/manifest.mpd".freeze
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module DashTimelineValidator
2
+ class Period
3
+ def self.process(context, period, index)
4
+ period_result = {}
5
+ period_result["name"] = "Period-#{index}"
6
+ DashTimelineValidator::Report.fill_report(period_result, period, "start", 0, :duration_iso8601_to_i)
7
+
8
+ all_adaptation_sets = period.nodes.select { |n| n.name == "AdaptationSet" }
9
+
10
+ period_result["adaptation_sets"] = all_adaptation_sets.each_with_index.map do |adaptation_set, i|
11
+ DashTimelineValidator::AdaptationSet.process({root: context[:root], previous: period_result}, adaptation_set, i)
12
+ end
13
+
14
+ period_result
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,50 @@
1
+ require "iso8601"
2
+
3
+ module DashTimelineValidator
4
+ class Report
5
+ def self.report_info(value)
6
+ value
7
+ end
8
+
9
+ def self.report_warn(value)
10
+ "[WARN] #{value}"
11
+ end
12
+
13
+ def self.report_error(value)
14
+ "[ERROR] #{value}"
15
+ end
16
+
17
+ def self.fill_report(report, mpd_leaf, key_name, default_value = nil, parser_fn = :identity)
18
+ if mpd_leaf.respond_to? key_name
19
+ report[key_name] = Report.report_info(self.send(parser_fn, mpd_leaf[key_name]))
20
+ elsif default_value
21
+ report[key_name] = Report.report_info(self.send(parser_fn, default_value))
22
+ end
23
+ end
24
+
25
+ def self.fill_report_mandatory(report, mpd_leaf, key_name, parser_fn = :identity)
26
+ if !mpd_leaf.respond_to? key_name
27
+ report[key_name] = report_error("Mandatory #{key_name} is missing")
28
+ error_exit(report)
29
+ else
30
+ report[key_name] = Report.report_info(self.send(parser_fn, mpd_leaf[key_name]))
31
+ end
32
+ end
33
+
34
+ def self.duration_iso8601_to_i(start)
35
+ ISO8601::Duration.new(start).to_seconds
36
+ end
37
+
38
+ def self.time_to_i(value)
39
+ Time.parse(value).to_i
40
+ end
41
+
42
+ def self.identity(value)
43
+ value
44
+ end
45
+
46
+ def self.to_i(value)
47
+ value.to_i
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,58 @@
1
+ module DashTimelineValidator
2
+ class Representation
3
+ def self.process(context, representation, index)
4
+ representation_result = {}
5
+ representation_result["name"] = "Representation-#{index}"
6
+
7
+ DashTimelineValidator::Report.fill_report(representation_result, representation, "bandwidth")
8
+ DashTimelineValidator::Report.fill_report(representation_result, representation, "codecs")
9
+ DashTimelineValidator::Report.fill_report(representation_result, representation, "presentationTimeOffset", 0, :duration_iso8601_to_i)
10
+
11
+ if representation.respond_to? "SegmentTemplate"
12
+ segment_template = representation.SegmentTemplate
13
+ representation_result["SegmentTemplate"] = {}
14
+
15
+ DashTimelineValidator::Report.fill_report_mandatory(representation_result["SegmentTemplate"], segment_template, "timescale", :to_i)
16
+ DashTimelineValidator::Report.fill_report_mandatory(representation_result["SegmentTemplate"], segment_template, "media")
17
+ DashTimelineValidator::Report.fill_report(representation_result["SegmentTemplate"], segment_template, "initialization")
18
+ DashTimelineValidator::Report.fill_report_mandatory(representation_result["SegmentTemplate"], segment_template, "startNumber")
19
+
20
+ if context[:root]["mpd"]["type"].eql? "dynamic"
21
+ report_edge_timeline_information = report_edge_timeline_information(context, representation_result, segment_template.SegmentTimeline.nodes)
22
+ representation_result["SegmentTemplate"]["timeline"] = report_edge_timeline_information
23
+ end
24
+
25
+ if segment_template.respond_to? "SegmentTimeline"
26
+ DashTimelineValidator::Segment.process({root: context[:root], previous: representation_result["SegmentTemplate"]}, segment_template.SegmentTimeline.nodes)
27
+ end
28
+ end
29
+
30
+ representation_result
31
+ end
32
+
33
+ def self.report_edge_timeline_information(context, representation_result, ss)
34
+ client_wallclock = context[:root]["client_wallclock"]
35
+ ast = context[:root]["mpd"]["availabilityStartTime"]
36
+ timescale = representation_result["SegmentTemplate"]["timescale"]
37
+ max_duration = ss.map { |s| s[:d].to_i }.max / timescale
38
+ min_buffer_time = context[:root]["mpd"]["minBufferTime"]
39
+ suggested_resentation_delay = context[:root]["mpd"]["suggestedPresentationDelay"]
40
+ default_presentation_delay = [DashTimelineValidator::Options::DEFAULT_PRESENTATION_DELAY, (min_buffer_time * 1.5)].max
41
+ timeline_delay = suggested_resentation_delay.nil? ? default_presentation_delay : suggested_resentation_delay
42
+
43
+ # suggested streaming edge based on shaka's behavior
44
+ streaming_edge = client_wallclock - ast - DashTimelineValidator::Options::BUFFERED_SEGMENTS * max_duration - timeline_delay
45
+
46
+ last_segment = ss.last
47
+ last_available_time = (last_segment[:t].to_i + (last_segment[:d].to_i * last_segment[:r].to_i)) / timescale
48
+
49
+ if streaming_edge > last_available_time
50
+ report = DashTimelineValidator::Report.report_warn("Live edge is at #{streaming_edge}s but last segment in timeline starts at #{last_available_time}s")
51
+ else
52
+ report = DashTimelineValidator::Report.report_info("Live edge is at #{streaming_edge}s and last segment in timeline starts at #{last_available_time}s")
53
+ end
54
+
55
+ report
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,67 @@
1
+ module DashTimelineValidator
2
+ class Segment
3
+ def self.process(context, ss)
4
+ mpd_type = context[:root]["mpd"]["type"]
5
+ previous = context[:previous]
6
+ previous["S"] = []
7
+
8
+ ss.each_with_index do |s, index|
9
+ unless s.respond_to? "d"
10
+ previous["S"].push(DashTimelineValidator::Report.report_error("Segment (#{index + 1}) doen't have mandatory value for 'd'"))
11
+ error_exit(previous)
12
+ end
13
+ if mpd_type.eql? "dynamic" and !s.respond_to? "t"
14
+ previous["S"].push(DashTimelineValidator::Report.report_warn("Segment <S d='#{s[:d]}' t='#{s[:t]}'/> doen't have a value for 't', it's necessary for MPD with 'dynamic' type"))
15
+ end
16
+ end
17
+
18
+ timeline_segments = ss.map { |s| {d: s.d.to_i, t: s.respond_to?("t") ? s.t.to_i : 0, r: s.respond_to?("r") ? s.r.to_i : 0} }
19
+
20
+ current_segment_number = context[:previous]["startNumber"].to_i
21
+
22
+ timeline_segments.each_with_index do |current_segment, index|
23
+ unless index.zero?
24
+ previous_segment = timeline_segments[index - 1]
25
+ current_segment_time = current_segment[:t]
26
+ expected_segment_time = (previous_segment[:t] + (previous_segment[:d]) * (1 + previous_segment[:r]))
27
+ drift = (expected_segment_time - current_segment_time).abs
28
+ if drift > DashTimelineValidator::Options::ACCEPTABLE_DRIFT
29
+ previous["S"].push(DashTimelineValidator::Report.report_warn("Timeline of <S d='#{current_segment[:d]}' t='#{current_segment[:t]}'/> was expected to be #{expected_segment_time}, but is #{current_segment_time} (drift = #{drift})"))
30
+ end
31
+ end
32
+
33
+ if DashTimelineValidator::Options::VERIFY_SEGMENTS_DURATION
34
+ (current_segment[:r].to_i + 1).times do |i|
35
+ duration_report = check_segment_duration(context, current_segment, current_segment_number, i.zero?)
36
+ previous["S"].push(duration_report) if duration_report
37
+ current_segment_number += 1
38
+ end
39
+ end
40
+ end
41
+ previous.delete("S") if previous["S"].empty?
42
+ end
43
+
44
+ def self.check_segment_duration(context, current_segment, current_segment_number, download_init = true)
45
+ init = context[:previous]["initialization"]
46
+ media = context[:previous]["media"]
47
+ base_path = context[:root]["base_path"]
48
+ init_path = "#{DashTimelineValidator::Options::ANALYZER_FOLDER}/#{init}"
49
+
50
+ DashTimelineValidator::DashFile.fetch_file("#{base_path}/#{init}", init_path) if download_init
51
+
52
+ segment_file = media.gsub("$Number$", current_segment_number.to_s)
53
+ segment_path = "#{DashTimelineValidator::Options::ANALYZER_FOLDER}/#{segment_file}"
54
+ full_segment_path = "#{DashTimelineValidator::Options::ANALYZER_FOLDER}/#{segment_file}".gsub(".", "-complete.")
55
+ DashTimelineValidator::DashFile.fetch_file("#{base_path}/#{segment_file}", segment_path)
56
+
57
+ `cat #{init_path} #{segment_path} > #{full_segment_path}`
58
+ duration = `mediainfo --Inform="General;%Duration%" #{full_segment_path}`.to_i
59
+ File.delete segment_path
60
+ File.delete full_segment_path
61
+ mediainfo_duration = duration.to_f / 1000 * context[:previous]["timescale"].to_i
62
+ if (mediainfo_duration != current_segment[:d])
63
+ return DashTimelineValidator::Report.report_warn("Mediainfo shows different duration for #{segment_file} compared to the advertised segment timeline item (#{(mediainfo_duration - current_segment[:d]).abs})")
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,63 @@
1
+ require "ox"
2
+
3
+ module DashTimelineValidator
4
+ class Validator
5
+ def self.analyze(manifest, mpd_content)
6
+ report = {}
7
+ mpd = nil
8
+
9
+ if DashTimelineValidator::DashFile.uri? manifest
10
+ uri = URI(manifest)
11
+ report["base_path"] = DashTimelineValidator::Report.report_info("#{uri.scheme}://#{uri.host}#{uri.path.split("/").reverse.drop(1).reverse.join("/")}")
12
+ else
13
+ report["base_path"] = DashTimelineValidator::Report.report_info(DashTimelineValidator::DashFile::ANALYZER_FOLDER)
14
+ end
15
+
16
+ begin
17
+ mpd = Ox.load(mpd_content)
18
+ rescue
19
+ DashTimelineValidator::Log.error("Error while parsing #{manifest} it might be malformed (a non 2xx response)")
20
+ error_exit(report)
21
+ end
22
+
23
+ report["client_wallclock"] = DashTimelineValidator::Report.report_info(client_wallclock(mpd))
24
+
25
+ report["mpd"] = {}
26
+
27
+ DashTimelineValidator::Report.fill_report(report["mpd"], mpd.MPD, "type", "static")
28
+ DashTimelineValidator::Report.fill_report_mandatory(report["mpd"], mpd.MPD, "minBufferTime", :duration_iso8601_to_i)
29
+
30
+ if report["mpd"]["type"] == "dynamic"
31
+ DashTimelineValidator::Report.fill_report_mandatory(report["mpd"], mpd.MPD, "availabilityStartTime", :time_to_i)
32
+ if mpd.MPD.respond_to? "suggestedPresentationDelay"
33
+ DashTimelineValidator::Report.fill_report(report["mpd"], mpd.MPD, "suggestedPresentationDelay", 0, :duration_iso8601_to_i)
34
+ end
35
+ else
36
+ DashTimelineValidator::Report.fill_report(report["mpd"], mpd.MPD, "availabilityStartTime", Time.now.iso8601(0), :time_to_i)
37
+ end
38
+
39
+ all_periods = mpd.MPD.nodes.select { |n| n.name == "Period" }
40
+ report["mpd"]["periods"] = all_periods.each_with_index.map do |period, index|
41
+ DashTimelineValidator::Period.process({root: report, previous: report["mpd"]}, period, index)
42
+ end
43
+
44
+ report
45
+ end
46
+
47
+ def self.client_wallclock(mpd)
48
+ if mpd.MPD.respond_to? "UTCTiming"
49
+ if mpd.MPD.UTCTiming["schemeIdUri"].eql? "urn:mpeg:dash:utc:direct:2014"
50
+ raw_time = mpd.MPD.UTCTiming["value"]
51
+ elsif mpd.MPD.UTCTiming["schemeIdUri"].eql? "urn:mpeg:dash:utc:http-iso:2014"
52
+ raw_time = Net::HTTP.get(URI.parse(mpd.MPD.UTCTiming["value"]))
53
+ end
54
+ end
55
+
56
+ if raw_time.nil?
57
+ return DateTime.now.to_time.to_i
58
+ end
59
+
60
+ DateTime.parse(raw_time).to_time.to_i
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module DashTimelineValidator
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dash_timeline_validator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ana Carolina Castro
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-04-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: iso8601
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.10.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.10.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: ox
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.9.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.9.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: awesome_print
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.8.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.8.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ description: MPEG-Dash Timeline Validator.
98
+ email:
99
+ - ana.castro@corp.globo.com
100
+ executables:
101
+ - dash_timeline_validator
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".travis.yml"
108
+ - Gemfile
109
+ - Gemfile.lock
110
+ - LICENSE
111
+ - README.md
112
+ - Rakefile
113
+ - bin/console
114
+ - bin/setup
115
+ - dash_timeline_validator.gemspec
116
+ - exe/dash_timeline_validator
117
+ - lib/dash_timeline_validator.rb
118
+ - lib/dash_timeline_validator/adaptation_set.rb
119
+ - lib/dash_timeline_validator/cli.rb
120
+ - lib/dash_timeline_validator/file.rb
121
+ - lib/dash_timeline_validator/log.rb
122
+ - lib/dash_timeline_validator/options.rb
123
+ - lib/dash_timeline_validator/period.rb
124
+ - lib/dash_timeline_validator/report.rb
125
+ - lib/dash_timeline_validator/representation.rb
126
+ - lib/dash_timeline_validator/segment.rb
127
+ - lib/dash_timeline_validator/validator.rb
128
+ - lib/dash_timeline_validator/version.rb
129
+ homepage: https://github.com/globocom/dash_timeline_validator
130
+ licenses: []
131
+ metadata:
132
+ homepage_uri: https://github.com/globocom/dash_timeline_validator
133
+ source_code_uri: https://github.com/globocom/dash_timeline_validator
134
+ changelog_uri: https://github.com/globocom/dash_timeline_validator
135
+ post_install_message:
136
+ rdoc_options: []
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubyforge_project:
151
+ rubygems_version: 2.7.3
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: MPEG-Dash Timeline Validator
155
+ test_files: []