module DashTimelineValidator class Segment def self.process(context, ss) mpd_type = context[:root]["mpd"]["type"] previous = context[:previous] previous["S"] = [] ss.each_with_index do |s, index| unless s.respond_to? "d" previous["S"].push(DashTimelineValidator::Report.report_error("Segment (#{index + 1}) doen't have mandatory value for 'd'")) error_exit(previous) end if mpd_type.eql? "dynamic" and !s.respond_to? "t" previous["S"].push(DashTimelineValidator::Report.report_warn("Segment doen't have a value for 't', it's necessary for MPD with 'dynamic' type")) end end 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} } current_segment_number = context[:previous]["startNumber"].to_i timeline_segments.each_with_index do |current_segment, index| unless index.zero? previous_segment = timeline_segments[index - 1] current_segment_time = current_segment[:t] expected_segment_time = (previous_segment[:t] + (previous_segment[:d]) * (1 + previous_segment[:r])) drift = (expected_segment_time - current_segment_time).abs if drift > DashTimelineValidator::Options::ACCEPTABLE_DRIFT previous["S"].push(DashTimelineValidator::Report.report_warn("Timeline of was expected to be #{expected_segment_time}, but is #{current_segment_time} (drift = #{drift})")) end end if DashTimelineValidator::Options::VERIFY_SEGMENTS_DURATION (current_segment[:r].to_i + 1).times do |i| duration_report = check_segment_duration(context, current_segment, current_segment_number, i.zero?) previous["S"].push(duration_report) if duration_report current_segment_number += 1 end end end previous.delete("S") if previous["S"].empty? end def self.check_segment_duration(context, current_segment, current_segment_number, download_init = true) init = context[:previous]["initialization"] media = context[:previous]["media"] base_path = context[:root]["base_path"] init_path = "#{DashTimelineValidator::Options::ANALYZER_FOLDER}/#{init}" DashTimelineValidator::DashFile.fetch_file("#{base_path}/#{init}", init_path) if download_init segment_file = media.gsub("$Number$", current_segment_number.to_s) segment_path = "#{DashTimelineValidator::Options::ANALYZER_FOLDER}/#{segment_file}" full_segment_path = "#{DashTimelineValidator::Options::ANALYZER_FOLDER}/#{segment_file}".gsub(".", "-complete.") DashTimelineValidator::DashFile.fetch_file("#{base_path}/#{segment_file}", segment_path) `cat #{init_path} #{segment_path} > #{full_segment_path}` duration = `mediainfo --Inform="General;%Duration%" #{full_segment_path}`.to_i File.delete segment_path File.delete full_segment_path mediainfo_duration = duration.to_f / 1000 * context[:previous]["timescale"].to_i if (mediainfo_duration != current_segment[:d]) 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})") end end end end