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