Ejemplo n.º 1
0
void SearchView::ShowMenu(void)
{
    if (GetFocusWidget() == m_tracksList)
    {
        QString label = tr("Search Actions");

        MythMenu *menu = new MythMenu(label, this, "searchviewmenu");

        MythUIButtonListItem *item = m_tracksList->GetItemCurrent();
        if (item)
        {
            MusicMetadata *mdata = qVariantValue<MusicMetadata*> (item->GetData());
            if (mdata)
            {
                if (gPlayer->getCurrentPlaylist() && gPlayer->getCurrentPlaylist()->checkTrack(mdata->ID()))
                    menu->AddItem(tr("Remove From Playlist"));
                else
                {
                    menu->AddItem(tr("Add To Playlist"));
                    menu->AddItem(tr("Add To Playlist And Play"));
                }
            }
        }

        if (GetFocusWidget() == m_tracksList || GetFocusWidget() == m_currentPlaylist)
            menu->AddItem(tr("Search List..."));

        menu->AddItem(tr("More Options"), NULL, createSubMenu());

        MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");

        MythDialogBox *menuPopup = new MythDialogBox(menu, popupStack, "actionmenu");

        if (menuPopup->Create())
            popupStack->AddScreen(menuPopup);
        else
            delete menu;
    }
    else
        MusicCommon::ShowMenu();
}
Ejemplo n.º 2
0
void Ripper::ScanFinished()
{
    delete m_scanThread;
    m_scanThread = nullptr;

    m_tracks->clear();

    if (m_decoder)
    {
        MusicMetadata *metadata;
        bool isCompilation = false;

        m_artistName.clear();
        m_albumName.clear();
        m_genreName.clear();
        m_year.clear();

        for (int trackno = 0; trackno < m_decoder->getNumTracks(); trackno++)
        {
            RipTrack *ripTrack = new RipTrack;

            metadata = m_decoder->getMetadata(trackno + 1);
            if (metadata)
            {
                ripTrack->metadata = metadata;
                ripTrack->length = metadata->Length();

                if (metadata->Compilation())
                {
                    isCompilation = true;
                    m_artistName = metadata->CompilationArtist();
                }
                else if (m_artistName.isEmpty())
                {
                    m_artistName = metadata->Artist();
                }

                if (m_albumName.isEmpty())
                    m_albumName = metadata->Album();

                if (m_genreName.isEmpty() && !metadata->Genre().isEmpty())
                    m_genreName = metadata->Genre();

                if (m_year.isEmpty() && metadata->Year() > 0)
                    m_year = QString::number(metadata->Year());

                QString title = metadata->Title();
                ripTrack->isNew = isNewTune(m_artistName, m_albumName, title);

                ripTrack->active = ripTrack->isNew;

                m_tracks->push_back(ripTrack);

            }
            else
                delete ripTrack;
        }

        m_artistEdit->SetText(m_artistName);
        m_albumEdit->SetText(m_albumName);
        m_genreEdit->SetText(m_genreName);
        m_yearEdit->SetText(m_year);
        m_compilationCheck->SetCheckState(isCompilation);

        if (!isCompilation)
            m_switchTitleArtist->SetVisible(false);
        else
            m_switchTitleArtist->SetVisible(true);
    }

    BuildFocusList();
    updateTrackList();

    CloseBusyPopup();
}
Ejemplo n.º 3
0
void CDRipperThread::run(void)
{
    RunProlog();

    if (m_tracks->size() <= 0)
    {
        RunEpilog();
        return;
    }

    m_totalSectors = 0;
    m_totalSectorsDone = 0;
    for (int trackno = 0; trackno < m_tracks->size(); trackno++)
    {
        m_totalSectors += getSectorCount(m_CDdevice, trackno + 1);
    }

    if (!m_totalSectors)
    {
        RunEpilog();
        return;
    }

    MusicMetadata *track = m_tracks->at(0)->metadata;
    QString tots;

    if (track->Compilation())
    {
        tots = track->CompilationArtist() + " ~ " + track->Album();
    }
    else
    {
        tots = track->Artist() + " ~ " + track->Album();
    }

    QApplication::postEvent(
        m_parent,
        new RipStatusEvent(RipStatusEvent::kOverallTextEvent, tots));
    QApplication::postEvent(
        m_parent,
        new RipStatusEvent(RipStatusEvent::kOverallProgressEvent, 0));
    QApplication::postEvent(
        m_parent,
        new RipStatusEvent(RipStatusEvent::kTrackProgressEvent, 0));

    QString textstatus;
    QString encodertype = gCoreContext->GetSetting("EncoderType");
    bool mp3usevbr = gCoreContext->GetNumSetting("Mp3UseVBR", 0);

    QApplication::postEvent(m_parent,
        new RipStatusEvent(RipStatusEvent::kOverallStartEvent, m_totalSectors));

    if (LCD *lcd = LCD::Get())
    {
        QString lcd_tots = tr("Importing %1").arg(tots);
        QList<LCDTextItem> textItems;
        textItems.append(LCDTextItem(1, ALIGN_CENTERED,
                                         lcd_tots, "Generic", false));
        lcd->switchToGeneric(textItems);
    }

    MusicMetadata *titleTrack = nullptr;
    QString saveDir = GetConfDir() + "/tmp/RipTemp/";
    QString outfile;

    std::unique_ptr<Encoder> encoder;

    for (int trackno = 0; trackno < m_tracks->size(); trackno++)
    {
        if (isCancelled())
            break;

        QApplication::postEvent(
            m_parent,
            new RipStatusEvent(RipStatusEvent::kStatusTextEvent,
                               QString("Track %1 of %2")
                               .arg(trackno + 1).arg(m_tracks->size())));

        QApplication::postEvent(
            m_parent,
            new RipStatusEvent(RipStatusEvent::kTrackProgressEvent, 0));

        track = m_tracks->at(trackno)->metadata;

        if (track)
        {
            textstatus = track->Title();
            QApplication::postEvent(
                m_parent,
                new RipStatusEvent(
                    RipStatusEvent::kTrackTextEvent, textstatus));
            QApplication::postEvent(
                m_parent,
                new RipStatusEvent(RipStatusEvent::kTrackProgressEvent, 0));
            QApplication::postEvent(
                m_parent,
                new RipStatusEvent(RipStatusEvent::kTrackPercentEvent, 0));

            // do we need to start a new file?
            if (m_tracks->at(trackno)->active)
            {
                titleTrack = track;
                titleTrack->setLength(m_tracks->at(trackno)->length);

                if (m_quality < 3)
                {
                    if (encodertype == "mp3")
                    {
                        outfile = QString("track%1.mp3").arg(trackno);
                        encoder.reset(new LameEncoder(saveDir + outfile, m_quality,
                                                      titleTrack, mp3usevbr));
                    }
                    else // ogg
                    {
                        outfile = QString("track%1.ogg").arg(trackno);
                        encoder.reset(new VorbisEncoder(saveDir + outfile, m_quality,
                                                        titleTrack));
                    }
                }
                else
                {
                    outfile = QString("track%1.flac").arg(trackno);
                    encoder.reset(new FlacEncoder(saveDir + outfile, m_quality,
                                                  titleTrack));
                }

                if (!encoder->isValid())
                {
                    QApplication::postEvent(
                        m_parent,
                        new RipStatusEvent(
                            RipStatusEvent::kEncoderErrorEvent,
                            "Encoder failed to open file for writing"));
                    LOG(VB_GENERAL, LOG_ERR, "MythMusic: Encoder failed"
                                             " to open file for writing");

                    RunEpilog();
                    return;
                }
            }

            if (!encoder.get())
            {
                // This should never happen.
                QApplication::postEvent(
                    m_parent,
                    new RipStatusEvent(RipStatusEvent::kEncoderErrorEvent,
                                       "Failed to create encoder"));
                LOG(VB_GENERAL, LOG_ERR, "MythMusic: No encoder, failing");
                RunEpilog();
                return;
            }
            ripTrack(m_CDdevice, encoder.get(), trackno + 1);

            if (isCancelled())
            {
                RunEpilog();
                return;
            }

            if (m_tracks->at(trackno)->active)
            {
                QString ext = QFileInfo(outfile).suffix();
                QString destFile = filenameFromMetadata(titleTrack) + '.' + ext;
                QUrl url(m_musicStorageDir);

                // save the metadata to the DB
                titleTrack->setFilename(destFile);
                titleTrack->setHostname(url.host());
                titleTrack->setFileSize((quint64)QFileInfo(outfile).size());
                titleTrack->dumpToDatabase();

                // this will delete the encoder which will write the metadata in it's dtor
                encoder.reset();

                // copy track to the BE
                destFile = gCoreContext->GenMythURL(url.host(), 0, destFile, "Music");

                QApplication::postEvent(m_parent, new RipStatusEvent(RipStatusEvent::kCopyStartEvent, 0));
                RemoteFile::CopyFile(saveDir + outfile, destFile, true);
                QApplication::postEvent(m_parent, new RipStatusEvent(RipStatusEvent::kCopyEndEvent, 0));
            }
        }
    }

    QString PostRipCDScript = gCoreContext->GetSetting("PostCDRipScript");

    if (!PostRipCDScript.isEmpty())
        myth_system(PostRipCDScript);

    QApplication::postEvent(
        m_parent, new RipStatusEvent(RipStatusEvent::kFinishedEvent, ""));

    RunEpilog();
}
Ejemplo n.º 4
0
bool avfDecoder::initialize()
{
    m_inited = m_userStop = m_finish = false;
    m_freq = m_bitrate = 0;
    m_stat = m_channels = 0;
    m_seekTime = -1.0;

    // give up if we dont have an audiooutput set
    if (!output())
    {
        error("avfDecoder: initialise called with a NULL audiooutput");
        return false;
    }

    if (!m_outputBuffer)
    {
        error("avfDecoder: couldn't allocate memory");
        return false;
    }

    output()->PauseUntilBuffered();

    if (m_inputContext)
        delete m_inputContext;

    m_inputContext = new RemoteAVFormatContext(getURL());

    if (!m_inputContext->isOpen())
    {
        error(QString("Could not open url  (%1)").arg(m_url));
        deinit();
        return false;
    }

    // if this is a ice/shoutcast or MMS stream start polling for metadata changes and buffer status
    if (getURL().startsWith("http://") || getURL().startsWith("mmsh://"))
    {
        m_mdataTimer = new QTimer;
        m_mdataTimer->setSingleShot(false);
        connect(m_mdataTimer, SIGNAL(timeout()), this, SLOT(checkMetatdata()));

        m_mdataTimer->start(500);

        // we don't get metadata updates for MMS streams so grab the metadata from the headers
        if (getURL().startsWith("mmsh://"))
        {
            AVDictionaryEntry *tag = nullptr;
            MusicMetadata mdata =  gPlayer->getDecoderHandler()->getMetadata();

            tag = av_dict_get(m_inputContext->getContext()->metadata, "title", tag, AV_DICT_IGNORE_SUFFIX);
            mdata.setTitle(tag->value);

            tag = av_dict_get(m_inputContext->getContext()->metadata, "artist", tag, AV_DICT_IGNORE_SUFFIX);
            mdata.setArtist(tag->value);

            mdata.setAlbum("");
            mdata.setLength(-1);

            DecoderHandlerEvent ev(DecoderHandlerEvent::Meta, mdata);
            dispatch(ev);
        }
    }

    // determine the stream format
    // this also populates information needed for metadata
    if (avformat_find_stream_info(m_inputContext->getContext(), nullptr) < 0)
    {
        error("Could not determine the stream format.");
        deinit();
        return false;
    }

    // let FFmpeg finds the best audio stream (should only be one), also catter
    // should the file/stream not be an audio one
    AVCodec *codec;
    int selTrack = av_find_best_stream(m_inputContext->getContext(), AVMEDIA_TYPE_AUDIO,
                                       -1, -1, &codec, 0);

    if (selTrack < 0)
    {
        error(QString("Could not find audio stream."));
        deinit();
        return false;
    }

    // Store the audio codec of the stream
    m_audioDec = gCodecMap->getCodecContext
        (m_inputContext->getContext()->streams[selTrack]);

    // Store the input format of the context
    m_inputFormat = m_inputContext->getContext()->iformat;

    if (avcodec_open2(m_audioDec, codec, nullptr) < 0)
    {
        error(QString("Could not open audio codec: %1")
            .arg(m_audioDec->codec_id));
        deinit();
        return false;
    }

    m_freq = m_audioDec->sample_rate;
    m_channels = m_audioDec->channels;

    if (m_channels <= 0)
    {
        error(QString("AVCodecContext tells us %1 channels are "
                                      "available, this is bad, bailing.")
                                      .arg(m_channels));
        deinit();
        return false;
    }

    AudioFormat format =
        AudioOutputSettings::AVSampleFormatToFormat(m_audioDec->sample_fmt,
                                                    m_audioDec->bits_per_raw_sample);
    if (format == FORMAT_NONE)
    {
        error(QString("Error: Unsupported sample format: %1")
              .arg(av_get_sample_fmt_name(m_audioDec->sample_fmt)));
        deinit();
        return false;
    }

    const AudioSettings settings(format, m_audioDec->channels,
                                 m_audioDec->codec_id,
                                 m_audioDec->sample_rate, false);

    output()->Reconfigure(settings);
    output()->SetSourceBitrate(m_audioDec->bit_rate);

    m_inited = true;
    return true;
}
Ejemplo n.º 5
0
/*!
 * \brief Insert file details into database.
 *        If it is an audio file, read the metadata and insert
 *        that information at the same time.
 *
 *        If it is an image file, just insert the filename and
 *        type.
 *
 * \param filename Full path to file.
 *
 * \returns Nothing.
 */
