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