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