void MusicFileScanner::AddFileToDB(const QString &filename)
{
    QString extension = filename.section( '.', -1 ) ;
    QString directory = filename;
    directory.remove(0, m_startdir.length());
    directory = directory.section( '/', 0, -2);

    QString nameFilter = gCoreContext->GetSetting("AlbumArtFilter", "*.png;*.jpg;*.jpeg;*.gif;*.bmp");

    // If this file is an image, insert the details into the music_albumart table
    if (nameFilter.indexOf(extension.toLower()) > -1)
    {
        QString name = filename.section( '/', -1);

        MSqlQuery query(MSqlQuery::InitCon());
        query.prepare("INSERT INTO music_albumart SET filename = :FILE, "
                      "directory_id = :DIRID, imagetype = :TYPE;");
        query.bindValue(":FILE", name);
        query.bindValue(":DIRID", m_directoryid[directory]);
        query.bindValue(":TYPE", AlbumArtImages::guessImageType(name));

        if (!query.exec() || query.numRowsAffected() <= 0)
        {
            MythDB::DBError("music insert artwork", query);
        }
        return;
    }

    LOG(VB_FILE, LOG_INFO,
        QString("Reading metadata from %1").arg(filename));
    MusicMetadata *data = MetaIO::readMetadata(filename);
    if (data)
    {
        data->setFileSize((quint64)QFileInfo(filename).size());

        QString album_cache_string;

        // Set values from cache
        int did = m_directoryid[directory];
        if (did > 0)
            data->setDirectoryId(did);

        int aid = m_artistid[data->Artist().toLower()];
        if (aid > 0)
        {
            data->setArtistId(aid);

            // The album cache depends on the artist id
            album_cache_string = data->getArtistId() + "#"
                                 + data->Album().toLower();

            if (m_albumid[album_cache_string] > 0)
                data->setAlbumId(m_albumid[album_cache_string]);
        }

        int gid = m_genreid[data->Genre().toLower()];
        if (gid > 0)
            data->setGenreId(gid);

        // Commit track info to database
        data->dumpToDatabase();

        // Update the cache
        m_artistid[data->Artist().toLower()] =
            data->getArtistId();

        m_genreid[data->Genre().toLower()] =
            data->getGenreId();

        album_cache_string = data->getArtistId() + "#"
                             + data->Album().toLower();
        m_albumid[album_cache_string] = data->getAlbumId();

        // read any embedded images from the tag
        MetaIO *tagger = MetaIO::createTagger(filename);

        if (tagger)
        {
            if (tagger->supportsEmbeddedImages())
            {
                AlbumArtList artList = tagger->getAlbumArtList(data->Filename());
                data->setEmbeddedAlbumArt(artList);
                data->getAlbumArtImages()->dumpToDatabase();
            }
            delete tagger;
        }

        delete data;
    }
}
Ejemplo n.º 6
0
void SearchView::customEvent(QEvent *event)
{
    bool handled = false;

    if (event->type() == MusicPlayerEvent::TrackRemovedEvent ||
        event->type() == MusicPlayerEvent::TrackAddedEvent)
    {
        MusicPlayerEvent *mpe = dynamic_cast<MusicPlayerEvent *>(event);

        if (!mpe)
            return;

        int trackID = mpe->TrackID;

        for (int x = 0; x < m_tracksList->GetCount(); x++)
        {
            MythUIButtonListItem *item = m_tracksList->GetItemAt(x);
            MusicMetadata *mdata = qVariantValue<MusicMetadata*> (item->GetData());
            if (mdata && (mdata->ID() == (MusicMetadata::IdType) trackID || trackID == -1))
            {
                if (gPlayer->getCurrentPlaylist() && gPlayer->getCurrentPlaylist()->checkTrack(mdata->ID()))
                    item->DisplayState("on", "selectedstate");
                else
                    item->DisplayState("off", "selectedstate");
            }
        }

        // call the default handler in MusicCommon so the playlist and UI is updated
        MusicCommon::customEvent(event);
        handled = true;

        if (m_playTrack)
        {
            m_playTrack = false;

            if (event->type() == MusicPlayerEvent::TrackAddedEvent)
            {
                // make the added track current and play it
                m_currentPlaylist->SetItemCurrent(m_currentPlaylist->GetCount() - 1);
                playlistItemClicked(m_currentPlaylist->GetItemCurrent());
            }
        }
    }
    else if (event->type() == MusicPlayerEvent::AllTracksRemovedEvent)
    {
        for (int x = 0; x < m_tracksList->GetCount(); x++)
        {
            MythUIButtonListItem *item = m_tracksList->GetItemAt(x);
            if (item)
                item->DisplayState("off", "selectedstate");
        }
    }
    else if (event->type() == MusicPlayerEvent::MetadataChangedEvent)
    {
        MusicPlayerEvent *mpe = dynamic_cast<MusicPlayerEvent *>(event);

        if (!mpe)
            return;

        uint trackID = mpe->TrackID;

        for (int x = 0; x < m_tracksList->GetCount(); x++)
        {
            MythUIButtonListItem *item = m_tracksList->GetItemAt(x);
            MusicMetadata *mdata = qVariantValue<MusicMetadata*> (item->GetData());
            if (mdata && mdata->ID() == trackID)
            {
                InfoMap metadataMap;
                mdata->toMap(metadataMap);
                item->SetTextFromMap(metadataMap);
            }
        }

//        if (trackID == gPlayer->getCurrentMetadata()->ID())
//            updateTrackInfo(gPlayer->getCurrentMetadata());
    }
    else if (event->type() == DialogCompletionEvent::kEventType)
    {
        DialogCompletionEvent *dce = static_cast<DialogCompletionEvent *>(event);

        // make sure the user didn't ESCAPE out of the menu
        if (dce->GetResult() < 0)
            return;

        QString resultid   = dce->GetId();
        QString resulttext = dce->GetResultText();
        if (resultid == "searchviewmenu")
        {
            if (resulttext == tr("Add To Playlist") || resulttext == tr("Remove From Playlist"))
            {
                if (GetFocusWidget() == m_tracksList)
                {
                    MythUIButtonListItem *item = m_tracksList->GetItemCurrent();
                    if (item)
                    {
                        m_playTrack = false;
                        trackClicked(item);
                    }
                }
            }
            else if (resulttext == tr("Add To Playlist And Play"))
            {
                if (GetFocusWidget() == m_tracksList)
                {
                    MythUIButtonListItem *item = m_tracksList->GetItemCurrent();
                    if (item)
                    {
                        m_playTrack = true;
                        trackClicked(item);
                    }
                }
            }
            else if (resulttext == tr("Search List..."))
                searchButtonList();
        }
    }

    if (!handled)
        MusicCommon::customEvent(event);
}
Ejemplo n.º 7
0
void SearchView::updateTracksList(void)
{
    m_tracksList->Reset();

    MythUIButtonListItem *item = m_fieldList->GetItemCurrent();

    if (!item)
        return;

    QString searchStr = m_criteriaEdit->GetText();
    int field = item->GetData().toInt();

    QString sql;
    MSqlQuery query(MSqlQuery::InitCon());

    if (searchStr.isEmpty())
    {
        sql = "SELECT song_id "
              "FROM music_songs ";

        query.prepare(sql);
    }
    else
    {
        switch(field)
        {
            case 1: // artist
            {
                sql = "SELECT song_id "
                      "FROM music_songs "
                      "LEFT JOIN music_artists ON "
                      "    music_songs.artist_id=music_artists.artist_id "
                      "WHERE music_artists.artist_name LIKE '%" + searchStr + "%' ";
                query.prepare(sql);
                break;
            }
            case 2: // album
            {
                sql = "SELECT song_id "
                      "FROM music_songs "
                      "LEFT JOIN music_albums ON music_songs.album_id=music_albums.album_id "
                      "WHERE music_albums.album_name LIKE '%" + searchStr + "%' ";
                query.prepare(sql);
                break;
            }
            case 3: // title
            {
                sql = "SELECT song_id "
                      "FROM music_songs "
                      "WHERE music_songs.name LIKE '%" + searchStr + "%' ";
                query.prepare(sql);
                break;
            }
            case 4: // genre
            {
                sql = "SELECT song_id "
                      "FROM music_songs "
                      "LEFT JOIN music_genres ON music_songs.genre_id=music_genres.genre_id "
                      "WHERE music_genres.genre LIKE '%" + searchStr + "%' ";
                query.prepare(sql);
                break;
            }
            case 5: // tags
            {
                //TODO add tag query
            }
            case 0: // all fields
            default:
            {
                sql = "SELECT song_id "
                      "FROM music_songs "
                      "LEFT JOIN music_artists ON "
                      "    music_songs.artist_id=music_artists.artist_id "
                      "LEFT JOIN music_albums ON music_songs.album_id=music_albums.album_id "
                      "LEFT JOIN music_artists AS music_comp_artists ON "
                      "    music_albums.artist_id=music_comp_artists.artist_id "
                      "LEFT JOIN music_genres ON music_songs.genre_id=music_genres.genre_id "
                      "WHERE music_songs.name LIKE '%" + searchStr + "%' "
                      "OR music_artists.artist_name LIKE '%" + searchStr + "%' "
                      "OR music_albums.album_name LIKE '%" + searchStr + "%' "
                      "OR music_genres.genre LIKE '%" + searchStr + "%' ";

                query.prepare(sql);
            }
        }
    }

    if (!query.exec() || !query.isActive())
    {
        MythDB::DBError("Search music database", query);
        return;
    }

    while (query.next())
    {
        int trackid = query.value(0).toInt();

        MusicMetadata *mdata = gMusicData->all_music->getMetadata(trackid);
        if (mdata)
        {
            MythUIButtonListItem *newitem = new MythUIButtonListItem(m_tracksList, "");
            newitem->SetData(qVariantFromValue(mdata));
            InfoMap metadataMap;
            mdata->toMap(metadataMap);
            newitem->SetTextFromMap(metadataMap);

            if (gPlayer->getCurrentPlaylist() && gPlayer->getCurrentPlaylist()->checkTrack(mdata->ID()))
                newitem->DisplayState("on", "selectedstate");
            else
                newitem->DisplayState("off", "selectedstate");

            // TODO rating state etc
        }
    }

    trackVisible(m_tracksList->GetItemCurrent());

    if (m_matchesText)
        m_matchesText->SetText(QString("%1").arg(m_tracksList->GetCount()));
}
Ejemplo n.º 8
0
//virtual
MusicMetadata *CdDecoder::getMetadata()
{
    QString artist, album, compilation_artist, title, genre;
    int year = 0;
    unsigned long length = 0;
    track_t tracknum = 0;

    if (-1 == m_settracknum)
        tracknum = getFilename().toUInt();
    else
    {
        tracknum = m_settracknum;
        setFilename(QString("%1" CDEXT).arg(tracknum));
    }

    QMutexLocker lock(&getCdioMutex());

    StCdioDevice cdio(m_devicename);
    if (!cdio)
        return NULL;

    const track_t lastTrack = cdio_get_last_track_num(cdio);
    if (CDIO_INVALID_TRACK == lastTrack)
        return NULL;

    if (TRACK_FORMAT_AUDIO != cdio_get_track_format(cdio, tracknum))
        return NULL;

    // Assume disc changed if max LSN different
    bool isDiscChanged = false;
    static lsn_t s_totalSectors;
    lsn_t totalSectors = cdio_get_track_lsn(cdio, CDIO_CDROM_LEADOUT_TRACK);
    if (s_totalSectors != totalSectors)
    {
        s_totalSectors = totalSectors;
        isDiscChanged = true;
    }

    // NB cdio_get_track_last_lsn is unreliable for the last audio track
    // of discs with data tracks beyond
    lsn_t end = cdio_get_track_last_lsn(cdio, tracknum);
    if (isDiscChanged)
    {
        const track_t audioTracks = getNumCDAudioTracks();
        s_lastAudioLsn = cdio_get_track_last_lsn(cdio, audioTracks);

        if (audioTracks < lastTrack)
        {
            cdrom_drive_t *dev = cdio_cddap_identify_cdio(cdio, 0, NULL);
            if (NULL != dev)
            {
                if (DRIVER_OP_SUCCESS == cdio_cddap_open(dev))
                {
                    // NB this can be S L O W  but is reliable
                    lsn_t end2 = cdio_cddap_track_lastsector(dev,
                        getNumCDAudioTracks());
                    if (CDIO_INVALID_LSN != end2)
                        s_lastAudioLsn = end2;
                }
                cdio_cddap_close_no_free_cdio(dev);
            }
        }
    }

    if (s_lastAudioLsn && s_lastAudioLsn < end)
        end = s_lastAudioLsn;

    const lsn_t start = cdio_get_track_lsn(cdio, tracknum);
    if (CDIO_INVALID_LSN != start && CDIO_INVALID_LSN != end)
    {
        length = ((end - start + 1) * 1000 + CDIO_CD_FRAMES_PER_SEC/2) /
            CDIO_CD_FRAMES_PER_SEC;
    }

    bool isCompilation = false;

#define CDTEXT 0 // Disabled - cd-text access on discs without it is S L O W
#if CDTEXT
    static int s_iCdtext;
    if (isDiscChanged)
        s_iCdtext = -1;

    if (s_iCdtext)
    {
        // cdio_get_cdtext can't take >5 seconds on some CD's without cdtext
        if (s_iCdtext < 0)
            LOG(VB_MEDIA, LOG_INFO,
                QString("Getting cdtext for track %1...").arg(tracknum));
        cdtext_t * cdtext = cdio_get_cdtext(m_cdio, tracknum);
        if (NULL != cdtext)
        {
            genre = cdtext_get_const(CDTEXT_GENRE, cdtext);
            artist = cdtext_get_const(CDTEXT_PERFORMER, cdtext);
            title = cdtext_get_const(CDTEXT_TITLE, cdtext);
            const char* isrc = cdtext_get_const(CDTEXT_ISRC, cdtext);
            /* ISRC codes are 12 characters long, in the form CCXXXYYNNNNN
             * CC = country code
             * XXX = registrant e.g. BMG
             * CC = year withou century
             * NNNNN = unique ID
             */
            if (isrc && strlen(isrc) >= 7)
            {
                year = (isrc[5] - '0') * 10 + (isrc[6] - '0');
                year += (year <= 30) ? 2000 : 1900;
            }

            cdtext_destroy(cdtext);

            if (!title.isNull())
            {
                if (s_iCdtext < 0)
                    LOG(VB_MEDIA, LOG_INFO, "Found cdtext track title");
                s_iCdtext = 1;

                // Get disc info
                cdtext = cdio_get_cdtext(cdio, 0);
                if (NULL != cdtext)
                {
                    compilation_artist = cdtext_get_const(
                        CDTEXT_PERFORMER, cdtext);
                    if (!compilation_artist.isEmpty() &&
                            artist != compilation_artist)
                        isCompilation = true;

                    album = cdtext_get_const(CDTEXT_TITLE, cdtext);

                    if (genre.isNull())
                        genre = cdtext_get_const(CDTEXT_GENRE, cdtext);

                    cdtext_destroy(cdtext);
                }
            }
            else
            {
                if (s_iCdtext < 0)
                    LOG(VB_MEDIA, LOG_INFO, "No cdtext title for track");
                s_iCdtext = 0;
            }
        }
        else
        {
            if (s_iCdtext < 0)
                LOG(VB_MEDIA, LOG_INFO, "No cdtext");
            s_iCdtext = 0;
        }
    }

    if (title.isEmpty() || artist.isEmpty() || album.isEmpty())
#endif // CDTEXT
    {
        // CDDB lookup
        Cddb::Toc toc;
        Cddb::Matches r;
        if (Cddb::Query(r, GetToc(cdio, toc)))
        {
            Cddb::Matches::match_t::const_iterator select = r.matches.begin();

            if (r.matches.size() > 1)
            {
                // TODO prompt user to select one
                // In the meantime, select the first non-generic genre
                for (Cddb::Matches::match_t::const_iterator it = select;
                    it != r.matches.end(); ++it)
                {
                    QString g = it->discGenre.toLower();
                    if (g != "misc" && g != "data")
                    {
                        select = it;
                        break;
                    }
                }
            }

            Cddb::Album info;
            if (Cddb::Read(info, select->discGenre, select->discID))
            {
                isCompilation = info.isCompilation;

                if (info.genre.toLower() != "unknown")
                    genre = info.genre[0].toTitleCase() + info.genre.mid(1);
                else
                    genre = info.discGenre[0].toTitleCase() + info.discGenre.mid(1);;

                album = info.title;
                compilation_artist = info.artist;
                year = info.year;

                if (info.tracks.size() >= tracknum)
                {
                    const Cddb::Track& track = info.tracks[tracknum - 1];
                    title = track.title;
                    artist = track.artist;
                }

                // Create a temporary local alias for future lookups
                if (r.discID != info.discID)
                    Cddb::Alias(info, r.discID);
            }
        }
    }

    if (compilation_artist.toLower().left(7) == "various")
        compilation_artist = QObject::tr("Various Artists");

    if (artist.isEmpty())
    {
        artist = compilation_artist;
        compilation_artist.clear();
    }

    if (title.isEmpty())
        title = QObject::tr("Track %1").arg(tracknum);

    MusicMetadata *m = new MusicMetadata(getFilename(), artist, compilation_artist,
        album, title, genre, year, tracknum, length);
    if (m)
        m->setCompilation(isCompilation);

    return m;
}
Ejemplo n.º 9
0
/*!
 * \copydoc MetaIO::read()
 */
