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