/* * Copyright 2013-2016 Skynav, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY SKYNAV, INC. AND ITS CONTRIBUTORS “AS IS” AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL SKYNAV, INC. OR ITS CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.skynav.ttx.transformer.isd; import java.util.List; import java.util.Map; import javax.xml.namespace.QName; import com.skynav.ttv.model.value.Time; import com.skynav.ttv.model.value.TimeParameters; import com.skynav.ttv.util.Reporter; import com.skynav.ttv.verifier.util.Timing; import com.skynav.ttx.transformer.TransformerContext; import com.skynav.ttx.util.TimeCoordinate; import com.skynav.ttx.util.TimeInterval; public class TimingState { public static final QName beginAttributeName = new QName(TTMLHelper.NAMESPACE_ISD,"begin"); public static final QName endAttributeName = new QName(TTMLHelper.NAMESPACE_ISD,"end"); // contextual state private TransformerContext context; // transformer context object private ISDHelper helper; // helper private Object content; // (jaxb) content object private TimeParameters timeParameters; // timing parameters // explicit timing state private TimeCoordinate durExplicit = TimeCoordinate.UNSPECIFIED; private TimeCoordinate beginExplicit = TimeCoordinate.UNSPECIFIED; private TimeCoordinate endExplicit = TimeCoordinate.UNSPECIFIED; // implicit timing state private TimeCoordinate durImplicit = TimeCoordinate.UNRESOLVED; // active timing state private TimeCoordinate beginActive = TimeCoordinate.UNRESOLVED; private TimeCoordinate endActive = TimeCoordinate.UNRESOLVED; public TimingState(TransformerContext context, Object content, TimeParameters timeParameters) { this.context = context; this.helper = (ISDHelper) context.getResourceState(ResourceState.isdHelper.name()); this.content = content; this.timeParameters = timeParameters; } public TimeCoordinate getSimpleDuration() { // SMIL 3.0 Timing §5.4.5 "Defining the Simple Duration" if (durExplicit.isUnspecified()) { if (endExplicit.isSpecified()) return TimeCoordinate.INDEFINITE; else if (durImplicit.isUnresolved()) return TimeCoordinate.UNRESOLVED; else return durImplicit; } else if (durExplicit.isIndefinite()) { return TimeCoordinate.INDEFINITE; } else return durExplicit; } public TimeCoordinate getBegin() { if (beginExplicit.isSpecified()) return beginExplicit; else return TimeCoordinate.ZERO; } public TimeCoordinate getEnd() { if (endExplicit.isSpecified()) return endExplicit; else return TimeCoordinate.UNRESOLVED; } public TimeCoordinate getRepeatCount() { return TimeCoordinate.UNSPECIFIED; } public TimeCoordinate getRepeatDuration() { return TimeCoordinate.UNSPECIFIED; } public TimeCoordinate getMinimum() { return TimeCoordinate.ZERO; } public TimeCoordinate getMaximum() { return TimeCoordinate.INDEFINITE; } private TimeCoordinate computeIntermediateActiveDuration() { // SMIL 3.0 Timing §5.4.5 "Intermediate Active Duration Computation" TimeCoordinate p0 = getSimpleDuration(); TimeCoordinate rc = getRepeatCount(); TimeCoordinate rd = getRepeatDuration(); TimeCoordinate p1 = rc.isUnspecified() ? TimeCoordinate.INDEFINITE : TimeCoordinate.mul(rc, rd); TimeCoordinate p2 = rd.isUnspecified() ? TimeCoordinate.INDEFINITE : rd; if (p0.isZero()) return TimeCoordinate.ZERO; else if (rc.isUnspecified() && rd.isUnspecified()) return p0; else return TimeCoordinate.min(p1, TimeCoordinate.min(p2, TimeCoordinate.INDEFINITE)); } public TimeCoordinate getActiveDuration() { // SMIL 3.0 Timing §5.4.5 "Computing the Active Duration" TimeCoordinate b = getBegin(); TimeCoordinate e = getEnd(); TimeCoordinate pad; if (endExplicit.isSpecified() && durExplicit.isUnspecified()) { if (e.isDefinite()) pad = TimeCoordinate.sub(e, b); else if (e.isIndefinite()) pad = TimeCoordinate.INDEFINITE; else pad = TimeCoordinate.UNRESOLVED; } else { TimeCoordinate iad = computeIntermediateActiveDuration(); if (endExplicit.isUnspecified() || endExplicit.isIndefinite()) { pad = iad; } else { pad = TimeCoordinate.min(iad, TimeCoordinate.sub(e, b)); } } TimeCoordinate min = getMinimum(); TimeCoordinate max = getMaximum(); return TimeCoordinate.min(max, TimeCoordinate.max(min, pad)); } public TimeCoordinate getActiveBegin() { return beginActive; } public TimeCoordinate getActiveEnd() { return endActive; } public void resolveExplicit() { resolveExplicitDuration(); resolveExplicitBegin(); resolveExplicitEnd(); } private void debug(int level, String label, String details) { Reporter reporter = context.getReporter(); if (reporter.getDebugLevel() >= level) { reporter.logDebug(reporter.message("*KEY*", "{0}: {1}[{2}].", label, helper.getClassString(content), details)); } } private void resolveExplicitDuration() { TimeCoordinate durExplicit = getDurationAttribute(content, timeParameters); if (durExplicit.isInvalid()) durExplicit = TimeCoordinate.UNSPECIFIED; this.durExplicit = durExplicit; if (!durExplicit.isUnspecified()) debug(2, "Resolve explicit duration", durExplicit.toString()); } private void resolveExplicitBegin() { TimeCoordinate beginExplicit = getBeginAttribute(content, timeParameters); if (beginExplicit.isInvalid()) beginExplicit = TimeCoordinate.UNSPECIFIED; this.beginExplicit = beginExplicit; if (!beginExplicit.isUnspecified()) debug(2, "Resolve explicit begin", beginExplicit.toString()); } private void resolveExplicitEnd() { TimeCoordinate endExplicit = getEndAttribute(content, timeParameters); if (endExplicit.isInvalid()) endExplicit = TimeCoordinate.UNSPECIFIED; this.endExplicit = endExplicit; if (!endExplicit.isUnspecified()) debug(2, "Resolve explicit end", endExplicit.toString()); } public void resolveImplicit() { resolveImplicitDuration(); } private void resolveImplicitDuration() { TimeCoordinate durImplicit = this.durImplicit; if (helper.isAnonymousSpan(content)) { if (helper.isSequenceContainer(getParent(content))) durImplicit = TimeCoordinate.ZERO; else durImplicit = TimeCoordinate.INDEFINITE; } else if (helper.isSequenceContainer(content)) { TimeCoordinate sum = null; for (TimingState ts : getChildrenTiming(content, timeParameters)) { TimeCoordinate begin = ts.getBegin(); TimeCoordinate activeDuration = ts.getActiveDuration(); TimeCoordinate end = TimeCoordinate.add(begin, activeDuration); if (sum == null) sum = end; else sum = TimeCoordinate.add(sum, end); } if (sum != null) durImplicit = sum; } else { TimeCoordinate max = null; for (TimingState ts : getChildrenTiming(content, timeParameters)) { TimeCoordinate begin = ts.getBegin(); TimeCoordinate activeDuration = ts.getActiveDuration(); TimeCoordinate end = TimeCoordinate.add(begin, activeDuration); if (max == null) max = end; else max = TimeCoordinate.max(max, end); } if (max != null) durImplicit = max; } if (durExplicit.isSpecified()) { TimeCoordinate durSimple = getSimpleDuration(); if (durImplicit.compareTo(durSimple) != 0) durImplicit = durSimple; } else if (helper.isTimedText(content)) { double externalDuration = timeParameters.getExternalDuration(); if (!Double.isNaN(externalDuration)) durImplicit = new TimeCoordinate(externalDuration); } this.durImplicit = durImplicit; debug(2, "Resolve implicit duration", durImplicit.toString()); } public void resolveActive() { TimeCoordinate b = getBegin(); TimeCoordinate e = getEnd(); if (helper.isTimedText(content)) { beginActive = b; endActive = e; if (!endActive.isDefinite()) endActive = TimeCoordinate.add(beginActive, getActiveDuration()); if (!endActive.isDefinite()) endActive = beginActive; } else { Object parent = getParent(content); TimingState tsParent = getTimingState(parent, timeParameters); TimeCoordinate bParent = (tsParent != null) ? tsParent.getActiveBegin() : TimeCoordinate.ZERO; TimeCoordinate reference; if (helper.isSequenceContainer(parent)) { TimingState tsElder = getTimingState(getPrevSibling(content, parent), timeParameters); reference = (tsElder != null) ? tsElder.getActiveEnd() : bParent; } else { reference = bParent; } // set active begin beginActive = TimeCoordinate.add(reference, b); // set active end endActive = TimeCoordinate.add(reference, e); if (!endActive.isDefinite()) { assert beginActive.isDefinite(); endActive = TimeCoordinate.add(beginActive, getActiveDuration()); } // clip active end to parent's active end TimeCoordinate eParent = (tsParent != null) ? tsParent.getActiveEnd() : TimeCoordinate.UNRESOLVED; if (!endActive.isDefinite() || endActive.compareTo(eParent) > 0) endActive = eParent; // pin active end to active begin if the former precedes the latter if (!endActive.isDefinite() || (endActive.compareTo(beginActive) < 0)) endActive = beginActive; } debug(2, "Resolve active", getActiveBegin() + "," + getActiveEnd()); // record active interval attributes Map<QName,String> otherAttributes = ISDHelper.getOtherAttributes(content); if (otherAttributes != null) { otherAttributes.put(beginAttributeName, beginActive.toString().toLowerCase()); otherAttributes.put(endAttributeName, endActive.toString().toLowerCase()); } } public void extractActiveInterval(java.util.Set<TimeInterval> intervals) { intervals.add(new TimeInterval(beginActive, endActive)); } private Object getParent(Object content) { Map<Object,Object> parents = ISD.getParents(context); if (parents.containsKey(content)) return parents.get(content); else return null; } private Object getPrevSibling(Object content, Object parent) { if (parent == null) return null; Object prevSibling = null; for (Object sibling : helper.getChildren(parent)) { if (sibling == content) return prevSibling; prevSibling = sibling; } return null; } private TimingState getTimingState(Object content, TimeParameters timeParameters) { if (content == null) return null; Map<Object,TimingState> timingStates = ISD.getTimingStates(context); if (!timingStates.containsKey(content)) timingStates.put(content, new TimingState(context, content, timeParameters)); return timingStates.get(content); } private List<TimingState> getChildrenTiming(Object content, TimeParameters timeParameters) { List<TimingState> childrenTiming = new java.util.ArrayList<TimingState>(); for (Object child: helper.getChildren(content)) { TimingState ts = getTimingState(child, timeParameters); childrenTiming.add(ts); } return childrenTiming; } @Override public String toString() { return "[b(" + getBegin() + "),e(" + getEnd() + "),d(" + getSimpleDuration() + "),di(" + durImplicit + ")]"; } public static TimeCoordinate getTimeCoordinateAttribute(Object content, String name, TimeParameters timeParameters) { String value = ISDHelper.getStringValuedAttribute(content, name); if (value != null) { Time[] times = new Time[1]; if (Timing.isCoordinate(value, null, null, timeParameters, times)) { assert times.length > 0; return TimeCoordinate.fromValue(times[0].getTime(timeParameters)); } else { return TimeCoordinate.INVALID; } } else return TimeCoordinate.UNSPECIFIED; } public static TimeCoordinate getDurationAttribute(Object content, TimeParameters timeParameters) { return getTimeCoordinateAttribute(content, "dur", timeParameters); } public static TimeCoordinate getBeginAttribute(Object content, TimeParameters timeParameters) { return getTimeCoordinateAttribute(content, "begin", timeParameters); } public static TimeCoordinate getEndAttribute(Object content, TimeParameters timeParameters) { return getTimeCoordinateAttribute(content, "end", timeParameters); } }