VideoEditorImpl.java revision 89782f501343fdbbe3bd6ab36e65474d49cd4b89
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.io.FileInputStream;
22import java.io.FileNotFoundException;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.io.StringWriter;
26import java.util.ArrayList;
27import java.util.Iterator;
28import java.util.List;
29import java.util.Map;
30import java.util.concurrent.Semaphore;
31import java.util.concurrent.TimeUnit;
32
33import org.xmlpull.v1.XmlPullParser;
34import org.xmlpull.v1.XmlPullParserException;
35import org.xmlpull.v1.XmlSerializer;
36
37import android.graphics.Bitmap;
38import android.graphics.Rect;
39import android.media.videoeditor.MediaImageItem;
40import android.media.videoeditor.MediaItem;
41import android.util.Log;
42import android.util.Xml;
43import android.view.Surface;
44import android.view.SurfaceHolder;
45
46/**
47 * The VideoEditor implementation {@hide}
48 */
49public class VideoEditorImpl implements VideoEditor {
50    /*
51     *  Logging
52     */
53    private static final String TAG = "VideoEditorImpl";
54
55    /*
56     *  The project filename
57     */
58    private static final String PROJECT_FILENAME = "videoeditor.xml";
59
60    /*
61     *  XML tags
62     */
63    private static final String TAG_PROJECT = "project";
64    private static final String TAG_MEDIA_ITEMS = "media_items";
65    private static final String TAG_MEDIA_ITEM = "media_item";
66    private static final String TAG_TRANSITIONS = "transitions";
67    private static final String TAG_TRANSITION = "transition";
68    private static final String TAG_OVERLAYS = "overlays";
69    private static final String TAG_OVERLAY = "overlay";
70    private static final String TAG_OVERLAY_USER_ATTRIBUTES = "overlay_user_attributes";
71    private static final String TAG_EFFECTS = "effects";
72    private static final String TAG_EFFECT = "effect";
73    private static final String TAG_AUDIO_TRACKS = "audio_tracks";
74    private static final String TAG_AUDIO_TRACK = "audio_track";
75
76    private static final String ATTR_ID = "id";
77    private static final String ATTR_FILENAME = "filename";
78    private static final String ATTR_AUDIO_WAVEFORM_FILENAME = "waveform";
79    private static final String ATTR_RENDERING_MODE = "rendering_mode";
80    private static final String ATTR_ASPECT_RATIO = "aspect_ratio";
81    private static final String ATTR_REGENERATE_PCM = "regeneratePCMFlag";
82    private static final String ATTR_TYPE = "type";
83    private static final String ATTR_DURATION = "duration";
84    private static final String ATTR_START_TIME = "start_time";
85    private static final String ATTR_BEGIN_TIME = "begin_time";
86    private static final String ATTR_END_TIME = "end_time";
87    private static final String ATTR_VOLUME = "volume";
88    private static final String ATTR_BEHAVIOR = "behavior";
89    private static final String ATTR_DIRECTION = "direction";
90    private static final String ATTR_BLENDING = "blending";
91    private static final String ATTR_INVERT = "invert";
92    private static final String ATTR_MASK = "mask";
93    private static final String ATTR_BEFORE_MEDIA_ITEM_ID = "before_media_item";
94    private static final String ATTR_AFTER_MEDIA_ITEM_ID = "after_media_item";
95    private static final String ATTR_COLOR_EFFECT_TYPE = "color_type";
96    private static final String ATTR_COLOR_EFFECT_VALUE = "color_value";
97    private static final String ATTR_START_RECT_LEFT = "start_l";
98    private static final String ATTR_START_RECT_TOP = "start_t";
99    private static final String ATTR_START_RECT_RIGHT = "start_r";
100    private static final String ATTR_START_RECT_BOTTOM = "start_b";
101    private static final String ATTR_END_RECT_LEFT = "end_l";
102    private static final String ATTR_END_RECT_TOP = "end_t";
103    private static final String ATTR_END_RECT_RIGHT = "end_r";
104    private static final String ATTR_END_RECT_BOTTOM = "end_b";
105    private static final String ATTR_LOOP = "loop";
106    private static final String ATTR_MUTED = "muted";
107    private static final String ATTR_DUCK_ENABLED = "ducking_enabled";
108    private static final String ATTR_DUCK_THRESHOLD = "ducking_threshold";
109    private static final String ATTR_DUCKED_TRACK_VOLUME = "ducking_volume";
110    private static final String ATTR_GENERATED_IMAGE_CLIP = "generated_image_clip";
111    private static final String ATTR_IS_IMAGE_CLIP_GENERATED = "is_image_clip_generated";
112    private static final String ATTR_GENERATED_TRANSITION_CLIP = "generated_transition_clip";
113    private static final String ATTR_IS_TRANSITION_GENERATED = "is_transition_generated";
114    private static final String ATTR_OVERLAY_RGB_FILENAME = "overlay_rgb_filename";
115    private static final String ATTR_OVERLAY_FRAME_WIDTH = "overlay_frame_width";
116    private static final String ATTR_OVERLAY_FRAME_HEIGHT = "overlay_frame_height";
117    private static final String ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH = "resized_RGBframe_width";
118    private static final String ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT = "resized_RGBframe_height";
119
120    private static final int ENGINE_ACCESS_MAX_TIMEOUT_MS = 500;
121    /*
122     *  Instance variables
123     */
124    private final Semaphore mLock;
125    private final String mProjectPath;
126    private final List<MediaItem> mMediaItems = new ArrayList<MediaItem>();
127    private final List<AudioTrack> mAudioTracks = new ArrayList<AudioTrack>();
128    private final List<Transition> mTransitions = new ArrayList<Transition>();
129    private long mDurationMs;
130    private int mAspectRatio;
131
132    /*
133     * Private Object for calling native Methods via MediaArtistNativeHelper
134     */
135    private MediaArtistNativeHelper mMANativeHelper;
136    private boolean mPreviewInProgress = false;
137
138    /**
139     * Constructor
140     *
141     * @param projectPath - The path where the VideoEditor stores all files
142     *        related to the project
143     */
144    public VideoEditorImpl(String projectPath) throws IOException {
145        mLock = new Semaphore(1, true);
146        mMANativeHelper = new MediaArtistNativeHelper(projectPath, mLock, this);
147        mProjectPath = projectPath;
148        final File projectXml = new File(projectPath, PROJECT_FILENAME);
149        if (projectXml.exists()) {
150            try {
151                load();
152            } catch (Exception ex) {
153                ex.printStackTrace();
154                throw new IOException(ex.toString());
155            }
156        } else {
157            mAspectRatio = MediaProperties.ASPECT_RATIO_16_9;
158            mDurationMs = 0;
159        }
160    }
161
162    /*
163     * @return The MediaArtistNativeHelper object
164     */
165    MediaArtistNativeHelper getNativeContext() {
166        return mMANativeHelper;
167    }
168
169    /*
170     * {@inheritDoc}
171     */
172    public synchronized void addAudioTrack(AudioTrack audioTrack) {
173        if (audioTrack == null) {
174            throw new IllegalArgumentException("Audio Track is null");
175        }
176
177        if (mAudioTracks.size() == 1) {
178            throw new IllegalArgumentException("No more tracks can be added");
179        }
180
181        mMANativeHelper.setGeneratePreview(true);
182
183        /*
184         * Add the audio track to AudioTrack list
185         */
186        mAudioTracks.add(audioTrack);
187
188        /*
189         * Form the audio PCM file path
190         */
191        final String audioTrackPCMFilePath = String.format(mProjectPath + "/"
192                    + "AudioPcm" + audioTrack.getId() + ".pcm");
193
194        /*
195         * Create PCM only if not generated in previous session
196         */
197        if (new File(audioTrackPCMFilePath).exists()) {
198            mMANativeHelper.setAudioflag(false);
199        }
200
201    }
202
203    /*
204     * {@inheritDoc}
205     */
206    public synchronized void addMediaItem(MediaItem mediaItem) {
207        /*
208         * Validate Media Item
209         */
210        if (mediaItem == null) {
211            throw new IllegalArgumentException("Media item is null");
212        }
213        /*
214         * Add the Media item to MediaItem list
215         */
216        if (mMediaItems.contains(mediaItem)) {
217            throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId());
218        }
219
220        mMANativeHelper.setGeneratePreview(true);
221
222        /*
223         *  Invalidate the end transition if necessary
224         */
225        final int mediaItemsCount = mMediaItems.size();
226        if (mediaItemsCount > 0) {
227            removeTransitionAfter(mediaItemsCount - 1);
228        }
229
230        /*
231         *  Add the new media item
232         */
233        mMediaItems.add(mediaItem);
234
235        computeTimelineDuration();
236
237        /*
238         *  Generate project thumbnail only from first media Item on storyboard
239         */
240        if (mMediaItems.size() == 1) {
241            generateProjectThumbnail();
242        }
243    }
244
245
246    /*
247     * {@inheritDoc}
248     */
249    public synchronized void addTransition(Transition transition) {
250        if (transition == null) {
251            throw new IllegalArgumentException("Null Transition");
252        }
253
254        final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
255        final MediaItem afterMediaItem = transition.getAfterMediaItem();
256        /*
257         * Check if the MediaItems are in sequence
258         */
259        if (mMediaItems == null) {
260            throw new IllegalArgumentException("No media items are added");
261        }
262
263        if ((afterMediaItem != null) &&  (beforeMediaItem != null)) {
264            final int afterMediaItemIndex = mMediaItems.indexOf(afterMediaItem);
265            final int beforeMediaItemIndex = mMediaItems.indexOf(beforeMediaItem);
266
267            if ((afterMediaItemIndex == -1) || (beforeMediaItemIndex == -1)) {
268                throw new IllegalArgumentException
269                    ("Either of the mediaItem is not found in the list");
270            }
271
272            if (afterMediaItemIndex != (beforeMediaItemIndex - 1) ) {
273                throw new IllegalArgumentException("MediaItems are not in sequence");
274            }
275        }
276
277        mMANativeHelper.setGeneratePreview(true);
278
279        mTransitions.add(transition);
280        /*
281         *  Cross reference the transitions
282         */
283        if (afterMediaItem != null) {
284            /*
285             *  If a transition already exists at the specified position then
286             *  invalidate it.
287             */
288            if (afterMediaItem.getEndTransition() != null) {
289                afterMediaItem.getEndTransition().invalidate();
290                mTransitions.remove(afterMediaItem.getEndTransition());
291            }
292            afterMediaItem.setEndTransition(transition);
293        }
294
295        if (beforeMediaItem != null) {
296            /*
297             *  If a transition already exists at the specified position then
298             *  invalidate it.
299             */
300            if (beforeMediaItem.getBeginTransition() != null) {
301                beforeMediaItem.getBeginTransition().invalidate();
302                mTransitions.remove(beforeMediaItem.getBeginTransition());
303            }
304            beforeMediaItem.setBeginTransition(transition);
305        }
306
307        computeTimelineDuration();
308    }
309
310    /*
311     * {@inheritDoc}
312     */
313    public void cancelExport(String filename) {
314        if (mMANativeHelper != null && filename != null) {
315            mMANativeHelper.stop(filename);
316        }
317    }
318
319    /*
320     * {@inheritDoc}
321     */
322    public void export(String filename, int height, int bitrate,
323                       int audioCodec, int videoCodec,
324                       ExportProgressListener listener) throws IOException {
325
326        switch (audioCodec) {
327            case MediaProperties.ACODEC_AAC_LC:
328                break;
329            case MediaProperties.ACODEC_AMRNB:
330                break;
331
332            default: {
333                String message = "Unsupported audio codec type " + audioCodec;
334                throw new IllegalArgumentException(message);
335            }
336        }
337
338        switch (videoCodec) {
339            case MediaProperties.VCODEC_H263:
340                break;
341            case MediaProperties.VCODEC_H264BP:
342                break;
343            case MediaProperties.VCODEC_MPEG4:
344                break;
345
346            default: {
347                String message = "Unsupported video codec type " + videoCodec;
348                throw new IllegalArgumentException(message);
349            }
350        }
351
352        export(filename, height, bitrate, listener);
353    }
354
355    /*
356     * {@inheritDoc}
357     */
358    public void export(String filename, int height, int bitrate,
359                       ExportProgressListener listener) throws IOException {
360        if (filename == null) {
361            throw new IllegalArgumentException("export: filename is null");
362        }
363
364        final File tempPathFile = new File(filename);
365        if (tempPathFile == null) {
366            throw new IOException(filename + "can not be created");
367        }
368
369        if (mMediaItems.size() == 0) {
370            throw new IllegalStateException("No MediaItems added");
371        }
372
373        switch (height) {
374            case MediaProperties.HEIGHT_144:
375                break;
376            case MediaProperties.HEIGHT_360:
377                break;
378            case MediaProperties.HEIGHT_480:
379                break;
380            case MediaProperties.HEIGHT_720:
381                break;
382
383            default: {
384                String message = "Unsupported height value " + height;
385                throw new IllegalArgumentException(message);
386            }
387        }
388
389        switch (bitrate) {
390            case MediaProperties.BITRATE_28K:
391                break;
392            case MediaProperties.BITRATE_40K:
393                break;
394            case MediaProperties.BITRATE_64K:
395                break;
396            case MediaProperties.BITRATE_96K:
397                break;
398            case MediaProperties.BITRATE_128K:
399                break;
400            case MediaProperties.BITRATE_192K:
401                break;
402            case MediaProperties.BITRATE_256K:
403                break;
404            case MediaProperties.BITRATE_384K:
405                break;
406            case MediaProperties.BITRATE_512K:
407                break;
408            case MediaProperties.BITRATE_800K:
409                break;
410            case MediaProperties.BITRATE_2M:
411                break;
412            case MediaProperties.BITRATE_5M:
413                break;
414            case MediaProperties.BITRATE_8M:
415                break;
416
417            default: {
418                final String message = "Unsupported bitrate value " + bitrate;
419                throw new IllegalArgumentException(message);
420            }
421        }
422
423        boolean semAcquireDone = false;
424        try {
425            lock();
426            semAcquireDone = true;
427
428            if (mMANativeHelper == null) {
429                throw new IllegalStateException("The video editor is not initialized");
430            }
431
432            mMANativeHelper.export(filename, mProjectPath, height,bitrate,
433                               mMediaItems, mTransitions, mAudioTracks, listener);
434        } catch (InterruptedException  ex) {
435            Log.e(TAG, "Sem acquire NOT successful in export");
436        } finally {
437            if (semAcquireDone) {
438                unlock();
439            }
440        }
441    }
442
443    /*
444     * {@inheritDoc}
445     */
446    public void generatePreview(MediaProcessingProgressListener listener) {
447        boolean semAcquireDone = false;
448        try {
449            lock();
450            semAcquireDone = true;
451
452            if (mMANativeHelper == null) {
453                throw new IllegalStateException("The video editor is not initialized");
454            }
455
456            if ((mMediaItems.size() > 0) || (mAudioTracks.size() > 0)) {
457                mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions, mAudioTracks,
458                        listener);
459            }
460        } catch (InterruptedException  ex) {
461            Log.e(TAG, "Sem acquire NOT successful in previewStoryBoard");
462        } finally {
463            if (semAcquireDone) {
464                unlock();
465            }
466        }
467    }
468
469    /*
470     * {@inheritDoc}
471     */
472    public List<AudioTrack> getAllAudioTracks() {
473        return mAudioTracks;
474    }
475
476    /*
477     * {@inheritDoc}
478     */
479    public List<MediaItem> getAllMediaItems() {
480        return mMediaItems;
481    }
482
483    /*
484     * {@inheritDoc}
485     */
486    public List<Transition> getAllTransitions() {
487        return mTransitions;
488    }
489
490    /*
491     * {@inheritDoc}
492     */
493    public int getAspectRatio() {
494        return mAspectRatio;
495    }
496
497    /*
498     * {@inheritDoc}
499     */
500    public AudioTrack getAudioTrack(String audioTrackId) {
501        for (AudioTrack at : mAudioTracks) {
502            if (at.getId().equals(audioTrackId)) {
503                return at;
504            }
505        }
506        return null;
507    }
508
509    /*
510     * {@inheritDoc}
511     */
512    public long getDuration() {
513        /**
514         *  Since MediaImageItem can change duration we need to compute the
515         *  duration here
516         */
517        computeTimelineDuration();
518        return mDurationMs;
519    }
520
521    /*
522     * Force updates the timeline duration
523     */
524    void updateTimelineDuration() {
525        computeTimelineDuration();
526    }
527
528    /*
529     * {@inheritDoc}
530     */
531    public synchronized MediaItem getMediaItem(String mediaItemId) {
532        for (MediaItem mediaItem : mMediaItems) {
533            if (mediaItem.getId().equals(mediaItemId)) {
534                return mediaItem;
535            }
536        }
537        return null;
538    }
539
540    /*
541     * {@inheritDoc}
542     */
543    public String getPath() {
544        return mProjectPath;
545    }
546
547    /*
548     * {@inheritDoc}
549     */
550    public Transition getTransition(String transitionId) {
551        for (Transition transition : mTransitions) {
552            if (transition.getId().equals(transitionId)) {
553                return transition;
554            }
555        }
556        return null;
557    }
558
559    /*
560     * {@inheritDoc}
561     */
562    public synchronized void insertAudioTrack(AudioTrack audioTrack,
563                                              String afterAudioTrackId) {
564        if (mAudioTracks.size() == 1) {
565            throw new IllegalArgumentException("No more tracks can be added");
566        }
567
568        if (afterAudioTrackId == null) {
569            mMANativeHelper.setGeneratePreview(true);
570            mAudioTracks.add(0, audioTrack);
571        } else {
572            final int audioTrackCount = mAudioTracks.size();
573            for (int i = 0; i < audioTrackCount; i++) {
574                AudioTrack at = mAudioTracks.get(i);
575                if (at.getId().equals(afterAudioTrackId)) {
576                    mMANativeHelper.setGeneratePreview(true);
577                    mAudioTracks.add(i + 1, audioTrack);
578                    return;
579                }
580            }
581
582            throw new IllegalArgumentException("AudioTrack not found: " + afterAudioTrackId);
583        }
584    }
585
586    /*
587     * {@inheritDoc}
588     */
589    public synchronized void insertMediaItem(MediaItem mediaItem, String afterMediaItemId) {
590        if (mMediaItems.contains(mediaItem)) {
591            throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId());
592        }
593
594        if (afterMediaItemId == null) {
595            mMANativeHelper.setGeneratePreview(true);
596            if (mMediaItems.size() > 0) {
597                /**
598                 *  Invalidate the transition at the beginning of the timeline
599                 */
600                removeTransitionBefore(0);
601            }
602
603            mMediaItems.add(0, mediaItem);
604            computeTimelineDuration();
605            generateProjectThumbnail();
606        } else {
607            final int mediaItemCount = mMediaItems.size();
608            for (int i = 0; i < mediaItemCount; i++) {
609                final MediaItem mi = mMediaItems.get(i);
610                if (mi.getId().equals(afterMediaItemId)) {
611                    mMANativeHelper.setGeneratePreview(true);
612                    /**
613                     *  Invalidate the transition at this position
614                     */
615                    removeTransitionAfter(i);
616                    /**
617                     *  Insert the new media item
618                     */
619                    mMediaItems.add(i + 1, mediaItem);
620                    computeTimelineDuration();
621                    return;
622                }
623            }
624
625            throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId);
626        }
627    }
628
629    /*
630     * {@inheritDoc}
631     */
632    public synchronized void moveAudioTrack(String audioTrackId, String afterAudioTrackId) {
633        throw new IllegalStateException("Not supported");
634    }
635
636    /*
637     * {@inheritDoc}
638     */
639    public synchronized void moveMediaItem(String mediaItemId, String afterMediaItemId) {
640        final MediaItem moveMediaItem = removeMediaItem(mediaItemId,true);
641        if (moveMediaItem == null) {
642            throw new IllegalArgumentException("Target MediaItem not found: " + mediaItemId);
643        }
644
645        if (afterMediaItemId == null) {
646            if (mMediaItems.size() > 0) {
647                mMANativeHelper.setGeneratePreview(true);
648
649                /**
650                 *  Invalidate adjacent transitions at the insertion point
651                 */
652                removeTransitionBefore(0);
653
654                /**
655                 *  Insert the media item at the new position
656                 */
657                mMediaItems.add(0, moveMediaItem);
658                computeTimelineDuration();
659
660                generateProjectThumbnail();
661            } else {
662                throw new IllegalStateException("Cannot move media item (it is the only item)");
663            }
664        } else {
665            final int mediaItemCount = mMediaItems.size();
666            for (int i = 0; i < mediaItemCount; i++) {
667                final MediaItem mi = mMediaItems.get(i);
668                if (mi.getId().equals(afterMediaItemId)) {
669                    mMANativeHelper.setGeneratePreview(true);
670                    /**
671                     *  Invalidate adjacent transitions at the insertion point
672                     */
673                    removeTransitionAfter(i);
674                    /**
675                     *  Insert the media item at the new position
676                     */
677                    mMediaItems.add(i + 1, moveMediaItem);
678                    computeTimelineDuration();
679                    return;
680                }
681            }
682
683            throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId);
684        }
685    }
686
687    /*
688     * {@inheritDoc}
689     */
690    public void release() {
691        stopPreview();
692
693        boolean semAcquireDone = false;
694        try {
695            lock();
696            semAcquireDone = true;
697
698            if (mMANativeHelper != null) {
699                mMediaItems.clear();
700                mAudioTracks.clear();
701                mTransitions.clear();
702                mMANativeHelper.releaseNativeHelper();
703                mMANativeHelper = null;
704            }
705        } catch (Exception  ex) {
706            Log.e(TAG, "Sem acquire NOT successful in export", ex);
707        } finally {
708            if (semAcquireDone) {
709                unlock();
710            }
711        }
712    }
713
714    /*
715     * {@inheritDoc}
716     */
717    public synchronized void removeAllMediaItems() {
718        mMANativeHelper.setGeneratePreview(true);
719
720        mMediaItems.clear();
721
722        /**
723         *  Invalidate all transitions
724         */
725        for (Transition transition : mTransitions) {
726            transition.invalidate();
727        }
728        mTransitions.clear();
729
730        mDurationMs = 0;
731        /**
732         * If a thumbnail already exists, then delete it
733         */
734        if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) {
735            (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete();
736        }
737
738    }
739
740    /*
741     * {@inheritDoc}
742     */
743    public synchronized AudioTrack removeAudioTrack(String audioTrackId) {
744        final AudioTrack audioTrack = getAudioTrack(audioTrackId);
745        if (audioTrack != null) {
746            mMANativeHelper.setGeneratePreview(true);
747            mAudioTracks.remove(audioTrack);
748            audioTrack.invalidate();
749            mMANativeHelper.invalidatePcmFile();
750            mMANativeHelper.setAudioflag(true);
751        } else {
752            throw new IllegalArgumentException(" No more audio tracks");
753        }
754        return audioTrack;
755    }
756
757    /*
758     * {@inheritDoc}
759     */
760    public synchronized MediaItem removeMediaItem(String mediaItemId) {
761        final String firstItemString = mMediaItems.get(0).getId();
762        final MediaItem mediaItem = getMediaItem(mediaItemId);
763        if (mediaItem != null) {
764            mMANativeHelper.setGeneratePreview(true);
765            /**
766             *  Remove the media item
767             */
768            mMediaItems.remove(mediaItem);
769            if (mediaItem instanceof MediaImageItem) {
770                ((MediaImageItem)mediaItem).invalidate();
771            }
772            final List<Overlay> overlays = mediaItem.getAllOverlays();
773            if (overlays.size() > 0) {
774                for (Overlay overlay : overlays) {
775                    if (overlay instanceof OverlayFrame) {
776                        final OverlayFrame overlayFrame = (OverlayFrame)overlay;
777                        overlayFrame.invalidate();
778                    }
779                }
780            }
781
782            /**
783             *  Remove the adjacent transitions
784             */
785            removeAdjacentTransitions(mediaItem);
786            computeTimelineDuration();
787        }
788
789        /**
790         * If string equals first mediaItem, then
791         * generate Project thumbnail
792         */
793        if (firstItemString.equals(mediaItemId)) {
794            generateProjectThumbnail();
795        }
796
797        if (mediaItem instanceof MediaVideoItem) {
798            /**
799             * Delete the graph file
800             */
801            ((MediaVideoItem)mediaItem).invalidate();
802        }
803        return mediaItem;
804    }
805
806    private synchronized MediaItem removeMediaItem(String mediaItemId, boolean flag) {
807        final String firstItemString = mMediaItems.get(0).getId();
808
809        final MediaItem mediaItem = getMediaItem(mediaItemId);
810        if (mediaItem != null) {
811            mMANativeHelper.setGeneratePreview(true);
812            /**
813             *  Remove the media item
814             */
815            mMediaItems.remove(mediaItem);
816            /**
817             *  Remove the adjacent transitions
818             */
819            removeAdjacentTransitions(mediaItem);
820            computeTimelineDuration();
821        }
822
823        /**
824         * If string equals first mediaItem, then
825         * generate Project thumbail
826         */
827        if (firstItemString.equals(mediaItemId)) {
828            generateProjectThumbnail();
829        }
830        return mediaItem;
831    }
832
833    /*
834     * {@inheritDoc}
835     */
836    public synchronized Transition removeTransition(String transitionId) {
837        final Transition transition = getTransition(transitionId);
838        if (transition == null) {
839            throw new IllegalStateException("Transition not found: " + transitionId);
840        }
841
842        mMANativeHelper.setGeneratePreview(true);
843
844        /**
845         *  Remove the transition references
846         */
847        final MediaItem afterMediaItem = transition.getAfterMediaItem();
848        if (afterMediaItem != null) {
849            afterMediaItem.setEndTransition(null);
850        }
851
852        final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
853        if (beforeMediaItem != null) {
854            beforeMediaItem.setBeginTransition(null);
855        }
856
857        mTransitions.remove(transition);
858        transition.invalidate();
859        computeTimelineDuration();
860        return transition;
861    }
862
863    /*
864     * {@inheritDoc}
865     */
866    public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs,
867                                    OverlayData overlayData) {
868        if (surfaceHolder == null) {
869            throw new IllegalArgumentException("Surface Holder is null");
870        }
871
872        final Surface surface = surfaceHolder.getSurface();
873        if (surface == null) {
874            throw new IllegalArgumentException("Surface could not be retrieved from Surface holder");
875        }
876
877        if (timeMs < 0) {
878            throw new IllegalArgumentException("requested time not correct");
879        } else if (timeMs > mDurationMs) {
880            throw new IllegalArgumentException("requested time more than duration");
881        }
882        long result = 0;
883
884        boolean semAcquireDone = false;
885        try {
886            semAcquireDone = lock(ENGINE_ACCESS_MAX_TIMEOUT_MS);
887            if (semAcquireDone == false) {
888                throw new IllegalStateException("Timeout waiting for semaphore");
889            }
890
891            if (mMANativeHelper == null) {
892                throw new IllegalStateException("The video editor is not initialized");
893            }
894
895            if (mMediaItems.size() > 0) {
896                final Rect frame = surfaceHolder.getSurfaceFrame();
897                result = mMANativeHelper.renderPreviewFrame(surface,
898                        timeMs, frame.width(), frame.height(), overlayData);
899            } else {
900                result = 0;
901            }
902        } catch (InterruptedException ex) {
903            Log.w(TAG, "The thread was interrupted", new Throwable());
904            throw new IllegalStateException("The thread was interrupted");
905        } finally {
906            if (semAcquireDone) {
907                unlock();
908            }
909        }
910        return result;
911    }
912
913    /**
914     *  the project form XML
915     */
916    private void load() throws FileNotFoundException, XmlPullParserException, IOException {
917        final File file = new File(mProjectPath, PROJECT_FILENAME);
918        /**
919         *  Load the metadata
920         */
921        final FileInputStream fis = new FileInputStream(file);
922        try {
923            final List<String> ignoredMediaItems = new ArrayList<String>();
924
925            final XmlPullParser parser = Xml.newPullParser();
926            parser.setInput(fis, "UTF-8");
927            int eventType = parser.getEventType();
928            String name;
929            MediaItem currentMediaItem = null;
930            Overlay currentOverlay = null;
931            boolean regenerateProjectThumbnail = false;
932            while (eventType != XmlPullParser.END_DOCUMENT) {
933                switch (eventType) {
934                    case XmlPullParser.START_TAG: {
935                        name = parser.getName();
936                        if (TAG_PROJECT.equals(name)) {
937                            mAspectRatio = Integer.parseInt(parser.getAttributeValue("",
938                                   ATTR_ASPECT_RATIO));
939
940                            final boolean mRegenPCM =
941                                Boolean.parseBoolean(parser.getAttributeValue("",
942                                    ATTR_REGENERATE_PCM));
943                            mMANativeHelper.setAudioflag(mRegenPCM);
944                        } else if (TAG_MEDIA_ITEM.equals(name)) {
945                            final String mediaItemId = parser.getAttributeValue("", ATTR_ID);
946                            try {
947                                currentMediaItem = parseMediaItem(parser);
948                                mMediaItems.add(currentMediaItem);
949                            } catch (Exception ex) {
950                                Log.w(TAG, "Cannot load media item: " + mediaItemId, ex);
951                                currentMediaItem = null;
952
953                                // First media item is invalid, mark for project thumbnail removal
954                                if (mMediaItems.size() == 0) {
955                                    regenerateProjectThumbnail = true;
956                                }
957                                // Ignore the media item
958                                ignoredMediaItems.add(mediaItemId);
959                            }
960                        } else if (TAG_TRANSITION.equals(name)) {
961                            try {
962                                final Transition transition = parseTransition(parser,
963                                        ignoredMediaItems);
964                                // The transition will be null if the bounding
965                                // media items are ignored
966                                if (transition != null) {
967                                    mTransitions.add(transition);
968                                }
969                            } catch (Exception ex) {
970                                Log.w(TAG, "Cannot load transition", ex);
971                            }
972                        } else if (TAG_OVERLAY.equals(name)) {
973                            if (currentMediaItem != null) {
974                                try {
975                                    currentOverlay = parseOverlay(parser, currentMediaItem);
976                                    currentMediaItem.addOverlay(currentOverlay);
977                                } catch (Exception ex) {
978                                    Log.w(TAG, "Cannot load overlay", ex);
979                                }
980                            }
981                        } else if (TAG_OVERLAY_USER_ATTRIBUTES.equals(name)) {
982                            if (currentOverlay != null) {
983                                final int attributesCount = parser.getAttributeCount();
984                                for (int i = 0; i < attributesCount; i++) {
985                                    currentOverlay.setUserAttribute(parser.getAttributeName(i),
986                                            parser.getAttributeValue(i));
987                                }
988                            }
989                        } else if (TAG_EFFECT.equals(name)) {
990                            if (currentMediaItem != null) {
991                                try {
992                                    final Effect effect = parseEffect(parser, currentMediaItem);
993                                    currentMediaItem.addEffect(effect);
994
995                                    if (effect instanceof EffectKenBurns) {
996                                        final boolean isImageClipGenerated =
997                                               Boolean.parseBoolean(parser.getAttributeValue("",
998                                                                  ATTR_IS_IMAGE_CLIP_GENERATED));
999                                        if(isImageClipGenerated) {
1000                                            final String filename = parser.getAttributeValue("",
1001                                                                  ATTR_GENERATED_IMAGE_CLIP);
1002                                            if (new File(filename).exists() == true) {
1003                                                ((MediaImageItem)currentMediaItem).
1004                                                            setGeneratedImageClip(filename);
1005                                                ((MediaImageItem)currentMediaItem).
1006                                                             setRegenerateClip(false);
1007                                             } else {
1008                                               ((MediaImageItem)currentMediaItem).
1009                                                             setGeneratedImageClip(null);
1010                                               ((MediaImageItem)currentMediaItem).
1011                                                             setRegenerateClip(true);
1012                                             }
1013                                        } else {
1014                                            ((MediaImageItem)currentMediaItem).
1015                                                             setGeneratedImageClip(null);
1016                                            ((MediaImageItem)currentMediaItem).
1017                                                            setRegenerateClip(true);
1018                                        }
1019                                    }
1020                                } catch (Exception ex) {
1021                                    Log.w(TAG, "Cannot load effect", ex);
1022                                }
1023                            }
1024                        } else if (TAG_AUDIO_TRACK.equals(name)) {
1025                            try {
1026                                final AudioTrack audioTrack = parseAudioTrack(parser);
1027                                addAudioTrack(audioTrack);
1028                            } catch (Exception ex) {
1029                                Log.w(TAG, "Cannot load audio track", ex);
1030                            }
1031                        }
1032                        break;
1033                    }
1034
1035                    case XmlPullParser.END_TAG: {
1036                        name = parser.getName();
1037                        if (TAG_MEDIA_ITEM.equals(name)) {
1038                            currentMediaItem = null;
1039                        } else if (TAG_OVERLAY.equals(name)) {
1040                            currentOverlay = null;
1041                        }
1042                        break;
1043                    }
1044
1045                    default: {
1046                        break;
1047                    }
1048                }
1049                eventType = parser.next();
1050            }
1051            computeTimelineDuration();
1052            // Regenerate project thumbnail
1053            if (regenerateProjectThumbnail) {
1054                generateProjectThumbnail();
1055                regenerateProjectThumbnail = false;
1056            }
1057        } finally {
1058            if (fis != null) {
1059                fis.close();
1060            }
1061        }
1062    }
1063
1064    /**
1065     * Parse the media item
1066     *
1067     * @param parser The parser
1068     * @return The media item
1069     */
1070    private MediaItem parseMediaItem(XmlPullParser parser) throws IOException {
1071        final String mediaItemId = parser.getAttributeValue("", ATTR_ID);
1072        final String type = parser.getAttributeValue("", ATTR_TYPE);
1073        final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1074        final int renderingMode = Integer.parseInt(parser.getAttributeValue("",
1075                ATTR_RENDERING_MODE));
1076
1077        final MediaItem currentMediaItem;
1078        if (MediaImageItem.class.getSimpleName().equals(type)) {
1079            final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
1080            currentMediaItem = new MediaImageItem(this, mediaItemId, filename,
1081                    durationMs, renderingMode);
1082        } else if (MediaVideoItem.class.getSimpleName().equals(type)) {
1083            final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1084            final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
1085            final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
1086            final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED));
1087            final String audioWaveformFilename = parser.getAttributeValue("",
1088                    ATTR_AUDIO_WAVEFORM_FILENAME);
1089            currentMediaItem = new MediaVideoItem(this, mediaItemId, filename,
1090                    renderingMode, beginMs, endMs, volume, muted, audioWaveformFilename);
1091
1092            final long beginTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1093            final long endTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
1094            ((MediaVideoItem)currentMediaItem).setExtractBoundaries(beginTimeMs, endTimeMs);
1095
1096            final int volumePercent = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
1097            ((MediaVideoItem)currentMediaItem).setVolume(volumePercent);
1098        } else {
1099            throw new IllegalArgumentException("Unknown media item type: " + type);
1100        }
1101
1102        return currentMediaItem;
1103    }
1104
1105    /**
1106     * Parse the transition
1107     *
1108     * @param parser The parser
1109     * @param ignoredMediaItems The list of ignored media items
1110     *
1111     * @return The transition
1112     */
1113    private Transition parseTransition(XmlPullParser parser, List<String> ignoredMediaItems) {
1114        final String transitionId = parser.getAttributeValue("", ATTR_ID);
1115        final String type = parser.getAttributeValue("", ATTR_TYPE);
1116        final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
1117        final int behavior = Integer.parseInt(parser.getAttributeValue("", ATTR_BEHAVIOR));
1118
1119        final String beforeMediaItemId = parser.getAttributeValue("", ATTR_BEFORE_MEDIA_ITEM_ID);
1120        final MediaItem beforeMediaItem;
1121        if (beforeMediaItemId != null) {
1122            if (ignoredMediaItems.contains(beforeMediaItemId)) {
1123                // This transition is ignored
1124                return null;
1125            }
1126
1127            beforeMediaItem = getMediaItem(beforeMediaItemId);
1128        } else {
1129            beforeMediaItem = null;
1130        }
1131
1132        final String afterMediaItemId = parser.getAttributeValue("", ATTR_AFTER_MEDIA_ITEM_ID);
1133        final MediaItem afterMediaItem;
1134        if (afterMediaItemId != null) {
1135            if (ignoredMediaItems.contains(afterMediaItemId)) {
1136                // This transition is ignored
1137                return null;
1138            }
1139
1140            afterMediaItem = getMediaItem(afterMediaItemId);
1141        } else {
1142            afterMediaItem = null;
1143        }
1144
1145        final Transition transition;
1146        if (TransitionAlpha.class.getSimpleName().equals(type)) {
1147            final int blending = Integer.parseInt(parser.getAttributeValue("", ATTR_BLENDING));
1148            final String maskFilename = parser.getAttributeValue("", ATTR_MASK);
1149            final boolean invert = Boolean.getBoolean(parser.getAttributeValue("", ATTR_INVERT));
1150            transition = new TransitionAlpha(transitionId, afterMediaItem, beforeMediaItem,
1151                    durationMs, behavior, maskFilename, blending, invert);
1152        } else if (TransitionCrossfade.class.getSimpleName().equals(type)) {
1153            transition = new TransitionCrossfade(transitionId, afterMediaItem, beforeMediaItem,
1154                    durationMs, behavior);
1155        } else if (TransitionSliding.class.getSimpleName().equals(type)) {
1156            final int direction = Integer.parseInt(parser.getAttributeValue("", ATTR_DIRECTION));
1157            transition = new TransitionSliding(transitionId, afterMediaItem, beforeMediaItem,
1158                    durationMs, behavior, direction);
1159        } else if (TransitionFadeBlack.class.getSimpleName().equals(type)) {
1160            transition = new TransitionFadeBlack(transitionId, afterMediaItem, beforeMediaItem,
1161                    durationMs, behavior);
1162        } else {
1163            throw new IllegalArgumentException("Invalid transition type: " + type);
1164        }
1165
1166        final boolean isTransitionGenerated = Boolean.parseBoolean(parser.getAttributeValue("",
1167                                                 ATTR_IS_TRANSITION_GENERATED));
1168        if (isTransitionGenerated == true) {
1169            final String transitionFile = parser.getAttributeValue("",
1170                                                ATTR_GENERATED_TRANSITION_CLIP);
1171
1172            if (new File(transitionFile).exists()) {
1173                transition.setFilename(transitionFile);
1174            } else {
1175                transition.setFilename(null);
1176            }
1177        }
1178
1179        // Use the transition
1180        if (beforeMediaItem != null) {
1181            beforeMediaItem.setBeginTransition(transition);
1182        }
1183
1184        if (afterMediaItem != null) {
1185            afterMediaItem.setEndTransition(transition);
1186        }
1187
1188        return transition;
1189    }
1190
1191    /**
1192     * Parse the overlay
1193     *
1194     * @param parser The parser
1195     * @param mediaItem The media item owner
1196     *
1197     * @return The overlay
1198     */
1199    private Overlay parseOverlay(XmlPullParser parser, MediaItem mediaItem) {
1200        final String overlayId = parser.getAttributeValue("", ATTR_ID);
1201        final String type = parser.getAttributeValue("", ATTR_TYPE);
1202        final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
1203        final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1204
1205        final Overlay overlay;
1206        if (OverlayFrame.class.getSimpleName().equals(type)) {
1207            final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1208            overlay = new OverlayFrame(mediaItem, overlayId, filename, startTimeMs, durationMs);
1209        } else {
1210            throw new IllegalArgumentException("Invalid overlay type: " + type);
1211        }
1212
1213        final String overlayRgbFileName = parser.getAttributeValue("", ATTR_OVERLAY_RGB_FILENAME);
1214        if (overlayRgbFileName != null) {
1215            ((OverlayFrame)overlay).setFilename(overlayRgbFileName);
1216
1217            final int overlayFrameWidth = Integer.parseInt(parser.getAttributeValue("",
1218                                   ATTR_OVERLAY_FRAME_WIDTH));
1219            final int overlayFrameHeight = Integer.parseInt(parser.getAttributeValue("",
1220                                   ATTR_OVERLAY_FRAME_HEIGHT));
1221
1222            ((OverlayFrame)overlay).setOverlayFrameWidth(overlayFrameWidth);
1223            ((OverlayFrame)overlay).setOverlayFrameHeight(overlayFrameHeight);
1224
1225            final int resizedRGBFrameWidth = Integer.parseInt(parser.getAttributeValue("",
1226                                   ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH));
1227            final int resizedRGBFrameHeight = Integer.parseInt(parser.getAttributeValue("",
1228                                   ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT));
1229
1230            ((OverlayFrame)overlay).setResizedRGBSize(resizedRGBFrameWidth, resizedRGBFrameHeight);
1231        }
1232
1233        return overlay;
1234    }
1235
1236    /**
1237     * Parse the effect
1238     *
1239     * @param parser The parser
1240     * @param mediaItem The media item owner
1241     *
1242     * @return The effect
1243     */
1244    private Effect parseEffect(XmlPullParser parser, MediaItem mediaItem) {
1245        final String effectId = parser.getAttributeValue("", ATTR_ID);
1246        final String type = parser.getAttributeValue("", ATTR_TYPE);
1247        final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
1248        final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1249
1250        final Effect effect;
1251        if (EffectColor.class.getSimpleName().equals(type)) {
1252            final int colorEffectType = Integer.parseInt(parser.getAttributeValue("",
1253                                                       ATTR_COLOR_EFFECT_TYPE));
1254            final int color;
1255            if (colorEffectType == EffectColor.TYPE_COLOR
1256                    || colorEffectType == EffectColor.TYPE_GRADIENT) {
1257                color = Integer.parseInt(parser.getAttributeValue("", ATTR_COLOR_EFFECT_VALUE));
1258            } else {
1259                color = 0;
1260            }
1261            effect = new EffectColor(mediaItem, effectId, startTimeMs,
1262                    durationMs, colorEffectType, color);
1263        } else if (EffectKenBurns.class.getSimpleName().equals(type)) {
1264            final Rect startRect = new Rect(
1265                    Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_LEFT)),
1266                    Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_TOP)),
1267                    Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_RIGHT)),
1268                    Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_BOTTOM)));
1269            final Rect endRect = new Rect(
1270                    Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_LEFT)),
1271                    Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_TOP)),
1272                    Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_RIGHT)),
1273                    Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_BOTTOM)));
1274            effect = new EffectKenBurns(mediaItem, effectId, startRect, endRect,
1275                                        startTimeMs, durationMs);
1276        } else {
1277            throw new IllegalArgumentException("Invalid effect type: " + type);
1278        }
1279
1280        return effect;
1281    }
1282
1283    /**
1284     * Parse the audio track
1285     *
1286     * @param parser The parser
1287     *
1288     * @return The audio track
1289     */
1290    private AudioTrack parseAudioTrack(XmlPullParser parser) throws IOException {
1291        final String audioTrackId = parser.getAttributeValue("", ATTR_ID);
1292        final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1293        final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_START_TIME));
1294        final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1295        final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
1296        final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
1297        final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED));
1298        final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_LOOP));
1299        final boolean duckingEnabled = Boolean.parseBoolean(
1300                parser.getAttributeValue("", ATTR_DUCK_ENABLED));
1301        final int duckThreshold = Integer.parseInt(
1302                parser.getAttributeValue("", ATTR_DUCK_THRESHOLD));
1303        final int duckedTrackVolume = Integer.parseInt(parser.getAttributeValue("",
1304                                                     ATTR_DUCKED_TRACK_VOLUME));
1305
1306        final String waveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME);
1307        final AudioTrack audioTrack = new AudioTrack(this, audioTrackId,
1308                                                     filename, startTimeMs,
1309                                                     beginMs, endMs, loop,
1310                                                     volume, muted,
1311                                                     duckingEnabled,
1312                                                     duckThreshold,
1313                                                     duckedTrackVolume,
1314                                                     waveformFilename);
1315
1316        return audioTrack;
1317    }
1318
1319    /*
1320     * {@inheritDoc}
1321     */
1322    public void save() throws IOException {
1323        final XmlSerializer serializer = Xml.newSerializer();
1324        final StringWriter writer = new StringWriter();
1325        serializer.setOutput(writer);
1326        serializer.startDocument("UTF-8", true);
1327        serializer.startTag("", TAG_PROJECT);
1328        serializer.attribute("",
1329                             ATTR_ASPECT_RATIO, Integer.toString(mAspectRatio));
1330
1331        serializer.attribute("", ATTR_REGENERATE_PCM,
1332                        Boolean.toString(mMANativeHelper.getAudioflag()));
1333
1334        serializer.startTag("", TAG_MEDIA_ITEMS);
1335        for (MediaItem mediaItem : mMediaItems) {
1336            serializer.startTag("", TAG_MEDIA_ITEM);
1337            serializer.attribute("", ATTR_ID, mediaItem.getId());
1338            serializer.attribute("", ATTR_TYPE,
1339                                          mediaItem.getClass().getSimpleName());
1340            serializer.attribute("", ATTR_FILENAME, mediaItem.getFilename());
1341            serializer.attribute("", ATTR_RENDERING_MODE, Integer.toString(
1342                    mediaItem.getRenderingMode()));
1343            if (mediaItem instanceof MediaVideoItem) {
1344                final MediaVideoItem mvi = (MediaVideoItem)mediaItem;
1345                serializer
1346                .attribute("", ATTR_BEGIN_TIME,
1347                                     Long.toString(mvi.getBoundaryBeginTime()));
1348                serializer.attribute("", ATTR_END_TIME,
1349                                       Long.toString(mvi.getBoundaryEndTime()));
1350                serializer.attribute("", ATTR_VOLUME,
1351                                             Integer.toString(mvi.getVolume()));
1352                serializer.attribute("", ATTR_MUTED,
1353                                               Boolean.toString(mvi.isMuted()));
1354                if (mvi.getAudioWaveformFilename() != null) {
1355                    serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
1356                            mvi.getAudioWaveformFilename());
1357                }
1358            } else if (mediaItem instanceof MediaImageItem) {
1359                serializer.attribute("", ATTR_DURATION,
1360                        Long.toString(mediaItem.getTimelineDuration()));
1361            }
1362
1363            final List<Overlay> overlays = mediaItem.getAllOverlays();
1364            if (overlays.size() > 0) {
1365                serializer.startTag("", TAG_OVERLAYS);
1366                for (Overlay overlay : overlays) {
1367                    serializer.startTag("", TAG_OVERLAY);
1368                    serializer.attribute("", ATTR_ID, overlay.getId());
1369                    serializer.attribute("",
1370                                 ATTR_TYPE, overlay.getClass().getSimpleName());
1371                    serializer.attribute("", ATTR_BEGIN_TIME,
1372                                         Long.toString(overlay.getStartTime()));
1373                    serializer.attribute("", ATTR_DURATION,
1374                                          Long.toString(overlay.getDuration()));
1375                    if (overlay instanceof OverlayFrame) {
1376                        final OverlayFrame overlayFrame = (OverlayFrame)overlay;
1377                        overlayFrame.save(getPath());
1378                        if (overlayFrame.getBitmapImageFileName() != null) {
1379                            serializer.attribute("", ATTR_FILENAME,
1380                                         overlayFrame.getBitmapImageFileName());
1381                        }
1382
1383                        if (overlayFrame.getFilename() != null) {
1384                            serializer.attribute("",
1385                                                 ATTR_OVERLAY_RGB_FILENAME,
1386                                                 overlayFrame.getFilename());
1387                            serializer.attribute("", ATTR_OVERLAY_FRAME_WIDTH,
1388                                                 Integer.toString(overlayFrame.getOverlayFrameWidth()));
1389                            serializer.attribute("", ATTR_OVERLAY_FRAME_HEIGHT,
1390                                                 Integer.toString(overlayFrame.getOverlayFrameHeight()));
1391                            serializer.attribute("", ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH,
1392                                                 Integer.toString(overlayFrame.getResizedRGBSizeWidth()));
1393                            serializer.attribute("", ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT,
1394                                                 Integer.toString(overlayFrame.getResizedRGBSizeHeight()));
1395
1396                        }
1397
1398                    }
1399
1400                    /**
1401                     *  Save the user attributes
1402                     */
1403                    serializer.startTag("", TAG_OVERLAY_USER_ATTRIBUTES);
1404                    final Map<String, String> userAttributes = overlay.getUserAttributes();
1405                    for (String name : userAttributes.keySet()) {
1406                        final String value = userAttributes.get(name);
1407                        if (value != null) {
1408                            serializer.attribute("", name, value);
1409                        }
1410                    }
1411                    serializer.endTag("", TAG_OVERLAY_USER_ATTRIBUTES);
1412
1413                    serializer.endTag("", TAG_OVERLAY);
1414                }
1415                serializer.endTag("", TAG_OVERLAYS);
1416            }
1417
1418            final List<Effect> effects = mediaItem.getAllEffects();
1419            if (effects.size() > 0) {
1420                serializer.startTag("", TAG_EFFECTS);
1421                for (Effect effect : effects) {
1422                    serializer.startTag("", TAG_EFFECT);
1423                    serializer.attribute("", ATTR_ID, effect.getId());
1424                    serializer.attribute("",
1425                                  ATTR_TYPE, effect.getClass().getSimpleName());
1426                    serializer.attribute("", ATTR_BEGIN_TIME,
1427                            Long.toString(effect.getStartTime()));
1428                    serializer.attribute("", ATTR_DURATION,
1429                                           Long.toString(effect.getDuration()));
1430                    if (effect instanceof EffectColor) {
1431                        final EffectColor colorEffect = (EffectColor)effect;
1432                        serializer.attribute("", ATTR_COLOR_EFFECT_TYPE,
1433                                Integer.toString(colorEffect.getType()));
1434                        if (colorEffect.getType() == EffectColor.TYPE_COLOR ||
1435                                colorEffect.getType() == EffectColor.TYPE_GRADIENT) {
1436                            serializer.attribute("", ATTR_COLOR_EFFECT_VALUE,
1437                                    Integer.toString(colorEffect.getColor()));
1438                        }
1439                    } else if (effect instanceof EffectKenBurns) {
1440                        final Rect startRect = ((EffectKenBurns)effect).getStartRect();
1441                        serializer.attribute("", ATTR_START_RECT_LEFT,
1442                                Integer.toString(startRect.left));
1443                        serializer.attribute("", ATTR_START_RECT_TOP,
1444                                Integer.toString(startRect.top));
1445                        serializer.attribute("", ATTR_START_RECT_RIGHT,
1446                                Integer.toString(startRect.right));
1447                        serializer.attribute("", ATTR_START_RECT_BOTTOM,
1448                                Integer.toString(startRect.bottom));
1449
1450                        final Rect endRect = ((EffectKenBurns)effect).getEndRect();
1451                        serializer.attribute("", ATTR_END_RECT_LEFT,
1452                                                Integer.toString(endRect.left));
1453                        serializer.attribute("", ATTR_END_RECT_TOP,
1454                                                 Integer.toString(endRect.top));
1455                        serializer.attribute("", ATTR_END_RECT_RIGHT,
1456                                               Integer.toString(endRect.right));
1457                        serializer.attribute("", ATTR_END_RECT_BOTTOM,
1458                                Integer.toString(endRect.bottom));
1459                        final MediaItem mItem = effect.getMediaItem();
1460                           if(((MediaImageItem)mItem).getGeneratedImageClip() != null) {
1461                               serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED,
1462                                       Boolean.toString(true));
1463                               serializer.attribute("", ATTR_GENERATED_IMAGE_CLIP,
1464                                     ((MediaImageItem)mItem).getGeneratedImageClip());
1465                            } else {
1466                                serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED,
1467                                     Boolean.toString(false));
1468                         }
1469                    }
1470
1471                    serializer.endTag("", TAG_EFFECT);
1472                }
1473                serializer.endTag("", TAG_EFFECTS);
1474            }
1475
1476            serializer.endTag("", TAG_MEDIA_ITEM);
1477        }
1478        serializer.endTag("", TAG_MEDIA_ITEMS);
1479
1480        serializer.startTag("", TAG_TRANSITIONS);
1481
1482        for (Transition transition : mTransitions) {
1483            serializer.startTag("", TAG_TRANSITION);
1484            serializer.attribute("", ATTR_ID, transition.getId());
1485            serializer.attribute("", ATTR_TYPE, transition.getClass().getSimpleName());
1486            serializer.attribute("", ATTR_DURATION, Long.toString(transition.getDuration()));
1487            serializer.attribute("", ATTR_BEHAVIOR, Integer.toString(transition.getBehavior()));
1488            serializer.attribute("", ATTR_IS_TRANSITION_GENERATED,
1489                                    Boolean.toString(transition.isGenerated()));
1490            if (transition.isGenerated() == true) {
1491                serializer.attribute("", ATTR_GENERATED_TRANSITION_CLIP, transition.mFilename);
1492            }
1493            final MediaItem afterMediaItem = transition.getAfterMediaItem();
1494            if (afterMediaItem != null) {
1495                serializer.attribute("", ATTR_AFTER_MEDIA_ITEM_ID, afterMediaItem.getId());
1496            }
1497
1498            final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
1499            if (beforeMediaItem != null) {
1500                serializer.attribute("", ATTR_BEFORE_MEDIA_ITEM_ID, beforeMediaItem.getId());
1501            }
1502
1503            if (transition instanceof TransitionSliding) {
1504                serializer.attribute("", ATTR_DIRECTION,
1505                        Integer.toString(((TransitionSliding)transition).getDirection()));
1506            } else if (transition instanceof TransitionAlpha) {
1507                TransitionAlpha ta = (TransitionAlpha)transition;
1508                serializer.attribute("", ATTR_BLENDING,
1509                                     Integer.toString(ta.getBlendingPercent()));
1510                serializer.attribute("", ATTR_INVERT,
1511                                               Boolean.toString(ta.isInvert()));
1512                if (ta.getMaskFilename() != null) {
1513                    serializer.attribute("", ATTR_MASK, ta.getMaskFilename());
1514                }
1515            }
1516            serializer.endTag("", TAG_TRANSITION);
1517        }
1518        serializer.endTag("", TAG_TRANSITIONS);
1519        serializer.startTag("", TAG_AUDIO_TRACKS);
1520        for (AudioTrack at : mAudioTracks) {
1521            serializer.startTag("", TAG_AUDIO_TRACK);
1522            serializer.attribute("", ATTR_ID, at.getId());
1523            serializer.attribute("", ATTR_FILENAME, at.getFilename());
1524            serializer.attribute("", ATTR_START_TIME, Long.toString(at.getStartTime()));
1525            serializer.attribute("", ATTR_BEGIN_TIME, Long.toString(at.getBoundaryBeginTime()));
1526            serializer.attribute("", ATTR_END_TIME, Long.toString(at.getBoundaryEndTime()));
1527            serializer.attribute("", ATTR_VOLUME, Integer.toString(at.getVolume()));
1528            serializer.attribute("", ATTR_DUCK_ENABLED,
1529                                       Boolean.toString(at.isDuckingEnabled()));
1530            serializer.attribute("", ATTR_DUCKED_TRACK_VOLUME,
1531                                   Integer.toString(at.getDuckedTrackVolume()));
1532            serializer.attribute("", ATTR_DUCK_THRESHOLD,
1533                                   Integer.toString(at.getDuckingThreshhold()));
1534            serializer.attribute("", ATTR_MUTED, Boolean.toString(at.isMuted()));
1535            serializer.attribute("", ATTR_LOOP, Boolean.toString(at.isLooping()));
1536            if (at.getAudioWaveformFilename() != null) {
1537                serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
1538                        at.getAudioWaveformFilename());
1539            }
1540
1541            serializer.endTag("", TAG_AUDIO_TRACK);
1542        }
1543        serializer.endTag("", TAG_AUDIO_TRACKS);
1544
1545        serializer.endTag("", TAG_PROJECT);
1546        serializer.endDocument();
1547
1548        /**
1549         *  Save the metadata XML file
1550         */
1551        final FileOutputStream out = new FileOutputStream(new File(getPath(),
1552                                                          PROJECT_FILENAME));
1553        out.write(writer.toString().getBytes());
1554        out.flush();
1555        out.close();
1556    }
1557
1558    /*
1559     * {@inheritDoc}
1560     */
1561    public void setAspectRatio(int aspectRatio) {
1562        mAspectRatio = aspectRatio;
1563        /**
1564         *  Invalidate all transitions
1565         */
1566        mMANativeHelper.setGeneratePreview(true);
1567
1568        for (Transition transition : mTransitions) {
1569            transition.invalidate();
1570        }
1571
1572        final Iterator<MediaItem> it = mMediaItems.iterator();
1573
1574        while (it.hasNext()) {
1575            final MediaItem t = it.next();
1576            List<Overlay> overlayList = t.getAllOverlays();
1577            for (Overlay overlay : overlayList) {
1578
1579                ((OverlayFrame)overlay).invalidateGeneratedFiles();
1580            }
1581        }
1582    }
1583
1584    /*
1585     * {@inheritDoc}
1586     */
1587    public void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs,
1588                             boolean loop, int callbackAfterFrameCount,
1589                             PreviewProgressListener listener) {
1590
1591        if (surfaceHolder == null) {
1592            throw new IllegalArgumentException();
1593        }
1594
1595        final Surface surface = surfaceHolder.getSurface();
1596        if (surface == null) {
1597            throw new IllegalArgumentException("Surface could not be retrieved from surface holder");
1598        }
1599
1600        if (listener == null) {
1601            throw new IllegalArgumentException();
1602        }
1603
1604        if (fromMs >= mDurationMs) {
1605            throw new IllegalArgumentException("Requested time not correct");
1606        }
1607
1608        if (fromMs < 0) {
1609            throw new IllegalArgumentException("Requested time not correct");
1610        }
1611
1612        boolean semAcquireDone = false;
1613        if (!mPreviewInProgress) {
1614            try{
1615                semAcquireDone = lock(ENGINE_ACCESS_MAX_TIMEOUT_MS);
1616                if (semAcquireDone == false) {
1617                    throw new IllegalStateException("Timeout waiting for semaphore");
1618                }
1619
1620                if (mMANativeHelper == null) {
1621                    throw new IllegalStateException("The video editor is not initialized");
1622                }
1623
1624                if (mMediaItems.size() > 0) {
1625                    mPreviewInProgress = true;
1626                    mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions,
1627                                                      mAudioTracks, null);
1628                    mMANativeHelper.doPreview(surface, fromMs, toMs, loop,
1629                                     callbackAfterFrameCount, listener);
1630                }
1631                /**
1632                 *  Release The lock on complete by calling stopPreview
1633                 */
1634            } catch (InterruptedException ex) {
1635                Log.w(TAG, "The thread was interrupted", new Throwable());
1636                throw new IllegalStateException("The thread was interrupted");
1637            }
1638         } else {
1639            throw new IllegalStateException("Preview already in progress");
1640        }
1641    }
1642
1643    /*
1644     * {@inheritDoc}
1645     */
1646    public long stopPreview() {
1647        long result = 0;
1648        if (mPreviewInProgress) {
1649            try {
1650                result = mMANativeHelper.stopPreview();
1651                /**
1652                 *  release on complete by calling stopPreview
1653                 */
1654                } finally {
1655                    mPreviewInProgress = false;
1656                    unlock();
1657                }
1658            return result;
1659        }
1660        else {
1661            return 0;
1662        }
1663    }
1664
1665    /*
1666     * Remove transitions associated with the specified media item
1667     *
1668     * @param mediaItem The media item
1669     */
1670    private void removeAdjacentTransitions(MediaItem mediaItem) {
1671        final Transition beginTransition = mediaItem.getBeginTransition();
1672        if (beginTransition != null) {
1673            if (beginTransition.getAfterMediaItem() != null) {
1674                beginTransition.getAfterMediaItem().setEndTransition(null);
1675            }
1676            beginTransition.invalidate();
1677            mTransitions.remove(beginTransition);
1678        }
1679
1680        final Transition endTransition = mediaItem.getEndTransition();
1681        if (endTransition != null) {
1682            if (endTransition.getBeforeMediaItem() != null) {
1683                endTransition.getBeforeMediaItem().setBeginTransition(null);
1684            }
1685            endTransition.invalidate();
1686            mTransitions.remove(endTransition);
1687        }
1688
1689        mediaItem.setBeginTransition(null);
1690        mediaItem.setEndTransition(null);
1691    }
1692
1693    /**
1694     * Remove the transition before this media item
1695     *
1696     * @param index The media item index
1697     */
1698    private void removeTransitionBefore(int index) {
1699        final MediaItem mediaItem = mMediaItems.get(index);
1700        final Iterator<Transition> it = mTransitions.iterator();
1701        while (it.hasNext()) {
1702            Transition t = it.next();
1703            if (t.getBeforeMediaItem() == mediaItem) {
1704                mMANativeHelper.setGeneratePreview(true);
1705                it.remove();
1706                t.invalidate();
1707                mediaItem.setBeginTransition(null);
1708                if (index > 0) {
1709                    mMediaItems.get(index - 1).setEndTransition(null);
1710                }
1711                break;
1712            }
1713        }
1714    }
1715
1716    /**
1717     * Remove the transition after this media item
1718     *
1719     * @param mediaItem The media item
1720     */
1721    private void removeTransitionAfter(int index) {
1722        final MediaItem mediaItem = mMediaItems.get(index);
1723        final Iterator<Transition> it = mTransitions.iterator();
1724        while (it.hasNext()) {
1725            Transition t = it.next();
1726            if (t.getAfterMediaItem() == mediaItem) {
1727                mMANativeHelper.setGeneratePreview(true);
1728                it.remove();
1729                t.invalidate();
1730                mediaItem.setEndTransition(null);
1731                /**
1732                 *  Invalidate the reference in the next media item
1733                 */
1734                if (index < mMediaItems.size() - 1) {
1735                    mMediaItems.get(index + 1).setBeginTransition(null);
1736                }
1737                break;
1738            }
1739        }
1740    }
1741
1742    /**
1743     * Compute the duration
1744     */
1745    private void computeTimelineDuration() {
1746        mDurationMs = 0;
1747        final int mediaItemsCount = mMediaItems.size();
1748        for (int i = 0; i < mediaItemsCount; i++) {
1749            final MediaItem mediaItem = mMediaItems.get(i);
1750            mDurationMs += mediaItem.getTimelineDuration();
1751            if (mediaItem.getEndTransition() != null) {
1752                if (i < mediaItemsCount - 1) {
1753                    mDurationMs -= mediaItem.getEndTransition().getDuration();
1754                }
1755            }
1756        }
1757    }
1758
1759    /*
1760     * Generate the project thumbnail
1761     */
1762    private void generateProjectThumbnail() {
1763        /*
1764         * If a thumbnail already exists, then delete it first
1765         */
1766        if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) {
1767            (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete();
1768        }
1769        /*
1770         * Generate a new thumbnail for the project from first media Item
1771         */
1772        if (mMediaItems.size() > 0) {
1773            MediaItem mI = mMediaItems.get(0);
1774            /*
1775             * Lets initialize the width for default aspect ratio i.e 16:9
1776             */
1777            int height = 480;
1778            int width = 854;
1779            switch (mI.getAspectRatio()) {
1780                case MediaProperties.ASPECT_RATIO_3_2:
1781                    width = 720;
1782                    break;
1783                case MediaProperties.ASPECT_RATIO_4_3:
1784                    width = 640;
1785                    break;
1786                case MediaProperties.ASPECT_RATIO_5_3:
1787                    width = 800;
1788                    break;
1789                case MediaProperties.ASPECT_RATIO_11_9:
1790                    width = 586;
1791                    break;
1792                case MediaProperties.ASPECT_RATIO_16_9:
1793                case MediaProperties.ASPECT_RATIO_UNDEFINED:
1794                    break;
1795            }
1796
1797            Bitmap projectBitmap = null;
1798            try {
1799                projectBitmap = mI.getThumbnail(width, height, 500);
1800            } catch (IllegalArgumentException e) {
1801                throw new IllegalArgumentException ("Illegal argument error creating project thumbnail");
1802            } catch (IOException e) {
1803                throw new IllegalArgumentException ("IO Error creating project thumbnail");
1804            }
1805
1806            try {
1807                FileOutputStream stream = new FileOutputStream(mProjectPath + "/"
1808                                                          + THUMBNAIL_FILENAME);
1809                projectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
1810                stream.flush();
1811                stream.close();
1812            } catch (IOException e) {
1813                throw new IllegalArgumentException ("Error creating project thumbnail");
1814            } finally {
1815                projectBitmap.recycle();
1816            }
1817        }
1818    }
1819
1820    /**
1821     * Clears the preview surface
1822     *
1823     * @param surfaceHolder SurfaceHolder where the preview is rendered
1824     * and needs to be cleared.
1825     */
1826    public void clearSurface(SurfaceHolder surfaceHolder) {
1827        if (surfaceHolder == null) {
1828            throw new IllegalArgumentException("Invalid surface holder");
1829        }
1830
1831        final Surface surface = surfaceHolder.getSurface();
1832        if (surface == null) {
1833            throw new IllegalArgumentException("Surface could not be retrieved from surface holder");
1834        }
1835
1836        if (mMANativeHelper != null) {
1837            mMANativeHelper.clearPreviewSurface(surface);
1838        } else {
1839            Log.w(TAG, "Native helper was not ready!");
1840        }
1841    }
1842
1843    /**
1844     * Grab the semaphore which arbitrates access to the editor
1845     *
1846     * @throws InterruptedException
1847     */
1848    private void lock() throws InterruptedException {
1849        if (Log.isLoggable(TAG, Log.DEBUG)) {
1850            Log.d(TAG, "lock: grabbing semaphore", new Throwable());
1851        }
1852        mLock.acquire();
1853        if (Log.isLoggable(TAG, Log.DEBUG)) {
1854            Log.d(TAG, "lock: grabbed semaphore");
1855        }
1856    }
1857
1858    /**
1859     * Tries to grab the semaphore with a specified time out which arbitrates access to the editor
1860     *
1861     * @param timeoutMs time out in ms.
1862     *
1863     * @return true if the semaphore is acquired, false otherwise
1864     * @throws InterruptedException
1865     */
1866    private boolean lock(long timeoutMs) throws InterruptedException {
1867        if (Log.isLoggable(TAG, Log.DEBUG)) {
1868            Log.d(TAG, "lock: grabbing semaphore with timeout " + timeoutMs, new Throwable());
1869        }
1870
1871        boolean acquireSem = mLock.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
1872        if (Log.isLoggable(TAG, Log.DEBUG)) {
1873            Log.d(TAG, "lock: grabbed semaphore status " + acquireSem);
1874        }
1875
1876        return acquireSem;
1877    }
1878
1879    /**
1880     * Release the semaphore which arbitrates access to the editor
1881     */
1882    private void unlock() {
1883        if (Log.isLoggable(TAG, Log.DEBUG)) {
1884            Log.d(TAG, "unlock: releasing semaphore");
1885        }
1886        mLock.release();
1887    }
1888}
1889