cowbell r36 - in trunk: base gui



Author: btaylor
Date: Mon Jun  2 04:35:27 2008
New Revision: 36
URL: http://svn.gnome.org/viewvc/cowbell?rev=36&view=rev

Log:
Some work in progress album cover support with MusicBrainz


Modified:
   trunk/base/IMetadataProxy.cs
   trunk/base/IMetadataProxyService.cs
   trunk/base/MetadataProxyService.cs
   trunk/base/MusicBrainzMetadataProxy.cs
   trunk/base/Utils.cs
   trunk/gui/AlbumCoverImage.cs

Modified: trunk/base/IMetadataProxy.cs
==============================================================================
--- trunk/base/IMetadataProxy.cs	(original)
+++ trunk/base/IMetadataProxy.cs	Mon Jun  2 04:35:27 2008
@@ -28,6 +28,7 @@
 	public interface IMetadataProxy
 	{
 		void Import (MetadataImportCompletedHandler completed_handler);
+		byte[] FetchAlbumArtwork ();
 	}
 
 	public class MetadataImportCompletedArgs : EventArgs

Modified: trunk/base/IMetadataProxyService.cs
==============================================================================
--- trunk/base/IMetadataProxyService.cs	(original)
+++ trunk/base/IMetadataProxyService.cs	Mon Jun  2 04:35:27 2008
@@ -26,5 +26,6 @@
 	public interface IMetadataProxyService : IService
 	{
 		void FetchMetadata (MetadataImportCompletedHandler import_completed);
+		byte[] FetchAlbumArtwork ();
 	}
 }

Modified: trunk/base/MetadataProxyService.cs
==============================================================================
--- trunk/base/MetadataProxyService.cs	(original)
+++ trunk/base/MetadataProxyService.cs	Mon Jun  2 04:35:27 2008
@@ -50,6 +50,18 @@
 			});
 		}
 