MusicMetadata *MetaIOID3::read(const QString &filename)
{
    if (!OpenFile(filename))
        return nullptr;

    TagLib::ID3v2::Tag *tag = GetID3v2Tag(true); // Create tag if none are found

    // if there is no ID3v2 tag, try to read the ID3v1 tag and copy it to
    // the ID3v2 tag structure
    if (tag->isEmpty())
    {
        TagLib::ID3v1::Tag *tag_v1 = GetID3v1Tag();

        if (!tag_v1)
            return nullptr;

        if (!tag_v1->isEmpty())
        {
            tag->setTitle(tag_v1->title());
            tag->setArtist(tag_v1->artist());
            tag->setAlbum(tag_v1->album());
            tag->setTrack(tag_v1->track());
            tag->setYear(tag_v1->year());
            tag->setGenre(tag_v1->genre());
        }
    }

    MusicMetadata *metadata = new MusicMetadata(filename);

    ReadGenericMetadata(tag, metadata);

    bool compilation = false;

    // Compilation Artist (TPE4 Remix) or fallback to (TPE2 Band)
    // N.B. The existance of a either frame is NOT an indication that this
    // is a compilation, but if it is then one of them will probably hold
    // the compilation artist.
    TextIdentificationFrame *tpeframe = nullptr;
    TagLib::ID3v2::FrameList tpelist = tag->frameListMap()["TPE4"];
    if (tpelist.isEmpty() || tpelist.front()->toString().isEmpty())
        tpelist = tag->frameListMap()["TPE2"];
    if (!tpelist.isEmpty())
        tpeframe = (TextIdentificationFrame *)tpelist.front();

    if (tpeframe && !tpeframe->toString().isEmpty())
    {
        QString compilation_artist = TStringToQString(tpeframe->toString())
                                                                    .trimmed();
        metadata->setCompilationArtist(compilation_artist);
    }

    // Rating and playcount, stored in POPM frame
    PopularimeterFrame *popm = findPOPM(tag, ""); // Global (all apps) tag

    // If no 'global' tag exists, look for the MythTV specific one
    if (!popm)
    {
        popm = findPOPM(tag, email);
    }

    // Fallback to using any POPM tag we can find
    if (!popm)
    {
        if (!tag->frameListMap()["POPM"].isEmpty())
            popm = dynamic_cast<PopularimeterFrame *>
                                        (tag->frameListMap()["POPM"].front());
    }

    if (popm)
    {
        int rating = popm->rating();
        rating = lroundf(static_cast<float>(rating) / 255.0f * 10.0f);
        metadata->setRating(rating);
        metadata->setPlaycount(popm->counter());
    }

    // Look for MusicBrainz Album+Artist ID in TXXX Frame
    UserTextIdentificationFrame *musicbrainz = find(tag,
                                            "MusicBrainz Album Artist Id");

    if (musicbrainz)
    {
        // If the MusicBrainz ID is the special "Various Artists" ID
        // then compilation is TRUE
        if (!compilation && !musicbrainz->fieldList().isEmpty())
        {
            TagLib::StringList l = musicbrainz->fieldList();
            for (TagLib::StringList::ConstIterator it = l.begin(); it != l.end(); it++)
            {
                QString ID = TStringToQString((*it));

                if (ID == MYTH_MUSICBRAINZ_ALBUMARTIST_UUID)
                {
                    compilation = true;
                    break;
                }
            }
        }
    }

    // TLEN - Ignored intentionally, some encoders write bad values
    // e.g. Lame under certain circumstances will always write a length of
    // 27 hours

    // Length
    if (!tag->frameListMap()["TLEN"].isEmpty())
    {
        int length = tag->frameListMap()["TLEN"].front()->toString().toInt();
        LOG(VB_FILE, LOG_DEBUG,
            QString("MetaIOID3::read: Length for '%1' from tag is '%2'\n").arg(filename).arg(length));
    }

    metadata->setCompilation(compilation);

    metadata->setLength(getTrackLength(m_file));

    // The number of tracks on the album, if supplied
    if (!tag->frameListMap()["TRCK"].isEmpty())
    {
        QString trackFrame = TStringToQString(
                                tag->frameListMap()["TRCK"].front()->toString())
                                    .trimmed();
        int trackCount = trackFrame.section('/', -1).toInt();
        if (trackCount > 0)
            metadata->setTrackCount(trackCount);
    }

    LOG(VB_FILE, LOG_DEBUG,
            QString("MetaIOID3::read: Length for '%1' from properties is '%2'\n").arg(filename).arg(metadata->Length()));

    // Look for MythTVLastPlayed in TXXX Frame
    UserTextIdentificationFrame *lastplayed = find(tag, "MythTVLastPlayed");
    if (lastplayed)
    {
        QString lastPlayStr = TStringToQString(lastplayed->toString());
        metadata->setLastPlay(QDateTime::fromString(lastPlayStr, Qt::ISODate));
    }

    // Part of a set
    if (!tag->frameListMap()["TPOS"].isEmpty())
    {
        QString pos = TStringToQString(
                        tag->frameListMap()["TPOS"].front()->toString()).trimmed();

        int discNumber = pos.section('/', 0, 0).toInt();
        int discCount  = pos.section('/', -1).toInt();

        if (discNumber > 0)
            metadata->setDiscNumber(discNumber);
        if (discCount > 0)
            metadata->setDiscCount(discCount);
    }

    return metadata;
}
Ejemplo n.º 10
0
static void handleCDMedia(MythMediaDevice *cd)
{

    if (!cd)
        return;

    LOG(VB_MEDIA, LOG_NOTICE, "Got a media changed event");

    QString newDevice;

    // save the device if valid
    if (cd->isUsable())
    {
#ifdef Q_OS_MAC
        newDevice = cd->getMountPath();
#else
        newDevice = cd->getDevicePath();
#endif

        gCDdevice = newDevice;
        LOG(VB_MEDIA, LOG_INFO, "MythMusic: Storing CD device " + gCDdevice);
    }
    else
    {
        LOG(VB_MEDIA, LOG_INFO, "Device is not usable clearing cd data");

        if (gPlayer->isPlaying() && gPlayer->getCurrentMetadata()
            && gPlayer->getCurrentMetadata()->isCDTrack())
        {
            // we was playing a cd track which is no longer available so stop playback
            // TODO should check the playing track is from the ejected drive if more than one is available
            gPlayer->stop(true);
        }

        // device is not usable so remove any existing CD tracks
        if (gMusicData->all_music)
        {
            gMusicData->all_music->clearCDData();
            gMusicData->all_playlists->getActive()->removeAllCDTracks();
        }

        gPlayer->activePlaylistChanged(-1, false);
        gPlayer->sendCDChangedEvent();

        return;
    }

    if (!gMusicData->initialized)
        gMusicData->loadMusic();

    // remove any existing CD tracks
    if (gMusicData->all_music)
    {
        gMusicData->all_music->clearCDData();
        gMusicData->all_playlists->getActive()->removeAllCDTracks();
    }

    // find any new cd tracks
    CdDecoder *decoder = new CdDecoder("cda", NULL, NULL);
    decoder->setDevice(newDevice);

    int tracks = decoder->getNumTracks();
    bool setTitle = false;

    for (int trackNo = 1; trackNo <= tracks; trackNo++)
    {
        MusicMetadata *track = decoder->getMetadata(trackNo);
        if (track)
        {
            gMusicData->all_music->addCDTrack(*track);

            if (!setTitle)
            {

                QString parenttitle = " ";
                if (track->FormatArtist().length() > 0)
                {
                    parenttitle += track->FormatArtist();
                    parenttitle += " ~ ";
                }

                if (track->Album().length() > 0)
                    parenttitle += track->Album();
                else
                {
                    parenttitle = " " + qApp->translate("(MythMusicMain)", 
                                                        "Unknown");
                    LOG(VB_GENERAL, LOG_INFO, "Couldn't find your "
                    " CD. It may not be in the freedb database.\n"
                    "    More likely, however, is that you need to delete\n"
                    "    ~/.cddb and ~/.cdserverrc and restart MythMusic.");
                }

                gMusicData->all_music->setCDTitle(parenttitle);
                setTitle = true;
            }

            delete track;
        }
    }

    gPlayer->sendCDChangedEvent();

    delete decoder;

    // if the AutoPlayCD setting is set we remove all the existing tracks
    // from the playlist and replace them with the new CD tracks found
    if (gCoreContext->GetNumSetting("AutoPlayCD", 0))
    {
        gMusicData->all_playlists->getActive()->removeAllTracks();

        QList<int> songList;

        for (int x = 1; x <= gMusicData->all_music->getCDTrackCount(); x++)
        {
            MusicMetadata *mdata = gMusicData->all_music->getCDMetadata(x);
            if (mdata)
                songList.append((mdata)->ID());
        }

        if (songList.count())
        {
            gMusicData->all_playlists->getActive()->fillSonglistFromList(
                    songList, true, PL_REPLACE, 0);
            gPlayer->setCurrentTrackPos(0);
        }
    }
    else
    {
        // don't show the music screen if AutoPlayCD is off
        return;
    }

    // if there is no music screen showing show the Playlist view
    if (!gPlayer->hasClient())
    {
        // make sure we start playing from the first track
        gCoreContext->SaveSetting("MusicBookmark", 0);
        gCoreContext->SaveSetting("MusicBookmarkPosition", 0);

        runMusicPlayback();
    }
}
Ejemplo n.º 11
0
static int ExtractImage(const MythUtilCommandLineParser &cmdline)
{
    if (cmdline.toString("songid").isEmpty())
    {
        LOG(VB_GENERAL, LOG_ERR, "Missing --songid option");
        return GENERIC_EXIT_INVALID_CMDLINE;
    }

    if (cmdline.toString("imagetype").isEmpty())
    {
        LOG(VB_GENERAL, LOG_ERR, "Missing --imagetype option");
        return GENERIC_EXIT_INVALID_CMDLINE;
    }

    int songID = cmdline.toInt("songid");
    ImageType type = (ImageType)cmdline.toInt("imagetype");

    MusicMetadata *mdata = MusicMetadata::createFromID(songID);
    if (!mdata)
    {
        LOG(VB_GENERAL, LOG_ERR, QString("Cannot find metadata for trackid: %1").arg(songID));
        return GENERIC_EXIT_NOT_OK;
    }

    AlbumArtImage *image = mdata->getAlbumArtImages()->getImage(type);
    if (!image)
    {
        LOG(VB_GENERAL, LOG_ERR, QString("Cannot find image of type: %1").arg(type));
        return GENERIC_EXIT_NOT_OK;
    }

    MetaIO *tagger = mdata->getTagger();
    if (!tagger)
    {
        LOG(VB_GENERAL, LOG_ERR, QString("Cannot find a tagger for this file: %1").arg(mdata->Filename(false)));
        return GENERIC_EXIT_NOT_OK;
    }


    if (!image->embedded || !tagger->supportsEmbeddedImages())
    {
        LOG(VB_GENERAL, LOG_ERR, QString("Either the image isn't embedded or the tagger doesn't support embedded images"));
        return GENERIC_EXIT_NOT_OK;
    }

    // find the tracks actual filename
    StorageGroup musicGroup("Music", gCoreContext->GetHostName(), false);
    QString trackFilename =  musicGroup.FindFile(mdata->Filename(false));

    // where are we going to save the image
    QString path;
    StorageGroup artGroup("MusicArt", gCoreContext->GetHostName(), false);
    QStringList dirList = artGroup.GetDirList();
    if (dirList.size())
        path = artGroup.FindNextDirMostFree();

    if (!QDir(path).exists())
    {
        LOG(VB_GENERAL, LOG_ERR, "Cannot find a directory in the 'MusicArt' storage group to save to");
        return GENERIC_EXIT_NOT_OK;
    }

    path += "/AlbumArt/";
    QDir dir(path);

    QString filename = QString("%1-%2.jpg").arg(mdata->ID()).arg(AlbumArtImages::getTypeFilename(image->imageType));

    if (QFile::exists(path + filename))
        QFile::remove(path + filename);

    if (!dir.exists())
        dir.mkpath(path);

    QImage *saveImage = tagger->getAlbumArt(trackFilename, image->imageType);
    if (saveImage)
    {
        saveImage->save(path + filename, "JPEG");
        delete saveImage;
    }

    delete tagger;

    // tell any clients that the albumart for this track has changed
    gCoreContext->SendMessage(QString("MUSIC_ALBUMART_CHANGED %1 %2").arg(songID).arg(type));

    return GENERIC_EXIT_OK;
}
Ejemplo n.º 12
0
static int UpdateMeta(const MythUtilCommandLineParser &cmdline)
{
    bool ok = true;
    int result = GENERIC_EXIT_OK;

    if (cmdline.toString("songid").isEmpty())
    {
        LOG(VB_GENERAL, LOG_ERR, "Missing --songid option");
        return GENERIC_EXIT_INVALID_CMDLINE;
    }
    int songID = cmdline.toInt("songid");

    MusicMetadata *mdata = MusicMetadata::createFromID(songID);
    if (!mdata)
    {
        LOG(VB_GENERAL, LOG_ERR, QString("Cannot find metadata for trackid: %1").arg(songID));
        return GENERIC_EXIT_NOT_OK;
    }

    if (!cmdline.toString("title").isEmpty())
        mdata->setTitle(cmdline.toString("title"));

    if (!cmdline.toString("artist").isEmpty())
        mdata->setArtist(cmdline.toString("artist"));

    if (!cmdline.toString("album").isEmpty())
        mdata->setAlbum(cmdline.toString("album"));

    if (!cmdline.toString("genre").isEmpty())
        mdata->setGenre(cmdline.toString("genre"));

    if (!cmdline.toString("trackno").isEmpty())
        mdata->setTrack(cmdline.toInt("trackno"));

    if (!cmdline.toString("year").isEmpty())
        mdata->setYear(cmdline.toInt("year"));

    if (!cmdline.toString("rating").isEmpty())
        mdata->setRating(cmdline.toInt("rating"));

    if (!cmdline.toString("playcount").isEmpty())
        mdata->setPlaycount(cmdline.toInt("playcount"));

    if (!cmdline.toString("lastplayed").isEmpty())
        mdata->setLastPlay(cmdline.toDateTime("lastplayed"));

    mdata->dumpToDatabase();

    MetaIO *tagger = mdata->getTagger();
    if (tagger)
    {
        ok = tagger->write(mdata->getLocalFilename(), mdata);

        if (!ok)
            LOG(VB_GENERAL, LOG_ERR, QString("Failed to write to tag for trackid: %1").arg(songID));
    }

    // tell any clients that the metadata for this track has changed
    gCoreContext->SendMessage(QString("MUSIC_METADATA_CHANGED %1").arg(songID));

    if (!ok)
        result = GENERIC_EXIT_NOT_OK;

    return result;
}
Ejemplo n.º 13
0
static int CalcTrackLength(const MythUtilCommandLineParser &cmdline)
{
    if (cmdline.toString("songid").isEmpty())
    {
        LOG(VB_GENERAL, LOG_ERR, "Missing --songid option");
        return GENERIC_EXIT_INVALID_CMDLINE;
    }

    int songID = cmdline.toInt("songid");

    MusicMetadata *mdata = MusicMetadata::createFromID(songID);
    if (!mdata)
    {
        LOG(VB_GENERAL, LOG_ERR, QString("Cannot find metadata for trackid: %1").arg(songID));
        return GENERIC_EXIT_NOT_OK;
    }

    QString musicFile = mdata->getLocalFilename();

    if (musicFile.isEmpty() || !QFile::exists(musicFile))
    {
        LOG(VB_GENERAL, LOG_ERR, QString("Cannot find file for trackid: %1").arg(songID));
        return GENERIC_EXIT_NOT_OK;
    }

    av_register_all();

    AVFormatContext *inputFC = NULL;
    AVInputFormat *fmt = NULL;

    // Open track
    LOG(VB_GENERAL, LOG_DEBUG, QString("CalcTrackLength: Opening '%1'")
            .arg(musicFile));

    QByteArray inFileBA = musicFile.toLocal8Bit();

    int ret = avformat_open_input(&inputFC, inFileBA.constData(), fmt, NULL);

    if (ret)
    {
        LOG(VB_GENERAL, LOG_ERR, "CalcTrackLength: Couldn't open input file" +
                                  ENO);
        return GENERIC_EXIT_NOT_OK;
    }

    // Getting stream information
    ret = avformat_find_stream_info(inputFC, NULL);

    if (ret < 0)
    {
        LOG(VB_GENERAL, LOG_ERR,
            QString("CalcTrackLength: Couldn't get stream info, error #%1").arg(ret));
        avformat_close_input(&inputFC);
        inputFC = NULL;
        return GENERIC_EXIT_NOT_OK;;
    }

    int duration = 0;
    long long time = 0;

    for (uint i = 0; i < inputFC->nb_streams; i++)
    {
        AVStream *st = inputFC->streams[i];
        char buf[256];

        avcodec_string(buf, sizeof(buf), st->codec, false);

        switch (inputFC->streams[i]->codec->codec_type)
        {
            case AVMEDIA_TYPE_AUDIO:
            {
                AVPacket pkt;
                av_init_packet(&pkt);

                while (av_read_frame(inputFC, &pkt) >= 0)
                {
                    if (pkt.stream_index == (int)i)
                        time = time + pkt.duration;

                    av_free_packet(&pkt);
                }

                duration = time * av_q2d(inputFC->streams[i]->time_base);
                break;
            }

            default:
                LOG(VB_GENERAL, LOG_ERR,
                    QString("Skipping unsupported codec %1 on stream %2")
                        .arg(inputFC->streams[i]->codec->codec_type).arg(i));
                break;
        }
    }

    // Close input file
    avformat_close_input(&inputFC);
    inputFC = NULL;

    if (mdata->Length() / 1000 != duration)
    {
        LOG(VB_GENERAL, LOG_INFO, QString("The length of this track in the database was %1s "
                                          "it is now %2s").arg(mdata->Length() / 1000).arg(duration));

        // update the track length in the database
        mdata->setLength(duration * 1000);
        mdata->dumpToDatabase();

        // tell any clients that the metadata for this track has changed
        gCoreContext->SendMessage(QString("MUSIC_METADATA_CHANGED %1").arg(songID));
    }
    else
    {
        LOG(VB_GENERAL, LOG_INFO, QString("The length of this track is unchanged %1s")
                                          .arg(mdata->Length() / 1000));
    }

    return GENERIC_EXIT_OK;
}
Ejemplo n.º 14
0
void Playlist::shuffleTracks(MusicPlayer::ShuffleMode shuffleMode)
{
    m_shuffledSongs.clear();

    switch (shuffleMode)
    {
        case MusicPlayer::SHUFFLE_RANDOM:
        {
            QMultiMap<int, MusicMetadata*> songMap;

            SongList::const_iterator it = m_songs.begin();
            for (; it != m_songs.end(); ++it)
            {
                songMap.insert(rand(), *it);
            }

            QMultiMap<int, MusicMetadata*>::const_iterator i = songMap.constBegin();
            while (i != songMap.constEnd())
            {
                m_shuffledSongs.append(i.value());
                ++i;
            }

            break;
        }

        case MusicPlayer::SHUFFLE_INTELLIGENT:
        {
            int RatingWeight = 2;
            int PlayCountWeight = 2;
            int LastPlayWeight = 2;
            int RandomWeight = 2;
            m_parent->FillIntelliWeights(RatingWeight, PlayCountWeight,
                                         LastPlayWeight, RandomWeight);

            // compute max/min playcount,lastplay for this playlist
            int playcountMin = 0;
            int playcountMax = 0;
            double lastplayMin = 0.0;
            double lastplayMax = 0.0;

            uint idx = 0;
            SongList::const_iterator it = m_songs.begin();
            for (; it != m_songs.end(); ++it, ++idx)
            {
                if (!(*it)->isCDTrack())
                {
                    MusicMetadata *mdata = (*it);

                    if (0 == idx)
                    {
                        // first song
                        playcountMin = playcountMax = mdata->PlayCount();
                        lastplayMin = lastplayMax = mdata->LastPlay().toTime_t();
                    }
                    else
                    {
                        if (mdata->PlayCount() < playcountMin)
                            playcountMin = mdata->PlayCount();
                        else if (mdata->PlayCount() > playcountMax)
                            playcountMax = mdata->PlayCount();

                        if (mdata->LastPlay().toTime_t() < lastplayMin)
                            lastplayMin = mdata->LastPlay().toTime_t();
                        else if (mdata->LastPlay().toTime_t() > lastplayMax)
                            lastplayMax = mdata->LastPlay().toTime_t();
                    }
                }
            }

            // next we compute all the weights
            std::map<int,double> weights;
            std::map<int,int> ratings;
            std::map<int,int> ratingCounts;
            int TotalWeight = RatingWeight + PlayCountWeight + LastPlayWeight;
            for (int trackItI = 0; trackItI < m_songs.size(); ++trackItI)
            {
                MusicMetadata *mdata = m_songs[trackItI];
                if (!mdata->isCDTrack())
                {
                    int rating = mdata->Rating();
                    int playcount = mdata->PlayCount();
                    double lastplaydbl = mdata->LastPlay().toTime_t();
                    double ratingValue = (double)(rating) / 10;
                    double playcountValue, lastplayValue;

                    if (playcountMax == playcountMin)
                        playcountValue = 0;
                    else
                        playcountValue = ((playcountMin - (double)playcount) / (playcountMax - playcountMin) + 1);

                    if (lastplayMax == lastplayMin)
                        lastplayValue = 0;
                    else
                        lastplayValue = ((lastplayMin - lastplaydbl) / (lastplayMax - lastplayMin) + 1);

                    double weight = (RatingWeight * ratingValue +
                                        PlayCountWeight * playcountValue +
                                        LastPlayWeight * lastplayValue) / TotalWeight;
                    weights[mdata->ID()] = weight;
                    ratings[mdata->ID()] = rating;
                    ++ratingCounts[rating];
                }
            }

            // then we divide weights with the number of songs in the rating class
            // (more songs in a class ==> lower weight, without affecting other classes)
            double totalWeights = 0;
            std::map<int,double>::iterator weightsIt, weightsEnd = weights.end();
            for (weightsIt = weights.begin() ; weightsIt != weightsEnd ; ++weightsIt)
            {
                weightsIt->second /= ratingCounts[ratings[weightsIt->first]];
                totalWeights += weightsIt->second;
            }

            // then we get a random order, balanced with relative weights of remaining songs
            std::map<int,uint32_t> order;
            uint32_t orderCpt = 1;
            std::map<int,double>::iterator weightIt, weightEnd;
            while (!weights.empty())
            {
                double hit = totalWeights * (double)rand() / (double)RAND_MAX;
                weightEnd = weights.end();
                weightIt = weights.begin();
                double pos = 0;
                while (weightIt != weightEnd)
                {
                    pos += weightIt->second;
                    if (pos >= hit)
                        break;
                    ++weightIt;
                }

                // FIXME If we don't exit here then we'll segfault, but it
                //       probably won't give us the desired randomisation
                //       either - There seems to be a flaw in this code, we
                //       erase items from the map but never adjust
                //       'totalWeights' so at a point 'pos' will never be
                //       greater or equal to 'hit' and we will always hit the
                //       end of the map
                if (weightIt == weightEnd)
                    break;

                order[weightIt->first] = orderCpt;
                totalWeights -= weightIt->second;
                weights.erase(weightIt);
                ++orderCpt;
            }

            // create a map of tracks sorted by the computed order
            QMultiMap<int, MusicMetadata*> songMap;
            it = m_songs.begin();
            for (; it != m_songs.end(); ++it)
                songMap.insert(order[(*it)->ID()], *it);

            // copy the shuffled tracks to the shuffled song list
            QMultiMap<int, MusicMetadata*>::const_iterator i = songMap.constBegin();
            while (i != songMap.constEnd())
            {
                m_shuffledSongs.append(i.value());
                ++i;
            }

            break;
        }

        case MusicPlayer::SHUFFLE_ALBUM:
        {
            // "intellegent/album" order

            typedef map<QString, uint32_t> AlbumMap;
            AlbumMap                       album_map;
            AlbumMap::iterator             Ialbum;
            QString                        album;

            // pre-fill the album-map with the album name.
            // This allows us to do album mode in album order
            SongList::const_iterator it = m_songs.begin();
            for (; it != m_songs.end(); ++it)
            {
                MusicMetadata *mdata = (*it);
                album = mdata->Album() + " ~ " + QString("%1").arg(mdata->getAlbumId());
                if ((Ialbum = album_map.find(album)) == album_map.end())
                    album_map.insert(AlbumMap::value_type(album, 0));
            }

            // populate the sort id into the album map
            uint32_t album_count = 1;
            for (Ialbum = album_map.begin(); Ialbum != album_map.end(); ++Ialbum)
            {
                Ialbum->second = album_count;
                album_count++;
            }

            // create a map of tracks sorted by the computed order
            QMultiMap<int, MusicMetadata*> songMap;
            it = m_songs.begin();
            for (; it != m_songs.end(); ++it)
            {
                uint32_t album_order;
                MusicMetadata *mdata = (*it);
                if (mdata)
                {
                    album = album = mdata->Album() + " ~ " + QString("%1").arg(mdata->getAlbumId());;
                    if ((Ialbum = album_map.find(album)) == album_map.end())
                    {
                        // we didn't find this album in the map,
                        // yet we pre-loaded them all. we are broken,
                        // but we just set the track order to 1, since there
                        // is no real point in reporting an error
                        album_order = 1;
                    }
                    else
                    {
                        album_order = Ialbum->second * 1000;
                    }
                    album_order += mdata->Track();

                    songMap.insert(album_order, *it);
                }
            }

            // copy the shuffled tracks to the shuffled song list
            QMultiMap<int, MusicMetadata*>::const_iterator i = songMap.constBegin();
            while (i != songMap.constEnd())
            {
                m_shuffledSongs.append(i.value());
                ++i;
            }

            break;
        }

        case MusicPlayer::SHUFFLE_ARTIST:
        {
            // "intellegent/album" order

            typedef map<QString, uint32_t> ArtistMap;
            ArtistMap                      artist_map;
            ArtistMap::iterator            Iartist;
            QString                        artist;

            // pre-fill the album-map with the album name.
            // This allows us to do artist mode in artist order
            SongList::const_iterator it = m_songs.begin();
            for (; it != m_songs.end(); ++it)
            {
                MusicMetadata *mdata = (*it);
                artist = mdata->Artist() + " ~ " + mdata->Title();
                if ((Iartist = artist_map.find(artist)) == artist_map.end())
                    artist_map.insert(ArtistMap::value_type(artist,0));
            }

            // populate the sort id into the artist map
            uint32_t artist_count = 1;
            for (Iartist = artist_map.begin(); Iartist != artist_map.end(); ++Iartist)
            {
                Iartist->second = artist_count;
                artist_count++;
            }

            // create a map of tracks sorted by the computed order
            QMultiMap<int, MusicMetadata*> songMap;
            it = m_songs.begin();
            for (; it != m_songs.end(); ++it)
            {
                uint32_t artist_order;
                MusicMetadata *mdata = (*it);
                if (mdata)
                {
                    artist = mdata->Artist() + " ~ " + mdata->Title();
                    if ((Iartist = artist_map.find(artist)) == artist_map.end())
                    {
                        // we didn't find this artist in the map,
                        // yet we pre-loaded them all. we are broken,
                        // but we just set the track order to 1, since there
                        // is no real point in reporting an error
                        artist_order = 1;
                    }
                    else
                    {
                        artist_order = Iartist->second * 1000;
                    }
                    artist_order += mdata->Track();

                    songMap.insert(artist_order, *it);
                }
            }

            // copy the shuffled tracks to the shuffled song list
            QMultiMap<int, MusicMetadata*>::const_iterator i = songMap.constBegin();
            while (i != songMap.constEnd())
            {
                m_shuffledSongs.append(i.value());
                ++i;
            }

            break;
        }

        default:
        {
            // copy the raw song list to the shuffled track list
            SongList::const_iterator it = m_songs.begin();
            for (; it != m_songs.end(); ++it)
            {
                m_shuffledSongs.append(*it);
            }

            break;
        }
    }
}
Ejemplo n.º 15
0
int Playlist::CreateCDMP3(void)
{
    // Check & get global settings
    if (!gCoreContext->GetNumSetting("CDWriterEnabled"))
    {
        LOG(VB_GENERAL, LOG_ERR, "CD Writer is not enabled.");
        return 1;
    }

    QString scsidev = MediaMonitor::defaultCDWriter();
    if (scsidev.isEmpty())
    {
        LOG(VB_GENERAL, LOG_ERR, "No CD Writer device defined.");
        return 1;
    }

    int disksize = gCoreContext->GetNumSetting("CDDiskSize", 2);
    QString writespeed = gCoreContext->GetSetting("CDWriteSpeed", "2");
    bool MP3_dir_flag = gCoreContext->GetNumSetting("CDCreateDir", 1);

    double size_in_MB = 0.0;

    QStringList reclist;

    SongList::const_iterator it = m_songs.begin();
    for (; it != m_songs.end(); ++it)
    {
        if ((*it)->isCDTrack())
            continue;

        // Normal track
        MusicMetadata *tmpdata = (*it);
        if (tmpdata)
        {
            // check filename..
            QFileInfo testit(tmpdata->Filename());
            if (!testit.exists())
                continue;
            size_in_MB += testit.size() / 1000000.0;
            QString outline;
            if (MP3_dir_flag)
            {
                if (tmpdata->Artist().length() > 0)
                    outline += tmpdata->Artist() + "/";
                if (tmpdata->Album().length() > 0)
                    outline += tmpdata->Album() + "/";
            }

            outline += "=";
            outline += tmpdata->Filename();

            reclist += outline;
        }
    }

    int max_size;
    if (disksize == 0)
        max_size = 650;
    else
        max_size = 700;

    if (size_in_MB >= max_size)
    {
        LOG(VB_GENERAL, LOG_ERR, "MP3 CD creation aborted -- cd size too big.");
        return 1;
    }

    // probably should tie stdout of mkisofs to stdin of cdrecord sometime
    QString tmptemplate("/tmp/mythmusicXXXXXX");

    QString tmprecordlist = createTempFile(tmptemplate);
    if (tmprecordlist == tmptemplate)
    {
        LOG(VB_GENERAL, LOG_ERR, "Unable to open temporary file");
        return 1;
    }

    QString tmprecordisofs = createTempFile(tmptemplate);
    if (tmprecordisofs == tmptemplate)
    {
        LOG(VB_GENERAL, LOG_ERR, "Unable to open temporary file");
        return 1;
    }

    QFile reclistfile(tmprecordlist);

    if (!reclistfile.open(QIODevice::WriteOnly))
    {
        LOG(VB_GENERAL, LOG_ERR, "Unable to open temporary file");
        return 1;
    }

    QTextStream recstream(&reclistfile);

    QStringList::Iterator iter;

    for (iter = reclist.begin(); iter != reclist.end(); ++iter)
    {
        recstream << *iter << "\n";
    }

    reclistfile.close();

    m_progress = new MythProgressDialog(QObject::tr("Creating CD File System"),
                                      100);
    m_progress->setProgress(1);

    QStringList args;
    QString command;

    command = "mkisofs";
    args << "-graft-points";
    args << "-path-list";
    args << tmprecordlist;
    args << "-o";
    args << tmprecordisofs;
    args << "-J";
    args << "-R";

    uint flags = kMSRunShell | kMSStdErr |
                 kMSDontDisableDrawing | kMSDontBlockInputDevs |
                 kMSRunBackground;

    m_proc = new MythSystemLegacy(command, args, flags);

    connect(m_proc, SIGNAL(readDataReady(int)), this, SLOT(mkisofsData(int)),
            Qt::DirectConnection);
    connect(m_proc, SIGNAL(finished()),         this, SLOT(processExit()),
            Qt::DirectConnection);
    connect(m_proc, SIGNAL(error(uint)),        this, SLOT(processExit(uint)),
            Qt::DirectConnection);

    m_procExitVal = GENERIC_EXIT_RUNNING;
    m_proc->Run();

    while( m_procExitVal == GENERIC_EXIT_RUNNING )
        usleep( 100000 );

    uint retval = m_procExitVal;

    m_progress->Close();
    m_progress->deleteLater();
    m_proc->disconnect();
    delete m_proc;

    if (retval)
    {
        LOG(VB_GENERAL, LOG_ERR, QString("Unable to run mkisofs: returns %1")
                .arg(retval));
    }
    else
    {
        m_progress = new MythProgressDialog(QObject::tr("Burning CD"), 100);
        m_progress->setProgress(2);

        command = "cdrecord";
        args = QStringList();
        args << "-v";
        //args << "-dummy";
        args << QString("dev=%1").arg(scsidev);

        if (writespeed.toInt() > 0)
        {
            args << "-speed=";
            args << writespeed;
        }

        args << "-data";
        args << tmprecordisofs;

        flags = kMSRunShell | kMSStdErr | kMSStdOut |
                kMSDontDisableDrawing | kMSDontBlockInputDevs |
                kMSRunBackground;

        m_proc = new MythSystemLegacy(command, args, flags);
        connect(m_proc, SIGNAL(readDataReady(int)),
                this, SLOT(cdrecordData(int)), Qt::DirectConnection);
        connect(m_proc, SIGNAL(finished()),
                this, SLOT(processExit()), Qt::DirectConnection);
        connect(m_proc, SIGNAL(error(uint)),
                this, SLOT(processExit(uint)), Qt::DirectConnection);
        m_procExitVal = GENERIC_EXIT_RUNNING;
        m_proc->Run();

        while( m_procExitVal == GENERIC_EXIT_RUNNING )
            usleep( 100000 );

        retval = m_procExitVal;

        m_progress->Close();
        m_progress->deleteLater();
        m_proc->disconnect();
        delete m_proc;

        if (retval)
        {
            LOG(VB_GENERAL, LOG_ERR,
                QString("Unable to run cdrecord: returns %1") .arg(retval));
        }
    }

    QFile::remove(tmprecordlist);
    QFile::remove(tmprecordisofs);

    return retval;
}
Ejemplo n.º 16
0
bool Ripper::deleteExistingTrack(RipTrack *track)
{
    if (!track)
        return false;

    MusicMetadata *metadata = track->metadata;

    if (!metadata)
        return false;

    QString artist = metadata->Artist();
    QString album = metadata->Album();
    QString title = metadata->Title();

    MSqlQuery query(MSqlQuery::InitCon());
    QString queryString("SELECT song_id, "
            "CONCAT_WS('/', music_directories.path, music_songs.filename) AS filename "
            "FROM music_songs "
            "LEFT JOIN music_artists"
            " ON music_songs.artist_id=music_artists.artist_id "
            "LEFT JOIN music_albums"
            " ON music_songs.album_id=music_albums.album_id "
            "LEFT JOIN music_directories "
            " ON music_songs.directory_id=music_directories.directory_id "
            "WHERE artist_name REGEXP \'");
    QString token = artist;
    token.replace(QRegExp("(/|\\\\|:|\'|\\,|\\!|\\(|\\)|\"|\\?|\\|)"),
                  QString("."));

    queryString += token + "\' AND " + "album_name REGEXP \'";
    token = album;
    token.replace(QRegExp("(/|\\\\|:|\'|\\,|\\!|\\(|\\)|\"|\\?|\\|)"),
                  QString("."));
    queryString += token + "\' AND " + "name    REGEXP \'";
    token = title;
    token.replace(QRegExp("(/|\\\\|:|\'|\\,|\\!|\\(|\\)|\"|\\?|\\|)"),
                  QString("."));
    queryString += token + "\' ORDER BY artist_name, album_name,"
                           " name, song_id, filename LIMIT 1";
    query.prepare(queryString);

    if (!query.exec() || !query.isActive())
    {
        MythDB::DBError("Search music database", query);
        return false;
    }

    if (query.next())
    {
        int trackID = query.value(0).toInt();
        QString filename = query.value(1).toString();
        QUrl url(m_musicStorageDir);
        filename = gCoreContext->GenMythURL(url.host(), 0, filename, "Music");

        // delete file
        // FIXME: RemoteFile::DeleteFile will only work with files on the master BE
        if (!RemoteFile::DeleteFile(filename))
        {
            LOG(VB_GENERAL, LOG_NOTICE, QString("Ripper::deleteExistingTrack() "
                                                "Could not delete %1")
                                                .arg(filename));
            return false;
        }

        // remove database entry
        MSqlQuery deleteQuery(MSqlQuery::InitCon());
        deleteQuery.prepare("DELETE FROM music_songs"
                            " WHERE song_id = :SONG_ID");
        deleteQuery.bindValue(":SONG_ID", trackID);
        if (!deleteQuery.exec())
        {
            MythDB::DBError("Delete Track", deleteQuery);
            return false;
        }
        return true;
    }

    return false;
}
Ejemplo n.º 17
0
QFileInfo Content::GetAlbumArt( int nTrackId, int nWidth, int nHeight )
{
    // ----------------------------------------------------------------------
    // Read AlbumArt file path from database
    // ----------------------------------------------------------------------

    MusicMetadata *metadata = MusicMetadata::createFromID(nTrackId);

    if (!metadata)
        return QFileInfo();

    QString sFullFileName = metadata->getAlbumArtFile();

    delete metadata;

    if (!RemoteFile::Exists(sFullFileName))
        return QFileInfo();

    QString sNewFileName = QString( "/tmp/%1.%2x%3.jpg" )
                              .arg( QFileInfo(sFullFileName).fileName() )
                              .arg( nWidth    )
                              .arg( nHeight   );

    // ----------------------------------------------------------------------
    // check to see if albumart image is already created.
    // ----------------------------------------------------------------------

    if (QFile::exists( sNewFileName ))
        return QFileInfo( sNewFileName );

    // ----------------------------------------------------------------------
    // Must generate Albumart Image, Generate Image and save.
    // ----------------------------------------------------------------------


    QImage img;
    if (sFullFileName.startsWith("myth://"))
    {
        RemoteFile rf(sFullFileName, false, false, 0);
        QByteArray data;
        rf.SaveAs(data);

        img.loadFromData(data);
    }
    else
        img.load(sFullFileName);

    if (img.isNull())
        return QFileInfo();

    // We don't need to scale if no height and width were specified
    // but still need to save as jpg if it's in another format
    if ((nWidth == 0) && (nHeight == 0))
    {
        QFileInfo fi(sFullFileName);
        if (fi.suffix().toLower() == "jpg")
            return fi;
    }
    else if (nWidth > img.width() && nHeight > img.height())
    {
        // Requested dimensions are larger than the source image, so instead of
        // scaling up which will produce horrible results return the fullsize
        // image and the user can scale further if they want instead
        // NOTE: If this behaviour is changed, for example making it optional,
        //       then upnp code will need changing to compensate
    }
    else
    {
        float fAspect = 0.0;
        if (fAspect <= 0)
            fAspect = (float)(img.width()) / img.height();

        if ( nWidth == 0 || nWidth > img.width() )
            nWidth = (int)rint(nHeight * fAspect);

        if ( nHeight == 0 || nHeight > img.height() )
            nHeight = (int)rint(nWidth / fAspect);

        img = img.scaled( nWidth, nHeight, Qt::KeepAspectRatio,
                                           Qt::SmoothTransformation);
    }

    QString fname = sNewFileName.toLatin1().constData();
    // Use JPG not PNG for compatibility with the most uPnP devices and
    // faster loading (smaller file to send over network)
    if (!img.save( fname, "JPG" ))
        return QFileInfo();

    return QFileInfo( sNewFileName );
}
Ejemplo n.º 18
0
/*!
 * \copydoc MetaIO::read()
 */
