Index: src/Core/Banshee.ThickClient/Banshee.Gui/TrackActions.cs
===================================================================
--- src/Core/Banshee.ThickClient/Banshee.Gui/TrackActions.cs	(revision 4559)
+++ src/Core/Banshee.ThickClient/Banshee.Gui/TrackActions.cs	(working copy)
@@ -38,7 +38,9 @@
 using Banshee.Playlist;
 using Banshee.Collection;
 using Banshee.Collection.Database;
+using Banshee.Configuration.Schema;
 using Banshee.ServiceStack;
+using Banshee.Streaming;
 using Banshee.Widgets;
 using Banshee.Gui.Dialogs;
 using Banshee.Gui.Widgets;
@@ -398,6 +400,12 @@
             ThreadAssist.SpawnFromMain (delegate {
                 (ActiveSource as DatabaseSource).RateSelectedTracks (rating_proxy.LastRating);
             });
+
+            if (LibrarySchema.WriteRatings.Get ()) {
+                foreach (TrackInfo track in current_source.TrackModel.SelectedItems) {
+                    Banshee.Kernel.Scheduler.Schedule (new SaveTrackMetadataJob (track), Banshee.Kernel.JobPriority.Highest);
+                }
+            }
         }
 
         private void OnSearchForSameArtist (object o, EventArgs args)
Index: src/Core/Banshee.ThickClient/Banshee.Gui.Dialogs/TrackEditor.cs
===================================================================
--- src/Core/Banshee.ThickClient/Banshee.Gui.Dialogs/TrackEditor.cs	(revision 4559)
+++ src/Core/Banshee.ThickClient/Banshee.Gui.Dialogs/TrackEditor.cs	(working copy)
@@ -654,7 +654,7 @@
             
             track.Track.Save ();
                 
