MediaItem.java revision ce03445c320042ca144bd4efd8c0356bb7775f9d
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
17package android.media.videoeditor;
18
19import java.io.IOException;
20import java.util.ArrayList;
21import java.util.List;
22
23import android.graphics.Bitmap;
24
25/**
26 * This abstract class describes the base class for any MediaItem. Objects are
27 * defined with a file path as a source data.
28 * {@hide}
29s */
30public abstract class MediaItem {
31    // A constant which can be used to specify the end of the file (instead of
32    // providing the actual duration of the media item).
33    public final static int END_OF_FILE = -1;
34
35    // Rendering modes
36    /**
37     * When using the RENDERING_MODE_BLACK_BORDER rendering mode video frames
38     * are resized by preserving the aspect ratio until the movie matches one of
39     * the dimensions of the output movie. The areas outside the resized video
40     * clip are rendered black.
41     */
42    public static final int RENDERING_MODE_BLACK_BORDER = 0;
43
44    /**
45     * When using the RENDERING_MODE_STRETCH rendering mode video frames are
46     * stretched horizontally or vertically to match the current aspect ratio of
47     * the video editor.
48     */
49    public static final int RENDERING_MODE_STRETCH = 1;
50
51    /**
52     * When using the RENDERING_MODE_CROPPING rendering mode video frames are
53     * scaled horizontally or vertically by preserving the original aspect
54     * ratio of the media item.
55     */
56    public static final int RENDERING_MODE_CROPPING = 2;
57
58
59    // The unique id of the MediaItem
60    private final String mUniqueId;
61
62    // The name of the file associated with the MediaItem
63    protected final String mFilename;
64
65    // List of effects
66    private final List<Effect> mEffects;
67
68    // List of overlays
69    private final List<Overlay> mOverlays;
70
71    // The rendering mode
72    private int mRenderingMode;
73
74    // Beginning and end transitions
75    protected Transition mBeginTransition;
76    protected Transition mEndTransition;
77
78    /**
79     * Constructor
80     *
81     * @param editor The video editor reference
82     * @param mediaItemId The MediaItem id
83     * @param filename name of the media file.
84     * @param renderingMode The rendering mode
85     *
86     * @throws IOException if file is not found
87     * @throws IllegalArgumentException if a capability such as file format is not
88     *             supported the exception object contains the unsupported
89     *             capability
90     */
91    protected MediaItem(VideoEditor editor, String mediaItemId, String filename,
92            int renderingMode) throws IOException {
93        mUniqueId = mediaItemId;
94        mFilename = filename;
95        mRenderingMode = renderingMode;
96        mEffects = new ArrayList<Effect>();
97        mOverlays = new ArrayList<Overlay>();
98        mBeginTransition = null;
99        mEndTransition = null;
100    }
101
102    /**
103     * @return The id of the media item
104     */
105    public String getId() {
106        return mUniqueId;
107    }
108
109    /**
110     * @return The media source file name
111     */
112    public String getFilename() {
113        return mFilename;
114    }
115
116    /**
117     * If aspect ratio of the MediaItem is different from the aspect ratio of
118     * the editor then this API controls the rendering mode.
119     *
120     * @param renderingMode rendering mode. It is one of:
121     *            {@link #RENDERING_MODE_BLACK_BORDER},
122     *            {@link #RENDERING_MODE_STRETCH}
123     */
124    public void setRenderingMode(int renderingMode) {
125        mRenderingMode = renderingMode;
126        if (mBeginTransition != null) {
127            mBeginTransition.invalidate();
128        }
129
130        if (mEndTransition != null) {
131            mEndTransition.invalidate();
132        }
133    }
134
135    /**
136     * @return The rendering mode
137     */
138    public int getRenderingMode() {
139        return mRenderingMode;
140    }
141
142    /**
143     * @param transition The beginning transition
144     */
145    void setBeginTransition(Transition transition) {
146        mBeginTransition = transition;
147    }
148
149    /**
150     * @return The begin transition
151     */
152    public Transition getBeginTransition() {
153        return mBeginTransition;
154    }
155
156    /**
157     * @param transition The end transition
158     */
159    void setEndTransition(Transition transition) {
160        mEndTransition = transition;
161    }
162
163    /**
164     * @return The end transition
165     */
166    public Transition getEndTransition() {
167        return mEndTransition;
168    }
169
170    /**
171     * @return The timeline duration. This is the actual duration in the
172     *      timeline (trimmed duration)
173     */
174    public abstract long getTimelineDuration();
175
176    /**
177     * @return The source file type
178     */
179    public abstract int getFileType();
180
181    /**
182     * @return Get the native width of the media item
183     */
184    public abstract int getWidth();
185
186    /**
187     * @return Get the native height of the media item
188     */
189    public abstract int getHeight();
190
191    /**
192     * Get aspect ratio of the source media item.
193     *
194     * @return the aspect ratio as described in MediaProperties.
195     *  MediaProperties.ASPECT_RATIO_UNDEFINED if aspect ratio is not
196     *  supported as in MediaProperties
197     */
198    public abstract int getAspectRatio();
199
200    /**
201     * Add the specified effect to this media item.
202     *
203     * Note that certain types of effects cannot be applied to video and to
204     * image media items. For example in certain implementation a Ken Burns
205     * implementation cannot be applied to video media item.
206     *
207     * This method invalidates transition video clips if the
208     * effect overlaps with the beginning and/or the end transition.
209     *
210     * @param effect The effect to apply
211     * @throws IllegalStateException if a preview or an export is in progress
212     * @throws IllegalArgumentException if the effect start and/or duration are
213     *      invalid or if the effect cannot be applied to this type of media
214     *      item or if the effect id is not unique across all the Effects
215     *      added.
216     */
217    public void addEffect(Effect effect) {
218        if (effect.getMediaItem() != this) {
219            throw new IllegalArgumentException("Media item mismatch");
220        }
221
222        if (mEffects.contains(effect)) {
223            throw new IllegalArgumentException("Effect already exists: " + effect.getId());
224        }
225
226        if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {
227            throw new IllegalArgumentException(
228                    "Effect start time + effect duration > media clip duration");
229        }
230
231        mEffects.add(effect);
232        invalidateTransitions(effect);
233    }
234
235    /**
236     * Remove the effect with the specified id.
237     *
238     * This method invalidates a transition video clip if the effect overlaps
239     * with a transition.
240     *
241     * @param effectId The id of the effect to be removed
242     *
243     * @return The effect that was removed
244     * @throws IllegalStateException if a preview or an export is in progress
245     */
246    public Effect removeEffect(String effectId) {
247        for (Effect effect : mEffects) {
248            if (effect.getId().equals(effectId)) {
249                mEffects.remove(effect);
250                invalidateTransitions(effect);
251                return effect;
252            }
253        }
254
255        return null;
256    }
257
258    /**
259     * Find the effect with the specified id
260     *
261     * @param effectId The effect id
262     *
263     * @return The effect with the specified id (null if it does not exist)
264     */
265    public Effect getEffect(String effectId) {
266        for (Effect effect : mEffects) {
267            if (effect.getId().equals(effectId)) {
268                return effect;
269            }
270        }
271
272        return null;
273    }
274
275    /**
276     * Get the list of effects.
277     *
278     * @return the effects list. If no effects exist an empty list will be returned.
279     */
280    public List<Effect> getAllEffects() {
281        return mEffects;
282    }
283
284    /**
285     * Add an overlay to the storyboard. This method invalidates a transition
286     * video clip if the overlay overlaps with a transition.
287     *
288     * @param overlay The overlay to add
289     * @throws IllegalStateException if a preview or an export is in progress or
290     *             if the overlay id is not unique across all the overlays
291     *             added or if the bitmap is not specified or if the dimensions of
292     *             the bitmap do not match the dimensions of the media item
293     */
294    public void addOverlay(Overlay overlay) {
295        if (overlay.getMediaItem() != this) {
296            throw new IllegalArgumentException("Media item mismatch");
297        }
298
299        if (mOverlays.contains(overlay)) {
300            throw new IllegalArgumentException("Overlay already exists: " + overlay.getId());
301        }
302
303        if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {
304            throw new IllegalArgumentException(
305                    "Overlay start time + overlay duration > media clip duration");
306        }
307
308        if (overlay instanceof OverlayFrame) {
309            final OverlayFrame frame = (OverlayFrame)overlay;
310            final Bitmap bitmap = frame.getBitmap();
311            if (bitmap == null) {
312                throw new IllegalArgumentException("Overlay bitmap not specified");
313            }
314
315            final int scaledWidth, scaledHeight;
316            if (this instanceof MediaVideoItem) {
317                scaledWidth = getWidth();
318                scaledHeight = getHeight();
319            } else {
320                scaledWidth = ((MediaImageItem)this).getScaledWidth();
321                scaledHeight = ((MediaImageItem)this).getScaledHeight();
322            }
323
324            // The dimensions of the overlay bitmap must be the same as the
325            // media item dimensions
326            if (bitmap.getWidth() != scaledWidth || bitmap.getHeight() != scaledHeight) {
327                throw new IllegalArgumentException(
328                        "Bitmap dimensions must match media item dimensions");
329            }
330        } else {
331            throw new IllegalArgumentException("Overlay not supported");
332        }
333
334        mOverlays.add(overlay);
335        invalidateTransitions(overlay);
336    }
337
338    /**
339     * Remove the overlay with the specified id.
340     *
341     * This method invalidates a transition video clip if the overlay overlaps
342     * with a transition.
343     *
344     * @param overlayId The id of the overlay to be removed
345     *
346     * @return The overlay that was removed
347     * @throws IllegalStateException if a preview or an export is in progress
348     */
349    public Overlay removeOverlay(String overlayId) {
350        for (Overlay overlay : mOverlays) {
351            if (overlay.getId().equals(overlayId)) {
352                mOverlays.remove(overlay);
353                if (overlay instanceof OverlayFrame) {
354                    ((OverlayFrame)overlay).invalidate();
355                }
356                invalidateTransitions(overlay);
357                return overlay;
358            }
359        }
360
361        return null;
362    }
363
364    /**
365     * Find the overlay with the specified id
366     *
367     * @param overlayId The overlay id
368     *
369     * @return The overlay with the specified id (null if it does not exist)
370     */
371    public Overlay getOverlay(String overlayId) {
372        for (Overlay overlay : mOverlays) {
373            if (overlay.getId().equals(overlayId)) {
374                return overlay;
375            }
376        }
377
378        return null;
379    }
380
381    /**
382     * Get the list of overlays associated with this media item
383     *
384     * Note that if any overlay source files are not accessible anymore,
385     * this method will still provide the full list of overlays.
386     *
387     * @return The list of overlays. If no overlays exist an empty list will
388     *  be returned.
389     */
390    public List<Overlay> getAllOverlays() {
391        return mOverlays;
392    }
393
394    /**
395     * Create a thumbnail at specified time in a video stream in Bitmap format
396     *
397     * @param width width of the thumbnail in pixels
398     * @param height height of the thumbnail in pixels
399     * @param timeMs The time in the source video file at which the thumbnail is
400     *            requested (even if trimmed).
401     *
402     * @return The thumbnail as a Bitmap.
403     *
404     * @throws IOException if a file error occurs
405     * @throws IllegalArgumentException if time is out of video duration
406     */
407    public abstract Bitmap getThumbnail(int width, int height, long timeMs) throws IOException;
408
409    /**
410     * Get the array of Bitmap thumbnails between start and end.
411     *
412     * @param width width of the thumbnail in pixels
413     * @param height height of the thumbnail in pixels
414     * @param startMs The start of time range in milliseconds
415     * @param endMs The end of the time range in milliseconds
416     * @param thumbnailCount The thumbnail count
417     *
418     * @return The array of Bitmaps
419     *
420     * @throws IOException if a file error occurs
421     */
422    public abstract Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,
423            int thumbnailCount) throws IOException;
424
425    /*
426     * {@inheritDoc}
427     */
428    @Override
429    public boolean equals(Object object) {
430        if (!(object instanceof MediaItem)) {
431            return false;
432        }
433        return mUniqueId.equals(((MediaItem)object).mUniqueId);
434    }
435
436    /*
437     * {@inheritDoc}
438     */
439    @Override
440    public int hashCode() {
441        return mUniqueId.hashCode();
442    }
443
444    /**
445     * Invalidate the start and end transitions if necessary
446     *
447     * @param effect The effect that was added or removed
448     */
449    void invalidateTransitions(Effect effect) {
450        // Check if the effect overlaps with the beginning and end transitions
451        if (mBeginTransition != null) {
452            if (effect.getStartTime() < mBeginTransition.getDuration()) {
453                mBeginTransition.invalidate();
454            }
455        }
456
457        if (mEndTransition != null) {
458            if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()
459                    - mEndTransition.getDuration()) {
460                mEndTransition.invalidate();
461            }
462        }
463    }
464
465    /**
466     * Invalidate the start and end transitions if necessary
467     *
468     * @param overlay The effect that was added or removed
469     */
470    void invalidateTransitions(Overlay overlay) {
471        // Check if the overlay overlaps with the beginning and end transitions
472        if (mBeginTransition != null) {
473            if (overlay.getStartTime() < mBeginTransition.getDuration()) {
474                mBeginTransition.invalidate();
475            }
476        }
477
478        if (mEndTransition != null) {
479            if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()
480                    - mEndTransition.getDuration()) {
481                mEndTransition.invalidate();
482            }
483        }
484    }
485
486    /**
487     * Adjust the duration transitions.
488     */
489    protected void adjustTransitions() {
490        // Check if the duration of transitions need to be adjusted
491        if (mBeginTransition != null) {
492            final long maxDurationMs = mBeginTransition.getMaximumDuration();
493            if (mBeginTransition.getDuration() > maxDurationMs) {
494                mBeginTransition.setDuration(maxDurationMs);
495            }
496        }
497
498        if (mEndTransition != null) {
499            final long maxDurationMs = mEndTransition.getMaximumDuration();
500            if (mEndTransition.getDuration() > maxDurationMs) {
501                mEndTransition.setDuration(maxDurationMs);
502            }
503        }
504    }
505
506    /**
507     * Adjust the start time and/or duration of effects.
508     */
509    protected void adjustEffects() {
510        final List<Effect> effects = getAllEffects();
511        for (Effect effect : effects) {
512            // Adjust the start time if necessary
513            final long effectStartTimeMs;
514            if (effect.getStartTime() > getTimelineDuration()) {
515                effectStartTimeMs = 0;
516            } else {
517                effectStartTimeMs = effect.getStartTime();
518            }
519
520            // Adjust the duration if necessary
521            final long effectDurationMs;
522            if (effectStartTimeMs + effect.getDuration() > getTimelineDuration()) {
523                effectDurationMs = getTimelineDuration() - effectStartTimeMs;
524            } else {
525                effectDurationMs = effect.getDuration();
526            }
527
528            if (effectStartTimeMs != effect.getStartTime() ||
529                    effectDurationMs != effect.getDuration()) {
530                effect.setStartTimeAndDuration(effectStartTimeMs, effectDurationMs);
531            }
532        }
533    }
534
535    /**
536     * Adjust the start time and/or duration of overlays.
537     */
538    protected void adjustOverlays() {
539        final List<Overlay> overlays = getAllOverlays();
540        for (Overlay overlay : overlays) {
541            // Adjust the start time if necessary
542            final long overlayStartTimeMs;
543            if (overlay.getStartTime() > getTimelineDuration()) {
544                overlayStartTimeMs = 0;
545            } else {
546                overlayStartTimeMs = overlay.getStartTime();
547            }
548
549            // Adjust the duration if necessary
550            final long overlayDurationMs;
551            if (overlayStartTimeMs + overlay.getDuration() > getTimelineDuration()) {
552                overlayDurationMs = getTimelineDuration() - overlayStartTimeMs;
553            } else {
554                overlayDurationMs = overlay.getDuration();
555            }
556
557            if (overlayStartTimeMs != overlay.getStartTime() ||
558                    overlayDurationMs != overlay.getDuration()) {
559                overlay.setStartTimeAndDuration(overlayStartTimeMs, overlayDurationMs);
560            }
561        }
562    }
563}
564