+		// TODO:
+		public byte[] FetchAlbumArtwork ()
+		{
+			foreach (DictionaryEntry d in proxies)
+			{
+				IMetadataProxy p = (IMetadataProxy)d.Value;
+				return p.FetchAlbumArtwork ();
+			}
+			
+			return null;
+		}
+
 		/**
 		 * Scans all external MetadataServices for matches to the
 		 * current tracks.  When finished, import_completed is invoked

Modified: trunk/base/MusicBrainzMetadataProxy.cs
==============================================================================
--- trunk/base/MusicBrainzMetadataProxy.cs	(original)
+++ trunk/base/MusicBrainzMetadataProxy.cs	Mon Jun  2 04:35:27 2008
@@ -1,6 +1,6 @@
 /*
  * cowbell
- * Copyright (c) 2005 Brad Taylor
+ * Copyright (c) 2008 Brad Taylor
  *
  * This file is part of cowbell.
  *
@@ -34,30 +34,33 @@
 	public class MusicBrainzMetadataProxy : IMetadataProxy
 	{
 #region public methods
-		public void Import (MetadataImportCompletedHandler completed)
+		public MusicBrainzMetadataProxy ()
 		{
 			db_svc = (IDatabaseService)ServiceManager.GetService (
 					typeof (IDatabaseService));
 
 			dispatch_svc = (IDispatchService)ServiceManager.GetService (
 					typeof (IDispatchService));
-			
-			Artist a = FindClosestArtist (db_svc.GlobalData.Artist);
-			if (a == null) {
+		}
+
+		public void Import (MetadataImportCompletedHandler completed)
+		{
+			Artist artist;
+			Release release;
+			FindClosestAlbumAndRelease (out artist, out release);
+
+			if (artist == null) {
 				completed (this, new MetadataImportCompletedArgs (
 					MetadataImportStatus.ArtistNotFound));
 				return;
-			}
-
-			Release r = FindClosestRelease (a, db_svc.GlobalData.Album);
-			if (r == null) {
+			} else if (release == null) {
 				completed (this, new MetadataImportCompletedArgs (
 					MetadataImportStatus.ReleaseNotFound));
 				return;
 			}
-			
+
 			int num_matches;
-			MatchTracks (a, r, db_svc, out num_matches);
+			MatchTracks (artist, release, db_svc, out num_matches);
 
 			if (num_matches == db_svc.Count) {
 				completed (this, new MetadataImportCompletedArgs (
@@ -68,6 +71,23 @@
 			completed (this, new MetadataImportCompletedArgs (
 				MetadataImportStatus.PartialTrackMatch));
 		}
+
+		public byte[] FetchAlbumArtwork ()
+		{
+			Artist artist;
+			Release release;
+			FindClosestAlbumAndRelease (out artist, out release);
+
+			if (release == null) {
+				return null;
+			}
+
+			string asin = release.GetAsin ();
+			if (String.IsNullOrEmpty (asin))
+				return null;
+
+			return Utils.GetBytesAtUri (String.Format (AMAZON_URI_FORMAT, asin));
+		}
 #endregion
 
 #region private delegates
@@ -77,6 +97,11 @@
 #region private fields
 		private IDatabaseService db_svc;
 		private IDispatchService dispatch_svc;
+
+		private Dictionary<string, Artist> mb_artists_cache = new Dictionary<string, Artist> ();
+		private Dictionary<string, Release> mb_releases_cache = new Dictionary<string, Release> ();
+
+		private const string AMAZON_URI_FORMAT = "http://images.amazon.com/images/P/{0}.01._SCLZZZZZZZ_.jpg";;
 		private const string LUCENE_ARTIST_FORMAT = "artist:{0}";
 		private const int MAX_TRIES = 5;
 		private const int ACCEPT_DISTANCE_THRESHOLD = 2;
@@ -154,6 +179,42 @@
 			}
 		}
 
+		private void FindClosestAlbumAndRelease (out Artist artist, out Release release)
+		{
+			artist = null;
+			release = null;
+
+			if (db_svc.GlobalData == null) {
+				return;
+			}
+
+			// TODO: sanitize this somehow?
+			string artist_name = db_svc.GlobalData.Artist;
+
+			if (!mb_artists_cache.ContainsKey (artist_name)) {
+				artist = FindClosestArtist (artist_name);
+				if (artist == null) {
+					return;
+				}
+				mb_artists_cache[artist_name] = artist;
+			}
+
+			artist = mb_artists_cache[artist_name];
+
+			// TODO: sanitize this somehow?
+			string album_name = db_svc.GlobalData.Album;
+			
+			if (!mb_releases_cache.ContainsKey (album_name)) {
+				release = FindClosestRelease (artist, album_name);
+				if (release == null) {
+					return;
+				}
+				mb_releases_cache[album_name] = release;
+			}
+
+			release = mb_releases_cache[album_name];
+		}
+
 		private Release FindClosestRelease (Artist artist, string release_search)
 		{
 			int min_distance = Int32.MaxValue;

Modified: trunk/base/Utils.cs
==============================================================================
--- trunk/base/Utils.cs	(original)
+++ trunk/base/Utils.cs	Mon Jun  2 04:35:27 2008
@@ -20,8 +20,11 @@
  */
 
 using System;
+using System.IO;
+using System.Net;
+using System.Web;
 using System.Threading;
-using System.Collections;
+using System.Collections.Generic;
 using System.Globalization;
 
 namespace Cowbell.Base
@@ -97,7 +100,7 @@
 
 		public static string HumanReadableTimeSpan (TimeSpan span)
 		{
-			ArrayList list = new ArrayList ();
+			List<string> list = new List<string> ();
 			if (span.Days > 0) {
 				list.Add (String.Format (Catalog.GetPluralString ("{0} day", "{0} days", span.Days), span.Days));
 			}
@@ -114,7 +117,7 @@
 				list.Add (String.Format (Catalog.GetPluralString ("{0} second", "{0} seconds", span.Seconds), span.Seconds));
 			}
 
-			return String.Join (", ", (string[])list.ToArray (typeof (string))); 
+			return String.Join (", ", list.ToArray ());
 		}
 
 		public static string ToTitleCase (string str)
@@ -164,5 +167,48 @@
 			}
 			return String.Join (" ", tokens);
 		}
