1 /*
2  *  Copyright (C) 2014 Arne Morten Kvarving
3  *
4  *  SPDX-License-Identifier: GPL-2.0-or-later
5  *  See LICENSES/README.md for more information.
6  */
7 
8 #include "AudioBookFileDirectory.h"
9 
10 #include "FileItem.h"
11 #include "TextureDatabase.h"
12 #include "URL.h"
13 #include "Util.h"
14 #include "filesystem/File.h"
15 #include "guilib/LocalizeStrings.h"
16 #include "music/tags/MusicInfoTag.h"
17 #include "utils/StringUtils.h"
18 
19 using namespace XFILE;
20 
cfile_file_read(void * h,uint8_t * buf,int size)21 static int cfile_file_read(void *h, uint8_t* buf, int size)
22 {
23   CFile* pFile = static_cast<CFile*>(h);
24   return pFile->Read(buf, size);
25 }
26 
cfile_file_seek(void * h,int64_t pos,int whence)27 static int64_t cfile_file_seek(void *h, int64_t pos, int whence)
28 {
29   CFile* pFile = static_cast<CFile*>(h);
30   if(whence == AVSEEK_SIZE)
31     return pFile->GetLength();
32   else
33     return pFile->Seek(pos, whence & ~AVSEEK_FORCE);
34 }
35 
~CAudioBookFileDirectory(void)36 CAudioBookFileDirectory::~CAudioBookFileDirectory(void)
37 {
38   if (m_fctx)
39     avformat_close_input(&m_fctx);
40   if (m_ioctx)
41   {
42     av_free(m_ioctx->buffer);
43     av_free(m_ioctx);
44   }
45 }
46 
GetDirectory(const CURL & url,CFileItemList & items)47 bool CAudioBookFileDirectory::GetDirectory(const CURL& url,
48                                            CFileItemList &items)
49 {
50   if (!m_fctx && !ContainsFiles(url))
51     return true;
52 
53   std::string title;
54   std::string author;
55   std::string album;
56 
57   AVDictionaryEntry* tag=nullptr;
58   while ((tag = av_dict_get(m_fctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
59   {
60     if (StringUtils::CompareNoCase(tag->key, "title") == 0)
61       title = tag->value;
62     else if (StringUtils::CompareNoCase(tag->key, "album") == 0)
63       album = tag->value;
64     else if (StringUtils::CompareNoCase(tag->key, "artist") == 0)
65       author = tag->value;
66   }
67 
68   std::string thumb;
69   if (m_fctx->nb_chapters > 1)
70     thumb = CTextureUtils::GetWrappedImageURL(url.Get(), "music");
71 
72   for (size_t i=0;i<m_fctx->nb_chapters;++i)
73   {
74     tag=nullptr;
75     std::string chaptitle = StringUtils::Format(g_localizeStrings.Get(25010).c_str(), i+1);
76     std::string chapauthor;
77     std::string chapalbum;
78     while ((tag=av_dict_get(m_fctx->chapters[i]->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
79     {
80       if (StringUtils::CompareNoCase(tag->key, "title") == 0)
81         chaptitle = tag->value;
82       else if (StringUtils::CompareNoCase(tag->key, "artist") == 0)
83         chapauthor = tag->value;
84       else if (StringUtils::CompareNoCase(tag->key, "album") == 0)
85         chapalbum = tag->value;
86     }
87     CFileItemPtr item(new CFileItem(url.Get(),false));
88     item->GetMusicInfoTag()->SetTrackNumber(i+1);
89     item->GetMusicInfoTag()->SetLoaded(true);
90     item->GetMusicInfoTag()->SetTitle(chaptitle);
91     if (album.empty())
92       item->GetMusicInfoTag()->SetAlbum(title);
93     else if (chapalbum.empty())
94       item->GetMusicInfoTag()->SetAlbum(album);
95     else
96       item->GetMusicInfoTag()->SetAlbum(chapalbum);
97     if (chapauthor.empty())
98       item->GetMusicInfoTag()->SetArtist(author);
99     else
100       item->GetMusicInfoTag()->SetArtist(chapauthor);
101 
102     item->SetLabel(StringUtils::Format("{0:02}. {1} - {2}",i+1,
103                    item->GetMusicInfoTag()->GetAlbum().c_str(),
104                    item->GetMusicInfoTag()->GetTitle()).c_str());
105     item->m_lStartOffset = CUtil::ConvertSecsToMilliSecs(m_fctx->chapters[i]->start*av_q2d(m_fctx->chapters[i]->time_base));
106     item->m_lEndOffset = m_fctx->chapters[i]->end*av_q2d(m_fctx->chapters[i]->time_base);
107     int compare = m_fctx->duration / (AV_TIME_BASE);
108     if (item->m_lEndOffset < 0 || item->m_lEndOffset > compare)
109     {
110       if (i < m_fctx->nb_chapters-1)
111         item->m_lEndOffset = m_fctx->chapters[i+1]->start*av_q2d(m_fctx->chapters[i+1]->time_base);
112       else
113         item->m_lEndOffset = compare;
114     }
115     item->m_lEndOffset = CUtil::ConvertSecsToMilliSecs(item->m_lEndOffset);
116     item->GetMusicInfoTag()->SetDuration(CUtil::ConvertMilliSecsToSecsInt(item->m_lEndOffset - item->m_lStartOffset));
117     item->SetProperty("item_start", item->m_lStartOffset);
118     if (!thumb.empty())
119       item->SetArt("thumb", thumb);
120     items.Add(item);
121   }
122 
123   return true;
124 }
125 
Exists(const CURL & url)126 bool CAudioBookFileDirectory::Exists(const CURL& url)
127 {
128   return CFile::Exists(url) && ContainsFiles(url);
129 }
130 
ContainsFiles(const CURL & url)131 bool CAudioBookFileDirectory::ContainsFiles(const CURL& url)
132 {
133   CFile file;
134   if (!file.Open(url))
135     return false;
136 
137   uint8_t* buffer = (uint8_t*)av_malloc(32768);
138   m_ioctx = avio_alloc_context(buffer, 32768, 0, &file, cfile_file_read,
139                                nullptr, cfile_file_seek);
140 
141   m_fctx = avformat_alloc_context();
142   m_fctx->pb = m_ioctx;
143 
144   if (file.IoControl(IOCTRL_SEEK_POSSIBLE, nullptr) == 0)
145     m_ioctx->seekable = 0;
146 
147   m_ioctx->max_packet_size = 32768;
148 
149   AVInputFormat* iformat=nullptr;
150   av_probe_input_buffer(m_ioctx, &iformat, url.Get().c_str(), nullptr, 0, 0);
151 
152   bool contains = false;
153   if (avformat_open_input(&m_fctx, url.Get().c_str(), iformat, nullptr) < 0)
154   {
155     if (m_fctx)
156       avformat_close_input(&m_fctx);
157     av_free(m_ioctx->buffer);
158     av_free(m_ioctx);
159     return false;
160   }
161 
162   contains = m_fctx->nb_chapters > 1;
163 
164   return contains;
165 }
166