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