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