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