Ejemplo n.º 1
0
        public async Task <IActionResult> OnGetAsync([FromQuery] int offset = 0)
        {
            this.Offset = offset;
            VisifyUser user = await _userManager.GetUserAsync(User);

            if (user == null)
            {
                return(NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."));
            }

            VOption <IList <VisifySavedTrack> > stracks = await DatabaseService.GetUsersSavedTracks(user.Id, offset, 50);

            if (!stracks.WasSuccess)
            {
                StatusMessage = "Sorry, we coudln't retrieve your saved songs, we are having database problems. Please try again later.";
                IsDisabled    = true;
            }
            VOption <int> libCountO = await DatabaseService.GetUserLibraryCount(user.Id);

            if (!libCountO.WasSuccess)
            {
                StatusMessage = "Sorry, we coudln't retrieve your saved songs, we are having database problems. Please try again later.";
                IsDisabled    = true;
            }

            SavedTracks    = stracks.Value;
            LibraryCount   = libCountO.Value;
            CanGoForwards  = (!IsDisabled && this.Offset < LibraryCount);
            CanGoBackwards = (!IsDisabled && this.Offset > 0);

            return(Page());
        }
Ejemplo n.º 2
0
        public async Task <VOption <RateLimit> > GetRateLimits(string userid)
        {
            // Check user is rate limited
            VOption <RateLimit> rlo = await DatabaseService.GetUsersRateLimits(userid);

            return(rlo);
        }
Ejemplo n.º 3
0
        public async Task <IActionResult> GetArtistLinks()
        {
            VisifyUser user = await _userManager.GetUserAsync(User);

            if (user == null)
            {
                logger.LogInformation($"Tried to get Artist Song Count graph data for user {_userManager.GetUserId(User)}, but user did not exist");
                return(NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."));
            }

            // Get the tracks and artists
            VOption <IList <VisifySavedTrack> > stracks = await DatabaseService.GetUsersSavedTracks(user.Id, limit : 10_000);

            if (!stracks.WasSuccess)
            {
                logger.LogError($"Failed to retreive saved tracks for user {_userManager.GetUserId(User)}", stracks.ErrorMessage);
                return(Json("FAILED"));
            }

            // create a proper d3 structure
            Dictionary <string, FDGNode> nodes = new Dictionary <string, FDGNode>();
            Dictionary <string, FDGEdge> edges = new Dictionary <string, FDGEdge>();

            foreach (VisifySavedTrack vst in stracks.Value)
            {
                string   tid   = $"tid_{vst.VisifyTrack.SpotifyId}";
                string[] idarr = new string[2];
                foreach (VisifyArtist va in vst.VisifyTrack.Artists)
                {
                    string sid = $"aid_{va.SpotifyId}";
                    idarr[0] = sid;
                    idarr[1] = tid;
                    Array.Sort(idarr);
                    string lid0 = String.Join("", idarr);
                    string lid1 = $"at_{lid0}";
                    if (!nodes.ContainsKey(sid))
                    {
                        nodes.Add(sid, new FDGNode(sid, 0, va.ArtistName, 0, 1));
                    }
                    else
                    {
                        nodes[sid].size += 1;
                    }
                    nodes.Add(lid1, new FDGNode(lid1, 0, vst.VisifyTrack.TrackName, 1, 1));
                    if (!edges.ContainsKey(lid0))
                    {
                        edges.Add(lid0, new FDGEdge(lid1, sid, 0.1));
                    }
                }
            }

            Dictionary <string, object> keys = new Dictionary <string, object>()
            {
                { "nodes", nodes.Values },
                { "links", edges.Values }
            };

            return(Json(keys));
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Draw a circle on an image.
        /// </summary>
        /// <example>
        /// <code language="lang-csharp">
        /// image.Mutate(x => x.DrawCircle(ink, cx, cy, radius, fill: bool));
        /// </code>
        /// </example>
        /// <param name="ink">Color for pixels.</param>
        /// <param name="cx">Centre of draw_circle.</param>
        /// <param name="cy">Centre of draw_circle.</param>
        /// <param name="radius">Radius in pixels.</param>
        /// <param name="fill">Draw a solid object.</param>
        public void DrawCircle(double[] ink, int cx, int cy, int radius, bool?fill = null)
        {
            var options = new VOption();

            options.AddIfPresent(nameof(fill), fill);

            this.Call("draw_circle", options, ink, cx, cy, radius);
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Paint a rectangle on an image.
        /// </summary>
        /// <example>
        /// <code language="lang-csharp">
        /// image.Mutate(x => x.DrawRect(ink, left, top, width, height, fill: bool));
        /// </code>
        /// </example>
        /// <param name="ink">Color for pixels.</param>
        /// <param name="left">Rect to fill.</param>
        /// <param name="top">Rect to fill.</param>
        /// <param name="width">Rect to fill.</param>
        /// <param name="height">Rect to fill.</param>
        /// <param name="fill">Draw a solid object.</param>
        public void DrawRect(double[] ink, int left, int top, int width, int height, bool?fill = null)
        {
            var options = new VOption();

            options.AddIfPresent(nameof(fill), fill);

            this.Call("draw_rect", options, ink, left, top, width, height);
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Paint an image into another image.
        /// </summary>
        /// <example>
        /// <code language="lang-csharp">
        /// image.Mutate(x => x.DrawImage(sub, x, y, mode: Enums.CombineMode));
        /// </code>
        /// </example>
        /// <param name="sub">Sub-image to insert into main image.</param>
        /// <param name="x">Draw image here.</param>
        /// <param name="y">Draw image here.</param>
        /// <param name="mode">Combining mode.</param>
        public void DrawImage(Image sub, int x, int y, Enums.CombineMode?mode = null)
        {
            var options = new VOption();

            options.AddIfPresent(nameof(mode), mode);

            this.Call("draw_image", options, sub, x, y);
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Flood-fill an area.
        /// </summary>
        /// <example>
        /// <code language="lang-csharp">
        /// image.Mutate(x => x.DrawFlood(ink, x, y, test: Image, equal: bool));
        /// </code>
        /// </example>
        /// <param name="ink">Color for pixels.</param>
        /// <param name="x">DrawFlood start point.</param>
        /// <param name="y">DrawFlood start point.</param>
        /// <param name="test">Test pixels in this image.</param>
        /// <param name="equal">DrawFlood while equal to edge.</param>
        public void DrawFlood(double[] ink, int x, int y, Image test = null, bool?equal = null)
        {
            var options = new VOption();

            options.AddIfPresent(nameof(test), test);
            options.AddIfPresent(nameof(equal), equal);

            this.Call("draw_flood", options, ink, x, y);
        }
Ejemplo n.º 8
0
        /// <summary>
        /// Flood-fill an area.
        /// </summary>
        /// <example>
        /// <code language="lang-csharp">
        /// image.Mutate(x => x.DrawFlood(ink, x, y, out var left, test: Image, equal: bool));
        /// </code>
        /// </example>
        /// <param name="ink">Color for pixels.</param>
        /// <param name="x">DrawFlood start point.</param>
        /// <param name="y">DrawFlood start point.</param>
        /// <param name="left">Left edge of modified area.</param>
        /// <param name="test">Test pixels in this image.</param>
        /// <param name="equal">DrawFlood while equal to edge.</param>
        public void DrawFlood(double[] ink, int x, int y, out int left, Image test = null, bool?equal = null)
        {
            var options = new VOption();

            options.AddIfPresent(nameof(test), test);
            options.AddIfPresent(nameof(equal), equal);

            options.Add("left", true);

            var results = this.Call("draw_flood", options, ink, x, y) as object[];

            var opts = results?[1] as VOption;

            left = opts?["left"] is int out1 ? out1 : 0;
        }
Ejemplo n.º 9
0
        internal VOption BuildLoadOptionsFromImageType(ImageType imageType)
        {
            var result = new VOption();

            if (imageType == ImageType.Svg || imageType == ImageType.Pdf)
            {
                result.Add("dpi", Options.Density);
            }
            else if (imageType == ImageType.Magick)
            {
                result.Add("density", Options.Density.ToString(CultureInfo.InvariantCulture));
            }

            if (imageType.SupportsPages)
            {
                result.Add("n", Options.PageCount);
                result.Add("page", Options.PageIndex);
            }

            return(result);
        }
Ejemplo n.º 10
0
        /// <summary>
        /// Flood-fill an area.
        /// </summary>
        /// <example>
        /// <code language="lang-csharp">
        /// image.Mutate(x => x.DrawFlood(ink, x, y, out var left, out var top, out var width, out var height, test: Image, equal: bool));
        /// </code>
        /// </example>
        /// <param name="ink">Color for pixels.</param>
        /// <param name="x">DrawFlood start point.</param>
        /// <param name="y">DrawFlood start point.</param>
        /// <param name="left">Left edge of modified area.</param>
        /// <param name="top">top edge of modified area.</param>
        /// <param name="width">width of modified area.</param>
        /// <param name="height">height of modified area.</param>
        /// <param name="test">Test pixels in this image.</param>
        /// <param name="equal">DrawFlood while equal to edge.</param>
        public void DrawFlood(double[] ink, int x, int y, out int left, out int top, out int width, out int height, Image test = null, bool?equal = null)
        {
            var options = new VOption();

            options.AddIfPresent(nameof(test), test);
            options.AddIfPresent(nameof(equal), equal);

            options.Add("left", true);
            options.Add("top", true);
            options.Add("width", true);
            options.Add("height", true);

            var results = this.Call("draw_flood", options, ink, x, y) as object[];

            var opts = results?[1] as VOption;

            left   = opts?["left"] is int out1 ? out1 : 0;
            top    = opts?["top"] is int out2 ? out2 : 0;
            width  = opts?["width"] is int out3 ? out3 : 0;
            height = opts?["height"] is int out4 ? out4 : 0;
        }
Ejemplo n.º 11
0
        private async Task <VOption <OAuthToken> > CheckRefesh(OAuthToken token)
        {
            // Check if keys need to be refreshed
            if (token.ShouldRenew)
            {
                VOption <OAuthToken> refreshed = await RenewToken(token);

                if (!refreshed.WasSuccess)
                {
                    // failed. Error and move on.
                    logger.Error($"Failed to renew tokens for some reason for user {token.AspNetUserId}");
                    return(new VOption <OAuthToken>(refreshed.ErrorCode, refreshed.ErrorMessage));
                }
                token = refreshed.Value;
                // Save token to db
                VOption <bool> opt = await SaveTokenToDb(token);

                if (!opt.WasSuccess)
                {
                    return(new VOption <OAuthToken>(ErrorCodes.DatabaseWriteError, $"Failed to write tokens to database for user {token.AspNetUserId}")); // Database failure
                }
            }
            return(new VOption <OAuthToken>(token));
        }
Ejemplo n.º 12
0
        internal void SaveLoadBuffer(string saver, string loader, Image im, int maxDiff = 0, VOption kwargs = null)
        {
            var buf = Operation.Call(saver, kwargs, im) as byte[];
            var x   = Operation.Call(loader, buf) as Image;

            Assert.Equal(x.Width, im.Width);
            Assert.Equal(x.Height, im.Height);
            Assert.Equal(x.Bands, im.Bands);
            Assert.True((im - x).Abs().Max() <= maxDiff);
        }
Ejemplo n.º 13
0
        public string Execute(string[] args)
        {
            // If you set a number to zero (0), it will resize on the other specified axis.
            var width  = 200;
            var height = 0;

            // "both" - for both up and down.
            // "up" - only upsize.
            // "down" - only downsize.
            // "force" - force size, that is, break aspect ratio.
            const string size = "both";

            // Just for example.
            var buffer = File.ReadAllBytes(Filename);

            // Find the name of the load operation vips will use to load a buffer
            // so that we can work out what options to pass to NewFromBuffer().
            var loader = Image.FindLoadBuffer(buffer);

            if (loader == null)
            {
                // No known loader is found, stop further processing.
                throw new Exception("Invalid or unsupported image format. Is it a valid image?");
            }

            var loadOptions = new VOption
            {
                { "access", Enums.Access.Sequential },
                { "fail", FailOnError }
            };
            var stringOptions = "";

            if (LoaderSupportPage(loader))
            {
                // -1 means "until the end of the document", handy for animated images.
                loadOptions.Add("n", -1);
                stringOptions = "[n=-1]";
            }

            Image image;

            try
            {
                image = (Image)Operation.Call(loader, loadOptions, buffer);

                // Or:
                // image = Image.NewFromBuffer(buffer, kwargs: loadOptions);
                // (but the loader is already found, so the above will be a little faster).
            }
            catch (VipsException e)
            {
                throw new Exception("Image has a corrupt header.", e);
            }

            var inputWidth  = image.Width;
            var inputHeight = image.Height;

            // Use 64-bit unsigned type, to handle PNG decompression bombs.
            if ((ulong)(inputWidth * inputHeight) > MaxImageSize)
            {
                throw new Exception(
                          "Image is too large for processing. Width x height should be less than 71 megapixels.");
            }

            var pageHeight = image.PageHeight;

            var isCmyk          = image.Interpretation == Enums.Interpretation.Cmyk;
            var isLabs          = image.Interpretation == Enums.Interpretation.Labs;
            var embeddedProfile = image.Contains(VipsMetaIccName);

            string importProfile = null;
            string exportProfile = null;
            string intent        = null;

            // Ensure we're using a device-independent color space
            if ((embeddedProfile || isCmyk) && !isLabs)
            {
                // Embedded profile; fallback in case the profile embedded in the image
                // is broken. No embedded profile; import using default CMYK profile.
                importProfile = isCmyk ? Enums.Interpretation.Cmyk : Enums.Interpretation.Srgb;

                // Convert to sRGB using embedded or import profile.
                exportProfile = Enums.Interpretation.Srgb;

                // Use "perceptual" intent to better match imagemagick.
                intent = Enums.Intent.Perceptual;
            }

            // Scaling calculations
            var thumbnailWidth  = width;
            var thumbnailHeight = height;

            if (width > 0 && height > 0) // Fixed width and height
            {
                var xFactor = (double)inputWidth / width;
                var yFactor = (double)pageHeight / height;

                if (xFactor > yFactor) // Or: if (xFactor < yFactor)
                {
                    thumbnailHeight = (int)Math.Round(pageHeight / xFactor);
                }
                else
                {
                    thumbnailWidth = (int)Math.Round(inputWidth / yFactor);
                }
            }
            else if (width > 0) // Fixed width
            {
                if (size == "force")
                {
                    thumbnailHeight = pageHeight;
                    height          = pageHeight;
                }
                else
                {
                    // Auto height
                    var yFactor = (double)inputWidth / width;
                    height = (int)Math.Round(pageHeight / yFactor);

                    // Height is missing, replace with a huuuge value to prevent
                    // reduction or enlargement in that axis
                    thumbnailHeight = VipsMaxCoord;
                }
            }
            else if (height > 0) // Fixed height
            {
                if (size == "force")
                {
                    thumbnailWidth = inputWidth;
                    width          = inputWidth;
                }
                else
                {
                    // Auto width
                    var xFactor = (double)pageHeight / height;
                    width = (int)Math.Round(inputWidth / xFactor);

                    // Width is missing, replace with a huuuge value to prevent
                    // reduction or enlargement in that axis
                    thumbnailWidth = VipsMaxCoord;
                }
            }
            else // Identity transform
            {
                thumbnailWidth = inputWidth;
                width          = inputWidth;

                thumbnailHeight = pageHeight;
                height          = pageHeight;
            }

            // Note: don't use "image.ThumbnailImage". Otherwise, none of the very fast
            // shrink-on-load tricks are possible. This can make thumbnailing of large
            // images extremely slow.
            image = Image.ThumbnailBuffer(buffer, thumbnailWidth, stringOptions, thumbnailHeight, size,
                                          false, importProfile: importProfile, exportProfile: exportProfile, intent: intent);

            image.WriteToFile("thumbnail.webp", new VOption
            {
                { "strip", true }
            });

            // Or:

            /*buffer = image.WriteToBuffer(".webp", new VOption
             * {
             *  {"strip", true}
             * });*/

            return("See thumbnail.webp");
        }
Ejemplo n.º 14
0
        public async Task <VOption <bool> > GetUsersLibrary(string spotifyId)
        {
            try {
                IdentityUserLogin <string> loginforuser = _context.UserLogins.Where(x => x.LoginProvider == "Spotify" && x.ProviderKey == spotifyId).FirstOrDefault();
                string aspnetuserId = loginforuser.UserId;
                if (loginforuser == null)
                {
                    //Critical error
                    logger.Error("User had no tokens at all.");
                    return(new VOption <bool>(ErrorCodes.NoUserByThatNameError, $"No user tokens found for user {aspnetuserId}"));
                }
                string spotifyid = loginforuser.ProviderKey;

                // Check if User is rate limited
                VOption <RateLimit> rlo = await GetRateLimits(spotifyid);

                if (!rlo.WasSuccess)
                {
                    return(new VOption <bool>(rlo.ErrorCode, rlo.ErrorMessage));
                }
                if (rlo.Value.RateLimitExpiresAt >= DateTimeOffset.Now)
                {
                    logger.Info($"user {aspnetuserId} is still rate limited");
                    return(new VOption <bool>(false)); // Rate limited condition
                }

                // Get Keys
                OAuthToken token = GetUserTokens(aspnetuserId, spotifyId);

                // Refresh if necessary + saves to db
                VOption <OAuthToken> refreshedO = await CheckRefesh(token);

                if (!refreshedO.WasSuccess)
                {
                    return(new VOption <bool>(refreshedO.ErrorCode, refreshedO.ErrorMessage));
                }

                // Get real offset
                int trueOffset = Math.Max(0, rlo.Value.LimitedAtOffset);

                // Get most recently added track
                VOption <DateTimeOffset> dtoo = await DatabaseService.GetMostRecentlyAddedAtForUser(spotifyid);

                if (!dtoo.WasSuccess)
                {
                    return(new VOption <bool>(dtoo.ErrorCode, dtoo.ErrorMessage));
                }

                bool cont = true;
                while (cont)
                {
                    // Fetch from Spotify
                    VOption <Paging <SavedTrack> > libraryo = await _GetUsersSpotifyLibrary(token, trueOffset);

                    if (!libraryo.WasSuccess)
                    {
                        logger.Error($"Failed to get spotify library tracks for user {aspnetuserId}");
                        RateLimit nrl = new RateLimit(spotifyid, rlo.Value.RateLimitExpiresAt, trueOffset);
                        // write offset to DB
                        VOption <bool> writeSuccess = await DatabaseService.InsertRateLimitForUser(aspnetuserId, spotifyId, nrl);

                        return(new VOption <bool>(libraryo.ErrorCode, libraryo.ErrorMessage));
                    }

                    Paging <SavedTrack>      pst        = libraryo.Value;
                    IList <VisifySavedTrack> fromPaging = _visifyTracksFromPaging(spotifyid, pst, dtoo.Value);
                    if (String.IsNullOrWhiteSpace(pst.Next) || !fromPaging.Any())
                    {
                        cont = false;
                    }
                    VOption <bool> insertSuccess = await DatabaseService.InsertVisifySavedTracks(aspnetuserId, spotifyId, fromPaging);

                    if (!insertSuccess.WasSuccess)
                    {
                        RateLimit      nrl          = new RateLimit(spotifyid, rlo.Value.RateLimitExpiresAt, trueOffset);
                        VOption <bool> writeSuccess = await DatabaseService.InsertRateLimitForUser(aspnetuserId, spotifyId, nrl);

                        return(new VOption <bool>(insertSuccess.ErrorCode, insertSuccess.ErrorMessage));
                    }

                    trueOffset += 50;
                }
                // Erase RateLimits
                await DatabaseService.ClearUserRateLimits(spotifyId);

                return(new VOption <bool>());
            }
            catch (Exception e) {
                logger.Error(e, "Failed to get users library");
                return(new VOption <bool>(ErrorCodes.MiscFailure, "Failed to get user library"));
            }
        }
Ejemplo n.º 15
0
        public static (Image Image, ImageType ImageType) OpenInput(InputDescriptor descriptor, string accessMethod)
        {
            descriptor = Guard.NotNull(descriptor, nameof(descriptor));

            Image     image;
            ImageType imageType;

            if (descriptor.Buffer != null)
            {
                if (descriptor.RawChannels > 0)
                {
                    // raw, uncompressed pixel data
                    image = Image.NewFromMemory(descriptor.Buffer, descriptor.RawWidth, descriptor.RawHeight, descriptor.RawChannels, Enums.BandFormat.Uchar);
                    image.Set("interpretation", descriptor.RawChannels < 3 ? Enums.Interpretation.Bw : Enums.Interpretation.Srgb);
                    imageType = ImageType.Raw;
                }
                else
                {
                    // Compressed data
                    imageType = ImageType.FromBuffer(descriptor.Buffer);
                    if (imageType == ImageType.Unknown)
                    {
                        throw new VipsException("Input buffer contains unsupported image format.");
                    }

                    try {
                        var option = new VOption {
                            { "access", accessMethod },
                            { "fail", descriptor.FailOnError }
                        };

                        if (imageType == ImageType.Svg || imageType == ImageType.Pdf)
                        {
                            option.Add("dpi", descriptor.Density);
                        }

                        if (imageType == ImageType.Magick)
                        {
                            option.Add("density", descriptor.Density);
                        }

                        if (imageType.SupportsPages)
                        {
                            option.Add("n", descriptor.Pages);
                            option.Add("page", descriptor.Page);
                        }

                        image = Image.NewFromBuffer(descriptor.Buffer, null, null, null, option);

                        if (imageType == ImageType.Svg || imageType == ImageType.Pdf || imageType == ImageType.Magick)
                        {
                            image.SetDensity(descriptor.Density);
                        }
                    }
                    catch (Exception ex) {
                        throw new VipsException($"Input buffer has corrupt header: {ex.Message}", ex);
                    }
                }
            }
            else
            {
                if (descriptor.CreateChannels > 0)
                {
                    // create new image
                    var background = new List <double> {
                        descriptor.CreateBackground[0],
                        descriptor.CreateBackground[1],
                        descriptor.CreateBackground[2]
                    };

                    if (descriptor.CreateChannels == 4)
                    {
                        background.Add(descriptor.CreateBackground[3]);
                    }

                    image = Image.NewFromArray(background.ToArray());                     // TODO: suspect
                    image.Set("interpretation", Enums.Interpretation.Srgb);
                    imageType = ImageType.Raw;
                }
                else
                {
                    imageType = ImageType.FromFile(descriptor.File !);

                    if (imageType == ImageType.Missing)
                    {
                        throw new VipsException($"Input file {descriptor.File} is missing.");
                    }

                    if (imageType == ImageType.Unknown)
                    {
                        throw new VipsException("Input file contains unsupported image format.");
                    }

                    try {
                        var option = new VOption {
                            { "access", accessMethod },
                            { "fail", descriptor.FailOnError }
                        };

                        if (imageType == ImageType.Svg || imageType == ImageType.Pdf)
                        {
                            option.Add("dpi", descriptor.Density);
                        }

                        if (imageType == ImageType.Magick)
                        {
                            option.Add("density", descriptor.Density);
                        }

                        if (imageType.SupportsPages)
                        {
                            option.Add("n", descriptor.Pages);
                            option.Add("page", descriptor.Page);
                        }

                        image = Image.NewFromFile(descriptor.File, null, null, null, option);
                        if (imageType == ImageType.Svg || imageType == ImageType.Pdf || imageType == ImageType.Magick)
                        {
                            image.SetDensity(descriptor.Density);
                        }
                    }
                    catch (Exception ex) {
                        throw new VipsException($"Input file has corrupt header: {ex.Message}", ex);
                    }
                }
            }

            return(image, imageType);
        }
Ejemplo n.º 16
0
        public async Task <IActionResult> GetArtistCollabLinks()
        {
            VisifyUser user = await _userManager.GetUserAsync(User);

            if (user == null)
            {
                logger.LogInformation($"Tried to get Artist Collab Link graph data for user {_userManager.GetUserId(User)}, but user did not exist");
                return(NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."));
            }

            // Get the tracks and artists
            VOption <IList <VisifySavedTrack> > stracks = await DatabaseService.GetUsersSavedTracks(user.Id, limit : 10_000);

            if (!stracks.WasSuccess)
            {
                logger.LogError($"Failed to retreive saved tracks for user {_userManager.GetUserId(User)}", stracks.ErrorMessage);
                return(Json("FAILED"));
            }

            // create a proper d3 structure
            Dictionary <string, FDGNode> nodes = new Dictionary <string, FDGNode>();
            Dictionary <string, FDGEdge> edges = new Dictionary <string, FDGEdge>();

            foreach (VisifySavedTrack vst in stracks.Value)
            {
                string         tid   = $"tid_{vst.VisifyTrack.SpotifyId}";
                string[]       idarr = new string[2];
                Stack <string> artistsOnThisTrack = new Stack <string>(vst.VisifyTrack.Artists.Count);
                for (int i = 0; i < vst.VisifyTrack.Artists.Count; i++)
                {
                    VisifyArtist va = vst.VisifyTrack.Artists[i];
                    if (!nodes.ContainsKey(va.SpotifyId))
                    {
                        nodes.Add(va.SpotifyId, new FDGNode(va.SpotifyId, 0, va.ArtistName, 1, 1000));
                    }
                    artistsOnThisTrack.Push(va.SpotifyId);
                }

                while (artistsOnThisTrack.Count > 0)
                {
                    string a = artistsOnThisTrack.Pop();
                    foreach (string othera in artistsOnThisTrack)
                    {
                        idarr[0] = a;
                        idarr[1] = othera;
                        Array.Sort(idarr);
                        string lid0 = String.Join("", idarr);
                        Dictionary <string, string> tn = new Dictionary <string, string>()
                        {
                            { "name", vst.VisifyTrack.TrackName }
                        };
                        if (edges.ContainsKey(lid0))
                        {
                            edges[lid0].strength += 0.1;
                            edges[lid0].tracks.Add(tn);
                        }
                        else
                        {
                            FDGEdge n = new FDGEdge(othera, a, 0.1);
                            n.tracks.Add(tn);
                            edges.Add(lid0, n);
                        }
                    }
                }
            }

            Dictionary <string, object> keys = new Dictionary <string, object>()
            {
                { "nodes", nodes.Values },
                { "links", edges.Values }
            };

            return(Json(keys));
        }