Add new APIs for media editing framework 30/273530/35 accepted/tizen/unified/20220419.142239 submit/tizen/20220419.052558
authorHaesu Gwon <haesu.gwon@samsung.com>
Thu, 7 Apr 2022 05:20:47 +0000 (14:20 +0900)
committerHaesu Gwon <haesu.gwon@samsung.com>
Mon, 18 Apr 2022 04:05:00 +0000 (13:05 +0900)
It supports for
- selecting the period of media clip and placing it to the output timeline
- adding audio, video effect
- saving editorial cut information and loading it to edit continuously

Change-Id: I5dd4b05dc7fd4023c95f2edc0717d03206b90d61

31 files changed:
AUTHORS [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
LICENSE.APLv2 [new file with mode: 0644]
NOTICE [new file with mode: 0644]
capi-media-editor-tool.manifest [new file with mode: 0644]
capi-media-editor.manifest [new file with mode: 0644]
capi-media-editor.pc.in [new file with mode: 0644]
doc/media_editor_doc.h [new file with mode: 0644]
gtest/CMakeLists.txt [new file with mode: 0644]
gtest/testbase.hpp [new file with mode: 0644]
gtest/ut_clip.cpp [new file with mode: 0644]
gtest/ut_composition.cpp [new file with mode: 0644]
gtest/ut_effect.cpp [new file with mode: 0644]
gtest/ut_layer.cpp [new file with mode: 0644]
gtest/ut_main.cpp [new file with mode: 0644]
gtest/ut_project.cpp [new file with mode: 0644]
include/media_editor.h [new file with mode: 0644]
include/media_editor_internal.h [new file with mode: 0644]
include/media_editor_private.h [new file with mode: 0644]
packaging/capi-media-editor.spec [new file with mode: 0644]
src/media_editor.c [new file with mode: 0644]
src/media_editor_clip.c [new file with mode: 0644]
src/media_editor_display.c [new file with mode: 0644]
src/media_editor_effect.c [new file with mode: 0644]
src/media_editor_ini.c [new file with mode: 0644]
src/media_editor_layer.c [new file with mode: 0644]
src/media_editor_private.c [new file with mode: 0644]
src/media_editor_project.c [new file with mode: 0644]
src/media_editor_resource.c [new file with mode: 0644]
test/CMakeLists.txt [new file with mode: 0644]
test/media_editor_test.c [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..d651608
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Haesu Gwon <haesu.gwon@samsung.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..89e8b59
--- /dev/null
@@ -0,0 +1,107 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12)
+SET(fw_name "capi-media-editor")
+
+PROJECT(${fw_name})
+
+SET(CMAKE_INSTALL_PREFIX /usr)
+SET(PREFIX ${CMAKE_INSTALL_PREFIX})
+
+SET(INC_DIR include)
+INCLUDE_DIRECTORIES(${INC_DIR})
+
+SET(dependents "glib-2.0 gio-2.0 dlog iniparser capi-base-common mm-common mm-display-interface capi-system-info\
+                gstreamer-1.0 gstreamer-plugins-base-1.0 gstreamer-pbutils-1.0 gst-editing-services-1.0")
+SET(pc_dependents "capi-base-common")
+
+IF(NOT TIZEN_PROFILE_TV)
+    SET(dependents "${dependents} mm-resource-manager")
+ELSE()
+    ADD_DEFINITIONS("-DTIZEN_TV")
+ENDIF()
+
+INCLUDE(FindPkgConfig)
+pkg_check_modules(${fw_name} REQUIRED ${dependents})
+FOREACH(flag ${${fw_name}_CFLAGS})
+    SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -fPIC -Wall -Werror -Wextra -Wno-array-bounds -Wno-empty-body -Wno-ignored-qualifiers -Wno-unused-parameter -Wshadow -Wwrite-strings -Wswitch-default -Wno-unused-but-set-parameter -Wno-unused-but-set-variable")
+SET(CMAKE_C_FLAGS_DEBUG "-O0 -g")
+
+ADD_DEFINITIONS("-DPREFIX=\"${CMAKE_INSTALL_PREFIX}\"")
+ADD_DEFINITIONS("-DTIZEN_DEBUG -DPRINT_CLIP_DEBUG_INFO -DPRINT_LAYER_DEBUG_INFO")
+
+SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed -Wl,--rpath=${LIB_INSTALL_DIR}")
+
+AUX_SOURCE_DIRECTORY (src MAIN_SRC)
+
+LIST (APPEND SOURCES
+     ${MAIN_SRC}
+)
+IF(TIZEN_PROFILE_TV)
+    LIST (REMOVE_ITEM SOURCES src/media_editor_resource.c)
+ENDIF()
+
+ADD_LIBRARY(${fw_name} SHARED ${SOURCES})
+
+TARGET_LINK_LIBRARIES(${fw_name} ${${fw_name}_LDFLAGS})
+
+SET_TARGET_PROPERTIES(${fw_name}
+     PROPERTIES
+     VERSION ${FULLVER}
+     SOVERSION ${MAJORVER}
+     CLEAN_DIRECT_OUTPUT 1
+)
+
+INSTALL(TARGETS ${fw_name} DESTINATION ${LIB_INSTALL_DIR})
+INSTALL(
+        DIRECTORY ${INC_DIR}/ DESTINATION include/media
+        FILES_MATCHING
+        PATTERN "*_private.h" EXCLUDE
+        PATTERN "${INC_DIR}/*.h"
+)
+
+SET(PC_NAME ${fw_name})
+SET(PC_REQUIRED ${pc_dependents})
+SET(PC_LDFLAGS -l${fw_name})
+
+CONFIGURE_FILE(
+    ${fw_name}.pc.in
+    ${CMAKE_CURRENT_SOURCE_DIR}/${fw_name}.pc
+    @ONLY
+)
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${fw_name}.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig)
+
+ADD_SUBDIRECTORY(test)
+ADD_SUBDIRECTORY(gtest)
+
+IF(UNIX)
+
+ADD_CUSTOM_TARGET (distclean @echo cleaning for source distribution)
+ADD_CUSTOM_COMMAND(
+        DEPENDS clean
+        COMMENT "distribution clean"
+        COMMAND find
+        ARGS    .
+        -not -name config.cmake -and \(
+        -name tester.c -or
+        -name Testing -or
+        -name CMakeFiles -or
+        -name cmake.depends -or
+        -name cmake.check_depends -or
+        -name CMakeCache.txt -or
+        -name cmake.check_cache -or
+        -name *.cmake -or
+        -name Makefile -or
+        -name core -or
+        -name core.* -or
+        -name gmon.out -or
+        -name install_manifest.txt -or
+        -name *.pc -or
+        -name *~ \)
+        | grep -v TC | xargs rm -rf
+        TARGET  distclean
+        VERBATIM
+)
+
+ENDIF(UNIX)
diff --git a/LICENSE.APLv2 b/LICENSE.APLv2
new file mode 100644 (file)
index 0000000..696ca6f
--- /dev/null
@@ -0,0 +1,206 @@
+Copyright (c) 2000 - 2022 Samsung Electronics Co., Ltd. All rights reserved.
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
diff --git a/NOTICE b/NOTICE
new file mode 100644 (file)
index 0000000..0e0f016
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,3 @@
+Copyright (c) Samsung Electronics Co., Ltd. All rights reserved.
+Except as noted, this software is licensed under Apache License, Version 2.
+Please, see the LICENSE.APLv2 file for Apache License terms and conditions.
diff --git a/capi-media-editor-tool.manifest b/capi-media-editor-tool.manifest
new file mode 100644 (file)
index 0000000..86dbb26
--- /dev/null
@@ -0,0 +1,5 @@
+<manifest>
+    <request>
+        <domain name="_" />
+    </request>
+</manifest>
diff --git a/capi-media-editor.manifest b/capi-media-editor.manifest
new file mode 100644 (file)
index 0000000..86dbb26
--- /dev/null
@@ -0,0 +1,5 @@
+<manifest>
+    <request>
+        <domain name="_" />
+    </request>
+</manifest>
diff --git a/capi-media-editor.pc.in b/capi-media-editor.pc.in
new file mode 100644 (file)
index 0000000..1508489
--- /dev/null
@@ -0,0 +1,15 @@
+
+# Package Information for pkg-config
+
+prefix=@PREFIX@
+exec_prefix=/usr
+libdir=@LIB_INSTALL_DIR@
+includedir=/usr/include/media
+
+Name: @PC_NAME@
+Description: @PACKAGE_DESCRIPTION@
+Version: @VERSION@
+Requires: @PC_REQUIRED@
+Libs: -L${libdir} @PC_LDFLAGS@
+Cflags: -I${includedir}
+
diff --git a/doc/media_editor_doc.h b/doc/media_editor_doc.h
new file mode 100644 (file)
index 0000000..c92dbdd
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef __TIZEN_MEDIA_EDITOR_DOC_H__
+#define __TIZEN_MEDIA_EDITOR_DOC_H__
+
+/**
+ * @file media_editor_doc.h
+ * @brief This file contains high level documentation for the media editor API.
+ */
+
+/**
+ * @ingroup CAPI_MEDIA_FRAMEWORK
+ * @defgroup CAPI_MEDIA_EDITOR_MODULE Mediaeditor
+ * @brief The @ref CAPI_MEDIA_EDITOR_MODULE API provides functions for editing media clip and creating output media file.
+ *
+ * @section CAPI_MEDIA_EDITOR_MODULE_HEADER Required Header
+ *   \#include <media_editor.h>
+ *
+ * @section CAPI_MEDIA_EDITOR_MODULE_OVERVIEW Overview
+ * The Mediaeditor API allows application developers to edit media clip.
+ *
+ * The Mediaeditor API set allows you to:
+ * - add/remove/split/group/ungroup the media clip
+ * - add/remove/move layer
+ * - add/remove audio, video effect to media clip
+ *
+ * @subsection CAPI_MEDIA_EDITOR_LIFE_CYCLE_STATE_TRANSITIONS State Transitions
+ * <div><table class="doxtable" >
+ * <tr>
+ *    <th><b>FUNCTION</b></th>
+ *    <th><b>PRE-STATE</b></th>
+ *    <th><b>POST-STATE</b></th>
+ *    <th><b>SYNC TYPE</b></th>
+ * </tr>
+ * <tr>
+ *    <td> mediaeditor_create() </td>
+ *    <td> NONE </td>
+ *    <td> IDLE </td>
+ *    <td> SYNC </td>
+ * </tr>
+ * <tr>
+ *    <td> mediaeditor_destroy() </td>
+ *    <td> IDLE/RENDERING/PREVIEW </td>
+ *    <td> NONE </td>
+ *    <td> SYNC </td>
+ * </tr>
+ * <tr>
+ *    <td> mediaeditor_start_preview() </td>
+ *    <td> IDLE </td>
+ *    <td> PREVIEW </td>
+ *    <td> ASYNC </td>
+ * </tr>
+ * <tr>
+ *    <td> mediaeditor_start_render() </td>
+ *    <td> IDLE </td>
+ *    <td> RENDERING </td>
+ *    <td> ASYNC </td>
+ * </tr>
+ * </table></div>
+ *
+ * (*) The transition from the RENDERING state to the IDLE state will be processed after finishing the rendering. \n
+ *
+ * @subsection CAPI_MEDIA_EDITOR_LIFE_CYCLE_CALLBACK_OPERATIONS Callback(Event) Operations
+ * The callback mechanism is used to notify the application about significant mediaeditor events.
+ * <div><table class="doxtable" >
+ *     <tr>
+ *        <th><b>REGISTER</b></th>
+ *        <th><b>UNREGISTER</b></th>
+ *        <th><b>CALLBACK</b></th>
+ *        <th><b>DESCRIPTION</b></th>
+ *    </tr>
+ *    <tr>
+ *        <td>mediaeditor_set_state_changed_cb()</td>
+ *        <td>mediaeditor_unset_state_changed_cb()</td>
+ *        <td>mediaeditor_state_changed_cb()</td>
+ *        <td>This callback is used to notify that the mediaeditor state has changed</td>
+ *    </tr>
+ *    <tr>
+ *        <td>mediaeditor_set_error_cb()</td>
+ *        <td>mediaeditor_unset_error_cb()</td>
+ *        <td>mediaeditor_error_cb()</td>
+ *        <td>This callback is used to notify that an error has occurred</td>
+ *     </tr>
+ *</table></div>
+ *
+ * @section CAPI_MEDIA_EDITOR_MODULE_FEATURE Related Feature
+ * This API is related with the following feature:\n
+ *  - %http://tizen.org/feature/display\n
+ *
+ * It is recommended to design feature related codes in your application for reliability.\n
+ * You can check if a device supports the related features for this API by using @ref CAPI_SYSTEM_SYSTEM_INFO_MODULE, thereby controlling the procedure of your application.\n
+ * To ensure your application is only running on the device with specific features, please define the features in your manifest file using the manifest editor in the SDK.\n
+ * More details on featuring your application can be found from <a href="https://docs.tizen.org/application/tizen-studio/native-tools/manifest-text-editor#feature-element"><b>Feature Element</b>.</a>
+ *
+ */
+
+/**
+ * @ingroup CAPI_MEDIA_EDITOR_MODULE
+ * @defgroup CAPI_MEDIA_EDITOR_LAYER_MODULE Layer
+ * @brief The @ref CAPI_MEDIA_EDITOR_LAYER_MODULE API provides functions to manage layers.
+ *
+ * @section CAPI_MEDIA_EDITOR_LAYER_MODULE_HEADER Required Header
+ *   \#include <media_editor.h>
+ *
+ * @section CAPI_MEDIA_EDITOR_LAYER_MODULE_OVERVIEW Overview
+ * The Mediaeditor Layer API allows you to:
+ * - add/remove layer to internal timeline
+ * - activate/deactivate layer which is already added
+ * - move layer
+ *
+*/
+
+/**
+ * @ingroup CAPI_MEDIA_EDITOR_MODULE
+ * @defgroup CAPI_MEDIA_EDITOR_CLIP_MODULE Clip
+ * @brief The @ref CAPI_MEDIA_EDITOR_CLIP_MODULE API provides functions to manage clips.
+ *
+ * @section CAPI_MEDIA_EDITOR_CLIP_MODULE_HEADER Required Header
+ *   \#include <media_editor.h>
+ *
+ * @section CAPI_MEDIA_EDITOR_CLIP_MODULE_OVERVIEW Overview
+ * The Mediaeditor Clip API allows you to:
+ * - add/remove media clips to internal timeline
+ * - split media clips with user given time position
+ * - group/ungroup media clips
+ * - get/set clip start, duration, resolution, volume
+ *
+*/
+
+/**
+ * @ingroup CAPI_MEDIA_EDITOR_MODULE
+ * @defgroup CAPI_MEDIA_EDITOR_EFFECT_MODULE Effect
+ * @brief The @ref CAPI_MEDIA_EDITOR_EFFECT_MODULE API provides functions to manage layers.
+ *
+ * @section CAPI_MEDIA_EDITOR_EFFECT_MODULE_HEADER Required Header
+ *   \#include <media_editor.h>
+ *
+ * @section CAPI_MEDIA_EDITOR_EFFECT_MODULE_OVERVIEW Overview
+ * The Mediaeditor Effect API allows you to:
+ * - add/remove effect
+ * - add transition effect where clips are overlapped
+ *
+*/
+
+/**
+ * @ingroup CAPI_MEDIA_EDITOR_MODULE
+ * @defgroup CAPI_MEDIA_EDITOR_PROJECT_MODULE Project
+ * @brief The @ref CAPI_MEDIA_EDITOR_PROJECT_MODULE API provides functions to manage project.
+ *
+ * @section CAPI_MEDIA_EDITOR_PROJECT_MODULE_HEADER Required Header
+ *   \#include <media_editor.h>
+ *
+ * @section CAPI_MEDIA_EDITOR_PROJECT_MODULE_OVERVIEW Overview
+ * The Mediaeditor Project API allows you to:
+ * - create/save editorial cut information as project file
+ * - load editorial cut information from project file and user can edit clip again
+ *
+*/
+
+#endif /* __TIZEN_MEDIA_EDITOR_DOC_H__ */
diff --git a/gtest/CMakeLists.txt b/gtest/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e9bf56a
--- /dev/null
@@ -0,0 +1,73 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12)
+SET(fw_name "mediaeditor_ut")
+
+SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
+SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
+
+SET(${fw_name}_CXXFLAGS "-Wall -pthread -fPIE -Wl,-z,relro -fstack-protector -fno-delete-null-pointer-checks -DEFL_BETA_API_SUPPORT")
+
+SET(${fw_name}_LDFLAGS)
+
+SET(ADD_LIBS
+  "capi-media-editor"
+)
+
+SET(dependents
+               "glib-2.0 gstreamer-base-1.0 gstreamer-app-1.0 dlog"
+               "boost"
+               "appcore-efl elementary ecore evas ecore-wl2"
+)
+
+find_package(GTest REQUIRED)
+set(GTEST_LIBRARY gtest)
+
+INCLUDE(FindPkgConfig)
+
+IF(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l)
+pkg_check_modules(${fw_name} REQUIRED ${dependents})
+ELSE(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l)
+pkg_check_modules(${fw_name} REQUIRED ${dependents})
+ENDIF(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l)
+
+FOREACH(flag ${${fw_name}_CFLAGS})
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+SET(EXTRA_CXXFLAGS "${EXTRA_CXXFLAGS} ${flag}")
+ENDFOREACH(flag)
+FOREACH(flag ${${fw_name}_CXXFLAGS})
+SET(EXTRA_CXXFLAGS "${EXTRA_CXXFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}")
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_CXXFLAGS}")
+
+INCLUDE_DIRECTORIES(
+  ${PROJECT_SOURCE_DIR}/src
+  ${PROJECT_SOURCE_DIR}/include
+  ${PROJECT_SOURCE_DIR}
+)
+
+SET(UT_SRC
+  ut_clip.cpp
+  ut_composition.cpp
+  ut_effect.cpp
+  ut_layer.cpp
+  ut_main.cpp
+  ut_project.cpp
+)
+
+ADD_EXECUTABLE(${fw_name} ${UT_SRC})
+
+LINK_DIRECTORIES(${LIB_INSTALL_DIR})
+
+TARGET_LINK_LIBRARIES(${fw_name}
+  ${GTEST_LIBRARY}
+  ${CMAKE_THREAD_LIBS_INIT}
+  ${ADD_LIBS}
+  ${${fw_name}_LDFLAGS}
+  "-pie"
+)
+
+INSTALL(
+  TARGETS ${fw_name}
+  DESTINATION bin
+)
diff --git a/gtest/testbase.hpp b/gtest/testbase.hpp
new file mode 100644 (file)
index 0000000..f0cf8a3
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <thread>
+#include <malloc.h>
+#include <dlog.h>
+#include <glib.h>
+
+#include "gtest/gtest.h"
+#include <media_editor.h>
+
+#undef LOG_TAG
+#define LOG_TAG "MEDIAEDITOR_UT"
+
+class MediaeditorTestBase : public ::testing::Test {
+public:
+    MediaeditorTestBase() : _editor(nullptr) {}
+
+    void SetUp() override {
+        LOGD("Enter");
+
+        int ret = mediaeditor_create(&_editor);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+        LOGD("Leave");
+    }
+
+    void TearDown() override {
+        LOGD("Enter");
+
+        if (_editor) {
+            int ret = mediaeditor_destroy(_editor);
+            ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+        }
+
+        LOGD("Leave");
+    }
+
+    mediaeditor_h GetHandle() { return _editor; }
+    const std::string& GetBgmPath() { return _bgmPath; }
+    const std::string& GetMp4Path(int idx) { return _mp4Ppath[idx]; }
+    const std::string& GetProjectPath(int idx) { return _projectPath[idx]; }
+
+private:
+    mediaeditor_h _editor;
+
+    const std::string _basePath = "/tmp/";
+    const std::string _bgmPath = _basePath + "bg.mp3";
+    const std::vector<std::string> _mp4Ppath = {
+        _basePath + "test1.mp4",
+        _basePath + "test2.mp4",
+        _basePath + "test3.mp4",
+        _basePath + "test4.mp4",
+        _basePath + "test5.mp4"
+    };
+    const std::vector<std::string> _projectPath = {
+        _basePath + "test1.xges",
+        _basePath + "test2.xges"
+    };
+};
diff --git a/gtest/ut_clip.cpp b/gtest/ut_clip.cpp
new file mode 100644 (file)
index 0000000..ed78ed9
--- /dev/null
@@ -0,0 +1,382 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "testbase.hpp"
+
+static GMainLoop *mainloop;
+
+class MediaeditorClipTest : public MediaeditorTestBase {
+public:
+    MediaeditorClipTest() {}
+
+    static void RenderCompletedCb(void *user_data)
+    {
+        LOGD("Enter");
+        if (mainloop)
+            g_main_loop_quit(mainloop);
+    }
+
+    static void ErrorCb(mediaeditor_error_e error, mediaeditor_state_e state, void *user_data)
+    {
+        LOGD("Enter. error[%d] state[%d]", error, state);
+        if (mainloop)
+            g_main_loop_quit(mainloop);
+    }
+
+    static void WaitForAsync()
+    {
+        mainloop = g_main_loop_new(NULL, TRUE);
+
+        LOGD("Wait render completed cb");
+        g_main_loop_run(mainloop);
+
+        g_main_loop_unref(mainloop);
+        mainloop = NULL;
+    }
+};
+
+TEST_F(MediaeditorClipTest, DISABLED_clip_create)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
+
+TEST_F(MediaeditorClipTest, DISABLED_clip_delete)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_remove_clip(GetHandle(), clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
+
+TEST_F(MediaeditorClipTest, DISABLED_clip_split)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int new_clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_split_clip(GetHandle(), clip_id, (start + duration) / 2, &new_clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
+
+TEST_F(MediaeditorClipTest, DISABLED_clip_group_ungroup)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int src_ids[5] = { 0 };
+    unsigned int *dst_ids = NULL;
+    unsigned int group_id = 0;
+    unsigned int ungroup_size = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    for (int i = 0; i < 5; i++) {
+        ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, i * 100, &src_ids[i]);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+        LOGD("clip id : %d", src_ids[i]);
+
+        start += duration;
+    }
+
+    for (int i = 0 ; i < 4 ; i++)
+        src_ids[i] += 1;
+
+    ret = mediaeditor_group_clip(GetHandle(), src_ids, 4, &group_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+    LOGD("grouped id : %d", group_id);
+
+    ret = mediaeditor_ungroup_clip(GetHandle(), group_id, &dst_ids, &ungroup_size);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    for (int i = 0 ; i < (int)ungroup_size ; i++) {
+        LOGD("src_ids[%d], ungrouped_ids[%d]", src_ids[i], dst_ids[i]);
+        ASSERT_EQ(src_ids[i], dst_ids[i]);
+    }
+}
+
+TEST_F(MediaeditorClipTest, DISABLED_clip_move)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+    LOGD("clip id : %d", clip_id);
+
+    ret = mediaeditor_move_clip_layer(GetHandle(), clip_id, 0);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
+
+TEST_F(MediaeditorClipTest, DISABLED_clip_set_duration)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+    unsigned int set_duration = 200;
+    unsigned int get_duration = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+    LOGD("original duration : %d", duration);
+
+    ret = mediaeditor_set_clip_duration(GetHandle(), clip_id, set_duration);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+    LOGD("set_duration : %d", set_duration);
+
+    ret = mediaeditor_get_clip_duration(GetHandle(), clip_id, &get_duration);
+    ASSERT_EQ(set_duration, get_duration);
+    LOGD("get_duration : %d", get_duration);
+}
+
+TEST_F(MediaeditorClipTest, DISABLED_clip_set_get_resolution)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+    unsigned int width = 0;
+    unsigned int height = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_get_clip_resolution(GetHandle(), clip_id, &width, &height);
+    LOGD("original resolution : %dx%d", width, height);
+
+    width = 640;
+    height = 360;
+    ret = mediaeditor_set_clip_resolution(GetHandle(), clip_id, width, height);
+    LOGD("set resolution : %dx%d", width, height);
+
+    ret = mediaeditor_get_clip_resolution(GetHandle(), clip_id, &width, &height);
+    LOGD("get resolution : %dx%d", width, height);
+}
+
+TEST_F(MediaeditorClipTest, DISABLED_audio_clip_set_get_resolution)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+    unsigned int width = 0;
+    unsigned int height = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetBgmPath().c_str(), layer_id, start, duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_get_clip_resolution(GetHandle(), clip_id, &width, &height);
+    LOGD("Audio clip doesn't have resolution information. Should return error.");
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_INVALID_OPERATION);
+}
+
+TEST_F(MediaeditorClipTest, DISABLED_volume)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+    double volume = 0.0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_get_clip_volume(GetHandle(), clip_id, &volume);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+    ASSERT_TRUE(volume == 1.0);
+
+    ret = mediaeditor_set_clip_volume(GetHandle(), clip_id, 2.0);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_get_clip_volume(GetHandle(), clip_id, &volume);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+    ASSERT_TRUE(volume == 2.0);
+    LOGD("get volume [%f]", volume);
+}
+
+TEST_F(MediaeditorClipTest, DISABLED_transition)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(1).c_str(), layer_id, start + (duration / 2), duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_transition(GetHandle(), MEDIAEDITOR_TRANSITION_TYPE_CROSSFADE, layer_id, start + (duration / 2), duration / 2);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
+
+TEST_F(MediaeditorClipTest, DISABLED_transition_n)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 1000;
+    unsigned int in_point = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_set_error_cb(GetHandle(), ErrorCb, GetHandle());
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    /* Test for no overlapped area */
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(1).c_str(), layer_id, start + (duration * 2), duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_transition(GetHandle(), MEDIAEDITOR_TRANSITION_TYPE_CROSSFADE, layer_id, start + duration, duration / 2);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_start_render(GetHandle(), "/tmp/out_trans.ogv", RenderCompletedCb, GetHandle());
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    WaitForAsync();
+}
+
+#ifdef TIZEN_MEDIAEDITOR_TEXTOVERLAY_SUPPORT
+TEST_F(MediaeditorClipTest, DISABLED_text_overlay)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id1 = 0;
+    unsigned int clip_id2 = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id1);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_text_overlay(GetHandle(), "Text Overlay", "sans-serif 13", start, duration, 0xFFFFFFFF, 0.5, 0.8, &clip_id2);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
+
+TEST_F(MediaeditorClipTest, DISABLED_title)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id1 = 0;
+    unsigned int layer_id2 = 0;
+    unsigned int layer_priority = 0;
+    unsigned int layer_priority2 = 0;
+    unsigned int clip_id1 = 0;
+    unsigned int clip_id2 = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id1, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id2, &layer_priority2);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id2, start, duration, in_point, &clip_id1);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_title(GetHandle(), "Title", "sans-serif 20", start, duration, 0x80FFFF00, 0.5, 0.8, &clip_id2);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
+#endif
diff --git a/gtest/ut_composition.cpp b/gtest/ut_composition.cpp
new file mode 100644 (file)
index 0000000..75924ec
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "testbase.hpp"
+
+enum LAYER
+{
+    VIDEO_LAYER,
+    BGM_LAYER
+};
+
+enum EDITING_ELEMENT
+{
+    VIDEO_ELEMENT,
+    TRANSITION_ELEMENT,
+    BGM_ELEMENT,
+#ifdef TIZEN_MEDIAEDITOR_TEXTOVERLAY_SUPPORT
+    TEXTOVERLAY_ELEMENT,
+#endif
+    EFFECT_ELEMENT,
+    ELEMENT_MAX
+};
+
+static GMainLoop *mainloop;
+
+class MediaeditorCompositionTest : public MediaeditorTestBase {
+public:
+    MediaeditorCompositionTest() {}
+
+    static void RenderCompletedCb(void *user_data)
+    {
+        LOGD("Enter");
+        if (mainloop)
+            g_main_loop_quit(mainloop);
+    }
+
+    static void WaitForAsync()
+    {
+        mainloop = g_main_loop_new(NULL, TRUE);
+
+        LOGD("Wait render completed cb");
+        g_main_loop_run(mainloop);
+
+        g_main_loop_unref(mainloop);
+        mainloop = NULL;
+    }
+};
+
+TEST_F(MediaeditorCompositionTest, DISABLED_mp4_bgm_transition_text)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    const int num_of_clip = 2;
+    const int num_of_layer = 2;
+    unsigned int layer_id[num_of_layer] = { 0 };
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int start = 0;
+    unsigned int total = 0;
+
+    bool editing_element[ELEMENT_MAX] = {
+                                            true,   // video
+                                            true,   // transition
+                                            true,   // bgm
+#ifdef TIZEN_MEDIAEDITOR_TEXTOVERLAY_SUPPORT
+                                            false,  // textoverlay
+#endif
+                                            true   // effect
+                                        };
+
+    unsigned int duration[num_of_clip] = {
+                                            G_TIME_SPAN_MILLISECOND * 3,
+                                            G_TIME_SPAN_MILLISECOND * 3
+                                         };
+    unsigned int duration_bgm = G_TIME_SPAN_MILLISECOND * 3;
+
+    bool is_transition[num_of_clip] = {
+                                        true,
+                                        false // The last is always false
+                                      };
+    unsigned int transition_duration = G_TIME_SPAN_MILLISECOND * 2;
+
+    // Create layer
+    for (int i = 0 ; i < num_of_layer ; i++) {
+        ret = mediaeditor_add_layer(GetHandle(), &layer_id[i], &layer_priority);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+    }
+
+    // Add clips
+    for (int i = 0 ; i < num_of_clip ; i++) {
+        ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(i).c_str(), layer_id[VIDEO_LAYER], start, duration[i], 0, &clip_id);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+        start += duration[i];
+
+        if (editing_element[TRANSITION_ELEMENT] && is_transition[i])
+        {
+            ASSERT_TRUE(start > transition_duration);
+            start -= transition_duration;
+
+            ret = mediaeditor_add_transition(GetHandle(), MEDIAEDITOR_TRANSITION_TYPE_CROSSFADE, layer_id[VIDEO_LAYER], start, transition_duration);
+            ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+        }
+
+        total = start;
+    }
+
+    LOGI("total = %d", total);
+
+    // Add BGM
+    if (editing_element[BGM_ELEMENT]) {
+        start = 0;
+        for (int i = 0, step = (int)(duration_bgm / G_TIME_SPAN_MILLISECOND) ; i < (int)(total / G_TIME_SPAN_MILLISECOND) ; i += step) {
+            if (start + duration_bgm > total)
+            {
+                duration_bgm = total - start;
+            }
+
+            ret = mediaeditor_add_clip(GetHandle(), GetBgmPath().c_str(), layer_id[BGM_LAYER], start, duration_bgm, G_TIME_SPAN_MILLISECOND * 0, &clip_id);
+            ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+            ret = mediaeditor_set_clip_volume(GetHandle(), clip_id, 0.3);
+            ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+            start += duration_bgm;
+        }
+    }
+
+#ifdef TIZEN_MEDIAEDITOR_TEXTOVERLAY_SUPPORT
+    // Add textoverlay
+    if (editing_element[TEXTOVERLAY_ELEMENT]) {
+        ret = mediaeditor_add_text_overlay(GetHandle(), "Editing test", "sans-serif 30", 0, total, 0xFFFFFF00, 0.5, 0.9, &clip_id);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+        ret = mediaeditor_set_text_overlay_outline(GetHandle(), clip_id, false);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+        ret = mediaeditor_set_text_overlay_shadow(GetHandle(), clip_id, true);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+    }
+#endif
+
+    // Add effect
+    ret = mediaeditor_add_effect(GetHandle(), MEDIAEDITOR_EFFECT_VIDEO_TYPE_AGINGTV, layer_id[VIDEO_LAYER],
+        G_TIME_SPAN_MILLISECOND * 0, duration[0], &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_start_render(GetHandle(), "/tmp/out_bgm_trans.ogv", RenderCompletedCb, GetHandle());
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    WaitForAsync();
+}
+
+TEST_F(MediaeditorCompositionTest, DISABLED_mp4_concat_many)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    for (int i = 0 ; i < (int)(G_TIME_SPAN_MILLISECOND * 10) ; i += 100) {
+        LOGD("offset : %d", i);
+        ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, i, 100, G_TIME_SPAN_MILLISECOND * 20 - i, &clip_id);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+    }
+
+    ret = mediaeditor_start_render(GetHandle(), "/tmp/out_concat_many.ogv", RenderCompletedCb, GetHandle());
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    WaitForAsync();
+}
diff --git a/gtest/ut_effect.cpp b/gtest/ut_effect.cpp
new file mode 100644 (file)
index 0000000..28b6cc8
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "testbase.hpp"
+
+class MediaeditorEffectTest : public MediaeditorTestBase {
+public:
+    MediaeditorEffectTest() {}
+};
+
+TEST_F(MediaeditorEffectTest, DISABLED_effect_add_remove)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int path_clip_id = 0;
+    unsigned int effect_clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &path_clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_effect(GetHandle(), MEDIAEDITOR_EFFECT_VIDEO_TYPE_AGINGTV, layer_id, start, duration, &effect_clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_remove_effect(GetHandle(), effect_clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
+
+TEST_F(MediaeditorEffectTest, DISABLED_audio_fade_in_out)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 400;
+    unsigned int in_point = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_effect(GetHandle(), MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN, 0, start, duration / 2, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_effect(GetHandle(), MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT, 0, start + (duration / 2), duration / 2, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
diff --git a/gtest/ut_layer.cpp b/gtest/ut_layer.cpp
new file mode 100644 (file)
index 0000000..65c3609
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "testbase.hpp"
+
+class MediaeditorLayerTest : public MediaeditorTestBase {
+public:
+    MediaeditorLayerTest() {}
+
+    static void LayerPriorityChangedCb(mediaeditor_layer_info_s *layer_info, unsigned int size, void *user_data)
+    {
+        for (unsigned int i = 0 ; i < size ; i++)
+            LOGD("layer id[%d], priority[%d]", layer_info[i].id, layer_info[i].priority);
+    }
+};
+
+TEST_F(MediaeditorLayerTest, DISABLED_layer_add)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority[2] = { 0 };
+
+    for (int i = 0 ; i < 2 ; i++) {
+        ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority[i]);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+        ASSERT_EQ(layer_priority[i], i);
+    }
+}
+
+TEST_F(MediaeditorLayerTest, DISABLED_layer_remove)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id[4] = { 0 };
+    unsigned int layer_priority;
+
+    ret = mediaeditor_set_layer_priority_changed_cb(GetHandle(), LayerPriorityChangedCb, NULL);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    for (int i = 0 ; i < 4 ; i++) {
+        ret = mediaeditor_add_layer(GetHandle(), &layer_id[i], &layer_priority);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+    }
+
+    ret = mediaeditor_remove_layer(GetHandle(), layer_id[0]);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_remove_layer(GetHandle(), layer_id[1]);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
+
+TEST_F(MediaeditorLayerTest, DISABLED_layer_remove_n)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    ret = mediaeditor_remove_layer(GetHandle(), 0);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_INVALID_PARAMETER);
+
+    ret = mediaeditor_remove_layer(GetHandle(), 10);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_INVALID_PARAMETER);
+}
+
+TEST_F(MediaeditorLayerTest, DISABLED_layer_move)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id[4] = { 0 };
+    unsigned int layer_priority = 0;
+
+    ret = mediaeditor_set_layer_priority_changed_cb(GetHandle(), LayerPriorityChangedCb, NULL);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    for (int i = 0 ; i < 4 ; i++) {
+        ret = mediaeditor_add_layer(GetHandle(), &layer_id[i], &layer_priority);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+    }
+
+    ret = mediaeditor_move_layer(GetHandle(), layer_id[0], 3);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_move_layer(GetHandle(), layer_id[0], 2);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
+
+TEST_F(MediaeditorLayerTest, DISABLED_layer_move_to_bottom)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id[3] = { 0 };
+    unsigned int layer_priority = 0;
+
+    for (int i = 0 ; i < 3 ; i++) {
+        ret = mediaeditor_add_layer(GetHandle(), &layer_id[i], &layer_priority);
+        ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+    }
+
+    ret = mediaeditor_get_layer_lowest_priority(GetHandle(), &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_move_layer(GetHandle(), layer_id[0], layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
+
+TEST_F(MediaeditorLayerTest, DISABLED_layer_activate_deactivate)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_activate_layer(GetHandle(), layer_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_deactivate_layer(GetHandle(), layer_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_remove_layer(GetHandle(), layer_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_activate_layer(GetHandle(), layer_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_INVALID_PARAMETER);
+
+    ret = mediaeditor_deactivate_layer(GetHandle(), layer_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_INVALID_PARAMETER);
+}
diff --git a/gtest/ut_main.cpp b/gtest/ut_main.cpp
new file mode 100644 (file)
index 0000000..9a19657
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gtest/gtest.h"
+
+int main(int argc, char *argv[]) {
+  ::testing::InitGoogleTest(&argc, argv);
+
+  auto ret = -1;
+  ret = RUN_ALL_TESTS();
+
+  return ret;
+}
diff --git a/gtest/ut_project.cpp b/gtest/ut_project.cpp
new file mode 100644 (file)
index 0000000..69d8e79
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "testbase.hpp"
+
+static GMainLoop *mainloop;
+
+class MediaeditorProjectTest : public MediaeditorTestBase {
+public:
+    MediaeditorProjectTest() {}
+
+    static void TimelineLoadedCb(void *user_data)
+    {
+        LOGD("Enter");
+        if (mainloop)
+            g_main_loop_quit(mainloop);
+    }
+
+    static void WaitForAsync()
+    {
+        mainloop = g_main_loop_new(NULL, TRUE);
+
+        LOGD("Wait timeline loaded cb");
+        g_main_loop_run(mainloop);
+
+        g_main_loop_unref(mainloop);
+        mainloop = NULL;
+    }
+};
+
+TEST_F(MediaeditorProjectTest, DISABLED_project_create_save)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+    unsigned int start = 0;
+    unsigned int duration = 100;
+    unsigned int in_point = 0;
+
+    ret = mediaeditor_create_project(GetHandle(), GetProjectPath(0).c_str());
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_add_effect(GetHandle(), MEDIAEDITOR_EFFECT_VIDEO_TYPE_AGINGTV, layer_id, start, duration, &clip_id);
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    ret = mediaeditor_save_project(GetHandle());
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
+
+TEST_F(MediaeditorProjectTest, DISABLED_project_load_save)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    ret = mediaeditor_load_project(GetHandle(), GetProjectPath(1).c_str(), TimelineLoadedCb, GetHandle());
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+
+    WaitForAsync();
+
+    ret = mediaeditor_save_project(GetHandle());
+    ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE);
+}
diff --git a/include/media_editor.h b/include/media_editor.h
new file mode 100644 (file)
index 0000000..721579a
--- /dev/null
@@ -0,0 +1,1226 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __TIZEN_MEDIA_EDITOR_H__
+#define __TIZEN_MEDIA_EDITOR_H__
+
+#include <tizen.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file media_editor.h
+ * @brief This file contains the media editor API, related structures and enumerations.
+ * @since_tizen 7.0
+ */
+
+/**
+ * @addtogroup CAPI_MEDIA_EDITOR_MODULE
+ * @{
+ */
+
+#ifndef TIZEN_ERROR_MEDIA_EDITOR
+#define TIZEN_ERROR_MEDIA_EDITOR        -0x030D0000 /* It'll be removed at end of ACR process */
+#endif
+
+/**
+ * @brief Enumeration for the error codes of media editor.
+ * @since_tizen 7.0
+ */
+typedef enum {
+  MEDIAEDITOR_ERROR_NONE              = TIZEN_ERROR_NONE,                /**< Successful */
+  MEDIAEDITOR_ERROR_NOT_SUPPORTED     = TIZEN_ERROR_NOT_SUPPORTED,       /**< Not supported */
+  MEDIAEDITOR_ERROR_INVALID_OPERATION = TIZEN_ERROR_INVALID_OPERATION,   /**< Internal error */
+  MEDIAEDITOR_ERROR_INVALID_PARAMETER = TIZEN_ERROR_INVALID_PARAMETER,   /**< Invalid parameter */
+  MEDIAEDITOR_ERROR_PERMISSION_DENIED = TIZEN_ERROR_PERMISSION_DENIED,   /**< The access to the resources can not be granted*/
+  MEDIAEDITOR_ERROR_INVALID_STATE     = TIZEN_ERROR_MEDIA_EDITOR | 0x01, /**< Invalid state */
+  MEDIAEDITOR_ERROR_RESOURCE_CONFLICT = TIZEN_ERROR_MEDIA_EDITOR | 0x02, /**< Blocked by resource conflicted */
+  MEDIAEDITOR_ERROR_RESOURCE_FAILED   = TIZEN_ERROR_MEDIA_EDITOR | 0x03, /**< Blocked by resource failed */
+} mediaeditor_error_e;
+
+/**
+ * @brief Enumeration for the media editor state.
+ * @since_tizen 7.0
+ */
+typedef enum {
+  MEDIAEDITOR_STATE_IDLE,      /**< Created, but not started to render */
+  MEDIAEDITOR_STATE_RENDERING, /**< Start to rendering and saving */
+  MEDIAEDITOR_STATE_PREVIEW    /**< Start to preview without saving */
+} mediaeditor_state_e;
+
+/**
+ * @brief Enumeration for the media editor display type.
+ * @since_tizen 7.0
+ */
+typedef enum {
+  MEDIAEDITOR_DISPLAY_TYPE_OVERLAY, /**< Overlay surface display */
+  MEDIAEDITOR_DISPLAY_TYPE_EVAS,    /**< Evas object surface display */
+  MEDIAEDITOR_DISPLAY_TYPE_ECORE,   /**< Ecore object surface display */
+  MEDIAEDITOR_DISPLAY_TYPE_NONE     /**< This disposes of buffers */
+} mediaeditor_display_type_e;
+
+/**
+ * @}
+ */
+
+/**
+ * @addtogroup CAPI_MEDIA_EDITOR_EFFECT_MODULE
+ * @{
+ */
+
+/**
+ * @brief Enumeration for the transition type.
+ * @since_tizen 7.0
+ */
+typedef enum {
+  MEDIAEDITOR_TRANSITION_TYPE_NONE = 0,                /**< Transition none */
+  MEDIAEDITOR_TRANSITION_TYPE_BAR_WIPE_LR = 1,         /**< A bar moves from left to right */
+  MEDIAEDITOR_TRANSITION_TYPE_BAR_WIPE_TB = 2,         /**< A bar moves from top to bottom */
+  MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_TL = 3,         /**< A box expands from the upper-left corner to the lower-right corner */
+  MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_TR = 4,         /**< A box expands from the upper-right corner to the lower-left corner */
+  MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_BR = 5,         /**< A box expands from the lower-right corner to the upper-left corner */
+  MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_BL = 6,         /**< A box expands from the lower-left corner to the upper-right corner */
+  MEDIAEDITOR_TRANSITION_TYPE_FOUR_BOX_WIPE_CI = 7,    /**< A box shape expands from each of the four corners toward the center */
+  MEDIAEDITOR_TRANSITION_TYPE_FOUR_BOX_WIPE_CO = 8,    /**< A box shape expands from the center of each quadrant toward the corners of each quadrant */
+  MEDIAEDITOR_TRANSITION_TYPE_BARNDOOR_V = 21,         /**< A central, vertical line splits and expands toward the left and right edges */
+  MEDIAEDITOR_TRANSITION_TYPE_BARNDOOR_H = 22,         /**< A central, horizontal line splits and expands toward the top and bottom edges */
+  MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_TC = 23,        /**< A box expands from the top edge's midpoint to the bottom corners */
+  MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_RC = 24,        /**< A box expands from the right edge's midpoint to the left corners */
+  MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_BC = 25,        /**< A box expands from the bottom edge's midpoint to the top corners */
+  MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_LC = 26,        /**< A box expands from the left edge's midpoint to the right corners */
+  MEDIAEDITOR_TRANSITION_TYPE_DIAGONAL_TL = 41,        /**< A diagonal line moves from the upper-left corner to the lower-right corner */
+  MEDIAEDITOR_TRANSITION_TYPE_DIAGONAL_TR = 42,        /**< A diagonal line moves from the upper right corner to the lower-left corner */
+  MEDIAEDITOR_TRANSITION_TYPE_BOWTIE_V = 43,           /**< Two wedge shapes slide in from the top and bottom edges toward the center */
+  MEDIAEDITOR_TRANSITION_TYPE_BOWTIE_H = 44,           /**< Two wedge shapes slide in from the left and right edges toward the center */
+  MEDIAEDITOR_TRANSITION_TYPE_BARNDOOR_DBL = 45,       /**< A diagonal line from the lower-left to upper-right corners splits and expands toward the opposite corners */
+  MEDIAEDITOR_TRANSITION_TYPE_BARNDOOR_DTL = 46,       /**< A diagonal line from upper-left to lower-right corners splits and expands toward the opposite corners */
+  MEDIAEDITOR_TRANSITION_TYPE_MISC_DIAGONAL_DBD = 47,  /**< Four wedge shapes split from the center and retract toward the four edges */
+  MEDIAEDITOR_TRANSITION_TYPE_MISC_DIAGONAL_DD = 48,   /**< A diamond connecting the four edge midpoints simultaneously contracts toward the center and expands toward the edges */
+  MEDIAEDITOR_TRANSITION_TYPE_VEE_D = 61,              /**< A wedge shape moves from top to bottom */
+  MEDIAEDITOR_TRANSITION_TYPE_VEE_L = 62,              /**< A wedge shape moves from right to left */
+  MEDIAEDITOR_TRANSITION_TYPE_VEE_U = 63,              /**< A wedge shape moves from bottom to top */
+  MEDIAEDITOR_TRANSITION_TYPE_VEE_R = 64,              /**< A wedge shape moves from left to right */
+  MEDIAEDITOR_TRANSITION_TYPE_BARNVEE_D = 65,          /**< A 'V' shape extending from the bottom edge's midpoint to the opposite corners contracts toward the center and expands toward the edges */
+  MEDIAEDITOR_TRANSITION_TYPE_BARNVEE_L = 66,          /**< A 'V' shape extending from the left edge's midpoint to the opposite corners contracts toward the center and expands toward the edges */
+  MEDIAEDITOR_TRANSITION_TYPE_BARNVEE_U = 67,          /**< A 'V' shape extending from the top edge's midpoint to the opposite corners contracts toward the center and expands toward the edges */
+  MEDIAEDITOR_TRANSITION_TYPE_BARNVEE_R = 68,          /**< A 'V' shape extending from the right edge's midpoint to the opposite corners contracts toward the center and expands toward the edges */
+  MEDIAEDITOR_TRANSITION_TYPE_IRIS_RECT = 101,         /**< A rectangle expands from the center. */
+  MEDIAEDITOR_TRANSITION_TYPE_CLOCK_CW12 = 201,        /**< A radial hand sweeps clockwise from the twelve o'clock position */
+  MEDIAEDITOR_TRANSITION_TYPE_CLOCK_CW3 = 202,         /**< A radial hand sweeps clockwise from the three o'clock position */
+  MEDIAEDITOR_TRANSITION_TYPE_CLOCK_CW6 = 203,         /**< A radial hand sweeps clockwise from the six o'clock position */
+  MEDIAEDITOR_TRANSITION_TYPE_CLOCK_CW9 = 204,         /**< A radial hand sweeps clockwise from the nine o'clock position */
+  MEDIAEDITOR_TRANSITION_TYPE_PINWHEEL_TBV = 205,      /**< Two radial hands sweep clockwise from the twelve and six o'clock positions */
+  MEDIAEDITOR_TRANSITION_TYPE_PINWHEEL_TBH = 206,      /**< Two radial hands sweep clockwise from the nine and three o'clock positions */
+  MEDIAEDITOR_TRANSITION_TYPE_PINWHEEL_FB = 207,       /**< Four radial hands sweep clockwise */
+  MEDIAEDITOR_TRANSITION_TYPE_FAN_CT = 211,            /**< A fan unfolds from the top edge, the fan axis at the center */
+  MEDIAEDITOR_TRANSITION_TYPE_FAN_CR = 212,            /**< A fan unfolds from the right edge, the fan axis at the center */
+  MEDIAEDITOR_TRANSITION_TYPE_DOUBLEFAN_FOV = 213,     /**< Two fans, their axes at the center, unfold from the top and bottom */
+  MEDIAEDITOR_TRANSITION_TYPE_DOUBLEFAN_FOH = 214,     /**< Two fans, their axes at the center, unfold from the left and right */
+  MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CWT = 221,   /**< A radial hand sweeps clockwise from the top edge's midpoint */
+  MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CWR = 222,   /**< A radial hand sweeps clockwise from the right edge's midpoint */
+  MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CWB = 223,   /**< A radial hand sweeps clockwise from the bottom edge's midpoint */
+  MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CWL = 224,   /**< A radial hand sweeps clockwise from the left edge's midpoint */
+  MEDIAEDITOR_TRANSITION_TYPE_DOUBLESWEEP_PV = 225,    /**< Two radial hands sweep clockwise and counter-clockwise from the top and bottom edges' midpoints */
+  MEDIAEDITOR_TRANSITION_TYPE_DOUBLESWEEP_PD = 226,    /**< Two radial hands sweep clockwise and counter-clockwise from the left and right edges' midpoints */
+  MEDIAEDITOR_TRANSITION_TYPE_DOUBLESWEEP_OV = 227,    /**< Two radial hands attached at the top and bottom edges' midpoints sweep from right to left */
+  MEDIAEDITOR_TRANSITION_TYPE_DOUBLESWEEP_OH = 228,    /**< Two radial hands attached at the left and right edges' midpoints sweep from top to bottom */
+  MEDIAEDITOR_TRANSITION_TYPE_FAN_T = 231,             /**< A fan unfolds from the bottom, the fan axis at the top edge's midpoint */
+  MEDIAEDITOR_TRANSITION_TYPE_FAN_R = 232,             /**< A fan unfolds from the left, the fan axis at the right edge's midpoint */
+  MEDIAEDITOR_TRANSITION_TYPE_FAN_B = 233,             /**< A fan unfolds from the top, the fan axis at the bottom edge's midpoint */
+  MEDIAEDITOR_TRANSITION_TYPE_FAN_L = 234,             /**< A fan unfolds from the right, the fan axis at the left edge's midpoint */
+  MEDIAEDITOR_TRANSITION_TYPE_DOUBLEFAN_FIV = 235,     /**< Two fans, their axes at the top and bottom, unfold from the center */
+  MEDIAEDITOR_TRANSITION_TYPE_DOUBLEFAN_FIH = 236,     /**< Two fans, their axes at the left and right, unfold from the center */
+  MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CWTL = 241,  /**< A radial hand sweeps clockwise from the upper-left corner */
+  MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CCWBL = 242, /**< A radial hand sweeps counter-clockwise from the lower-left corner */
+  MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CWBR = 243,  /**< A radial hand sweeps clockwise from the lower-right corner */
+  MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CCWTR = 244, /**< A radial hand sweeps counter-clockwise from the upper-right corner */
+  MEDIAEDITOR_TRANSITION_TYPE_DOUBLESWEEP_PDTL = 245,  /**< Two radial hands attached at the upper-left and lower-right corners sweep down and up */
+  MEDIAEDITOR_TRANSITION_TYPE_DOUBLESWEEP_PDBL = 246,  /**< Two radial hands attached at the lower-left and upper-right corners sweep down and up */
+  MEDIAEDITOR_TRANSITION_TYPE_SALOONDOOR_T = 251,      /**< Two radial hands attached at the upper-left and upper-right corners sweep down */
+  MEDIAEDITOR_TRANSITION_TYPE_SALOONDOOR_L = 252,      /**< Two radial hands attached at the upper-left and lower-left corners sweep to the right */
+  MEDIAEDITOR_TRANSITION_TYPE_SALOONDOOR_B = 253,      /**< Two radial hands attached at the lower-left and lower-right corners sweep up */
+  MEDIAEDITOR_TRANSITION_TYPE_SALOONDOOR_R = 254,      /**< Two radial hands attached at the upper-right and lower-right corners sweep to the left */
+  MEDIAEDITOR_TRANSITION_TYPE_WINDSHIELD_R = 261,      /**< Two radial hands attached at the midpoints of the top and bottom halves sweep from right to left */
+  MEDIAEDITOR_TRANSITION_TYPE_WINDSHIELD_U = 262,      /**< Two radial hands attached at the midpoints of the left and right halves sweep from top to bottom */
+  MEDIAEDITOR_TRANSITION_TYPE_WINDSHIELD_V = 263,      /**< Two sets of radial hands attached at the midpoints of the top and bottom halves sweep from top to bottom and bottom to top */
+  MEDIAEDITOR_TRANSITION_TYPE_WINDSHIELD_H = 264,      /**< Two sets of radial hands attached at the midpoints of the left and right halves sweep from left to right and right to left */
+  MEDIAEDITOR_TRANSITION_TYPE_CROSSFADE = 512          /**< Crossfade */
+} mediaeditor_transition_type_e;
+
+/**
+ * @brief Enumeration for the effect type.
+ * @since_tizen 7.0
+ * @remarks #MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN and #MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT Can not be rolled back *
+ */
+typedef enum {
+  MEDIAEDITOR_EFFECT_TYPE_NONE,               /**< None */
+  MEDIAEDITOR_EFFECT_VIDEO_TYPE_EDGETV,       /**< Applies edge detect on video */
+  MEDIAEDITOR_EFFECT_VIDEO_TYPE_AGINGTV,      /**< Adds age to video input using scratches and dust */
+  MEDIAEDITOR_EFFECT_VIDEO_TYPE_DICETV,       /**< Dices the screen up into many small squares */
+  MEDIAEDITOR_EFFECT_VIDEO_TYPE_WARPTV,       /**< Realtime goo'ing of the video input */
+  MEDIAEDITOR_EFFECT_VIDEO_TYPE_SHAGADELICTV, /**< Makes images shagadelic */
+  MEDIAEDITOR_EFFECT_VIDEO_TYPE_VERTIGOTV,    /**< A loopback alpha blending effector with rotating and scaling */
+  MEDIAEDITOR_EFFECT_VIDEO_TYPE_REVTV,        /**< A video waveform monitor for each line of video processed */
+  MEDIAEDITOR_EFFECT_VIDEO_TYPE_QUARKTV,      /**< Motion dissolver */
+  MEDIAEDITOR_EFFECT_VIDEO_TYPE_OPTV,         /**< Optical art meets real-time video effect */
+  MEDIAEDITOR_EFFECT_VIDEO_TYPE_RADIOACTV,    /**< Detects a difference from previous frame and blurs it */
+  MEDIAEDITOR_EFFECT_VIDEO_TYPE_STREAKTV,     /**< Makes after images of moving objects */
+  MEDIAEDITOR_EFFECT_VIDEO_TYPE_RIPPLETV,     /**< Makes ripple mark effect on the video input */
+  MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN,      /**< Audio fade in */
+  MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT,     /**< Audio fade out */
+  MEDIAEDITOR_EFFECT_AUDIO_TYPE_ECHO          /**< Adds an echo or reverb effect to an audio stream */
+} mediaeditor_effect_type_e;
+
+/**
+ * @}
+ */
+
+/**
+ * @addtogroup CAPI_MEDIA_EDITOR_MODULE
+ * @{
+ */
+
+
+/**
+ * @brief The media editor handle.
+ * @since_tizen 7.0
+ */
+typedef void *mediaeditor_h;
+
+/**
+ * @brief The media editor display handle type.
+ * @since_tizen 7.0
+ */
+typedef void *mediaeditor_display_h;
+
+/**
+ * @brief Called when an error occurs.
+ * @details The following error codes can be received:\n
+ *          #MEDIAEDITOR_ERROR_INVALID_OPERATION\n
+ *          #MEDIAEDITOR_ERROR_INVALID_STATE\n
+ *          #MEDIAEDITOR_ERROR_RESOURCE_CONFLICT\n
+ *          #MEDIAEDITOR_ERROR_RESOURCE_FAILED
+ *
+ * @since_tizen 7.0
+ * @param[in] error     The error code
+ * @param[in] state     The state when error was occurred
+ * @param[in] user_data The user data passed from the callback registration function
+ * @see mediaeditor_set_error_cb()
+ * @see mediaeditor_unset_error_cb()
+ */
+typedef void (*mediaeditor_error_cb)(mediaeditor_error_e error, mediaeditor_state_e state, void *user_data);
+
+/**
+ * @brief Called when the state of media editor is changed.
+ * @since_tizen 7.0
+ * @param[in] previous  The previous state
+ * @param[in] current   The current state
+ * @param[in] user_data The user data passed from the callback registration function
+ * @see mediaeditor_set_state_changed_cb()
+ * @see mediaeditor_unset_state_changed_cb()
+ */
+typedef void (*mediaeditor_state_changed_cb)(mediaeditor_state_e previous, mediaeditor_state_e current, void *user_data);
+
+/**
+ * @brief Called when rendering output is completed.
+ * @since_tizen 7.0
+ * @param[in] user_data The user data passed from the callback registration function
+ * @see mediaeditor_start_render()
+ */
+typedef void (*mediaeditor_render_completed_cb)(void *user_data);
+
+/**
+ * @brief Creates a new media editor handle.
+ * @since_tizen 7.0
+ * @remarks The @a editor must be released using mediaeditor_destroy().\n
+ *          The timeline which is composed of a set of layers and clips will be created.
+ * @param[out] editor A newly returned handle to the media editor
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_RESOURCE_FAILED   Resource manager initialization error
+ * @post @a editor state will be #MEDIAEDITOR_STATE_IDLE.
+ *
+ * @see mediaeditor_destroy()
+ */
+int mediaeditor_create(mediaeditor_h *editor);
+
+/**
+ * @brief Destroys a media editor handle and releases all its resources.
+ * @since_tizen 7.0
+ * @param[in] editor The media editor handle
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_RESOURCE_FAILED   Resource manager deinitialization error
+ *
+ * @see mediaeditor_create()
+ */
+int mediaeditor_destroy(mediaeditor_h editor);
+
+/**
+ * @brief Sets a display for preview.
+ * @since_tizen 7.0
+ * @param[in] editor  The media editor handle
+ * @param[in] type    The display type
+ * @param[in] display The display handle
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_NOT_SUPPORTED     The feature is not supported
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ *
+ * @see mediaeditor_start_preview()
+ */
+int mediaeditor_set_display(mediaeditor_h editor, mediaeditor_display_type_e type, mediaeditor_display_h display);
+
+/**
+ * @brief Gets the current state.
+ * @since_tizen 7.0
+ * @param[in] editor The media editor handle
+ * @param[out] state The media editor state
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ *
+ * @see #mediaeditor_state_e
+ */
+int mediaeditor_get_state(mediaeditor_h editor, mediaeditor_state_e *state);
+
+/**
+ * @brief Starts to render with given path, asynchronously.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @privilege %http://tizen.org/privilege/mediastorage
+ * @privilege %http://tizen.org/privilege/externalstorage
+ * @remarks If you want to access only internal storage by using this function, you should add privilege %http://tizen.org/privilege/mediastorage.\n
+ *          Or if you want to access only external storage by using this function, you should add privilege %http://tizen.org/privilege/externalstorage.\n
+ *          If you want to access both storage, you must add both privileges.
+ * @param[in] editor    The media editor handle
+ * @param[in] path      The path to save rendered output
+ * @param[in] callback  The callback function to register
+ * @param[in] user_data The user data to be passed to the callback function
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_PERMISSION_DENIED Permission denied
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre User must add clip by calling mediaeditor_add_clip().
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @post @a editor state will be #MEDIAEDITOR_STATE_RENDERING.
+ * @post mediaeditor_render_completed_cb() will be invoked.
+ * @see mediaeditor_add_clip()
+ * @see mediaeditor_cancel_render()
+ */
+int mediaeditor_start_render(mediaeditor_h editor, const char *path, mediaeditor_render_completed_cb callback, void *user_data);
+
+/**
+ * @brief Cancels rendering.
+ * @since_tizen 7.0
+ * @param[in] editor The media editor handle
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_RENDERING.
+ * @post @a editor state will be #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_start_render()
+ */
+int mediaeditor_cancel_render(mediaeditor_h editor);
+
+/**
+ * @brief Starts to preview.
+ * @since_tizen 7.0
+ * @param[in] editor The media editor handle
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_NOT_SUPPORTED     The feature is not supported
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre The display handle must be set by calling mediaeditor_set_display()
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @post @a editor state will be #MEDIAEDITOR_STATE_PREVIEW.
+ * @see mediaeditor_set_display()
+ */
+int mediaeditor_start_preview(mediaeditor_h editor);
+
+/**
+ * @brief Stops to preview.
+ * @since_tizen 7.0
+ * @param[in] editor The media editor handle
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_NOT_SUPPORTED     The feature is not supported
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_PREVIEW.
+ * @post @a editor state will be #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_start_preview()
+ */
+int mediaeditor_stop_preview(mediaeditor_h editor);
+
+/**
+ * @brief Sets a callback function to be invoked when an asynchronous operation error occurs.
+ * @since_tizen 7.0
+ * @param[in] editor    The media editor handle
+ * @param[in] callback  Callback function pointer
+ * @param[in] user_data The user data to be passed to the callback function
+ * @return @c 0 on success,
+ *         otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE    Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @post mediaeditor_error_cb() will be invoked.
+ * @see mediaeditor_unset_error_cb()
+ * @see mediaeditor_error_cb()
+ */
+int mediaeditor_set_error_cb(mediaeditor_h editor, mediaeditor_error_cb callback, void *user_data);
+
+/**
+ * @brief Unsets an error callback function.
+ * @since_tizen 7.0
+ * @param[in] editor The media editor handle
+ * @return @c 0 on success,
+ *         otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE    Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_set_error_cb()
+ */
+int mediaeditor_unset_error_cb(mediaeditor_h editor);
+
+/**
+ * @brief Sets a callback function to be invoked when the media editor state is changed.
+ * @since_tizen 7.0
+ * @param[in] editor    The media editor handle
+ * @param[in] callback  Callback function pointer
+ * @param[in] user_data The user data to be passed to the callback function
+ * @return @c 0 on success,
+ *         otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE    Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @post mediaeditor_state_changed_cb() will be invoked.
+ * @see mediaeditor_unset_state_changed_cb()
+ * @see mediaeditor_state_changed_cb()
+ */
+int mediaeditor_set_state_changed_cb(mediaeditor_h editor, mediaeditor_state_changed_cb callback, void *user_data);
+
+/**
+ * @brief Unsets a state changed callback function.
+ * @since_tizen 7.0
+ * @param[in] editor The media editor handle
+ * @return @c 0 on success,
+ *         otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE    Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_set_state_changed_cb()
+ */
+int mediaeditor_unset_state_changed_cb(mediaeditor_h editor);
+
+/**
+ * @}
+ */
+
+/**
+ * @addtogroup CAPI_MEDIA_EDITOR_LAYER_MODULE
+ * @{
+ */
+
+/**
+ * @brief The structure type of the layer information.
+ * @since_tizen 7.0
+ */
+typedef struct {
+  unsigned int id;       /**< The layer ID */
+  unsigned int priority; /**< The layer priority */
+} mediaeditor_layer_info_s;
+
+/**
+ * @brief Called when the priority of layers is changed.
+ * @since_tizen 7.0
+ * @remarks @a layer_info should be released using free().
+ * @param[in] layer_infos The layer information including layer id and its priority. It's array.
+ * @param[in] size        The number of layer_infos array
+ * @param[in] user_data   The user data passed from the callback registration function
+ * @see mediaeditor_set_layer_priority_changed_cb()
+ * @see mediaeditor_unset_layer_priority_changed_cb()
+ */
+typedef void (*mediaeditor_layer_priority_changed_cb)(mediaeditor_layer_info_s *layer_infos, unsigned int size, void *user_data);
+
+/**
+ * @brief Adds a layer to timeline.
+ * @since_tizen 7.0
+ * @remarks Layers are responsible for ordering of contained clips. The order is determined by layers' priorities.\n
+ *          The layers are stacked in a hierarchical structure.\n
+ *          e.g. If we have 3 layers, it will have the following hierarchy.\n
+ * <pre>
+ *          Top    : layer ID 1, layer priority 0 (The highest priority)
+ *                   layer ID 2, layer priority 1
+ *          Bottom : layer ID 3, layer priority 2 (The lowest priority)
+ * </pre>
+ *          Priorities are always a continuous sequence, with no numbers missing in-between.\n
+ *          For example, priorities 0, 1, 3, 4 are not possible.\n
+ *          But, layer Id could be a discontinuous sequence. Please refer to examples of mediaeditor_remove_layer().\n
+ *          The @a layer_priority of newly added layer will be lowest priority.
+ * @param[in] editor          The media editor handle
+ * @param[out] layer_id       The layer ID. It'll be used when you want to control this layer.
+ * @param[out] layer_priority The layer priority represents the hierarchical ordering of contained clips.
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_remove_layer()
+ * @see mediaeditor_move_layer()
+ * @see mediaeditor_activate_layer()
+ * @see mediaeditor_deactivate_layer()
+ */
+int mediaeditor_add_layer(mediaeditor_h editor, unsigned int *layer_id, unsigned int *layer_priority);
+
+/**
+ * @brief Removes a layer from timeline.
+ * @since_tizen 7.0
+ * @remarks The other layer's layer ID is not changed after removing @a layer_id.\n
+ *          If @a layer_id is not bottom layer, layer priority will be rearranged.\n
+ *          e.g. There're 3 layers like the followings.\n
+ * \n
+ *          case 1 : remove layer ID 3.\n
+ *                   The layer ID 3 is removed and other layers' priorities are not rearranged.\n
+ * <pre>
+ *                   | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before   |          layer ID 2, layer priority 1 (including 3 clips)
+ *          removing | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          --------------------------------------------------------------------
+ *          After    | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          removing | bottom : layer ID 2, layer priority 1 (including 3 clips)
+ * </pre>
+ * \n
+ *          case 2 : remove layer ID 1.\n
+ * <pre>
+ *                   | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before   |          layer ID 2, layer priority 1 (including 3 clips)
+ *          removing | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          --------------------------------------------------------------------
+ *          After    | top    : layer ID 2, layer priority 0 (including 3 clips)
+ *          removing | bottom : layer ID 3, layer priority 1 (including 5 clips)
+ * </pre>
+ * \n
+ * *        case 3 : remove layer ID 2 and add a new layer.\n
+ * <pre>
+ *                   | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before   |          layer ID 2, layer priority 1 (including 3 clips)
+ *          removing | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          --------------------------------------------------------------------
+ *          After    | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          removing | bottom : layer ID 3, layer priority 1 (including 5 clips)
+ *          --------------------------------------------------------------------
+ *          After    | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          adding   |        : layer ID 3, layer priority 1 (including 5 clips)
+ *          a layer  | bottom : layer ID 4, layer priority 2 (including 7 clips)
+ * </pre>
+ * @param[in] editor   The media editor handle
+ * @param[in] layer_id The layer ID
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @post mediaeditor_layer_priority_changed_cb() will be invoked if it's set.
+ * @see mediaeditor_add_layer()
+ * @see mediaeditor_set_layer_priority_changed_cb()
+ * @see mediaeditor_unset_layer_priority_changed_cb()
+ */
+int mediaeditor_remove_layer(mediaeditor_h editor, unsigned int layer_id);
+
+/**
+ * @brief Moves a @a layer_id layer to @a layer_priority position.
+ * @since_tizen 7.0
+ * @remarks e.g. There're 3 layers.\n
+ *          case 1 : move layer ID 1 to layer priority 0.\n
+ *                   Nothing happens.\n
+ * <pre>
+ *                 | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before |          layer ID 2, layer priority 1 (including 3 clips)
+ *          moving | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          ------------------------------------------------------------------
+ *                 | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          After  |          layer ID 2, layer priority 1 (including 3 clips)
+ *          moving | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ * </pre>
+ * \n
+ *          case 2 : move layer ID 3 to layer priority 1.\n
+ * <pre>
+ *                 | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before |          layer ID 2, layer priority 1 (including 3 clips)
+ *          moving | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          ------------------------------------------------------------------
+ *                 | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          After  |          layer ID 3, layer priority 1 (including 5 clips)
+ *          moving | bottom : layer ID 2, layer priority 2 (including 3 clips)
+ * </pre>
+ * \n
+ *          case 3 : move layer ID 1 to layer priority 3.\n
+ *                   (Currently, there's no priority 3 layer.)\n
+ * <pre>
+ *                 | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before |          layer ID 2, layer priority 1 (including 3 clips)
+ *          moving | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          ------------------------------------------------------------------
+ *                 | top    : layer ID 2, layer priority 0 (including 3 clips)
+ *          After  |          layer ID 3, layer priority 1 (including 5 clips)
+ *          moving | bottom : layer ID 1, layer priority 2 (including 1 clips)
+ * </pre>
+ * \n
+ *          case 4 : move layer ID 1 to layer priority 5.\n
+ *                   (Currently, there's no priority 5 layer and it's not continuous sequence.)\n
+ * <pre>
+ *                 | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before |          layer ID 2, layer priority 1 (including 3 clips)
+ *          moving | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          ------------------------------------------------------------------
+ *                 | top    : layer ID 2, layer priority 0 (including 3 clips)
+ *          After  |          layer ID 3, layer priority 1 (including 5 clips)
+ *          moving | bottom : layer ID 1, layer priority 2 (including 1 clips)
+ * </pre>
+ * \n
+ *          If you can move layer to the lowest priority position, you can get the lowest priority using mediaeditor_get_layer_lowest_priority()
+ * @param[in] editor         The media editor handle
+ * @param[in] layer_id       The layer ID
+ * @param[in] layer_priority The target layer priority that @a layer_id is moved
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @post mediaeditor_layer_priority_changed_cb() will be invoked if it's set.
+ * @see mediaeditor_add_layer()
+ * @see mediaeditor_get_layer_lowest_priority()
+ * @see mediaeditor_set_layer_priority_changed_cb()
+ * @see mediaeditor_unset_layer_priority_changed_cb()
+ */
+int mediaeditor_move_layer(mediaeditor_h editor, unsigned int layer_id, unsigned int layer_priority);
+
+/**
+ * @brief Activates given layer on timeline. The layer will be included when it's rendered.
+ * @since_tizen 7.0
+ * @remarks Note that the newly created layer will be activated by default.
+ * @param[in] editor   The media editor handle
+ * @param[in] layer_id The layer ID
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_deactivate_layer()
+ */
+int mediaeditor_activate_layer(mediaeditor_h editor, unsigned int layer_id);
+
+/**
+ * @brief Deactivates given layer on timeline.
+ * @since_tizen 7.0
+ * @remarks The layer is not removed actually but just excluded when it's rendered.
+ * @param[in] editor   The media editor handle
+ * @param[in] layer_id The layer ID
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_activate_layer()
+ */
+int mediaeditor_deactivate_layer(mediaeditor_h editor, unsigned int layer_id);
+
+/**
+ * @brief Gets the priority of @a layer_id layer.
+ * @since_tizen 7.0
+ * @param[in] editor          The media editor handle
+ * @param[in] layer_id        The layer ID
+ * @param[out] layer_priority The layer priority
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @see mediaeditor_add_layer()
+ * @see mediaeditor_get_layer_lowest_priority()
+ * @see mediaeditor_get_layer_id()
+ */
+int mediaeditor_get_layer_priority(mediaeditor_h editor, unsigned int layer_id, unsigned int *layer_priority);
+
+/**
+ * @brief Gets the lowest priority of all layers.
+ * @since_tizen 7.0
+ * @param[in] editor          The media editor handle
+ * @param[out] layer_priority The lowest layer priority
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @see mediaeditor_add_layer()
+ * @see mediaeditor_get_layer_priority()
+ * @see mediaeditor_get_layer_id()
+ */
+int mediaeditor_get_layer_lowest_priority(mediaeditor_h editor, unsigned int *layer_priority);
+
+/**
+ * @brief Gets the layer ID of @a layer_priority layer.
+ * @since_tizen 7.0
+ * @param[in] editor         The media editor handle
+ * @param[in] layer_priority The layer priority
+ * @param[out] layer_id      The layer ID
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @see mediaeditor_add_layer()
+ * @see mediaeditor_get_layer_priority()
+ * @see mediaeditor_get_layer_lowest_priority()
+ */
+int mediaeditor_get_layer_id(mediaeditor_h editor, unsigned int layer_priority, unsigned int *layer_id);
+
+/**
+ * @brief Sets a callback function to be invoked when a layer priority is changed.
+ * @since_tizen 7.0
+ * @param[in] editor    The media editor handle
+ * @param[in] callback  Callback function pointer
+ * @param[in] user_data The user data to be passed to the callback function
+ * @return @c 0 on success,
+ *         otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE    Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @post mediaeditor_layer_priority_changed_cb() will be invoked.
+ * @see mediaeditor_unset_layer_priority_changed_cb()
+ * @see mediaeditor_layer_priority_changed_cb()
+ */
+int mediaeditor_set_layer_priority_changed_cb(mediaeditor_h editor, mediaeditor_layer_priority_changed_cb callback, void *user_data);
+
+/**
+ * @brief Unsets a layer priority changed callback function.
+ * @since_tizen 7.0
+ * @param[in] editor The media editor handle
+ * @return @c 0 on success,
+ *         otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE    Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_set_layer_priority_changed_cb()
+ */
+int mediaeditor_unset_layer_priority_changed_cb(mediaeditor_h editor);
+
+/**
+ * @}
+ */
+
+/**
+ * @addtogroup CAPI_MEDIA_EDITOR_CLIP_MODULE
+ * @{
+ */
+
+/**
+ * @brief Adds a clip to timeline.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @privilege %http://tizen.org/privilege/mediastorage
+ * @privilege %http://tizen.org/privilege/externalstorage
+ * @remarks If you want to access only internal storage by using this function, you should add privilege %http://tizen.org/privilege/mediastorage.\n
+ *          Or if you want to access only external storage by using this function, you should add privilege %http://tizen.org/privilege/externalstorage.\n
+ *          If you want to access both storage, you must add both privileges.
+ * @param[in] editor   The media editor handle
+ * @param[in] path     The content location to add
+ * @param[in] layer_id The layer ID to add clip
+ * @param[in] start    The starting position of @a path clip which is placed in timeline (in milliseconds)\n
+ *                     If this is less than 0, clip will be added to the end of layer. i.e. it will be set to layer's duration
+ * @param[in] duration The duration that the clip is in effect for in the timeline (in milliseconds)\n
+ *                     It should be lesser than or equal to clip's duration. If not, error will be occurred in runtime.\n
+ *                     The clip should have enough internal content greater than @a duration.
+ * @param[in] in_point The initial offset of @a path clip to use internally when outputting content (in milliseconds)
+ * @param[out] clip_id The ID of added clip. It'll be used when you want to delete this clip.
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_PERMISSION_DENIED Permission denied
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre The layer must be added by calling mediaeditor_add_layer().
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_add_layer()
+ * @see mediaeditor_remove_clip()
+ */
+int mediaeditor_add_clip(mediaeditor_h editor, const char *path, unsigned int layer_id,
+  int start, unsigned int duration, unsigned int in_point, unsigned int *clip_id);
+
+/**
+ * @brief Removes a clip from timeline.
+ * @since_tizen 7.0
+ * @param[in] editor  The media editor handle
+ * @param[in] clip_id The clip ID that will be removed.
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_add_clip()
+ */
+int mediaeditor_remove_clip(mediaeditor_h editor, unsigned int clip_id);
+
+/**
+ * @brief Splits a clip.
+ * @since_tizen 7.0
+ * @remarks After splitting the clip into two clips, the source clip's duration will be changed.
+ * @param[in] editor       The media editor handle
+ * @param[in] src_clip_id  The current clip ID that will be split.
+ * @param[in] position     The time position at which the source clip will be split. (in milliseconds)
+ * @param[out] new_clip_id The newly created clip ID.
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_add_clip()
+ */
+int mediaeditor_split_clip(mediaeditor_h editor, unsigned int src_clip_id, unsigned int position,
+  unsigned int *new_clip_id);
+
+/**
+ * @brief Groups clips.
+ * @since_tizen 7.0
+ * @param[in] editor    The media editor handle
+ * @param[in] clip_ids  The clip IDs to be grouped. Array.
+ * @param[in] size      The number of clips
+ * @param[out] group_id The grouped clip ID.
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_add_clip()
+ * @see mediaeditor_ungroup_clip()
+ */
+int mediaeditor_group_clip(mediaeditor_h editor, unsigned int *clip_ids, unsigned int size, unsigned int *group_id);
+
+/**
+ * @brief Ungroups a clip.
+ * @since_tizen 7.0
+ * @remarks Layer priorities of ungrouped clips are the same as before grouping.\n
+ *          If there's no matched @a group_id, #MEDIAEDITOR_ERROR_INVALID_PARAMETER will be returned.\n
+ *          @a clip_ids should be released using free().
+ * @param[in] editor    The media editor handle
+ * @param[in] group_id  The grouped clip ID
+ * @param[out] clip_ids The ungrouped clip IDs. Array
+ * @param[out] size     The size of ungrouped clips
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_group_clip()
+ */
+int mediaeditor_ungroup_clip(mediaeditor_h editor, unsigned int group_id, unsigned int **clip_ids, unsigned int *size);
+
+/**
+ * @brief Moves a clip to different layer.
+ * @since_tizen 7.0
+ * @param[in] editor         The media editor handle
+ * @param[in] clip_id        The clip ID that will be moved
+ * @param[in] layer_priority The destination layer priority
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_add_clip()
+ */
+int mediaeditor_move_clip_layer(mediaeditor_h editor, unsigned int clip_id, unsigned int layer_priority);
+
+/**
+ * @brief Gets the start position of clip.
+ * @since_tizen 7.0
+ * @param[in] editor  The media editor handle
+ * @param[in] clip_id The clip ID
+ * @param[out] start  The current start position of clip (in milliseconds)
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @see mediaeditor_add_clip()
+ * @see mediaeditor_set_clip_start()
+ * @see mediaeditor_get_clip_duration()
+ * @see mediaeditor_set_clip_duration()
+ * @see mediaeditor_get_clip_in_point()
+ * @see mediaeditor_set_clip_in_point()
+ */
+int mediaeditor_get_clip_start(mediaeditor_h editor, unsigned int clip_id, unsigned int *start);
+
+/**
+ * @brief Sets the start position of clip.
+ * @since_tizen 7.0
+ * @param[in] editor  The media editor handle
+ * @param[in] clip_id The clip ID that will be changed
+ * @param[in] start   The new start position (in milliseconds)
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_add_clip()
+ * @see mediaeditor_get_clip_start()
+ * @see mediaeditor_get_clip_duration()
+ * @see mediaeditor_set_clip_duration()
+ * @see mediaeditor_get_clip_in_point()
+ * @see mediaeditor_set_clip_in_point()
+ */
+int mediaeditor_set_clip_start(mediaeditor_h editor, unsigned int clip_id, unsigned int start);
+
+/**
+ * @brief Gets the duration of clip.
+ * @since_tizen 7.0
+ * @param[in] editor    The media editor handle
+ * @param[in] clip_id   The clip ID
+ * @param[out] duration The current time duration of clip (in milliseconds)
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @see mediaeditor_add_clip()
+ * @see mediaeditor_get_clip_start()
+ * @see mediaeditor_set_clip_start()
+ * @see mediaeditor_set_clip_duration()
+ * @see mediaeditor_get_clip_in_point()
+ * @see mediaeditor_set_clip_in_point()
+ */
+int mediaeditor_get_clip_duration(mediaeditor_h editor, unsigned int clip_id, unsigned int *duration);
+
+/**
+ * @brief Sets the duration of clip.
+ * @since_tizen 7.0
+ * @param[in] editor   The media editor handle
+ * @param[in] clip_id  The clip ID that will be changed
+ * @param[in] duration The new time duration (in milliseconds)
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_add_clip()
+ * @see mediaeditor_get_clip_start()
+ * @see mediaeditor_set_clip_start()
+ * @see mediaeditor_get_clip_duration()
+ * @see mediaeditor_get_clip_in_point()
+ * @see mediaeditor_set_clip_in_point()
+ */
+int mediaeditor_set_clip_duration(mediaeditor_h editor, unsigned int clip_id, unsigned int duration);
+
+/**
+ * @brief Gets the offset of clip.
+ * @since_tizen 7.0
+ * @param[in] editor    The media editor handle
+ * @param[in] clip_id   The clip ID that will be changed
+ * @param[out] in_point The offset of clip to use internally when outputting content (in milliseconds)
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @see mediaeditor_add_clip()
+ * @see mediaeditor_get_clip_start()
+ * @see mediaeditor_set_clip_start()
+ * @see mediaeditor_get_clip_duration()
+ * @see mediaeditor_set_clip_duration()
+ * @see mediaeditor_set_clip_in_point()
+ */
+int mediaeditor_get_clip_in_point(mediaeditor_h editor, unsigned int clip_id, unsigned int *in_point);
+
+/**
+ * @brief Sets the offset of clip.
+ * @since_tizen 7.0
+ * @param[in] editor    The media editor handle
+ * @param[in] clip_id   The clip ID
+ * @param[in] in_point The offset of clip to use internally when outputting content (in milliseconds)
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_add_clip()
+ * @see mediaeditor_get_clip_start()
+ * @see mediaeditor_set_clip_start()
+ * @see mediaeditor_get_clip_duration()
+ * @see mediaeditor_set_clip_duration()
+ * @see mediaeditor_get_clip_in_point()
+ */
+int mediaeditor_set_clip_in_point(mediaeditor_h editor, unsigned int clip_id, unsigned int in_point);
+
+/**
+ * @brief Gets the resolution of clip.
+ * @since_tizen 7.0
+ * @remarks If the clip doesn't have video, #MEDIAEDITOR_ERROR_INVALID_OPERATION will be returned.
+ * @param[in] editor  The media editor handle
+ * @param[in] clip_id The clip ID
+ * @param[out] width  The width
+ * @param[out] height The height
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @see mediaeditor_add_clip()
+ * @see mediaeditor_set_clip_resolution()
+ */
+int mediaeditor_get_clip_resolution(mediaeditor_h editor, unsigned int clip_id, unsigned int *width, unsigned int *height);
+
+/**
+ * @brief Sets the resolution of clip.
+ * @since_tizen 7.0
+ * @remarks If the clip doesn't have video, #MEDIAEDITOR_ERROR_INVALID_OPERATION will be returned.
+ * @param[in] editor  The media editor handle
+ * @param[in] clip_id The clip ID
+ * @param[in] width   The width
+ * @param[in] height  The height
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_add_clip()
+ * @see mediaeditor_get_clip_resolution()
+ */
+int mediaeditor_set_clip_resolution(mediaeditor_h editor, unsigned int clip_id, unsigned int width, unsigned int height);
+
+/**
+ * @brief Gets the volume of clip.
+ * @since_tizen 7.0
+ * @remarks If the clip doesn't have audio, #MEDIAEDITOR_ERROR_INVALID_OPERATION will be returned.
+ * @param[in] editor   The media editor handle
+ * @param[in] clip_id  The clip ID
+ * @param[out] volume  The current audio volume (0.0 ~ 10.0)
+ *                     The default value is 1.0 (100%)
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @see mediaeditor_add_clip()
+ * @see mediaeditor_set_clip_volume()
+ */
+int mediaeditor_get_clip_volume(mediaeditor_h editor, unsigned int clip_id, double *volume);
+
+/**
+ * @brief Sets the volume of clip.
+ * @since_tizen 7.0
+ * @remarks If the clip doesn't have audio, #MEDIAEDITOR_ERROR_INVALID_OPERATION will be returned.
+ * @param[in] editor  The media editor handle
+ * @param[in] clip_id The clip ID
+ * @param[in] volume  The new audio volume (0.0 ~ 10.0)\n
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_add_clip()
+ * @see mediaeditor_get_clip_volume()
+ */
+int mediaeditor_set_clip_volume(mediaeditor_h editor, unsigned int clip_id, double volume);
+
+/**
+ * @}
+ */
+
+/**
+ * @addtogroup CAPI_MEDIA_EDITOR_EFFECT_MODULE
+ * @{
+ */
+
+/**
+ * @brief Adds transition effect to the overlapped clip section.
+ * @since_tizen 7.0
+ * @remarks Note that clips should be already overlapped. If not, error will be occurred.\n
+ *          Only one transition effect can be applied in the overlapped section.\n
+ *          For each overlapped section, this function should be called to apply transition effect.
+ * @param[in] editor   The media editor handle
+ * @param[in] type     The transition type
+ * @param[in] layer_id The layer ID
+ * @param[in] start    The start position of overlapped clip area to be applied transition effect
+ * @param[in] duration The duration of overlapped clip area to be applied transition effect
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_add_layer()
+ * @see mediaeditor_add_clip()
+ */
+int mediaeditor_add_transition(mediaeditor_h editor, mediaeditor_transition_type_e type, unsigned int layer_id,
+  unsigned int start, unsigned int duration);
+
+/**
+ * @brief Adds a new effect.
+ * @since_tizen 7.0
+ * @remarks #MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN and #MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT can not be rolled back using
+ *          mediaeditor_remove_effect().
+ * @param[in] editor     The media editor handle
+ * @param[in] type       The effect type
+ * @param[in] layer_id   The layer priority
+ * @param[in] start      The starting position of effect which is placed in timeline (in milliseconds)
+ * @param[in] duration   The duration of effect (in milliseconds)
+ * @param[out] effect_id The effect ID
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_remove_effect()
+ */
+int mediaeditor_add_effect(mediaeditor_h editor, mediaeditor_effect_type_e type, unsigned int layer_id,
+    unsigned int start, unsigned int duration, unsigned int *effect_id);
+
+/**
+ * @brief Removes an effect from timeline.
+ * @since_tizen 7.0
+ * @param[in] editor    The media editor handle
+ * @param[in] effect_id The effect ID
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_add_effect()
+ */
+int mediaeditor_remove_effect(mediaeditor_h editor, unsigned int effect_id);
+
+/**
+ * @}
+ */
+
+/**
+ * @addtogroup CAPI_MEDIA_EDITOR_PROJECT_MODULE
+ * @{
+ */
+
+/**
+ * @brief Called when a timeline of the project is loaded.
+ * @since_tizen 7.0
+ * @param[in] user_data The user data passed from the callback registration function
+ * @see mediaeditor_load_project()
+ */
+typedef void (*mediaeditor_project_loaded_cb)(void *user_data);
+
+/**
+ * @brief Creates a project to @a path.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @privilege %http://tizen.org/privilege/mediastorage
+ * @privilege %http://tizen.org/privilege/externalstorage
+ * @remarks If you want to access only internal storage by using this function, you should add privilege %http://tizen.org/privilege/mediastorage.\n
+ *          Or if you want to access only external storage by using this function, you should add privilege %http://tizen.org/privilege/externalstorage.\n
+ *          If you want to access both storage, you must add both privileges.
+ * @param[in] editor The media editor handle
+ * @param[in] path   The path to create project
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_PERMISSION_DENIED The access to the resources can not be granted
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_save_project()
+ */
+int mediaeditor_create_project(mediaeditor_h editor, const char *path);
+
+/**
+ * @brief Loads a project from @a path, asynchronously.
+ * @since_tizen 7.0
+ * @privlevel public
+ * @privilege %http://tizen.org/privilege/mediastorage
+ * @privilege %http://tizen.org/privilege/externalstorage
+ * @remarks If you want to access only internal storage by using this function, you should add privilege %http://tizen.org/privilege/mediastorage.\n
+ *          Or if you want to access only external storage by using this function, you should add privilege %http://tizen.org/privilege/externalstorage.\n
+ *          If you want to access both storage, you must add both privileges.
+ * @param[in] editor The media editor handle
+ * @param[in] path   The path to the saved project
+ * @param[in] callback  The callback function to register
+ * @param[in] user_data The user data to be passed to the callback function
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_PERMISSION_DENIED The access to the resources can not be granted
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @post mediaeditor_project_loaded_cb() will be invoked.
+ * @see mediaeditor_save_project()
+ */
+int mediaeditor_load_project(mediaeditor_h editor, const char *path, mediaeditor_project_loaded_cb callback, void *user_data);
+
+/**
+ * @brief Saves the current editing information.
+ * @since_tizen 7.0
+ * @remarks The project will be saved to the project's path, which was set during creation or loading.
+ * @param[in] editor The media editor handle
+ * @return @c 0 on success, otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE     Invalid state
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @pre The project must be created or loaded.
+ * @see mediaeditor_create_project()
+ * @see mediaeditor_load_project()
+ */
+int mediaeditor_save_project(mediaeditor_h editor);
+
+/**
+ * @}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __TIZEN_MEDIA_EDITOR_H__ */
diff --git a/include/media_editor_internal.h b/include/media_editor_internal.h
new file mode 100644 (file)
index 0000000..7bef4d5
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __TIZEN_MEDIA_EDITOR_INTERNAL_H__
+#define __TIZEN_MEDIA_EDITOR_INTERNAL_H__
+
+#include <media_editor.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @internal
+ * @brief Sets the ecore wayland video display.
+ * @since_tizen 7.0
+ * @remarks This function must be called in main thread of the application.
+ *          Otherwise, it will return #MEDIAEDITOR_ERROR_INVALID_OPERATION by internal restriction.
+ *          To avoid #MEDIAEDITOR_ERROR_INVALID_OPERATION in sub thread, ecore_thread_main_loop_begin() and
+ *          ecore_thread_main_loop_end() can be used, but deadlock can occur if the main thread is busy.
+ *          So, it's not recommended to use them.
+ * @param[in] editor The handle to the mediaeditor
+ * @param[in] ecore_wl_window The ecore wayland window handle
+ * @return @c 0 on success,
+ *         otherwise a negative error value
+ * @retval #MEDIAEDITOR_ERROR_NONE Successful
+ * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state
+ * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation
+ * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE.
+ * @see mediaeditor_start_preview()
+ * @see ecore_thread_main_loop_begin()
+ * @see ecore_thread_main_loop_end()
+ */
+int mediaeditor_set_ecore_wl_display(mediaeditor_h editor, void *ecore_wl_window);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __TIZEN_MEDIA_EDITOR_INTERNAL_H__ */
diff --git a/include/media_editor_private.h b/include/media_editor_private.h
new file mode 100644 (file)
index 0000000..ddb4cda
--- /dev/null
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __TIZEN_MEDIA_EDITOR_PRIVATE_H__
+#define __TIZEN_MEDIA_EDITOR_PRIVATE_H__
+
+#include <stdio.h>
+#include <dlog.h>
+
+#include <gst/gst.h>
+#include <ges/ges.h>
+
+#include <iniparser.h>
+#include <mm_display_interface.h>
+#ifndef TIZEN_TV
+#include <mm_resource_manager.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+
+#define LOG_TAG "TIZEN_N_MEDIAEDITOR"
+
+#define FONT_COLOR_RESET    "\033[0m"
+#define FONT_COLOR_RED      "\033[31m"
+#define FONT_COLOR_GREEN    "\033[32m"
+#define FONT_COLOR_YELLOW   "\033[33m"
+#define FONT_COLOR_PURPLE   "\033[35m"
+
+#define LOG_DEBUG(fmt, arg...) \
+do { \
+    LOGD(FONT_COLOR_RESET""fmt""FONT_COLOR_RESET, ##arg); \
+} while (0)
+
+#define LOG_INFO(fmt, arg...) \
+do { \
+    LOGI(FONT_COLOR_GREEN""fmt""FONT_COLOR_RESET, ##arg); \
+} while (0)
+
+#define LOG_WARNING(fmt, arg...) \
+do { \
+    LOGW(FONT_COLOR_YELLOW""fmt""FONT_COLOR_RESET, ##arg); \
+} while (0)
+
+#define LOG_ERROR(fmt, arg...) \
+do { \
+    LOGE(FONT_COLOR_RED""fmt""FONT_COLOR_RESET, ##arg); \
+} while (0)
+
+#define LOG_ERROR_IF_REACHED(fmt, arg...) \
+do { \
+    LOGE(FONT_COLOR_RED"should not be reached here. "fmt""FONT_COLOR_RESET, ##arg); \
+} while (0)
+
+#define LOG_DEBUG_ENTER() \
+do { \
+    LOGD(FONT_COLOR_PURPLE"<Enter>"FONT_COLOR_RESET); \
+} while (0)
+
+#define LOG_DEBUG_LEAVE() \
+do { \
+    LOGD(FONT_COLOR_PURPLE"<Leave>"FONT_COLOR_RESET); \
+} while (0)
+
+#define LOG_WARNING_IF_CALLBACK_EXISTS(x_callback) \
+do { \
+    if (x_callback.callback) \
+        LOG_WARNING("previous callback[%p] user_data[%p]", x_callback.callback, x_callback.user_data); \
+} while (0)
+
+#define RET_IF(expr, fmt, arg...) \
+do { \
+    if ((expr)) { \
+        LOG_ERROR(""fmt"", ##arg); \
+        return; \
+    } \
+} while (0)
+
+#define RET_VAL_IF(expr, val, fmt, arg...) \
+do { \
+    if ((expr)) { \
+        LOG_ERROR(""fmt"", ##arg); \
+        return (val);\
+    } \
+} while (0)
+
+#define RET_WITH_UNLOCK_IF(expr, mutex, fmt, arg...) \
+do { \
+    if ((expr)) { \
+        LOG_ERROR(""fmt"", ##arg); \
+        g_mutex_unlock(mutex); \
+        return; \
+    } \
+} while (0)
+
+#define RET_VAL_WITH_UNLOCK_IF(expr, val, mutex, fmt, arg...) \
+do { \
+    if ((expr)) { \
+        LOG_ERROR(""fmt"", ##arg); \
+        g_mutex_unlock(mutex); \
+        return (val);\
+    } \
+} while (0)
+
+#define RET_ERR_IF_FEATURE_IS_NOT_SUPPORTED(x_feature) \
+do { \
+       int fret = _check_feature(x_feature); \
+       if (fret != MEDIAEDITOR_ERROR_NONE) return fret; \
+} while (0)
+
+#define SAFE_FREE(x)  { if (x) { free(x); x = NULL; } }
+#define SAFE_G_FREE(x) { if (x) { g_free(x); x = NULL; } }
+
+#define GENERATE_DOT(x_mediaeditor, x_fmt, x_arg...) \
+do { \
+    gchar *dot_name; \
+    if (!x_mediaeditor->ini.general.generate_dot) break; \
+    dot_name = g_strdup_printf(""x_fmt"", x_arg); \
+    _generate_dot(x_mediaeditor, dot_name); \
+    g_free(dot_name); \
+} while (0)
+
+#define URI_TO_PATH_OFFSET 7
+
+#define MILLI_TO_NANO(x) ((guint64)((x) * G_GUINT64_CONSTANT(1000000)))
+#define NANO_TO_MILLI(x) ((unsigned int)((x) / G_GUINT64_CONSTANT(1000000)))
+
+#define CONTAINER_TYPE_OGG  "ogg"
+#define CONTAINER_TYPE_MPEG "mpeg"
+#define AUDIO_ENCODER_TYPE_VORBIS "vorbis"
+#define AUDIO_ENCODER_TYPE_MPEG   "mpeg"
+#define VIDEO_ENCODER_TYPE_THEORA "theora"
+#define VIDEO_ENCODER_TYPE_MPEG   "mpeg"
+
+#define CONTAINER_MIME_TYPE_OGG  "application/ogg"
+#define CONTAINER_MIME_TYPE_MPEG "video/quicktime,variant=iso"
+#define AUDIO_ENCODER_MIME_TYPE_VORBIS "audio/x-vorbis"
+#define AUDIO_ENCODER_MIME_TYPE_MPEG   "audio/mpeg,channels=2,rate=44100,mpegversion=4,stream-format=raw,base-profile=lc"
+#define VIDEO_ENCODER_MIME_TYPE_THEORA "video/x-theora"
+#define VIDEO_ENCODER_MIME_TYPE_MPEG   "video/mpeg,mpegversion=4,chroma-site=raw"
+
+#define MEDIAEDITOR_CLIP(x) (((mediaeditor_clip_s *)((x)->data))->clip)
+#define MEDIAEDITOR_GROUP(x) (((mediaeditor_group_s *)((x)->data))->group)
+
+#ifndef TIZEN_TV
+#define RESOURCE_TYPE_MAX MM_RESOURCE_MANAGER_RES_TYPE_VIDEO_ENCODER + 1
+
+typedef struct _mediaeditor_resource_s {
+    mm_resource_manager_h mgr;
+    mm_resource_manager_res_h res[RESOURCE_TYPE_MAX];
+    gboolean need_to_acquire[RESOURCE_TYPE_MAX];
+    gboolean release_cb_is_calling;
+} mediaeditor_resource_s;
+#endif
+
+typedef struct _mediaeditor_callbacks_s {
+    void *callback;
+    void *user_data;
+} mediaeditor_callbacks_s;
+
+typedef struct _mediaeditor_gst_s {
+    GESPipeline *pipeline;
+    GESTimeline *timeline;
+    GESProject *project;
+
+    GstBus *bus;
+} mediaeditor_gst_s;
+
+typedef struct _ini_item_general_s {
+    bool generate_dot;
+    const char *dot_path;
+    gchar **gst_args;
+} ini_item_general_s;
+
+typedef struct _ini_item_encoder_s {
+    const char *container;
+    const char *audio_hw_encoder;
+    const char *video_hw_encoder;
+} ini_item_encoder_s;
+
+typedef struct _ini_item_decoder_s {
+    const char *audio_hw_decoder;
+    const char *video_hw_decoder;
+} ini_item_decoder_s;
+
+typedef struct _ini_item_resource_acquisition_s {
+       bool video_encoder;
+       bool video_decoder;
+} ini_item_resource_acquisition_s;
+
+typedef struct _mediaeditor_ini_s {
+    dictionary *dict;
+    ini_item_general_s general;
+    ini_item_encoder_s encoder;
+    ini_item_decoder_s decoder;
+    ini_item_resource_acquisition_s resource_acquisition;
+} mediaeditor_ini_s;
+
+typedef struct _mediaeditor_display {
+    GMutex mutex;
+
+    int type;
+    int overlay_surface_id;
+
+    mediaeditor_display_h object; // The window object to display
+    mm_display_interface_h mm_display;
+} mediaeditor_display_s;
+
+typedef struct _mediaeditor_effect_s {
+    GESEffect *effect;
+    unsigned int id;
+} mediaeditor_effect_s;
+
+typedef struct _mediaeditor_layer_s {
+    GESLayer *layer;
+    unsigned int id;
+} mediaeditor_layer_s;
+
+typedef struct _mediaeditor_clip_s {
+    GESClip *clip;
+    unsigned int id;
+    mediaeditor_effect_type_e effect_type;
+} mediaeditor_clip_s;
+
+typedef struct _mediaeditor_group_s {
+    GESGroup *group;
+    unsigned int id;
+} mediaeditor_group_s;
+
+typedef enum _idle_cb_type_e {
+    IDLE_CB_TYPE_STATE,
+    IDLE_CB_TYPE_ERROR,
+    IDLE_CB_TYPE_NUM
+} idle_cb_type_e;
+
+typedef struct _mediaeditor_s {
+    GMutex mutex;
+    GMutex event_src_mutex;
+    GCond cond;
+
+    mediaeditor_gst_s gst;
+
+    mediaeditor_state_e state;
+    mediaeditor_state_e pend_state;
+
+    mediaeditor_callbacks_s error_cb;
+    mediaeditor_callbacks_s state_changed_cb;
+    mediaeditor_callbacks_s layer_priority_changed_cb;
+    mediaeditor_callbacks_s render_completed_cb;
+    mediaeditor_callbacks_s project_loaded_cb;
+
+    guint idle_cb_event_source_ids[IDLE_CB_TYPE_NUM];
+
+    mediaeditor_ini_s ini;
+
+#ifndef TIZEN_TV
+    mediaeditor_resource_s resource;
+#endif
+    mediaeditor_display_s *display;
+
+    GList *layers;
+    GList *clips;
+    GList *groups;
+    GSList *audio_fade_effects;
+
+    unsigned int layer_id;
+    unsigned int clip_id;
+    unsigned int group_id;
+} mediaeditor_s;
+
+/* media_editor_private */
+void _invoke_error_cb(mediaeditor_s *editor, mediaeditor_error_e error);
+void _post_error_cb_in_idle(mediaeditor_s *editor, mediaeditor_error_e error);
+void _remove_remained_event_sources(mediaeditor_s *editor);
+int _gst_init(mediaeditor_s *editor);
+int _gst_pipeline_set_state(mediaeditor_s *editor, GstState state);
+void _gst_pipeline_destroy(mediaeditor_s *editor);
+int _mediaeditor_stop(mediaeditor_s *editor);
+int _mediaeditor_create_pipeline(mediaeditor_s *editor);
+int _mediaeditor_start_render(mediaeditor_s *editor, const char *path);
+int _mediaeditor_cancel_render(mediaeditor_s *editor);
+int _mediaeditor_start_preview(mediaeditor_s *editor);
+int _mediaeditor_stop_preview(mediaeditor_s *editor);
+int _check_privilege(const char *path, bool file_exist, int mode);
+int _check_feature(const char *feature);
+
+#ifndef TIZEN_TV
+/* media_editor_resource */
+int _create_resource_manager(mediaeditor_s *editor);
+int _acquire_resource_for_type(mediaeditor_s *editor, mm_resource_manager_res_type_e type);
+int _release_all_resources(mediaeditor_s *editor);
+int _destroy_resource_manager(mediaeditor_s *editor);
+#endif
+
+/* media_editor_layer */
+int _mediaeditor_get_layer(mediaeditor_s *editor, unsigned int layer_id, GESLayer **layer);
+int _mediaeditor_add_layer(mediaeditor_s *editor, unsigned int *layer_id, unsigned int *layer_priority);
+int _mediaeditor_remove_layer(mediaeditor_s *editor, unsigned int layer_id);
+int _mediaeditor_move_layer(mediaeditor_s *editor, unsigned int layer_id, unsigned int layer_priority);
+int _mediaeditor_activate_layer(mediaeditor_s *editor, unsigned int layer_id);
+int _mediaeditor_deactivate_layer(mediaeditor_s *editor, unsigned int layer_id);
+int _mediaeditor_get_layer_priority(mediaeditor_s *editor, unsigned int layer_id, unsigned int *layer_priority);
+int _mediaeditor_get_layer_lowest_priority(mediaeditor_s *editor, unsigned int *layer_priority);
+int _mediaeditor_get_layer_id(mediaeditor_s *editor, unsigned int layer_priority, unsigned int *layer_id);
+
+/* media_editor_clip */
+mediaeditor_clip_s * _mediaeditor_find_clip_data(GList *lists, unsigned int id);
+int _mediaeditor_create_clip_common(mediaeditor_s *editor, GESClip *clip, mediaeditor_effect_type_e effect_type, unsigned int *clip_id);
+int _mediaeditor_add_clip(mediaeditor_s *editor, const char *path, unsigned int layer_id,
+    int start, unsigned int duration, unsigned int in_point, unsigned int *clip_id);
+int _mediaeditor_remove_clip(mediaeditor_s *editor, unsigned int clip_id);
+int _mediaeditor_split_clip(mediaeditor_s *editor, unsigned int src_clip_id, unsigned int position, unsigned int *new_clip_id);
+int _mediaeditor_group_clip(mediaeditor_s *editor, unsigned int *clip_ids, unsigned int size, unsigned int *group_id);
+int _mediaeditor_ungroup_clip(mediaeditor_s *editor, unsigned int group_id, unsigned int **clip_ids, unsigned int *size);
+int _mediaeditor_move_clip_layer(mediaeditor_s *editor, unsigned int clip_id, unsigned int layer_id);
+int _mediaeditor_get_clip_start(mediaeditor_s *editor, unsigned int clip_id, unsigned int *start);
+int _mediaeditor_set_clip_start(mediaeditor_s *editor, unsigned int clip_id, unsigned int start);
+int _mediaeditor_get_clip_duration(mediaeditor_s *editor, unsigned int clip_id, unsigned int *duration);
+int _mediaeditor_set_clip_duration(mediaeditor_s *editor, unsigned int clip_id, unsigned int duration);
+int _mediaeditor_get_clip_in_point(mediaeditor_s *editor, unsigned int clip_id, unsigned int *in_point);
+int _mediaeditor_set_clip_in_point(mediaeditor_s *editor, unsigned int clip_id, unsigned int in_point);
+int _mediaeditor_get_clip_resolution(mediaeditor_s *editor, unsigned int clip_id, unsigned int *width, unsigned int *height);
+int _mediaeditor_set_clip_resolution(mediaeditor_s *editor, unsigned int clip_id, unsigned int width, unsigned int height);
+int _mediaeditor_get_clip_volume(mediaeditor_s *editor, unsigned int clip_id, double *volume);
+int _mediaeditor_set_clip_volume(mediaeditor_s *editor, unsigned int clip_id, double volume);
+
+/* media_editor_display */
+int _mediaeditor_set_display(mediaeditor_h editor, mediaeditor_display_type_e type, mediaeditor_display_h object);
+void _mediaeditor_release_display(mediaeditor_display_s *display);
+
+/* media_editor_effect */
+int _mediaeditor_add_transition(mediaeditor_s *editor, mediaeditor_transition_type_e type, unsigned int layer_id,
+    unsigned int start, unsigned int duration);
+int _mediaeditor_add_effect(mediaeditor_s *editor, mediaeditor_effect_type_e type, unsigned int layer_id,
+    unsigned int start, unsigned int duration, unsigned int *effect_id);
+int _mediaeditor_remove_effect(mediaeditor_s *editor, unsigned int effect_id);
+
+/* media_editor_project */
+int _mediaeditor_create_project(mediaeditor_s *editor, const char *path);
+int _mediaeditor_load_project(mediaeditor_s *editor, const char *path);
+int _mediaeditor_save_project(mediaeditor_s *editor, const gchar *uri);
+
+/* media_editor_ini */
+int _load_ini(mediaeditor_s *editor);
+void _unload_ini(mediaeditor_s *editor);
+bool _is_resource_required(mediaeditor_ini_s *ini);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __TIZEN_MEDIA_EDITOR_PRIVATE_H__ */
diff --git a/packaging/capi-media-editor.spec b/packaging/capi-media-editor.spec
new file mode 100644 (file)
index 0000000..b5a47dc
--- /dev/null
@@ -0,0 +1,137 @@
+Name:       capi-media-editor
+Summary:    A Tizen Media Editor API
+Version:    0.0.1
+Release:    0
+Group:      Multimedia/API
+License:    Apache-2.0
+Source0:    %{name}-%{version}.tar.gz
+BuildRequires:  cmake
+BuildRequires:  pkgconfig(dlog)
+BuildRequires:  pkgconfig(glib-2.0)
+BuildRequires:  pkgconfig(gio-2.0)
+BuildRequires:  pkgconfig(mm-common)
+BuildRequires:  pkgconfig(mm-display-interface)
+BuildRequires:  pkgconfig(capi-base-common)
+BuildRequires:  pkgconfig(capi-media-tool)
+BuildRequires:  pkgconfig(appcore-efl)
+BuildRequires:  pkgconfig(elementary)
+BuildRequires:  pkgconfig(ecore)
+BuildRequires:  pkgconfig(evas)
+BuildRequires:  pkgconfig(gstreamer-1.0)
+BuildRequires:  pkgconfig(gstreamer-plugins-base-1.0)
+BuildRequires:  pkgconfig(gstreamer-pbutils-1.0)
+BuildRequires:  pkgconfig(gst-editing-services-1.0)
+BuildRequires:  pkgconfig(boost)
+BuildRequires:  pkgconfig(ecore-wl2)
+BuildRequires:  pkgconfig(wayland-client)
+BuildRequires:  pkgconfig(tizen-extension-client)
+BuildRequires:  pkgconfig(iniparser)
+BuildRequires:  pkgconfig(capi-system-info)
+BuildRequires:  gtest-devel
+BuildRequires:  gtest
+Requires(post): /sbin/ldconfig
+Requires(postun): /sbin/ldconfig
+%if "%{tizen_profile_name}" != "tv"
+BuildRequires:  pkgconfig(mm-resource-manager)
+%endif
+
+%description
+A Media Editor library in Tizen Native API.
+
+%package devel
+Summary:  A Media Editor API (Development)
+Requires: %{name} = %{version}-%{release}
+
+%description devel
+Development related files for a Media Editor library in Tizen Native API.
+
+%package tool
+Summary:  A Media Editor API testsuite with gtest
+Requires: %{name} = %{version}-%{release}
+BuildRequires: gtest-devel
+BuildRequires: gtest
+
+%description tool
+Tizen Native Media Editor API testsuite with gtest.
+
+%files tool
+%defattr(-,root,root,-)
+%{_bindir}/mediaeditor_ut
+
+%if 0%{?gcov:1}
+%package gcov
+Summary: Line Coverage of Media Editor library in Tizen Native API
+Group: Development/Multimedia
+
+%description gcov
+Collection of files related to Line Coverage. It is teseted as gcov for a Media Editor library in Tizen native API
+%endif
+
+%prep
+%setup -q
+
+%build
+export CFLAGS+=" -DSYSCONFDIR=\\\"%{_hal_sysconfdir}\\\""
+export CXXFLAGS+=" -DSYSCONFDIR=\\\"%{_hal_sysconfdir}\\\""
+
+%if 0%{?sec_build_binary_debug_enable}
+export CFLAGS+=" -DTIZEN_DEBUG_ENABLE"
+%endif
+
+%if 0%{?gcov:1}
+export CFLAGS+=" -fprofile-arcs -ftest-coverage"
+export CXXFLAGS+=" -fprofile-arcs -ftest-coverage"
+export LDFLAGS+=" -lgcov"
+%endif
+
+MAJORVER=`echo %{version} | awk 'BEGIN {FS="."}{print $1}'`
+%cmake . -DCMAKE_INSTALL_PREFIX=%{_prefix} -DFULLVER=%{version} -DMAJORVER=${MAJORVER} \
+%if "%{tizen_profile_name}" == "tv"
+-DTIZEN_PROFILE_TV=on
+%else
+-DTIZEN_PROFILE_TV=off
+%endif
+
+make %{?jobs:-j%jobs}
+
+%if 0%{?gcov:1}
+mkdir -p gcov-obj
+find . -name '*.gcno' -exec cp '{}' gcov-obj ';'
+%endif
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}%{_bindir}
+cp test/media_editor_test %{buildroot}%{_bindir}
+
+%make_install
+
+%if 0%{?gcov:1}
+mkdir -p %{buildroot}%{_datadir}/gcov/obj
+install -m 0644 gcov-obj/* %{buildroot}%{_datadir}/gcov/obj
+%endif
+
+%post -p /sbin/ldconfig
+
+%postun -p /sbin/ldconfig
+
+%files
+%manifest capi-media-editor.manifest
+%license LICENSE.APLv2
+%{_libdir}/libcapi-media-editor.so.*
+
+%files devel
+%{_includedir}/media/media_editor.h
+%{_includedir}/media/media_editor_internal.h
+%{_libdir}/pkgconfig/*.pc
+%{_libdir}/libcapi-media-editor.so
+
+%files tool
+%manifest capi-media-editor-tool.manifest
+%license LICENSE.APLv2
+%{_bindir}/*
+
+%if 0%{?gcov:1}
+%files gcov
+%{_datadir}/gcov/obj/*
+%endif
diff --git a/src/media_editor.c b/src/media_editor.c
new file mode 100644 (file)
index 0000000..fb1e61c
--- /dev/null
@@ -0,0 +1,1099 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "media_editor.h"
+#include "media_editor_private.h"
+
+#define _MEDIAEDITOR_FEATURE_DISPLAY "http://tizen.org/feature/display"
+
+int mediaeditor_create(mediaeditor_h *editor)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    mediaeditor_s *_editor = NULL;
+    g_autoptr(GMutexLocker) locker = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    _editor = g_new0(mediaeditor_s, 1);
+
+    g_mutex_init(&_editor->mutex);
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    g_mutex_init(&_editor->event_src_mutex);
+    g_cond_init(&_editor->cond);
+
+    _load_ini(_editor);
+
+    if (_is_resource_required(&_editor->ini)) {
+#ifndef TIZEN_TV
+        if ((ret = _create_resource_manager(_editor)) != MEDIAEDITOR_ERROR_NONE)
+            goto error;
+#else
+        LOG_WARNING("no resource manager integration yet");
+#endif
+    }
+
+    if ((ret = _gst_init(_editor)) != MEDIAEDITOR_ERROR_NONE)
+        goto error;
+
+    if ((ret = _mediaeditor_create_pipeline(_editor)) != MEDIAEDITOR_ERROR_NONE)
+        goto error;
+
+    _editor->state = MEDIAEDITOR_STATE_IDLE;
+
+    _editor->layer_id = 1;
+    _editor->clip_id = 1;
+    _editor->group_id = 1;
+
+    *editor = _editor;
+
+    LOG_INFO("media editor[%p] is created", *editor);
+
+    return MEDIAEDITOR_ERROR_NONE;
+
+error:
+    _unload_ini(_editor);
+    g_free(_editor);
+
+    return ret;
+}
+
+int mediaeditor_destroy(mediaeditor_h editor)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", _editor);
+
+    _remove_remained_event_sources(_editor);
+
+    ret = _gst_pipeline_set_state(_editor, GST_STATE_NULL);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set gst state");
+
+    if (_editor->display)
+        _mediaeditor_release_display(_editor->display);
+
+    _gst_pipeline_destroy(_editor);
+
+#ifndef TIZEN_TV
+    if (_destroy_resource_manager(_editor) != MEDIAEDITOR_ERROR_NONE)
+        LOG_ERROR("failed to destroy editor[%p]", _editor);
+#else
+    LOG_WARNING("no resource manager integration yet");
+#endif
+
+    _unload_ini(_editor);
+
+    ges_deinit();
+
+    g_cond_clear(&_editor->cond);
+    g_mutex_clear(&_editor->event_src_mutex);
+
+    g_clear_pointer(&locker, g_mutex_locker_free);
+    g_mutex_clear(&_editor->mutex);
+
+    LOG_INFO("media editor[%p] is destroyed", _editor);
+
+    SAFE_G_FREE(_editor);
+
+    LOG_INFO("Leave");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int mediaeditor_set_display(mediaeditor_h editor, mediaeditor_display_type_e type, mediaeditor_display_h display)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_ERR_IF_FEATURE_IS_NOT_SUPPORTED(_MEDIAEDITOR_FEATURE_DISPLAY);
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(display == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "display is NULL");
+    RET_VAL_IF(type < MEDIAEDITOR_DISPLAY_TYPE_OVERLAY || type > MEDIAEDITOR_DISPLAY_TYPE_NONE,
+        MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid display type");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_set_display(_editor, type, display);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_get_state(mediaeditor_h editor, mediaeditor_state_e *state)
+{
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    g_mutex_lock(&_editor->mutex);
+
+    *state = _editor->state;
+
+    g_mutex_unlock(&_editor->mutex);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int mediaeditor_start_render(mediaeditor_h editor, const char* path, mediaeditor_render_completed_cb callback, void *user_data)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL");
+    ret = _check_privilege(path, false, R_OK | W_OK);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to check privilege");
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(callback == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "callback is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    _editor->render_completed_cb.callback = callback;
+    _editor->render_completed_cb.user_data = user_data;
+
+    ret = _mediaeditor_start_render(_editor, path);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_cancel_render(mediaeditor_h editor)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_RENDERING, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be RENDERING");
+
+    ret = _mediaeditor_cancel_render(_editor);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_start_preview(mediaeditor_h editor)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_ERR_IF_FEATURE_IS_NOT_SUPPORTED(_MEDIAEDITOR_FEATURE_DISPLAY);
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_start_preview(_editor);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_stop_preview(mediaeditor_h editor)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_ERR_IF_FEATURE_IS_NOT_SUPPORTED(_MEDIAEDITOR_FEATURE_DISPLAY);
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p], state [%d], pend_state[%d]", editor, _editor->state, _editor->pend_state);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_PREVIEW, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be PREVIEW");
+
+    ret = _mediaeditor_stop_preview(_editor);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_add_layer(mediaeditor_h editor, unsigned int *layer_id, unsigned int *layer_priority)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(layer_id == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_id is NULL");
+    RET_VAL_IF(layer_priority == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_priority is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_add_layer(_editor, layer_id, layer_priority);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_remove_layer(mediaeditor_h editor, unsigned int layer_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_remove_layer(_editor, layer_id);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_move_layer(mediaeditor_h editor, unsigned int layer_id, unsigned int layer_priority)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_move_layer(_editor, layer_id, layer_priority);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_activate_layer(mediaeditor_h editor, unsigned int layer_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_activate_layer(_editor, layer_id);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_deactivate_layer(mediaeditor_h editor, unsigned int layer_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_deactivate_layer(_editor, layer_id);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_get_layer_priority(mediaeditor_h editor, unsigned int layer_id, unsigned int *layer_priority)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(layer_priority == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_priority is NULL");
+
+    g_mutex_lock(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    ret = _mediaeditor_get_layer_priority(_editor, layer_id, layer_priority);
+
+    g_mutex_unlock(&_editor->mutex);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_get_layer_lowest_priority(mediaeditor_h editor, unsigned int *layer_priority)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(layer_priority == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_priority is NULL");
+
+    g_mutex_lock(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    ret = _mediaeditor_get_layer_lowest_priority(_editor, layer_priority);
+
+    g_mutex_unlock(&_editor->mutex);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_get_layer_id(mediaeditor_h editor, unsigned int layer_priority, unsigned int *layer_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(layer_id == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_id is NULL");
+
+    g_mutex_lock(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    ret = _mediaeditor_get_layer_id(_editor, layer_priority, layer_id);
+
+    g_mutex_unlock(&_editor->mutex);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_add_clip(mediaeditor_h editor, const char *path, unsigned int layer_id,
+    int start, unsigned int duration, unsigned int in_point, unsigned int *clip_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL");
+    ret = _check_privilege(path, true, R_OK);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to check privilege");
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_add_clip(_editor, path, layer_id, start, duration, in_point, clip_id);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_remove_clip(mediaeditor_h editor, unsigned int clip_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_remove_clip(_editor, clip_id);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_split_clip(mediaeditor_h editor, unsigned int src_clip_id, unsigned int position,
+  unsigned int *new_clip_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_split_clip(_editor, src_clip_id, position, new_clip_id);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_group_clip(mediaeditor_h editor, unsigned int *clip_ids, unsigned int size, unsigned int *group_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_group_clip(_editor, clip_ids, size, group_id);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_ungroup_clip(mediaeditor_h editor, unsigned int group_id, unsigned int **clip_ids, unsigned int *size)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_ungroup_clip(_editor, group_id, clip_ids, size);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_move_clip_layer(mediaeditor_h editor, unsigned int clip_id, unsigned int layer_priority)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_move_clip_layer(_editor, clip_id, layer_priority);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_get_clip_start(mediaeditor_h editor, unsigned int clip_id, unsigned int *start)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(start == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "start is NULL");
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    g_mutex_lock(&_editor->mutex);
+
+    ret = _mediaeditor_get_clip_start(_editor, clip_id, start);
+
+    g_mutex_unlock(&_editor->mutex);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_set_clip_start(mediaeditor_h editor, unsigned int id, unsigned int start)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_set_clip_start(_editor, id, start);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_get_clip_duration(mediaeditor_h editor, unsigned int id, unsigned int *duration)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(duration == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "duration is NULL");
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    g_mutex_lock(&_editor->mutex);
+
+    ret = _mediaeditor_get_clip_duration(_editor, id, duration);
+
+    g_mutex_unlock(&_editor->mutex);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_set_clip_duration(mediaeditor_h editor, unsigned int id, unsigned int duration)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_set_clip_duration(_editor, id, duration);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_get_clip_in_point(mediaeditor_h editor, unsigned int id, unsigned int *in_point)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(in_point == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "in_point is NULL");
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    g_mutex_lock(&_editor->mutex);
+
+    ret = _mediaeditor_get_clip_in_point(_editor, id, in_point);
+
+    g_mutex_unlock(&_editor->mutex);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_set_clip_in_point(mediaeditor_h editor, unsigned int id, unsigned int in_point)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_set_clip_in_point(_editor, id, in_point);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_get_clip_resolution(mediaeditor_h editor, unsigned int id, unsigned int *width, unsigned int *height)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(width == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "width is NULL");
+    RET_VAL_IF(height == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "height is NULL");
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    g_mutex_lock(&_editor->mutex);
+
+    ret = _mediaeditor_get_clip_resolution(_editor, id, width, height);
+
+    g_mutex_unlock(&_editor->mutex);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_set_clip_resolution(mediaeditor_h editor, unsigned int id, unsigned int width, unsigned int height)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_set_clip_resolution(_editor, id, width, height);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_get_clip_volume(mediaeditor_h editor, unsigned int id, double *volume)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(volume == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "volume is NULL");
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    g_mutex_lock(&_editor->mutex);
+
+    ret = _mediaeditor_get_clip_volume(_editor, id, volume);
+
+    g_mutex_unlock(&_editor->mutex);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_set_clip_volume(mediaeditor_h editor, unsigned int id, double volume)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(volume < 0.0 || volume > 10.0, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "volume is out of range");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_set_clip_volume(_editor, id, volume);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_add_transition(mediaeditor_h editor, mediaeditor_transition_type_e type, unsigned int layer_id,
+    unsigned int start, unsigned int duration)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(type < MEDIAEDITOR_TRANSITION_TYPE_NONE || type > MEDIAEDITOR_TRANSITION_TYPE_CROSSFADE,
+        MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid transition type");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_add_transition(_editor, type, layer_id, start, duration);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_add_effect(mediaeditor_h editor, mediaeditor_effect_type_e type, unsigned int layer_id,
+    unsigned int start, unsigned int duration, unsigned int *effect_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(type <= MEDIAEDITOR_EFFECT_TYPE_NONE || type > MEDIAEDITOR_EFFECT_AUDIO_TYPE_ECHO,
+        MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid effect type");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_add_effect(_editor, type, layer_id, start, duration, effect_id);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_remove_effect(mediaeditor_h editor, unsigned int effect_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_remove_effect(_editor, effect_id);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_create_project(mediaeditor_h editor, const char *path)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL");
+    ret = _check_privilege(path, false, R_OK | W_OK);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to check privilege");
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_create_project(_editor, path);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_load_project(mediaeditor_h editor, const char *path, mediaeditor_project_loaded_cb callback, void *user_data)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL");
+    ret = _check_privilege(path, true, R_OK);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to check privilege");
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(callback == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "callback is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    _editor->project_loaded_cb.callback = callback;
+    _editor->project_loaded_cb.user_data = user_data;
+
+    ret = _mediaeditor_load_project(_editor, path);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_save_project(mediaeditor_h editor)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+    g_autofree gchar *uri = NULL;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(_editor->gst.project == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "project is not loaded or created");
+
+    uri = ges_project_get_uri(_editor->gst.project);
+    RET_VAL_IF(uri == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to get uri from project");
+    ret = _check_privilege(uri + URI_TO_PATH_OFFSET, false, W_OK);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to check privilege");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    ret = _mediaeditor_save_project(_editor, uri);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return ret;
+}
+
+int mediaeditor_set_error_cb(mediaeditor_h editor, mediaeditor_error_cb callback, void *user_data)
+{
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(callback == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "callback is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    _editor->error_cb.callback = callback;
+    _editor->error_cb.user_data = user_data;
+
+    LOG_INFO("callback[%p] user_data[%p]", callback, user_data);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int mediaeditor_unset_error_cb(mediaeditor_h editor)
+{
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+    RET_VAL_IF(_editor->error_cb.callback == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION,
+        "callback was not set");
+
+    _editor->error_cb.callback = NULL;
+    _editor->error_cb.user_data = NULL;
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int mediaeditor_set_state_changed_cb(mediaeditor_h editor, mediaeditor_state_changed_cb callback, void *user_data)
+{
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(callback == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "callback is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    _editor->state_changed_cb.callback = callback;
+    _editor->state_changed_cb.user_data = user_data;
+
+    LOG_INFO("callback[%p] user_data[%p]", callback, user_data);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int mediaeditor_unset_state_changed_cb(mediaeditor_h editor)
+{
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+    RET_VAL_IF(_editor->state_changed_cb.callback == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION,
+        "callback was not set");
+
+    _editor->state_changed_cb.callback = NULL;
+    _editor->state_changed_cb.user_data = NULL;
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int mediaeditor_set_layer_priority_changed_cb(mediaeditor_h editor, mediaeditor_layer_priority_changed_cb callback, void *user_data)
+{
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(callback == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "callback is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+
+    _editor->layer_priority_changed_cb.callback = callback;
+    _editor->layer_priority_changed_cb.user_data = user_data;
+
+    LOG_INFO("callback[%p] user_data[%p]", callback, user_data);
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int mediaeditor_unset_layer_priority_changed_cb(mediaeditor_h editor)
+{
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s *)editor;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&_editor->mutex);
+
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should be IDLE");
+    RET_VAL_IF(_editor->layer_priority_changed_cb.callback == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION,
+        "callback was not set");
+
+    _editor->layer_priority_changed_cb.callback = NULL;
+    _editor->layer_priority_changed_cb.user_data = NULL;
+
+    LOG_DEBUG("Leave [%p]", editor);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
\ No newline at end of file
diff --git a/src/media_editor_clip.c b/src/media_editor_clip.c
new file mode 100644 (file)
index 0000000..14c3319
--- /dev/null
@@ -0,0 +1,609 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <media_editor.h>
+#include <media_editor_private.h>
+
+static void __print_clip_data(mediaeditor_clip_s *clip_data)
+{
+#ifdef PRINT_CLIP_DEBUG_INFO
+    RET_IF(clip_data == NULL, "clip_data is null");
+    LOG_DEBUG("clip_data[%p], clip[%p], id[%d], effect_type[%d]", clip_data, clip_data->clip, clip_data->id, (int)clip_data->effect_type);
+#endif
+}
+
+static mediaeditor_clip_s *__mediaeditor_create_clip_data(GESClip *clip, mediaeditor_effect_type_e effect_type, unsigned int id)
+{
+    mediaeditor_clip_s *data = NULL;
+
+    data = g_new0(mediaeditor_clip_s, 1);
+
+    data->clip = clip;
+    data->effect_type = effect_type;
+    data->id = id;
+
+    return data;
+}
+
+static mediaeditor_group_s *__mediaeditor_create_group_data(GESGroup *group, unsigned int id)
+{
+    mediaeditor_group_s *data = NULL;
+
+    data = g_new0(mediaeditor_group_s, 1);
+
+    data->group = group;
+    data->id = id;
+
+    return data;
+}
+
+static gint __mediaeditor_comparefunc_find_clip(gconstpointer a, gconstpointer b)
+{
+    mediaeditor_clip_s *clip_data = (mediaeditor_clip_s *)a;
+    unsigned int *id = (unsigned int *)b;
+
+    if (clip_data == NULL || clip_data->id != *id)
+        return -1;
+
+    LOG_DEBUG("clip_data->id[%d], *id[%d]", clip_data->id,*id);
+
+    return 0;
+}
+
+static GList *__mediaeditor_find_clip_list(GList *lists, unsigned int id)
+{
+    GList *list = NULL;
+
+    list = g_list_find_custom(lists, (gconstpointer)&id, __mediaeditor_comparefunc_find_clip);
+    RET_VAL_IF(list == NULL, NULL, "failed to find matched clip");
+
+    LOG_DEBUG("list->data(clip)[%p]", list->data);
+
+    return list;
+}
+
+mediaeditor_clip_s *_mediaeditor_find_clip_data(GList *lists, unsigned int id)
+{
+    GList *list = NULL;
+
+    list = __mediaeditor_find_clip_list(lists, id);
+    RET_VAL_IF(list == NULL, NULL, "failed to find clip list");
+
+    __print_clip_data(list->data);
+
+    return list->data;
+}
+
+static GESClip *__mediaeditor_find_clip(GList *lists, unsigned int id)
+{
+    mediaeditor_clip_s *clip_data = NULL;
+
+    clip_data = _mediaeditor_find_clip_data(lists, id);
+    RET_VAL_IF(clip_data == NULL, NULL, "failed to find clip data");
+
+    return clip_data->clip;
+}
+
+/* this method find/return effect and don't remove it from list */
+static GList *__mediaeditor_peek_clips(GList *lists, unsigned int *ids, unsigned int size)
+{
+    GList *list_new = NULL;
+    GESClip *clip = NULL;
+
+    for (unsigned int i = 0 ; i < size ; i++) {
+        if ((clip = __mediaeditor_find_clip(lists, ids[i])))
+            list_new = g_list_append(list_new, clip);
+    }
+
+    return list_new;
+}
+
+static void __mediaeditor_find_id(mediaeditor_s *editor, GList *lists, unsigned int *ids)
+{
+    int i = 0;
+    GList *sl = NULL;
+    GList *dl = NULL;
+    mediaeditor_clip_s *clip_data = NULL;
+
+    for (sl = lists ; sl ; sl = sl->next, i++) {
+        for (dl = editor->clips ; dl ; dl = dl->next) {
+            clip_data = dl->data;
+
+            if (clip_data->clip == (GESClip *)sl->data) {
+                ids[i] = clip_data->id;
+                LOG_DEBUG("found clip : [%p]", clip_data->clip);
+                break;
+            }
+        }
+    }
+}
+
+static gint __mediaeditor_comparefunc_find_group(gconstpointer a, gconstpointer b)
+{
+    mediaeditor_group_s *group_data = (mediaeditor_group_s *)a;
+    unsigned int *id = (unsigned int *)b;
+
+    if (group_data == NULL || group_data->id != *id)
+        return -1;
+
+    return 0;
+}
+
+static GList *__mediaeditor_find_group_list(GList *lists, unsigned int id)
+{
+    GList *list = NULL;
+
+    list = g_list_find_custom(lists, &id, __mediaeditor_comparefunc_find_group);
+    RET_VAL_IF(list == NULL, NULL, "failed to find matched group");
+
+    LOG_DEBUG("list-data(clip)[%p]", list->data);
+
+    return list;
+}
+
+static mediaeditor_group_s *__mediaeditor_find_group_data(GList *lists, unsigned int id)
+{
+    GList *list = NULL;
+
+    list = __mediaeditor_find_group_list(lists, id);
+    RET_VAL_IF(list == NULL, NULL, "failed to find group list");
+
+    return list->data;
+}
+
+static GESGroup *__mediaeditor_find_group(GList *lists, unsigned int id)
+{
+    mediaeditor_group_s *group_data = NULL;
+
+    group_data = __mediaeditor_find_group_data(lists, id);
+    RET_VAL_IF(group_data == NULL, NULL, "failed to find group data");
+
+    return group_data->group;
+}
+
+static bool __has_audio_track(GESClip *clip)
+{
+    RET_VAL_IF(clip == NULL, false, "clip is NULL");
+
+    if (ges_clip_find_track_element(clip, NULL, GES_TYPE_AUDIO_SOURCE) != NULL) {
+        LOG_DEBUG("The clip have audio");
+        return true;
+    }
+
+    return false;
+}
+
+static bool __has_video_track(GESClip *clip)
+{
+    RET_VAL_IF(clip == NULL, false, "clip is NULL");
+
+    if (ges_clip_find_track_element(clip, NULL, GES_TYPE_VIDEO_SOURCE) != NULL) {
+        LOG_DEBUG("The clip have video");
+        return true;
+    }
+
+    return false;
+}
+
+static void __mediaeditor_add_child_properties_resolution(GESClip *clip)
+{
+    GValue val = G_VALUE_INIT;
+
+    g_value_init(&val, G_TYPE_INT);
+
+    if (!ges_timeline_element_get_child_property(GES_TIMELINE_ELEMENT(clip), "width", &val)) {
+        GObjectClass *eklass = G_OBJECT_GET_CLASS(clip);
+
+        if (ges_timeline_element_add_child_property(GES_TIMELINE_ELEMENT(clip), g_object_class_find_property(eklass, "width"), G_OBJECT(clip)))
+            LOG_DEBUG("width property is added in URI clip");
+
+        if (ges_timeline_element_add_child_property(GES_TIMELINE_ELEMENT(clip), g_object_class_find_property(eklass, "height"), G_OBJECT(clip)))
+            LOG_DEBUG("height property is added in URI clip");
+    }
+}
+
+int _mediaeditor_create_clip_common(mediaeditor_s *editor, GESClip *clip, mediaeditor_effect_type_e effect_type, unsigned int *clip_id)
+{
+    mediaeditor_clip_s *clip_data = NULL;
+
+    clip_data = __mediaeditor_create_clip_data(clip, effect_type, editor->clip_id);
+    RET_VAL_IF(clip_data == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "clip_data is null");
+
+    __print_clip_data(clip_data);
+
+    editor->clips = g_list_append(editor->clips, clip_data);
+    RET_VAL_IF(editor->clips == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "list is null");
+
+    *clip_id = (editor->clip_id)++;
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_add_clip(mediaeditor_s *editor, const char *path, unsigned int layer_id,
+    int start, unsigned int duration, unsigned int in_point, unsigned int *clip_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    GESClip *clip = NULL;
+    GESLayer *layer = NULL;
+    guint64 start_nsec = 0;
+    guint64 duration_nsec = 0;
+    guint64 in_point_nsec = 0;
+    g_autofree gchar *uri = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL");
+    RET_VAL_IF(clip_id == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "clip_id is NULL");
+
+    uri = gst_filename_to_uri(path, NULL);
+    RET_VAL_IF(uri == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "failed to get uri");
+    LOGI("uri : [%s]", uri);
+
+    clip = GES_CLIP(ges_uri_clip_new(uri));
+
+    start_nsec = MILLI_TO_NANO(start);
+    duration_nsec = MILLI_TO_NANO(duration);
+    in_point_nsec = MILLI_TO_NANO(in_point);
+
+    LOG_INFO("uri[%s], layer_id[%d], clip[%p], start[%"G_GUINT64_FORMAT"], duration[%"G_GUINT64_FORMAT"]",
+        uri, layer_id, clip, start_nsec, duration_nsec);
+    ges_timeline_element_set_start(GES_TIMELINE_ELEMENT(clip), start_nsec);
+    ges_timeline_element_set_duration(GES_TIMELINE_ELEMENT(clip), duration_nsec);
+    ges_timeline_element_set_inpoint(GES_TIMELINE_ELEMENT(clip), in_point_nsec);
+
+    ret = _mediaeditor_get_layer(editor, layer_id, &layer);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get layer");
+
+    if (!ges_layer_add_clip(layer, clip)) {
+        LOG_ERROR("failed to add clip to layer");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    ret = _mediaeditor_create_clip_common(editor, clip, MEDIAEDITOR_EFFECT_TYPE_NONE, clip_id);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create clip common");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_remove_clip(mediaeditor_s *editor, unsigned int clip_id)
+{
+    GESClip *clip = NULL;
+    GESLayer *layer = NULL;
+    g_autofree mediaeditor_clip_s *clip_data = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    clip_data = _mediaeditor_find_clip_data(editor->clips, clip_id);
+    RET_VAL_IF(clip_data == NULL || clip_data->clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    clip = clip_data->clip;
+    layer = ges_clip_get_layer(clip);
+    RET_VAL_IF(layer == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to get layer");
+
+    if (!ges_layer_remove_clip(layer, clip)) {
+        LOG_ERROR("failed to remove clip from layer");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    editor->clips = g_list_remove(editor->clips, clip_data);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_split_clip(mediaeditor_s *editor, unsigned int src_clip_id, unsigned int position,
+  unsigned int *new_clip_id)
+{
+    GESClip *clip = NULL;
+    GESClip *clip_new = NULL;
+    mediaeditor_clip_s *clip_data_new = NULL;
+    guint64 position_nsec = 0;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(src_clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    clip = __mediaeditor_find_clip(editor->clips, src_clip_id);
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    position_nsec = MILLI_TO_NANO(position);
+    LOG_DEBUG("src_clip_id[%d], position[%"G_GUINT64_FORMAT"]", src_clip_id, position_nsec);
+
+    clip_new = ges_clip_split(clip, position_nsec);
+    RET_VAL_IF(clip_new == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to split clip");
+
+    clip_data_new = __mediaeditor_create_clip_data(clip_new, MEDIAEDITOR_EFFECT_TYPE_NONE, editor->clip_id);
+
+    /* add new clip to managed clip list. */
+    editor->clips = g_list_append(editor->clips, clip_data_new);
+    *new_clip_id = (editor->clip_id)++;
+
+    __print_clip_data(clip_data_new);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_group_clip(mediaeditor_s *editor, unsigned int *clip_ids, unsigned int size, unsigned int *group_id)
+{
+    GList *list = NULL;
+    GESGroup *group = NULL;
+    mediaeditor_group_s *group_data = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    for (unsigned int i = 0 ; i < size ; i++)
+        RET_VAL_IF(clip_ids[i] >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    list = __mediaeditor_peek_clips(editor->clips, clip_ids, size);
+    RET_VAL_IF(list == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to get clip from list");
+
+    group = GES_GROUP(ges_container_group(list));
+    group_data = __mediaeditor_create_group_data(group, editor->group_id);
+
+    editor->groups = g_list_append(editor->groups, group_data);
+    *group_id = (editor->group_id)++;
+
+    g_list_free(list);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_ungroup_clip(mediaeditor_s *editor, unsigned int group_id, unsigned int **clip_ids, unsigned int *size)
+{
+    GESGroup *group = NULL;
+    GList *clips = NULL;
+    int number_of_clips = 0;
+    unsigned int *ids  = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(group_id <= 0 || group_id >= editor->group_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid group id");
+
+    group = __mediaeditor_find_group(editor->groups, group_id);
+    RET_VAL_IF(group == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid group id");
+
+    clips = ges_container_ungroup(GES_CONTAINER(group), FALSE);
+    RET_VAL_IF(clips == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to ungroup");
+
+    number_of_clips = g_list_length(clips);
+    LOG_DEBUG("The ungrouped clips are %d", number_of_clips);
+
+    ids = (unsigned int *)malloc(sizeof(unsigned int) * number_of_clips);
+    RET_VAL_IF(ids == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to alloc ids");
+
+    memset(ids, 0, sizeof(unsigned int) * number_of_clips);
+
+    __mediaeditor_find_id(editor, clips, ids);
+
+    *clip_ids = ids;
+    *size = number_of_clips;
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_move_clip_layer(mediaeditor_s *editor, unsigned int clip_id, unsigned int layer_priority)
+{
+    GESLayer *layer = NULL;
+    GESClip *clip = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    layer = ges_timeline_get_layer(editor->gst.timeline, layer_priority);
+    RET_VAL_IF(layer == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid layer id");
+
+    clip = __mediaeditor_find_clip(editor->clips, clip_id);
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    if (!ges_clip_move_to_layer(clip, layer)) {
+        LOG_ERROR("failed to move layer");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_get_clip_start(mediaeditor_s *editor, unsigned int clip_id, unsigned int *start)
+{
+    GESClip *clip = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    clip = __mediaeditor_find_clip(editor->clips, clip_id);
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    *start = NANO_TO_MILLI(ges_timeline_element_get_start(GES_TIMELINE_ELEMENT(clip)));
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_set_clip_start(mediaeditor_s *editor, unsigned int clip_id, unsigned int start)
+{
+    GESClip *clip = NULL;
+    guint64 start_nsec = 0;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    clip = __mediaeditor_find_clip(editor->clips, clip_id);
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    start_nsec = MILLI_TO_NANO(start);
+    LOG_DEBUG("clip_id[%d], start[%"G_GUINT64_FORMAT"]", clip_id, start_nsec);
+
+    if (!ges_timeline_element_set_start(GES_TIMELINE_ELEMENT(clip), start_nsec)) {
+        LOG_ERROR("failed to set start");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_get_clip_duration(mediaeditor_s *editor, unsigned int clip_id, unsigned int *duration)
+{
+    GESClip *clip = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    clip = __mediaeditor_find_clip(editor->clips, clip_id);
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    *duration = NANO_TO_MILLI(ges_timeline_element_get_duration(GES_TIMELINE_ELEMENT(clip)));
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_set_clip_duration(mediaeditor_s *editor, unsigned int clip_id, unsigned int duration)
+{
+    GESClip *clip = NULL;
+    guint64 duration_nsec = 0;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    clip = __mediaeditor_find_clip(editor->clips, clip_id);
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    duration_nsec = MILLI_TO_NANO(duration);
+    LOG_DEBUG("clip_id[%d], duration[%"G_GUINT64_FORMAT"]", clip_id, duration_nsec);
+
+    if (!ges_timeline_element_set_duration(GES_TIMELINE_ELEMENT(clip), duration_nsec)) {
+        LOG_ERROR("failed to set duration");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_get_clip_in_point(mediaeditor_s *editor, unsigned int clip_id, unsigned int *in_point)
+{
+    GESClip *clip = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    clip = __mediaeditor_find_clip(editor->clips, clip_id);
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    *in_point = NANO_TO_MILLI(ges_timeline_element_get_inpoint(GES_TIMELINE_ELEMENT(clip)));
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_set_clip_in_point(mediaeditor_s *editor, unsigned int clip_id, unsigned int in_point)
+{
+    GESClip *clip = NULL;
+    guint64 in_point_nsec = 0;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    clip = __mediaeditor_find_clip(editor->clips, clip_id);
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+
+    in_point_nsec = MILLI_TO_NANO(in_point);
+    LOG_DEBUG("clip_id[%d], in_point[%"G_GUINT64_FORMAT"]", clip_id, in_point_nsec);
+
+    if (!ges_timeline_element_set_inpoint(GES_TIMELINE_ELEMENT(clip), in_point_nsec)) {
+        LOG_ERROR("failed to set in_point");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_get_clip_resolution(mediaeditor_s *editor, unsigned int clip_id,
+  unsigned int *width, unsigned int *height)
+{
+    GESClip *clip = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid id");
+
+    clip = __mediaeditor_find_clip(editor->clips, clip_id);
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+    RET_VAL_IF(!__has_video_track(clip), MEDIAEDITOR_ERROR_INVALID_OPERATION, "Not video clip");
+
+    __mediaeditor_add_child_properties_resolution(clip);
+
+    ges_timeline_element_get_child_properties(GES_TIMELINE_ELEMENT(clip), "width", width, "height", height, NULL);
+
+    LOGD("width:%d, height:%d", *width, *height);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_set_clip_resolution(mediaeditor_s *editor, unsigned int clip_id,
+  unsigned int width, unsigned int height)
+{
+    GESClip *clip = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid id");
+
+    clip = __mediaeditor_find_clip(editor->clips, clip_id);
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+    RET_VAL_IF(!__has_video_track(clip), MEDIAEDITOR_ERROR_INVALID_OPERATION, "Not video clip");
+
+    LOGD("width:%d, height:%d", width, height);
+
+    __mediaeditor_add_child_properties_resolution(clip);
+
+    ges_timeline_element_set_child_properties(GES_TIMELINE_ELEMENT(clip), "width", width, "height", height, NULL);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_get_clip_volume(mediaeditor_s *editor, unsigned int clip_id, double *volume)
+{
+    GESClip *clip = NULL;
+    GValue val = { 0 };
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    clip = __mediaeditor_find_clip(editor->clips, clip_id);
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+    RET_VAL_IF(!__has_audio_track(clip), MEDIAEDITOR_ERROR_INVALID_OPERATION, "Not audio clip");
+
+    g_value_init(&val, G_TYPE_DOUBLE);
+    if (!ges_timeline_element_get_child_property(GES_TIMELINE_ELEMENT(clip), "volume", &val)) {
+        LOG_ERROR("failed to get volume");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    *volume = g_value_get_double(&val);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_set_clip_volume(mediaeditor_s *editor, unsigned int clip_id, double volume)
+{
+    GESClip *clip = NULL;
+    GValue val = { 0 };
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    clip = __mediaeditor_find_clip(editor->clips, clip_id);
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+    RET_VAL_IF(!__has_audio_track(clip), MEDIAEDITOR_ERROR_INVALID_OPERATION, "Not audio clip");
+
+    g_value_init(&val, G_TYPE_DOUBLE);
+    g_value_set_double(&val, volume);
+    if (!ges_timeline_element_set_child_property(GES_TIMELINE_ELEMENT(clip), "volume", &val)) {
+        LOG_ERROR("failed to set volume");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
diff --git a/src/media_editor_display.c b/src/media_editor_display.c
new file mode 100644 (file)
index 0000000..d57219d
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <mm_error.h>
+#include "media_editor.h"
+#include "media_editor_private.h"
+#include <gst/video/videooverlay.h>
+
+static int __set_evas_display(mediaeditor_display_s *display)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    RET_VAL_IF(display == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "display is NULL");
+
+    ret = mm_display_interface_set_display_mainloop_sync(display->mm_display, MM_DISPLAY_TYPE_EVAS, display->object, NULL);
+    RET_VAL_IF(ret != MM_ERROR_NONE, MEDIAEDITOR_ERROR_INVALID_OPERATION,
+        "failed to mm_display_interface_set_display_mainloop_sync()");
+
+    ret = mm_display_interface_evas_set_mode(display->mm_display, 0);
+    RET_VAL_IF(ret != MM_ERROR_NONE, MEDIAEDITOR_ERROR_INVALID_OPERATION,
+        "failed to mm_display_interface_evas_set_mode()");
+
+    ret = mm_display_interface_evas_set_rotation(display->mm_display, 0);
+    RET_VAL_IF(ret != MM_ERROR_NONE, MEDIAEDITOR_ERROR_INVALID_OPERATION,
+        "failed to mm_display_interface_evas_set_rotation()");
+
+    ret = mm_display_interface_evas_set_visible(display->mm_display, true);
+    RET_VAL_IF(ret != MM_ERROR_NONE, MEDIAEDITOR_ERROR_INVALID_OPERATION,
+        "failed to mm_display_interface_evas_set_visible()");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+static int __set_wl_sink(mediaeditor_s *_editor)
+{
+    GstElement *videosink = NULL;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(_editor->gst.pipeline == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "pipeline is NULL");
+
+    videosink = gst_element_factory_make("tizenwlsink", NULL);
+    RET_VAL_IF(videosink == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create element");
+
+    ges_pipeline_preview_set_video_sink(_editor->gst.pipeline, videosink);
+
+    gst_video_overlay_set_wl_window_wl_surface_id(GST_VIDEO_OVERLAY(videosink), _editor->display->overlay_surface_id);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+static int __set_overlay_display(mediaeditor_s *_editor, mediaeditor_display_s *display)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    mm_display_type_e type = MM_DISPLAY_TYPE_OVERLAY;
+
+    RET_VAL_IF(display == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "display is NULL");
+
+    if (display->type == MEDIAEDITOR_DISPLAY_TYPE_ECORE)
+        type = MM_DISPLAY_TYPE_OVERLAY_EXT;
+
+    ret = mm_display_interface_set_display_mainloop_sync(display->mm_display, type, display->object, &display->overlay_surface_id);
+    RET_VAL_IF(ret != MM_ERROR_NONE, MEDIAEDITOR_ERROR_INVALID_OPERATION,
+        "failed to mm_display_interface_set_display_mainloop_sync()");
+
+    ret = __set_wl_sink(_editor);
+    RET_VAL_IF(ret != MM_ERROR_NONE, ret, "failed to set wayland sink");
+
+    LOG_INFO("overlay_surface_id[%d]", display->overlay_surface_id);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+static int __init_display(mediaeditor_display_s **display)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    RET_VAL_IF(*display != NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION,
+        "display is already initialized.");
+
+    mediaeditor_display_s *_display = g_new0(mediaeditor_display_s, 1);
+
+    g_mutex_init(&_display->mutex);
+
+    if ((ret = mm_display_interface_init(&_display->mm_display)) != MM_ERROR_NONE) {
+        LOG_ERROR("failed to init display. (0x%x)", ret);
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    LOG_DEBUG("initialized display[%p], mm_display[%p]", _display, _display->mm_display);
+
+    *display = _display;
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_set_display(mediaeditor_h editor, mediaeditor_display_type_e type, mediaeditor_display_h object)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+    mediaeditor_s *_editor = (mediaeditor_s*)editor;
+    mediaeditor_display_s *_display = _editor->display;
+
+    RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    if (_display == NULL) {
+        if ((ret = __init_display(&_display)) != MEDIAEDITOR_ERROR_NONE) {
+            LOG_ERROR("failed to initialize display[0x%x]", ret);
+            return ret;
+        }
+    }
+
+    LOG_DEBUG("_display[%p]", _display);
+    LOG_DEBUG("mutex[%p]", &_display->mutex);
+
+    locker = g_mutex_locker_new(&_display->mutex);
+
+    _display->type = type;
+    _display->object = object;
+
+    LOG_DEBUG("display type : %d", type);
+    switch (type) {
+    case MEDIAEDITOR_DISPLAY_TYPE_OVERLAY:
+        ret = __set_overlay_display(_editor, _display);
+        break;
+    case MEDIAEDITOR_DISPLAY_TYPE_EVAS:
+        ret = __set_evas_display(_display);
+        break;
+    case MEDIAEDITOR_DISPLAY_TYPE_ECORE:
+        ret = __set_overlay_display(_editor, _display);
+        break;
+    default:
+        LOG_DEBUG("display type none");
+        break;
+    }
+
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set display[%d]", type);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+void _mediaeditor_release_display(mediaeditor_display_s *display)
+{
+    RET_IF(display == NULL, "display is NULL");
+
+    g_mutex_lock(&display->mutex);
+
+    if (display->mm_display) {
+        LOG_DEBUG("deinit display->mm_display[%p]", display->mm_display);
+        mm_display_interface_deinit(display->mm_display);
+        display->mm_display = NULL;
+    }
+
+    g_mutex_unlock(&display->mutex);
+    g_mutex_clear(&display->mutex);
+
+    LOG_DEBUG("free display[%p]", display);
+
+    g_free(display);
+}
diff --git a/src/media_editor_effect.c b/src/media_editor_effect.c
new file mode 100644 (file)
index 0000000..1f9f46b
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "media_editor.h"
+#include "media_editor_private.h"
+
+static gchar *__get_effect_name(mediaeditor_effect_type_e type)
+{
+    switch (type) {
+    case MEDIAEDITOR_EFFECT_VIDEO_TYPE_EDGETV:
+        return g_strdup("edgetv");
+    case MEDIAEDITOR_EFFECT_VIDEO_TYPE_AGINGTV:
+        return g_strdup("agingtv");
+    case MEDIAEDITOR_EFFECT_VIDEO_TYPE_DICETV:
+        return g_strdup("dicetv");
+    case MEDIAEDITOR_EFFECT_VIDEO_TYPE_WARPTV:
+        return g_strdup("warptv");
+    case MEDIAEDITOR_EFFECT_VIDEO_TYPE_SHAGADELICTV:
+        return g_strdup("shagadelictv");
+    case MEDIAEDITOR_EFFECT_VIDEO_TYPE_VERTIGOTV:
+        return g_strdup("vertigotv");
+    case MEDIAEDITOR_EFFECT_VIDEO_TYPE_REVTV:
+        return g_strdup("revtv");
+    case MEDIAEDITOR_EFFECT_VIDEO_TYPE_QUARKTV:
+        return g_strdup("quarktv");
+    case MEDIAEDITOR_EFFECT_VIDEO_TYPE_OPTV:
+        return g_strdup("optv");
+    case MEDIAEDITOR_EFFECT_VIDEO_TYPE_RADIOACTV:
+        return g_strdup("radioactv");
+    case MEDIAEDITOR_EFFECT_VIDEO_TYPE_STREAKTV:
+        return g_strdup("streaktv");
+    case MEDIAEDITOR_EFFECT_VIDEO_TYPE_RIPPLETV:
+        return g_strdup("rippletv");
+    case MEDIAEDITOR_EFFECT_AUDIO_TYPE_ECHO:
+        return g_strdup("audioecho");
+    default:
+        LOG_ERROR("Not supported effect");
+        break;
+    }
+
+    return NULL;
+}
+
+static GstTimedValue *__new_timed_value(GstClockTime time, gdouble val)
+{
+    GstTimedValue *tmval = g_new0(GstTimedValue, 1);
+
+    tmval->value = val;
+    tmval->timestamp = time;
+
+    return tmval;
+}
+
+static int __keep_audio_fade_effect(mediaeditor_s *editor, unsigned int start, unsigned int duration, bool is_fade_in)
+{
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    if (is_fade_in) {
+        LOG_DEBUG("FADE IN, start[%"G_GUINT64_FORMAT"], end[%"G_GUINT64_FORMAT"]",
+            start * GST_MSECOND, (start + duration) * GST_MSECOND);
+        editor->audio_fade_effects = g_slist_prepend(editor->audio_fade_effects, __new_timed_value(start * GST_MSECOND, 0.0));
+        editor->audio_fade_effects = g_slist_prepend(editor->audio_fade_effects, __new_timed_value((start + 100) * GST_MSECOND, 0.0));
+        editor->audio_fade_effects = g_slist_prepend(editor->audio_fade_effects, __new_timed_value((start + duration) * GST_MSECOND, 1.0));
+    } else {
+        LOG_DEBUG("FADE OUT, start[%"G_GUINT64_FORMAT"], end[%"G_GUINT64_FORMAT"]",
+            start * GST_MSECOND, (start + duration) * GST_MSECOND);
+        editor->audio_fade_effects = g_slist_prepend(editor->audio_fade_effects, __new_timed_value(start * GST_MSECOND, 1.0));
+        editor->audio_fade_effects = g_slist_prepend(editor->audio_fade_effects, __new_timed_value((start + duration - 100) * GST_MSECOND, 0.0));
+        editor->audio_fade_effects = g_slist_prepend(editor->audio_fade_effects, __new_timed_value((start + duration) * GST_MSECOND, 0.0));
+    }
+
+    RET_VAL_IF(editor->audio_fade_effects == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to prepend fade effect");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_add_transition(mediaeditor_s *editor, mediaeditor_transition_type_e type, unsigned int layer_id,
+    unsigned int start, unsigned int duration)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    gboolean ges_ret = FALSE;
+    GESLayer *layer = NULL;
+    GESTransitionClip *transition_clip = NULL;
+    guint64 start_nsec = 0;
+    guint64 duration_nsec = 0;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    if (!(transition_clip = ges_transition_clip_new((GESVideoStandardTransitionType)type))) {
+        LOGE("Failed to create transition clip :%d", type);
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    start_nsec = MILLI_TO_NANO(start);
+    duration_nsec = MILLI_TO_NANO(duration);
+
+    LOG_DEBUG("layer_id[%d], layer[%p], start[%"G_GUINT64_FORMAT"], duration[%"G_GUINT64_FORMAT"]",
+        layer_id, layer, start_nsec, duration_nsec);
+
+    ges_ret = ges_timeline_element_set_start(GES_TIMELINE_ELEMENT(transition_clip), start_nsec);
+    RET_VAL_IF(!ges_ret, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to set start");
+    ges_ret = ges_timeline_element_set_duration(GES_TIMELINE_ELEMENT(transition_clip), duration_nsec);
+    RET_VAL_IF(!ges_ret, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to set duration");
+
+    ret = _mediaeditor_get_layer(editor, layer_id, &layer);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get layer");
+
+    ges_layer_add_clip(layer, GES_CLIP(transition_clip));
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+static int __mediaeditor_create_effect_clip(mediaeditor_s *editor, mediaeditor_effect_type_e type, unsigned int layer_id,
+    unsigned int start, unsigned int duration, unsigned int *effect_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    gboolean ges_ret = FALSE;
+    GESClip *clip = NULL;
+    GESLayer *layer = NULL;
+    g_autofree gchar *effect_name = NULL;
+    guint64 start_nsec = 0;
+    guint64 duration_nsec = 0;
+
+    effect_name = __get_effect_name(type);
+    RET_VAL_IF(effect_name == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to get effect name");
+
+    clip = GES_CLIP(ges_effect_clip_new(effect_name, NULL));
+    RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create effect clip");
+
+    start_nsec = MILLI_TO_NANO(start);
+    duration_nsec = MILLI_TO_NANO(duration);
+
+    LOG_DEBUG("start[%"G_GUINT64_FORMAT"], duration[%"G_GUINT64_FORMAT"]", start_nsec, duration_nsec);
+
+    ges_ret = ges_timeline_element_set_start(GES_TIMELINE_ELEMENT(clip), start_nsec);
+    RET_VAL_IF(!ges_ret, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to set start");
+    ges_ret = ges_timeline_element_set_duration(GES_TIMELINE_ELEMENT(clip), duration_nsec);
+    RET_VAL_IF(!ges_ret, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to set duration");
+
+    ret = _mediaeditor_get_layer(editor, layer_id, &layer);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        LOG_ERROR("failed to get layer");
+        g_object_unref(clip);
+        return ret;
+    }
+
+    if (!ges_layer_add_clip(layer, clip)) {
+        LOG_ERROR("failed to add clip to layer");
+        g_object_unref(clip);
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    LOG_INFO("add effect layer[%p] clip[%p]", layer, clip);
+
+    ret = _mediaeditor_create_clip_common(editor, clip, type, effect_id);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        LOG_ERROR("failed to create clip common");
+        g_object_unref(clip);
+        return ret;
+    }
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+static int __mediaeditor_create_effect(mediaeditor_s *editor, mediaeditor_effect_type_e type,
+    unsigned int start, unsigned int duration, unsigned int *effect_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    switch (type) {
+    case MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN:
+        ret = __keep_audio_fade_effect(editor, start, duration, true);
+        break;
+    case MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT:
+        ret = __keep_audio_fade_effect(editor, start, duration, false);
+        break;
+    default:
+        LOG_ERROR("There's no matched effect");
+        ret = MEDIAEDITOR_ERROR_INVALID_PARAMETER;
+        break;
+    }
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to apply audio effect");
+
+    ret = _mediaeditor_create_clip_common(editor, NULL, type, effect_id);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create clip common");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_add_effect(mediaeditor_s *editor, mediaeditor_effect_type_e type, unsigned int layer_id,
+    unsigned int start, unsigned int duration, unsigned int *effect_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    switch (type) {
+    case MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN:
+    case MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT:
+        ret = __mediaeditor_create_effect(editor, type, start, duration, effect_id);
+        break;
+    default:
+        ret = __mediaeditor_create_effect_clip(editor, type, layer_id, start, duration, effect_id);
+        break;
+    }
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create effect");
+
+    return ret;
+}
+
+int _mediaeditor_remove_effect(mediaeditor_s *editor, unsigned int effect_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    mediaeditor_clip_s *clip_data = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    clip_data = _mediaeditor_find_clip_data(editor->clips, effect_id);
+    RET_VAL_IF(clip_data == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id");
+    RET_VAL_IF(clip_data->effect_type == MEDIAEDITOR_EFFECT_TYPE_NONE, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "not effect clip id");
+
+    switch (clip_data->effect_type) {
+    case MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN:
+    case MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT:
+        editor->clips = g_list_remove(editor->clips, clip_data);
+        free(clip_data);
+        break;
+    default:
+        ret = _mediaeditor_remove_clip(editor, effect_id);
+        break;
+    }
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to remove effect");
+
+    return ret;
+}
diff --git a/src/media_editor_ini.c b/src/media_editor_ini.c
new file mode 100644 (file)
index 0000000..c42becb
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <media_editor.h>
+#include <media_editor_private.h>
+
+#define MEDIAEDITOR_INI_PATH SYSCONFDIR"/multimedia/mmfw_media_editor.ini"
+#define DEFAULT_GENERATE_DOT false
+#define DEFAULT_DOT_PATH     "/tmp"
+
+/* categories */
+#define INI_CATEGORY_GENERAL              "general"
+#define INI_CATEGORY_ENCODER              "encoder"
+#define INI_CATEGORY_DECODER              "decoder"
+#define INI_CATEGORY_RESOURCE_ACQUISITION "resource acquisition"
+
+/* items for general */
+#define INI_ITEM_DOT_PATH     "dot path"
+#define INI_ITEM_DOT_GENERATE "generate dot"
+#define INI_ITEM_GST_ARGS     "gstreamer arguments"
+
+/* items for encoder */
+#define INI_ITEM_ENCODER_CONTAINER "container"
+#define INI_ITEM_AUDIO_HW_ENCODER  "audio hw encoder"
+#define INI_ITEM_VIDEO_HW_ENCODER  "video hw encoder"
+
+/* items for decoder */
+#define INI_ITEM_AUDIO_HW_DECODER  "audio hw decoder"
+#define INI_ITEM_VIDEO_HW_DECODER  "video hw decoder"
+
+/* items for resource acquisition */
+#define INI_ITEM_RESOURCE_VIDEO_ENCODER          "video encoder"
+#define INI_ITEM_RESOURCE_VIDEO_DECODER          "video decoder"
+
+#define DEFAULT_RESOURCE_VIDEO_ENCODER_REQUIRED  false
+#define DEFAULT_RESOURCE_VIDEO_DECODER_REQUIRED  false
+
+#define DEFAULT_CONTAINER     "ogg"
+#define DEFAULT_AUDIO_ENCODER "vorbis"
+#define DEFAULT_VIDEO_ENCODER "theora"
+
+typedef enum {
+    INI_ITEM_TYPE_BOOL,
+    INI_ITEM_TYPE_INT,
+    INI_ITEM_TYPE_STRING,
+    INI_ITEM_TYPE_STRINGS
+} ini_item_type_e;
+
+static void __dump_item(const char *prefix_str, ini_item_type_e type, void *item)
+{
+    RET_IF(prefix_str == NULL, "prefix_str is NULL");
+
+    if (item == NULL)
+        return;
+
+    switch (type) {
+    case INI_ITEM_TYPE_BOOL:
+        LOG_INFO("- %-19s = %s", prefix_str, *(bool*)item ? "yes" : "no");
+        break;
+    case INI_ITEM_TYPE_INT:
+        LOG_INFO("- %-19s = %d", prefix_str, *(int*)item);
+        break;
+    case INI_ITEM_TYPE_STRING:
+        LOG_INFO("- %-19s = %s", prefix_str, (const char *)item);
+        break;
+    case INI_ITEM_TYPE_STRINGS: {
+        gchar *joined_str = g_strjoinv(" ", item);
+        LOG_INFO("- %-19s = %s", prefix_str, joined_str);
+        g_free(joined_str);
+        break;
+    }
+    default:
+        LOG_ERROR("not supported type[%d]", type);
+        break;
+    }
+}
+
+static void __dump_ini(mediaeditor_ini_s *ini)
+{
+    RET_IF(ini == NULL, "ini is NULL");
+
+    LOG_INFO("[%s]", INI_CATEGORY_GENERAL);
+    __dump_item(INI_ITEM_DOT_GENERATE, INI_ITEM_TYPE_BOOL, &ini->general.generate_dot);
+    __dump_item(INI_ITEM_DOT_PATH, INI_ITEM_TYPE_STRING, (void *)ini->general.dot_path);
+    __dump_item(INI_ITEM_GST_ARGS, INI_ITEM_TYPE_STRINGS, ini->general.gst_args);
+
+    LOG_INFO("[%s]", INI_CATEGORY_ENCODER);
+    __dump_item(INI_ITEM_ENCODER_CONTAINER, INI_ITEM_TYPE_STRING, (void *)ini->encoder.container);
+    __dump_item(INI_ITEM_AUDIO_HW_ENCODER, INI_ITEM_TYPE_STRING, (void *)ini->encoder.audio_hw_encoder);
+    __dump_item(INI_ITEM_VIDEO_HW_ENCODER, INI_ITEM_TYPE_STRING, (void *)ini->encoder.video_hw_encoder);
+
+    LOG_INFO("[%s]", INI_CATEGORY_DECODER);
+    __dump_item(INI_ITEM_AUDIO_HW_DECODER, INI_ITEM_TYPE_STRING, (void *)ini->decoder.audio_hw_decoder);
+    __dump_item(INI_ITEM_VIDEO_HW_DECODER, INI_ITEM_TYPE_STRING, (void *)ini->decoder.video_hw_decoder);
+
+    LOG_INFO("[%s]", INI_CATEGORY_RESOURCE_ACQUISITION);
+    __dump_item(INI_ITEM_RESOURCE_VIDEO_ENCODER, INI_ITEM_TYPE_STRING, (void *)ini->resource_acquisition.video_encoder);
+    __dump_item(INI_ITEM_RESOURCE_VIDEO_DECODER, INI_ITEM_TYPE_STRING, (void *)ini->resource_acquisition.video_decoder);
+}
+
+static const char* __get_delimiter(const char *ini_path)
+{
+    const char *delimiter = ",";
+
+    if (g_strrstr(ini_path, INI_ITEM_GST_ARGS))
+        delimiter = "|";
+
+    return delimiter;
+}
+
+static void __ini_read_list(dictionary *dict, const char *category, const char *item, gchar ***list)
+{
+    const char *str = NULL;
+    g_autofree gchar *path = NULL;
+    g_autofree gchar *strtmp = NULL;
+
+    if (dict == NULL)
+        return;
+
+    RET_IF(category == NULL, "category is NULL");
+    RET_IF(item == NULL, "item is NULL");
+    RET_IF(list == NULL, "list is NULL");
+
+    path = g_strconcat(category, ":", item, NULL);
+    str = iniparser_getstring(dict, path, NULL);
+    if (str && strlen(str) > 0) {
+        strtmp = g_strdup(str);
+        g_strstrip(strtmp);
+        *list = g_strsplit(strtmp, __get_delimiter((const char *)path), 10);
+    }
+
+    LOG_DEBUG("[%s] %s", path, strtmp ? strtmp : "none");
+}
+
+static const char* __ini_get_string(dictionary *dict, const char *category, const char *item, const char *default_value)
+{
+    g_autofree gchar *path = NULL;
+    const char *ret_val = NULL;
+
+    if (dict == NULL)
+        return default_value;
+
+    RET_VAL_IF(category == NULL, default_value, "category is NULL");
+    RET_VAL_IF(item == NULL, default_value, "item is NULL");
+
+    path = g_strconcat(category, ":", item, NULL);
+    ret_val = iniparser_getstring(dict, path, default_value);
+
+    if (ret_val != NULL && strlen(ret_val) == 0)
+        return default_value;
+
+    return ret_val;
+}
+
+static bool __ini_get_boolean(dictionary *dict, const char *category, const char *item, bool default_value)
+{
+    g_autofree gchar *path = NULL;
+    bool ret_val = false;
+
+    if (dict == NULL)
+        return default_value;
+    RET_VAL_IF(category == NULL, default_value, "category is NULL");
+    RET_VAL_IF(item == NULL, default_value, "item is NULL");
+
+    path = g_strconcat(category, ":", item, NULL);
+    ret_val = (bool)iniparser_getboolean(dict, path, default_value);
+
+    return ret_val;
+}
+
+int _load_ini(mediaeditor_s *editor)
+{
+    LOG_DEBUG("Enter [%p]", editor);
+    mediaeditor_ini_s *ini = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    memset(&editor->ini, 0, sizeof(mediaeditor_ini_s));
+    ini = &editor->ini;
+
+    ini->dict = iniparser_load(MEDIAEDITOR_INI_PATH);
+    if (!ini->dict)
+        LOG_WARNING("could not open ini[%s], use default values", MEDIAEDITOR_INI_PATH);
+
+    /* general */
+    ini->general.generate_dot = __ini_get_boolean(ini->dict, INI_CATEGORY_GENERAL, INI_ITEM_DOT_GENERATE, DEFAULT_GENERATE_DOT);
+    ini->general.dot_path = __ini_get_string(ini->dict, INI_CATEGORY_GENERAL, INI_ITEM_DOT_PATH, DEFAULT_DOT_PATH);
+    if (ini->general.generate_dot) {
+        LOG_INFO("dot file will be stored in [%s]", ini->general.dot_path);
+        g_setenv("GST_DEBUG_DUMP_DOT_DIR", ini->general.dot_path, FALSE);
+    }
+    __ini_read_list(ini->dict, INI_CATEGORY_GENERAL, INI_ITEM_GST_ARGS, &ini->general.gst_args);
+
+    /* encoder */
+    ini->encoder.container = __ini_get_string(ini->dict, INI_CATEGORY_ENCODER, INI_ITEM_ENCODER_CONTAINER, DEFAULT_CONTAINER);
+    ini->encoder.audio_hw_encoder = __ini_get_string(ini->dict, INI_CATEGORY_ENCODER, INI_ITEM_AUDIO_HW_ENCODER, NULL);
+    ini->encoder.video_hw_encoder = __ini_get_string(ini->dict, INI_CATEGORY_ENCODER, INI_ITEM_VIDEO_HW_ENCODER, NULL);
+
+    /* decoder */
+    ini->decoder.audio_hw_decoder = __ini_get_string(ini->dict, INI_CATEGORY_DECODER, INI_ITEM_AUDIO_HW_DECODER, NULL);
+    ini->decoder.video_hw_decoder = __ini_get_string(ini->dict, INI_CATEGORY_DECODER, INI_ITEM_VIDEO_HW_DECODER, NULL);
+
+    /* resource acqusition */
+    ini->resource_acquisition.video_encoder = __ini_get_boolean(ini->dict, INI_CATEGORY_RESOURCE_ACQUISITION,
+        INI_ITEM_RESOURCE_VIDEO_ENCODER, DEFAULT_RESOURCE_VIDEO_ENCODER_REQUIRED);
+    ini->resource_acquisition.video_decoder = __ini_get_boolean(ini->dict, INI_CATEGORY_RESOURCE_ACQUISITION,
+        INI_ITEM_RESOURCE_VIDEO_DECODER, DEFAULT_RESOURCE_VIDEO_DECODER_REQUIRED);
+
+    __dump_ini(ini);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+void _unload_ini(mediaeditor_s *editor)
+{
+    LOG_DEBUG("Enter [%p]", editor);
+
+    RET_IF(editor == NULL, "editor is NULL");
+    RET_IF(editor->ini.dict == NULL, "ini.dict is NULL");
+
+    g_strfreev(editor->ini.general.gst_args);
+    editor->ini.general.gst_args = NULL;
+
+    iniparser_freedict(editor->ini.dict);
+    LOG_DEBUG("ini instance[%p] is freed", editor->ini.dict);
+    editor->ini.dict = NULL;
+}
+
+bool _is_resource_required(mediaeditor_ini_s *ini)
+{
+    RET_VAL_IF(ini == NULL, false, "ini is NULL");
+
+    return (ini->resource_acquisition.video_encoder ||
+            ini->resource_acquisition.video_decoder);
+}
diff --git a/src/media_editor_layer.c b/src/media_editor_layer.c
new file mode 100644 (file)
index 0000000..930791d
--- /dev/null
@@ -0,0 +1,436 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "media_editor.h"
+#include "media_editor_private.h"
+#include <limits.h>
+
+static void __print_layer_data(mediaeditor_layer_s *layer_data)
+{
+#ifdef PRINT_LAYER_DEBUG_INFO
+    RET_IF(layer_data == NULL, "layer_data is null");
+    LOG_DEBUG("layer_data[%p], layer[%p], id[%d]", layer_data, layer_data->layer, layer_data->id);
+#endif
+}
+
+static void __print_layer_priorities(GESTimeline *timeline)
+{
+#ifdef PRINT_LAYER_DEBUG_INFO
+    guint priority = 0;
+
+    RET_IF(timeline == NULL, "timeline is NULL");
+
+    for (GList *layer_iter = timeline->layers; layer_iter; layer_iter = layer_iter->next) {
+        GESLayer *layer = GES_LAYER(layer_iter->data);
+
+        priority = ges_layer_get_priority(layer);
+        LOG_DEBUG("layer[%p] layer priority[%d]", layer, priority);
+    }
+#endif
+}
+
+static gint __mediaeditor_comparefunc_find_layer(gconstpointer a, gconstpointer b)
+{
+    mediaeditor_layer_s *layer_data = (mediaeditor_layer_s *)a;
+    unsigned int *id = (unsigned int *)b;
+
+    if (layer_data == NULL || layer_data->id != *id)
+        return -1;
+
+    LOG_DEBUG("layer id[%d], layer[%p]", *id, layer_data->layer);
+
+    return 0;
+}
+
+static mediaeditor_layer_s *__mediaeditor_find_layer_data(GList *lists, unsigned int id)
+{
+    GList *list = NULL;
+
+    list = g_list_find_custom(lists, (gconstpointer)&id, __mediaeditor_comparefunc_find_layer);
+    RET_VAL_IF(list == NULL, NULL, "failed to find matched layer");
+
+    __print_layer_data(list->data);
+
+    return list->data;
+}
+
+static GESLayer *__mediaeditor_find_layer(GList *lists, unsigned int id)
+{
+    mediaeditor_layer_s *layer_data = NULL;
+
+    layer_data = __mediaeditor_find_layer_data(lists, id);
+    RET_VAL_IF(layer_data == NULL, NULL, "failed to find layer data");
+
+    return layer_data->layer;
+}
+
+static gint __mediaeditor_comparefunc_find_id(gconstpointer a, gconstpointer b)
+{
+    mediaeditor_layer_s *layer_data = (mediaeditor_layer_s *)a;
+    GESLayer *layer = (GESLayer *)b;
+
+    if (layer_data == NULL || layer_data->layer != layer)
+        return -1;
+
+    LOG_DEBUG("layer[%p], layer id[%d]", layer_data->layer, layer_data->id);
+
+    return 0;
+}
+
+static unsigned int __mediaeditor_find_layer_id(GList *lists, GESLayer *layer)
+{
+    mediaeditor_layer_s *layer_data = NULL;
+    GList *list = NULL;
+
+    list = g_list_find_custom(lists, (gconstpointer)layer, __mediaeditor_comparefunc_find_id);
+    RET_VAL_IF(list == NULL, INT_MAX, "failed to find matched layer");
+
+    layer_data = (mediaeditor_layer_s *)list->data;
+    RET_VAL_IF(layer_data == NULL, INT_MAX, "failed to find layer");
+
+    return layer_data->id;
+}
+
+static mediaeditor_layer_s * __mediaeditor_create_layer_data(GESLayer *layer, unsigned int id)
+{
+    mediaeditor_layer_s *data = NULL;
+
+    data = g_new0(mediaeditor_layer_s, 1);
+
+    data->layer = layer;
+    data->id = id;
+
+    return data;
+}
+
+static int __resync_layers(mediaeditor_s *editor, unsigned int layer_priority, bool *is_resynced)
+{
+    /* It should be done in GES but it's not curreltly. This is workaround.  */
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    GESTimeline *timeline = editor->gst.timeline;
+    GESLayer *layer = NULL;
+    unsigned int layer_id = 0;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(is_resynced == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "is_resynced is NULL");
+    *is_resynced = false;
+
+    LOG_DEBUG("layer_priority[%d]", layer_priority);
+
+    GList *list = ges_timeline_get_layers(editor->gst.timeline);
+    if (list == NULL) {
+        LOG_DEBUG("There's no layers to resync");
+        return MEDIAEDITOR_ERROR_NONE;
+    }
+
+    ret = _mediaeditor_get_layer_id(editor, layer_priority + 1, &layer_id);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret , "failed to get layer id");
+
+    ret = _mediaeditor_get_layer(editor, layer_id, &layer);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        LOG_DEBUG("There's nothing to resync");
+        return MEDIAEDITOR_ERROR_NONE;
+    }
+    LOG_DEBUG("priority + 1[%d], layer id[%d], layer[%p]", layer_priority + 1, layer_id, layer);
+
+    /* ges_timeline_move_layer() resync priority internally. So we don't need to move layer anymore */
+    if (!ges_timeline_move_layer(timeline, layer, layer_priority)) {
+        LOG_DEBUG("failed to move layer");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    *is_resynced = true;
+    LOG_DEBUG("layer is resynced");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+static void __invoke_layer_priority_changed_cb(mediaeditor_s *editor, mediaeditor_layer_info_s *layer_info, unsigned int size)
+{
+    RET_IF(editor == NULL, "editor is NULL");
+
+    if (editor->layer_priority_changed_cb.callback) {
+        LOG_DEBUG(">>> callback[%p] user_data[%p]", editor->layer_priority_changed_cb.callback, editor->layer_priority_changed_cb.user_data);
+        ((mediaeditor_layer_priority_changed_cb)(editor->layer_priority_changed_cb.callback))(layer_info, size, editor->layer_priority_changed_cb.user_data);
+        LOG_DEBUG("<<< end of the callback");
+    }
+}
+
+static int __create_layer_info(mediaeditor_s *editor, mediaeditor_layer_info_s **layer_info, unsigned int *size)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int lowest_priority = 0;
+    unsigned int layer_id = 0;
+    unsigned int number_of_layer = 0;
+    mediaeditor_layer_info_s *layer_info_tmp = NULL;
+
+    ret = _mediaeditor_get_layer_lowest_priority(editor, &lowest_priority);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get lowest layer priority");
+
+    number_of_layer = lowest_priority + 1;
+    layer_info_tmp = (mediaeditor_layer_info_s *)malloc(sizeof(mediaeditor_layer_info_s) * number_of_layer);
+    RET_VAL_IF(layer_info_tmp == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to alloc layer info");
+
+    memset(layer_info_tmp, 0, sizeof(mediaeditor_layer_info_s) * number_of_layer);
+
+    for (unsigned int i = 0 ; i < number_of_layer ; i++) {
+        ret = _mediaeditor_get_layer_id(editor, i, &layer_id);
+        RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get layer id");
+
+        layer_info_tmp[i].id = layer_id;
+        layer_info_tmp[i].priority = i;
+    }
+
+    *layer_info = layer_info_tmp;
+    *size = number_of_layer;
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+static int __mediaeditor_create_layer_common(mediaeditor_s *editor, GESLayer *layer, unsigned int *layer_id)
+{
+    mediaeditor_layer_s *layer_data = NULL;
+
+    layer_data = __mediaeditor_create_layer_data(layer, editor->layer_id);
+    RET_VAL_IF(layer_data == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "layer_data is null");
+
+    __print_layer_data(layer_data);
+
+    editor->layers = g_list_append(editor->layers, layer_data);
+    RET_VAL_IF(editor->layers == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "list is null");
+
+    *layer_id = (editor->layer_id)++;
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_get_layer(mediaeditor_s *editor, unsigned int layer_id, GESLayer **layer)
+{
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(layer == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer is NULL");
+
+    *layer = __mediaeditor_find_layer(editor->layers, layer_id);
+    RET_VAL_IF(*layer == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid layer id");
+
+    LOG_DEBUG("layer[%p]", *layer);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_add_layer(mediaeditor_s *editor, unsigned int *layer_id, unsigned int *layer_priority)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    GESLayer *layer = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(layer_id == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_id is NULL");
+    RET_VAL_IF(layer_priority == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_priority is NULL");
+
+    layer = ges_timeline_append_layer(editor->gst.timeline);
+    RET_VAL_IF(layer == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to append layer");
+
+    *layer_priority = ges_layer_get_priority(layer);
+
+    LOG_DEBUG("added layer[%p], layer_priority[%d]", layer, *layer_priority);
+
+    ret = __mediaeditor_create_layer_common(editor, layer, layer_id);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create layer common");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_remove_layer(mediaeditor_s *editor, unsigned int layer_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    GESLayer *layer = NULL;
+    g_autofree mediaeditor_layer_s *layer_data = NULL;
+    unsigned int layer_priority = 0;
+    unsigned int number_of_layer = 0;
+    mediaeditor_layer_info_s *layer_info = NULL;
+    bool is_resynced = false;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    layer_data = __mediaeditor_find_layer_data(editor->layers, layer_id);
+    RET_VAL_IF(layer_data == NULL || layer_data->layer == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid layer id");
+    layer = layer_data->layer;
+
+    __print_layer_priorities(editor->gst.timeline);
+
+    layer_priority = ges_layer_get_priority(layer);
+    LOG_DEBUG("layer_priority to remove [%d]", layer_priority);
+
+    if (!ges_timeline_remove_layer(editor->gst.timeline, layer)) {
+        LOG_ERROR("failed to remove layer frome timeline");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    __print_layer_priorities(editor->gst.timeline);
+
+    editor->layers = g_list_remove(editor->layers, layer_data);
+
+    ret = __resync_layers(editor, layer_priority, &is_resynced);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to resync layer");
+
+    __print_layer_priorities(editor->gst.timeline);
+
+    if (is_resynced)
+    {
+        ret = __create_layer_info(editor, &layer_info, &number_of_layer);
+        RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create layer info");
+
+        __invoke_layer_priority_changed_cb(editor, layer_info, number_of_layer);
+    }
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_move_layer(mediaeditor_s *editor, unsigned int layer_id, unsigned int layer_priority)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    GESLayer *layer = NULL;
+    gboolean result = FALSE;
+    unsigned int current_priority = 0;
+    unsigned int number_of_layer = 0;
+    mediaeditor_layer_info_s *layer_info = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    ret = _mediaeditor_get_layer(editor, layer_id, &layer);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get layer");
+
+    ret = _mediaeditor_get_layer_priority(editor, layer_id, &current_priority);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "invalid layer id");
+
+    if (current_priority == layer_priority) {
+        LOG_DEBUG("Nothing to move");
+        return MEDIAEDITOR_ERROR_NONE;
+    }
+
+    LOG_DEBUG("move layer ID[%d] -> layer priority[%d]", layer_id, layer_priority);
+
+    __print_layer_priorities(editor->gst.timeline);
+
+    result = ges_timeline_move_layer(editor->gst.timeline, layer, layer_priority);
+    RET_VAL_IF(result == FALSE, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to move layer");
+
+    __print_layer_priorities(editor->gst.timeline);
+
+    ret = __create_layer_info(editor, &layer_info, &number_of_layer);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create layer info");
+
+    __invoke_layer_priority_changed_cb(editor, layer_info, number_of_layer);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_activate_layer(mediaeditor_s *editor, unsigned int layer_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    GESLayer *layer = NULL;
+    GList *tracks = NULL;
+    gboolean result = FALSE;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    tracks = ges_timeline_get_tracks(editor->gst.timeline);
+    RET_VAL_IF(tracks == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "tracks is NULL");
+
+    ret = _mediaeditor_get_layer(editor, layer_id, &layer);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get layer");
+
+    result = ges_layer_set_active_for_tracks(layer, true, tracks);
+    RET_VAL_IF(result == FALSE, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to set active");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_deactivate_layer(mediaeditor_s *editor, unsigned int layer_id)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    GESLayer *layer = NULL;
+    GList *tracks = NULL;
+    gboolean result = FALSE;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    tracks = ges_timeline_get_tracks(editor->gst.timeline);
+    RET_VAL_IF(tracks == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "tracks is NULL");
+
+    ret = _mediaeditor_get_layer(editor, layer_id, &layer);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get layer");
+
+    result = ges_layer_set_active_for_tracks(layer, false, tracks);
+    RET_VAL_IF(result == FALSE, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to set active");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_get_layer_priority(mediaeditor_s *editor, unsigned int layer_id, unsigned int *layer_priority)
+{
+    GESLayer *layer = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is null");
+    RET_VAL_IF(layer_priority == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_priority is null");
+
+    layer = __mediaeditor_find_layer(editor->layers, layer_id);
+    RET_VAL_IF(layer == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "failed to get layer");
+
+    *layer_priority = ges_layer_get_priority(layer);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_get_layer_lowest_priority(mediaeditor_s *editor, unsigned int *layer_priority)
+{
+    unsigned int priority_max = 0;
+    unsigned int priority_tmp = 0;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is null");
+    RET_VAL_IF(layer_priority == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_priority is null");
+    RET_VAL_IF(editor->layers == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "there's no layer");
+
+    for (GList *layer_iter = editor->layers; layer_iter; layer_iter = layer_iter->next) {
+        mediaeditor_layer_s *layer_data = (mediaeditor_layer_s *)(layer_iter->data);
+
+        priority_tmp = ges_layer_get_priority(layer_data->layer);
+        if (priority_tmp > priority_max) {
+            priority_max = priority_tmp;
+        }
+    }
+
+    *layer_priority = priority_max;
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_get_layer_id(mediaeditor_s *editor, unsigned int layer_priority, unsigned int *layer_id)
+{
+    GESLayer *layer = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is null");
+    RET_VAL_IF(layer_id == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_id is null");
+
+    LOG_DEBUG("layer priority[%d]", layer_priority);
+
+    layer = ges_timeline_get_layer(editor->gst.timeline, layer_priority);
+    RET_VAL_IF(layer == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invlid layer_priority");
+
+    *layer_id = __mediaeditor_find_layer_id(editor->layers, layer);
+    RET_VAL_IF(*layer_id == INT_MAX, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invlid layer_priority");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
diff --git a/src/media_editor_private.c b/src/media_editor_private.c
new file mode 100644 (file)
index 0000000..a6e06e7
--- /dev/null
@@ -0,0 +1,752 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "media_editor.h"
+#include "media_editor_private.h"
+#include <system_info.h>
+
+#define DEFAULT_DOT_FILE_NAME_PREFIX   "mediaeditor"
+
+static const char* __state_str[] = {
+    [MEDIAEDITOR_STATE_IDLE] = "IDLE",
+    [MEDIAEDITOR_STATE_RENDERING] = "RENDERING",
+    [MEDIAEDITOR_STATE_PREVIEW] = "PREVIEW"
+};
+
+typedef struct _idle_userdata {
+    mediaeditor_s *editor;
+    idle_cb_type_e type;
+    union {
+        mediaeditor_state_e state;
+        mediaeditor_error_e error;
+    } new;
+} idle_userdata_s;
+
+static const char *__get_error_string(mediaeditor_error_e error)
+{
+    switch (error) {
+    case MEDIAEDITOR_ERROR_NONE:
+        return "NONE";
+    case MEDIAEDITOR_ERROR_PERMISSION_DENIED:
+        return "PERMISSION_DENIED";
+    case MEDIAEDITOR_ERROR_INVALID_PARAMETER:
+        return "INVALID_PARAMETER";
+    case MEDIAEDITOR_ERROR_INVALID_OPERATION:
+        return "INVALID_OPERATION";
+    case MEDIAEDITOR_ERROR_INVALID_STATE:
+        return "INVALID_STATE";
+    case MEDIAEDITOR_ERROR_RESOURCE_FAILED:
+        return "RESOURCE_FAILED";
+    case MEDIAEDITOR_ERROR_RESOURCE_CONFLICT:
+        return "RESOURCE_CONFLICT";
+    default:
+        LOG_ERROR_IF_REACHED("invalid error(0x%x)", error);
+        return "(invalid error)";
+    }
+}
+
+static int __create_encoding_profile(mediaeditor_s *editor, const char* path)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    GstEncodingContainerProfile *profile = NULL;
+    GstCaps *container_caps  = NULL;
+    GstCaps *audio_caps  = NULL;
+    GstCaps *video_caps  = NULL;
+    g_autofree gchar *container_mime_type = NULL;
+    g_autofree gchar *audio_mime_type = NULL;
+    g_autofree gchar *video_mime_type = NULL;
+    g_autofree gchar *uri = NULL;
+
+    uri = gst_filename_to_uri(path, NULL);
+    RET_VAL_IF(uri == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "failed to get uri");
+    LOGI("uri : [%s]", uri);
+
+    LOG_INFO("Container : %s", editor->ini.encoder.container);
+    if (!strcmp(editor->ini.encoder.container, CONTAINER_TYPE_MPEG)) {
+        container_mime_type = g_strdup(CONTAINER_MIME_TYPE_MPEG);
+        audio_mime_type = g_strdup(AUDIO_ENCODER_MIME_TYPE_MPEG);
+        video_mime_type = g_strdup(VIDEO_ENCODER_MIME_TYPE_MPEG);
+    } else {
+        /* default is ogg */
+        container_mime_type = g_strdup(CONTAINER_MIME_TYPE_OGG);
+        audio_mime_type = g_strdup(AUDIO_ENCODER_MIME_TYPE_VORBIS);
+        video_mime_type = g_strdup(VIDEO_ENCODER_MIME_TYPE_THEORA);;
+    }
+
+    container_caps = gst_caps_from_string(container_mime_type);
+    RET_VAL_IF(container_caps == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create caps");
+    profile = gst_encoding_container_profile_new(NULL, NULL, container_caps, NULL);
+    if (profile == NULL) {
+        LOG_ERROR("failed to create profile");
+        ret = MEDIAEDITOR_ERROR_INVALID_OPERATION;
+        goto error_container;
+    }
+
+    audio_caps = gst_caps_from_string(audio_mime_type);
+    RET_VAL_IF(audio_caps == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create audio_caps");
+    if (!gst_encoding_container_profile_add_profile(profile, (GstEncodingProfile *)gst_encoding_audio_profile_new(audio_caps, NULL, NULL, 0))) {
+        LOG_ERROR("failed to add audio prifile");
+        ret = MEDIAEDITOR_ERROR_INVALID_OPERATION;
+        g_object_unref(profile);
+        goto error_audio_caps;
+    }
+
+    video_caps = gst_caps_from_string(video_mime_type);
+    RET_VAL_IF(video_caps == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create video_caps");
+    if (!gst_encoding_container_profile_add_profile(profile, (GstEncodingProfile *)gst_encoding_video_profile_new(video_caps, NULL, NULL, 0))) {
+        LOG_ERROR("failed to add audio prifile");
+        ret = MEDIAEDITOR_ERROR_INVALID_OPERATION;
+        g_object_unref(profile);
+        goto error_video_caps;
+    }
+
+    if (!ges_pipeline_set_render_settings(editor->gst.pipeline, uri, (GstEncodingProfile *)profile)) {
+        LOG_ERROR("failed to add audio prifile");
+        ret = MEDIAEDITOR_ERROR_INVALID_OPERATION;
+        g_object_unref(profile);
+    }
+
+error_video_caps:
+    gst_caps_unref(video_caps);
+error_audio_caps:
+    gst_caps_unref(audio_caps);
+error_container:
+    gst_caps_unref(container_caps);
+
+    return ret;
+}
+
+static int __commit_audio_fade_effect(mediaeditor_s *editor)
+{
+    GESTrackElement *track_element = NULL;
+    GstControlSource *control_source = NULL;
+
+    LOG_DEBUG("Enter");
+
+    if (!editor->audio_fade_effects) {
+        LOG_DEBUG("No audio fade effect");
+        return MEDIAEDITOR_ERROR_NONE;
+    }
+
+    GList * tracks = ges_timeline_get_tracks(editor->gst.timeline);
+    for (GList *tmp = tracks; tmp; tmp = tmp->next) {
+        GList *trackelements = ges_track_get_elements(GES_TRACK(tmp->data));
+
+        if (GES_IS_AUDIO_URI_SOURCE(trackelements->data)) {
+            track_element = GES_TRACK_ELEMENT(trackelements->data);
+            break;
+        }
+
+        g_list_free_full(trackelements, gst_object_unref);
+    }
+    RET_VAL_IF(track_element == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to get track element");
+
+    control_source = GST_CONTROL_SOURCE(gst_interpolation_control_source_new());
+    g_object_set(G_OBJECT(control_source), "mode", GST_INTERPOLATION_MODE_LINEAR, NULL);
+
+    if (!gst_timed_value_control_source_set_from_list(GST_TIMED_VALUE_CONTROL_SOURCE(control_source), editor->audio_fade_effects)) {
+        LOG_ERROR("failed to set timed value control source");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    if (!ges_track_element_set_control_source(track_element, control_source, "volume", "direct-absolute")) {
+        LOG_ERROR("failed to set control source to track");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    LOG_DEBUG("Leave");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+static void _generate_dot(mediaeditor_s *editor, const gchar *name)
+{
+    g_autofree gchar *dot_name = NULL;
+
+    RET_IF(editor == NULL, "editor is NULL");
+    RET_IF(editor->gst.pipeline == NULL, "pipeline is NULL");
+
+    LOG_INFO("Enter");
+
+    if (!name)
+        dot_name = g_strdup(DEFAULT_DOT_FILE_NAME_PREFIX);
+    else
+        dot_name = g_strconcat(DEFAULT_DOT_FILE_NAME_PREFIX, ".", name, NULL);
+
+    GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(editor->gst.pipeline), GST_DEBUG_GRAPH_SHOW_ALL, dot_name);
+
+    LOG_INFO("dot file[%s] is generated", dot_name);
+}
+
+static bool __meet_gst_state(mediaeditor_state_e state, GstState gst_state)
+{
+    if ((state == MEDIAEDITOR_STATE_IDLE && gst_state == GST_STATE_READY) ||
+        ((state == MEDIAEDITOR_STATE_PREVIEW || state == MEDIAEDITOR_STATE_RENDERING) && gst_state == GST_STATE_PAUSED))
+        return true;
+
+    return false;
+}
+
+static void __invoke_state_changed_cb(mediaeditor_s *editor, mediaeditor_state_e old, mediaeditor_state_e new)
+{
+    RET_IF(editor == NULL, "editor is NULL");
+
+    LOG_INFO("state is changed [%s] -> [%s]", __state_str[old], __state_str[new]);
+
+    if (editor->state_changed_cb.callback) {
+        LOG_DEBUG(">>> callback[%p], user_data[%p]", editor->state_changed_cb.callback, editor->state_changed_cb.user_data);
+        ((mediaeditor_state_changed_cb)(editor->state_changed_cb.callback))(old, new, editor->state_changed_cb.user_data);
+        LOG_DEBUG("<<< end of the callback");
+    }
+
+    GENERATE_DOT(editor, "STATE_%s", __state_str[editor->state]);
+}
+
+void _invoke_error_cb(mediaeditor_s *editor, mediaeditor_error_e error)
+{
+    RET_IF(editor == NULL, "editor is NULL");
+
+    LOG_ERROR("error[0x%x, %s]", error, __get_error_string(error));
+
+    if (editor->error_cb.callback) {
+        LOG_DEBUG(">>> callback[%p], user_data[%p]", editor->error_cb.callback, editor->error_cb.user_data);
+        ((mediaeditor_error_cb)(editor->error_cb.callback))(error, editor->state, editor->error_cb.user_data);
+        LOG_DEBUG("<<< end of the callback");
+    }
+}
+
+static void __invoke_render_completed_cb(mediaeditor_s *editor)
+{
+    RET_IF(editor == NULL, "editor is NULL");
+
+    if (editor->render_completed_cb.callback) {
+        LOG_DEBUG(">>> callback[%p] user_data[%p]", editor->render_completed_cb.callback, editor->render_completed_cb.user_data);
+        ((mediaeditor_render_completed_cb)(editor->render_completed_cb.callback))(editor->render_completed_cb.user_data);
+        LOG_DEBUG("<<< end of the callback");
+
+        editor->render_completed_cb.callback = NULL;
+        editor->render_completed_cb.user_data = NULL;
+    }
+}
+
+static gboolean __bus_cb(GstBus *bus, GstMessage *message, gpointer *data)
+{
+    mediaeditor_s *editor = (mediaeditor_s *)data;
+    GError *err = NULL;
+    mediaeditor_error_e error = MEDIAEDITOR_ERROR_NONE;
+    GstState gst_state_old = GST_STATE_VOID_PENDING;
+    GstState gst_state_new = GST_STATE_VOID_PENDING;
+    GstState gst_state_pending = GST_STATE_VOID_PENDING;
+    gchar *state_transition_name = NULL;
+
+    RET_VAL_IF(editor == NULL, FALSE, "editor is NULL");
+
+    switch (GST_MESSAGE_TYPE(message)) {
+    case GST_MESSAGE_ERROR:
+        gst_message_parse_error(message, &err, NULL);
+
+        LOG_ERROR("Error[from %s]: message[%s], code[%d]",
+            GST_OBJECT_NAME(GST_OBJECT_CAST(GST_ELEMENT(GST_MESSAGE_SRC(message)))), err->message, err->code);
+
+        if (err->domain == GST_RESOURCE_ERROR)
+            error = MEDIAEDITOR_ERROR_RESOURCE_FAILED;
+        else
+            error = MEDIAEDITOR_ERROR_INVALID_OPERATION;
+
+        _invoke_error_cb(editor, error);
+
+        g_error_free(err);
+
+        break;
+    case GST_MESSAGE_STATE_CHANGED:
+        if (GST_MESSAGE_SRC(message) != GST_OBJECT(editor->gst.pipeline))
+            return TRUE;
+
+        gst_message_parse_state_changed(message, &gst_state_old, &gst_state_new, &gst_state_pending);
+        state_transition_name = g_strdup_printf("Old[GST_STATE_%s] New[GST_STATE_%s] Pending[GST_STATE_%s]",
+                                gst_element_state_get_name(gst_state_old), gst_element_state_get_name(gst_state_new),
+                                gst_element_state_get_name(gst_state_pending));
+
+        LOG_INFO("GST_MESSAGE_STATE_CHANGED: %s", state_transition_name);
+        g_free(state_transition_name);
+
+        if (editor->pend_state == editor->state) {
+            LOG_DEBUG("pend_state[%s] is same with current state", __state_str[editor->pend_state]);
+            break;
+        }
+
+        LOG_INFO("editor state[%d], pend_state[%d]", editor->state, editor->pend_state);
+        if (__meet_gst_state(editor->pend_state, gst_state_new)) {
+            mediaeditor_state_e old_state = editor->state;
+            editor->state = editor->pend_state;
+            __invoke_state_changed_cb(editor, old_state, editor->state);
+            break;
+        }
+
+        break;
+    case GST_MESSAGE_EOS:
+        LOG_INFO("EOS reached");
+        int ret = _gst_pipeline_set_state(editor, GST_STATE_NULL);
+        RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, FALSE, "failed to set GST_STATE_NULL");
+
+        __invoke_render_completed_cb(editor);
+
+#ifndef TIZEN_TV
+        ret = _release_all_resources(editor);
+        RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to release all resources");
+#endif
+
+        editor->state = MEDIAEDITOR_STATE_IDLE;
+
+        return TRUE;
+    default:
+        break;
+    }
+
+    return TRUE;
+}
+
+static gboolean __idle_cb(gpointer user_data)
+{
+    idle_userdata_s *data = (idle_userdata_s*)user_data;
+    mediaeditor_s *editor = NULL;
+
+    RET_VAL_IF(data == NULL, G_SOURCE_REMOVE, "userdata is NULL");
+    if (data->editor == NULL) {
+        LOG_ERROR("editor is NULL");
+        return G_SOURCE_REMOVE;
+    }
+
+    editor = data->editor;
+
+    switch (data->type) {
+    case IDLE_CB_TYPE_STATE: {
+        mediaeditor_state_e old_state;
+
+        g_mutex_lock(&editor->mutex);
+        g_mutex_lock(&editor->event_src_mutex);
+        editor->idle_cb_event_source_ids[data->type] = 0;
+        g_mutex_unlock(&editor->event_src_mutex);
+        old_state = editor->state;
+        editor->state = data->new.state;
+        g_mutex_unlock(&editor->mutex);
+
+        __invoke_state_changed_cb(editor, old_state, editor->state);
+        break;
+    }
+    case IDLE_CB_TYPE_ERROR:
+        g_mutex_lock(&editor->event_src_mutex);
+        editor->idle_cb_event_source_ids[data->type] = 0;
+        g_mutex_unlock(&editor->event_src_mutex);
+
+        _invoke_error_cb(editor, data->new.error);
+        break;
+    default:
+        LOG_ERROR_IF_REACHED("type(%d)", data->type);
+    }
+
+    return G_SOURCE_REMOVE;
+}
+
+static void __post_state_cb_in_idle(mediaeditor_s *editor, mediaeditor_state_e new_state)
+{
+    idle_userdata_s *data = NULL;
+
+    RET_IF(editor == NULL, "editor is NULL");
+
+    if (editor->state == new_state)
+        return;
+
+    data = g_new0(idle_userdata_s, 1);
+    data->editor = editor;
+    data->type = IDLE_CB_TYPE_STATE;
+    data->new.state = new_state;
+
+    editor->pend_state = new_state;
+
+    g_mutex_lock(&editor->event_src_mutex);
+    editor->idle_cb_event_source_ids[data->type] = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, __idle_cb, data, g_free);
+    g_mutex_unlock(&editor->event_src_mutex);
+
+    LOG_DEBUG("state will be changed [%s] -> [%s]", __state_str[editor->state], __state_str[new_state]);
+}
+
+void _post_error_cb_in_idle(mediaeditor_s *editor, mediaeditor_error_e error)
+{
+    idle_userdata_s *data = NULL;
+
+    RET_IF(editor == NULL, "editor is NULL");
+
+    data = g_new0(idle_userdata_s, 1);
+    data->editor = editor;
+    data->type = IDLE_CB_TYPE_ERROR;
+    data->new.error = error;
+
+    g_mutex_lock(&editor->event_src_mutex);
+    editor->idle_cb_event_source_ids[data->type] = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, __idle_cb, data, g_free);
+    g_mutex_unlock(&editor->event_src_mutex);
+
+    LOG_DEBUG("error will occur [0x%x]", error);
+}
+
+void _remove_remained_event_sources(mediaeditor_s *editor)
+{
+    RET_IF(editor == NULL, "editor is NULL");
+
+    for (int i = IDLE_CB_TYPE_STATE; i < IDLE_CB_TYPE_NUM; i++) {
+        if (editor->idle_cb_event_source_ids[i] == 0)
+            continue;
+
+        g_source_remove(editor->idle_cb_event_source_ids[i]);
+        LOG_DEBUG("idle_cb_event_source_ids[%d] source id[%u]", i, editor->idle_cb_event_source_ids[i]);
+        editor->idle_cb_event_source_ids[i] = 0;
+    }
+}
+
+int _gst_init(mediaeditor_s *editor)
+{
+    static gboolean initialized = FALSE;
+    gboolean ret = FALSE;
+    char **argv = NULL;
+    gint argc = 1;
+    gchar **gst_args;
+    GError *err = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    if (initialized) {
+        LOG_ERROR("gstreamer is already initialized.");
+        return TRUE;
+    }
+
+    gst_args = editor->ini.general.gst_args;
+
+    if (gst_args)
+        argc += g_strv_length(gst_args);
+
+    argv = (char **)calloc(argc, sizeof(char*));
+    RET_VAL_IF(argv == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to calloc()");
+
+    argv[0] = g_strdup("capi-media-editor");
+
+    for (int i = 0 ; gst_args && gst_args[i]; ++i) {
+        if (argc <= i + 1) {
+            LOG_ERROR("need to check, prevent overrun");
+            break;
+        }
+        argv[i + 1] = gst_args[i];
+        LOG_DEBUG("[%s] is added", argv[i + 1]);
+    }
+
+    ret = gst_init_check(&argc, &argv, &err);
+
+    /* we need to free argv even if gst_init_check() is failed */
+    for (int i = 1 ; i < argc ; i++)
+        argv[i] = NULL;
+
+    SAFE_FREE(argv[0]);
+    SAFE_FREE(argv);
+
+    if (!ret) {
+        LOG_ERROR("Failed to initialize gstreamer; %s", err->message);
+        g_clear_error(&err);
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    /* Initialize the GStreamer Editing Services */
+    ges_init();
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _gst_pipeline_set_state(mediaeditor_s *editor, GstState state)
+{
+    GstStateChangeReturn ret = GST_STATE_CHANGE_FAILURE;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(editor->gst.pipeline == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "pipeline is NULL");
+
+    ret = gst_element_set_state(GST_ELEMENT(editor->gst.pipeline), state);
+    if (ret == GST_STATE_CHANGE_FAILURE) {
+        LOG_ERROR("failed to gst_element_set_state(), state[%s]", gst_element_state_get_name(state));
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+void _gst_pipeline_destroy(mediaeditor_s *editor)
+{
+    LOG_DEBUG("Enter");
+
+    RET_IF(editor == NULL, "editor is NULL");
+
+    if (editor->gst.bus) {
+        gst_object_unref(editor->gst.bus);
+        editor->gst.bus = NULL;
+    }
+    if (editor->gst.pipeline) {
+        gst_object_unref(G_OBJECT(editor->gst.pipeline));
+        editor->gst.pipeline = NULL;
+    }
+}
+
+static int __mediaeditor_set_bus_cb(mediaeditor_s *editor)
+{
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    editor->gst.bus = gst_pipeline_get_bus(GST_PIPELINE(editor->gst.pipeline));
+    RET_VAL_IF(editor->gst.bus == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to get bus from pipeline");
+
+    gst_bus_add_watch_full(editor->gst.bus, G_PRIORITY_DEFAULT, (GstBusFunc) __bus_cb, editor, NULL);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+static bool __check_clip_added(mediaeditor_s *editor)
+{
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    return editor->clips != NULL;
+}
+
+int _mediaeditor_stop(mediaeditor_s *editor)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autoptr(GMutexLocker) locker = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    locker = g_mutex_locker_new(&editor->mutex);
+
+    RET_VAL_IF(editor->state == MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE,
+        "the state should not be IDLE");
+
+    ret = _gst_pipeline_set_state(editor, GST_STATE_NULL);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set GST_STATE_NULL");
+
+#ifndef TIZEN_TV
+    ret = _release_all_resources(editor);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to release all resources");
+#endif
+
+    __post_state_cb_in_idle(editor, MEDIAEDITOR_STATE_IDLE);
+
+    LOG_INFO("media editor[%p] is destroyed", editor);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_create_pipeline(mediaeditor_s *editor)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    editor->gst.pipeline = ges_pipeline_new();
+    RET_VAL_IF(editor->gst.pipeline == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create ges pipeline");
+
+    if (editor->gst.timeline == NULL) {
+#ifdef TIZEN_MEDIAEDITOR_EXPERIMENTAL_AUDIO_TRACK_ONLY
+        LOG_DEBUG("Create timeline with audio track only");
+        GESTrack *tracka;
+        GESTimeline *timeline;
+
+        /* This is our main GESTimeline */
+        timeline = ges_timeline_new();
+
+        tracka = GES_TRACK(ges_audio_track_new());
+
+        if (!ges_timeline_add_track(timeline, tracka)) {
+            gst_object_unref(timeline);
+            timeline = NULL;
+        }
+        editor->gst.timeline = timeline;
+#else
+        LOG_DEBUG("Create timeline with audio, video track");
+        editor->gst.timeline = ges_timeline_new_audio_video();
+#endif
+        if (editor->gst.timeline == NULL) {
+            LOG_ERROR("failed to create ges timeline");
+            gst_object_unref(editor->gst.pipeline);
+            return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+        }
+    }
+
+    if (!ges_pipeline_set_timeline(editor->gst.pipeline, editor->gst.timeline)) {
+        LOG_ERROR("failed to set ges timeline to ges pipeline");
+        gst_object_unref(editor->gst.pipeline);
+        gst_object_unref(editor->gst.timeline);
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    ret = __mediaeditor_set_bus_cb(editor);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set bus callback");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_start_render(mediaeditor_s *editor, const char* path)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL");
+    RET_VAL_IF(!__check_clip_added(editor), MEDIAEDITOR_ERROR_INVALID_OPERATION, "There's no clip to render");
+
+#ifndef TIZEN_TV
+    if (editor->ini.resource_acquisition.video_decoder) {
+        ret = _acquire_resource_for_type(editor, MM_RESOURCE_MANAGER_RES_TYPE_VIDEO_DECODER);
+        RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to acquire video decoder resource");
+    }
+
+    if (editor->ini.resource_acquisition.video_encoder) {
+        ret = _acquire_resource_for_type(editor, MM_RESOURCE_MANAGER_RES_TYPE_VIDEO_ENCODER);
+        RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to acquire video encoder resource");
+    }
+#endif
+
+    if(!ges_timeline_commit_sync(editor->gst.timeline)) {
+        LOG_ERROR("There's nothing to render");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    ret = __commit_audio_fade_effect(editor);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to commit audio fade effect");
+
+    ret = __create_encoding_profile(editor, path);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create encoding profile");
+
+    if (!ges_pipeline_set_mode(editor->gst.pipeline, GES_PIPELINE_MODE_SMART_RENDER)) {
+        LOG_ERROR("failed to set smart render pipeline mode");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    ret = _gst_pipeline_set_state(editor, GST_STATE_PLAYING);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set GST_STATE_PLAYING");
+
+    editor->pend_state = MEDIAEDITOR_STATE_RENDERING;
+
+    GENERATE_DOT(editor, "%s", GST_ELEMENT_NAME(editor->gst.pipeline));
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_cancel_render(mediaeditor_s *editor)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    ret = _gst_pipeline_set_state(editor, GST_STATE_READY);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set GST_STATE_READY");
+
+    __post_state_cb_in_idle(editor, MEDIAEDITOR_STATE_IDLE);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_start_preview(mediaeditor_s *editor)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(!__check_clip_added(editor), MEDIAEDITOR_ERROR_INVALID_OPERATION, "There's no clip to render");
+
+    if(!ges_timeline_commit_sync(editor->gst.timeline)) {
+        LOG_ERROR("There's nothing to render");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    if (!ges_pipeline_set_mode(editor->gst.pipeline, GES_PIPELINE_MODE_PREVIEW)) {
+        LOG_ERROR("failed to set preview pipeline mode");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    ret = _gst_pipeline_set_state(editor, GST_STATE_PLAYING);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set GST_STATE_PLAYING");
+
+    editor->pend_state = MEDIAEDITOR_STATE_PREVIEW;
+
+    GENERATE_DOT(editor, "%s", GST_ELEMENT_NAME(editor->gst.pipeline));
+
+    LOG_ERROR("State[%d],[%d]", editor->state, editor->pend_state);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_stop_preview(mediaeditor_s *editor)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    ret = _gst_pipeline_set_state(editor, GST_STATE_READY);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set GST_STATE_READY");
+
+    __post_state_cb_in_idle(editor, MEDIAEDITOR_STATE_IDLE);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _check_privilege(const char *path, bool file_exist, int mode)
+{
+    int ret = 0;
+
+    RET_VAL_IF(!path, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is null");
+
+    if (file_exist) {
+        ret = access(path, mode);
+    } else {
+        g_autofree gchar *dirname = g_path_get_dirname(path);
+        RET_VAL_IF(!dirname, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid path");
+
+        ret = access(dirname, mode);
+    }
+
+    if (ret != 0) {
+        if (errno == EACCES || errno == EPERM) {
+            LOG_ERROR("fail to access path[%s]: permission denied", path);
+            return MEDIAEDITOR_ERROR_PERMISSION_DENIED;
+        } else {
+            LOG_ERROR("fail to access path[%s]: invalid path", path);
+            return MEDIAEDITOR_ERROR_INVALID_PARAMETER;
+        }
+    }
+
+        return MEDIAEDITOR_ERROR_NONE;
+    }
+
+int _check_feature(const char *feature)
+{
+    bool supported = false;
+
+    RET_VAL_IF(feature == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "feature is NULL");
+
+    if (system_info_get_platform_bool(feature, &supported) != SYSTEM_INFO_ERROR_NONE) {
+        LOG_ERROR("failed to system_info_get_platform_bool(), feature[%s]", feature);
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    if (!supported) {
+        LOG_ERROR("feature[%s] is not supported", feature);
+        return MEDIAEDITOR_ERROR_NOT_SUPPORTED;
+    }
+
+    LOG_INFO("feature[%s] is supported", feature);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
diff --git a/src/media_editor_project.c b/src/media_editor_project.c
new file mode 100644 (file)
index 0000000..af4ba70
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <media_editor.h>
+#include <media_editor_private.h>
+
+static void __project_loaded_cb(GESProject *project, GESTimeline *timeline, mediaeditor_s *editor)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    LOG_DEBUG("project[%p]:timeline[%p] is loaded", project, timeline);
+
+    g_signal_handlers_disconnect_by_func(project, (GCallback)__project_loaded_cb, editor);
+
+    ret = _mediaeditor_create_pipeline(editor);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        _invoke_error_cb(editor, ret);
+        return;
+    }
+
+    if (editor->project_loaded_cb.callback) {
+        LOG_DEBUG(">>> callback[%p], user_data[%p]", editor->project_loaded_cb.callback, editor->project_loaded_cb.user_data);
+        ((mediaeditor_project_loaded_cb)(editor->project_loaded_cb.callback))(editor->project_loaded_cb.user_data);
+        LOG_DEBUG("<<< end of the callback");
+    }
+}
+
+static int __connect_project_loaded_signal(mediaeditor_s *editor)
+{
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    g_signal_connect(editor->gst.project, "loaded", (GCallback)__project_loaded_cb, editor);
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+static int __extract_timeline(mediaeditor_s *editor)
+{
+    GESTimeline *timeline = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    timeline = GES_TIMELINE(ges_asset_extract(GES_ASSET(editor->gst.project), NULL));
+    RET_VAL_IF(timeline == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to extract timeline from project");
+
+    editor->gst.timeline = timeline;
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+static int __mediaeditor_create_ges_project(mediaeditor_s *editor, gchar *uri)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    GESProject *project = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    project = ges_project_new(uri);
+    RET_VAL_IF(project == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create project");
+
+    editor->gst.project = project;
+
+    ret = __extract_timeline(editor);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to load timeline");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_create_project(mediaeditor_s *editor, const char *path)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autofree gchar *uri = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL");
+
+    uri = gst_filename_to_uri(path, NULL);
+    RET_VAL_IF(uri == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "failed to get uri");
+    LOGI("uri : [%s]", uri);
+
+    if (editor->gst.pipeline)
+        _gst_pipeline_destroy(editor);
+
+    ret = __mediaeditor_create_ges_project(editor, NULL);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create ges project");
+
+    ret = _mediaeditor_create_pipeline(editor);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create pipeline");
+
+    ret = _mediaeditor_save_project(editor, uri);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to save project");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_load_project(mediaeditor_s *editor, const char *path)
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    g_autofree gchar *uri = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL");
+
+    uri = gst_filename_to_uri(path, NULL);
+    RET_VAL_IF(uri == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "failed to get uri");
+    LOGI("uri : [%s]", uri);
+
+    if (editor->gst.pipeline)
+        _gst_pipeline_destroy(editor);
+
+    if (editor->gst.project)
+        gst_object_unref(editor->gst.project);
+
+    ret = __mediaeditor_create_ges_project(editor, uri);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create ges project");
+
+    ret = __connect_project_loaded_signal(editor);
+    RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to connect signal");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _mediaeditor_save_project(mediaeditor_s *editor, const gchar *uri)
+{
+    GESAsset *formatter_asset = NULL;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+    RET_VAL_IF(uri == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "uri is NULL");
+
+    formatter_asset = ges_asset_request(GES_TYPE_FORMATTER, "ges", NULL);
+    RET_VAL_IF(formatter_asset == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to request formatter");
+
+    if (!ges_project_save(editor->gst.project, editor->gst.timeline, uri, formatter_asset, TRUE, NULL)) {
+        LOGE("failed to save project");
+        return MEDIAEDITOR_ERROR_INVALID_OPERATION;
+    }
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
diff --git a/src/media_editor_resource.c b/src/media_editor_resource.c
new file mode 100644 (file)
index 0000000..220f14d
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "media_editor.h"
+#include "media_editor_private.h"
+
+#ifndef TIZEN_TV
+static int __resource_release_cb(mm_resource_manager_h mgr,
+        mm_resource_manager_res_h res, void *user_data)
+{
+    int ret = true;
+    mediaeditor_s *editor = (mediaeditor_s *)user_data;
+
+    RET_VAL_IF(editor == NULL, false, "editor is NULL");
+
+    editor->resource.release_cb_is_calling = true;
+
+    for (int i = 0; i < RESOURCE_TYPE_MAX; i++) {
+        if (editor->resource.res[i] == res) {
+            LOG_INFO("type[%d] resource was released by resource manager", i);
+            editor->resource.res[i] = NULL;
+        }
+    }
+
+    if (_mediaeditor_stop(editor) != MEDIAEDITOR_ERROR_NONE)
+        ret = false;
+
+    _post_error_cb_in_idle(editor, MEDIAEDITOR_ERROR_RESOURCE_CONFLICT);
+
+    editor->resource.release_cb_is_calling = false;
+
+    return ret;
+}
+
+static bool __is_valid_resource_type(mm_resource_manager_res_type_e type)
+{
+    if (type < MM_RESOURCE_MANAGER_RES_TYPE_VIDEO_DECODER || type >= RESOURCE_TYPE_MAX) {
+        LOG_ERROR("Type[%d] is a invalid resource type", type);
+        return false;
+    }
+    return true;
+}
+
+int _create_resource_manager(mediaeditor_s *editor)
+{
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    if (mm_resource_manager_create(MM_RESOURCE_MANAGER_APP_CLASS_MEDIA,
+                __resource_release_cb, editor,
+                &editor->resource.mgr) != MM_RESOURCE_MANAGER_ERROR_NONE) {
+        LOG_ERROR("Failed to init resource manager for media");
+        return MEDIAEDITOR_ERROR_RESOURCE_FAILED;
+    }
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _acquire_resource_for_type(mediaeditor_s *editor, mm_resource_manager_res_type_e type)
+{
+    int ret = MM_RESOURCE_MANAGER_ERROR_NONE;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    if (!editor->resource.mgr) {
+        LOG_DEBUG("There's no acquired hw encoder, decoder resources");
+        return MEDIAEDITOR_ERROR_NONE;
+    }
+
+    RET_VAL_IF(!__is_valid_resource_type(type), MEDIAEDITOR_ERROR_INVALID_PARAMETER,
+            "type is wrong");
+
+    if (editor->resource.res[type] != NULL) {
+        LOG_ERROR("type[%d] resource was already acquired", type);
+        return MEDIAEDITOR_ERROR_RESOURCE_FAILED;
+    }
+
+    LOG_DEBUG("mark for acquire type[%d] resource", type);
+    ret = mm_resource_manager_mark_for_acquire(editor->resource.mgr, type,
+                MM_RESOURCE_MANAGER_RES_VOLUME_FULL, &editor->resource.res[type]);
+    if (ret != MM_RESOURCE_MANAGER_ERROR_NONE) {
+        LOG_ERROR("failed to mark resource for acquire, ret[0x%x]", ret);
+        return MEDIAEDITOR_ERROR_RESOURCE_FAILED;
+    }
+
+    LOG_DEBUG("commit type[%d] resource", type);
+    ret = mm_resource_manager_commit(editor->resource.mgr);
+    if (ret != MM_RESOURCE_MANAGER_ERROR_NONE) {
+        LOG_ERROR("failed to commit of resource, ret([0x%x]", ret);
+        return MEDIAEDITOR_ERROR_RESOURCE_FAILED;
+    }
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _release_all_resources(mediaeditor_s *editor)
+{
+    int ret = MM_RESOURCE_MANAGER_ERROR_NONE;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    if (!editor->resource.mgr) {
+        LOG_DEBUG("There's no acquired hw encoder, decoder resources");
+        return MEDIAEDITOR_ERROR_NONE;
+    }
+
+    if(editor->resource.release_cb_is_calling) {
+        LOG_INFO("__resource_release_cb is calling, so skip");
+        return MEDIAEDITOR_ERROR_NONE;
+    }
+
+    ret = mm_resource_manager_mark_all_for_release(editor->resource.mgr);
+    if (ret != MM_RESOURCE_MANAGER_ERROR_NONE) {
+        LOG_ERROR("failed to mark all for release, ret[0x%x]", ret);
+        return MEDIAEDITOR_ERROR_RESOURCE_FAILED;
+    }
+    ret = mm_resource_manager_commit(editor->resource.mgr);
+    if (ret != MM_RESOURCE_MANAGER_ERROR_NONE) {
+        LOG_ERROR("failed to commit resource, ret[0x%x]", ret);
+        return MEDIAEDITOR_ERROR_RESOURCE_FAILED;
+    }
+    LOG_DEBUG("all resources were released by resource manager");
+
+    for (int i = 0; i < RESOURCE_TYPE_MAX; i++) {
+        editor->resource.need_to_acquire[i] = false;
+        editor->resource.res[i] = NULL;
+    }
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+
+int _destroy_resource_manager(mediaeditor_s *editor)
+{
+    int ret = MM_RESOURCE_MANAGER_ERROR_NONE;
+
+    RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL");
+
+    if (!editor->resource.mgr) {
+        LOG_DEBUG("There's no acquired hw encoder, decoder resources");
+        return MEDIAEDITOR_ERROR_NONE;
+    }
+
+    ret = mm_resource_manager_destroy(editor->resource.mgr);
+    if (ret != MM_RESOURCE_MANAGER_ERROR_NONE) {
+        LOG_ERROR("failed to destroy resource manager, ret[0x%x]", ret);
+        return MEDIAEDITOR_ERROR_RESOURCE_FAILED;
+    }
+
+    editor->resource.mgr = NULL;
+    LOG_DEBUG("destroyed resource manager");
+
+    return MEDIAEDITOR_ERROR_NONE;
+}
+#endif
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644 (file)
index 0000000..86d77fa
--- /dev/null
@@ -0,0 +1,22 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12)
+SET(fw_test "${fw_name}-test")
+
+INCLUDE(FindPkgConfig)
+pkg_check_modules(${fw_test} REQUIRED elementary evas ecore appcore-efl)
+FOREACH(flag ${${fw_test}_CFLAGS})
+    SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+    MESSAGE(${flag})
+ENDFOREACH()
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -fPIC -pie -Wall -Werror")
+
+aux_source_directory(. sources)
+
+FOREACH(src ${sources})
+    GET_FILENAME_COMPONENT(src_name ${src} NAME_WE)
+    MESSAGE("${src_name}")
+    ADD_EXECUTABLE(${src_name} ${src})
+    TARGET_LINK_LIBRARIES(${src_name} ${fw_name} ${${fw_test}_LDFLAGS})
+    INSTALL(TARGETS ${src_name} DESTINATION bin)
+
+ENDFOREACH()
diff --git a/test/media_editor_test.c b/test/media_editor_test.c
new file mode 100644 (file)
index 0000000..826652d
--- /dev/null
@@ -0,0 +1,417 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <sys/time.h>
+#include <dlog.h>
+#include <Ecore.h>
+#include <Elementary.h>
+#include <appcore-efl.h>
+#include <media_editor.h>
+
+#define EXPORT_API __attribute__((__visibility__("default")))
+
+#ifdef PACKAGE
+#undef PACKAGE
+#endif
+#define PACKAGE "media_editor_test"
+
+#define GET_DISPLAY(x) (void*)(x)
+#define DEFAULT_FILE_PATH    "/tmp"
+#define MAX_FILE_NAME_LENGTH 256
+#define MAX_FILE_PATH_LENGTH (MAX_FILE_NAME_LENGTH - 20)
+
+#define CHECK_MM_ERROR(expr) \
+do { \
+    int ret = 0; \
+    ret = expr; \
+    if (ret != 0) { \
+        g_print("[%s:%d] error code : %x \n", __func__, __LINE__, ret); \
+        return; \
+    } \
+} while (0)
+
+#ifndef SAFE_FREE
+#define SAFE_FREE(x) \
+if (x) { \
+    g_free(x); \
+    x = NULL; \
+}
+#endif
+
+enum {
+    MENU_STATE_INIT,
+    MENU_STATE_MAIN,
+    MENU_STATE_NUM,
+};
+
+static void print_menu();
+static gboolean cmd_input(GIOChannel *channel, GIOCondition condition, gpointer data);
+static gboolean mode_change(gchar buf);
+static int app_create(void *data);
+static int app_terminate(void *data);
+
+typedef struct _appdata {
+    Evas_Object *win;
+    Evas_Object *eo;
+    Evas_Object *bg;
+    Evas_Object *rect;
+} appdata;
+
+struct appcore_ops ops = {
+    .create = app_create,
+    .terminate = app_terminate,
+};
+
+typedef struct _mediaeditor_handle {
+    mediaeditor_h mediaeditor;
+    int type;
+    int menu_state;
+} mediaeditor_handle_t;
+
+static struct timeval previous_time;
+static struct timeval current_time;
+static struct timeval result_time;
+
+appdata ad;
+GIOChannel *stdin_channel = NULL;
+static GTimer *timer = NULL;
+mediaeditor_h heditor = NULL;
+static mediaeditor_handle_t *hmediaeditor;
+
+static inline void flush_stdin()
+{
+    int ch;
+    while ((ch = getchar()) != EOF && ch != '\n');
+}
+
+static void render_completed_cb(void *user_data)
+{
+    g_print("Enter\n");
+}
+
+static void print_menu()
+{
+    switch (hmediaeditor->menu_state) {
+    case MENU_STATE_INIT:
+        g_print("\n\t=======================================\n");
+        g_print("\t   MEDIAEDITOR_TESTSUITE\n");
+        g_print("\t=======================================\n");
+        g_print("\t   '1' Start preview\n");
+        g_print("\t   '2' Stop preview\n");
+        g_print("\t   '3' Start render\n");
+        g_print("\t   'q' Exit\n");
+        g_print("\t=======================================\n");
+        break;
+    default:
+        g_print("\n\tunknow menu state !!\n");
+        return;
+    }
+
+    g_print("\tCommand >> ");
+
+    return;
+}
+
+static gboolean cmd_input(GIOChannel *channel, GIOCondition condition, gpointer data)
+{
+    gchar *buf = NULL;
+    gsize read_size;
+    GError *g_error = NULL;
+
+    g_print("\n\tENTER\n");
+
+    g_io_channel_read_line(channel, &buf, &read_size, NULL, &g_error);
+    if (g_error) {
+        g_print("\n\tg_io_channel_read_chars error\n");
+        g_error_free(g_error);
+        g_error = NULL;
+    }
+
+    if (buf) {
+        g_strstrip(buf);
+
+        g_print("\n\tMenu Status : %d\n", hmediaeditor->menu_state);
+        switch (hmediaeditor->menu_state) {
+        case MENU_STATE_INIT:
+            mode_change(buf[0]);
+            break;
+        default:
+            break;
+        }
+
+        g_free(buf);
+        buf = NULL;
+
+        print_menu();
+    } else {
+        g_print("\n\tNo read input\n");
+    }
+
+    return TRUE;
+}
+
+static gboolean init_handle()
+{
+    hmediaeditor->menu_state = MENU_STATE_INIT;
+
+    return TRUE;
+}
+
+
+static void start_preview()
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+
+    ret = mediaeditor_add_layer(heditor, &layer_id, &layer_priority);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        g_print("failed to add layer : error[0x%x]\n", ret);
+        return;
+    }
+
+    ret = mediaeditor_add_clip(heditor, "/tmp/test1.mp4", layer_priority, 0, G_TIME_SPAN_MILLISECOND * 10, 0, &clip_id);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        g_print("failed to add clip : error[0x%x]\n", ret);
+        return;
+    }
+
+    ret = mediaeditor_set_display(heditor, MEDIAEDITOR_DISPLAY_TYPE_EVAS, GET_DISPLAY(ad.eo));
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        g_print("failed to set display : error[0x%x]\n", ret);
+        return;
+    }
+
+    ret = mediaeditor_start_preview(heditor);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        g_print("failed to start preview : error[0x%x]\n", ret);
+        return;
+    }
+}
+
+static void stop_preview()
+{
+    int ret = 0;
+
+    ret = mediaeditor_stop_preview(heditor);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        g_print("failed to stop preview : error[0x%x]\n", ret);
+        return;
+    }
+}
+
+static void start_render()
+{
+    int ret = MEDIAEDITOR_ERROR_NONE;
+    unsigned int layer_id = 0;
+    unsigned int layer_priority = 0;
+    unsigned int clip_id = 0;
+
+    ret = mediaeditor_add_layer(heditor, &layer_id, &layer_priority);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        g_print("failed to add layer : error[0x%x]\n", ret);
+        return;
+    }
+
+    ret = mediaeditor_add_clip(heditor, "/tmp/test1.mp4", layer_priority, 0, 100, 0, &clip_id);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        g_print("failed to add clip : error[0x%x]\n", ret);
+        return;
+    }
+
+    g_print("Call render async\n");
+    ret = mediaeditor_start_render(heditor, "/tmp/out_render.mp4", render_completed_cb, heditor);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        g_print("failed to start render : error[0x%x]\n", ret);
+        return;
+    }
+}
+
+static gboolean mode_change(gchar buf)
+{
+    switch (buf) {
+    case '1':
+        g_print("Start preview\n");
+        start_preview();
+        break;
+    case '2':
+        g_print("Stop preview\n");
+        stop_preview();
+        break;
+    case '3':
+        g_print("Start render\n");
+        start_render();
+        break;
+    case 'q':
+        g_print("\t Quit MediaEditor Testsuite!!\n");
+        elm_exit();
+        return FALSE;
+    default:
+        return FALSE;
+    }
+
+    gettimeofday(&previous_time, NULL);
+
+    g_timer_reset(timer);
+
+    gettimeofday(&current_time, NULL);
+    timersub(&current_time, &previous_time, &result_time);
+
+    g_print("\n\tElapsed time : %ld.%lds\n", result_time.tv_sec, result_time.tv_usec);
+
+    return TRUE;
+}
+
+static int app_create(void *data)
+{
+    appdata *app_data = data;
+    int w = 0;
+    int h = 0;
+    Evas_Object *win = NULL;
+    Evas_Object *eo = NULL;
+    Evas_Object *bg = NULL;
+    Evas_Object *rect = NULL;
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    if (app_data == NULL) {
+        g_print("\t\nappdata is NULL\n");
+        return 0;
+    }
+
+    /* use gl backend */
+    elm_config_accel_preference_set("opengl");
+
+    win = elm_win_add(NULL, "media_editor_test", ELM_WIN_BASIC);
+    if (win) {
+        elm_win_title_set(win, "media_editor_test");
+        elm_win_borderless_set(win, EINA_TRUE);
+        elm_win_screen_size_get(win, NULL, NULL, &w, &h);
+        g_print("\n\tscreen size %dx%d\n\n", w, h);
+        evas_object_resize(win, w, h);
+        elm_win_autodel_set(win, EINA_TRUE);
+        elm_win_alpha_set(win, EINA_TRUE);
+    } else {
+        g_print("\n\tfailed to get window\n\n");
+        return 1;
+    }
+
+    bg = elm_bg_add(win);
+    if (bg) {
+        elm_win_resize_object_add(win, bg);
+        evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+        evas_object_show(bg);
+    } else {
+        g_print("\n\tfailed to get elm bg\n\n");
+        return 1;
+    }
+
+    rect = evas_object_rectangle_add(evas_object_evas_get(win));
+    if (rect) {
+        evas_object_color_set(rect, 0, 0, 0, 0);
+        evas_object_render_op_set(rect, EVAS_RENDER_COPY);
+    } else {
+        g_print("\n\tfailed to get rectangle\n\n");
+        return 1;
+    }
+
+    elm_win_resize_object_add(win, rect);
+    evas_object_size_hint_weight_set(rect, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+    evas_object_show(rect);
+
+    /* Create evas image object for EVAS surface */
+    eo = evas_object_image_add(evas_object_evas_get(win));
+    evas_object_image_size_set(eo, w, h);
+    evas_object_image_fill_set(eo, 0, 0, w, h);
+    evas_object_resize(eo, w, h);
+    evas_object_show(eo);
+
+    elm_win_activate(win);
+    evas_object_show(win);
+
+    app_data->win = win;
+    app_data->eo = eo;
+
+    timer = g_timer_new();
+    g_timer_reset(timer);
+
+    init_handle();
+
+    g_print("create");
+    ret = mediaeditor_create(&heditor);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        g_print("failed to create mediaeditor : error[0x%x]", ret);
+        return 1;
+    }
+
+    print_menu();
+
+    return 0;
+}
+
+static int app_terminate(void *data)
+{
+    appdata *app_data = data;
+    int ret = MEDIAEDITOR_ERROR_NONE;
+
+    if (app_data == NULL) {
+        g_print("\n\tappdata is NULL\n");
+        return 0;
+    }
+
+    if (timer) {
+        g_timer_stop(timer);
+        g_timer_destroy(timer);
+        timer = NULL;
+    }
+
+    ret = mediaeditor_destroy(heditor);
+    if (ret != MEDIAEDITOR_ERROR_NONE) {
+        g_print("failed to destroy : error[0x%x]", ret);
+        return 1;
+    }
+
+    return 0;
+}
+
+/**
+ * This function is the example main function for media editor API.
+ * @return  This function returns 0.
+ */
+int main(int argc, char **argv)
+{
+    int bret = 0;
+
+    hmediaeditor = (mediaeditor_handle_t *) g_malloc0(sizeof(mediaeditor_handle_t));
+
+    stdin_channel = g_io_channel_unix_new(fileno(stdin));/* read from stdin */
+    g_io_add_watch(stdin_channel, G_IO_IN, (GIOFunc)cmd_input, NULL);
+
+    memset(&ad, 0x0, sizeof(appdata));
+    ops.data = &ad;
+
+    bret = appcore_efl_main(PACKAGE, &argc, &argv, &ops);
+
+    g_print("\n\treturn appcore_efl : %d\n\n", bret);
+
+    g_free(hmediaeditor);
+    g_io_channel_unref(stdin_channel);
+
+    return bret;
+}