2 * Copyright (c) 2013 Samsung Electronics Co., Ltd All Rights Reserved
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include <metadata_editor.h>
18 #include <metadata_editor_private.h>
19 #include <sys/types.h>
25 #include <taglib/tag.h>
26 #include <taglib/fileref.h>
27 #include <taglib/tpropertymap.h>
28 #include <taglib/mpegfile.h>
29 #include <taglib/mp4file.h>
30 #include <taglib/flacfile.h>
31 #include <taglib/oggflacfile.h>
32 #include <taglib/vorbisfile.h>
33 #include <taglib/wavfile.h>
34 #include <taglib/attachedpictureframe.h>
35 #include <taglib/tfilestream.h>
37 using namespace TagLib;
39 #define MIME_TYPE_JPEG "image/jpeg"
40 #define MIME_TYPE_PNG "image/png"
42 static String __get_file_ext(const char *file_path)
44 String s = String(file_path, String::UTF8);
45 const int pos = s.rfind(".");
47 return s.substr(pos + 1).upper();
51 static const char * __get_picture_type(const char *path)
53 String ext = __get_file_ext(path);
54 if (ext == "JPG" || ext == "JPEG")
55 return MIME_TYPE_JPEG;
65 explicit PictureFrame(const char *path)
66 : __stream(path, true) {}
67 virtual ~PictureFrame() = default;
70 return __stream.readBlock(__stream.length());
74 return __get_picture_type(__stream.name());
78 return __stream.isOpen();
81 MP4::CoverArt::Format mp4format() const {
82 if (mime() == MIME_TYPE_JPEG)
83 return MP4::CoverArt::JPEG;
84 if (mime() == MIME_TYPE_PNG)
85 return MP4::CoverArt::PNG;
86 return MP4::CoverArt::Unknown;
94 static bool __is_valid_index(const List<T>& lst, int index)
96 ME_RETVM_IF(lst.isEmpty(), false, "No picture");
97 ME_RETVM_IF((index < 0) || (lst.size() <= static_cast<uint>(index)),
98 false, "Out of range:size[%d] index[%d]", lst.size(), index);
102 static int __get_APIC(ID3v2::Tag *tag, int index, void **picture, int *size, char **mime_type)
104 ME_RETVM_IF(!tag, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Tag does not exist");
106 auto lst = tag->frameListMap()["APIC"];
107 ME_RETV_IF(!__is_valid_index(lst, index), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
109 auto pictureFrame = static_cast<ID3v2::AttachedPictureFrame*>(lst[index]);
111 uint pictureSize = pictureFrame->picture().size();
112 ME_RETVM_IF(pictureSize == 0, METADATA_EDITOR_ERROR_OPERATION_FAILED, "Size of picture is 0");
114 *picture = g_memdup2(pictureFrame->picture().data(), pictureSize);
116 *mime_type = g_strdup(pictureFrame->mimeType().toCString());
118 return METADATA_EDITOR_ERROR_NONE;
121 static int __get_flac_picture(List<FLAC::Picture *> lst, int index, void **picture, int *size, char **mime_type)
123 ME_RETV_IF(!__is_valid_index(lst, index), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
125 auto pictureFrame = static_cast<FLAC::Picture*>(lst[index]);
126 int pictureSize = pictureFrame->data().size();
127 ME_RETVM_IF(pictureSize == 0, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Size of picture is 0");
129 *picture = g_memdup2(pictureFrame->data().data(), pictureSize);
131 *mime_type = g_strdup(pictureFrame->mimeType().toCString());
133 return METADATA_EDITOR_ERROR_NONE;
136 static int __append_APIC(ID3v2::Tag *tag, PictureFrame& pic)
138 ME_RETVM_IF(!tag, METADATA_EDITOR_ERROR_METADATA_UPDATE_NOT_POSSIBLE, "Invalid ID3v2");
140 auto pictureFrame = new ID3v2::AttachedPictureFrame();
141 pictureFrame->setPicture(pic.data());
142 pictureFrame->setMimeType(pic.mime());
144 tag->addFrame(pictureFrame);
146 return METADATA_EDITOR_ERROR_NONE;
149 static int __append_ogg_picture(Ogg::XiphComment *xtag, PictureFrame& pic)
151 ME_RETVM_IF(!xtag, METADATA_EDITOR_ERROR_METADATA_UPDATE_NOT_POSSIBLE, "Invalid XiphComment");
153 auto frontCover = new FLAC::Picture;
154 frontCover->setData(pic.data());
155 frontCover->setMimeType(pic.mime());
157 xtag->addPicture(frontCover);
159 return METADATA_EDITOR_ERROR_NONE;
162 static int __remove_APIC(ID3v2::Tag *tag, int index)
164 ME_RETVM_IF(!tag, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid ID3v2");
166 auto lst = tag->frameListMap()["APIC"];
167 ME_RETV_IF(!__is_valid_index(lst, index), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
169 tag->removeFrame(lst[index]);
171 return METADATA_EDITOR_ERROR_NONE;
174 static int __remove_ogg_picture(Ogg::XiphComment *xtag, int index)
176 ME_RETVM_IF(!xtag, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid XiphComment");
177 ME_RETV_IF(!__is_valid_index(xtag->pictureList(), index), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
179 /* xiphComment::removePicture works abnormally. Do not modify this fuction.
180 - Use xtag->pictureList()[index] : crashed
181 - Use copied xtag->pictureList()[index] : crashed
182 - Use iterator with std::next() : heap-use-after-free occured
184 List<FLAC::Picture *>::Iterator it = xtag->pictureList().begin();
185 std::advance(it, index);
187 /* No need to set it to 'true'. taglib sets auto-delete. */
188 xtag->removePicture(*it, false);
190 return METADATA_EDITOR_ERROR_NONE;
196 virtual ~IAlbumArt() = default;
198 virtual int append(PictureFrame &pic) = 0;
199 virtual int remove(int index) = 0;
200 virtual int read(int index, void **picture, int *size, char **mime_type) = 0;
201 virtual uint count() = 0;
204 class Mp3AlbumArt : public IAlbumArt {
206 explicit Mp3AlbumArt(FileRef *fileref)
207 : __file(dynamic_cast<MPEG::File *>(fileref->file())) {}
208 ~Mp3AlbumArt() override = default;
210 int append(PictureFrame &pic) override {
211 return __append_APIC(__file->ID3v2Tag(true), pic);
214 int remove(int index) override {
215 return __remove_APIC(__file->ID3v2Tag(), index);
218 int read(int index, void **picture, int *size, char **mime_type) override {
219 return __get_APIC(__file->ID3v2Tag(), index, picture, size, mime_type);
222 uint count() override {
223 return __file->ID3v2Tag() ? __file->ID3v2Tag()->frameListMap()["APIC"].size() : 0;
230 class Mp4AlbumArt : public IAlbumArt {
232 explicit Mp4AlbumArt(FileRef *fileref)
233 : __file(dynamic_cast<MP4::File *>(fileref->file())) {
234 if (__file && __file->tag()->contains("covr"))
235 __coverList = __file->tag()->item("covr").toCoverArtList();
237 ~Mp4AlbumArt() override = default;
239 int append(PictureFrame &pic) override {
240 __coverList.append(MP4::CoverArt(pic.mp4format(), pic.data()));
241 __file->tag()->setItem("covr", __coverList);
243 return METADATA_EDITOR_ERROR_NONE;
246 int remove(int index) override {
247 ME_RETV_IF(!__is_valid_index(__coverList, index), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
249 __coverList.erase(std::next(__coverList.begin(), index));
250 __file->tag()->setItem("covr", __coverList);
252 return METADATA_EDITOR_ERROR_NONE;
255 int read(int index, void **picture, int *size, char **mime_type) override {
256 ME_RETV_IF(!__is_valid_index(__coverList, index), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
258 auto coverArt = static_cast<MP4::CoverArt>(__coverList[index]);
259 int coverArtSize = coverArt.data().size();
260 ME_RETVM_IF(coverArtSize == 0, METADATA_EDITOR_ERROR_OPERATION_FAILED, "Size of picture is 0");
262 *picture = g_memdup2(coverArt.data().data(), coverArtSize);
263 *size = coverArtSize;
264 if (coverArt.format() == MP4::CoverArt::JPEG)
265 *mime_type = g_strdup(MIME_TYPE_JPEG);
266 else if (coverArt.format() == MP4::CoverArt::PNG)
267 *mime_type = g_strdup(MIME_TYPE_PNG);
269 return METADATA_EDITOR_ERROR_NONE;
272 uint count() override {
273 return __coverList.size();
278 MP4::CoverArtList __coverList;
281 class FlacAlbumArt : public IAlbumArt {
283 explicit FlacAlbumArt(FileRef *fileref)
284 : __file(dynamic_cast<FLAC::File *>(fileref->file())) {}
285 ~FlacAlbumArt() override = default;
287 int append(PictureFrame &pic) override {
288 auto frontCover = new FLAC::Picture;
289 frontCover->setData(pic.data());
290 frontCover->setMimeType(pic.mime());
291 __file->addPicture(frontCover);
292 return METADATA_EDITOR_ERROR_NONE;
295 int remove(int index) override {
296 auto lst = __file->pictureList();
297 ME_RETV_IF(!__is_valid_index(lst, index), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
299 /* No need to set it to 'true'. taglib sets auto-delete. */
300 __file->removePicture(lst[index], false);
301 return METADATA_EDITOR_ERROR_NONE;
304 int read(int index, void **picture, int *size, char **mime_type) override {
305 return __get_flac_picture(__file->pictureList(), index, picture, size, mime_type);
308 uint count() override {
309 return __file->pictureList().size();
316 class OggVorbisAlbumArt : public IAlbumArt {
318 explicit OggVorbisAlbumArt(FileRef *fileref)
319 : __file(dynamic_cast<Ogg::Vorbis::File *>(fileref->file())) {}
320 ~OggVorbisAlbumArt() override = default;
322 int append(PictureFrame &pic) override {
323 return __append_ogg_picture(__file->tag(), pic);
326 int remove(int index) override {
327 return __remove_ogg_picture(__file->tag(), index);
330 int read(int index, void **picture, int *size, char **mime_type) override {
331 ME_RETVM_IF(!__file->tag(), METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid XiphComment");
332 return __get_flac_picture(__file->tag()->pictureList(), index, picture, size, mime_type);
335 uint count() override {
336 return __file->tag() ? __file->tag()->pictureList().size() : 0;
340 Ogg::Vorbis::File *__file;
343 class OggFlacAlbumArt : public IAlbumArt {
345 explicit OggFlacAlbumArt(FileRef *fileref)
346 : __file(dynamic_cast<Ogg::FLAC::File *>(fileref->file())) {}
347 ~OggFlacAlbumArt() override = default;
349 int append(PictureFrame &pic) override {
350 return __append_ogg_picture(__file->tag(), pic);
353 int remove(int index) override {
354 return __remove_ogg_picture(__file->tag(), index);
357 int read(int index, void **picture, int *size, char **mime_type) override {
358 ME_RETVM_IF(!__file->tag(), METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid XiphComment");
359 return __get_flac_picture(__file->tag()->pictureList(), index, picture, size, mime_type);
362 uint count() override {
363 return __file->tag() ? __file->tag()->pictureList().size() : 0;
367 Ogg::FLAC::File *__file;
370 class WavAlbumArt : public IAlbumArt {
372 explicit WavAlbumArt(FileRef *fileref)
373 : __file(dynamic_cast<RIFF::WAV::File *>(fileref->file())) {}
374 ~WavAlbumArt() override = default;
376 int append(PictureFrame &pic) override {
377 return __append_APIC(__file->tag(), pic);
380 int remove(int index) override {
381 return __remove_APIC(__file->tag(), index);
384 int read(int index, void **picture, int *size, char **mime_type) override {
385 return __get_APIC(__file->tag(), index, picture, size, mime_type);
388 uint count() override {
389 return __file->tag() ? __file->tag()->frameListMap()["APIC"].size() : 0;
393 RIFF::WAV::File *__file;
396 class AlbumArtFactory {
398 static IAlbumArt* create(FileRef *fref) {
402 if (dynamic_cast<MPEG::File *>(fref->file()))
403 return new Mp3AlbumArt(fref);
404 if (dynamic_cast<MP4::File *>(fref->file()))
405 return new Mp4AlbumArt(fref);
406 if (dynamic_cast<FLAC::File *>(fref->file()))
407 return new FlacAlbumArt(fref);
408 if (dynamic_cast<RIFF::WAV::File *>(fref->file()))
409 return new WavAlbumArt(fref);
410 if (dynamic_cast<Ogg::Vorbis::File *>(fref->file()))
411 return new OggVorbisAlbumArt(fref);
412 if (dynamic_cast<Ogg::FLAC::File *>(fref->file()))
413 return new OggFlacAlbumArt(fref);
419 typedef struct _metadata_editor_s {
424 static int __check_file_validity(const char *path)
426 ME_RETVM_IF(!path, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid path");
427 ME_SDBG("path : [%s]", path);
429 if (access(path, R_OK) < 0) {
430 if (errno == EACCES || errno == EPERM) {
431 ME_ERR("Permission denied");
432 return METADATA_EDITOR_ERROR_PERMISSION_DENIED;
435 ME_ERR("Fail to open path");
436 return METADATA_EDITOR_ERROR_FILE_EXISTS;
439 return METADATA_EDITOR_ERROR_NONE;
442 static int __is_valid_handle(metadata_editor_s *metadata, bool isWritable)
444 ME_RETVM_IF(!metadata, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid metadata");
445 ME_RETVM_IF(!metadata->fref, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "File loading fail");
446 ME_RETVM_IF(isWritable && metadata->fref->file()->readOnly(), METADATA_EDITOR_ERROR_PERMISSION_DENIED, "Readonly error");
447 ME_RETVM_IF(!metadata->ifart, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid tag");
448 return METADATA_EDITOR_ERROR_NONE;
451 static int __set_to_property_map(File *file, String key, const char *value)
453 ME_RETVM_IF(!file, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid file");
454 ME_RETVM_IF(key.isEmpty(), METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid key");
456 PropertyMap tags = file->properties();
458 if (value == NULL || strlen(value) == 0)
461 tags.replace(key, StringList(String(value, String::UTF8)));
463 file->setProperties(tags);
465 return METADATA_EDITOR_ERROR_NONE;
468 static int __get_from_property_map(PropertyMap tags, String key, char **value)
470 ME_RETVM_IF(key.isEmpty(), METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid field_name");
471 ME_RETVM_IF(!value, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid value");
473 ME_RETV_IF(tags.isEmpty(), METADATA_EDITOR_ERROR_NONE);
475 PropertyMap::ConstIterator found = tags.find(key);
477 if (found != tags.end())
478 *value = g_strdup(found->second[0].toCString(true));
480 return METADATA_EDITOR_ERROR_NONE;
483 class OggFileTypeResolver : public FileRef::FileTypeResolver {
484 File *createFile(FileName fileName, bool, AudioProperties::ReadStyle) const {
485 String ext = __get_file_ext(fileName);
487 File *file = new Ogg::FLAC::File(fileName);
491 return new Ogg::Vorbis::File(fileName);
497 extern "C" int metadata_editor_create(metadata_editor_h *metadata)
499 ME_RETVM_IF(!metadata, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid metadata");
501 metadata_editor_s* _metadata = (metadata_editor_s*)malloc(sizeof(metadata_editor_s));
502 memset(_metadata, 0, sizeof(metadata_editor_s));
504 *metadata = (metadata_editor_h)_metadata;
506 return METADATA_EDITOR_ERROR_NONE;
509 extern "C" int metadata_editor_destroy(metadata_editor_h metadata)
511 auto _metadata = static_cast<metadata_editor_s*>(metadata);
512 ME_RETVM_IF(!_metadata, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid metadata");
514 delete _metadata->fref;
515 delete _metadata->ifart;
518 return METADATA_EDITOR_ERROR_NONE;
521 extern "C" int metadata_editor_set_path(metadata_editor_h metadata, const char *path)
523 int ret = METADATA_EDITOR_ERROR_NONE;
524 auto _metadata = static_cast<metadata_editor_s*>(metadata);
525 ME_RETVM_IF(!_metadata, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid metadata");
527 ret = __check_file_validity(path);
528 ME_RETVM_IF(ret != METADATA_EDITOR_ERROR_NONE, ret, "Invalid path");
530 if (_metadata->fref) {
531 ME_INFO("file free [%p]", _metadata->fref);
532 delete _metadata->fref;
533 delete _metadata->ifart;
537 //FileTypeResolver is not deleted until terminated. So, it should be called only once.
538 static auto oggResolver = FileRef::addFileTypeResolver(new OggFileTypeResolver);
539 ME_INFO("%p", oggResolver);
540 _metadata->fref = new FileRef(path);
541 } catch (const std::bad_alloc &ex) {
542 ME_RETVM_IF(!_metadata->fref, METADATA_EDITOR_ERROR_OUT_OF_MEMORY, "OUT_OF_MEMORY");
545 if (!_metadata->fref->file()->isOpen()) {
546 delete _metadata->fref;
547 _metadata->fref = nullptr;
548 return METADATA_EDITOR_ERROR_PERMISSION_DENIED;
551 _metadata->ifart = AlbumArtFactory::create(_metadata->fref);
553 return METADATA_EDITOR_ERROR_NONE;
556 extern "C" int metadata_editor_append_picture(metadata_editor_h metadata, const char *path)
558 int ret = METADATA_EDITOR_ERROR_NONE;
559 auto _metadata = static_cast<metadata_editor_s*>(metadata);
560 ME_RETV_IF(__is_valid_handle(_metadata, true), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
562 ret = __check_file_validity(path);
563 ME_RETVM_IF(ret != METADATA_EDITOR_ERROR_NONE, ret, "Invalid path");
565 PictureFrame pic(path);
566 ME_RETVM_IF(!pic.opened(), METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid path");
568 return _metadata->ifart->append(pic);
571 extern "C" int metadata_editor_remove_picture(metadata_editor_h metadata, int index)
573 auto _metadata = static_cast<metadata_editor_s*>(metadata);
574 ME_RETV_IF(__is_valid_handle(_metadata, true), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
576 return _metadata->ifart->remove(index);
579 extern "C" int metadata_editor_get_picture(metadata_editor_h metadata, int index, void **picture, int *size, char **mime_type)
581 auto _metadata = static_cast<metadata_editor_s*>(metadata);
582 ME_RETV_IF(__is_valid_handle(_metadata, false), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
583 ME_RETVM_IF(!picture, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid picture");
584 ME_RETVM_IF(!size, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid size");
585 ME_RETVM_IF(!mime_type, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid mime_type");
587 return _metadata->ifart->read(index, picture, size, mime_type);
590 static const char * __get_attr_str(metadata_editor_attr_e attribute)
593 case METADATA_EDITOR_ATTR_ARTIST:
595 case METADATA_EDITOR_ATTR_TITLE:
597 case METADATA_EDITOR_ATTR_ALBUM:
599 case METADATA_EDITOR_ATTR_GENRE:
601 case METADATA_EDITOR_ATTR_AUTHOR:
603 case METADATA_EDITOR_ATTR_COPYRIGHT:
605 case METADATA_EDITOR_ATTR_DATE:
607 case METADATA_EDITOR_ATTR_DESCRIPTION:
608 return "DESCRIPTION";
609 case METADATA_EDITOR_ATTR_COMMENT:
611 case METADATA_EDITOR_ATTR_TRACK_NUM:
612 return "TRACKNUMBER";
613 case METADATA_EDITOR_ATTR_CONDUCTOR:
615 case METADATA_EDITOR_ATTR_UNSYNCLYRICS:
622 extern "C" int metadata_editor_set_metadata(metadata_editor_h metadata, metadata_editor_attr_e attribute, const char *value)
624 auto _metadata = static_cast<metadata_editor_s*>(metadata);
625 ME_RETV_IF(__is_valid_handle(_metadata, true), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
627 return __set_to_property_map(_metadata->fref->file(), __get_attr_str(attribute), value);
630 extern "C" int metadata_editor_get_metadata(metadata_editor_h metadata, metadata_editor_attr_e attribute, char **value)
632 auto _metadata = static_cast<metadata_editor_s*>(metadata);
633 ME_RETV_IF(__is_valid_handle(_metadata, false), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
634 ME_RETVM_IF(!value, METADATA_EDITOR_ERROR_INVALID_PARAMETER, "Invalid value");
636 /* exceptional case */
637 if (attribute == METADATA_EDITOR_ATTR_PICTURE_NUM) {
638 *value = g_strdup_printf("%u", _metadata->ifart->count());
639 return METADATA_EDITOR_ERROR_NONE;
642 return __get_from_property_map(_metadata->fref->file()->properties(), __get_attr_str(attribute), value);
645 extern "C" int metadata_editor_update_metadata(metadata_editor_h metadata)
647 auto _metadata = static_cast<metadata_editor_s*>(metadata);
648 ME_RETV_IF(__is_valid_handle(_metadata, true), METADATA_EDITOR_ERROR_INVALID_PARAMETER);
650 if (!_metadata->fref->file()->save())
651 return METADATA_EDITOR_ERROR_OPERATION_FAILED;
653 return METADATA_EDITOR_ERROR_NONE;