-            if (LibrarySchema.WriteMetadata.Get ()) {
+            if (LibrarySchema.WriteMetadata.Get () || LibrarySchema.WriteRatings.Get ()) {
                 SaveToFile (track);
             }
 
Index: src/Core/Banshee.Services/Banshee.Preferences/Page.cs
===================================================================
--- src/Core/Banshee.Services/Banshee.Preferences/Page.cs	(revision 4559)
+++ src/Core/Banshee.Services/Banshee.Preferences/Page.cs	(working copy)
@@ -63,6 +63,10 @@
             music_library.Add (new SchemaPreference<bool> (LibrarySchema.WriteMetadata, 
                 Catalog.GetString ("Write _metadata to files"),
                 Catalog.GetString ("Enable this option to save tags and other metadata inside supported audio files.")));
+            
+            music_library.Add (new SchemaPreference<bool> (LibrarySchema.WriteRatings, 
+                Catalog.GetString ("Read/Write _ratings metadata to files"),
+                Catalog.GetString ("Enable this option to also import and export rating and playcount metadata inside supported audio files.")));
 
             music_library.Add (new SchemaPreference<bool> (LibrarySchema.MoveOnInfoSave,
                 Catalog.GetString ("_Update file and folder names"),
Index: src/Core/Banshee.Core/Makefile.am
===================================================================
--- src/Core/Banshee.Core/Makefile.am	(revision 4559)
+++ src/Core/Banshee.Core/Makefile.am	(working copy)
@@ -61,6 +61,7 @@
 	Banshee.Streaming/CommonTags.cs \
 	Banshee.Streaming/SaveTrackMetadataJob.cs \
 	Banshee.Streaming/StreamPlaybackError.cs \
+	Banshee.Streaming/StreamRatingTagger.cs \
 	Banshee.Streaming/StreamTag.cs \
 	Banshee.Streaming/StreamTagger.cs
 
Index: src/Core/Banshee.Core/Banshee.Configuration.Schema/LibrarySchema.cs
===================================================================
--- src/Core/Banshee.Core/Banshee.Configuration.Schema/LibrarySchema.cs	(revision 4559)
+++ src/Core/Banshee.Core/Banshee.Configuration.Schema/LibrarySchema.cs	(working copy)
@@ -102,6 +102,13 @@
             "If enabled, metadata (tags) will be written back to audio files when using the track metadata editor."
         );
         
+        public static readonly SchemaEntry<bool> WriteRatings = new SchemaEntry<bool>(
+            "library", "write_rating",
+            false,
+            "Import/Export ratings on supported files",
+            "If enabled, a supported file's rating and playcount metadata will be parsed at import, and re-exported to the file whenever its rating is modified."
+        );
+        
         public static readonly SchemaEntry<int> PlaylistSortOrder = new SchemaEntry<int>(
             "library", "playlist_sort_order",
             0,
Index: src/Core/Banshee.Core/Banshee.Streaming/SaveTrackMetadataJob.cs
===================================================================
--- src/Core/Banshee.Core/Banshee.Streaming/SaveTrackMetadataJob.cs	(revision 4559)
+++ src/Core/Banshee.Core/Banshee.Streaming/SaveTrackMetadataJob.cs	(working copy)
@@ -49,7 +49,7 @@
     
         public void Run ()
         {
-            if (!LibrarySchema.WriteMetadata.Get ()) {
+            if (!LibrarySchema.WriteMetadata.Get () && !LibrarySchema.WriteRatings.Get ()) {
                 Console.WriteLine ("Skipping scheduled metadata write, preference disabled after scheduling");
                 return;
             }
@@ -81,6 +81,11 @@
             file.Tag.Disc = (uint)track.DiscNumber;
             file.Tag.DiscCount = (uint)track.DiscCount;
             file.Tag.Year = (uint)track.Year;
+                
+            if (LibrarySchema.WriteRatings.Get()) {
+                StreamRatingTagger.StoreRating (track.Rating, track.PlayCount, file);
+            }
+            
             file.Tag.BeatsPerMinute = (uint)track.Bpm;
             
             SaveIsCompilation (file.Tag, track.IsCompilation);
Index: src/Core/Banshee.Core/Banshee.Streaming/StreamTagger.cs
===================================================================
--- src/Core/Banshee.Core/Banshee.Streaming/StreamTagger.cs	(revision 4559)
+++ src/Core/Banshee.Core/Banshee.Streaming/StreamTagger.cs	(working copy)
@@ -138,6 +138,11 @@
             track.DiscCount = Choose ((int)file.Tag.DiscCount, track.DiscCount, preferTrackInfo);
             track.Year = Choose ((int)file.Tag.Year, track.Year, preferTrackInfo);
             track.Bpm = Choose ((int)file.Tag.BeatsPerMinute, track.Bpm, preferTrackInfo);
+            
+            int file_rating = 0, file_playcount = 0;
+            StreamRatingTagger.GetRating (file, ref file_rating, ref file_playcount);
+            track.Rating = Choose (file_rating, track.Rating, preferTrackInfo);
+            track.PlayCount = Choose (file_playcount, track.PlayCount, preferTrackInfo);
 
             if (String.IsNullOrEmpty (track.TrackTitle)) {
                 try {
Index: src/Core/Banshee.Core/Banshee.Streaming/StreamRatingTagger.cs
===================================================================
--- src/Core/Banshee.Core/Banshee.Streaming/StreamRatingTagger.cs	(revision 0)
+++ src/Core/Banshee.Core/Banshee.Streaming/StreamRatingTagger.cs	(revision 0)
@@ -0,0 +1,136 @@
+//
+// StreamRatingTagger.cs
+//
+// Author:
+//   Nicholas Parker <nickbp@gmail.com>
+//
+// Copyright (C) 2008 Nicholas Parker
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using Banshee.Collection;
+
+namespace Banshee.Streaming
+{
+    public static class ID3v2RatingTagger
+    {
+        // What we call ourselves in POPM tags.
+        private static string POPM_our_creator_name = "Banshee";
+        
+        // Ordered list of ID3v2 POPM authors to attempt.
+        // Banshee must be given priority, to avoid "rating thrashing".
+        // Example:
+        // 1) File has Banshee=3star, WMP=3star.
+        // 2) We set Banshee=4star.
+        // 3) We want to see 4star in the future, not 3star.
+        private static string[] POPM_creator_list = {
+            POPM_our_creator_name,//this item must be first
+            "Windows Media Player 9 Series",
+            "no@email"
+        };        
+        
+        // Converts ID3v2 POPM rating to Banshee rating
+        private static int PopmToBanshee (byte popm_rating)
+        {
+            // The following schemes are used by the other POPM-compatible players:
+            // WMP/Vista: "Windows Media Player 9 Series" ratings:
+            //   1 = 1, 2 = 64, 3=128, 4=196 (not 192), 5=255
+            // MediaMonkey: "no@email" ratings:
+            //   0.5=26, 1=51, 1.5=76, 2=102, 2.5=128, 3=153, 3.5=178, 4=204, 4.5=230, 5=255
+            // Compatible with both rating scales (what we'll use):
+            //   unrated=0, 1=1-63, 2=64-127, 3=128-191, 4=192-254, 5=255
+            if (popm_rating == 0x0)//unrated
+                return 0;
+            if (popm_rating < 0x40)//1-63
+                return 1;
+            if (popm_rating < 0x80)//64-127
+                return 2;
+            if (popm_rating < 0xC0)//128-191
+                return 3;
+            if (popm_rating < 0xFF)//192-254
+                return 4;
+            return 5;//255
+        }
+        
+        // Converts Banshee rating to ID3v2 POPM rating
+        private static byte BansheeToPopm (int banshee_rating)
+        {
+            switch (banshee_rating) {
+            case 1:
+                return 0x1;
+            case 2:
+                return 0x40;//64
+            case 3:
+                return 0x80;//128
+            case 4:
+                return 0xC0;//192
+            case 5:
+                return 0xFF;//255
+            default:
+                return 0x0;//unrated/unknown
+            }
+        }
+        
+        public static void StoreRating (int rating, int playcount, TagLib.File to_file) {
+            // NOTE: If this rating was originally obtained as a MediaMonkey or WMP POPM rating,
+            // we are now creating a new "Banshee" copy of that rating within the same file.
+            TagLib.Id3v2.Tag id3v2tag = to_file.GetTag (TagLib.TagTypes.Id3v2) as TagLib.Id3v2.Tag;
+            TagLib.Id3v2.PopularimeterFrame popm = TagLib.Id3v2.PopularimeterFrame.Get (id3v2tag,
+                                                                                        POPM_our_creator_name,
+                                                                                        true);
+            popm.Rating = BansheeToPopm (rating);
+            popm.PlayCount = (ulong)playcount;
+        }
+        
+        public static void GetRating (TagLib.File from_file, ref int rating, ref int playcount) {
+            TagLib.Id3v2.PopularimeterFrame popm = null;
+
+            for (int i = 0; i < POPM_creator_list.Length; i++) {
+                TagLib.Id3v2.Tag id3v2tag = from_file.GetTag (TagLib.TagTypes.Id3v2) as TagLib.Id3v2.Tag;
+                popm = TagLib.Id3v2.PopularimeterFrame.Get (id3v2tag, POPM_creator_list[i], false);
+                if (popm != null)
+                    break;
+            }
+
+            if (popm != null) {
+                rating = PopmToBanshee (popm.Rating);
+                playcount = (int)popm.PlayCount;
+            }
+        }
+    }
+    
+    public static class StreamRatingTagger
+    {
+        public static void GetRating (TagLib.File from_file, ref int rating, ref int playcount) {
+            if ((from_file.Tag.TagTypes & TagLib.TagTypes.Id3v2) != 0) {
+                ID3v2RatingTagger.GetRating (from_file, ref rating, ref playcount);
+            }
+        }
+        
+        public static void StoreRating (int rating, int playcount, TagLib.File to_file) {
+            if ((to_file.Tag.TagTypes & TagLib.TagTypes.Id3v2) != 0) {
+                ID3v2RatingTagger.StoreRating (rating, playcount, to_file);
+            }
+        }
+    }
+}
