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