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 package com.android.videoeditor.service; 18 19 import java.io.File; 20 import java.io.FileInputStream; 21 import java.io.FileNotFoundException; 22 import java.io.FileOutputStream; 23 import java.io.IOException; 24 import java.io.StringWriter; 25 import java.util.ArrayList; 26 import java.util.List; 27 28 import org.xmlpull.v1.XmlPullParser; 29 import org.xmlpull.v1.XmlPullParserException; 30 import org.xmlpull.v1.XmlSerializer; 31 32 import android.media.videoeditor.MediaProperties; 33 import android.media.videoeditor.MediaVideoItem; 34 import android.media.videoeditor.VideoEditor; 35 import android.media.videoeditor.VideoEditor.PreviewProgressListener; 36 import android.net.Uri; 37 import android.util.Xml; 38 import android.view.SurfaceHolder; 39 40 41 /** 42 * The video editor project encapsulates the video editor and the project metadata. 43 */ 44 public class VideoEditorProject { 45 // The name of the metadata file 46 private final static String PROJECT_METADATA_FILENAME = "metadata.xml"; 47 48 public static final int DEFAULT_ZOOM_LEVEL = 20; 49 50 // XML definitions 51 private static final String TAG_PROJECT = "project"; 52 private static final String TAG_MOVIE = "movie"; 53 private static final String TAG_DOWNLOAD = "download"; 54 private static final String ATTR_NAME = "name"; 55 private static final String ATTR_URI = "uri"; 56 private static final String ATTR_SAVED = "saved"; 57 private static final String ATTR_THEME = "theme"; 58 private static final String ATTR_PLAYHEAD_POSITION = "playhead"; 59 private static final String ATTR_DURATION = "duration"; 60 private static final String ATTR_ZOOM_LEVEL = "zoom_level"; 61 private static final String ATTR_MIME = "mime"; 62 private static final String ATTR_FILENAME = "filename"; 63 private static final String ATTR_TIME = "time"; 64 65 // Instance variables 66 private final VideoEditor mVideoEditor; 67 private final String mProjectPath; 68 private final long mProjectDurationMs; 69 private final List<Download> mDownloads; 70 private String mProjectName; 71 private long mLastSaved; 72 private Uri mExportedMovieUri; 73 private int mAspectRatio; 74 private String mTheme; 75 private long mPlayheadPosMs; 76 private int mZoomLevel; 77 private List<MovieMediaItem> mMediaItems = new ArrayList<MovieMediaItem>(); 78 private List<MovieAudioTrack> mAudioTracks = new ArrayList<MovieAudioTrack>(); 79 private boolean mClean; 80 81 /** 82 * Download item 83 */ 84 public static class Download { 85 private final String mMediaUri; 86 private final String mMimeType; 87 private final String mFilename; 88 private final long mTime; 89 90 /** 91 * Constructor 92 * 93 * @param mediaUri The media URI 94 * @param mimeType The mime type 95 * @param filename The filename 96 * @param time The time when the file was downloaded 97 */ 98 private Download(String mediaUri, String mimeType, String filename, long time) { 99 mMediaUri = mediaUri; 100 mMimeType = mimeType; 101 mFilename = filename; 102 mTime = time; 103 } 104 105 /** 106 * @return the media URI 107 */ 108 public String getMediaUri() { 109 return mMediaUri; 110 } 111 112 /** 113 * @return the mime type 114 */ 115 public String getMimeType() { 116 return mMimeType; 117 } 118 119 /** 120 * @return the filename 121 */ 122 public String getFilename() { 123 return mFilename; 124 } 125 126 /** 127 * @return the mTime 128 */ 129 public long getTime() { 130 return mTime; 131 } 132 } 133 134 /** 135 * Constructor 136 * 137 * @param videoEditor The video editor. Note that this can be null when 138 * we create the project for the purpose of displaying a project preview. 139 * @param projectPath The project path 140 * @param projectName The project name 141 * @param lastSaved Time when project was last saved 142 * @param playheadPosMs The playhead position 143 * @param durationMs The project duration 144 * @param zoomLevel The zoom level 145 * @param exportedMovieUri The exported movie URI 146 * @param theme The project theme 147 * @param downloads The list of downloads 148 */ 149 VideoEditorProject(VideoEditor videoEditor, String projectPath, String projectName, 150 long lastSaved, long playheadPosMs, long durationMs, int zoomLevel, 151 Uri exportedMovieUri, String theme, List<Download> downloads) { 152 mVideoEditor = videoEditor; 153 if (videoEditor != null) { 154 mAspectRatio = videoEditor.getAspectRatio(); 155 } 156 157 if (downloads != null) { 158 mDownloads = downloads; 159 } else { 160 mDownloads = new ArrayList<Download>(); 161 } 162 mProjectPath = projectPath; 163 mProjectName = projectName; 164 mLastSaved = lastSaved; 165 mPlayheadPosMs = playheadPosMs; 166 mProjectDurationMs = durationMs; 167 mZoomLevel = zoomLevel; 168 mExportedMovieUri = exportedMovieUri; 169 mTheme = theme; 170 mClean = true; 171 } 172 173 /** 174 * @param clean true if this is clean 175 */ 176 public void setClean(boolean clean) { 177 mClean = clean; 178 } 179 180 /** 181 * @return true if no change was made 182 */ 183 public boolean isClean() { 184 return mClean; 185 } 186 187 /** 188 * @return The project path 189 */ 190 public String getPath() { 191 return mProjectPath; 192 } 193 194 /** 195 * @param projectName The project name 196 */ 197 public void setProjectName(String projectName) { 198 mProjectName = projectName; 199 mClean = false; 200 } 201 202 /** 203 * @return The project name 204 */ 205 public String getName() { 206 return mProjectName; 207 } 208 209 /** 210 * @return Time when time was last saved 211 */ 212 public long getLastSaved() { 213 return mLastSaved; 214 } 215 216 /** 217 * @return The project duration. 218 * 219 * Note: This method should only be called to retrieve the project duration 220 * as saved on disk. Once a project is opened call computeDuration() to get 221 * the current duration. 222 */ 223 public long getProjectDuration() { 224 return mProjectDurationMs; 225 } 226 227 /** 228 * @return The zoom level 229 */ 230 public int getZoomLevel() { 231 return mZoomLevel; 232 } 233 234 /** 235 * @param zoomLevel The zoom level 236 */ 237 public void setZoomLevel(int zoomLevel) { 238 mZoomLevel = zoomLevel; 239 } 240 241 /** 242 * @return The aspect ratio 243 */ 244 public int getAspectRatio() { 245 return mAspectRatio; 246 } 247 248 /** 249 * @return The playhead position 250 */ 251 public long getPlayheadPos() { 252 return mPlayheadPosMs; 253 } 254 255 /** 256 * @param playheadPosMs The playhead position 257 */ 258 public void setPlayheadPos(long playheadPosMs) { 259 mPlayheadPosMs = playheadPosMs; 260 } 261 262 /** 263 * @param aspectRatio The aspect ratio 264 */ 265 void setAspectRatio(int aspectRatio) { 266 mAspectRatio = aspectRatio; 267 mClean = false; 268 } 269 270 /** 271 * Add the URI of an exported movie 272 * 273 * @param uri The movie URI 274 */ 275 void addExportedMovieUri(Uri uri) { 276 mExportedMovieUri = uri; 277 mClean = false; 278 } 279 280 /** 281 * @return The exported movie URI 282 */ 283 public Uri getExportedMovieUri() { 284 return mExportedMovieUri; 285 } 286 287 /** 288 * @param theme The theme 289 */ 290 void setTheme(String theme) { 291 mTheme = theme; 292 mClean = false; 293 } 294 295 /** 296 * @return The theme 297 */ 298 public String getTheme() { 299 return mTheme; 300 } 301 302 /** 303 * Set the media items 304 * 305 * @param mediaItems The media items 306 */ 307 void setMediaItems(List<MovieMediaItem> mediaItems) { 308 mMediaItems = mediaItems; 309 mClean = false; 310 } 311 312 /** 313 * Insert a media item after the specified media item id 314 * 315 * @param mediaItem The media item 316 * @param afterMediaItemId Insert after this media item id 317 */ 318 void insertMediaItem(MovieMediaItem mediaItem, String afterMediaItemId) { 319 if (afterMediaItemId == null) { 320 if (mMediaItems.size() > 0) { 321 // Invalidate the transition at the beginning of the timeline 322 final MovieMediaItem firstMediaItem = mMediaItems.get(0); 323 if (firstMediaItem.getBeginTransition() != null) { 324 firstMediaItem.setBeginTransition(null); 325 } 326 } 327 328 mMediaItems.add(0, mediaItem); 329 mClean = false; 330 } else { 331 final int mediaItemCount = mMediaItems.size(); 332 for (int i = 0; i < mediaItemCount; i++) { 333 final MovieMediaItem mi = mMediaItems.get(i); 334 if (mi.getId().equals(afterMediaItemId)) { 335 // Invalidate the transition at the end of this media item 336 mi.setEndTransition(null); 337 // Invalidate the reference in the next media item (if any) 338 if (i < mediaItemCount - 1) { 339 mMediaItems.get(i + 1).setBeginTransition(null); 340 } 341 342 // Insert the new media item 343 mMediaItems.add(i + 1, mediaItem); 344 mClean = false; 345 return; 346 } 347 } 348 349 throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId); 350 } 351 } 352 353 /** 354 * Update the specified media item 355 * 356 * @param newMediaItem The media item can be a new instance of the media 357 * item or an updated version of the same instance. 358 */ 359 void updateMediaItem(MovieMediaItem newMediaItem) { 360 final String newMediaItemId = newMediaItem.getId(); 361 final int count = mMediaItems.size(); 362 for (int i = 0; i < count; i++) { 363 final MovieMediaItem mediaItem = mMediaItems.get(i); 364 if (mediaItem.getId().equals(newMediaItemId)) { 365 mMediaItems.set(i, newMediaItem); 366 mClean = false; 367 // Update the transitions of the previous and next item 368 if (i > 0) { 369 final MovieMediaItem prevMediaItem = mMediaItems.get(i - 1); 370 prevMediaItem.setEndTransition(newMediaItem.getBeginTransition()); 371 } 372 373 if (i < count - 1) { 374 final MovieMediaItem nextMediaItem = mMediaItems.get(i + 1); 375 nextMediaItem.setBeginTransition(newMediaItem.getEndTransition()); 376 } 377 break; 378 } 379 } 380 } 381 382 /** 383 * Remove the specified media item 384 * 385 * @param mediaItemId The media item id 386 * @param transition The transition to be set between at the delete 387 * position 388 */ 389 void removeMediaItem(String mediaItemId, MovieTransition transition) { 390 String prevMediaItemId = null; 391 final int count = mMediaItems.size(); 392 for (int i = 0; i < count; i++) { 393 final MovieMediaItem mediaItem = mMediaItems.get(i); 394 if (mediaItem.getId().equals(mediaItemId)) { 395 mMediaItems.remove(i); 396 mClean = false; 397 if (transition != null) { 398 addTransition(transition, prevMediaItemId); 399 } else { 400 if (i > 0) { 401 final MovieMediaItem prevMediaItem = mMediaItems.get(i - 1); 402 prevMediaItem.setEndTransition(null); 403 } 404 405 if (i < count - 1) { 406 final MovieMediaItem nextMediaItem = mMediaItems.get(i); 407 nextMediaItem.setBeginTransition(null); 408 } 409 } 410 break; 411 } 412 413 prevMediaItemId = mediaItem.getId(); 414 } 415 } 416 417 /** 418 * @return The media items list 419 */ 420 public List<MovieMediaItem> getMediaItems() { 421 return mMediaItems; 422 } 423 424 /** 425 * @return The media item count 426 */ 427 public int getMediaItemCount() { 428 return mMediaItems.size(); 429 } 430 431 /** 432 * @param mediaItemId The media item id 433 * 434 * @return The media item 435 */ 436 public MovieMediaItem getMediaItem(String mediaItemId) { 437 for (MovieMediaItem mediaItem : mMediaItems) { 438 if (mediaItem.getId().equals(mediaItemId)) { 439 return mediaItem; 440 } 441 } 442 443 return null; 444 } 445 446 /** 447 * @return The first media item 448 */ 449 public MovieMediaItem getFirstMediaItem() { 450 if (mMediaItems.size() == 0) { 451 return null; 452 } else { 453 return mMediaItems.get(0); 454 } 455 } 456 457 /** 458 * Check if the specified media item id is the first media item 459 * 460 * @param mediaItemId The media item id 461 * 462 * @return true if this is the first media item 463 */ 464 public boolean isFirstMediaItem(String mediaItemId) { 465 final MovieMediaItem mediaItem = getFirstMediaItem(); 466 if (mediaItem == null) { 467 return false; 468 } else { 469 return mediaItem.getId().equals(mediaItemId); 470 } 471 } 472 473 /** 474 * @return The last media item. {@code null} if no item is in the project. 475 */ 476 public MovieMediaItem getLastMediaItem() { 477 final int count = mMediaItems.size(); 478 if (count == 0) { 479 return null; 480 } else { 481 return mMediaItems.get(count - 1); 482 } 483 } 484 485 /** 486 * Gets the id of the last media item in this project. 487 * 488 * @return Id of the last media item. {@code null} if no item is in this project. 489 */ 490 public String getLastMediaItemId() { 491 MovieMediaItem lastMediaItem = getLastMediaItem(); 492 if (lastMediaItem != null) 493 return lastMediaItem.getId(); 494 return null; 495 } 496 497 /** 498 * Check if the specified media item id is the last media item 499 * 500 * @param mediaItemId The media item id 501 * 502 * @return true if this is the last media item 503 */ 504 public boolean isLastMediaItem(String mediaItemId) { 505 final MovieMediaItem mediaItem = getLastMediaItem(); 506 if (mediaItem == null) { 507 return false; 508 } else { 509 return mediaItem.getId().equals(mediaItemId); 510 } 511 } 512 513 /** 514 * Find the previous media item with the specified id 515 * 516 * @param mediaItemId The media item id 517 * @return The previous media item 518 */ 519 public MovieMediaItem getPreviousMediaItem(String mediaItemId) { 520 MovieMediaItem prevMediaItem = null; 521 for (MovieMediaItem mediaItem : mMediaItems) { 522 if (mediaItemId.equals(mediaItem.getId())) { 523 break; 524 } else { 525 prevMediaItem = mediaItem; 526 } 527 } 528 529 return prevMediaItem; 530 } 531 532 /** 533 * Find the next media item with the specified id 534 * 535 * @param mediaItemId The media item id 536 * @return The next media item 537 */ 538 public MovieMediaItem getNextMediaItem(String mediaItemId) { 539 boolean getNext = false; 540 final int count = mMediaItems.size(); 541 for (int i = 0; i < count; i++) { 542 final MovieMediaItem mi = mMediaItems.get(i); 543 if (getNext) { 544 return mi; 545 } else { 546 if (mediaItemId.equals(mi.getId())) { 547 getNext = true; 548 } 549 } 550 } 551 552 return null; 553 } 554 555 /** 556 * Get the previous media item 557 * 558 * @param positionMs The current position in ms 559 * @return The previous media item 560 */ 561 public MovieMediaItem getPreviousMediaItem(long positionMs) { 562 long startTimeMs = 0; 563 MovieMediaItem prevMediaItem = null; 564 for (MovieMediaItem mediaItem : mMediaItems) { 565 if (positionMs == startTimeMs) { 566 break; 567 } else if (positionMs > startTimeMs 568 && positionMs < startTimeMs + mediaItem.getAppTimelineDuration()) { 569 return mediaItem; 570 } else { 571 prevMediaItem = mediaItem; 572 } 573 574 startTimeMs += mediaItem.getAppTimelineDuration(); 575 if (mediaItem.getEndTransition() != null) { 576 startTimeMs -= mediaItem.getEndTransition().getAppDuration(); 577 } 578 } 579 580 return prevMediaItem; 581 } 582 583 /** 584 * Get the next media item 585 * 586 * @param positionMs The current position in ms 587 * @return The next media item 588 */ 589 public MovieMediaItem getNextMediaItem(long positionMs) { 590 long startTimeMs = 0; 591 final int count = mMediaItems.size(); 592 for (int i = 0; i < count; i++) { 593 final MovieMediaItem mediaItem = mMediaItems.get(i); 594 if (positionMs >= startTimeMs 595 && positionMs < startTimeMs + mediaItem.getAppTimelineDuration() - 596 getEndTransitionDuration(mediaItem)) { 597 if (i < count - 1) { 598 return mMediaItems.get(i + 1); 599 } else { 600 return null; 601 } 602 } else if (positionMs >= startTimeMs 603 && positionMs < startTimeMs + mediaItem.getAppTimelineDuration()) { 604 if (i < count - 2) { 605 return mMediaItems.get(i + 2); 606 } else { 607 return null; 608 } 609 } else { 610 startTimeMs += mediaItem.getAppTimelineDuration(); 611 startTimeMs -= getEndTransitionDuration(mediaItem); 612 } 613 } 614 615 return null; 616 } 617 618 /** 619 * Get the beginning media item of the specified transition 620 * 621 * @param transition The transition 622 * 623 * @return The media item 624 */ 625 public MovieMediaItem getPreviousMediaItem(MovieTransition transition) { 626 final int count = mMediaItems.size(); 627 for (int i = 0; i < count; i++) { 628 final MovieMediaItem mediaItem = mMediaItems.get(i); 629 if (i == 0) { 630 if (mediaItem.getBeginTransition() == transition) { 631 return null; 632 } 633 } 634 635 if (mediaItem.getEndTransition() == transition) { 636 return mediaItem; 637 } 638 } 639 640 return null; 641 } 642 643 /** 644 * Return the end transition duration 645 * 646 * @param mediaItem The media item 647 * @return the end transition duration 648 */ 649 private static long getEndTransitionDuration(MovieMediaItem mediaItem) { 650 if (mediaItem.getEndTransition() != null) { 651 return mediaItem.getEndTransition().getAppDuration(); 652 } else { 653 return 0; 654 } 655 } 656 657 /** 658 * Determine the media item after which a new media item will be inserted. 659 * 660 * @param timeMs The inquiry position 661 * 662 * @return The media item after which the insertion will be performed 663 */ 664 public MovieMediaItem getInsertAfterMediaItem(long timeMs) { 665 long beginMs = 0; 666 long endMs = 0; 667 MovieMediaItem prevMediaItem = null; 668 final int mediaItemsCount = mMediaItems.size(); 669 for (int i = 0; i < mediaItemsCount; i++) { 670 final MovieMediaItem mediaItem = mMediaItems.get(i); 671 672 endMs = beginMs + mediaItem.getAppTimelineDuration(); 673 674 if (mediaItem.getEndTransition() != null) { 675 if (i < mediaItemsCount - 1) { 676 endMs -= mediaItem.getEndTransition().getAppDuration(); 677 } 678 } 679 680 if (timeMs >= beginMs && timeMs <= endMs) { 681 if (timeMs - beginMs < endMs - timeMs) { // Closer to the beginning 682 return prevMediaItem; 683 } else { // Closer to the end 684 return mediaItem; // Insert after this item 685 } 686 } 687 688 beginMs = endMs; 689 prevMediaItem = mediaItem; 690 } 691 692 return null; 693 } 694 695 /** 696 * @return true if media items have different aspect ratios 697 */ 698 public boolean hasMultipleAspectRatios() { 699 int aspectRatio = MediaProperties.ASPECT_RATIO_UNDEFINED; 700 for (MovieMediaItem mediaItem : mMediaItems) { 701 if (aspectRatio == MediaProperties.ASPECT_RATIO_UNDEFINED) { 702 aspectRatio = mediaItem.getAspectRatio(); 703 } else if (mediaItem.getAspectRatio() != aspectRatio) { 704 return true; 705 } 706 } 707 708 return false; 709 } 710 711 /** 712 * @return The list of unique aspect ratios 713 */ 714 public ArrayList<Integer> getUniqueAspectRatiosList() { 715 final ArrayList<Integer> aspectRatiosList = new ArrayList<Integer>(); 716 for (MovieMediaItem mediaItem : mMediaItems) { 717 int aspectRatio = mediaItem.getAspectRatio(); 718 if (!aspectRatiosList.contains(aspectRatio)) { 719 aspectRatiosList.add(aspectRatio); 720 } 721 } 722 723 return aspectRatiosList; 724 } 725 726 /** 727 * Add a new transition 728 * 729 * @param transition The transition 730 * @param afterMediaItemId Add the transition after this media item 731 */ 732 void addTransition(MovieTransition transition, String afterMediaItemId) { 733 final int count = mMediaItems.size(); 734 if (afterMediaItemId != null) { 735 MovieMediaItem afterMediaItem = null; 736 int afterMediaItemIndex = -1; 737 for (int i = 0; i < count; i++) { 738 final MovieMediaItem mediaItem = mMediaItems.get(i); 739 if (mediaItem.getId().equals(afterMediaItemId)) { 740 afterMediaItem = mediaItem; 741 afterMediaItemIndex = i; 742 break; 743 } 744 } 745 746 // Link the transition to the next and previous media items 747 if (afterMediaItem == null) { 748 throw new IllegalArgumentException("Media item not found: " + afterMediaItemId); 749 } 750 751 afterMediaItem.setEndTransition(transition); 752 753 if (afterMediaItemIndex < count - 1) { 754 final MovieMediaItem beforeMediaItem = mMediaItems.get(afterMediaItemIndex + 1); 755 beforeMediaItem.setBeginTransition(transition); 756 } 757 } else { 758 if (count == 0) { 759 throw new IllegalArgumentException("Media item not found at the beginning"); 760 } 761 762 final MovieMediaItem beforeMediaItem = mMediaItems.get(0); 763 beforeMediaItem.setBeginTransition(transition); 764 } 765 766 mClean = false; 767 } 768 769 /** 770 * Remove the specified transition 771 * 772 * @param transitionId The transition id 773 */ 774 void removeTransition(String transitionId) { 775 final int count = mMediaItems.size(); 776 for (int i = 0; i < count; i++) { 777 final MovieMediaItem mediaItem = mMediaItems.get(i); 778 final MovieTransition beginTransition = mediaItem.getBeginTransition(); 779 if (beginTransition != null && beginTransition.getId().equals(transitionId)) { 780 mediaItem.setBeginTransition(null); 781 break; 782 } 783 784 final MovieTransition endTransition = mediaItem.getEndTransition(); 785 if (endTransition != null && endTransition.getId().equals(transitionId)) { 786 mediaItem.setEndTransition(null); 787 } 788 } 789 790 mClean = false; 791 } 792 793 /** 794 * Find the transition with the specified id 795 * 796 * @param transitionId The transition id 797 * @return The transition 798 */ 799 public MovieTransition getTransition(String transitionId) { 800 final MovieMediaItem firstMediaItem = getFirstMediaItem(); 801 if (firstMediaItem == null) { 802 return null; 803 } 804 805 final MovieTransition beginTransition = firstMediaItem.getBeginTransition(); 806 if (beginTransition != null && beginTransition.getId().equals(transitionId)) { 807 return beginTransition; 808 } 809 810 for (MovieMediaItem mediaItem : mMediaItems) { 811 final MovieTransition endTransition = mediaItem.getEndTransition(); 812 if (endTransition != null && endTransition.getId().equals(transitionId)) { 813 return endTransition; 814 } 815 } 816 817 return null; 818 } 819 820 /** 821 * Add the overlay 822 * 823 * @param mediaItemId The media item id 824 * @param overlay The overlay 825 */ 826 void addOverlay(String mediaItemId, MovieOverlay overlay) { 827 final MovieMediaItem mediaItem = getMediaItem(mediaItemId); 828 829 // Remove an existing overlay (if any) 830 final MovieOverlay oldOverlay = mediaItem.getOverlay(); 831 if (oldOverlay != null) { 832 mediaItem.removeOverlay(oldOverlay.getId()); 833 } 834 835 mediaItem.addOverlay(overlay); 836 mClean = false; 837 } 838 839 /** 840 * Remove the specified overlay 841 * 842 * @param mediaItemId The media item id 843 * @param overlayId The overlay id 844 */ 845 void removeOverlay(String mediaItemId, String overlayId) { 846 final MovieMediaItem mediaItem = getMediaItem(mediaItemId); 847 mediaItem.removeOverlay(overlayId); 848 mClean = false; 849 } 850 851 /** 852 * Get the specified overlay 853 * 854 * @param mediaItemId The media item id 855 * @param overlayId The overlay id 856 * @return The movie overlay 857 */ 858 public MovieOverlay getOverlay(String mediaItemId, String overlayId) { 859 final MovieMediaItem mediaItem = getMediaItem(mediaItemId); 860 return mediaItem.getOverlay(); 861 } 862 863 /** 864 * Add the effect 865 * 866 * @param mediaItemId The media item id 867 * @param effect The effect 868 */ 869 void addEffect(String mediaItemId, MovieEffect effect) { 870 final MovieMediaItem mediaItem = getMediaItem(mediaItemId); 871 // Remove an existing effect 872 final MovieEffect oldEffect = mediaItem.getEffect(); 873 if (oldEffect != null) { 874 mediaItem.removeEffect(oldEffect.getId()); 875 } 876 877 mediaItem.addEffect(effect); 878 mClean = false; 879 } 880 881 /** 882 * Remove the specified effect 883 * 884 * @param mediaItemId The media item id 885 * @param effectId The effect id 886 */ 887 void removeEffect(String mediaItemId, String effectId) { 888 final MovieMediaItem mediaItem = getMediaItem(mediaItemId); 889 mediaItem.removeEffect(effectId); 890 mClean = false; 891 } 892 893 /** 894 * Get the specified effect 895 * 896 * @param mediaItemId The media item id 897 * @param effectId The effect id 898 * @return The movie effect 899 */ 900 public MovieEffect getEffect(String mediaItemId, String effectId) { 901 final MovieMediaItem mediaItem = getMediaItem(mediaItemId); 902 return mediaItem.getEffect(); 903 } 904 905 /** 906 * Set the audio tracks 907 * 908 * @param audioTracks The audio tracks 909 */ 910 void setAudioTracks(List<MovieAudioTrack> audioTracks) { 911 mAudioTracks = audioTracks; 912 mClean = false; 913 } 914 915 /** 916 * Add an audio track 917 * 918 * @param audioTrack The audio track 919 */ 920 void addAudioTrack(MovieAudioTrack audioTrack) { 921 mAudioTracks.add(audioTrack); 922 mClean = false; 923 } 924 925 /** 926 * Remove the specified audio track 927 * 928 * @param audioTrackId The audio track id 929 */ 930 void removeAudioTrack(String audioTrackId) { 931 final int count = mAudioTracks.size(); 932 for (int i = 0; i < count; i++) { 933 final MovieAudioTrack audioTrack = mAudioTracks.get(i); 934 if (audioTrack.getId().equals(audioTrackId)) { 935 mAudioTracks.remove(i); 936 mClean = false; 937 break; 938 } 939 } 940 } 941 942 /** 943 * @return The audio tracks 944 */ 945 public List<MovieAudioTrack> getAudioTracks() { 946 return mAudioTracks; 947 } 948 949 /** 950 * @param audioTrackId The audio track id 951 * @return The audio track 952 */ 953 public MovieAudioTrack getAudioTrack(String audioTrackId) { 954 for (MovieAudioTrack audioTrack : mAudioTracks) { 955 if (audioTrack.getId().equals(audioTrackId)) { 956 return audioTrack; 957 } 958 } 959 960 return null; 961 } 962 963 /** 964 * Compute the begin time for this media item 965 * 966 * @param mediaItemId The media item id for which we compute the begin time 967 * 968 * @return The begin time for this media item 969 */ 970 public long getMediaItemBeginTime(String mediaItemId) { 971 long beginMs = 0; 972 final int mediaItemsCount = mMediaItems.size(); 973 for (int i = 0; i < mediaItemsCount; i++) { 974 final MovieMediaItem mi = mMediaItems.get(i); 975 if (mi.getId().equals(mediaItemId)) { 976 break; 977 } 978 979 beginMs += mi.getAppTimelineDuration(); 980 981 if (mi.getEndTransition() != null) { 982 if (i < mediaItemsCount - 1) { 983 beginMs -= mi.getEndTransition().getAppDuration(); 984 } 985 } 986 } 987 988 return beginMs; 989 } 990 991 /** 992 * @return The total duration 993 */ 994 public long computeDuration() { 995 long totalDurationMs = 0; 996 final int mediaItemsCount = mMediaItems.size(); 997 for (int i = 0; i < mediaItemsCount; i++) { 998 final MovieMediaItem mediaItem = mMediaItems.get(i); 999 totalDurationMs += mediaItem.getAppTimelineDuration(); 1000 1001 if (mediaItem.getEndTransition() != null) { 1002 if (i < mediaItemsCount - 1) { 1003 totalDurationMs -= mediaItem.getEndTransition().getAppDuration(); 1004 } 1005 } 1006 } 1007 1008 return totalDurationMs; 1009 } 1010 1011 /** 1012 * Render a frame according to the preview aspect ratio and activating all 1013 * storyboard items relative to the specified time. 1014 * 1015 * @param surfaceHolder SurfaceHolder used by the application 1016 * @param timeMs time corresponding to the frame to display 1017 * @param overlayData The overlay data 1018 * 1019 * @return The accurate time stamp of the frame that is rendered. 1020 * @throws IllegalStateException if a preview or an export is already in 1021 * progress 1022 * @throws IllegalArgumentException if time is negative or beyond the 1023 * preview duration 1024 */ 1025 public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs, 1026 VideoEditor.OverlayData overlayData) { 1027 if (mVideoEditor != null) { 1028 return mVideoEditor.renderPreviewFrame(surfaceHolder, timeMs, overlayData); 1029 } else { 1030 return 0; 1031 } 1032 } 1033 1034 /** 1035 * Render a frame of a media item. 1036 * 1037 * @param surfaceHolder SurfaceHolder used by the application 1038 * @param mediaItemId The media item id 1039 * @param timeMs time corresponding to the frame to display 1040 * 1041 * @return The accurate time stamp of the frame that is rendered . 1042 * @throws IllegalStateException if a preview or an export is already in 1043 * progress 1044 * @throws IllegalArgumentException if time is negative or beyond the 1045 * preview duration 1046 */ 1047 public long renderMediaItemFrame(SurfaceHolder surfaceHolder, String mediaItemId, 1048 long timeMs) { 1049 if (mVideoEditor != null) { 1050 final MediaVideoItem mediaItem = 1051 (MediaVideoItem)mVideoEditor.getMediaItem(mediaItemId); 1052 if (mediaItem != null) { 1053 return mediaItem.renderFrame(surfaceHolder, timeMs); 1054 } else { 1055 return -1; 1056 } 1057 } else { 1058 return 0; 1059 } 1060 } 1061 1062 /** 1063 * Start the preview of all the storyboard items applied on all MediaItems 1064 * This method does not block (does not wait for the preview to complete). 1065 * The PreviewProgressListener allows to track the progress at the time 1066 * interval determined by the callbackAfterFrameCount parameter. The 1067 * SurfaceHolder has to be created and ready for use before calling this 1068 * method. The method is a no-op if there are no MediaItems in the 1069 * storyboard. 1070 * 1071 * @param surfaceHolder SurfaceHolder where the preview is rendered. 1072 * @param fromMs The time (relative to the timeline) at which the preview 1073 * will start 1074 * @param toMs The time (relative to the timeline) at which the preview will 1075 * stop. Use -1 to play to the end of the timeline 1076 * @param loop true if the preview should be looped once it reaches the end 1077 * @param callbackAfterFrameCount The listener interface should be invoked 1078 * after the number of frames specified by this parameter. 1079 * @param listener The listener which will be notified of the preview 1080 * progress 1081 * 1082 * @throws IllegalArgumentException if fromMs is beyond the preview duration 1083 * @throws IllegalStateException if a preview or an export is already in 1084 * progress 1085 */ 1086 public void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop, 1087 int callbackAfterFrameCount, PreviewProgressListener listener) { 1088 if (mVideoEditor != null) { 1089 mVideoEditor.startPreview(surfaceHolder, fromMs, toMs, loop, callbackAfterFrameCount, 1090 listener); 1091 } 1092 } 1093 1094 /** 1095 * Stop the current preview. This method blocks until ongoing preview is 1096 * stopped. Ignored if there is no preview running. 1097 * 1098 * @return The accurate current time when stop is effective expressed in 1099 * milliseconds 1100 */ 1101 public long stopPreview() { 1102 if (mVideoEditor != null) { 1103 return mVideoEditor.stopPreview(); 1104 } else { 1105 return 0; 1106 } 1107 } 1108 1109 /** 1110 * Clear the surface 1111 * 1112 * @param surfaceHolder SurfaceHolder where the preview is rendered. 1113 */ 1114 public void clearSurface(SurfaceHolder surfaceHolder) { 1115 if (mVideoEditor != null) { 1116 mVideoEditor.clearSurface(surfaceHolder); 1117 } 1118 } 1119 1120 /** 1121 * Release the project 1122 */ 1123 public void release() { 1124 } 1125 1126 /** 1127 * Add a new download to the project 1128 * 1129 * @param mediaUri The media URI 1130 * @param mimeType The mime type 1131 * @param filename The local filename 1132 */ 1133 public void addDownload(String mediaUri, String mimeType, String filename) { 1134 mDownloads.add(new Download(mediaUri, mimeType, filename, System.currentTimeMillis())); 1135 mClean = false; 1136 } 1137 1138 /** 1139 * Remove a download 1140 * 1141 * @param mediaUri The media URI 1142 */ 1143 public void removeDownload(String mediaUri) { 1144 final int count = mDownloads.size(); 1145 for (int i = 0; i < count; i++) { 1146 final Download download = mDownloads.get(i); 1147 final String uri = download.getMediaUri(); 1148 if (mediaUri.equals(uri)) { 1149 // Delete the file associated with the download 1150 final String filename = download.getFilename(); 1151 new File(filename).delete(); 1152 1153 // Remove the download from the list 1154 mDownloads.remove(i); 1155 mClean = false; 1156 break; 1157 } 1158 } 1159 } 1160 1161 /** 1162 * @return The list of downloads 1163 */ 1164 public List<Download> getDownloads() { 1165 return mDownloads; 1166 } 1167 1168 /** 1169 * Load metadata from file 1170 * 1171 * @param videoEditor The video editor 1172 * @param projectPath The project path 1173 * 1174 * @return A new instance of the VideoEditorProject 1175 */ 1176 public static VideoEditorProject fromXml(VideoEditor videoEditor, String projectPath) 1177 throws XmlPullParserException, FileNotFoundException, IOException { 1178 final File file = new File(projectPath, PROJECT_METADATA_FILENAME); 1179 final FileInputStream fis = new FileInputStream(file); 1180 final List<Download> downloads = new ArrayList<Download>(); 1181 try { 1182 // Load the metadata 1183 final XmlPullParser parser = Xml.newPullParser(); 1184 parser.setInput(fis, "UTF-8"); 1185 int eventType = parser.getEventType(); 1186 1187 String projectName = null; 1188 String themeId = null; 1189 Uri exportedMovieUri = null; 1190 long lastSaved = 0; 1191 long playheadPosMs = 0; 1192 long durationMs = 0; 1193 int zoomLevel = DEFAULT_ZOOM_LEVEL; 1194 while (eventType != XmlPullParser.END_DOCUMENT) { 1195 String name = null; 1196 switch (eventType) { 1197 case XmlPullParser.START_TAG: { 1198 name = parser.getName(); 1199 if (name.equalsIgnoreCase(TAG_PROJECT)) { 1200 projectName = parser.getAttributeValue("", ATTR_NAME); 1201 themeId = parser.getAttributeValue("", ATTR_THEME); 1202 lastSaved = Long.parseLong(parser.getAttributeValue("", ATTR_SAVED)); 1203 playheadPosMs = Long.parseLong(parser.getAttributeValue("", 1204 ATTR_PLAYHEAD_POSITION)); 1205 durationMs = Long.parseLong(parser.getAttributeValue("", 1206 ATTR_DURATION)); 1207 zoomLevel = Integer.parseInt(parser.getAttributeValue("", 1208 ATTR_ZOOM_LEVEL)); 1209 } else if (name.equalsIgnoreCase(TAG_MOVIE)) { 1210 exportedMovieUri = Uri.parse(parser.getAttributeValue("", ATTR_URI)); 1211 } else if (name.equalsIgnoreCase(TAG_DOWNLOAD)) { 1212 downloads.add(new Download(parser.getAttributeValue("", ATTR_URI), 1213 parser.getAttributeValue("", ATTR_MIME), 1214 parser.getAttributeValue("", ATTR_FILENAME), 1215 Long.parseLong(parser.getAttributeValue("", ATTR_TIME)))); 1216 } 1217 1218 break; 1219 } 1220 1221 default: { 1222 break; 1223 } 1224 } 1225 eventType = parser.next(); 1226 } 1227 1228 return new VideoEditorProject(videoEditor, projectPath, projectName, lastSaved, 1229 playheadPosMs, durationMs, zoomLevel, exportedMovieUri, themeId, downloads); 1230 } finally { 1231 if (fis != null) { 1232 fis.close(); 1233 } 1234 } 1235 } 1236 1237 /** 1238 * Save the content to XML 1239 */ 1240 public void saveToXml() throws IOException { 1241 // Save the project metadata 1242 final XmlSerializer serializer = Xml.newSerializer(); 1243 final StringWriter writer = new StringWriter(); 1244 serializer.setOutput(writer); 1245 serializer.startDocument("UTF-8", true); 1246 serializer.startTag("", TAG_PROJECT); 1247 if (mProjectName != null) { 1248 serializer.attribute("", ATTR_NAME, mProjectName); 1249 } 1250 if (mTheme != null) { 1251 serializer.attribute("", ATTR_THEME, mTheme); 1252 } 1253 1254 serializer.attribute("", ATTR_PLAYHEAD_POSITION, Long.toString(mPlayheadPosMs)); 1255 serializer.attribute("", ATTR_DURATION, Long.toString(computeDuration())); 1256 serializer.attribute("", ATTR_ZOOM_LEVEL, Integer.toString(mZoomLevel)); 1257 1258 mLastSaved = System.currentTimeMillis(); 1259 serializer.attribute("", ATTR_SAVED, Long.toString(mLastSaved)); 1260 if (mExportedMovieUri != null) { 1261 serializer.startTag("", TAG_MOVIE); 1262 serializer.attribute("", ATTR_URI, mExportedMovieUri.toString()); 1263 serializer.endTag("", TAG_MOVIE); 1264 } 1265 1266 for (Download download : mDownloads) { 1267 serializer.startTag("", TAG_DOWNLOAD); 1268 serializer.attribute("", ATTR_URI, download.getMediaUri()); 1269 serializer.attribute("", ATTR_MIME, download.getMimeType()); 1270 serializer.attribute("", ATTR_FILENAME, download.getFilename()); 1271 serializer.attribute("", ATTR_TIME, Long.toString(download.getTime())); 1272 serializer.endTag("", TAG_DOWNLOAD); 1273 } 1274 serializer.endTag("", TAG_PROJECT); 1275 serializer.endDocument(); 1276 1277 // Save the metadata XML file 1278 final FileOutputStream out = new FileOutputStream(new File(mVideoEditor.getPath(), 1279 PROJECT_METADATA_FILENAME)); 1280 out.write(writer.toString().getBytes("UTF-8")); 1281 out.flush(); 1282 out.close(); 1283 } 1284 } 1285