1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package android.media.videoeditor;
19
20import java.io.File;
21import java.util.ArrayList;
22import java.util.List;
23
24import android.media.videoeditor.MediaArtistNativeHelper.AlphaMagicSettings;
25import android.media.videoeditor.MediaArtistNativeHelper.AudioTransition;
26import android.media.videoeditor.MediaArtistNativeHelper.ClipSettings;
27import android.media.videoeditor.MediaArtistNativeHelper.EditSettings;
28import android.media.videoeditor.MediaArtistNativeHelper.EffectSettings;
29import android.media.videoeditor.MediaArtistNativeHelper.SlideTransitionSettings;
30import android.media.videoeditor.MediaArtistNativeHelper.TransitionSettings;
31import android.media.videoeditor.MediaArtistNativeHelper.VideoTransition;
32
33/**
34 * This class is super class for all transitions. Transitions (with the
35 * exception of TransitionAtStart and TransitioAtEnd) can only be inserted
36 * between media items.
37 *
38 * Adding a transition between MediaItems makes the
39 * duration of the storyboard shorter by the duration of the Transition itself.
40 * As a result, if the duration of the transition is larger than the smaller
41 * duration of the two MediaItems associated with the Transition, an exception
42 * will be thrown.
43 *
44 * During a transition, the audio track are cross-fading
45 * automatically. {@hide}
46 */
47public abstract class Transition {
48    /**
49     *  The transition behavior
50     */
51    private static final int BEHAVIOR_MIN_VALUE = 0;
52
53    /** The transition starts slowly and speed up */
54    public static final int BEHAVIOR_SPEED_UP = 0;
55    /** The transition start fast and speed down */
56    public static final int BEHAVIOR_SPEED_DOWN = 1;
57    /** The transition speed is constant */
58    public static final int BEHAVIOR_LINEAR = 2;
59    /** The transition starts fast and ends fast with a slow middle */
60    public static final int BEHAVIOR_MIDDLE_SLOW = 3;
61    /** The transition starts slowly and ends slowly with a fast middle */
62    public static final int BEHAVIOR_MIDDLE_FAST = 4;
63
64    private static final int BEHAVIOR_MAX_VALUE = 4;
65
66    /**
67     *  The unique id of the transition
68     */
69    private final String mUniqueId;
70
71    /**
72     *  The transition is applied at the end of this media item
73     */
74    private final MediaItem mAfterMediaItem;
75    /**
76     *  The transition is applied at the beginning of this media item
77     */
78    private final MediaItem mBeforeMediaItem;
79
80    /**
81     *  The transition behavior
82     */
83    protected final int mBehavior;
84
85    /**
86     *  The transition duration
87     */
88    protected long mDurationMs;
89
90    /**
91     *  The transition filename
92     */
93    protected String mFilename;
94
95    protected MediaArtistNativeHelper mNativeHelper;
96    /**
97     * An object of this type cannot be instantiated by using the default
98     * constructor
99     */
100    @SuppressWarnings("unused")
101    private Transition() {
102        this(null, null, null, 0, 0);
103    }
104
105    /**
106     * Constructor
107     *
108     * @param transitionId The transition id
109     * @param afterMediaItem The transition is applied to the end of this
110     *      media item
111     * @param beforeMediaItem The transition is applied to the beginning of
112     *      this media item
113     * @param durationMs The duration of the transition in milliseconds
114     * @param behavior The transition behavior
115     */
116    protected Transition(String transitionId, MediaItem afterMediaItem,
117                         MediaItem beforeMediaItem,long durationMs,
118                         int behavior) {
119        if (behavior < BEHAVIOR_MIN_VALUE || behavior > BEHAVIOR_MAX_VALUE) {
120            throw new IllegalArgumentException("Invalid behavior: " + behavior);
121        }
122        if ((afterMediaItem == null) && (beforeMediaItem == null)) {
123            throw new IllegalArgumentException("Null media items");
124        }
125        mUniqueId = transitionId;
126        mAfterMediaItem = afterMediaItem;
127        mBeforeMediaItem = beforeMediaItem;
128        mDurationMs = durationMs;
129        mBehavior = behavior;
130        mNativeHelper = null;
131        if (durationMs > getMaximumDuration()) {
132            throw new IllegalArgumentException("The duration is too large");
133        }
134        if (afterMediaItem != null) {
135            mNativeHelper = afterMediaItem.getNativeContext();
136        }else {
137            mNativeHelper = beforeMediaItem.getNativeContext();
138        }
139    }
140
141    /**
142     * Get the ID of the transition.
143     *
144     * @return The ID of the transition
145     */
146    public String getId() {
147        return mUniqueId;
148    }
149
150    /**
151     * Get the media item at the end of which the transition is applied.
152     *
153     * @return The media item at the end of which the transition is applied
154     */
155    public MediaItem getAfterMediaItem() {
156        return mAfterMediaItem;
157    }
158
159    /**
160     * Get the media item at the beginning of which the transition is applied.
161     *
162     * @return The media item at the beginning of which the transition is
163     *      applied
164     */
165    public MediaItem getBeforeMediaItem() {
166        return mBeforeMediaItem;
167    }
168
169    /**
170     * Set the duration of the transition.
171     *
172     * @param durationMs the duration of the transition in milliseconds
173     */
174    public void setDuration(long durationMs) {
175        if (durationMs > getMaximumDuration()) {
176            throw new IllegalArgumentException("The duration is too large");
177        }
178
179        mDurationMs = durationMs;
180        invalidate();
181        mNativeHelper.setGeneratePreview(true);
182    }
183
184    /**
185     * Get the duration of the transition.
186     *
187     * @return the duration of the transition in milliseconds
188     */
189    public long getDuration() {
190        return mDurationMs;
191    }
192
193    /**
194     * The duration of a transition cannot be greater than half of the minimum
195     * duration of the bounding media items.
196     *
197     * @return The maximum duration of this transition
198     */
199    public long getMaximumDuration() {
200        if (mAfterMediaItem == null) {
201            return mBeforeMediaItem.getTimelineDuration() / 2;
202        } else if (mBeforeMediaItem == null) {
203            return mAfterMediaItem.getTimelineDuration() / 2;
204        } else {
205            return (Math.min(mAfterMediaItem.getTimelineDuration(),
206                    mBeforeMediaItem.getTimelineDuration()) / 2);
207        }
208    }
209
210    /**
211     * Get the behavior of the transition.
212     *
213     * @return The behavior
214     */
215    public int getBehavior() {
216        return mBehavior;
217    }
218
219    /**
220     * Get the transition data.
221     *
222     * @return The transition data in TransitionSettings object
223     * {@link android.media.videoeditor.MediaArtistNativeHelper.TransitionSettings}
224     */
225    TransitionSettings getTransitionSettings() {
226        TransitionAlpha transitionAlpha = null;
227        TransitionSliding transitionSliding = null;
228        TransitionCrossfade transitionCrossfade = null;
229        TransitionFadeBlack transitionFadeBlack = null;
230        TransitionSettings transitionSetting = null;
231        transitionSetting = new TransitionSettings();
232        transitionSetting.duration = (int)getDuration();
233        if (this instanceof TransitionAlpha) {
234            transitionAlpha = (TransitionAlpha)this;
235            transitionSetting.videoTransitionType = VideoTransition.ALPHA_MAGIC;
236            transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE;
237            transitionSetting.transitionBehaviour = mNativeHelper
238            .getVideoTransitionBehaviour(transitionAlpha.getBehavior());
239            transitionSetting.alphaSettings = new AlphaMagicSettings();
240            transitionSetting.slideSettings = null;
241            transitionSetting.alphaSettings.file = transitionAlpha.getPNGMaskFilename();
242            transitionSetting.alphaSettings.blendingPercent = transitionAlpha.getBlendingPercent();
243            transitionSetting.alphaSettings.invertRotation = transitionAlpha.isInvert();
244            transitionSetting.alphaSettings.rgbWidth = transitionAlpha.getRGBFileWidth();
245            transitionSetting.alphaSettings.rgbHeight = transitionAlpha.getRGBFileHeight();
246
247        } else if (this instanceof TransitionSliding) {
248            transitionSliding = (TransitionSliding)this;
249            transitionSetting.videoTransitionType = VideoTransition.SLIDE_TRANSITION;
250            transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE;
251            transitionSetting.transitionBehaviour = mNativeHelper
252            .getVideoTransitionBehaviour(transitionSliding.getBehavior());
253            transitionSetting.alphaSettings = null;
254            transitionSetting.slideSettings = new SlideTransitionSettings();
255            transitionSetting.slideSettings.direction = mNativeHelper
256            .getSlideSettingsDirection(transitionSliding.getDirection());
257        } else if (this instanceof TransitionCrossfade) {
258            transitionCrossfade = (TransitionCrossfade)this;
259            transitionSetting.videoTransitionType = VideoTransition.CROSS_FADE;
260            transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE;
261            transitionSetting.transitionBehaviour = mNativeHelper
262            .getVideoTransitionBehaviour(transitionCrossfade.getBehavior());
263            transitionSetting.alphaSettings = null;
264            transitionSetting.slideSettings = null;
265        } else if (this instanceof TransitionFadeBlack) {
266            transitionFadeBlack = (TransitionFadeBlack)this;
267            transitionSetting.videoTransitionType = VideoTransition.FADE_BLACK;
268            transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE;
269            transitionSetting.transitionBehaviour = mNativeHelper
270            .getVideoTransitionBehaviour(transitionFadeBlack.getBehavior());
271            transitionSetting.alphaSettings = null;
272            transitionSetting.slideSettings = null;
273        }
274
275        return transitionSetting;
276    }
277
278    /**
279     * Checks if the effect and overlay applied on a media item
280     * overlaps with the transition on media item.
281     *
282     * @param m The media item
283     * @param clipSettings The ClipSettings object
284     * @param clipNo The clip no.(out of the two media items
285     * associated with current transition)for which the effect
286     * clip should be generated
287     * @return List of effects that overlap with the transition
288     */
289
290    List<EffectSettings>  isEffectandOverlayOverlapping(MediaItem m, ClipSettings clipSettings,
291                                         int clipNo) {
292        List<Effect> effects;
293        List<Overlay> overlays;
294        List<EffectSettings> effectSettings = new ArrayList<EffectSettings>();
295        EffectSettings tmpEffectSettings;
296
297        overlays = m.getAllOverlays();
298        for (Overlay overlay : overlays) {
299            tmpEffectSettings = mNativeHelper.getOverlaySettings((OverlayFrame)overlay);
300            mNativeHelper.adjustEffectsStartTimeAndDuration(tmpEffectSettings,
301                    clipSettings.beginCutTime, clipSettings.endCutTime);
302            if (tmpEffectSettings.duration != 0) {
303                effectSettings.add(tmpEffectSettings);
304            }
305        }
306
307        effects = m.getAllEffects();
308        for (Effect effect : effects) {
309            if (effect instanceof EffectColor) {
310                tmpEffectSettings = mNativeHelper.getEffectSettings((EffectColor)effect);
311                mNativeHelper.adjustEffectsStartTimeAndDuration(tmpEffectSettings,
312                        clipSettings.beginCutTime, clipSettings.endCutTime);
313                if (tmpEffectSettings.duration != 0) {
314                    if (m instanceof MediaVideoItem) {
315                        tmpEffectSettings.fiftiesFrameRate = mNativeHelper
316                        .GetClosestVideoFrameRate(((MediaVideoItem)m).getFps());
317                    }
318                    effectSettings.add(tmpEffectSettings);
319                }
320            }
321        }
322
323         return effectSettings;
324    }
325
326    /**
327     * Generate the video clip for the specified transition. This method may
328     * block for a significant amount of time. Before the method completes
329     * execution it sets the mFilename to the name of the newly generated
330     * transition video clip file.
331     */
332    void generate() {
333        MediaItem m1 = this.getAfterMediaItem();
334        MediaItem m2 = this.getBeforeMediaItem();
335        ClipSettings clipSettings1 = new ClipSettings();
336        ClipSettings clipSettings2 = new ClipSettings();
337        TransitionSettings transitionSetting = null;
338        EditSettings editSettings = new EditSettings();
339        List<EffectSettings> effectSettings_clip1;
340        List<EffectSettings> effectSettings_clip2;
341
342        String output = null;
343
344        if (mNativeHelper == null) {
345            if (m1 != null)
346                mNativeHelper = m1.getNativeContext();
347            else if (m2 != null)
348                mNativeHelper = m2.getNativeContext();
349        }
350        transitionSetting = getTransitionSettings();
351        if (m1 != null && m2 != null) {
352            /* transition between media items */
353            clipSettings1 = m1.getClipSettings();
354            clipSettings2 = m2.getClipSettings();
355            clipSettings1.beginCutTime = (int)(clipSettings1.endCutTime -
356                                                              this.mDurationMs);
357            clipSettings2.endCutTime = (int)(clipSettings2.beginCutTime +
358                                                              this.mDurationMs);
359            /*
360             * Check how many effects and overlays overlap with transition and
361             * generate effect clip first if there is any overlap
362             */
363            effectSettings_clip1 = isEffectandOverlayOverlapping(m1, clipSettings1,1);
364            effectSettings_clip2 = isEffectandOverlayOverlapping(m2, clipSettings2,2);
365            for (int index = 0; index < effectSettings_clip2.size(); index++ ) {
366                effectSettings_clip2.get(index).startTime += this.mDurationMs;
367            }
368            editSettings.effectSettingsArray =
369                                              new EffectSettings[effectSettings_clip1.size()
370                                                 + effectSettings_clip2.size()];
371            int i=0,j=0;
372            while (i < effectSettings_clip1.size()) {
373                editSettings.effectSettingsArray[j] = effectSettings_clip1.get(i);
374                i++;
375                j++;
376            }
377            i=0;
378            while (i < effectSettings_clip2.size()) {
379                editSettings.effectSettingsArray[j] = effectSettings_clip2.get(i);
380                i++;
381                j++;
382            }
383        } else if (m1 == null && m2 != null) {
384            /* begin transition at first media item */
385            m2.generateBlankFrame(clipSettings1);
386            clipSettings2 = m2.getClipSettings();
387            clipSettings1.endCutTime = (int)(this.mDurationMs + 50);
388            clipSettings2.endCutTime = (int)(clipSettings2.beginCutTime +
389                                                              this.mDurationMs);
390            /*
391             * Check how many effects and overlays overlap with transition and
392             * generate effect clip first if there is any overlap
393             */
394            effectSettings_clip2 = isEffectandOverlayOverlapping(m2, clipSettings2,2);
395            for (int index = 0; index < effectSettings_clip2.size(); index++ ) {
396                effectSettings_clip2.get(index).startTime += this.mDurationMs;
397            }
398            editSettings.effectSettingsArray = new EffectSettings[effectSettings_clip2.size()];
399            int i=0, j=0;
400            while (i < effectSettings_clip2.size()) {
401                editSettings.effectSettingsArray[j] = effectSettings_clip2.get(i);
402                i++;
403                j++;
404            }
405        } else if (m1 != null && m2 == null) {
406            /* end transition at last media item */
407            clipSettings1 = m1.getClipSettings();
408            m1.generateBlankFrame(clipSettings2);
409            clipSettings1.beginCutTime = (int)(clipSettings1.endCutTime -
410                                                              this.mDurationMs);
411            clipSettings2.endCutTime = (int)(this.mDurationMs + 50);
412            /*
413             * Check how many effects and overlays overlap with transition and
414             * generate effect clip first if there is any overlap
415             */
416            effectSettings_clip1 = isEffectandOverlayOverlapping(m1, clipSettings1,1);
417            editSettings.effectSettingsArray = new EffectSettings[effectSettings_clip1.size()];
418            int i=0,j=0;
419            while (i < effectSettings_clip1.size()) {
420                editSettings.effectSettingsArray[j] = effectSettings_clip1.get(i);
421                i++;
422                j++;
423            }
424        }
425
426        editSettings.clipSettingsArray = new ClipSettings[2];
427        editSettings.clipSettingsArray[0] = clipSettings1;
428        editSettings.clipSettingsArray[1] = clipSettings2;
429        editSettings.backgroundMusicSettings = null;
430        editSettings.transitionSettingsArray = new TransitionSettings[1];
431        editSettings.transitionSettingsArray[0] = transitionSetting;
432        output = mNativeHelper.generateTransitionClip(editSettings, mUniqueId,
433                                                      m1, m2,this);
434        setFilename(output);
435    }
436
437
438    /**
439     * Set the transition filename.
440     */
441    void setFilename(String filename) {
442        mFilename = filename;
443    }
444
445    /**
446     * Get the transition filename.
447     */
448    String getFilename() {
449        return mFilename;
450    }
451
452    /**
453     * Remove any resources associated with this transition
454     */
455    void invalidate() {
456        if (mFilename != null) {
457            new File(mFilename).delete();
458            mFilename = null;
459        }
460    }
461
462    /**
463     * Check if the transition is generated.
464     *
465     * @return true if the transition is generated
466     */
467    boolean isGenerated() {
468        return (mFilename != null);
469    }
470
471    /*
472     * {@inheritDoc}
473     */
474    @Override
475    public boolean equals(Object object) {
476        if (!(object instanceof Transition)) {
477            return false;
478        }
479        return mUniqueId.equals(((Transition)object).mUniqueId);
480    }
481
482    /*
483     * {@inheritDoc}
484     */
485    @Override
486    public int hashCode() {
487        return mUniqueId.hashCode();
488    }
489}
490