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