MusicMetadata* MetaIOMP4::read(const QString &filename)
{
    QString title, artist, album, genre;
    int year = 0, tracknum = 0, length = 0;
    bool compilation = false;

    AVFormatContext* p_context = NULL;
    AVInputFormat* p_inputformat = NULL;

    QByteArray local8bit = filename.toLocal8Bit();
    if ((avformat_open_input(&p_context, local8bit.constData(),
                             p_inputformat, NULL) < 0))
    {
        return NULL;
    }

    if (avformat_find_stream_info(p_context, NULL) < 0)
        return NULL;

#if 0
    //### Debugging, enable to dump a list of all field names/values found

    AVDictionaryEntry *tag = av_dict_get(p_context->metadata, "\0", NULL,
                                         AV_METADATA_IGNORE_SUFFIX);
    while (tag != NULL)
    {
        LOG(VB_GENERAL, LOG_DEBUG, QString("Tag: %1 Value: %2")
                .arg(tag->key) .arg(QString::fromUtf8(tag->value)));
        tag = av_dict_get(p_context->metadata, "\0", tag,
                          AV_METADATA_IGNORE_SUFFIX);
    }
    //####
#endif

    title = getFieldValue(p_context, "title");
    if (title.isEmpty())
    {
        readFromFilename(filename, artist, album, title, genre, tracknum);
    }
    else
    {
        title = getFieldValue(p_context, "title");
        artist = getFieldValue(p_context, "author");
        // Author is the correct fieldname, but
        // we've been saving to artist for years
        if (artist.isEmpty())
            artist = getFieldValue(p_context, "artist");
        album = getFieldValue(p_context, "album");
        year = getFieldValue(p_context, "year").toInt();
        genre = getFieldValue(p_context, "genre");
        tracknum = getFieldValue(p_context, "track").toInt();
        compilation = getFieldValue(p_context, "").toInt();
        length = getTrackLength(p_context);
    }

    metadataSanityCheck(&artist, &album, &title, &genre);

    MusicMetadata *retdata = new MusicMetadata(filename,
                                     artist,
                                     compilation ? artist : "",
                                     album,
                                     title,
                                     genre,
                                     year,
                                     tracknum,
                                     length);

    retdata->setCompilation(compilation);

    avformat_close_input(&p_context);

    return retdata;
}