dash_timeline_validator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []