Index: mythmusic/playbackbox.cpp =================================================================== --- mythmusic/playbackbox.cpp (revision 13144) +++ mythmusic/playbackbox.cpp (working copy) @@ -1355,6 +1355,9 @@ void PlaybackBoxMusic::stop(void) { + + saveMetadata(); + stopDecoder(); if (output) @@ -1406,6 +1409,18 @@ } } +void PlaybackBoxMusic::saveMetadata() +{ + + if (curMeta && curMeta->hasChanged()) + { + curMeta->persist(); + if (decoder) + decoder->commitMetadata(curMeta); + } + +} + void PlaybackBoxMusic::previous() { if (repeatmode == REPEAT_ALL) @@ -1965,6 +1980,9 @@ { // It's a track + // Save changes to metadata + saveMetadata(); + curMeta = all_music->getMetadata(node_int); if (title_text) title_text->SetText(curMeta->FormatTitle()); Index: mythmusic/metaio_libid3hack.h =================================================================== --- mythmusic/metaio_libid3hack.h (revision 13144) +++ mythmusic/metaio_libid3hack.h (working copy) @@ -6,5 +6,6 @@ #define ID3_FILE_UPDATE myth_id3_file_update int myth_id3_file_update(struct id3_file *file); +int myth_id3_field_setintplus(union id3_field *field, id3_byte_t const *data, id3_length_t length); } #endif Index: mythmusic/metadata.cpp =================================================================== --- mythmusic/metadata.cpp (revision 13144) +++ mythmusic/metadata.cpp (working copy) @@ -72,6 +72,8 @@ if (!query.exec() || query.numRowsAffected() < 1) MythContext::DBError("music persist", query); + + m_changed = false; } int Metadata::compare(Metadata *other) @@ -329,12 +331,14 @@ strQuery = "INSERT INTO music_songs ( directory_id, lastplay," " artist_id, album_id, name, genre_id," " year, track, length, filename," - " rating, format, date_entered, date_modified ) " + " rating, numplays, format, date_entered," + " date_modified ) " "VALUES ( " " :DIRECTORY, :LASTPLAY," " :ARTIST, :ALBUM, :TITLE, :GENRE," " :YEAR, :TRACKNUM, :LENGTH, :FILENAME," - " :RATING, :FORMAT, :DATE_ADD, :DATE_MOD );"; + " :RATING, :PLAYCOUNT,:FORMAT, :DATE_ADD," + " :DATE_MOD );"; } else { @@ -349,6 +353,7 @@ ", length = :LENGTH" ", filename = :FILENAME" ", rating = :RATING" + ", numplays = :PLAYCOUNT" ", format = :FORMAT" ", date_modified = :DATE_MOD " "WHERE song_id= :ID ;"; @@ -366,6 +371,7 @@ query.bindValue(":LENGTH", m_length); query.bindValue(":FILENAME", sqlfilename.utf8()); query.bindValue(":RATING", m_rating); + query.bindValue(":PLAYCOUNT", m_playcount); query.bindValue(":FORMAT", m_format); query.bindValue(":DATE_MOD", QDateTime::currentDateTime()); Index: mythmusic/metaioid3v2.h =================================================================== --- mythmusic/metaioid3v2.h (revision 13144) +++ mythmusic/metaioid3v2.h (working copy) @@ -11,6 +11,7 @@ #define MYTH_ID3_FRAME_COMPILATIONARTIST "TPE4" #define MYTH_ID3_FRAME_COMMENT "TXXX" #define MYTH_ID3_FRAME_MUSICBRAINZ_ALBUMARTISTDESC "MusicBrainz Album Artist Id" +#define MYTH_ID3_FRAME_POPULARIMETER "POPM" #define XING_MAGIC (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g') @@ -48,6 +49,8 @@ bool setComment(id3_tag *pTag, const char* pLabel, const QString value, const QString desc = ""); + bool setPopularimeter(id3_tag *pTag, const QString email, const int rating, + const QString playcount); bool findXingHeader(xing *xing, struct mad_bitptr ptr, unsigned int bitlen); }; Index: mythmusic/main.cpp =================================================================== --- mythmusic/main.cpp (revision 13144) +++ mythmusic/main.cpp (working copy) @@ -427,12 +427,7 @@ static void postMusic(MusicData *mdata) { - // Automagically save all playlists and metadata (ratings) that have changed - if (mdata->all_music->cleanOutThreads()) - { - mdata->all_music->save(); - } - + // Automagically save all playlists that have changed if (mdata->all_playlists->cleanOutThreads()) { mdata->all_playlists->save(); Index: mythmusic/playbackbox.h =================================================================== --- mythmusic/playbackbox.h (revision 13144) +++ mythmusic/playbackbox.h (working copy) @@ -49,6 +49,7 @@ void pause(); void stop(); void stopDecoder(); + void saveMetadata(); void previous(); void next(); void seekforward(); Index: mythmusic/metaioid3v2.cpp =================================================================== --- mythmusic/metaioid3v2.cpp (revision 13144) +++ mythmusic/metaioid3v2.cpp (working copy) @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -120,6 +121,13 @@ setComment(tag, ID3_FRAME_YEAR, QString("%1").arg(mdata->Year())); } + if (mdata->Rating() > 0 || mdata->PlayCount() > 0) + { + removeComment(tag, MYTH_ID3_FRAME_POPULARIMETER, "devnull@mythtv.org"); + int rating = (int) (mdata->Rating() / 10 * 255); + setPopularimeter(tag, "devnull@mythtv.org", rating, QString("%1").arg(mdata->PlayCount())); + } + // Write Genre maintaining the ID3v1 genre number if applicable QString data = mdata->Genre(); if (!data.isEmpty()) @@ -139,21 +147,19 @@ QString("%1").arg(genrenum)); else setComment(tag, ID3_FRAME_GENRE, data); - } - + } + if (0 != mdata->Track()) { if (!exclusive) removeComment(tag, ID3_FRAME_TRACK); setComment(tag, ID3_FRAME_TRACK, QString("%1").arg(mdata->Track())); } - - id3_tag_options(tag, ID3_TAG_OPTION_COMPRESSION, 0); id3_tag_options(tag, ID3_TAG_OPTION_CRC, 0); id3_tag_options(tag, ID3_TAG_OPTION_UNSYNCHRONISATION, 0); id3_tag_options(tag, ID3_TAG_OPTION_ID3V1, 0); - + // The id3_file_update function has a feature deficit. // See the files: metaio_libid3hack.c/h bool rv = (0 == ID3_FILE_UPDATE(p_input)); @@ -173,7 +179,7 @@ { QString artist = "", compilation_artist = "", album = "", title = "", genre = ""; - int year = 0, tracknum = 0, length = 0; + int year = 0, tracknum = 0, length = 0, playcount = 0, rating = 0, id = 0; bool compilation = false; id3_file *p_input = NULL; @@ -243,6 +249,17 @@ // If the ID3v2 tag contains the length, then use it length = getComment(tag, "TLEN").toInt(); + // Read rating and play count + struct id3_frame *p_frame = 0; + for (int i=0; NULL != (p_frame = id3_tag_findframe(tag, MYTH_ID3_FRAME_POPULARIMETER, i)); ++i) { + QString popm_id = (const char*)p_frame->fields[0].latin1.ptr; + if (popm_id == "devnull@mythtv.org") { + rating = p_frame->fields[1].number.value; + playcount = QString::fromLatin1((const char *)p_frame->fields[2].binary.data).replace(QRegExp("^([0-9]*).*"), QString("\\1")).toInt(); + playcount = (int) floor((playcount / 255 * 10) + 0.5); + } + } + id3_file_close(p_input); } @@ -264,7 +281,8 @@ } Metadata *retdata = new Metadata(filename, artist, compilation_artist, album, - title, genre, year, tracknum, length); + title, genre, year, tracknum, length, + id, rating, playcount); retdata->setCompilation(compilation); @@ -476,24 +494,37 @@ inline QString MetaIOID3v2::getRawID3String(union id3_field *pField) { QString tmp = ""; - - id3_ucs4_t const *p_ucs4 = (id3_ucs4_t *) id3_field_getstring(pField); - - if (p_ucs4) + + if (pField->type == ID3_FIELD_TYPE_LATIN1) { - id3_utf8_t *p_utf8 = id3_ucs4_utf8duplicate(p_ucs4); - - if (!p_utf8) - return ""; - tmp = QString::fromUtf8((const char*)p_utf8); + id3_latin1_t const *p_latin1 = id3_field_getlatin1(pField); + tmp = QString("%1").arg((const char*)p_latin1); - free(p_utf8); } - else + else if (pField->type == ID3_FIELD_TYPE_STRING) { + + id3_ucs4_t const *p_ucs4 = (id3_ucs4_t *) id3_field_getstring(pField); + + if (p_ucs4) + { + id3_utf8_t *p_utf8 = id3_ucs4_utf8duplicate(p_ucs4); + + if (!p_utf8) + return ""; + + tmp = QString::fromUtf8((const char*)p_utf8); + + free(p_utf8); + } + } + else if (pField->type == ID3_FIELD_TYPE_STRINGLIST) + { + id3_ucs4_t const *p_ucs4; + unsigned int nstrings = id3_field_getnstrings(pField); - + for (unsigned int j=0; jfields[1]); + if (pLabel == MYTH_ID3_FRAME_POPULARIMETER) + tmp = getRawID3String(&p_frame->fields[0]); + else + tmp = getRawID3String(&p_frame->fields[1]); // Let's compare the descriptions. if (tmp == desc) @@ -702,3 +738,48 @@ return true; } + +/*! + * \brief Function to set the Rating + * + * \param pTag Pointer to a id3_file object + * \param email The user or player indentifier, in the form of an email address + * \param rating The track rating between 0 and 255 + * \param playcount The track play count + * \returns True or False depending on success + */ +bool MetaIOID3v2::setPopularimeter(id3_tag *pTag, + const QString email, + const int rating, + const QString playcount) +{ + struct id3_frame* p_frame = NULL; + + p_frame = id3_frame_new(MYTH_ID3_FRAME_POPULARIMETER); + + if (NULL == p_frame) + return false; + + if (email.isEmpty() || + rating < 0 || rating > 255) + { + id3_frame_delete(p_frame); + return false; + } + + id3_field_setlatin1(&p_frame->fields[0], (const id3_latin1_t*)(const char *)email); + id3_field_setint(&p_frame->fields[1], rating); + + const id3_byte_t* playcount_to_char = (const id3_byte_t*)(const char *)playcount; + + myth_id3_field_setintplus(&p_frame->fields[2], playcount_to_char, sizeof(*playcount_to_char)); + + if (0 != id3_tag_attachframe(pTag, p_frame)) + { + VERBOSE(VB_GENERAL, "Failed to set POPM Tag"); + id3_frame_delete(p_frame); + return false; + } + + return true; +} Index: mythmusic/metaio_libid3hack.c =================================================================== --- mythmusic/metaio_libid3hack.c (revision 13144) +++ mythmusic/metaio_libid3hack.c (working copy) @@ -229,7 +229,7 @@ /* render ID3v2 */ - id3_tag_options(file->primary, ID3_TAG_OPTION_ID3V1, 0); + //id3_tag_options(file->primary, ID3_TAG_OPTION_ID3V1, 0); v2size = id3_tag_render(file->primary, 0); if (v2size) { @@ -281,3 +281,31 @@ return result; } +int myth_id3_field_setintplus(union id3_field *field, id3_byte_t const *data, id3_length_t length) +{ + if (!field) + return -1; + + if (field->type != ID3_FIELD_TYPE_INT32PLUS) + return -1; + + id3_byte_t *mem; + + if (length == 0) + mem = 0; + else { + mem = malloc(length); + if (mem == 0) + return -1; + + if (!data) + return -1; + + memcpy(mem, data, length); + } + + field->binary.data = mem; + field->binary.length = length; + + return 0; +}