MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
musicutils.cpp
Go to the documentation of this file.
1 // c/c++
2 #include <iostream>
3 
4 // qt
5 #include <QFile>
6 #include <QRegExp>
7 #include <QDir>
8 
9 // mythtv
10 #include <mythdirs.h>
11 #include <mythlogging.h>
12 #include <mythcorecontext.h>
13 #include <remotefile.h>
14 
15 extern "C" {
16 #include <libavformat/avformat.h>
17 #include <libavcodec/avcodec.h>
18 }
19 
20 // libmythmetadata
21 #include "musicmetadata.h"
22 #include "musicutils.h"
23 
24 static QString musicDirectory;
25 
26 QString getMusicDirectory(void)
27 {
28  if (musicDirectory.isEmpty())
29  {
30  musicDirectory = gCoreContext->GetSetting("MusicLocation");
31  musicDirectory = QDir::cleanPath(musicDirectory);
32  if (!musicDirectory.isEmpty() && !musicDirectory.endsWith("/"))
33  musicDirectory += "/";
34  }
35 
36  return musicDirectory;
37 }
38 
39 void setMusicDirectory(const QString &musicDir)
40 {
41  musicDirectory = musicDir;
42 }
43 
44 static QRegExp badChars = QRegExp("(/|\\\\|:|\'|\"|\\?|\\|)");
45 
46 QString fixFilename(const QString &filename)
47 {
48  QString ret = filename;
49  ret.replace(badChars, "_");
50  return ret;
51 }
52 
53 QString findIcon(const QString &type, const QString &name)
54 {
55  QString cleanName = fixFilename(name);
56  QString file = QString("Icons/%1/%2").arg(type).arg(cleanName);
57 
58  // first look in the 'MusicArt' storage group
59  QString filename = gCoreContext->GenMythURL(gCoreContext->GetSetting("MasterServerIP"),
60  gCoreContext->GetNumSetting("MasterServerPort"),
61  file, "MusicArt");
62 
63  if (RemoteFile::Exists(filename + ".jpg"))
64  return filename + ".jpg";
65 
66  if (RemoteFile::Exists(filename + ".jpeg"))
67  return filename + ".jpeg";
68 
69  if (RemoteFile::Exists(filename + ".png"))
70  return filename + ".png";
71 
72  if (RemoteFile::Exists(filename + ".gif"))
73  return filename + ".gif";
74 
75  // not found so try the local config directory
76  file = GetConfDir() + "MythMusic/" + file;
77 
78  if (QFile::exists(file + ".jpg"))
79  return file + ".jpg";
80 
81  if (QFile::exists(file + ".jpeg"))
82  return file + ".jpeg";
83 
84  if (QFile::exists(file + ".png"))
85  return file + ".png";
86 
87  if (QFile::exists(file + ".gif"))
88  return file + ".gif";
89 
90  LOG(VB_FILE, LOG_INFO, QString("findicon: not found for type: %1, name: %2").arg(type).arg(name));
91 
92  return QString();
93 }
94 
95 //TODO this needs updating to also use storage groups
96 uint calcTrackLength(const QString &musicFile)
97 {
98 // const char *type = NULL;
99 
100  AVFormatContext *inputFC = NULL;
101  AVInputFormat *fmt = NULL;
102 
103 // if (type)
104 // fmt = av_find_input_format(type);
105 
106  // Open recording
107  LOG(VB_GENERAL, LOG_DEBUG, QString("calcTrackLength: Opening '%1'")
108  .arg(musicFile));
109 
110  QByteArray inFileBA = musicFile.toLocal8Bit();
111 
112  int ret = avformat_open_input(&inputFC, inFileBA.constData(), fmt, NULL);
113 
114  if (ret)
115  {
116  LOG(VB_GENERAL, LOG_ERR, "calcTrackLength: Couldn't open input file" +
117  ENO);
118  return 0;
119  }
120 
121  // Getting stream information
122  ret = avformat_find_stream_info(inputFC, NULL);
123 
124  if (ret < 0)
125  {
126  LOG(VB_GENERAL, LOG_ERR,
127  QString("calcTrackLength: Couldn't get stream info, error #%1").arg(ret));
128  avformat_close_input(&inputFC);
129  inputFC = NULL;
130  return 0;
131  }
132 
133  uint duration = 0;
134  long long time = 0;
135 
136  for (uint i = 0; i < inputFC->nb_streams; i++)
137  {
138  AVStream *st = inputFC->streams[i];
139  char buf[256];
140 
141  avcodec_string(buf, sizeof(buf), st->codec, false);
142 
143  switch (inputFC->streams[i]->codec->codec_type)
144  {
145  case AVMEDIA_TYPE_AUDIO:
146  {
147  AVPacket pkt;
148  av_init_packet(&pkt);
149 
150  while (av_read_frame(inputFC, &pkt) >= 0)
151  {
152  if (pkt.stream_index == (int)i)
153  time = time + pkt.duration;
154 
155  av_free_packet(&pkt);
156  }
157 
158  duration = time * av_q2d(inputFC->streams[i]->time_base);
159  break;
160  }
161 
162  default:
163  LOG(VB_GENERAL, LOG_ERR,
164  QString("Skipping unsupported codec %1 on stream %2")
165  .arg(inputFC->streams[i]->codec->codec_type).arg(i));
166  break;
167  }
168  }
169 
170  // Close input file
171  avformat_close_input(&inputFC);
172  inputFC = NULL;
173 
174  return duration;
175 }
176 
177 inline QString fixFileToken_sl(QString token)
178 {
179  // this version doesn't remove fwd-slashes so we can
180  // pick them up later and create directories as required
181  token.replace(QRegExp("(\\\\|:|\'|\"|\\?|\\|)"), QString("_"));
182  return token;
183 }
184 
185 QString filenameFromMetadata(MusicMetadata *track, bool createDir)
186 {
187  QDir directoryQD(getMusicDirectory());
188  QString filename;
189  QString fntempl = gCoreContext->GetSetting("FilenameTemplate");
190  bool no_ws = gCoreContext->GetNumSetting("NoWhitespace", 0);
191 
192  QRegExp rx_ws("\\s{1,}");
193  QRegExp rx("(GENRE|ARTIST|ALBUM|TRACK|TITLE|YEAR)");
194  int i = 0;
195  int old_i = 0;
196  while (i >= 0)
197  {
198  i = rx.indexIn(fntempl, i);
199  if (i >= 0)
200  {
201  if (i > 0)
202  filename += fixFileToken_sl(fntempl.mid(old_i,i-old_i));
203  i += rx.matchedLength();
204  old_i = i;
205 
206  if ((rx.capturedTexts()[1] == "GENRE") && (!track->Genre().isEmpty()))
207  filename += fixFilename(track->Genre());
208 
209  if ((rx.capturedTexts()[1] == "ARTIST")
210  && (!track->FormatArtist().isEmpty()))
211  filename += fixFilename(track->FormatArtist());
212 
213  if ((rx.capturedTexts()[1] == "ALBUM") && (!track->Album().isEmpty()))
214  filename += fixFilename(track->Album());
215 
216  if ((rx.capturedTexts()[1] == "TRACK") && (track->Track() >= 0))
217  {
218  QString tempstr = QString::number(track->Track(), 10);
219  if (track->Track() < 10)
220  tempstr.prepend('0');
221  filename += fixFilename(tempstr);
222  }
223 
224  if ((rx.capturedTexts()[1] == "TITLE")
225  && (!track->FormatTitle().isEmpty()))
226  filename += fixFilename(track->FormatTitle());
227 
228  if ((rx.capturedTexts()[1] == "YEAR") && (track->Year() >= 0))
229  filename += fixFilename(QString::number(track->Year(), 10));
230  }
231  }
232 
233  if (no_ws)
234  filename.replace(rx_ws, "_");
235 
236 
237  if (filename == "" || filename.length() > FILENAME_MAX)
238  {
239  QString tempstr = QString::number(track->Track(), 10);
240  tempstr += " - " + track->FormatTitle();
241  filename = fixFilename(tempstr);
242  LOG(VB_GENERAL, LOG_ERR, "Invalid file storage definition.");
243  }
244 
245  if (createDir)
246  {
247  QFileInfo fi(filename);
248  if (!directoryQD.mkpath(getMusicDirectory() + fi.path()))
249  LOG(VB_GENERAL, LOG_ERR,
250  QString("filenameFromMetadata: Failed to create directory path: '%1'").arg(getMusicDirectory() + filename));
251  }
252 
253  return filename;
254 }
255 
256 bool isNewTune(const QString& artist, const QString& album, const QString& title)
257 {
258 
259  QString matchartist = artist;
260  QString matchalbum = album;
261  QString matchtitle = title;
262 
263  if (! matchartist.isEmpty())
264  {
265  matchartist.replace(QRegExp("(/|\\\\|:|\'|\\,|\\!|\\(|\\)|\"|\\?|\\|)"), QString("_"));
266  }
267 
268  if (! matchalbum.isEmpty())
269  {
270  matchalbum.replace(QRegExp("(/|\\\\|:|\'|\\,|\\!|\\(|\\)|\"|\\?|\\|)"), QString("_"));
271  }
272 
273  if (! matchtitle.isEmpty())
274  {
275  matchtitle.replace(QRegExp("(/|\\\\|:|\'|\\,|\\!|\\(|\\)|\"|\\?|\\|)"), QString("_"));
276  }
277 
278  MSqlQuery query(MSqlQuery::InitCon());
279  QString queryString("SELECT filename, artist_name,"
280  " album_name, name, song_id "
281  "FROM music_songs "
282  "LEFT JOIN music_artists"
283  " ON music_songs.artist_id=music_artists.artist_id "
284  "LEFT JOIN music_albums"
285  " ON music_songs.album_id=music_albums.album_id "
286  "WHERE artist_name LIKE :ARTIST "
287  "AND album_name LIKE :ALBUM "
288  "AND name LIKE :TITLE "
289  "ORDER BY artist_name, album_name,"
290  " name, song_id, filename");
291 
292  query.prepare(queryString);
293 
294  query.bindValue(":ARTIST", matchartist);
295  query.bindValue(":ALBUM", matchalbum);
296  query.bindValue(":TITLE", matchtitle);
297 
298  if (!query.exec() || !query.isActive())
299  {
300  MythDB::DBError("Search music database", query);
301  return true;
302  }
303 
304  if (query.size() > 0)
305  return false;
306 
307  return true;
308 }