+
+		public static byte[] GetBytesAtUri (string uri)
+		{
+			try {
+				HttpWebRequest req = (HttpWebRequest)WebRequest.Create (uri);
+				req.UserAgent = "Cowbell";
+				req.KeepAlive = false;
+				req.Timeout = 30000; // 30 sec
+
+				List<byte> bytes = new List<byte> ();
+				using (WebResponse resp = req.GetResponse ()) {
+					// This number won't always be correct,
+					// but it could be a good starting
+					// point, also ignore it if its > 5 MB
+					if (resp.ContentLength < 5242880)
+						bytes.Capacity = (int)resp.ContentLength;
+
+					Stream s = resp.GetResponseStream ();
+				
+					byte[] buffer = new byte[8192];
+					int n;
+					
+					while ((n = s.Read (buffer, 0, buffer.Length)) != 0) {
+						if (n == 0) {
+							break;
+						}
+
+						if (n < buffer.Length) {
+							// resize the array
+							byte[] tmp = new byte[n];
+							Array.Copy (buffer, tmp, n);
+							buffer = tmp;
+						}
+
+						bytes.AddRange (buffer);
+					}
+				}
+
+				return bytes.ToArray ();
+			} catch (WebException) {
+				return null;
+			}
+		}
 	}
 }

Modified: trunk/gui/AlbumCoverImage.cs
==============================================================================
--- trunk/gui/AlbumCoverImage.cs	(original)
+++ trunk/gui/AlbumCoverImage.cs	Mon Jun  2 04:35:27 2008
@@ -42,11 +42,13 @@
 	 */
 	public class AlbumCoverImage : EventBox
 	{
-		/* public methods */
+#region public methods
 		public AlbumCoverImage ()
 		{
 			db_svc = (IDatabaseService)ServiceManager.GetService (
 					typeof (IDatabaseService));
+			prx_svc = (IMetadataProxyService)ServiceManager.GetService (
+					typeof (IMetadataProxyService));
 
 			pixbuf = null;
 			UserPixbuf = false;
@@ -120,8 +122,13 @@
 					}
 					Refresh ();
 				} else {
+					// We're already loading
+					if (original == loadingcover) {
+						return;
+					}
+
 					ShowLoadingCover ();
-					dispatch_svc.BackgroundDispatch (new VoidHandler (AmazonDownload));
+					dispatch_svc.BackgroundDispatch (new VoidHandler (ProxyDownload));
 				}
 			}
 		}
@@ -137,75 +144,73 @@
 			ShowDefaultCover ();
 			UserPixbuf = false;
 		}
+#endregion
 
-		/* private fields */
-		Gtk.Image image;
+#region private fields
+		private Gtk.Image image;
 
-		static Pixbuf defaultcover;
-		static Pixbuf loadingcover;
-		Pixbuf pixbuf;
-		Pixbuf original;
-		string stamp;
+		private static Pixbuf defaultcover;
+		private static Pixbuf loadingcover;
 
-		Hashtable covers;
-		Hashtable large_covers;
-		
-		bool UserPixbuf;
+		private Pixbuf pixbuf;
+		private Pixbuf original;
+		private string stamp;
 
-//		static string DevTag = "1G1CFFX6R1ZWFXDMVJG2";
+		private Hashtable covers;
+		private Hashtable large_covers;
+		
+		private bool UserPixbuf;
 
-		static TargetEntry[] CoverDragEntries = new TargetEntry[] {
+		private static TargetEntry[] CoverDragEntries = new TargetEntry[] {
 			new TargetEntry ("text/uri-list", 0, (uint)TargetType.UriList),
 			new TargetEntry ("x-special/gnome-icon-list", 0, (uint)TargetType.UriList),
 			new TargetEntry ("_NETSCAPE_URL", 0, (uint)TargetType.Uri)
 		};
 
-		static string[] CoverFilenames = {"cover.jpg",
-		                                  "Cover.jpg",
-		                                  "cover.jpeg",
-		                                  "Cover.jpeg",
-		                                  "cover.png",
-		                                  "Cover.png",
-		                                  "cover.gif",
-		                                  "Cover.gif",
-		                                  "cover.bmp",
-		                                  "Cover.bmp",
-		                                  "folder.jpg",
-		                                  "Folder.jpg",
-		                                  "folder.jpeg",
-		                                  "Folder.jpeg",
-		                                  ".folder.png",
-		                                  "folder.png",
-		                                  "Folder.png"};
+		private static string[] CoverFilenames = {
+			"cover.jpg", "Cover.jpg", "cover.jpeg",
+			"Cover.jpeg", "cover.png", "Cover.png",
+			"cover.gif", "Cover.gif", "cover.bmp",
+			"Cover.bmp", "folder.jpg", "Folder.jpg",
+			"folder.jpeg", "Folder.jpeg", ".folder.png",
+			"folder.png", "Folder.png"
+		};
 
-		enum TargetType {
+		private enum TargetType {
 			UriList,
 			Uri
 		};
 		
 		private IDatabaseService db_svc;
+		private IMetadataProxyService prx_svc;
+#endregion
 
-		/* private methods */
+#region private methods
 		private void ShowLoadingCover ()
 		{
 			original = loadingcover;
 			Refresh ();
 		}
 
-		private void AmazonDownload ()
+		private void ProxyDownload ()
 		{
+			byte[] bytes = prx_svc.FetchAlbumArtwork ();
+
 			try {
-				pixbuf = GetCoverFromAmazon ();
+				if (bytes != null && bytes.Length > 0) {
+					pixbuf = new Gdk.Pixbuf (bytes);
+				}
 			} catch (Exception e) {
 				pixbuf = null;
 
 				Debug.WriteLine (e.Message);
 				Debug.WriteLine (e.StackTrace);
 			}
-			GLib.Idle.Add (new GLib.IdleHandler (ProcessAmazonDownload));
+
+			GLib.Idle.Add (new GLib.IdleHandler (ProcessCoverDownload));
 		}
 
-		private bool ProcessAmazonDownload ()
+		private bool ProcessCoverDownload ()
 		{
 			lock (this) {
 				if (pixbuf == null) {
@@ -256,170 +261,6 @@
 			return border;
 		}
 
-		/*
-		 * TODO: Rewrite this code based on AmazonMetadataProxy
-		 */
-		private Pixbuf GetCoverFromAmazon ()
-		{
-/*
-			Song song = db_svc.GlobalData;
-
-			AmazonSearchService search_service = new AmazonSearchService ();
-
-			string sane_album_title = SanitizeString (song.Album);
-
-			string [] album_title_array = sane_album_title.Split (' ');
-			Array.Sort (album_title_array);
-
-			* This assumes the right artist is always in Artists [0] *
-			string sane_artist = SanitizeString (song.Artist);
-			
-			* Prepare for handling multi-page results *
-			int total_pages = 1;
-			int current_page = 1;
-			int max_pages = 2; * check no more than 2 pages *
-			
-			* Create Encapsulated Request *
-			ArtistRequest asearch = new ArtistRequest ();
-			asearch.devtag = DevTag;
-
-			if (!db_svc.MultipleArtists)
-				asearch.artist = sane_artist;
-			else
-				asearch.artist = "Various Artists";
-
-			asearch.keywords = sane_album_title;
-			asearch.type = "heavy";
-			asearch.mode = "music";
-			asearch.tag = "webservices-20";
-
-			* Use selected Amazon service *
-			search_service.Url = "http://soap.amazon.com/onca/soap3";;
-
-			double best_match_percent = 0.0;
-			Pixbuf best_match = null;
-
-			while (current_page <= total_pages && current_page <= max_pages)
-			{
-				asearch.page = Convert.ToString (current_page);
-
-				ProductInfo pi;
-				
-				* Amazon API requires this .. *
-				System.Threading.Thread.Sleep (1000);
-			
-				* Web service calls timeout after 30 seconds *
-				search_service.Timeout = 30000;
-				
-				* This may throw an exception, we catch it in Song.cs in the calling function *
-				pi = search_service.ArtistSearchRequest (asearch);
-
-				int num_results = pi.Details.Length;
-				total_pages = Convert.ToInt32 (pi.TotalPages);
-
-				* Work out how many matches are on this page *
-				if (num_results < 1)
-					return null;
-
-				for (int i = 0; i < num_results; i++)
-				{
-					* Ignore bracketed text on the result from Amazon *
-					string sane_product_name = SanitizeString (pi.Details[i].ProductName);
-
-					* Compare the two strings statistically *
-					string [] product_name_array = sane_product_name.Split (' ');
-					Array.Sort (product_name_array);
-
-					int match_count = 0;
-					foreach (string s in album_title_array)
-					{
-						if (Array.BinarySearch (product_name_array, s) >= 0)
-							match_count++;
-					} 
-
-					double match_percent;
-					match_percent = match_count / (double) album_title_array.Length;
-
-					if (match_percent >= 0.6) {
-						string url = pi.Details[i].ImageUrlMedium;
-
-						if (url != null && url.Length > 0) {
-							double backward_match_percent = 0.0;
-							int backward_match_count = 0;
-
-							foreach (string s in product_name_array)
-							{
-								if (Array.BinarySearch (album_title_array, s) >= 0)
-									backward_match_count++;
-							}
-
-							backward_match_percent = backward_match_count / (double) product_name_array.Length;
-
-							double total_match_percent = match_percent + backward_match_percent;
-
-							if (total_match_percent > best_match_percent) {
-								Pixbuf pix;
-								
-								try {
-									pix = GetCoverFromUrl (url);
-								} catch (WebException e) {
-									throw e;
-								} catch (Exception) {
-									pix = null;
-								}
-
-								if (pix != null) {
-									best_match_percent = total_match_percent;
-									best_match = pix;
-
-									if (best_match_percent == 2.0)
-										return best_match;
-								}
-							}
-							// ELSE keep iterating to find a better match
-						}
-					}
-				}
-
-				current_page++;
-			}
-
-			return best_match;
-*/
-			return null;
-		}
-
-		private Pixbuf GetCoverFromUrl (string url)
-		{
-			Pixbuf cover;
-
-			/* read the cover image */
-			HttpWebRequest req = (HttpWebRequest) WebRequest.Create (url);
-			req.UserAgent = "Cowbell";
-			req.KeepAlive = false;
-			req.Timeout = 30000; /* Timeout after 30 seconds */
-				
-			WebResponse resp = null;
-		
-			/*
-			 * May throw an exception, but we catch it in the calling
-			 * function in Song.cs 
-			 */
-			resp = req.GetResponse ();
-
-			Stream s = resp.GetResponseStream ();
-		
-			cover = new Pixbuf (s);
-
-			resp.Close ();
-
-			/* Trap Amazon 1x1 images */
-			if (cover.Height == 1 && cover.Width == 1)
-				return null;
-
-			return cover;
-		}
-
 		private Pixbuf GetCoverLocal (string path)
 		{
 			Pixbuf cover;
@@ -433,19 +274,6 @@
 			return cover;
 		}
 
-/*
-		private string SanitizeString (string s)
-		{
-			s = s.ToLower ();
-			s = Regex.Replace (s, "\\(.*\\)", "");
-			s = Regex.Replace (s, "\\[.*\\]", "");
-			s = s.Replace ("-", " ");
-			s = s.Replace ("_", " ");
-
-			return s;
-		}
-*/
-
 		/* DnD Callbacks mostly stolen from Jorn Baayen */
 		private void DragDataReceivedCb (object o, DragDataReceivedArgs args)
 		{
@@ -464,14 +292,18 @@
 				if (uri.Scheme != "http")
 					break;
 
+				success = false;
+
+				byte[] bytes = Utils.GetBytesAtUri (SafeUri.UriToFilename (uri));
+				if (bytes == null || bytes.Length == 0) {
+					break;
+				}
+
 				try {
-					original = GetCoverFromUrl (SafeUri.UriToFilename (uri));
+					original = new Gdk.Pixbuf (bytes);
 					UserPixbuf = true;
 					success = true;
-				} catch {
-					success = false;
-					break;
-				}
+				} catch { }
 
 				break;
 			
@@ -482,14 +314,12 @@
 				if (path == null)
 					break;
 			
+				success = false;
 				try {
 					original = GetCoverLocal (path);
 					UserPixbuf = true;
 					success = true;
-				} catch {
-					success = false;
-					break;
-				}
+				} catch { }
 
 				break;
 			}
@@ -511,5 +341,6 @@
 			}
 			return "0";
 		}
+#endregion
 	}
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]