Playlist::Entries SIDPlay::fetchTracks(const QString &url, bool &ok) { Playlist::Entries entries; if (open(url, true)) { const int tracks = m_tune->getInfo()->songs(); for (int i = 0; i < tracks; ++i) { const SidTuneInfo *info = m_tune->getInfo(i); if (info) { Playlist::Entry entry; entry.url = SIDPlayName + QString("://{%1}%2").arg(m_url).arg(i); entry.name = getTitle(info, i); entry.length = m_length; entries.append(entry); } } if (entries.length() > 1) { for (int i = 0; i < entries.length(); ++i) entries[i].parent = 1; Playlist::Entry entry; entry.name = Functions::fileName(m_url, false); entry.url = m_url; entry.GID = 1; entries.prepend(entry); } } ok = !entries.isEmpty(); return entries; }
Playlist::Entries GME::fetchTracks(const QString &url, bool &ok) { Playlist::Entries entries; if (open(url, true)) { const int tracks = gme_track_count(m_gme); for (int i = 0; i < tracks; ++i) { gme_info_t *info = NULL; if (!gme_track_info(m_gme, &info, i) && info) { Playlist::Entry entry; entry.url = GMEName + QString("://{%1}%2").arg(m_url).arg(i); entry.name = getTitle(info, i); entry.length = getLength(info); gme_free_info(info); entries.append(entry); } } if (entries.length() > 1) { for (int i = 0; i < entries.length(); ++i) entries[i].parent = 1; Playlist::Entry entry; entry.name = Functions::fileName(m_url, false); entry.url = m_url; entry.GID = 1; entries.prepend(entry); } } ok = !entries.isEmpty(); return entries; }
bool AddThr::add(const QStringList &urls, QTreeWidgetItem *parent, const Functions::DemuxersInfo &demuxersInfo, QStringList *existingEntries, bool loadList) { const bool displayOnlyFileName = QMPlay2Core.getSettings().getBool("DisplayOnlyFileName"); QTreeWidgetItem *currentItem = parent; QSet<int> playlistIndexesToSkip; bool added = false; if (!loadList && QMPlay2Core.getSettings().getBool("SkipPlaylistsWithinFiles")) { // Don't load playlist within other files const auto e = Playlist::extensions(); for (int i = 0; i < urls.size(); ++i) { const QString ext = Functions::fileExt(urls.at(i)).toLower(); if (e.contains(ext)) playlistIndexesToSkip.insert(i); } if (playlistIndexesToSkip.count() == urls.count()) playlistIndexesToSkip.clear(); } for (int i = 0; i < urls.size(); ++i) { if (ioCtrl.isAborted()) break; if (playlistIndexesToSkip.contains(i)) continue; const QString entryName = QMPlay2Core.getNameForUrl(urls.at(i)); // Get the default entry name - it'll be used if doesn't exist in stream QString url = Functions::Url(urls.at(i)); int insertChildAt = -1; if (existingEntries) { //For quick group sync only - find where to place the new item const QString newUrl = url.startsWith("file://") ? url.mid(7) : url; const QString newFileName = Functions::fileName(newUrl); const QString filePath = Functions::filePath(newUrl); const bool newIsDir = QFileInfo(newUrl).isDir(); insertChildAt = existingEntries->count(); for (int i = 0; i < insertChildAt; ++i) { const QString fileName = existingEntries->at(i); const bool isDir = QFileInfo(filePath + fileName).isDir(); if ((newIsDir && !isDir) || (newIsDir == isDir && fileName > newFileName)) { insertChildAt = i; break; } } } QString name; const Playlist::Entries entries = Playlist::read(url, &name); if (!name.isEmpty()) //Loading playlist { QTreeWidgetItem *playlistParent = currentItem; if (loadList) //Loading QMPlay2 playlist on startup pLW.clear(); //This can be executed only from GUI thread! else { const QString groupName = Functions::fileName(url, false); if (sync != FILE_SYNC) playlistParent = pLW.newGroup(groupName, url, currentItem, insertChildAt, existingEntries); //Adding a new playlist group else { //Reuse current playlist group QMetaObject::invokeMethod(this, "changeItemText0", Q_ARG(QTreeWidgetItem *, currentItem), Q_ARG(QString, groupName)); sync = NO_SYNC; } } QTreeWidgetItem *tmpFirstItem = insertPlaylistEntries(entries, playlistParent, demuxersInfo, insertChildAt, existingEntries); if (!firstItem) firstItem = tmpFirstItem; added = !entries.isEmpty(); if (loadList) break; } else if (!loadList)
Playlist::Entries FFDemux::fetchTracks(const QString &url, bool &ok) { if (url.contains("://{") || !url.startsWith("file://")) return {}; const auto createFmtCtx = [&] { FormatContext *fmtCtx = new FormatContext; { QMutexLocker mL(&mutex); formatContexts.append(fmtCtx); } return fmtCtx; }; const auto destroyFmtCtx = [&](FormatContext *fmtCtx) { { QMutexLocker mL(&mutex); const int idx = formatContexts.indexOf(fmtCtx); if (idx > -1) formatContexts.remove(idx); } delete fmtCtx; }; if (url.endsWith(".cue", Qt::CaseInsensitive)) { QFile cue(url.mid(7)); if (cue.size() <= 0xFFFF && cue.open(QFile::ReadOnly | QFile::Text)) { QList<QByteArray> data = Functions::textWithFallbackEncoding(cue.readAll()).split('\n'); QString title, performer, audioUrl; double index0 = -1.0, index1 = -1.0; QHash<int, QPair<double, double>> indexes; Playlist::Entries entries; Playlist::Entry entry; int track = -1; const auto maybeFlushTrack = [&](int prevTrack) { if (track <= 0) return; const auto cutFromQuotation = [](QString &str) { const int idx1 = str.indexOf('"'); const int idx2 = str.lastIndexOf('"'); if (idx1 > -1 && idx2 > idx1) str = str.mid(idx1 + 1, idx2 - idx1 - 1); else str.clear(); }; cutFromQuotation(title); cutFromQuotation(performer); if (!title.isEmpty() && !performer.isEmpty()) entry.name = performer + " - " + title; else if (title.isEmpty() && !performer.isEmpty()) entry.name = performer; else if (!title.isEmpty() && performer.isEmpty()) entry.name = title; if (entry.name.isEmpty()) { if (entry.parent == 1) entry.name = tr("Track") + " " + QString::number(prevTrack); else entry.name = Functions::fileName(entry.url, false); } title.clear(); performer.clear(); if (prevTrack > 0) { if (index0 <= 0.0) index0 = index1; // "INDEX 00" doesn't exist, use "INDEX 01" indexes[prevTrack].first = index0; indexes[prevTrack].second = index1; if (entries.count() <= prevTrack) entries.resize(prevTrack + 1); entries[prevTrack] = entry; } else { entries.prepend(entry); } entry = Playlist::Entry(); entry.parent = 1; entry.url = audioUrl; index0 = index1 = -1.0; }; const auto parseTime = [](const QByteArray &time) { int m = 0, s = 0, f = 0; if (sscanf(time.constData(), "%2d:%2d:%2d", &m, &s, &f) == 3) return m * 60.0 + s + f / 75.0; return -1.0; }; entry.url = url; entry.GID = 1; for (QByteArray &line : data) { line = line.trimmed(); if (track < 0) { if (line.startsWith("TITLE ")) title = line; else if (line.startsWith("PERFORMER ")) performer = line; else if (line.startsWith("FILE ")) { const int idx = line.lastIndexOf('"'); if (idx > -1) { audioUrl = line.mid(6, idx - 6); if (!audioUrl.isEmpty()) audioUrl.prepend(Functions::filePath(url)); } } } else if (line.startsWith("FILE ")) { // QMPlay2 supports CUE files which uses only single audio file return {}; } if (line.startsWith("TRACK ")) { if (entries.isEmpty() && audioUrl.isEmpty()) break; if (line.endsWith(" AUDIO")) { const int prevTrack = track; track = line.mid(6, 2).toInt(); if (track < 99) maybeFlushTrack(prevTrack); else track = 0; } else { track = 0; } } else if (track > 0) { if (line.startsWith("TITLE ")) title = line; else if (line.startsWith("PERFORMER ")) performer = line; else if (line.startsWith("INDEX 00 ")) index0 = parseTime(line.mid(9)); else if (line.startsWith("INDEX 01 ")) index1 = parseTime(line.mid(9)); } } maybeFlushTrack(track); for (int i = entries.count() - 1; i >= 1; --i) { Playlist::Entry &entry = entries[i]; const bool lastItem = (i == entries.count() - 1); const double start = indexes.value(i).second; const double end = indexes.value(i + 1, {-1.0, -1.0}).first; if (entry.url.isEmpty() || start < 0.0 || (end <= 0.0 && !lastItem)) { entries.removeAt(i); continue; } const QString param = QString("CUE:%1:%2").arg(start).arg(end); if (lastItem && end < 0.0) // Last entry doesn't have specified length in CUE file { FormatContext *fmtCtx = createFmtCtx(); if (fmtCtx->open(entry.url, param)) entry.length = fmtCtx->length(); destroyFmtCtx(fmtCtx); if (abortFetchTracks) { ok = false; return {}; } } else { entry.length = end - start; } entry.url = QString("%1://{%2}%3").arg(DemuxerName, entry.url, param); } if (!entries.isEmpty()) return entries; } } OggHelper oggHelper(url.mid(7), abortFetchTracks); if (oggHelper.io) { Playlist::Entries entries; int i = 0; for (const OggHelper::Chain &chains : oggHelper.getOggChains(ok)) { const QString param = QString("OGG:%1:%2:%3").arg(++i).arg(chains.first).arg(chains.second); FormatContext *fmtCtx = createFmtCtx(); if (fmtCtx->open(url, param)) { Playlist::Entry entry; entry.url = QString("%1://{%2}%3").arg(DemuxerName, url, param); entry.name = fmtCtx->title(); entry.length = fmtCtx->length(); entries.append(entry); } destroyFmtCtx(fmtCtx); if (abortFetchTracks) { ok = false; return {}; } } if (ok && !entries.isEmpty()) { for (int i = 0; i < entries.count(); ++i) entries[i].parent = 1; Playlist::Entry entry; entry.name = Functions::fileName(url, false); entry.url = url; entry.GID = 1; entries.prepend(entry); return entries; } } return {}; }