1 /*
2 * Copyright (C) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "video_composition_callback_imp.h"
17
18 #include <nlohmann/json.hpp>
19
20 #include "media_log.h"
21 #include "media_file_utils.h"
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <fcntl.h>
25 #include "medialibrary_errno.h"
26 #include "photo_file_utils.h"
27 #include "dfx_utils.h"
28 #include "directory_ex.h"
29 #include "medialibrary_object_utils.h"
30
31 using std::string;
32
33 namespace OHOS {
34 namespace Media {
35 static const mode_t CHOWN_RW_USR_GRP = 0600;
36 static const int32_t LOG_FREQUENCY = 10;
37 std::unordered_map<uint32_t, std::shared_ptr<VideoEditor>> VideoCompositionCallbackImpl::editorMap_;
38 std::queue<VideoCompositionCallbackImpl::Task> VideoCompositionCallbackImpl::waitQueue_;
39 int32_t VideoCompositionCallbackImpl::curWorkerNum_ = 0;
40 std::mutex VideoCompositionCallbackImpl::mutex_;
41 // LCOV_EXCL_START
CheckDirPathReal(const std::string & filePath)42 static int32_t CheckDirPathReal(const std::string &filePath)
43 {
44 string dirPath;
45 auto index = filePath.rfind('/');
46 CHECK_AND_RETURN_RET_LOG(index != std::string::npos, E_HAS_FS_ERROR,
47 "find split for last string failed, %{private}s, errno: %{public}d", filePath.c_str(), errno);
48 dirPath = filePath.substr(0, index);
49 string absDirPath;
50 CHECK_AND_RETURN_RET_LOG(PathToRealPath(dirPath, absDirPath),
51 E_HAS_FS_ERROR, "file is not real path: %{private}s, errno: %{public}d", dirPath.c_str(), errno);
52 return E_OK;
53 }
54
VideoCompositionCallbackImpl()55 VideoCompositionCallbackImpl::VideoCompositionCallbackImpl() {}
56
CleanTempFilters(const std::string & tempFilters)57 static void CleanTempFilters(const std::string &tempFilters)
58 {
59 if (!MediaFileUtils::IsFileExists(tempFilters)) {
60 return;
61 }
62 if (!MediaFileUtils::DeleteFile(tempFilters)) {
63 MEDIA_ERR_LOG("Clean TempFilters errno: %{public}d", errno);
64 }
65 }
66
SaveTempFiltersToVideo(const std::string & tempFilters,const std::string & videoPath)67 static int32_t SaveTempFiltersToVideo(const std::string &tempFilters, const std::string &videoPath)
68 {
69 if (!MediaFileUtils::IsFileExists(tempFilters)) {
70 MEDIA_ERR_LOG("SaveTempFiltersToVideo tempFilters not exists");
71 return E_HAS_FS_ERROR;
72 }
73 return rename(tempFilters.c_str(), videoPath.c_str());
74 }
75
HandleAddFiltersError(const std::string & sourceVideoPath,const std::string & videoPath)76 static bool HandleAddFiltersError(const std::string &sourceVideoPath, const std::string &videoPath)
77 {
78 if (MediaFileUtils::IsFileExists(videoPath)) {
79 MEDIA_ERR_LOG("HandleAddFiltersError videoPath already exists");
80 return true;
81 }
82 if (!MediaFileUtils::IsFileExists(sourceVideoPath)) {
83 MEDIA_ERR_LOG("HandleAddFiltersError sourceVideoPath not exists");
84 return false;
85 }
86 return MediaFileUtils::CopyFileUtil(sourceVideoPath, videoPath);
87 }
88
onResult(VEFResult result,VEFError errorCode)89 void VideoCompositionCallbackImpl::onResult(VEFResult result, VEFError errorCode)
90 {
91 close(inputFileFd_);
92 close(outputFileFd_);
93 editorMap_.erase(inputFileFd_);
94 if (errorCode != VEFError::ERR_OK) {
95 mutex_.lock();
96 --curWorkerNum_;
97 mutex_.unlock();
98 MEDIA_ERR_LOG("VideoCompositionCallbackImpl onResult error:%{public}d", (int32_t)errorCode);
99 // Video Composite failed save sourceVideo to photo directory
100 CHECK_AND_PRINT_LOG(HandleAddFiltersError(sourceVideoPath_, videoPath_),
101 "Copy sourceVideoPath to videoPath, path:%{private}s", sourceVideoPath_.c_str());
102 CleanTempFilters(tempFilters_);
103 return;
104 }
105 int32_t ret = SaveTempFiltersToVideo(tempFilters_, videoPath_);
106 if (ret != E_OK) {
107 CleanTempFilters(tempFilters_);
108 }
109 if (isNeedScan_ && MediaFileUtils::IsFileExists(assetPath_)) {
110 MediaLibraryObjectUtils::ScanMovingPhotoVideoAsync(assetPath_, true);
111 }
112 mutex_.lock();
113 if (waitQueue_.empty()) {
114 --curWorkerNum_;
115 mutex_.unlock();
116 } else {
117 Task task = std::move(waitQueue_.front());
118 waitQueue_.pop();
119 mutex_.unlock();
120 if (CallStartComposite(task.sourceVideoPath_, task.videoPath_, task.editData_,
121 task.assetPath_, task.isNeedScan_) != E_OK) {
122 mutex_.lock();
123 --curWorkerNum_;
124 mutex_.unlock();
125 MEDIA_ERR_LOG("Failed to CallStartComposite, path:%{private}s", task.videoPath_.c_str());
126 CHECK_AND_RETURN_LOG(HandleAddFiltersError(task.sourceVideoPath_, task.videoPath_),
127 "Copy sourceVideoPath to videoPath, path:%{private}s", task.sourceVideoPath_.c_str());
128 }
129 }
130 }
131
onProgress(uint32_t progress)132 void VideoCompositionCallbackImpl::onProgress(uint32_t progress)
133 {
134 if (!(progress % LOG_FREQUENCY)) {
135 MEDIA_INFO_LOG("VideoCompositionCallbackImpl onProcess:%{public}d, tempPath:%{public}s",
136 (int32_t)progress, videoPath_.c_str());
137 }
138 }
139
GetTempFiltersPath(const std::string videoPath)140 static std::string GetTempFiltersPath(const std::string videoPath)
141 {
142 return videoPath.substr(0, videoPath.rfind('.')) + "_temp_filters_"
143 + std::to_string(MediaFileUtils::UTCTimeMilliSeconds()) + videoPath.substr(videoPath.rfind('.'));
144 }
145
CallStartComposite(const std::string & sourceVideoPath,const std::string & videoPath,const std::string & effectDescription,const std::string & assetPath,bool isNeedScan)146 int32_t VideoCompositionCallbackImpl::CallStartComposite(const std::string& sourceVideoPath,
147 const std::string& videoPath, const std::string& effectDescription, const std::string& assetPath, bool isNeedScan)
148 {
149 MEDIA_INFO_LOG("StartComposite, sourceVideoPath: %{public}s", DfxUtils::GetSafePath(sourceVideoPath).c_str());
150 string absSourceVideoPath;
151 CHECK_AND_RETURN_RET_LOG(PathToRealPath(sourceVideoPath, absSourceVideoPath), E_HAS_FS_ERROR,
152 "file is not real path, file path: %{private}s, errno: %{public}d", sourceVideoPath.c_str(), errno);
153 int32_t inputFileFd = open(absSourceVideoPath.c_str(), O_RDONLY);
154 CHECK_AND_RETURN_RET_LOG(inputFileFd != -1, E_ERR, "Open failed for inputFileFd file, errno: %{public}d", errno);
155 if (CheckDirPathReal(videoPath) != E_OK) {
156 MEDIA_ERR_LOG("dirFile is not real path, file path: %{private}s, errno: %{public}d", videoPath.c_str(), errno);
157 close(inputFileFd);
158 return E_HAS_FS_ERROR;
159 }
160 auto callBack = std::make_shared<VideoCompositionCallbackImpl>();
161 auto editor = VideoEditorFactory::CreateVideoEditor();
162 if (editor == nullptr) {
163 close(inputFileFd);
164 MEDIA_ERR_LOG("CreateEditor failed with error");
165 return E_ERR;
166 }
167 VEFError error = editor->AppendVideoFile(inputFileFd, effectDescription);
168 if (error != VEFError::ERR_OK) {
169 close(inputFileFd);
170 editor = nullptr;
171 MEDIA_ERR_LOG("AppendVideoFile failed with error: %{public}d", (int32_t)error);
172 return E_ERR;
173 }
174 callBack->tempFilters_ = GetTempFiltersPath(videoPath);
175 int32_t outputFileFd = open(callBack->tempFilters_.c_str(), O_WRONLY|O_CREAT, CHOWN_RW_USR_GRP);
176 if (outputFileFd == -1) {
177 close(inputFileFd);
178 MEDIA_ERR_LOG("Open failed for outputFileFd file, errno: %{public}d", errno);
179 return E_ERR;
180 }
181
182 InitCallbackImpl(callBack, inputFileFd, outputFileFd, videoPath, absSourceVideoPath, assetPath, isNeedScan);
183
184 auto compositionOptions = std::make_shared<CompositionOptions>(outputFileFd, callBack);
185 error = editor->StartComposite(compositionOptions);
186 if (error != VEFError::ERR_OK) {
187 close(inputFileFd);
188 close(outputFileFd);
189 CleanTempFilters(callBack->tempFilters_);
190 editor = nullptr;
191 MEDIA_ERR_LOG("StartComposite failed with error: %{public}d", (int32_t)error);
192 return E_ERR;
193 }
194 callBack->editorMap_[inputFileFd] = editor;
195 return E_OK;
196 }
197
AddCompositionTask(const std::string & assetPath,std::string & editData,bool isNeedScan)198 void VideoCompositionCallbackImpl::AddCompositionTask(const std::string& assetPath,
199 std::string& editData, bool isNeedScan)
200 {
201 string sourceImagePath = PhotoFileUtils::GetEditDataSourcePath(assetPath);
202 string videoPath = MediaFileUtils::GetMovingPhotoVideoPath(assetPath);
203 string sourceVideoPath = MediaFileUtils::GetMovingPhotoVideoPath(sourceImagePath);
204
205 mutex_.lock();
206 if (curWorkerNum_ < MAX_CONCURRENT_NUM) {
207 ++curWorkerNum_;
208 mutex_.unlock();
209 if (CallStartComposite(sourceVideoPath, videoPath, editData, assetPath, isNeedScan) != E_OK) {
210 mutex_.lock();
211 --curWorkerNum_;
212 mutex_.unlock();
213 MEDIA_ERR_LOG("Failed to CallStartComposite, path:%{private}s", videoPath.c_str());
214 CHECK_AND_RETURN_LOG(HandleAddFiltersError(sourceVideoPath, videoPath),
215 "Copy sourceVideoPath to videoPath, path:%{private}s", sourceVideoPath.c_str());
216 }
217 } else {
218 MEDIA_WARN_LOG("Failed to CallStartComposite, curWorkerNum over MAX_CONCURRENT_NUM");
219 Task newWaitTask{sourceVideoPath, videoPath, editData, assetPath, isNeedScan};
220 waitQueue_.push(std::move(newWaitTask));
221 mutex_.unlock();
222 }
223 }
224
EraseStickerField(std::string & editData,size_t index,bool isTimingSticker)225 void VideoCompositionCallbackImpl::EraseStickerField(std::string& editData, size_t index, bool isTimingSticker)
226 {
227 auto begin = index - START_DISTANCE;
228 auto end = index;
229 while (editData[end] != '}') {
230 ++end;
231 }
232 if (!isTimingSticker) {
233 ++end;
234 }
235 auto len = end - begin + 1;
236 editData.erase(begin, len);
237 }
238
EraseWatermarkTag(std::string & editData)239 void VideoCompositionCallbackImpl::EraseWatermarkTag(std::string& editData)
240 {
241 CHECK_AND_RETURN_LOG(nlohmann::json::accept(editData),
242 "Failed to verify the editData format, editData is: %{public}s", editData.c_str());
243 nlohmann::json data = nlohmann::json::parse(editData);
244 if (data.contains(IMAGE_EFFECT) && data[IMAGE_EFFECT].contains(FILTERS_FIELD)) {
245 nlohmann::json filters = data[IMAGE_EFFECT][FILTERS_FIELD];
246 nlohmann::json newFilters;
247 for (const auto& filter : filters) {
248 if (!filter.contains(FILTER_CATEGORY) || filter[FILTER_CATEGORY] != BORDER_WATERMARK) {
249 newFilters.push_back(filter);
250 }
251 }
252 nlohmann::json newData = data;
253 newData[IMAGE_EFFECT][FILTERS_FIELD] = newFilters;
254 editData = newData.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace);
255 }
256 }
257
InitCallbackImpl(std::shared_ptr<VideoCompositionCallbackImpl> & callBack,int32_t inputFileFd,int32_t outputFileFd,const std::string & videoPath,std::string & absSourceVideoPath,const std::string & assetPath,bool isNeedScan)258 void VideoCompositionCallbackImpl::InitCallbackImpl(std::shared_ptr<VideoCompositionCallbackImpl>& callBack,
259 int32_t inputFileFd, int32_t outputFileFd, const std::string& videoPath, std::string& absSourceVideoPath,
260 const std::string& assetPath, bool isNeedScan)
261 {
262 callBack->inputFileFd_ = inputFileFd;
263 callBack->outputFileFd_ = outputFileFd;
264 callBack->videoPath_ = videoPath;
265 callBack->sourceVideoPath_ = absSourceVideoPath;
266 callBack->assetPath_ = assetPath;
267 callBack->isNeedScan_ = isNeedScan;
268 }
269 // LCOV_EXCL_STOP
270 } // end of namespace
271 }