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