private static string FormatFromGd3(string pattern, Gd3Tag gd3) { return(pattern .Replace("[title]", gd3.Title.English) .Replace("[system]", gd3.System.English) .Replace("[game]", gd3.Game.English) .Replace("[composer]", gd3.Composer.English)); }
private static void Render(Settings settings, AudioLoader loader) { if (settings.OutputFile != null) { // Emit normalized data to a WAV file for later mixing if (settings.MasterAudioFile == null && !settings.NoMasterMix) { settings.MasterAudioFile = settings.OutputFile + ".wav"; loader.MixToFile(settings.MasterAudioFile, !settings.NoMasterMixReplayGain); } } Console.WriteLine("Generating background image..."); var backgroundImage = new BackgroundRenderer(settings.Width, settings.Height, ParseColor(settings.BackgroundColor)); if (settings.BackgroundImageFile != null) { using (var bm = Image.FromFile(settings.BackgroundImageFile)) { backgroundImage.Add(new ImageInfo(bm, ContentAlignment.MiddleCenter, true, DockStyle.None, 0.5f)); } } if (settings.LogoImageFile != null) { using (var bm = Image.FromFile(settings.LogoImageFile)) { backgroundImage.Add(new ImageInfo(bm, ContentAlignment.BottomRight, false, DockStyle.None, 1)); } } if (settings.VgmFile != null) { var gd3 = Gd3Tag.LoadFromVgm(settings.VgmFile); var gd3Text = gd3.ToString(); if (gd3Text.Length > 0) { backgroundImage.Add(new TextInfo(gd3Text, settings.Gd3Font, settings.Gd3FontSize, ContentAlignment.BottomLeft, FontStyle.Regular, DockStyle.Bottom, ParseColor(settings.Gd3FontColor))); } } var renderer = new WaveformRenderer { BackgroundImage = backgroundImage.Image, Columns = settings.Columns, FramesPerSecond = settings.FramesPerSecond, Width = settings.Width, Height = settings.Height, SamplingRate = loader.SampleRate, RenderedLineWidthInSamples = settings.ViewWidthMs * loader.SampleRate / 1000, RenderingBounds = backgroundImage.WaveArea }; if (settings.GridLineWidth > 0) { renderer.Grid = new WaveformRenderer.GridConfig { Color = ParseColor(settings.GridColor), Width = settings.GridLineWidth, DrawBorder = settings.GridBorder }; } if (settings.ZeroLineWidth > 0) { renderer.ZeroLine = new WaveformRenderer.ZeroLineConfig { Color = ParseColor(settings.ZeroLineColor), Width = settings.ZeroLineWidth }; } // Add the data to the renderer foreach (var channel in loader.Data) { renderer.AddChannel(new Channel( channel.Samples, ParseColor(settings.LineColor), settings.LineWidth, GuessChannelName(channel.Filename), CreateTriggerAlgorithm(settings.TriggerAlgorithm), settings.TriggerLookahead)); } if (settings.ChannelLabelsFont != null) { renderer.ChannelLabels = new WaveformRenderer.LabelConfig { Color = ParseColor(settings.ChannelLabelsColor), FontName = settings.ChannelLabelsFont, Size = settings.ChannelLabelsSize }; } var outputs = new List <IGraphicsOutput>(); if (settings.FfMpegPath != null) { Console.WriteLine("Adding FFMPEG renderer..."); outputs.Add(new FfmpegOutput(settings.FfMpegPath, settings.OutputFile, settings.Width, settings.Height, settings.FramesPerSecond, settings.FfMpegExtraOptions, settings.MasterAudioFile)); } if (settings.PreviewFrameskip > 0) { Console.WriteLine("Adding preview renderer..."); outputs.Add(new PreviewOutput(settings.PreviewFrameskip)); } try { Console.WriteLine("Rendering..."); var sw = Stopwatch.StartNew(); renderer.Render(outputs); sw.Stop(); int numFrames = (int)(loader.Length.TotalSeconds * settings.FramesPerSecond); Console.WriteLine($"Rendering complete in {sw.Elapsed:g}, average {numFrames / sw.Elapsed.TotalSeconds:N} fps"); } catch (Exception ex) { // Should mean it was cancelled Console.WriteLine($"Rendering cancelled: {ex.Message}"); } finally { foreach (var graphicsOutput in outputs) { graphicsOutput.Dispose(); } } }
private static async Task <string> UploadToYouTube(Settings settings) { ClientSecrets secrets; if (settings.YouTubeUploadClientSecret != null) { using (var stream = new FileStream(settings.YouTubeUploadClientSecret, FileMode.Open, FileAccess.Read)) { secrets = GoogleClientSecrets.Load(stream).Secrets; } } else { // We use our embedded client secret using (var stream = Properties.Resources.ResourceManager.GetStream(nameof(Properties.Resources.ClientSecret))) { secrets = GoogleClientSecrets.Load(stream).Secrets; } } var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync( secrets, // This OAuth 2.0 access scope allows an application to upload files to the // authenticated user's YouTube channel, but doesn't allow other types of access. new[] { YouTubeService.Scope.YoutubeUpload, YouTubeService.Scope.YoutubeForceSsl }, "SidWizPlus", CancellationToken.None ); var youtubeService = new YouTubeService(new BaseClientService.Initializer { HttpClientInitializer = credential, ApplicationName = Assembly.GetExecutingAssembly().GetName().Name, GZipEnabled = true }); var video = new Video { Snippet = new VideoSnippet { Title = settings.YouTubeTitle, CategoryId = "10" // Music }, Status = new VideoStatus { PrivacyStatus = "public" } // or "private" or "public" }; var tags = new List <string>(); if (settings.YouTubeTags != null) { tags.AddRange(settings.YouTubeTags.Split(',')); } Gd3Tag gd3 = null; if (settings.VgmFile != null) { gd3 = Gd3Tag.LoadFromVgm(settings.VgmFile); } if (gd3 != null) { video.Snippet.Description = $"Oscilloscope View of {gd3.Title}"; if (gd3.Game.English.Length > 0) { video.Snippet.Description += $" from the game {gd3.Game.English}"; } if (gd3.System.English.Length > 0) { video.Snippet.Description += $" for the {gd3.System.English}"; } if (gd3.Composer.English.Length > 0) { video.Snippet.Description += $", composed by {gd3.Composer}"; } video.Snippet.Description += "."; if (gd3.Ripper.Length > 0) { video.Snippet.Description += $"\nRipped by {gd3.Ripper}"; } if (gd3.Notes.Length > 0) { video.Snippet.Description += "\n\nNotes:\n" + gd3.Notes; } } if (settings.YouTubeDescriptionsExtra != null) { video.Snippet.Description += "\n" + settings.YouTubeDescriptionsExtra; } video.Snippet.Description += "\n\nVideo created using SidWizPlus - https://github.com/maxim-zhao/SidWizPlus"; if (settings.YouTubeTagsFromGd3 && gd3 != null) { tags.Add(gd3.Game.English); tags.Add(gd3.System.English); tags.AddRange(gd3.Composer.English.Split(';')); } video.Snippet.Tags = tags.Where(t => !string.IsNullOrEmpty(t)).Select(t => t.Trim()).ToList(); if (settings.YouTubeCategory != null) { var request = youtubeService.VideoCategories.List("snippet"); request.RegionCode = "US"; var response = request.Execute(); video.Snippet.CategoryId = response.Items .Where(c => c.Snippet.Title.ToLowerInvariant().Contains(settings.YouTubeCategory.ToLowerInvariant())) .Select(c => c.Id) .FirstOrDefault(); if (video.Snippet.CategoryId == null) { Console.Error.WriteLine($"Warning: couldn't find category matching \"{settings.YouTubeCategory}\", defaulting to \"Music\""); } } if (gd3 != null) { video.Snippet.Title = FormatFromGd3(video.Snippet.Title, gd3); } if (video.Snippet.Title.Length > 100) { video.Snippet.Title = video.Snippet.Title.Substring(0, 97) + "..."; } // We now escape some strings as the API doesn't do it internally... video.Snippet.Title = RemoveAngledBrackets(video.Snippet.Title); video.Snippet.Description = RemoveAngledBrackets(video.Snippet.Description); video.Snippet.Tags = video.Snippet.Tags.Select(RemoveAngledBrackets).ToList(); using (var fileStream = new FileStream(settings.OutputFile, FileMode.Open)) { var videosInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", fileStream, "video/*"); videosInsertRequest.ChunkSize = ResumableUpload.MinimumChunkSize; long totalSize = fileStream.Length; bool shouldRetry = true; var sw = Stopwatch.StartNew(); videosInsertRequest.ProgressChanged += progress => { switch (progress.Status) { case UploadStatus.Uploading: { var elapsedSeconds = sw.Elapsed.TotalSeconds; var fractionComplete = (double)progress.BytesSent / totalSize; var eta = TimeSpan.FromSeconds(elapsedSeconds / fractionComplete - elapsedSeconds); var sent = (double)progress.BytesSent / 1024 / 1024; var kbPerSecond = progress.BytesSent / sw.Elapsed.TotalSeconds / 1024; Console.Write($"\r{sent:f}MB sent ({fractionComplete:P}, average {kbPerSecond:f}KB/s, ETA {eta:g})"); break; } case UploadStatus.Failed: Console.Error.WriteLine($"Upload failed: {progress.Exception}"); // Google API says we can retry if we get a non-API error, or one of these four 5xx error codes shouldRetry = !(progress.Exception is GoogleApiException errorCode) || new[] { HttpStatusCode.InternalServerError, HttpStatusCode.BadGateway, HttpStatusCode.ServiceUnavailable, HttpStatusCode.GatewayTimeout }.Contains(errorCode.HttpStatusCode); if (shouldRetry) { Console.WriteLine("Retrying..."); } break; case UploadStatus.Completed: Console.WriteLine($"Progress: {progress.Status}"); shouldRetry = false; break; default: Console.WriteLine($"Progress: {progress.Status}"); break; } }; videosInsertRequest.ResponseReceived += video1 => { video.Id = video1.Id; Console.WriteLine($"\nUpload completed: video ID is {video1.Id}"); }; try { videosInsertRequest.Upload(); } catch (Exception ex) { Console.Error.WriteLine($"Upload failed: {ex}"); } while (shouldRetry) { try { videosInsertRequest.Resume(); } catch (Exception ex) { Console.Error.WriteLine($"Upload failed: {ex}"); } } } if (settings.YouTubePlaylist != null && !string.IsNullOrEmpty(video.Id)) { if (gd3 != null) { settings.YouTubePlaylist = RemoveAngledBrackets(FormatFromGd3(settings.YouTubePlaylist, gd3)); } // We need to decide if it's an existing playlist // We iterate over all channels... var playlistsRequest = youtubeService.Playlists.List("snippet"); playlistsRequest.Mine = true; var playlistsResponse = playlistsRequest.Execute(); var playlist = playlistsResponse.Items.FirstOrDefault(p => p.Snippet.Title == settings.YouTubePlaylist); if (playlist == null) { // Create it playlist = new Playlist { Snippet = new PlaylistSnippet { Title = settings.YouTubePlaylist }, Status = new PlaylistStatus { PrivacyStatus = "public" } }; if (settings.YouTubePlaylistDescriptionFile != null) { playlist.Snippet.Description = RemoveAngledBrackets(File.ReadAllText(settings.YouTubePlaylistDescriptionFile)); } if (settings.YouTubeDescriptionsExtra != null) { playlist.Snippet.Description += "\n\n" + settings.YouTubeDescriptionsExtra; } playlist = youtubeService.Playlists.Insert(playlist, "snippet, status").Execute(); Console.WriteLine($"Created playlist \"{settings.YouTubePlaylist}\" with ID {playlist.Id}"); } // Add to it var newPlaylistItem = new PlaylistItem { Snippet = new PlaylistItemSnippet { PlaylistId = playlist.Id, ResourceId = new ResourceId { Kind = "youtube#video", VideoId = video.Id } } }; newPlaylistItem = await youtubeService.PlaylistItems.Insert(newPlaylistItem, "snippet").ExecuteAsync(); Console.WriteLine($"Added video {video.Id} ({video.Snippet.Title}) to playlist {playlist.Id} ({playlist.Snippet.Title}) as item {newPlaylistItem.Id}"); } return(video.Id); }
private static void Render(Settings settings, IReadOnlyCollection <Channel> channels) { Console.WriteLine("Generating background image..."); var backgroundImage = new BackgroundRenderer(settings.Width, settings.Height, ParseColor(settings.BackgroundColor)); if (settings.BackgroundImageFile != null) { using (var bm = Image.FromFile(settings.BackgroundImageFile)) { backgroundImage.Add(new ImageInfo(bm, ContentAlignment.MiddleCenter, true, DockStyle.None, 0.5f)); } } if (!string.IsNullOrEmpty(settings.LogoImageFile)) { using (var bm = Image.FromFile(settings.LogoImageFile)) { backgroundImage.Add(new ImageInfo(bm, ContentAlignment.BottomRight, false, DockStyle.None, 1)); } } if (settings.VgmFile != null) { var gd3 = Gd3Tag.LoadFromVgm(settings.VgmFile); var gd3Text = gd3.ToString(); if (gd3Text.Length > 0) { backgroundImage.Add(new TextInfo(gd3Text, settings.Gd3Font, settings.Gd3FontSize, ContentAlignment.BottomLeft, FontStyle.Regular, DockStyle.Bottom, ParseColor(settings.Gd3FontColor))); } } if (settings.MaximumAspectRatio > 0.0) { Console.WriteLine($"Determining column count for maximum aspect ratio {settings.MaximumAspectRatio}:"); for (var columns = 1; columns < 100; ++columns) { var width = backgroundImage.WaveArea.Width / columns; var rows = channels.Count / columns + (channels.Count % columns == 0 ? 0 : 1); var height = backgroundImage.WaveArea.Height / rows; var ratio = (double)width / height; Console.WriteLine($"- {columns} columns => {width} x {height} pixels => ratio {ratio}"); if (ratio < settings.MaximumAspectRatio) { settings.Columns = columns; break; } } } var renderer = new WaveformRenderer { BackgroundImage = backgroundImage.Image, Columns = settings.Columns, FramesPerSecond = settings.FramesPerSecond, Width = settings.Width, Height = settings.Height, SamplingRate = channels.First().SampleRate, RenderingBounds = backgroundImage.WaveArea }; if (settings.GridLineWidth > 0) { foreach (var channel in channels) { channel.BorderColor = ParseColor(settings.GridColor); channel.BorderWidth = settings.GridLineWidth; channel.BorderEdges = settings.GridBorder; } } // Add the data to the renderer foreach (var channel in channels) { renderer.AddChannel(channel); } var outputs = new List <IGraphicsOutput>(); if (settings.FfMpegPath != null) { Console.WriteLine("Adding FFMPEG renderer..."); outputs.Add(new FfmpegOutput(settings.FfMpegPath, settings.OutputFile, settings.Width, settings.Height, settings.FramesPerSecond, settings.FfMpegExtraOptions, settings.MasterAudioFile)); } if (settings.PreviewFrameskip > 0) { Console.WriteLine("Adding preview renderer..."); outputs.Add(new PreviewOutput(settings.PreviewFrameskip, true)); } try { Console.WriteLine("Rendering..."); var sw = Stopwatch.StartNew(); renderer.Render(outputs); sw.Stop(); int numFrames = (int)(channels.Max(x => x.Length).TotalSeconds *settings.FramesPerSecond); Console.WriteLine($"Rendering complete in {sw.Elapsed:g}, average {numFrames / sw.Elapsed.TotalSeconds:N} fps"); } catch (Exception ex) { // Should mean it was cancelled Console.WriteLine($"Rendering cancelled: {ex.Message}"); } finally { foreach (var graphicsOutput in outputs) { graphicsOutput.Dispose(); } } }