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