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