1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "MusicInfoTagLoaderFFmpeg.h"
10
11 #include "MusicInfoTag.h"
12 #include "cores/FFmpeg.h"
13 #include "filesystem/File.h"
14 #include "utils/StringUtils.h"
15
16 using namespace MUSIC_INFO;
17 using namespace XFILE;
18
vfs_file_read(void * h,uint8_t * buf,int size)19 static int vfs_file_read(void *h, uint8_t* buf, int size)
20 {
21 CFile* pFile = static_cast<CFile*>(h);
22 return pFile->Read(buf, size);
23 }
24
vfs_file_seek(void * h,int64_t pos,int whence)25 static int64_t vfs_file_seek(void *h, int64_t pos, int whence)
26 {
27 CFile* pFile = static_cast<CFile*>(h);
28 if (whence == AVSEEK_SIZE)
29 return pFile->GetLength();
30 else
31 return pFile->Seek(pos, whence & ~AVSEEK_FORCE);
32 }
33
34 CMusicInfoTagLoaderFFmpeg::CMusicInfoTagLoaderFFmpeg(void) = default;
35
36 CMusicInfoTagLoaderFFmpeg::~CMusicInfoTagLoaderFFmpeg() = default;
37
Load(const std::string & strFileName,CMusicInfoTag & tag,EmbeddedArt * art)38 bool CMusicInfoTagLoaderFFmpeg::Load(const std::string& strFileName, CMusicInfoTag& tag, EmbeddedArt *art)
39 {
40 tag.SetLoaded(false);
41
42 CFile file;
43 if (!file.Open(strFileName))
44 return false;
45
46 int bufferSize = 4096;
47 int blockSize = file.GetChunkSize();
48 if (blockSize > 1)
49 bufferSize = blockSize;
50 uint8_t* buffer = (uint8_t*)av_malloc(bufferSize);
51 AVIOContext* ioctx = avio_alloc_context(buffer, bufferSize, 0,
52 &file, vfs_file_read, NULL,
53 vfs_file_seek);
54
55 AVFormatContext* fctx = avformat_alloc_context();
56 fctx->pb = ioctx;
57
58 if (file.IoControl(IOCTRL_SEEK_POSSIBLE, NULL) != 1)
59 ioctx->seekable = 0;
60
61 AVInputFormat* iformat=NULL;
62 av_probe_input_buffer(ioctx, &iformat, strFileName.c_str(), NULL, 0, 0);
63
64 if (avformat_open_input(&fctx, strFileName.c_str(), iformat, NULL) < 0)
65 {
66 if (fctx)
67 avformat_close_input(&fctx);
68 av_free(ioctx->buffer);
69 av_free(ioctx);
70 return false;
71 }
72
73 /* ffmpeg supports the return of ID3v2 metadata but has its own naming system
74 for some, but not all, of the keys. In particular the key for the conductor
75 tag TPE3 is called "performer".
76 See https://github.com/xbmc/FFmpeg/blob/master/libavformat/id3v2.c#L43
77 Other keys are retuened using their 4 char name.
78 Only single frame values are returned even for v2.4 fomart tags e.g. while
79 tagged with multiple TPE1 frames "artist1", "artist2", "artist3" only
80 "artist1" is returned by ffmpeg.
81 Hence, like with v2.3 format tags, multiple values for artist, genre etc.
82 need to be combined when tagging into a single value using a known item
83 separator e.g. "artist1 / artist2 / artist3"
84
85 Any changes to ID3v2 tag processing in CTagLoaderTagLib need to be
86 repeated here
87 */
88 auto&& ParseTag = [&tag](AVDictionaryEntry* avtag)
89 {
90 if (StringUtils::CompareNoCase(avtag->key, "album") == 0)
91 tag.SetAlbum(avtag->value);
92 else if (StringUtils::CompareNoCase(avtag->key, "artist") == 0)
93 tag.SetArtist(avtag->value);
94 else if (StringUtils::CompareNoCase(avtag->key, "album_artist") == 0 ||
95 StringUtils::CompareNoCase(avtag->key, "album artist") == 0)
96 tag.SetAlbumArtist(avtag->value);
97 else if (StringUtils::CompareNoCase(avtag->key, "title") == 0)
98 tag.SetTitle(avtag->value);
99 else if (StringUtils::CompareNoCase(avtag->key, "genre") == 0)
100 tag.SetGenre(avtag->value);
101 else if (StringUtils::CompareNoCase(avtag->key, "part_number") == 0 ||
102 StringUtils::CompareNoCase(avtag->key, "track") == 0)
103 tag.SetTrackNumber(
104 static_cast<int>(strtol(avtag->value, nullptr, 10)));
105 else if (StringUtils::CompareNoCase(avtag->key, "disc") == 0)
106 tag.SetDiscNumber(
107 static_cast<int>(strtol(avtag->value, nullptr, 10)));
108 else if (StringUtils::CompareNoCase(avtag->key, "date") == 0)
109 tag.SetReleaseDate(avtag->value);
110 else if (StringUtils::CompareNoCase(avtag->key, "compilation") == 0)
111 tag.SetCompilation((strtol(avtag->value, nullptr, 10) == 0) ? false : true);
112 else if (StringUtils::CompareNoCase(avtag->key, "encoded_by") == 0) {}
113 else if (StringUtils::CompareNoCase(avtag->key, "composer") == 0)
114 tag.AddArtistRole("Composer", avtag->value);
115 else if (StringUtils::CompareNoCase(avtag->key, "performer") == 0) // Conductor or TPE3 tag
116 tag.AddArtistRole("Conductor", avtag->value);
117 else if (StringUtils::CompareNoCase(avtag->key, "TEXT") == 0)
118 tag.AddArtistRole("Lyricist", avtag->value);
119 else if (StringUtils::CompareNoCase(avtag->key, "TPE4") == 0)
120 tag.AddArtistRole("Remixer", avtag->value);
121 else if (StringUtils::CompareNoCase(avtag->key, "LABEL") == 0 ||
122 StringUtils::CompareNoCase(avtag->key, "TPUB") == 0)
123 tag.SetRecordLabel(avtag->value);
124 else if (StringUtils::CompareNoCase(avtag->key, "copyright") == 0 ||
125 StringUtils::CompareNoCase(avtag->key, "TCOP") == 0) {} // Copyright message
126 else if (StringUtils::CompareNoCase(avtag->key, "TDRC") == 0)
127 tag.SetReleaseDate(avtag->value);
128 else if (StringUtils::CompareNoCase(avtag->key, "TDOR") == 0 ||
129 StringUtils::CompareNoCase(avtag->key, "TORY") == 0)
130 tag.SetOriginalDate(avtag->value);
131 else if (StringUtils::CompareNoCase(avtag->key , "TDAT") == 0)
132 tag.AddReleaseDate(avtag->value, true); // MMDD part
133 else if (StringUtils::CompareNoCase(avtag->key, "TYER") == 0)
134 tag.AddReleaseDate(avtag->value); // YYYY part
135 else if (StringUtils::CompareNoCase(avtag->key, "TBPM") == 0)
136 tag.SetBPM(static_cast<int>(strtol(avtag->value, nullptr, 10)));
137 else if (StringUtils::CompareNoCase(avtag->key, "TDTG") == 0) {} // Tagging time
138 else if (StringUtils::CompareNoCase(avtag->key, "language") == 0 ||
139 StringUtils::CompareNoCase(avtag->key, "TLAN") == 0) {} // Languages
140 else if (StringUtils::CompareNoCase(avtag->key, "mood") == 0 ||
141 StringUtils::CompareNoCase(avtag->key, "TMOO") == 0)
142 tag.SetMood(avtag->value);
143 else if (StringUtils::CompareNoCase(avtag->key, "artist-sort") == 0 ||
144 StringUtils::CompareNoCase(avtag->key, "TSOP") == 0) {}
145 else if (StringUtils::CompareNoCase(avtag->key, "TSO2") == 0) {} // Album artist sort
146 else if (StringUtils::CompareNoCase(avtag->key, "TSOC") == 0) {} // composer sort
147 else if (StringUtils::CompareNoCase(avtag->key, "TSST") == 0)
148 tag.SetDiscSubtitle(avtag->value);
149 };
150
151 AVDictionaryEntry* avtag=nullptr;
152 while ((avtag = av_dict_get(fctx->metadata, "", avtag, AV_DICT_IGNORE_SUFFIX)))
153 ParseTag(avtag);
154
155 const AVStream* st = fctx->streams[0];
156 if (st)
157 while ((avtag = av_dict_get(st->metadata, "", avtag, AV_DICT_IGNORE_SUFFIX)))
158 ParseTag(avtag);
159
160 if (!tag.GetTitle().empty())
161 tag.SetLoaded(true);
162
163 avformat_close_input(&fctx);
164 av_free(ioctx->buffer);
165 av_free(ioctx);
166
167 return true;
168 }
169