Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Configured Youtube Boombox v0.3.1
BepInEx/plugins/com.github.lordfirespeed.configured_youtube_boombox.dll
Decompiled 2 years agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ConfiguredYoutubeBoombox.util; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using YoutubeDLSharp; using YoutubeDLSharp.Metadata; using YoutubeDLSharp.Options; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("")] [assembly: AssemblyCompany("com.github.lordfirespeed.configured_youtube_boombox")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("0.3.1.0")] [assembly: AssemblyInformationalVersion("0.3.1+94197c943dd1e332ed0b0b6d30ba7a9aea585a9e")] [assembly: AssemblyProduct("Configured Youtube Boombox")] [assembly: AssemblyTitle("com.github.lordfirespeed.configured_youtube_boombox")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ConfiguredYoutubeBoombox { public class ConfiguredTrackListFile { [JsonProperty("tracks")] public ConfiguredTrack[]? Tracks { get; set; } } public class ConfiguredTrack { [JsonProperty("youtubeVideoId")] public string? VideoId { get; set; } [JsonProperty("trackName")] public string? TrackName { get; set; } [JsonProperty("startTimestamp")] public string? StartTimestamp { get; set; } [JsonProperty("endTimestamp")] public string? EndTimestamp { get; set; } [JsonProperty("volumeScalar")] public float? VolumeScalar { get; set; } } public class VideoTooLongException : Exception { public VideoTooLongException() { } public VideoTooLongException(string message) : base(message) { } public VideoTooLongException(string message, Exception inner) : base(message, inner) { } } public class VideoTooLongExceptiona : Exception { public VideoTooLongExceptiona() { } public VideoTooLongExceptiona(string message) : base(message) { } public VideoTooLongExceptiona(string message, Exception inner) : base(message, inner) { } } public class YoutubeDLProcessFailed : Exception { public YoutubeDLProcessFailed() { } public YoutubeDLProcessFailed(string message) : base(message) { } public YoutubeDLProcessFailed(string message, Exception inner) : base(message, inner) { } } [BepInPlugin("com.github.lordfirespeed.configured_youtube_boombox", "Configured Youtube Boombox", "0.3.1")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource? Logger; internal static string? PluginDataPath; internal static string? DownloadsPath; internal static ConfigEntry<float> MaxSongDuration { get; private set; } = null; internal static YoutubeDL YoutubeDL { get; } = new YoutubeDL((byte)4); internal static JsonSerializer TrackListSerializer { get; } = new JsonSerializer(); public Plugin() { Logger = ((BaseUnityPlugin)this).Logger; } private async void Awake() { MaxSongDuration = ((BaseUnityPlugin)this).Config.Bind<float>(new ConfigDefinition("General", "Max Song Duration"), 600f, new ConfigDescription("Maximum song duration in seconds. Any video longer than this will not be downloaded.", (AcceptableValueBase)null, Array.Empty<object>())); await InitializeToolsAndDirectories(); await DownloadConfiguredTracks(); } private async Task InitializeToolsAndDirectories() { PluginDataPath = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "configured-youtube-boombox-data"); DownloadsPath = Path.Combine(Paths.BepInExRootPath, "Custom Songs", "Boombox Music"); if (!Directory.Exists(PluginDataPath)) { Directory.CreateDirectory(PluginDataPath); } if (!Directory.Exists(DownloadsPath)) { Directory.CreateDirectory(DownloadsPath); } Func<Task> ensureYtDlp = async delegate { if (!Directory.GetFiles(PluginDataPath).Any((string file) => file.Contains("yt-dl"))) { await Utils.DownloadYtDlp(PluginDataPath); } }; Func<Task> ensureFfMpeg = async delegate { if (!Directory.GetFiles(PluginDataPath).Any((string file) => file.Contains("ffmpeg"))) { await Utils.DownloadFFmpeg(PluginDataPath); } }; await Task.WhenAll(ensureYtDlp(), ensureFfMpeg()); YoutubeDL.YoutubeDLPath = Directory.GetFiles(PluginDataPath).First((string file) => file.Contains("yt-dl")); YoutubeDL.FFmpegPath = Directory.GetFiles(PluginDataPath).First((string file) => file.Contains("ffmpeg")); YoutubeDL.OutputFolder = DownloadsPath; YoutubeDL.OutputFileTemplate = "%(id)s.%(ext)s"; } private IEnumerable<string> DiscoverConfiguredTrackListFiles() { return (from pluginDirectory in Directory.GetDirectories(Paths.PluginPath) select Path.Join((ReadOnlySpan<char>)pluginDirectory, (ReadOnlySpan<char>)"configured-youtube-boombox-tracks.json")).Where(File.Exists); } private ConfiguredTrack[] DeserializeConfiguredTrackListFile(string trackListFilePath) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown using StreamReader streamReader = new StreamReader(trackListFilePath); JsonTextReader val = new JsonTextReader((TextReader)streamReader); try { ConfiguredTrackListFile configuredTrackListFile = TrackListSerializer.Deserialize<ConfiguredTrackListFile>((JsonReader)(object)val); if (configuredTrackListFile?.Tracks != null) { return configuredTrackListFile.Tracks; } ManualLogSource? logger = Logger; if (logger != null) { logger.LogWarning((object)("Failed to deserialize any tracks from " + trackListFilePath + ".")); } return Array.Empty<ConfiguredTrack>(); } finally { ((IDisposable)val)?.Dispose(); } } private IEnumerable<ConfiguredTrack> DiscoverConfiguredTracks() { return DiscoverConfiguredTrackListFiles().SelectMany(DeserializeConfiguredTrackListFile); } private async Task DownloadConfiguredTracks() { await Task.WhenAll(DiscoverConfiguredTracks().Select(TrackDownloader.DownloadTrack)); } } public class TrackDownloader { protected static async Task<float> FetchSongDuration(string id) { ManualLogSource? logger = Plugin.Logger; if (logger != null) { logger.LogDebug((object)"Fetching video metadata."); } RunResult<VideoData> videoDataResult = await Plugin.YoutubeDL.RunVideoDataFetch(id, default(CancellationToken), true, false, (OptionSet)null); int num; if (videoDataResult != null && videoDataResult.Success) { VideoData data = videoDataResult.Data; if (data != null) { num = (data.Duration.HasValue ? 1 : 0); goto IL_00ed; } } num = 0; goto IL_00ed; IL_00ed: if (num != 0) { return videoDataResult.Data.Duration.Value; } throw new Exception("Failed to fetch video duration."); } protected static TimeSpan ParseTimestamp(string timestamp) { if (string.IsNullOrWhiteSpace(timestamp)) { throw new ArgumentException("Timestamp cannot be null or whitespace"); } if (float.TryParse(timestamp, out var result)) { return TimeSpan.FromSeconds(result); } return TimeSpan.Parse(timestamp, new CultureInfo("en-us")); } protected static async Task<float> GetEffectiveDuration(ConfiguredTrack track) { if (track.VideoId == null) { throw new NullReferenceException("Track VideoId must not be null."); } float duration = await InfoCache.DurationCache.ComputeIfAbsentAsync(track.VideoId, FetchSongDuration); float effectiveDuration = duration; if (track.StartTimestamp != null) { float startTime = (float)ParseTimestamp(track.StartTimestamp).TotalSeconds; if (duration <= startTime) { return 0f; } effectiveDuration -= startTime; } if (track.EndTimestamp != null) { float endTime = (float)ParseTimestamp(track.EndTimestamp).TotalSeconds; if (duration <= endTime) { return effectiveDuration; } effectiveDuration -= duration - endTime; } return effectiveDuration; } public static async Task DownloadTrack(ConfiguredTrack track) { if (track.VideoId == null) { throw new NullReferenceException("Track VideoId must not be null."); } if (track.TrackName == null) { throw new NullReferenceException("Track TrackName must not be null."); } ManualLogSource? logger = Plugin.Logger; if (logger != null) { logger.LogDebug((object)("Downloading '" + track.TrackName + "' (" + track.VideoId + ")")); } string newPath = Path.Combine(Plugin.DownloadsPath, "cytbb." + track.TrackName + "-" + track.VideoId + ".mp3"); if (File.Exists(newPath)) { ManualLogSource? logger2 = Plugin.Logger; if (logger2 != null) { logger2.LogDebug((object)"Track already downloaded, nothing to do."); } return; } if (await GetEffectiveDuration(track) > Plugin.MaxSongDuration.Value) { throw new VideoTooLongException("Track too long, skipping."); } string?[] downloaderArgs = new string[4] { "ffmpeg:-nostats", "ffmpeg:-loglevel 0", (track.StartTimestamp != null) ? ("ffmpeg:-ss " + track.StartTimestamp) : null, (track.EndTimestamp != null) ? ("ffmpeg:-to " + track.EndTimestamp) : null }; string?[] extractAudioFilterArgs = new string[1] { track.VolumeScalar.HasValue ? $"volume={track.VolumeScalar}" : null }; extractAudioFilterArgs = extractAudioFilterArgs.Where((string x) => x != null).Cast<string>().ToArray(); string?[] postProcessorArgs = new string[1] { (extractAudioFilterArgs.Length != 0) ? ("ExtractAudio:-filter:a " + string.Join(":", extractAudioFilterArgs)) : null }; ManualLogSource? logger3 = Plugin.Logger; if (logger3 != null) { logger3.LogDebug((object)("Starting download (" + track.TrackName + ").")); } YoutubeDL youtubeDL = Plugin.YoutubeDL; string? videoId = track.VideoId; OptionSet val = new OptionSet(); val.Downloader = MultiValue<string>.op_Implicit(new string[1] { "ffmpeg" }); val.DownloaderArgs = MultiValue<string>.op_Implicit(downloaderArgs.Where((string x) => x != null).Cast<string>().ToArray()); val.PostprocessorArgs = MultiValue<string>.op_Implicit(postProcessorArgs.Where((string x) => x != null).Cast<string>().ToArray()); OptionSet val2 = val; RunResult<string> res = await youtubeDL.RunAudioDownload(videoId, (AudioConversionFormat)3, default(CancellationToken), (IProgress<DownloadProgress>)null, (IProgress<string>)null, val2); ManualLogSource? logger4 = Plugin.Logger; if (logger4 != null) { logger4.LogDebug((object)("Download complete (" + track.TrackName + ").")); } if (!res.Success) { throw new YoutubeDLProcessFailed("Failed to download '" + track.TrackName + "' (" + track.VideoId + ")."); } File.Move(res.Data, newPath); ManualLogSource? logger5 = Plugin.Logger; if (logger5 != null) { logger5.LogDebug((object)("'" + track.TrackName + "' (" + track.VideoId + ") downloaded successfully.")); } } } public static class InfoCache { public static readonly Dictionary<string, float> DurationCache = new Dictionary<string, float>(); } public static class MyPluginInfo { public const string PLUGIN_GUID = "com.github.lordfirespeed.configured_youtube_boombox"; public const string PLUGIN_NAME = "Configured Youtube Boombox"; public const string PLUGIN_VERSION = "0.3.1"; } } namespace ConfiguredYoutubeBoombox.util { public static class DictionaryComputeIfAbsent { public delegate TValue ValueProvider<TKey, TValue>(TKey key); public delegate Task<TValue> ValueProviderAsync<TKey, TValue>(TKey key); public static TValue ComputeIfAbsent<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, ValueProvider<TKey, TValue> provider) { if (dictionary.TryGetValue(key, out TValue value)) { return value; } return dictionary[key] = provider(key); } public static async Task<TValue> ComputeIfAbsentAsync<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, ValueProviderAsync<TKey, TValue> provider) { if (dictionary.TryGetValue(key, out TValue value)) { return value; } return dictionary[key] = await provider(key); } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }