Example #1
0
        /// <summary>
        /// Extract metadata from the genpuid generated XML report.
        /// </summary>
        /// <param name="xml"></param>
        /// <returns>A Tags instance populated with extracted tag information.</returns>

        private void ExtractGenPUIDTags(string xml, ITrackBasics track)
        {
            XElement root;

            try
            {
                root = XElement.Parse(xml, LoadOptions.None);
            }
            catch (Exception exc)
            {
                Logger.WriteLine(LogCategory, "Error parsing analysis", exc);
                Logger.WriteLine(Logger.Level.Error, LogCategory, xml);
                return;
            }
#if Verbose
            Logger.WriteLine(Logger.Level.Debug, LogCategory,
                             Environment.NewLine + root.ToString(SaveOptions.None) +
                             Environment.NewLine);
#endif
            XNamespace ns = root.GetDefaultNamespace();

            var puid =
                (from a in root.Elements(ns + "track")
                 where a.Attribute(ns + "puid") != null
                 select a.Attribute(ns + "puid")).FirstOrDefault();

            if (puid != null)
            {
                track.UniqueID = puid.Value;
            }
        }
Example #2
0
        /// <summary>
        /// Retrieve the best possible tag information from a series of providers including
        /// genpuid MusicDns and MusicBrainz.
        /// </summary>
        /// <param name="path">Full path of the media file to analyze.</param>
        /// <returns>
        /// A new Tags object containing meta data for the media file.
        /// If metadata could not be found, the Tags.IsPopulated property is <b>false</b>.
        /// </returns>

        public void RetrieveTags(ITrackBasics track)
        {
            if (!File.Exists(track.Location))
            {
                track.UniqueID = null;
                return;
            }

            if (!NetworkStatus.IsAvailable)
            {
                return;
            }

            // enforce MusicBrainz min wait threshold of one second.  the probability of falling
            // waiting is low considering genpuid usually takes longer than a second anyway...
            DateTime now  = DateTime.Now;
            TimeSpan span = now.Subtract(lastdttm);

            if (span.TotalMilliseconds < MinWaitTime)
            {
                Thread.Sleep(MinWaitTime - (int)span.TotalMilliseconds);
            }

            lastdttm = DateTime.Now;

            // genpuid can retrieve basic tag information
            RetrieveGenPUIDTags(track);

            // we have the basics, now override with more complete information
            if (track.IsAnalyzed)
            {
                RetrieveBrainzTags(track);
            }
        }
Example #3
0
        //========================================================================================
        // Methods
        //========================================================================================

        /// <summary>
        /// Use reflection here to load an invoke the taglib-sharp assembly and types.  This way,
        /// we do not need to reference directly the DLL and can store it in the ThirdParty
        /// subdirectory.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>

        public static void ReadFileTags(ITrackBasics track)
        {
            if (tagAssembly == null)
            {
                tagAssembly = Assembly.LoadFrom(@"ThirdParty\taglib-sharp.dll");
            }

            if (tagAssembly != null)
            {
                var type   = tagAssembly.GetType("TagLib.File");
                var create = type.GetMethod("Create", new Type[] { typeof(String) });

                var file = create.Invoke(null, new object[] { track.Location }) as IDisposable;
                if (file != null)
                {
                    TimeSpan span = (TimeSpan)tagAssembly.GetType("TagLib.Properties")
                                    .GetProperty("Duration").GetGetMethod()
                                    .Invoke(type.GetProperty("Properties").GetValue(file, null), null);

                    track.Duration = (long)Math.Floor(span.TotalSeconds);

                    var tag = type.GetProperty("Tag").GetGetMethod().Invoke(file, null);
                    type         = tagAssembly.GetType("TagLib.Tag");
                    track.Album  = type.GetProperty("Album").GetGetMethod().Invoke(tag, null) as String;
                    track.Artist = type.GetProperty("FirstAlbumArtist").GetGetMethod().Invoke(tag, null) as String;
                    track.Title  = type.GetProperty("Title").GetGetMethod().Invoke(tag, null) as String;

                    Logger.WriteLine(Logger.Level.Debug, "Tagger",
                                     String.Format("applied TagLib information ({0} | {1} | {2}) @ {3}s",
                                                   track.Artist, track.Album, track.Title, track.Duration));
                }
            }
        }
Example #4
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="track"></param>
        /// <param name="release"></param>

        private void ReconcileReleaseInfo(ITrackBasics track, ReleaseInfo release)
        {
            if (!String.IsNullOrEmpty(release.Album))
            {
                track.Album = release.Album;
            }

            if (!String.IsNullOrEmpty(release.Artist))
            {
                track.Artist = release.Artist;
            }

            if (!String.IsNullOrEmpty(release.Genre))
            {
                track.Genre = release.Genre;
            }

            if (!String.IsNullOrEmpty(release.Title))
            {
                track.Title = release.Title;
            }

            if (!String.IsNullOrEmpty(release.TrackNumber))
            {
                int number;
                if (Int32.TryParse(release.TrackNumber, out number))
                {
                    if (number > 0)
                    {
                        track.TrackNumber = number;
                    }
                }
            }

            DateTime dttm;

            if (DateTime.TryParse(release.Year, out dttm))
            {
                if ((dttm.Year > 1600) && (dttm.Year < DateTime.Now.Year))
                {
                    track.Year = dttm.Year;
                }
            }
            else
            {
                int year = 0;
                if (Int32.TryParse(release.Year, out year))
                {
                    if ((year >= 1600) && (year <= DateTime.Now.Year))
                    {
                        track.Year = year;
                    }
                }
            }
        }
Example #5
0
        /// <summary>
        /// Cloning construction, actually used to copy basic properties of an actual Track
        /// for the Information Scanner.
        /// </summary>
        /// <param name="track">The track to copy</param>

        public TrackFile(ITrackBasics track)
        {
            this.Album       = track.Album;
            this.Artist      = track.Artist;
            this.ArtistURL   = track.ArtistURL;
            this.Genre       = track.Genre;
            this.Title       = track.Title;
            this.TrackNumber = track.TrackNumber;
            this.Year        = track.Year;
            this.location    = track.Location;
            this.uniqueID    = track.UniqueID;
        }
Example #6
0
        /// <summary>
        /// Fetch quality metadata for the given track and overwrite the track information
        /// with these results.
        /// </summary>
        /// <param name="track">The track to analyze and tag.</param>

        private void RetrieveBrainzTags(ITrackBasics track)
        {
            // query and parse PUID information
            var release = RetrieveReleaseInfo(track);

            if ((release == null) || String.IsNullOrEmpty(release.ArtistID))
            {
                // signals that we could not find any information for this recording
                track.UniqueID = null;
                return;
            }

            // query and parse Artist information (Artist/Band Web site URL)
            RetrieveArtistInfo(track, release.ArtistID);
        }
Example #7
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="track"></param>
        /// <param name="artistID"></param>

        private void RetrieveArtistInfo(ITrackBasics track, string artistID)
        {
            var root = _ArtistRoot =
                QueryBrainz(String.Format(BrainzArtistUri, artistID));

            if (root == null)
            {
                return;
            }

            var ns = root.GetDefaultNamespace();

            // metadata/artist/relation-list/relation@type=url/target
            // seek the relation-list element which contains all (relation type="url") elements

            var relations =
                (from a in root.Elements(ns + "artist").Elements(ns + "relation-list")
                 where a.Attribute("target-type") != null &&
                 a.Attribute("target-type").Value.Equals("url")
                 select a).FirstOrDefault();

            if (relations != null)
            {
                // prioritorize URLs by 1) official homepage, 2) wiki, 3) IMDb
                var target =
                    (from a in relations.Elements(ns + "relation")
                     let type = a.Attribute("type")
                                where type != null &&
                                (type.Value.Equals("official homepage") ||
                                 type.Value.Equals("wikipedia") ||
                                 type.Value.Equals("IMDb"))
                                select a).FirstOrDefault();

                // none of the priorities found so assume first available
                if (target == null)
                {
                    target =
                        (from a in relations.Elements(ns + "relation").Elements(ns + "target")
                         select a).FirstOrDefault();
                }

                if (target != null)
                {
                    track.ArtistURL = target.Value;
                }
            }
        }
Example #8
0
        /// <summary>
        /// Invoke the genpuid utility in a subprocess with the given music file path.
        /// This reads metadata from the local track media file and generates a PUID/fingerprint
        /// that uniquely identifies this track; that PUID can then be used by MusicBrainz to
        /// retrieve quality metadata...
        /// </summary>
        /// <param name="path">The full path of the music file to analyze.</param>
        /// <returns>The analyzed Track including the PUID and any available metadata tags.</returns>

        private void RetrieveGenPUIDTags(ITrackBasics track)
        {
            ProcessStartInfo info = new ProcessStartInfo();

            info.Arguments              = String.Format(@"{0} -xml ""{1}""", DefaultDnsKey, track.Location);
            info.CreateNoWindow         = true;
            info.FileName               = @"ThirdParty\genpuid.exe";
            info.RedirectStandardOutput = true;
            info.UseShellExecute        = false;

            string xml = null;

            try
            {
                using (Process process = new Process())
                {
                    process.StartInfo = info;
                    process.Start();

                    // PriorityClass must be set after process is started.
                    process.PriorityClass = ProcessPriorityClass.BelowNormal;

                    // read before waiting
                    xml = Encoding.UTF8.GetString(
                        process.StandardOutput.CurrentEncoding.GetBytes(
                            process.StandardOutput.ReadToEnd()));

                    if (process.ExitCode != 0)
                    {
                        string error = process.StandardError.ReadToEnd();
                    }

                    // ensures process terminates
                    bool ok = process.WaitForExit(GenTimeout);
                }
            }
            catch (Exception exc)
            {
                Logger.WriteLine(LogCategory, "Error analyzing " + track.Location, exc);
                return;
            }

            if (!String.IsNullOrEmpty(xml))
            {
                ExtractGenPUIDTags(xml, track);
            }
        }
Example #9
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="track"></param>
        /// <returns></returns>

        private ReleaseInfo RetrieveReleaseInfo(ITrackBasics track)
        {
            XElement root = _PuidRoot =
                QueryBrainz(String.Format(BrainzPuidUri, track.UniqueID));

            if (root == null)
            {
                return(null);
            }

            XNamespace ns = root.GetDefaultNamespace();

            // While pretty lengthy and initially perhaps daunting, this rather elegant bit of
            // LINQ generates an ordered list of distinct recordings.  Given the shape of the XML
            // it was more efficient and straight-forward to start at the release level, pulling
            // out the title, track number, and date, and then move back up to the recording
            // element to read the artist and primary tag.  (Reversing this approach made it much
            // more complicated to read all release elements across multiple recordings.)
            //
            // After the initial select where we instantiate ReleaseInfo instances, we first
            // order by album, artist, title, and year - mainly to make the following groupby
            // more efficient, then filter by Distinct to remove exact duplicates leaving either
            // unique items or those that differ only in date.  So, finally, we group by album,
            // and then select only the first element from each album-group.
            //
            // Album  = metadata/puid/recording-list/recording/release-list/release/title
            // Artist = metadata/puid/recording-list/recording/artist-credit/name-credit/artist/sort-name
            // Genre  = metadata/puid/recording-list/recording/artist-credit/name-credit/artist/name/tag-list/tag/name
            // Title  = metadata/puid/recording-list/recording/title
            // Track# = metadata/puid/recording-list/recording/release-list/release/medium-list/medium/track-list/track/position
            // Year   = metadata/puid/recording-list/recording/release-list/release/date

            var releases =
                (from rel in root.Elements(ns + "puid")
                 .Elements(ns + "recording-list").Elements(ns + "recording")
                 .Elements(ns + "release-list").Elements(ns + "release")
                 let med = rel
                           .Elements(ns + "medium-list").Elements(ns + "medium")
                           .Elements(ns + "track-list").Elements(ns + "track").FirstOrDefault()
                           let dat = rel.Elements(ns + "date").FirstOrDefault()
                                     let rec = rel.Parent.Parent
                                               let art = rec
                                                         .Elements(ns + "artist-credit").Elements(ns + "name-credit")
                                                         .Elements(ns + "artist").FirstOrDefault()
                                                         let tag = rec.Elements(ns + "tag-list").Elements(ns + "tag").FirstOrDefault()
                                                                   select new ReleaseInfo
            {
                Album = rel.Elements(ns + "title").FirstOrDefault().Value,
                Artist = art.Element(ns + "name").Value,
                ArtistID = (art.Attribute("id") == null ? String.Empty : art.Attribute("id").Value),
                Genre = (tag == null ? String.Empty : tag.Element(ns + "name").Value),
                Title = rec.Elements(ns + "title").FirstOrDefault().Value,
                TrackNumber = (med == null ? "0" : med.Element(ns + "position").Value),
                Year = (dat == null ? String.Empty : dat.Value)
            }).OrderBy(x => x.Album).ThenBy(x => x.Artist).ThenBy(x => x.Title).ThenBy(x => x.Year)
                .Distinct()
                .GroupBy(c => c.Album, c => c,
                         (key, elements) => new { Key = key, Item = elements.ElementAt(0) })
                .Select(r => r.Item);

            if ((releases == null) || (releases.Count() == 0))
            {
                return(null);
            }

            // convert LINQ results to a more concrete List<> so we can requery the results
            // without a stack overflow.  When reusing local variables to filter results, the
            // deferred execution nature of LINQ will cause a stack overflow because the original
            // query is run again when that variable is referenced later on.

            var candidates = releases.ToList();

            ReleaseInfo release = null;

            if (candidates.Count() == 1)
            {
                // since the URI filters out all but Official releases, we're presuming
                // if there are multiple releases, we can safely choose the first one
                release = candidates.First();
                ReconcileReleaseInfo(track, release);
            }
            else
            {
                int index;

                // match by priority: 1) Title... 2) Artist... 3) Album
                // we keep filtering the result list until we narrow it down to the most
                // probable candidate based on the available fuzzy criteria

                if (!String.IsNullOrEmpty(track.Title))
                {
                    // find the one entry with the best match for Title
                    index = track.Title.SelectSimilar(candidates.Select(r => r.Title));

                    if (index >= 0)
                    {
                        // select all entries with that same result Title
                        var entries =
                            from r in candidates
                            where r.Title.Equals(candidates.ElementAt(index).Title)
                            select r;

                        // the filtered results now becomes our new release candidates list
                        if ((entries != null) && (entries.Count() > 0))
                        {
                            candidates = entries.ToList();
                        }
                    }
                }

                // 2) Artist

                if ((candidates != null) && (candidates.Count() > 1) &&
                    !String.IsNullOrEmpty(track.Artist))
                {
                    index = track.Artist.SelectSimilar(candidates.Select(r => r.Artist));

                    if (index >= 0)
                    {
                        var entries =
                            from r in candidates
                            where r.Artist.Equals(candidates.ElementAt(index).Artist)
                            select r;

                        if ((entries != null) && (entries.Count() > 0))
                        {
                            candidates = entries.ToList();
                        }
                    }
                }

                // 3) Album

                if ((candidates != null) && (candidates.Count() > 1) &&
                    !String.IsNullOrEmpty(track.Album))
                {
                    index = track.Album.SelectSimilar(candidates.Select(r => r.Album));

                    if (index >= 0)
                    {
                        var entries =
                            from r in candidates
                            where r.Album.Equals(candidates.ElementAt(index).Album)
                            select r;

                        if ((entries != null) && (entries.Count() > 0))
                        {
                            candidates = entries.ToList();
                        }
                    }
                }

                if ((candidates != null) && (candidates.Count() > 0))
                {
                    // found a probable match
                    release = candidates.First();
                }
                else
                {
                    // did not confidently find a match, so choose the first chronologically
                    release = releases.OrderBy(r => r.Year).First();
                }

                if (release != null)
                {
                    ReconcileReleaseInfo(track, release);
                }
            }

            return(release);
        }