VideoEditorImpl.java revision 3f1c57394091de7204d218898696baeb20c04f25
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            while (eventType != XmlPullParser.END_DOCUMENT) {
932                switch (eventType) {
933                    case XmlPullParser.START_TAG: {
934                        name = parser.getName();
935                        if (TAG_PROJECT.equals(name)) {
936                            mAspectRatio = Integer.parseInt(parser.getAttributeValue("",
937                                   ATTR_ASPECT_RATIO));
938
939                            final boolean mRegenPCM =
940                                Boolean.parseBoolean(parser.getAttributeValue("",
941                                    ATTR_REGENERATE_PCM));
942                            mMANativeHelper.setAudioflag(mRegenPCM);
943                        } else if (TAG_MEDIA_ITEM.equals(name)) {
944                            final String mediaItemId = parser.getAttributeValue("", ATTR_ID);
945                            try {
946                                currentMediaItem = parseMediaItem(parser);
947                                mMediaItems.add(currentMediaItem);
948                            } catch (Exception ex) {
949                                Log.w(TAG, "Cannot load media item: " + mediaItemId, ex);
950                                currentMediaItem = null;
951                                // Ignore the media item
952                                ignoredMediaItems.add(mediaItemId);
953                            }
954                        } else if (TAG_TRANSITION.equals(name)) {
955                            try {
956                                final Transition transition = parseTransition(parser,
957                                        ignoredMediaItems);
958                                // The transition will be null if the bounding
959                                // media items are ignored
960                                if (transition != null) {
961                                    mTransitions.add(transition);
962                                }
963                            } catch (Exception ex) {
964                                Log.w(TAG, "Cannot load transition", ex);
965                            }
966                        } else if (TAG_OVERLAY.equals(name)) {
967                            if (currentMediaItem != null) {
968                                try {
969                                    currentOverlay = parseOverlay(parser, currentMediaItem);
970                                    currentMediaItem.addOverlay(currentOverlay);
971                                } catch (Exception ex) {
972                                    Log.w(TAG, "Cannot load overlay", ex);
973                                }
974                            }
975                        } else if (TAG_OVERLAY_USER_ATTRIBUTES.equals(name)) {
976                            if (currentOverlay != null) {
977                                final int attributesCount = parser.getAttributeCount();
978                                for (int i = 0; i < attributesCount; i++) {
979                                    currentOverlay.setUserAttribute(parser.getAttributeName(i),
980                                            parser.getAttributeValue(i));
981                                }
982                            }
983                        } else if (TAG_EFFECT.equals(name)) {
984                            if (currentMediaItem != null) {
985                                try {
986                                    final Effect effect = parseEffect(parser, currentMediaItem);
987                                    currentMediaItem.addEffect(effect);
988
989                                    if (effect instanceof EffectKenBurns) {
990                                        final boolean isImageClipGenerated =
991                                               Boolean.parseBoolean(parser.getAttributeValue("",
992                                                                  ATTR_IS_IMAGE_CLIP_GENERATED));
993                                        if(isImageClipGenerated) {
994                                            final String filename = parser.getAttributeValue("",
995                                                                  ATTR_GENERATED_IMAGE_CLIP);
996                                            if (new File(filename).exists() == true) {
997                                                ((MediaImageItem)currentMediaItem).
998                                                            setGeneratedImageClip(filename);
999                                                ((MediaImageItem)currentMediaItem).
1000                                                             setRegenerateClip(false);
1001                                             } else {
1002                                               ((MediaImageItem)currentMediaItem).
1003                                                             setGeneratedImageClip(null);
1004                                               ((MediaImageItem)currentMediaItem).
1005                                                             setRegenerateClip(true);
1006                                             }
1007                                        } else {
1008                                            ((MediaImageItem)currentMediaItem).
1009                                                             setGeneratedImageClip(null);
1010                                            ((MediaImageItem)currentMediaItem).
1011                                                            setRegenerateClip(true);
1012                                        }
1013                                    }
1014                                } catch (Exception ex) {
1015                                    Log.w(TAG, "Cannot load effect", ex);
1016                                }
1017                            }
1018                        } else if (TAG_AUDIO_TRACK.equals(name)) {
1019                            try {
1020                                final AudioTrack audioTrack = parseAudioTrack(parser);
1021                                addAudioTrack(audioTrack);
1022                            } catch (Exception ex) {
1023                                Log.w(TAG, "Cannot load audio track", ex);
1024                            }
1025                        }
1026                        break;
1027                    }
1028
1029                    case XmlPullParser.END_TAG: {
1030                        name = parser.getName();
1031                        if (TAG_MEDIA_ITEM.equals(name)) {
1032                            currentMediaItem = null;
1033                        } else if (TAG_OVERLAY.equals(name)) {
1034                            currentOverlay = null;
1035                        }
1036                        break;
1037                    }
1038
1039                    default: {
1040                        break;
1041                    }
1042                }
1043                eventType = parser.next();
1044            }
1045            computeTimelineDuration();
1046        } finally {
1047            if (fis != null) {
1048                fis.close();
1049            }
1050        }
1051    }
1052
1053    /**
1054     * Parse the media item
1055     *
1056     * @param parser The parser
1057     * @return The media item
1058     */
1059    private MediaItem parseMediaItem(XmlPullParser parser) throws IOException {
1060        final String mediaItemId = parser.getAttributeValue("", ATTR_ID);
1061        final String type = parser.getAttributeValue("", ATTR_TYPE);
1062        final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1063        final int renderingMode = Integer.parseInt(parser.getAttributeValue("",
1064                ATTR_RENDERING_MODE));
1065
1066        final MediaItem currentMediaItem;
1067        if (MediaImageItem.class.getSimpleName().equals(type)) {
1068            final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
1069            currentMediaItem = new MediaImageItem(this, mediaItemId, filename,
1070                    durationMs, renderingMode);
1071        } else if (MediaVideoItem.class.getSimpleName().equals(type)) {
1072            final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1073            final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
1074            final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
1075            final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED));
1076            final String audioWaveformFilename = parser.getAttributeValue("",
1077                    ATTR_AUDIO_WAVEFORM_FILENAME);
1078            currentMediaItem = new MediaVideoItem(this, mediaItemId, filename,
1079                    renderingMode, beginMs, endMs, volume, muted, audioWaveformFilename);
1080
1081            final long beginTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1082            final long endTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
1083            ((MediaVideoItem)currentMediaItem).setExtractBoundaries(beginTimeMs, endTimeMs);
1084
1085            final int volumePercent = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
1086            ((MediaVideoItem)currentMediaItem).setVolume(volumePercent);
1087        } else {
1088            throw new IllegalArgumentException("Unknown media item type: " + type);
1089        }
1090
1091        return currentMediaItem;
1092    }
1093
1094    /**
1095     * Parse the transition
1096     *
1097     * @param parser The parser
1098     * @param ignoredMediaItems The list of ignored media items
1099     *
1100     * @return The transition
1101     */
1102    private Transition parseTransition(XmlPullParser parser, List<String> ignoredMediaItems) {
1103        final String transitionId = parser.getAttributeValue("", ATTR_ID);
1104        final String type = parser.getAttributeValue("", ATTR_TYPE);
1105        final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
1106        final int behavior = Integer.parseInt(parser.getAttributeValue("", ATTR_BEHAVIOR));
1107
1108        final String beforeMediaItemId = parser.getAttributeValue("", ATTR_BEFORE_MEDIA_ITEM_ID);
1109        final MediaItem beforeMediaItem;
1110        if (beforeMediaItemId != null) {
1111            if (ignoredMediaItems.contains(beforeMediaItemId)) {
1112                // This transition is ignored
1113                return null;
1114            }
1115
1116            beforeMediaItem = getMediaItem(beforeMediaItemId);
1117        } else {
1118            beforeMediaItem = null;
1119        }
1120
1121        final String afterMediaItemId = parser.getAttributeValue("", ATTR_AFTER_MEDIA_ITEM_ID);
1122        final MediaItem afterMediaItem;
1123        if (afterMediaItemId != null) {
1124            if (ignoredMediaItems.contains(afterMediaItemId)) {
1125                // This transition is ignored
1126                return null;
1127            }
1128
1129            afterMediaItem = getMediaItem(afterMediaItemId);
1130        } else {
1131            afterMediaItem = null;
1132        }
1133
1134        final Transition transition;
1135        if (TransitionAlpha.class.getSimpleName().equals(type)) {
1136            final int blending = Integer.parseInt(parser.getAttributeValue("", ATTR_BLENDING));
1137            final String maskFilename = parser.getAttributeValue("", ATTR_MASK);
1138            final boolean invert = Boolean.getBoolean(parser.getAttributeValue("", ATTR_INVERT));
1139            transition = new TransitionAlpha(transitionId, afterMediaItem, beforeMediaItem,
1140                    durationMs, behavior, maskFilename, blending, invert);
1141        } else if (TransitionCrossfade.class.getSimpleName().equals(type)) {
1142            transition = new TransitionCrossfade(transitionId, afterMediaItem, beforeMediaItem,
1143                    durationMs, behavior);
1144        } else if (TransitionSliding.class.getSimpleName().equals(type)) {
1145            final int direction = Integer.parseInt(parser.getAttributeValue("", ATTR_DIRECTION));
1146            transition = new TransitionSliding(transitionId, afterMediaItem, beforeMediaItem,
1147                    durationMs, behavior, direction);
1148        } else if (TransitionFadeBlack.class.getSimpleName().equals(type)) {
1149            transition = new TransitionFadeBlack(transitionId, afterMediaItem, beforeMediaItem,
1150                    durationMs, behavior);
1151        } else {
1152            throw new IllegalArgumentException("Invalid transition type: " + type);
1153        }
1154
1155        final boolean isTransitionGenerated = Boolean.parseBoolean(parser.getAttributeValue("",
1156                                                 ATTR_IS_TRANSITION_GENERATED));
1157        if (isTransitionGenerated == true) {
1158            final String transitionFile = parser.getAttributeValue("",
1159                                                ATTR_GENERATED_TRANSITION_CLIP);
1160
1161            if (new File(transitionFile).exists()) {
1162                transition.setFilename(transitionFile);
1163            } else {
1164                transition.setFilename(null);
1165            }
1166        }
1167
1168        // Use the transition
1169        if (beforeMediaItem != null) {
1170            beforeMediaItem.setBeginTransition(transition);
1171        }
1172
1173        if (afterMediaItem != null) {
1174            afterMediaItem.setEndTransition(transition);
1175        }
1176
1177        return transition;
1178    }
1179
1180    /**
1181     * Parse the overlay
1182     *
1183     * @param parser The parser
1184     * @param mediaItem The media item owner
1185     *
1186     * @return The overlay
1187     */
1188    private Overlay parseOverlay(XmlPullParser parser, MediaItem mediaItem) {
1189        final String overlayId = parser.getAttributeValue("", ATTR_ID);
1190        final String type = parser.getAttributeValue("", ATTR_TYPE);
1191        final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
1192        final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1193
1194        final Overlay overlay;
1195        if (OverlayFrame.class.getSimpleName().equals(type)) {
1196            final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1197            overlay = new OverlayFrame(mediaItem, overlayId, filename, startTimeMs, durationMs);
1198        } else {
1199            throw new IllegalArgumentException("Invalid overlay type: " + type);
1200        }
1201
1202        final String overlayRgbFileName = parser.getAttributeValue("", ATTR_OVERLAY_RGB_FILENAME);
1203        if (overlayRgbFileName != null) {
1204            ((OverlayFrame)overlay).setFilename(overlayRgbFileName);
1205
1206            final int overlayFrameWidth = Integer.parseInt(parser.getAttributeValue("",
1207                                   ATTR_OVERLAY_FRAME_WIDTH));
1208            final int overlayFrameHeight = Integer.parseInt(parser.getAttributeValue("",
1209                                   ATTR_OVERLAY_FRAME_HEIGHT));
1210
1211            ((OverlayFrame)overlay).setOverlayFrameWidth(overlayFrameWidth);
1212            ((OverlayFrame)overlay).setOverlayFrameHeight(overlayFrameHeight);
1213
1214            final int resizedRGBFrameWidth = Integer.parseInt(parser.getAttributeValue("",
1215                                   ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH));
1216            final int resizedRGBFrameHeight = Integer.parseInt(parser.getAttributeValue("",
1217                                   ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT));
1218
1219            ((OverlayFrame)overlay).setResizedRGBSize(resizedRGBFrameWidth, resizedRGBFrameHeight);
1220        }
1221
1222        return overlay;
1223    }
1224
1225    /**
1226     * Parse the effect
1227     *
1228     * @param parser The parser
1229     * @param mediaItem The media item owner
1230     *
1231     * @return The effect
1232     */
1233    private Effect parseEffect(XmlPullParser parser, MediaItem mediaItem) {
1234        final String effectId = parser.getAttributeValue("", ATTR_ID);
1235        final String type = parser.getAttributeValue("", ATTR_TYPE);
1236        final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
1237        final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1238
1239        final Effect effect;
1240        if (EffectColor.class.getSimpleName().equals(type)) {
1241            final int colorEffectType = Integer.parseInt(parser.getAttributeValue("",
1242                                                       ATTR_COLOR_EFFECT_TYPE));
1243            final int color;
1244            if (colorEffectType == EffectColor.TYPE_COLOR
1245                    || colorEffectType == EffectColor.TYPE_GRADIENT) {
1246                color = Integer.parseInt(parser.getAttributeValue("", ATTR_COLOR_EFFECT_VALUE));
1247            } else {
1248                color = 0;
1249            }
1250            effect = new EffectColor(mediaItem, effectId, startTimeMs,
1251                    durationMs, colorEffectType, color);
1252        } else if (EffectKenBurns.class.getSimpleName().equals(type)) {
1253            final Rect startRect = new Rect(
1254                    Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_LEFT)),
1255                    Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_TOP)),
1256                    Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_RIGHT)),
1257                    Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_BOTTOM)));
1258            final Rect endRect = new Rect(
1259                    Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_LEFT)),
1260                    Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_TOP)),
1261                    Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_RIGHT)),
1262                    Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_BOTTOM)));
1263            effect = new EffectKenBurns(mediaItem, effectId, startRect, endRect,
1264                                        startTimeMs, durationMs);
1265        } else {
1266            throw new IllegalArgumentException("Invalid effect type: " + type);
1267        }
1268
1269        return effect;
1270    }
1271
1272    /**
1273     * Parse the audio track
1274     *
1275     * @param parser The parser
1276     *
1277     * @return The audio track
1278     */
1279    private AudioTrack parseAudioTrack(XmlPullParser parser) throws IOException {
1280        final String audioTrackId = parser.getAttributeValue("", ATTR_ID);
1281        final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1282        final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_START_TIME));
1283        final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1284        final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
1285        final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
1286        final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED));
1287        final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_LOOP));
1288        final boolean duckingEnabled = Boolean.parseBoolean(
1289                parser.getAttributeValue("", ATTR_DUCK_ENABLED));
1290        final int duckThreshold = Integer.parseInt(
1291                parser.getAttributeValue("", ATTR_DUCK_THRESHOLD));
1292        final int duckedTrackVolume = Integer.parseInt(parser.getAttributeValue("",
1293                                                     ATTR_DUCKED_TRACK_VOLUME));
1294
1295        final String waveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME);
1296        final AudioTrack audioTrack = new AudioTrack(this, audioTrackId,
1297                                                     filename, startTimeMs,
1298                                                     beginMs, endMs, loop,
1299                                                     volume, muted,
1300                                                     duckingEnabled,
1301                                                     duckThreshold,
1302                                                     duckedTrackVolume,
1303                                                     waveformFilename);
1304
1305        return audioTrack;
1306    }
1307
1308    /*
1309     * {@inheritDoc}
1310     */
1311    public void save() throws IOException {
1312        final XmlSerializer serializer = Xml.newSerializer();
1313        final StringWriter writer = new StringWriter();
1314        serializer.setOutput(writer);
1315        serializer.startDocument("UTF-8", true);
1316        serializer.startTag("", TAG_PROJECT);
1317        serializer.attribute("",
1318                             ATTR_ASPECT_RATIO, Integer.toString(mAspectRatio));
1319
1320        serializer.attribute("", ATTR_REGENERATE_PCM,
1321                        Boolean.toString(mMANativeHelper.getAudioflag()));
1322
1323        serializer.startTag("", TAG_MEDIA_ITEMS);
1324        for (MediaItem mediaItem : mMediaItems) {
1325            serializer.startTag("", TAG_MEDIA_ITEM);
1326            serializer.attribute("", ATTR_ID, mediaItem.getId());
1327            serializer.attribute("", ATTR_TYPE,
1328                                          mediaItem.getClass().getSimpleName());
1329            serializer.attribute("", ATTR_FILENAME, mediaItem.getFilename());
1330            serializer.attribute("", ATTR_RENDERING_MODE, Integer.toString(
1331                    mediaItem.getRenderingMode()));
1332            if (mediaItem instanceof MediaVideoItem) {
1333                final MediaVideoItem mvi = (MediaVideoItem)mediaItem;
1334                serializer
1335                .attribute("", ATTR_BEGIN_TIME,
1336                                     Long.toString(mvi.getBoundaryBeginTime()));
1337                serializer.attribute("", ATTR_END_TIME,
1338                                       Long.toString(mvi.getBoundaryEndTime()));
1339                serializer.attribute("", ATTR_VOLUME,
1340                                             Integer.toString(mvi.getVolume()));
1341                serializer.attribute("", ATTR_MUTED,
1342                                               Boolean.toString(mvi.isMuted()));
1343                if (mvi.getAudioWaveformFilename() != null) {
1344                    serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
1345                            mvi.getAudioWaveformFilename());
1346                }
1347            } else if (mediaItem instanceof MediaImageItem) {
1348                serializer.attribute("", ATTR_DURATION,
1349                        Long.toString(mediaItem.getTimelineDuration()));
1350            }
1351
1352            final List<Overlay> overlays = mediaItem.getAllOverlays();
1353            if (overlays.size() > 0) {
1354                serializer.startTag("", TAG_OVERLAYS);
1355                for (Overlay overlay : overlays) {
1356                    serializer.startTag("", TAG_OVERLAY);
1357                    serializer.attribute("", ATTR_ID, overlay.getId());
1358                    serializer.attribute("",
1359                                 ATTR_TYPE, overlay.getClass().getSimpleName());
1360                    serializer.attribute("", ATTR_BEGIN_TIME,
1361                                         Long.toString(overlay.getStartTime()));
1362                    serializer.attribute("", ATTR_DURATION,
1363                                          Long.toString(overlay.getDuration()));
1364                    if (overlay instanceof OverlayFrame) {
1365                        final OverlayFrame overlayFrame = (OverlayFrame)overlay;
1366                        overlayFrame.save(getPath());
1367                        if (overlayFrame.getBitmapImageFileName() != null) {
1368                            serializer.attribute("", ATTR_FILENAME,
1369                                         overlayFrame.getBitmapImageFileName());
1370                        }
1371
1372                        if (overlayFrame.getFilename() != null) {
1373                            serializer.attribute("",
1374                                                 ATTR_OVERLAY_RGB_FILENAME,
1375                                                 overlayFrame.getFilename());
1376                            serializer.attribute("", ATTR_OVERLAY_FRAME_WIDTH,
1377                                                 Integer.toString(overlayFrame.getOverlayFrameWidth()));
1378                            serializer.attribute("", ATTR_OVERLAY_FRAME_HEIGHT,
1379                                                 Integer.toString(overlayFrame.getOverlayFrameHeight()));
1380                            serializer.attribute("", ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH,
1381                                                 Integer.toString(overlayFrame.getResizedRGBSizeWidth()));
1382                            serializer.attribute("", ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT,
1383                                                 Integer.toString(overlayFrame.getResizedRGBSizeHeight()));
1384
1385                        }
1386
1387                    }
1388
1389                    /**
1390                     *  Save the user attributes
1391                     */
1392                    serializer.startTag("", TAG_OVERLAY_USER_ATTRIBUTES);
1393                    final Map<String, String> userAttributes = overlay.getUserAttributes();
1394                    for (String name : userAttributes.keySet()) {
1395                        final String value = userAttributes.get(name);
1396                        if (value != null) {
1397                            serializer.attribute("", name, value);
1398                        }
1399                    }
1400                    serializer.endTag("", TAG_OVERLAY_USER_ATTRIBUTES);
1401
1402                    serializer.endTag("", TAG_OVERLAY);
1403                }
1404                serializer.endTag("", TAG_OVERLAYS);
1405            }
1406
1407            final List<Effect> effects = mediaItem.getAllEffects();
1408            if (effects.size() > 0) {
1409                serializer.startTag("", TAG_EFFECTS);
1410                for (Effect effect : effects) {
1411                    serializer.startTag("", TAG_EFFECT);
1412                    serializer.attribute("", ATTR_ID, effect.getId());
1413                    serializer.attribute("",
1414                                  ATTR_TYPE, effect.getClass().getSimpleName());
1415                    serializer.attribute("", ATTR_BEGIN_TIME,
1416                            Long.toString(effect.getStartTime()));
1417                    serializer.attribute("", ATTR_DURATION,
1418                                           Long.toString(effect.getDuration()));
1419                    if (effect instanceof EffectColor) {
1420                        final EffectColor colorEffect = (EffectColor)effect;
1421                        serializer.attribute("", ATTR_COLOR_EFFECT_TYPE,
1422                                Integer.toString(colorEffect.getType()));
1423                        if (colorEffect.getType() == EffectColor.TYPE_COLOR ||
1424                                colorEffect.getType() == EffectColor.TYPE_GRADIENT) {
1425                            serializer.attribute("", ATTR_COLOR_EFFECT_VALUE,
1426                                    Integer.toString(colorEffect.getColor()));
1427                        }
1428                    } else if (effect instanceof EffectKenBurns) {
1429                        final Rect startRect = ((EffectKenBurns)effect).getStartRect();
1430                        serializer.attribute("", ATTR_START_RECT_LEFT,
1431                                Integer.toString(startRect.left));
1432                        serializer.attribute("", ATTR_START_RECT_TOP,
1433                                Integer.toString(startRect.top));
1434                        serializer.attribute("", ATTR_START_RECT_RIGHT,
1435                                Integer.toString(startRect.right));
1436                        serializer.attribute("", ATTR_START_RECT_BOTTOM,
1437                                Integer.toString(startRect.bottom));
1438
1439                        final Rect endRect = ((EffectKenBurns)effect).getEndRect();
1440                        serializer.attribute("", ATTR_END_RECT_LEFT,
1441                                                Integer.toString(endRect.left));
1442                        serializer.attribute("", ATTR_END_RECT_TOP,
1443                                                 Integer.toString(endRect.top));
1444                        serializer.attribute("", ATTR_END_RECT_RIGHT,
1445                                               Integer.toString(endRect.right));
1446                        serializer.attribute("", ATTR_END_RECT_BOTTOM,
1447                                Integer.toString(endRect.bottom));
1448                        final MediaItem mItem = effect.getMediaItem();
1449                           if(((MediaImageItem)mItem).getGeneratedImageClip() != null) {
1450                               serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED,
1451                                       Boolean.toString(true));
1452                               serializer.attribute("", ATTR_GENERATED_IMAGE_CLIP,
1453                                     ((MediaImageItem)mItem).getGeneratedImageClip());
1454                            } else {
1455                                serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED,
1456                                     Boolean.toString(false));
1457                         }
1458                    }
1459
1460                    serializer.endTag("", TAG_EFFECT);
1461                }
1462                serializer.endTag("", TAG_EFFECTS);
1463            }
1464
1465            serializer.endTag("", TAG_MEDIA_ITEM);
1466        }
1467        serializer.endTag("", TAG_MEDIA_ITEMS);
1468
1469        serializer.startTag("", TAG_TRANSITIONS);
1470
1471        for (Transition transition : mTransitions) {
1472            serializer.startTag("", TAG_TRANSITION);
1473            serializer.attribute("", ATTR_ID, transition.getId());
1474            serializer.attribute("", ATTR_TYPE, transition.getClass().getSimpleName());
1475            serializer.attribute("", ATTR_DURATION, Long.toString(transition.getDuration()));
1476            serializer.attribute("", ATTR_BEHAVIOR, Integer.toString(transition.getBehavior()));
1477            serializer.attribute("", ATTR_IS_TRANSITION_GENERATED,
1478                                    Boolean.toString(transition.isGenerated()));
1479            if (transition.isGenerated() == true) {
1480                serializer.attribute("", ATTR_GENERATED_TRANSITION_CLIP, transition.mFilename);
1481            }
1482            final MediaItem afterMediaItem = transition.getAfterMediaItem();
1483            if (afterMediaItem != null) {
1484                serializer.attribute("", ATTR_AFTER_MEDIA_ITEM_ID, afterMediaItem.getId());
1485            }
1486
1487            final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
1488            if (beforeMediaItem != null) {
1489                serializer.attribute("", ATTR_BEFORE_MEDIA_ITEM_ID, beforeMediaItem.getId());
1490            }
1491
1492            if (transition instanceof TransitionSliding) {
1493                serializer.attribute("", ATTR_DIRECTION,
1494                        Integer.toString(((TransitionSliding)transition).getDirection()));
1495            } else if (transition instanceof TransitionAlpha) {
1496                TransitionAlpha ta = (TransitionAlpha)transition;
1497                serializer.attribute("", ATTR_BLENDING,
1498                                     Integer.toString(ta.getBlendingPercent()));
1499                serializer.attribute("", ATTR_INVERT,
1500                                               Boolean.toString(ta.isInvert()));
1501                if (ta.getMaskFilename() != null) {
1502                    serializer.attribute("", ATTR_MASK, ta.getMaskFilename());
1503                }
1504            }
1505            serializer.endTag("", TAG_TRANSITION);
1506        }
1507        serializer.endTag("", TAG_TRANSITIONS);
1508        serializer.startTag("", TAG_AUDIO_TRACKS);
1509        for (AudioTrack at : mAudioTracks) {
1510            serializer.startTag("", TAG_AUDIO_TRACK);
1511            serializer.attribute("", ATTR_ID, at.getId());
1512            serializer.attribute("", ATTR_FILENAME, at.getFilename());
1513            serializer.attribute("", ATTR_START_TIME, Long.toString(at.getStartTime()));
1514            serializer.attribute("", ATTR_BEGIN_TIME, Long.toString(at.getBoundaryBeginTime()));
1515            serializer.attribute("", ATTR_END_TIME, Long.toString(at.getBoundaryEndTime()));
1516            serializer.attribute("", ATTR_VOLUME, Integer.toString(at.getVolume()));
1517            serializer.attribute("", ATTR_DUCK_ENABLED,
1518                                       Boolean.toString(at.isDuckingEnabled()));
1519            serializer.attribute("", ATTR_DUCKED_TRACK_VOLUME,
1520                                   Integer.toString(at.getDuckedTrackVolume()));
1521            serializer.attribute("", ATTR_DUCK_THRESHOLD,
1522                                   Integer.toString(at.getDuckingThreshhold()));
1523            serializer.attribute("", ATTR_MUTED, Boolean.toString(at.isMuted()));
1524            serializer.attribute("", ATTR_LOOP, Boolean.toString(at.isLooping()));
1525            if (at.getAudioWaveformFilename() != null) {
1526                serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
1527                        at.getAudioWaveformFilename());
1528            }
1529
1530            serializer.endTag("", TAG_AUDIO_TRACK);
1531        }
1532        serializer.endTag("", TAG_AUDIO_TRACKS);
1533
1534        serializer.endTag("", TAG_PROJECT);
1535        serializer.endDocument();
1536
1537        /**
1538         *  Save the metadata XML file
1539         */
1540        final FileOutputStream out = new FileOutputStream(new File(getPath(),
1541                                                          PROJECT_FILENAME));
1542        out.write(writer.toString().getBytes());
1543        out.flush();
1544        out.close();
1545    }
1546
1547    /*
1548     * {@inheritDoc}
1549     */
1550    public void setAspectRatio(int aspectRatio) {
1551        mAspectRatio = aspectRatio;
1552        /**
1553         *  Invalidate all transitions
1554         */
1555        mMANativeHelper.setGeneratePreview(true);
1556
1557        for (Transition transition : mTransitions) {
1558            transition.invalidate();
1559        }
1560
1561        final Iterator<MediaItem> it = mMediaItems.iterator();
1562
1563        while (it.hasNext()) {
1564            final MediaItem t = it.next();
1565            List<Overlay> overlayList = t.getAllOverlays();
1566            for (Overlay overlay : overlayList) {
1567
1568                ((OverlayFrame)overlay).invalidateGeneratedFiles();
1569            }
1570        }
1571    }
1572
1573    /*
1574     * {@inheritDoc}
1575     */
1576    public void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs,
1577                             boolean loop, int callbackAfterFrameCount,
1578                             PreviewProgressListener listener) {
1579
1580        if (surfaceHolder == null) {
1581            throw new IllegalArgumentException();
1582        }
1583
1584        final Surface surface = surfaceHolder.getSurface();
1585        if (surface == null) {
1586            throw new IllegalArgumentException("Surface could not be retrieved from surface holder");
1587        }
1588
1589        if (listener == null) {
1590            throw new IllegalArgumentException();
1591        }
1592
1593        if (fromMs >= mDurationMs) {
1594            throw new IllegalArgumentException("Requested time not correct");
1595        }
1596
1597        if (fromMs < 0) {
1598            throw new IllegalArgumentException("Requested time not correct");
1599        }
1600
1601        boolean semAcquireDone = false;
1602        if (!mPreviewInProgress) {
1603            try{
1604                semAcquireDone = lock(ENGINE_ACCESS_MAX_TIMEOUT_MS);
1605                if (semAcquireDone == false) {
1606                    throw new IllegalStateException("Timeout waiting for semaphore");
1607                }
1608
1609                if (mMANativeHelper == null) {
1610                    throw new IllegalStateException("The video editor is not initialized");
1611                }
1612
1613                if (mMediaItems.size() > 0) {
1614                    mPreviewInProgress = true;
1615                    mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions,
1616                                                      mAudioTracks, null);
1617                    mMANativeHelper.doPreview(surface, fromMs, toMs, loop,
1618                                     callbackAfterFrameCount, listener);
1619                }
1620                /**
1621                 *  Release The lock on complete by calling stopPreview
1622                 */
1623            } catch (InterruptedException ex) {
1624                Log.w(TAG, "The thread was interrupted", new Throwable());
1625                throw new IllegalStateException("The thread was interrupted");
1626            }
1627         } else {
1628            throw new IllegalStateException("Preview already in progress");
1629        }
1630    }
1631
1632    /*
1633     * {@inheritDoc}
1634     */
1635    public long stopPreview() {
1636        long result = 0;
1637        if (mPreviewInProgress) {
1638            try {
1639                result = mMANativeHelper.stopPreview();
1640                /**
1641                 *  release on complete by calling stopPreview
1642                 */
1643                } finally {
1644                    mPreviewInProgress = false;
1645                    unlock();
1646                }
1647            return result;
1648        }
1649        else {
1650            return 0;
1651        }
1652    }
1653
1654    /*
1655     * Remove transitions associated with the specified media item
1656     *
1657     * @param mediaItem The media item
1658     */
1659    private void removeAdjacentTransitions(MediaItem mediaItem) {
1660        final Transition beginTransition = mediaItem.getBeginTransition();
1661        if (beginTransition != null) {
1662            if (beginTransition.getAfterMediaItem() != null) {
1663                beginTransition.getAfterMediaItem().setEndTransition(null);
1664            }
1665            beginTransition.invalidate();
1666            mTransitions.remove(beginTransition);
1667        }
1668
1669        final Transition endTransition = mediaItem.getEndTransition();
1670        if (endTransition != null) {
1671            if (endTransition.getBeforeMediaItem() != null) {
1672                endTransition.getBeforeMediaItem().setBeginTransition(null);
1673            }
1674            endTransition.invalidate();
1675            mTransitions.remove(endTransition);
1676        }
1677
1678        mediaItem.setBeginTransition(null);
1679        mediaItem.setEndTransition(null);
1680    }
1681
1682    /**
1683     * Remove the transition before this media item
1684     *
1685     * @param index The media item index
1686     */
1687    private void removeTransitionBefore(int index) {
1688        final MediaItem mediaItem = mMediaItems.get(index);
1689        final Iterator<Transition> it = mTransitions.iterator();
1690        while (it.hasNext()) {
1691            Transition t = it.next();
1692            if (t.getBeforeMediaItem() == mediaItem) {
1693                mMANativeHelper.setGeneratePreview(true);
1694                it.remove();
1695                t.invalidate();
1696                mediaItem.setBeginTransition(null);
1697                if (index > 0) {
1698                    mMediaItems.get(index - 1).setEndTransition(null);
1699                }
1700                break;
1701            }
1702        }
1703    }
1704
1705    /**
1706     * Remove the transition after this media item
1707     *
1708     * @param mediaItem The media item
1709     */
1710    private void removeTransitionAfter(int index) {
1711        final MediaItem mediaItem = mMediaItems.get(index);
1712        final Iterator<Transition> it = mTransitions.iterator();
1713        while (it.hasNext()) {
1714            Transition t = it.next();
1715            if (t.getAfterMediaItem() == mediaItem) {
1716                mMANativeHelper.setGeneratePreview(true);
1717                it.remove();
1718                t.invalidate();
1719                mediaItem.setEndTransition(null);
1720                /**
1721                 *  Invalidate the reference in the next media item
1722                 */
1723                if (index < mMediaItems.size() - 1) {
1724                    mMediaItems.get(index + 1).setBeginTransition(null);
1725                }
1726                break;
1727            }
1728        }
1729    }
1730
1731    /**
1732     * Compute the duration
1733     */
1734    private void computeTimelineDuration() {
1735        mDurationMs = 0;
1736        final int mediaItemsCount = mMediaItems.size();
1737        for (int i = 0; i < mediaItemsCount; i++) {
1738            final MediaItem mediaItem = mMediaItems.get(i);
1739            mDurationMs += mediaItem.getTimelineDuration();
1740            if (mediaItem.getEndTransition() != null) {
1741                if (i < mediaItemsCount - 1) {
1742                    mDurationMs -= mediaItem.getEndTransition().getDuration();
1743                }
1744            }
1745        }
1746    }
1747
1748    /*
1749     * Generate the project thumbnail
1750     */
1751    private void generateProjectThumbnail() {
1752        /*
1753         * If a thumbnail already exists, then delete it first
1754         */
1755        if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) {
1756            (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete();
1757        }
1758        /*
1759         * Generate a new thumbnail for the project from first media Item
1760         */
1761        if (mMediaItems.size() > 0) {
1762            MediaItem mI = mMediaItems.get(0);
1763            /*
1764             * Lets initialize the width for default aspect ratio i.e 16:9
1765             */
1766            int height = 480;
1767            int width = 854;
1768            switch (mI.getAspectRatio()) {
1769                case MediaProperties.ASPECT_RATIO_3_2:
1770                    width = 720;
1771                    break;
1772                case MediaProperties.ASPECT_RATIO_4_3:
1773                    width = 640;
1774                    break;
1775                case MediaProperties.ASPECT_RATIO_5_3:
1776                    width = 800;
1777                    break;
1778                case MediaProperties.ASPECT_RATIO_11_9:
1779                    width = 586;
1780                    break;
1781                case MediaProperties.ASPECT_RATIO_16_9:
1782                case MediaProperties.ASPECT_RATIO_UNDEFINED:
1783                    break;
1784            }
1785
1786            Bitmap projectBitmap = null;
1787            try {
1788                projectBitmap = mI.getThumbnail(width, height, 500);
1789            } catch (IllegalArgumentException e) {
1790                throw new IllegalArgumentException ("Illegal argument error creating project thumbnail");
1791            } catch (IOException e) {
1792                throw new IllegalArgumentException ("IO Error creating project thumbnail");
1793            }
1794
1795            try {
1796                FileOutputStream stream = new FileOutputStream(mProjectPath + "/"
1797                                                          + THUMBNAIL_FILENAME);
1798                projectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
1799                stream.flush();
1800                stream.close();
1801            } catch (IOException e) {
1802                throw new IllegalArgumentException ("Error creating project thumbnail");
1803            } finally {
1804                projectBitmap.recycle();
1805            }
1806        }
1807    }
1808
1809    /**
1810     * Clears the preview surface
1811     *
1812     * @param surfaceHolder SurfaceHolder where the preview is rendered
1813     * and needs to be cleared.
1814     */
1815    public void clearSurface(SurfaceHolder surfaceHolder) {
1816        if (surfaceHolder == null) {
1817            throw new IllegalArgumentException("Invalid surface holder");
1818        }
1819
1820        final Surface surface = surfaceHolder.getSurface();
1821        if (surface == null) {
1822            throw new IllegalArgumentException("Surface could not be retrieved from surface holder");
1823        }
1824
1825        if (mMANativeHelper != null) {
1826            mMANativeHelper.clearPreviewSurface(surface);
1827        } else {
1828            Log.w(TAG, "Native helper was not ready!");
1829        }
1830    }
1831
1832    /**
1833     * Grab the semaphore which arbitrates access to the editor
1834     *
1835     * @throws InterruptedException
1836     */
1837    private void lock() throws InterruptedException {
1838        if (Log.isLoggable(TAG, Log.DEBUG)) {
1839            Log.d(TAG, "lock: grabbing semaphore", new Throwable());
1840        }
1841        mLock.acquire();
1842        if (Log.isLoggable(TAG, Log.DEBUG)) {
1843            Log.d(TAG, "lock: grabbed semaphore");
1844        }
1845    }
1846
1847    /**
1848     * Tries to grab the semaphore with a specified time out which arbitrates access to the editor
1849     *
1850     * @param timeoutMs time out in ms.
1851     *
1852     * @return true if the semaphore is acquired, false otherwise
1853     * @throws InterruptedException
1854     */
1855    private boolean lock(long timeoutMs) throws InterruptedException {
1856        if (Log.isLoggable(TAG, Log.DEBUG)) {
1857            Log.d(TAG, "lock: grabbing semaphore with timeout " + timeoutMs, new Throwable());
1858        }
1859
1860        boolean acquireSem = mLock.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
1861        if (Log.isLoggable(TAG, Log.DEBUG)) {
1862            Log.d(TAG, "lock: grabbed semaphore status " + acquireSem);
1863        }
1864
1865        return acquireSem;
1866    }
1867
1868    /**
1869     * Release the semaphore which arbitrates access to the editor
1870     */
1871    private void unlock() {
1872        if (Log.isLoggable(TAG, Log.DEBUG)) {
1873            Log.d(TAG, "unlock: releasing semaphore");
1874        }
1875        mLock.release();
1876    }
1877}
1878