Decompiled source of BingBongBoomboxPlus v2.0.1
BingBongBoomBox.dll
Decompiled 3 days agousing System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Threading.Tasks; using System.Web; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; using UnityEngine.Networking; using YoutubeDLSharp; using Zorro.Core; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("BingBongBoomBox")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+3a4bb035269eb489b8be6a93caad20fd1910a332")] [assembly: AssemblyProduct("BingBong BoomBox")] [assembly: AssemblyTitle("BingBongBoomBox")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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] [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 BepInEx { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class BepInAutoPluginAttribute : Attribute { public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace BepInEx.Preloader.Core.Patching { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class PatcherAutoPluginAttribute : Attribute { public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace BingBongBoomBox { [BepInPlugin("xyz.restorerootfs.bingbongboomboxplus", "BingBongBoomBox", "1.2.0")] public class BingBongPlayerPlugin : BaseUnityPlugin { private enum SongLoadingState { Loaded, Loading, Error } [Serializable] private class SongMetadata { public string? title; } [Serializable] private class SongRequest { public string url = ""; public string hash = ""; public string requestedBy = ""; public int requestedByActor = -1; public string title = ""; } [Serializable] private class SongQueueWrapper { public List<SongRequest> items = new List<SongRequest>(); } [Serializable] private class HostConfigPayload { public bool publicEnqueue; public bool globalAudio; public bool bypassEnv; public bool allowDuplicates; public bool autoAdvance; public float maxHearingDistance; public int maxQueueSize; public int perUserQueueLimit; public string v; } [HarmonyPatch(typeof(Item))] private static class ItemPatches { private static bool IsBingBong(Item it) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) if ((it.itemTags & 0x10) == 0) { ItemUIData uIData = it.UIData; if (uIData == null || !(uIData.itemName?.IndexOf("BingBong", StringComparison.OrdinalIgnoreCase) >= 0)) { string name = ((Object)it).name; if (name == null) { return false; } return name.IndexOf("BingBong", StringComparison.OrdinalIgnoreCase) >= 0; } } return true; } [HarmonyPostfix] [HarmonyPatch("Awake")] private static void Awake_Post(Item __instance) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Invalid comparison between Unknown and I4 if (!IsBingBong(__instance)) { return; } bingBong = ((Component)__instance).transform; Transform transform = ((Component)__instance).transform; bingBongMouth = null; Animator[] componentsInChildren = ((Component)transform).GetComponentsInChildren<Animator>(true); foreach (Animator val in componentsInChildren) { try { AnimatorControllerParameter[] parameters = val.parameters; AnimatorControllerParameter val2 = null; AnimatorControllerParameter[] array = parameters; foreach (AnimatorControllerParameter val3 in array) { if ((int)val3.type == 1 && val3.name.IndexOf("mouth", StringComparison.OrdinalIgnoreCase) >= 0) { val2 = val3; break; } } if (val2 != null) { mouthOpenParam = val2.name; mouthOpenHash = val2.nameHash; bingBongMouth = val; break; } } catch { } } } [HarmonyPrefix] [HarmonyPatch("OnDestroy")] private static void OnDestroy_Pre(Item __instance) { if ((Object)(object)bingBong == (Object)(object)((Component)__instance).transform) { mouthOpenParam = null; mouthOpenHash = 0; bingBongMouth = null; bingBong = null; } } } private static ManualLogSource? logger; private static AudioSource? audioSource; private static PhotonView? photonView; private string? ytDlpPath; private string? ffmpegPath; private string? denoPath; private string urlInput = ""; private Coroutine? currentPlaybackCoroutine; private Coroutine? debouncePlayCoroutine; private Coroutine? downloadCoroutine; private const ushort BingBongItemID = 13; private static string? mouthOpenParam; private static int mouthOpenHash = 0; public static Transform? bingBong = null; public static Animator? bingBongMouth = null; private ConfigEntry<float>? volumeSetting; private ConfigEntry<int>? maxAudioSizeMbCfg; private ConfigEntry<int>? maxTempFilesCfg; private ConfigEntry<int>? maxQueueSizeCfg; private ConfigEntry<int>? perUserQueueLimitCfg; private ConfigEntry<bool>? allowDuplicatesCfg; private ConfigEntry<bool>? autoAdvanceCfg; private ConfigEntry<int>? titleProbeTimeoutSecCfg; private ConfigEntry<int>? playlistProbeTimeoutSecCfg; private ConfigEntry<bool>? globalAudioCfg; private ConfigEntry<bool>? bypassEnvCfg; private ConfigEntry<bool>? publicEnqueueCfg; private ConfigEntry<float>? maxHearingDistanceCfg; private ConfigEntry<float>? uiScaleCfg; private ConfigEntry<bool>? showAdvancedCfg; private bool hostPublicEnqueue; private bool hostGlobalAudio; private bool hostBypassEnv; private bool hostAllowDuplicates; private bool hostAutoAdvance = true; private float hostMaxHearingDistance = 35f; private int hostMaxQueueSize = 20; private int hostPerUserQueueLimit = 5; private float syncInterval = 5f; private float lastSyncTime; private float lastConfigBroadcast = -999f; private bool sentInitialConfig; public float vocalLow = 300f; public float vocalHigh = 3400f; public int sampleSize = 256; public float currentVolume = 0.45f; public FFTWindow fftWindow = (FFTWindow)5; private bool lastConnectedState; private float[]? spectrumData; private string? tempAudioDir; private string currentSongTitle = ""; private string currentSongHash = ""; private string lastUsedPlayer = ""; private string userId = Guid.NewGuid().ToString(); private bool manualStop; private SongLoadingState songLoadingState; public static readonly string[] LoadingProgress = new string[4] { "Loading.", "Loading..", "Loading...", "Loading" }; private bool hostHasMod = PhotonNetwork.IsMasterClient; private readonly List<SongRequest> queue = new List<SongRequest>(); private Vector2 queueScroll = Vector2.zero; private Vector2 panelScroll = Vector2.zero; private readonly HashSet<string> titleFetchInProgress = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private float spectrumTimer; private float spectrumInterval = 0.012f; private GUIStyle? boxStyle; private GUIStyle? labelStyle; private GUIStyle? buttonStyle; private GUIStyle? sliderStyle; private GUIStyle? sliderThumbStyle; private GUIStyle? headerStyle; private GUIStyle? smallLabelStyle; private GUIStyle? toggleStyle; private GUIStyle? foldoutStyle; private Texture2D? boxBgTex; private Texture2D? sliderBgTex; private Texture2D? sliderThumbTex; private Texture2D? buttonNormalBgTex; private Texture2D? buttonHoverBgTex; private bool initializedStyles; private float appliedUiScale = -1f; private static readonly string[] AllowedHosts = new string[20] { "youtube.com", "www.youtube.com", "m.youtube.com", "music.youtube.com", "www.music.youtube.com", "youtu.be", "www.youtu.be", "soundcloud.com", "www.soundcloud.com", "vimeo.com", "www.vimeo.com", "bandcamp.com", "www.bandcamp.com", "twitch.tv", "www.twitch.tv", "clips.twitch.tv", "tiktok.com", "www.tiktok.com", "mixcloud.com", "www.mixcloud.com" }; private int MaxAudioSizeMb => Mathf.Max(1, maxAudioSizeMbCfg?.Value ?? 100); private int MaxTempFiles => Mathf.Max(1, maxTempFilesCfg?.Value ?? 5); private int MaxQueueSize => Mathf.Max(1, (!ActAsHost()) ? ((hostMaxQueueSize > 0) ? hostMaxQueueSize : 20) : (maxQueueSizeCfg?.Value ?? 20)); private int PerUserQueueLimit => Mathf.Max(1, (!ActAsHost()) ? ((hostPerUserQueueLimit > 0) ? hostPerUserQueueLimit : 5) : (perUserQueueLimitCfg?.Value ?? 5)); private bool AllowDuplicates { get { if (!ActAsHost()) { return hostAllowDuplicates; } return allowDuplicatesCfg?.Value ?? false; } } private bool AutoAdvance { get { if (!ActAsHost()) { return hostAutoAdvance; } return autoAdvanceCfg?.Value ?? true; } } private int TitleProbeTimeoutSec => Mathf.Max(5, titleProbeTimeoutSecCfg?.Value ?? 20); private int PlaylistProbeTimeoutSec => Mathf.Max(5, playlistProbeTimeoutSecCfg?.Value ?? 30); private bool GlobalAudio { get { if (!ActAsHost()) { return hostGlobalAudio; } return globalAudioCfg?.Value ?? false; } } private bool BypassEnv { get { if (!ActAsHost()) { return hostBypassEnv; } return bypassEnvCfg?.Value ?? false; } } private bool PublicEnqueue { get { if (!ActAsHost()) { return hostPublicEnqueue; } return publicEnqueueCfg?.Value ?? false; } } private float MaxHearingDistance => Mathf.Clamp((!ActAsHost()) ? ((hostMaxHearingDistance <= 0f) ? 35f : hostMaxHearingDistance) : (maxHearingDistanceCfg?.Value ?? 35f), 5f, 10000f); private float UiScale => Mathf.Clamp(uiScaleCfg?.Value ?? 1f, 0.75f, 2f); private bool ShowAdvanced => showAdvancedCfg?.Value ?? false; private bool ActAsHost() { if (!PhotonNetwork.IsMasterClient) { if (!hostHasMod) { return lastUsedPlayer == userId; } return false; } return true; } private int LocalActor() { if (PhotonNetwork.LocalPlayer == null) { return -1; } return PhotonNetwork.LocalPlayer.ActorNumber; } private int GetCurrentHolderActorNumber() { try { foreach (Player allPlayer in PlayerHandler.GetAllPlayers()) { if ((Object)(object)allPlayer == (Object)null || (Object)(object)allPlayer.character == (Object)null || !allPlayer.HasInAnySlot((ushort)13)) { continue; } int num = -1; try { Character character = allPlayer.character; int? obj; if (character == null) { obj = null; } else { PhotonView obj2 = ((MonoBehaviourPun)character).photonView; obj = ((obj2 != null) ? new int?(obj2.OwnerActorNr) : ((int?)null)); } num = obj ?? (-1); } catch { } if (num <= 0) { try { PhotonView obj4 = ((MonoBehaviourPun)allPlayer).photonView; num = ((obj4 != null) ? obj4.OwnerActorNr : (-1)); } catch { } } if (num > 0) { return num; } } } catch (Exception ex) { ManualLogSource? obj6 = logger; if (obj6 != null) { obj6.LogWarning((object)("GetCurrentHolderActorNumber failed: " + ex.Message)); } } return -1; } private bool IsRequestorHolder(int requestorActorNumber) { int currentHolderActorNumber = GetCurrentHolderActorNumber(); if (currentHolderActorNumber > 0) { return requestorActorNumber == currentHolderActorNumber; } return false; } private void Awake() { //IL_0437: Unknown result type (might be due to invalid IL or missing references) logger = ((BaseUnityPlugin)this).Logger; photonView = ((Component)this).gameObject.AddComponent<PhotonView>(); photonView.ViewID = 215151321; spectrumData = new float[sampleSize]; string pluginDir = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? Paths.PluginPath; ytDlpPath = Path.Combine(pluginDir, "yt-dlp.exe"); ffmpegPath = Path.Combine(pluginDir, "ffmpeg.exe"); denoPath = Path.Combine(pluginDir, "deno.exe"); tempAudioDir = Path.Combine(Path.GetTempPath(), "BingBongAudio"); Directory.CreateDirectory(tempAudioDir); CleanupOldTempFiles(); if (!File.Exists(ytDlpPath)) { ManualLogSource? obj = logger; if (obj != null) { obj.LogWarning((object)"yt-dlp was not found! Fetching..."); } Task.Run(async delegate { await Utils.DownloadYtDlp(pluginDir); }).GetAwaiter().GetResult(); } if (!File.Exists(ffmpegPath)) { ManualLogSource? obj2 = logger; if (obj2 != null) { obj2.LogWarning((object)"ffmpeg was not found! Fetching..."); } Task.Run(async delegate { await Utils.DownloadFFmpeg(pluginDir); }).GetAwaiter().GetResult(); } if (!File.Exists(denoPath)) { ManualLogSource? obj3 = logger; if (obj3 != null) { obj3.LogWarning((object)"Deno was not found! Fetching..."); } Task.Run(async delegate { await Utils.DownloadDeno(pluginDir); }).GetAwaiter().GetResult(); } volumeSetting = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "Volume", 0.45f, "Default playback volume (0.0 to 1.0)"); currentVolume = Mathf.Clamp01(volumeSetting.Value); maxAudioSizeMbCfg = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "MaxAudioSizeMB", 100, "Max file size yt-dlp may download."); maxTempFilesCfg = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "MaxTempFiles", 5, "How many cached WAVs to keep."); maxQueueSizeCfg = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "MaxQueueSize", 20, "Max items allowed in queue. (Host only)"); perUserQueueLimitCfg = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "PerUserQueueLimit", 5, "Max items one user can have pending in the queue. (Host only)"); allowDuplicatesCfg = ((BaseUnityPlugin)this).Config.Bind<bool>("Queue", "AllowDuplicates", false, "Allow the same URL to be enqueued multiple times. (Host only)"); autoAdvanceCfg = ((BaseUnityPlugin)this).Config.Bind<bool>("Queue", "AutoAdvance", true, "Automatically advance to next track when one finishes. (Host only)"); titleProbeTimeoutSecCfg = ((BaseUnityPlugin)this).Config.Bind<int>("Advanced", "TitleProbeTimeoutSec", 20, "Timeout for title probes (seconds)."); playlistProbeTimeoutSecCfg = ((BaseUnityPlugin)this).Config.Bind<int>("Advanced", "PlaylistProbeTimeoutSec", 30, "Timeout for playlist expansion (seconds)."); globalAudioCfg = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "GlobalAudio", false, "2D audio heard anywhere. (Host only)"); bypassEnvCfg = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "BypassEnvironmentEffects", false, "Bypass reverb/occlusion. (Host only)"); maxHearingDistanceCfg = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "MaxHearingDistance", 35f, "3D audio max distance. (Host only)"); publicEnqueueCfg = ((BaseUnityPlugin)this).Config.Bind<bool>("Queue", "PublicEnqueue", false, "Non-holders may add and control playback. (Host only)"); uiScaleCfg = ((BaseUnityPlugin)this).Config.Bind<float>("UI", "UiScale", 1f, "Scale UI text/vertical only (client)"); showAdvancedCfg = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "ShowAdvanced", false, "Show Advanced section (client)"); audioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); audioSource.spatialBlend = 1f; audioSource.volume = currentVolume; audioSource.loop = false; audioSource.rolloffMode = (AudioRolloffMode)1; audioSource.minDistance = 1f; audioSource.maxDistance = MaxHearingDistance; audioSource.bypassEffects = BypassEnv; audioSource.bypassListenerEffects = BypassEnv; audioSource.bypassReverbZones = BypassEnv; new Harmony("xyz.restorerootfs.bingbongboomboxplus").PatchAll(); } private void OnDestroy() { if (currentPlaybackCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(currentPlaybackCoroutine); } if (debouncePlayCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(debouncePlayCoroutine); } if (downloadCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(downloadCoroutine); } } private void LateUpdate() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0354: Unknown result type (might be due to invalid IL or missing references) bool flag = (int)PhotonNetwork.Server == 1; if (lastConnectedState && !flag) { StopAndResetSong(); queue.Clear(); sentInitialConfig = false; } lastConnectedState = flag; if ((Object)(object)audioSource == (Object)null || !PhotonNetwork.IsConnectedAndReady || (Object)(object)photonView == (Object)null) { return; } if (ActAsHost()) { if (!sentInitialConfig) { BroadcastHostConfig(); sentInitialConfig = true; } if (Time.time - lastConfigBroadcast > 15f) { BroadcastHostConfig(); } } if (ActAsHost() && Time.time - lastSyncTime > syncInterval) { photonView.RPC("RPC_SyncPlaying", (RpcTarget)1, new object[4] { currentSongHash, audioSource.isPlaying, PhotonNetwork.IsMasterClient, currentSongTitle }); if (audioSource.isPlaying) { photonView.RPC("RPC_SyncTime", (RpcTarget)1, new object[2] { currentSongHash, audioSource.time }); } lastSyncTime = Time.time; } bool flag2 = Singleton<PeakHandler>.Instance?.isPlayingCinematic ?? false; Vector3? val = null; if ((Object)(object)bingBong != (Object)null) { val = bingBong.position; } else { Player? obj = FindPlayerWithBingBong(); Vector3? obj2; if (obj == null) { obj2 = null; } else { Character character = obj.character; obj2 = ((character != null) ? new Vector3?(character.Center) : ((Vector3?)null)); } val = obj2; } if (GlobalAudio) { audioSource.spatialBlend = 0f; audioSource.volume = currentVolume; } else { audioSource.spatialBlend = (flag2 ? 0f : 1f); audioSource.maxDistance = MaxHearingDistance; audioSource.volume = (val.HasValue ? currentVolume : 0f); if (val.HasValue) { ((Component)audioSource).transform.position = val.Value; } } audioSource.bypassEffects = BypassEnv; audioSource.bypassListenerEffects = BypassEnv; audioSource.bypassReverbZones = BypassEnv; if (ActAsHost() && AutoAdvance && !manualStop && (Object)(object)audioSource.clip != (Object)null && !audioSource.isPlaying && !string.IsNullOrEmpty(currentSongHash) && songLoadingState == SongLoadingState.Loaded) { PlayNextImpl(); } if (!((Object)(object)audioSource != (Object)null) || !((Object)(object)bingBongMouth != (Object)null) || mouthOpenParam == null) { return; } spectrumTimer += Time.deltaTime; if (!(spectrumTimer >= spectrumInterval)) { return; } spectrumTimer = 0f; float num = 0f; if (audioSource.isPlaying && spectrumData != null && (Object)(object)audioSource.clip != (Object)null) { audioSource.GetSpectrumData(spectrumData, 0, fftWindow); float num2 = (float)AudioSettings.outputSampleRate / 2f / (float)sampleSize; int num3 = Mathf.FloorToInt(vocalLow / num2); int num4 = Mathf.CeilToInt(vocalHigh / num2); int num5 = Mathf.Clamp(num3, 0, sampleSize - 1); num4 = Mathf.Clamp(num4, 0, sampleSize - 1); float num6 = 0f; int num7 = 0; for (int i = num5; i <= num4; i++) { num6 += spectrumData[i]; num7++; } num = Mathf.Clamp01(((num7 > 0) ? (num6 / (float)num7) : 0f) * 50f); } try { bingBongMouth.SetFloat(mouthOpenHash, num); } catch { } } private void BroadcastHostConfig() { lastConfigBroadcast = Time.time; HostConfigPayload hostConfigPayload = new HostConfigPayload { publicEnqueue = PublicEnqueue, globalAudio = GlobalAudio, bypassEnv = BypassEnv, allowDuplicates = AllowDuplicates, autoAdvance = AutoAdvance, maxHearingDistance = MaxHearingDistance, maxQueueSize = MaxQueueSize, perUserQueueLimit = PerUserQueueLimit, v = "1.2.0" }; PhotonView? obj = photonView; if (obj != null) { obj.RPC("RPC_ConfigSync", (RpcTarget)1, new object[1] { JsonUtility.ToJson((object)hostConfigPayload) }); } } [PunRPC] private void RPC_ConfigSync(string json) { try { HostConfigPayload hostConfigPayload = JsonUtility.FromJson<HostConfigPayload>(json); if (hostConfigPayload != null) { hostPublicEnqueue = hostConfigPayload.publicEnqueue; hostGlobalAudio = hostConfigPayload.globalAudio; hostBypassEnv = hostConfigPayload.bypassEnv; hostAllowDuplicates = hostConfigPayload.allowDuplicates; hostAutoAdvance = hostConfigPayload.autoAdvance; hostMaxHearingDistance = hostConfigPayload.maxHearingDistance; hostMaxQueueSize = hostConfigPayload.maxQueueSize; hostPerUserQueueLimit = hostConfigPayload.perUserQueueLimit; } } catch { } } private Player? FindPlayerWithBingBong() { foreach (Player allPlayer in PlayerHandler.GetAllPlayers()) { if ((Object)(object)allPlayer.character != (Object)null && allPlayer.HasInAnySlot((ushort)13)) { return allPlayer; } } return null; } private Texture2D MakeSolidColorTex(Color col) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown Texture2D val = new Texture2D(1, 1); val.SetPixel(0, 0, col); val.Apply(); return val; } private void InitStyles() { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Expected O, but got Unknown //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Expected O, but got Unknown //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Expected O, but got Unknown //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Expected O, but got Unknown //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Expected O, but got Unknown //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Expected O, but got Unknown //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Expected O, but got Unknown //IL_024a: Expected O, but got Unknown //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Expected O, but got Unknown //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_02f9: Expected O, but got Unknown //IL_02f9: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Expected O, but got Unknown //IL_0336: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_0375: Unknown result type (might be due to invalid IL or missing references) //IL_037a: Unknown result type (might be due to invalid IL or missing references) //IL_038b: Unknown result type (might be due to invalid IL or missing references) //IL_03a8: Expected O, but got Unknown //IL_03b3: Unknown result type (might be due to invalid IL or missing references) //IL_03b8: Unknown result type (might be due to invalid IL or missing references) //IL_03c9: Unknown result type (might be due to invalid IL or missing references) //IL_03e1: Unknown result type (might be due to invalid IL or missing references) //IL_03fe: Expected O, but got Unknown if (initializedStyles && Mathf.Abs(appliedUiScale - UiScale) < 0.001f) { return; } appliedUiScale = UiScale; Font val = null; Font[] array = Resources.FindObjectsOfTypeAll<Font>(); foreach (Font val2 in array) { if (((Object)val2).name == "DarumaDropOne-Regular") { val = val2; break; } } if (!((Object)(object)val == (Object)null) || PhotonNetwork.IsConnectedAndReady) { boxBgTex = MakeSolidColorTex(new Color(0f, 0f, 0f, 0.8f)); int num = Mathf.RoundToInt(12f * UiScale); GUIStyle val3 = new GUIStyle(GUI.skin.box) { padding = new RectOffset(12, 12, num, num), margin = new RectOffset(10, 10, 10, 10) }; val3.normal.background = boxBgTex; boxStyle = val3; GUIStyle val4 = new GUIStyle(GUI.skin.label) { fontSize = Mathf.RoundToInt(14f * UiScale) }; val4.normal.textColor = Color.white; val4.richText = true; labelStyle = val4; GUIStyle val5 = new GUIStyle(GUI.skin.label) { fontSize = Mathf.RoundToInt(12f * UiScale) }; val5.normal.textColor = Color.white; val5.richText = true; smallLabelStyle = val5; GUIStyle val6 = new GUIStyle(GUI.skin.label) { fontSize = Mathf.RoundToInt(22f * UiScale) }; val6.normal.textColor = Color.white; val6.richText = true; headerStyle = val6; toggleStyle = new GUIStyle(GUI.skin.toggle) { fontSize = Mathf.RoundToInt(13f * UiScale) }; foldoutStyle = new GUIStyle(GUI.skin.button) { fontSize = Mathf.RoundToInt(13f * UiScale), alignment = (TextAnchor)3, padding = new RectOffset(8, 8, Mathf.RoundToInt(6f * UiScale), Mathf.RoundToInt(6f * UiScale)) }; buttonNormalBgTex = MakeSolidColorTex(new Color(0.2f, 0.2f, 0.2f, 0.95f)); buttonHoverBgTex = MakeSolidColorTex(new Color(0.3f, 0.3f, 0.3f, 0.95f)); GUIStyle val7 = new GUIStyle(GUI.skin.button) { fontSize = Mathf.RoundToInt(13f * UiScale), margin = new RectOffset(5, 5, 5, 5), padding = new RectOffset(8, 8, Mathf.RoundToInt(6f * UiScale), Mathf.RoundToInt(6f * UiScale)) }; val7.normal.background = buttonNormalBgTex; val7.hover.background = buttonHoverBgTex; buttonStyle = val7; sliderBgTex = MakeSolidColorTex(new Color(0.15f, 0.15f, 0.15f, 1f)); sliderThumbTex = MakeSolidColorTex(new Color(1f, 0.6f, 0f, 1f)); GUIStyle val8 = new GUIStyle(GUI.skin.horizontalSlider); val8.normal.background = sliderBgTex; val8.fixedHeight = Mathf.RoundToInt(12f * UiScale); sliderStyle = val8; GUIStyle val9 = new GUIStyle(GUI.skin.horizontalSliderThumb); val9.normal.background = sliderThumbTex; val9.fixedWidth = Mathf.RoundToInt(16f * UiScale); val9.fixedHeight = Mathf.RoundToInt(16f * UiScale); sliderThumbStyle = val9; if ((Object)(object)val != (Object)null) { labelStyle.font = val; smallLabelStyle.font = val; headerStyle.font = val; buttonStyle.font = val; toggleStyle.font = val; foldoutStyle.font = val; } initializedStyles = true; } } private void OnGUI() { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0c78: Unknown result type (might be due to invalid IL or missing references) //IL_0c9c: Unknown result type (might be due to invalid IL or missing references) //IL_0ca1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.localPlayer == (Object)null || (Object)(object)photonView == (Object)null) { return; } InitStyles(); if (!initializedStyles) { return; } if (!GUIManager.InPauseMenu) { if (Player.localPlayer.HasInAnySlot((ushort)13)) { GUILayout.BeginArea(new Rect(10f, 10f, 480f, (float)Mathf.RoundToInt(100f * UiScale))); GUILayout.Label("<b>Press [ESC] to access Music Player</b>", labelStyle, Array.Empty<GUILayoutOption>()); GUILayout.EndArea(); } return; } float num = 10f; float num2 = Mathf.Min(720f, (float)Screen.width - 2f * num); float num3 = (float)Screen.height - 2f * num; GUILayout.BeginArea(new Rect(num, num, num2, num3), boxStyle); panelScroll = GUILayout.BeginScrollView(panelScroll, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(num2 - 8f), GUILayout.Height(num3 - 8f) }); GUILayout.Label("【BingBong Player】", headerStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label(ActAsHost() ? "※ You are the acting host." : "※ Waiting for host…", smallLabelStyle, Array.Empty<GUILayoutOption>()); bool flag = Player.localPlayer.HasInAnySlot((ushort)13); if (flag || PublicEnqueue) { GUILayout.Space(4f * UiScale); GUILayout.Label("URL:", labelStyle, Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); urlInput = GUILayout.TextField(urlInput, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(num2 - 180f), GUILayout.Height(26f * UiScale) }); if (GUILayout.Button("Clear", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(70f), GUILayout.Height(26f * UiScale) })) { urlInput = ""; } if (GUILayout.Button("Add »", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(70f), GUILayout.Height(26f * UiScale) })) { RequestEnqueue(urlInput); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUI.enabled = flag || PublicEnqueue; if (GUILayout.Button("Stop", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(26f * UiScale) })) { photonView.RPC("RPC_RequestStop", (RpcTarget)0, Array.Empty<object>()); } if (GUILayout.Button("〈〈 10s", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(26f * UiScale) })) { photonView.RPC("RPC_RequestSeek", (RpcTarget)0, new object[1] { -10f }); } if (GUILayout.Button("10s 〉〉", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(26f * UiScale) })) { photonView.RPC("RPC_RequestSeek", (RpcTarget)0, new object[1] { 10f }); } if (GUILayout.Button("Next »", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(26f * UiScale) })) { photonView.RPC("RPC_RequestNext", (RpcTarget)0, Array.Empty<object>()); } GUI.enabled = true; if (!flag && PublicEnqueue) { GUILayout.Label("※ Public enqueue is enabled.", smallLabelStyle, Array.Empty<GUILayoutOption>()); } GUILayout.EndHorizontal(); } GUILayout.Space(8f * UiScale); GUILayout.Box("", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(1f) }); GUILayout.Space(8f * UiScale); GUILayout.Label($"Local Volume: {Mathf.RoundToInt(currentVolume * 100f)}%", labelStyle, Array.Empty<GUILayoutOption>()); float num4 = GUILayout.HorizontalSlider(currentVolume, 0f, 1f, sliderStyle, sliderThumbStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num2 - 40f) }); if (Mathf.Abs(num4 - currentVolume) > 0.001f) { currentVolume = num4; if (volumeSetting != null) { volumeSetting.Value = num4; } if ((Object)(object)audioSource != (Object)null) { audioSource.volume = currentVolume; } } GUILayout.Space(8f * UiScale); GUILayout.Box("", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(1f) }); GUILayout.Space(8f * UiScale); bool flag2 = ShowAdvanced; if (GUILayout.Button(flag2 ? "▼ Advanced" : "► Advanced", foldoutStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(26f * UiScale) })) { flag2 = !flag2; if (showAdvancedCfg != null) { showAdvancedCfg.Value = flag2; } } if (flag2) { GUI.enabled = ActAsHost(); bool flag3 = GUILayout.Toggle(GlobalAudio, " Global Audio (2D / Hear Anywhere)", toggleStyle, Array.Empty<GUILayoutOption>()); bool flag4 = GUILayout.Toggle(BypassEnv, " Bypass Environment Effects", toggleStyle, Array.Empty<GUILayoutOption>()); bool flag5 = GUILayout.Toggle(PublicEnqueue, " Allow Public Enqueue (add/controls)", toggleStyle, Array.Empty<GUILayoutOption>()); if (flag3 != GlobalAudio) { globalAudioCfg.Value = flag3; BroadcastHostConfig(); } if (flag4 != BypassEnv) { bypassEnvCfg.Value = flag4; BroadcastHostConfig(); } if (flag5 != PublicEnqueue) { publicEnqueueCfg.Value = flag5; BroadcastHostConfig(); } if (!GlobalAudio) { GUILayout.Label($"Max Hearing Distance: {Mathf.RoundToInt(MaxHearingDistance)}m", labelStyle, Array.Empty<GUILayoutOption>()); float num5 = GUILayout.HorizontalSlider(MaxHearingDistance, 5f, 300f, sliderStyle, sliderThumbStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num2 - 40f) }); if (Mathf.Abs(num5 - MaxHearingDistance) > 0.001f) { maxHearingDistanceCfg.Value = num5; BroadcastHostConfig(); } } GUILayout.Label($"Max Queue Size: {MaxQueueSize}", smallLabelStyle, Array.Empty<GUILayoutOption>()); int num6 = Mathf.RoundToInt(GUILayout.HorizontalSlider((float)MaxQueueSize, 5f, 50f, sliderStyle, sliderThumbStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num2 - 40f) })); if (num6 != MaxQueueSize) { maxQueueSizeCfg.Value = num6; BroadcastHostConfig(); } GUILayout.Label($"Per-User Queue Limit: {PerUserQueueLimit}", smallLabelStyle, Array.Empty<GUILayoutOption>()); int num7 = Mathf.RoundToInt(GUILayout.HorizontalSlider((float)PerUserQueueLimit, 1f, 15f, sliderStyle, sliderThumbStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num2 - 40f) })); if (num7 != PerUserQueueLimit) { perUserQueueLimitCfg.Value = num7; BroadcastHostConfig(); } bool flag6 = GUILayout.Toggle(AllowDuplicates, " Allow Duplicate URLs", toggleStyle, Array.Empty<GUILayoutOption>()); if (flag6 != AllowDuplicates) { allowDuplicatesCfg.Value = flag6; BroadcastHostConfig(); } bool flag7 = GUILayout.Toggle(AutoAdvance, " Auto-Advance", toggleStyle, Array.Empty<GUILayoutOption>()); if (flag7 != AutoAdvance) { autoAdvanceCfg.Value = flag7; BroadcastHostConfig(); } GUI.enabled = true; GUILayout.Label($"UI Scale: {UiScale:0.00}x", smallLabelStyle, Array.Empty<GUILayoutOption>()); float num8 = GUILayout.HorizontalSlider(UiScale, 0.75f, 2f, sliderStyle, sliderThumbStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num2 - 40f) }); if (Mathf.Abs(num8 - UiScale) > 0.001f) { uiScaleCfg.Value = num8; } } if (songLoadingState == SongLoadingState.Loading) { GUILayout.Space(8f * UiScale); GUILayout.Box("", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(1f) }); GUILayout.Space(8f * UiScale); int num9 = Mathf.FloorToInt(Time.time) % LoadingProgress.Length; GUILayout.Label(LoadingProgress[num9], labelStyle, Array.Empty<GUILayoutOption>()); } else if (songLoadingState == SongLoadingState.Error) { GUILayout.Space(8f * UiScale); GUILayout.Box("", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(1f) }); GUILayout.Space(8f * UiScale); GUILayout.Label("ERROR! Couldn't load audio.", labelStyle, Array.Empty<GUILayoutOption>()); } else if (!string.IsNullOrEmpty(currentSongTitle)) { AudioSource? obj = audioSource; if ((Object)(object)((obj != null) ? obj.clip : null) != (Object)null && (audioSource.isPlaying || audioSource.time > 0f)) { GUILayout.Space(8f * UiScale); GUILayout.Box("", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(1f) }); GUILayout.Space(8f * UiScale); GUILayout.Label("Now Playing: 「" + currentSongTitle + "」", labelStyle, Array.Empty<GUILayoutOption>()); int num10 = Mathf.FloorToInt(audioSource.time / 60f); int num11 = Mathf.FloorToInt(audioSource.time % 60f); int num12 = Mathf.FloorToInt(audioSource.clip.length / 60f); int num13 = Mathf.FloorToInt(audioSource.clip.length % 60f); GUILayout.Label($"{num10:D2}:{num11:D2} / {num12:D2}:{num13:D2}", labelStyle, Array.Empty<GUILayoutOption>()); } } GUILayout.Space(8f * UiScale); GUILayout.Box("", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(1f) }); GUILayout.Space(8f * UiScale); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label($"【Queue】 ({queue.Count}/{MaxQueueSize})", labelStyle, Array.Empty<GUILayoutOption>()); GUILayout.FlexibleSpace(); if (flag || PublicEnqueue) { if (GUILayout.Button("〜 Shuffle", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(24f * UiScale) })) { photonView.RPC("RPC_RequestShuffle", (RpcTarget)0, Array.Empty<object>()); } if (GUILayout.Button("[ ] Clear", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(24f * UiScale) })) { photonView.RPC("RPC_RequestClearQueue", (RpcTarget)0, Array.Empty<object>()); } } GUILayout.EndHorizontal(); float num14 = 140f * UiScale; float num15 = Mathf.Clamp((float)Screen.height * 0.65f, 160f, (float)Screen.height - 260f); float num16 = 22f * UiScale; float num17 = Mathf.Clamp((float)queue.Count * num16 + 8f, num14, num15); queueScroll = GUILayout.BeginScrollView(queueScroll, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(num17), GUILayout.Width(num2 - 20f) }); for (int i = 0; i < queue.Count; i++) { SongRequest songRequest = queue[i]; string arg = (string.IsNullOrEmpty(songRequest.title) ? "Fetching title…" : songRequest.title); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label($"{i + 1}. • {arg} ({ShortId(songRequest.requestedBy)})", smallLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num2 - 180f) }); GUI.enabled = flag || PublicEnqueue || songRequest.requestedByActor == LocalActor(); if (GUILayout.Button("Remove", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) })) { photonView.RPC("RPC_RequestRemoveAt", (RpcTarget)0, new object[1] { i }); } GUI.enabled = true; GUILayout.EndHorizontal(); } GUILayout.EndScrollView(); GUILayout.EndScrollView(); GUILayout.EndArea(); } private string ShortId(string id) { if (!string.IsNullOrEmpty(id)) { if (id.Length > 6) { return id.Substring(0, 6); } return id; } return "??"; } private void RequestEnqueue(string url) { if (!string.IsNullOrWhiteSpace(url) && IsSupportedMediaUrl(url)) { if (debouncePlayCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(debouncePlayCoroutine); debouncePlayCoroutine = null; } debouncePlayCoroutine = ((MonoBehaviour)this).StartCoroutine(DebounceRequestCoroutine(0.4f, url)); } } private IEnumerator DebounceRequestCoroutine(float delay, string url) { yield return (object)new WaitForSecondsRealtime(delay); PhotonView? obj = photonView; if (obj != null) { obj.RPC("RPC_RequestEnqueue", (RpcTarget)0, new object[2] { url, userId }); } } [PunRPC] private void RPC_RequestEnqueue(string url, string requestorId, PhotonMessageInfo info) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!IsSupportedMediaUrl(url) || !ActAsHost()) { return; } int requestorActor = ((info.Sender != null) ? info.Sender.ActorNumber : (-1)); if (IsYouTubePlaylistUrl(url)) { ((MonoBehaviour)this).StartCoroutine(EnqueueYouTubePlaylist(url, requestorId, requestorActor)); return; } SongRequest addedItem; bool num = TryEnqueueInternal(url, requestorId, requestorActor, out addedItem, null); BroadcastQueue(); if (num && addedItem != null && string.IsNullOrEmpty(addedItem.title)) { StartTitleFetchIfNeeded(addedItem.hash, addedItem.url); } if (!audioSource.isPlaying && songLoadingState != SongLoadingState.Loading) { PlayNextImpl(); } } [PunRPC] private void RPC_RequestRemoveAt(int index, PhotonMessageInfo info) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (ActAsHost() && index >= 0 && index < queue.Count) { int num = ((info.Sender != null) ? info.Sender.ActorNumber : (-1)); if (queue[index].requestedByActor == num || IsRequestorHolder(num) || PublicEnqueue) { queue.RemoveAt(index); BroadcastQueue(); } } } [PunRPC] private void RPC_RequestClearQueue(PhotonMessageInfo info) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (ActAsHost()) { int requestorActorNumber = ((info.Sender != null) ? info.Sender.ActorNumber : (-1)); if (PublicEnqueue || IsRequestorHolder(requestorActorNumber)) { queue.Clear(); BroadcastQueue(); } } } [PunRPC] private void RPC_RequestNext(PhotonMessageInfo info) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (ActAsHost()) { int requestorActorNumber = ((info.Sender != null) ? info.Sender.ActorNumber : (-1)); if (PublicEnqueue || IsRequestorHolder(requestorActorNumber)) { PlayNextImpl(); } } } [PunRPC] private void RPC_RequestShuffle(PhotonMessageInfo info) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (!ActAsHost()) { return; } int requestorActorNumber = ((info.Sender != null) ? info.Sender.ActorNumber : (-1)); if (PublicEnqueue || IsRequestorHolder(requestorActorNumber)) { Random random = new Random(); for (int num = queue.Count - 1; num > 0; num--) { int num2 = random.Next(num + 1); List<SongRequest> list = queue; int index = num; List<SongRequest> list2 = queue; int index2 = num2; SongRequest value = queue[num2]; SongRequest value2 = queue[num]; list[index] = value; list2[index2] = value2; } BroadcastQueue(); } } [PunRPC] private void RPC_RequestStop(PhotonMessageInfo info) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (!ActAsHost()) { return; } int requestorActorNumber = ((info.Sender != null) ? info.Sender.ActorNumber : (-1)); if (PublicEnqueue || IsRequestorHolder(requestorActorNumber)) { PhotonView? obj = photonView; if (obj != null) { obj.RPC("RPC_StopAudio", (RpcTarget)0, Array.Empty<object>()); } } } [PunRPC] private void RPC_RequestSeek(float seconds, PhotonMessageInfo info) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (ActAsHost()) { int requestorActorNumber = ((info.Sender != null) ? info.Sender.ActorNumber : (-1)); if ((PublicEnqueue || IsRequestorHolder(requestorActorNumber)) && !((Object)(object)audioSource == (Object)null) && !((Object)(object)audioSource.clip == (Object)null)) { float num = Mathf.Clamp(audioSource.time + seconds, 0f, audioSource.clip.length); audioSource.time = num; photonView.RPC("RPC_SyncTime", (RpcTarget)1, new object[2] { currentSongHash, num }); } } } private bool IsYouTubePlaylistUrl(string url) { if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result)) { return false; } string text = result.Host.ToLowerInvariant(); if (!text.Contains("youtube.com") && !text.Contains("youtu.be") && !text.Contains("music.youtube.com")) { return false; } NameValueCollection nameValueCollection = HttpUtility.ParseQueryString(result.Query); bool num = !string.IsNullOrEmpty(nameValueCollection["list"]); bool flag = false; if (text.Contains("youtu.be")) { flag = !string.IsNullOrEmpty(result.AbsolutePath.Trim('/')); } else { if (!string.IsNullOrEmpty(nameValueCollection["v"])) { flag = true; } if (result.AbsolutePath.ToLowerInvariant().StartsWith("/shorts/")) { flag = true; } } if (num) { return !flag; } return false; } private IEnumerator EnqueueYouTubePlaylist(string playlistUrl, string requestorId, int requestorActor) { if (string.IsNullOrEmpty(ytDlpPath) || !File.Exists(ytDlpPath)) { yield break; } int val = Math.Max(0, MaxQueueSize - queue.Count); int num = queue.Count((SongRequest it) => it.requestedByActor == requestorActor); int val2 = Math.Max(0, PerUserQueueLimit - num); int num2 = Math.Min(val, val2); if (num2 <= 0) { yield break; } ProcessStartInfo processStartInfo = new ProcessStartInfo { FileName = ytDlpPath, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; processStartInfo.ArgumentList.Add("--yes-playlist"); processStartInfo.ArgumentList.Add("--flat-playlist"); processStartInfo.ArgumentList.Add("--ignore-errors"); processStartInfo.ArgumentList.Add("--playlist-items"); processStartInfo.ArgumentList.Add($"1-{num2}"); processStartInfo.ArgumentList.Add("--print"); processStartInfo.ArgumentList.Add("%(title)s\t%(webpage_url)s"); processStartInfo.ArgumentList.Add("--js-runtimes"); processStartInfo.ArgumentList.Add(denoPath); processStartInfo.ArgumentList.Add(playlistUrl); Process proc = new Process { StartInfo = processStartInfo }; List<string> lines = new List<string>(); proc.OutputDataReceived += delegate(object s, DataReceivedEventArgs e) { if (!string.IsNullOrEmpty(e.Data)) { lines.Add(e.Data); } }; proc.ErrorDataReceived += delegate { }; bool flag = false; try { flag = proc.Start(); } catch (Exception) { } if (!flag) { yield break; } proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); float startTime = Time.realtimeSinceStartup; while (!proc.HasExited) { if (Time.realtimeSinceStartup - startTime > (float)PlaylistProbeTimeoutSec) { try { proc.Kill(); } catch { } break; } yield return null; } int num3 = 0; foreach (string item in lines) { if (string.IsNullOrWhiteSpace(item)) { continue; } int num4 = item.IndexOf('\t'); if (num4 > 0 && num4 < item.Length - 1) { string knownTitle = item.Substring(0, num4).Trim(); string url = item.Substring(num4 + 1).Trim(); if (queue.Count >= MaxQueueSize || queue.Count((SongRequest it) => it.requestedByActor == requestorActor) >= PerUserQueueLimit) { break; } if ((AllowDuplicates || !queue.Any((SongRequest it) => it.url.Equals(url, StringComparison.OrdinalIgnoreCase))) && TryEnqueueInternal(url, requestorId, requestorActor, out SongRequest addedItem, knownTitle) && addedItem != null) { num3++; } } } if (num3 > 0) { BroadcastQueue(); } if (num3 > 0 && !audioSource.isPlaying && songLoadingState != SongLoadingState.Loading) { PlayNextImpl(); } } private bool TryEnqueueInternal(string url, string requestorId, int requestorActor, out SongRequest? addedItem, string? knownTitle) { addedItem = null; if (queue.Count >= MaxQueueSize) { return false; } if (queue.Count((SongRequest it) => it.requestedByActor == requestorActor) >= PerUserQueueLimit) { return false; } if (!AllowDuplicates && queue.Any((SongRequest it) => it.url.Equals(url, StringComparison.OrdinalIgnoreCase))) { return false; } string safeSongId = GetSafeSongId(url); string path = Path.Combine(tempAudioDir, "bingbong_" + safeSongId + ".json"); string text = knownTitle ?? ""; if (string.IsNullOrEmpty(text) && File.Exists(path)) { try { text = JsonUtility.FromJson<SongMetadata>(File.ReadAllText(path))?.title ?? ""; } catch { text = ""; } } SongRequest songRequest = new SongRequest { url = url, hash = safeSongId, requestedBy = requestorId, requestedByActor = requestorActor, title = text }; queue.Add(songRequest); addedItem = songRequest; if (!string.IsNullOrEmpty(text)) { try { File.WriteAllText(path, JsonUtility.ToJson((object)new SongMetadata { title = text })); } catch { } } if (string.IsNullOrEmpty(text)) { StartTitleFetchIfNeeded(safeSongId, url); } return true; } private void PlayNextImpl() { manualStop = false; if (queue.Count == 0) { StopAndResetSong(); BroadcastQueue(); return; } SongRequest songRequest = queue[0]; queue.RemoveAt(0); BroadcastQueue(); lastUsedPlayer = songRequest.requestedBy; PhotonView? obj = photonView; if (obj != null) { obj.RPC("RPC_StopAudio", (RpcTarget)0, Array.Empty<object>()); } PlayAudio(songRequest.url, lastUsedPlayer); if (string.IsNullOrEmpty(songRequest.title)) { StartTitleFetchIfNeeded(songRequest.hash, songRequest.url); } } private void BroadcastQueue() { string text = JsonUtility.ToJson((object)new SongQueueWrapper { items = queue }); photonView.RPC("RPC_SyncQueue", (RpcTarget)1, new object[1] { text }); } [PunRPC] private void RPC_SyncQueue(string json) { try { SongQueueWrapper songQueueWrapper = JsonUtility.FromJson<SongQueueWrapper>(json); if (songQueueWrapper != null && songQueueWrapper.items != null) { queue.Clear(); queue.AddRange(songQueueWrapper.items.Take(MaxQueueSize)); } } catch { } } private IEnumerator DebouncePlayCoroutine(float delay, string url, string userId) { yield return (object)new WaitForSecondsRealtime(delay); PlayAudio(url, userId); } [PunRPC] private void RPC_PlayAudio(string url, string userId, PhotonMessageInfo info) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Player sender = info.Sender; if ((sender == null || sender.IsMasterClient || !hostHasMod) && !string.IsNullOrWhiteSpace(url) && IsSupportedMediaUrl(url)) { if (debouncePlayCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(debouncePlayCoroutine); debouncePlayCoroutine = null; } debouncePlayCoroutine = ((MonoBehaviour)this).StartCoroutine(DebouncePlayCoroutine(1f, url, userId)); } } private void PlayAudio(string url, string userId) { debouncePlayCoroutine = null; if (string.IsNullOrWhiteSpace(url) || !IsSupportedMediaUrl(url)) { return; } lastUsedPlayer = userId; manualStop = false; if (ActAsHost()) { AudioSource? obj = audioSource; if (obj != null) { obj.Stop(); } PhotonView? obj2 = photonView; if (obj2 != null) { obj2.RPC("RPC_StopAudio", (RpcTarget)1, Array.Empty<object>()); } PhotonView? obj3 = photonView; if (obj3 != null) { obj3.RPC("RPC_PlayAudio", (RpcTarget)1, new object[2] { url, userId }); } } currentSongTitle = "Unknown"; currentSongHash = GetSafeSongId(url); string text = Path.Combine(tempAudioDir, "bingbong_" + currentSongHash + ".wav"); string text2 = Path.Combine(tempAudioDir, "bingbong_" + currentSongHash + ".json"); if (File.Exists(text2)) { try { SongMetadata songMetadata = JsonUtility.FromJson<SongMetadata>(File.ReadAllText(text2)); if (!string.IsNullOrEmpty(songMetadata?.title)) { currentSongTitle = songMetadata.title; } } catch { currentSongTitle = "Unknown"; } } if (currentPlaybackCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(currentPlaybackCoroutine); } if (File.Exists(text)) { currentPlaybackCoroutine = ((MonoBehaviour)this).StartCoroutine(LoadAndPlay(text)); return; } if (downloadCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(downloadCoroutine); downloadCoroutine = null; } downloadCoroutine = ((MonoBehaviour)this).StartCoroutine(DownloadAudio(url, text, text2)); } private IEnumerator DownloadAudio(string url, string outputPath, string metadataPath) { CleanupOldTempFiles(); songLoadingState = SongLoadingState.Loading; string titleFromPrint = ""; if (string.IsNullOrEmpty(ytDlpPath) || !File.Exists(ytDlpPath) || string.IsNullOrEmpty(ffmpegPath) || !File.Exists(ffmpegPath)) { songLoadingState = SongLoadingState.Error; yield break; } ProcessStartInfo processStartInfo = new ProcessStartInfo { FileName = ytDlpPath, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; processStartInfo.ArgumentList.Add("--no-playlist"); processStartInfo.ArgumentList.Add("--max-filesize"); processStartInfo.ArgumentList.Add($"{MaxAudioSizeMb}M"); processStartInfo.ArgumentList.Add("--extractor-args"); processStartInfo.ArgumentList.Add("youtube:player_client=tv_simply,default,-tv"); processStartInfo.ArgumentList.Add("-x"); processStartInfo.ArgumentList.Add("-f"); processStartInfo.ArgumentList.Add("bestaudio[abr<=128]/bestaudio[abr<=160]/bestaudio"); processStartInfo.ArgumentList.Add("--ffmpeg-location"); processStartInfo.ArgumentList.Add(ffmpegPath); processStartInfo.ArgumentList.Add("--js-runtimes"); processStartInfo.ArgumentList.Add(denoPath); processStartInfo.ArgumentList.Add("--audio-format"); processStartInfo.ArgumentList.Add("wav"); processStartInfo.ArgumentList.Add("--print"); processStartInfo.ArgumentList.Add("title-current:%(title)s"); processStartInfo.ArgumentList.Add("--no-simulate"); processStartInfo.ArgumentList.Add("-o"); processStartInfo.ArgumentList.Add(outputPath); processStartInfo.ArgumentList.Add(url); Process proc = new Process { StartInfo = processStartInfo }; proc.OutputDataReceived += delegate(object s, DataReceivedEventArgs e) { if (!string.IsNullOrEmpty(e.Data) && e.Data.StartsWith("title-current:")) { titleFromPrint = e.Data.Substring("title-current:".Length).Trim(); } }; proc.ErrorDataReceived += delegate { }; try { proc.Start(); } catch (Exception) { songLoadingState = SongLoadingState.Error; downloadCoroutine = null; yield break; } proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); float startTime = Time.realtimeSinceStartup; while (!proc.HasExited) { if (Time.realtimeSinceStartup - startTime > (float)(TitleProbeTimeoutSec + 60)) { try { proc.Kill(); } catch { } break; } yield return null; } if (File.Exists(outputPath)) { if (!string.IsNullOrWhiteSpace(titleFromPrint)) { currentSongTitle = titleFromPrint; } try { SongMetadata songMetadata = new SongMetadata { title = currentSongTitle }; File.WriteAllText(metadataPath, JsonUtility.ToJson((object)songMetadata)); } catch { } currentPlaybackCoroutine = ((MonoBehaviour)this).StartCoroutine(LoadAndPlay(outputPath)); } else { songLoadingState = SongLoadingState.Error; if (ActAsHost()) { PlayNextImpl(); } } downloadCoroutine = null; } private string GetSafeSongId(string url) { try { Uri uri = new Uri(url); string text = uri.Host.ToLowerInvariant(); if (text.Contains("youtu.be")) { string text2 = uri.AbsolutePath.Trim('/'); if (!string.IsNullOrEmpty(text2)) { return text2; } } if (text.Contains("youtube.com") || text.Contains("music.youtube.com")) { string text3 = HttpUtility.ParseQueryString(uri.Query)["v"]; if (!string.IsNullOrEmpty(text3)) { return text3; } string text4 = uri.AbsolutePath.Trim('/'); if (text4.StartsWith("shorts/", StringComparison.OrdinalIgnoreCase)) { string[] array = text4.Split('/'); if (array.Length >= 2 && array[1].Length > 0) { return array[1]; } } } } catch { } using SHA1 sHA = SHA1.Create(); return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(url)), 0, 6).Replace("-", ""); } private IEnumerator LoadAndPlay(string path) { if ((Object)(object)audioSource == (Object)null) { songLoadingState = SongLoadingState.Error; yield break; } songLoadingState = SongLoadingState.Loaded; UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip("file://" + path, (AudioType)20); try { yield return www.SendWebRequest(); if ((int)www.result != 1) { songLoadingState = SongLoadingState.Error; yield break; } AudioClip content = DownloadHandlerAudioClip.GetContent(www); audioSource.clip = content; if (ActAsHost()) { audioSource.Play(); } } finally { ((IDisposable)www)?.Dispose(); } } [PunRPC] private void RPC_StopAudio() { manualStop = true; if ((Object)(object)audioSource != (Object)null) { audioSource.Stop(); audioSource.clip = null; } } [PunRPC] private void RPC_SyncTime(string songHash, float hostTime) { if (!ActAsHost() && (Object)(object)audioSource != (Object)null && (Object)(object)audioSource.clip != (Object)null && songHash == currentSongHash && Mathf.Abs(audioSource.time - hostTime) > 0.5f && hostTime >= 0f && hostTime <= audioSource.clip.length) { audioSource.time = hostTime; } } [PunRPC] private void RPC_SyncPlaying(string songHash, bool hostIsPlaying, bool isOwner, string hostTitle) { if (isOwner) { hostHasMod = true; } if (!string.IsNullOrEmpty(hostTitle)) { currentSongTitle = hostTitle; } if (!ActAsHost() && (Object)(object)audioSource != (Object)null) { if (!audioSource.isPlaying && hostIsPlaying && (Object)(object)audioSource.clip != (Object)null && songHash == currentSongHash) { audioSource.Play(); } if (audioSource.isPlaying && (!hostIsPlaying || songHash != currentSongHash)) { audioSource.Stop(); } } } private void StopAndResetSong() { if (currentPlaybackCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(currentPlaybackCoroutine); } AudioSource? obj = audioSource; if (obj != null) { obj.Stop(); } if ((Object)(object)audioSource != (Object)null) { audioSource.clip = null; } songLoadingState = SongLoadingState.Loaded; currentSongTitle = ""; currentSongHash = ""; lastUsedPlayer = ""; manualStop = false; hostHasMod = PhotonNetwork.IsMasterClient; } private void CleanupOldTempFiles() { if (string.IsNullOrEmpty(tempAudioDir) || !Directory.Exists(tempAudioDir)) { return; } List<FileInfo> list = (from f in new DirectoryInfo(tempAudioDir).GetFiles("bingbong_*.wav") orderby f.LastAccessTimeUtc descending select f).ToList(); for (int num = MaxTempFiles; num < list.Count; num++) { FileInfo fileInfo = list[num]; try { fileInfo.Delete(); } catch { } string path = Path.ChangeExtension(fileInfo.FullName, ".json"); if (File.Exists(path)) { try { File.Delete(path); } catch { } } } } private bool IsSupportedMediaUrl(string url) { if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result)) { return false; } string value = result.Host.ToLowerInvariant(); if (AllowedHosts.Contains(value)) { return true; } if (!(result.Scheme == Uri.UriSchemeHttp)) { return result.Scheme == Uri.UriSchemeHttps; } return true; } private void StartTitleFetchIfNeeded(string hash, string url) { if (!ActAsHost() || titleFetchInProgress.Contains(hash)) { return; } string path = Path.Combine(tempAudioDir, "bingbong_" + hash + ".json"); if (File.Exists(path)) { try { SongMetadata songMetadata = JsonUtility.FromJson<SongMetadata>(File.ReadAllText(path)); if (!string.IsNullOrEmpty(songMetadata?.title)) { UpdateQueueTitle(hash, songMetadata.title); return; } } catch { } } ((MonoBehaviour)this).StartCoroutine(FetchTitleCoroutine(hash, url)); } private IEnumerator FetchTitleCoroutine(string hash, string url) { titleFetchInProgress.Add(hash); if (string.IsNullOrEmpty(ytDlpPath) || !File.Exists(ytDlpPath)) { titleFetchInProgress.Remove(hash); yield break; } string fetched = ""; ProcessStartInfo processStartInfo = new ProcessStartInfo { FileName = ytDlpPath, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; processStartInfo.ArgumentList.Add("--no-playlist"); processStartInfo.ArgumentList.Add("--skip-download"); processStartInfo.ArgumentList.Add("--print"); processStartInfo.ArgumentList.Add("title-current:%(title)s"); processStartInfo.ArgumentList.Add("--js-runtimes"); processStartInfo.ArgumentList.Add(denoPath); processStartInfo.ArgumentList.Add(url); Process proc = new Process { StartInfo = processStartInfo }; proc.OutputDataReceived += delegate(object s, DataReceivedEventArgs e) { if (!string.IsNullOrEmpty(e.Data) && e.Data.StartsWith("title-current:")) { fetched = e.Data.Substring("title-current:".Length).Trim(); } }; proc.ErrorDataReceived += delegate { }; bool flag = false; try { flag = proc.Start(); } catch (Exception) { } if (!flag) { titleFetchInProgress.Remove(hash); yield break; } proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); float startTime = Time.realtimeSinceStartup; while (!proc.HasExited) { if (Time.realtimeSinceStartup - startTime > (float)TitleProbeTimeoutSec) { try { proc.Kill(); } catch { } break; } yield return null; } if (!string.IsNullOrWhiteSpace(fetched)) { UpdateQueueTitle(hash, fetched); try { File.WriteAllText(Path.Combine(tempAudioDir, "bingbong_" + hash + ".json"), JsonUtility.ToJson((object)new SongMetadata { title = fetched })); } catch { } } titleFetchInProgress.Remove(hash); } private void UpdateQueueTitle(string hash, string title) { bool flag = false; for (int i = 0; i < queue.Count; i++) { if (queue[i].hash == hash && string.IsNullOrEmpty(queue[i].title)) { queue[i].title = title; flag = true; } } if (flag) { BroadcastQueue(); } if (currentSongHash == hash && (string.IsNullOrEmpty(currentSongTitle) || currentSongTitle == "Unknown")) { currentSongTitle = title; } } } }
YoutubeDLSharp.dll
Decompiled 3 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using YoutubeDLSharp.Converters; using YoutubeDLSharp.Helpers; using YoutubeDLSharp.Metadata; using YoutubeDLSharp.Options; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("Bluegrams")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("© 2020-2026 Bluegrams")] [assembly: AssemblyDescription("\r\n\t\tA simple .NET wrapper library for youtube-dl and yt-dlp.\r\n\r\nNote: Package versions >= 1.0 are optimized to work with yt-dlp.\r\nPackage versions 0.x retain support for the original youtube-dl.\r\n\t")] [assembly: AssemblyFileVersion("1.2.0.265")] [assembly: AssemblyInformationalVersion("1.2.0+bfe03288151c994ad64401870ab4940635576fc5")] [assembly: AssemblyProduct("YoutubeDLSharp")] [assembly: AssemblyTitle("YoutubeDLSharp")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/Bluegrams/YoutubeDLSharp")] [assembly: AssemblyVersion("1.2.0.265")] namespace YoutubeDLSharp { public enum DownloadState { None, PreProcessing, Downloading, PostProcessing, Error, Success } public class DownloadProgress { public DownloadState State { get; } public float Progress { get; } public string TotalDownloadSize { get; } public string DownloadSpeed { get; } public string ETA { get; } public int VideoIndex { get; } public string Data { get; } public DownloadProgress(DownloadState status, float progress = 0f, string totalDownloadSize = null, string downloadSpeed = null, string eta = null, int index = 1, string data = null) { State = status; Progress = progress; TotalDownloadSize = totalDownloadSize; DownloadSpeed = downloadSpeed; ETA = eta; VideoIndex = index; Data = data; } } public class RunResult<T> { public bool Success { get; } public string[] ErrorOutput { get; } public T Data { get; } public RunResult(bool success, string[] error, T result) { Success = success; ErrorOutput = error; Data = result; } } public static class Utils { internal class FFmpegApi { public class Root { [JsonProperty("version")] public string Version { get; set; } [JsonProperty("permalink")] public string Permalink { get; set; } [JsonProperty("bin")] public Bin Bin { get; set; } } public class Bin { [JsonProperty("windows-64")] public OsBinVersion Windows64 { get; set; } [JsonProperty("linux-64")] public OsBinVersion Linux64 { get; set; } [JsonProperty("osx-64")] public OsBinVersion Osx64 { get; set; } } public class OsBinVersion { [JsonProperty("ffmpeg")] public string Ffmpeg { get; set; } [JsonProperty("ffprobe")] public string Ffprobe { get; set; } } public enum BinaryType { [EnumMember(Value = "ffmpeg")] FFmpeg, [EnumMember(Value = "ffprobe")] FFprobe } } private static readonly Regex rgxTimestamp = new Regex("[0-9]+(?::[0-9]+)+", RegexOptions.Compiled); private static readonly Dictionary<char, string> accentChars = "ÂÃÄÀÁÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖŐØŒÙÚÛÜŰÝÞßàáâãäåæçèéêëìíîïðñòóôõöőøœùúûüűýþÿ".Zip(new string[68] { "A", "A", "A", "A", "A", "A", "AE", "C", "E", "E", "E", "E", "I", "I", "I", "I", "D", "N", "O", "O", "O", "O", "O", "O", "O", "OE", "U", "U", "U", "U", "U", "Y", "P", "ss", "a", "a", "a", "a", "a", "a", "ae", "c", "e", "e", "e", "e", "i", "i", "i", "i", "o", "n", "o", "o", "o", "o", "o", "o", "o", "oe", "u", "u", "u", "u", "u", "y", "p", "y" }, (char c, string s) => new { Key = c, Val = s }).ToDictionary(o => o.Key, o => o.Val); private const string DENO_VERSION_URL = "https://dl.deno.land/release-latest.txt"; private const string DENO_DOWNLOAD_URL_TEMPLATE = "https://dl.deno.land/release/{0}/deno-{1}.zip"; public static int TimeoutSeconds { get; set; } = 300; public static string YtDlpBinaryName => GetYtDlpBinaryName(); public static string FfmpegBinaryName => GetFfmpegBinaryName(); public static string FfprobeBinaryName => GetFfprobeBinaryName(); public static string Sanitize(string s, bool restricted = false) { rgxTimestamp.Replace(s, (Match m) => m.Groups[0].Value.Replace(':', '_')); string text = string.Join("", s.Select((char c) => sanitizeChar(c, restricted))); text = text.Replace("__", "_").Trim(new char[1] { '_' }); if (restricted && text.StartsWith("-_")) { text = text.Substring(2); } if (text.StartsWith("-")) { text = "_" + text.Substring(1); } text = text.TrimStart(new char[1] { '.' }); if (string.IsNullOrWhiteSpace(text)) { text = "_"; } return text; } private static string sanitizeChar(char c, bool restricted) { if (restricted && accentChars.ContainsKey(c)) { return accentChars[c]; } if (c != '?' && c >= ' ') { switch (c) { case '\u007f': break; case '"': if (!restricted) { return "'"; } return ""; case ':': if (!restricted) { return " -"; } return "_-"; default: if (Enumerable.Contains("\\/|*<>", c)) { return "_"; } if (restricted && Enumerable.Contains("!&'()[]{}$;`^,# ", c)) { return "_"; } if (restricted && c > '\u007f') { return "_"; } return c.ToString(); } } return ""; } public static string GetFullPath(string fileName) { if (File.Exists(fileName)) { return Path.GetFullPath(fileName); } string[] array = Environment.GetEnvironmentVariable("PATH").Split(new char[1] { Path.PathSeparator }); for (int i = 0; i < array.Length; i++) { string text = Path.Combine(array[i], fileName); if (File.Exists(text)) { return text; } } return null; } public static async Task DownloadBinaries(bool skipExisting = true, string directoryPath = "", bool downloadJSRuntime = true) { if (skipExisting) { if (!File.Exists(Path.Combine(directoryPath, GetYtDlpBinaryName()))) { await DownloadYtDlp(directoryPath); } if (!File.Exists(Path.Combine(directoryPath, GetFfmpegBinaryName()))) { await DownloadFFmpeg(directoryPath); } if (!File.Exists(Path.Combine(directoryPath, GetFfprobeBinaryName()))) { await DownloadFFprobe(directoryPath); } if (downloadJSRuntime && !File.Exists(Path.Combine(directoryPath, GetDenoBinaryName()))) { await DownloadDeno(directoryPath); } } else { await DownloadYtDlp(directoryPath); await DownloadFFmpeg(directoryPath); await DownloadFFprobe(directoryPath); if (downloadJSRuntime) { await DownloadDeno(directoryPath); } } } private static string GetYtDlpBinaryName() { return Path.GetFileName(GetYtDlpDownloadUrl()); } private static string GetFfmpegBinaryName() { switch (OSHelper.GetOSVersion()) { case OSVersion.Windows: return "ffmpeg.exe"; case OSVersion.OSX: case OSVersion.Linux: return "ffmpeg"; default: throw new Exception("Your OS isn't supported"); } } private static string GetFfprobeBinaryName() { switch (OSHelper.GetOSVersion()) { case OSVersion.Windows: return "ffprobe.exe"; case OSVersion.OSX: case OSVersion.Linux: return "ffprobe"; default: throw new Exception("Your OS isn't supported"); } } private static string GetDenoBinaryName() { switch (OSHelper.GetOSVersion()) { case OSVersion.Windows: return "deno.exe"; case OSVersion.OSX: case OSVersion.Linux: return "deno"; default: throw new Exception("Your OS isn't supported"); } } private static string GetYtDlpDownloadUrl() { return OSHelper.GetOSVersion() switch { OSVersion.Windows => "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe", OSVersion.OSX => "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos", OSVersion.Linux => "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp", _ => throw new Exception("Your OS isn't supported"), }; } public static async Task DownloadYtDlp(string directoryPath = "") { string ytDlpDownloadUrl = GetYtDlpDownloadUrl(); if (string.IsNullOrEmpty(directoryPath)) { directoryPath = Directory.GetCurrentDirectory(); } string downloadLocation = Path.Combine(directoryPath, Path.GetFileName(ytDlpDownloadUrl)); File.WriteAllBytes(downloadLocation, await DownloadFileBytesAsync(ytDlpDownloadUrl)); SetUnixExecPerms(downloadLocation); } public static async Task DownloadFFmpeg(string directoryPath = "") { await FFDownloader(directoryPath); } public static async Task DownloadFFprobe(string directoryPath = "") { await FFDownloader(directoryPath, FFmpegApi.BinaryType.FFprobe); } private static void SetUnixExecPerms(string filename) { if (OSHelper.IsWindows) { return; } throw new PlatformNotSupportedException("Setting Unix permissions is not supported on this platform."); } private static HttpClient GetHttpClient() { HttpClient httpClient = null; if (httpClient == null) { httpClient = new HttpClient(); httpClient.Timeout = TimeSpan.FromSeconds(TimeoutSeconds); } return httpClient; } private static async Task FFDownloader(string directoryPath = "", FFmpegApi.BinaryType binary = FFmpegApi.BinaryType.FFmpeg) { HttpClient client = GetHttpClient(); if (string.IsNullOrEmpty(directoryPath)) { directoryPath = Directory.GetCurrentDirectory(); } FFmpegApi.Root root = JsonConvert.DeserializeObject<FFmpegApi.Root>(await (await client.GetAsync("https://ffbinaries.com/api/v1/version/latest")).Content.ReadAsStringAsync()); FFmpegApi.OsBinVersion osBinVersion = OSHelper.GetOSVersion() switch { OSVersion.Windows => root?.Bin.Windows64, OSVersion.OSX => root?.Bin.Osx64, OSVersion.Linux => root?.Bin.Linux64, _ => throw new NotImplementedException("Your OS isn't supported"), }; byte[] buffer = await DownloadFileBytesAsync((binary == FFmpegApi.BinaryType.FFmpeg) ? osBinVersion.Ffmpeg : osBinVersion.Ffprobe); string unixExecPerms = string.Empty; using (MemoryStream stream = new MemoryStream(buffer)) { using ZipArchive zipArchive = new ZipArchive(stream, ZipArchiveMode.Read); if (zipArchive.Entries.Count > 0) { unixExecPerms = Path.Combine(directoryPath, zipArchive.Entries[0].FullName); zipArchive.Entries[0].ExtractToFile(Path.Combine(directoryPath, zipArchive.Entries[0].FullName), overwrite: true); } } SetUnixExecPerms(unixExecPerms); client?.Dispose(); } private static async Task<byte[]> DownloadFileBytesAsync(string uri) { if (!Uri.TryCreate(uri, UriKind.Absolute, out Uri _)) { throw new InvalidOperationException("URI is invalid."); } HttpClient client = GetHttpClient(); byte[] result2 = await client.GetByteArrayAsync(uri); client?.Dispose(); return result2; } public static async Task DownloadDeno(string directoryPath = "") { HttpClient client = GetHttpClient(); if (string.IsNullOrEmpty(directoryPath)) { directoryPath = Directory.GetCurrentDirectory(); } string arg = (await (await client.GetAsync("https://dl.deno.land/release-latest.txt")).Content.ReadAsStringAsync()).Trim(); byte[] buffer = await DownloadFileBytesAsync(string.Format("https://dl.deno.land/release/{0}/deno-{1}.zip", arg, OSHelper.GetOSVersion() switch { OSVersion.Windows => "x86_64-pc-windows-msvc", OSVersion.OSX => "aarch64-apple-darwin", OSVersion.Linux => "x86_64-unknown-linux-gnu", _ => throw new NotImplementedException("Your OS isn't supported"), })); string unixExecPerms = string.Empty; using (MemoryStream stream = new MemoryStream(buffer)) { using ZipArchive zipArchive = new ZipArchive(stream, ZipArchiveMode.Read); if (zipArchive.Entries.Count > 0) { unixExecPerms = Path.Combine(directoryPath, zipArchive.Entries[0].FullName); zipArchive.Entries[0].ExtractToFile(Path.Combine(directoryPath, zipArchive.Entries[0].FullName), overwrite: true); } } SetUnixExecPerms(unixExecPerms); client?.Dispose(); } public static void EnsureSuccess<T>(this RunResult<T> runResult) { if (!runResult.Success) { throw new Exception("Download failed:\n" + string.Join("\n", runResult.ErrorOutput)); } } } public class YoutubeDL { private static readonly Regex rgxFile = new Regex("^outfile:\\s\\\"?(.*)\\\"?", RegexOptions.Compiled); private static Regex rgxFilePostProc = new Regex("\\[download\\] Destination: [a-zA-Z]:\\\\\\S+\\.\\S{3,}", RegexOptions.Compiled); protected ProcessRunner runner; public string YoutubeDLPath { get; set; } = Utils.YtDlpBinaryName; public string FFmpegPath { get; set; } = Utils.FfmpegBinaryName; public string OutputFolder { get; set; } = Environment.CurrentDirectory; public string OutputFileTemplate { get; set; } = "%(title)s [%(id)s].%(ext)s"; public bool RestrictFilenames { get; set; } public bool OverwriteFiles { get; set; } = true; public bool IgnoreDownloadErrors { get; set; } = true; public string PythonInterpreterPath { get; set; } public string Version => FileVersionInfo.GetVersionInfo(Utils.GetFullPath(YoutubeDLPath)).FileVersion; public YoutubeDL(byte maxNumberOfProcesses = 4) { runner = new ProcessRunner(maxNumberOfProcesses); } public async Task SetMaxNumberOfProcesses(byte count) { await runner.SetTotalCount(count); } public async Task<RunResult<string[]>> RunWithOptions(string[] urls, OptionSet options, CancellationToken ct) { List<string> output = new List<string>(); YoutubeDLProcess youtubeDLProcess = CreateYoutubeDLProcess(); youtubeDLProcess.OutputReceived += delegate(object o, DataReceivedEventArgs e) { output.Add(e.Data); }; var (num, error) = await runner.RunThrottled(youtubeDLProcess, urls, options, ct); return new RunResult<string[]>(num == 0, error, output.ToArray()); } public async Task<RunResult<string>> RunWithOptions(string url, OptionSet options, CancellationToken ct = default(CancellationToken), IProgress<DownloadProgress> progress = null, IProgress<string> output = null, bool showArgs = true) { string outFile = string.Empty; YoutubeDLProcess youtubeDLProcess = CreateYoutubeDLProcess(); if (showArgs) { output?.Report("Arguments: " + youtubeDLProcess.ConvertToArgs(new string[1] { url }, options) + "\n"); } else { output?.Report("Starting Download: " + url); } youtubeDLProcess.OutputReceived += delegate(object o, DataReceivedEventArgs e) { Match match = rgxFilePostProc.Match(e.Data); if (match.Success) { outFile = match.Groups[0].ToString().Replace("[download] Destination:", "").Replace(" ", ""); progress?.Report(new DownloadProgress(DownloadState.Success, 0f, null, null, null, 1, outFile)); } output?.Report(e.Data); }; var (num, error) = await runner.RunThrottled(youtubeDLProcess, new string[1] { url }, options, ct, progress); return new RunResult<string>(num == 0, error, outFile); } public async Task<string> RunUpdate() { string output = string.Empty; YoutubeDLProcess youtubeDLProcess = CreateYoutubeDLProcess(); youtubeDLProcess.OutputReceived += delegate(object o, DataReceivedEventArgs e) { output = e.Data; }; await youtubeDLProcess.RunAsync(null, new OptionSet { Update = true }); return output; } public async Task<RunResult<VideoData>> RunVideoDataFetch(string url, CancellationToken ct = default(CancellationToken), bool flat = true, bool fetchComments = false, OptionSet overrideOptions = null) { OptionSet optionSet = GetDownloadOptions(); optionSet.DumpSingleJson = true; optionSet.FlatPlaylist = flat; optionSet.WriteComments = fetchComments; if (overrideOptions != null) { optionSet = optionSet.OverrideOptions(overrideOptions); } VideoData videoData = null; YoutubeDLProcess process = CreateYoutubeDLProcess(); process.OutputReceived += delegate(object o, DataReceivedEventArgs e) { try { videoData = JsonConvert.DeserializeObject<VideoData>(e.Data); } catch (JsonSerializationException) { process.RedirectToError(e); } }; var (num, error) = await runner.RunThrottled(process, new string[1] { url }, optionSet, ct); return new RunResult<VideoData>(num == 0, error, videoData); } public async Task<RunResult<string>> RunVideoDownload(string url, string format = "bestvideo+bestaudio/best", DownloadMergeFormat mergeFormat = DownloadMergeFormat.Unspecified, VideoRecodeFormat recodeFormat = VideoRecodeFormat.None, CancellationToken ct = default(CancellationToken), IProgress<DownloadProgress> progress = null, IProgress<string> output = null, OptionSet overrideOptions = null) { OptionSet optionSet = GetDownloadOptions(); optionSet.Format = format; optionSet.MergeOutputFormat = mergeFormat; optionSet.RecodeVideo = recodeFormat; if (overrideOptions != null) { optionSet = optionSet.OverrideOptions(overrideOptions); } string outputFile = string.Empty; YoutubeDLProcess youtubeDLProcess = CreateYoutubeDLProcess(); output?.Report("Arguments: " + youtubeDLProcess.ConvertToArgs(new string[1] { url }, optionSet) + "\n"); youtubeDLProcess.OutputReceived += delegate(object o, DataReceivedEventArgs e) { Match match = rgxFile.Match(e.Data); if (match.Success) { outputFile = match.Groups[1].ToString().Trim(new char[1] { '"' }); progress?.Report(new DownloadProgress(DownloadState.Success, 0f, null, null, null, 1, outputFile)); } output?.Report(e.Data); }; var (num, error) = await runner.RunThrottled(youtubeDLProcess, new string[1] { url }, optionSet, ct, progress); return new RunResult<string>(num == 0, error, outputFile); } public async Task<RunResult<string[]>> RunVideoPlaylistDownload(string url, int? start = 1, int? end = null, int[] items = null, string format = "bestvideo+bestaudio/best", VideoRecodeFormat recodeFormat = VideoRecodeFormat.None, CancellationToken ct = default(CancellationToken), IProgress<DownloadProgress> progress = null, IProgress<string> output = null, OptionSet overrideOptions = null) { OptionSet optionSet = GetDownloadOptions(); optionSet.NoPlaylist = false; optionSet.PlaylistStart = start; optionSet.PlaylistEnd = end; if (items != null) { optionSet.PlaylistItems = string.Join(",", items); } optionSet.Format = format; optionSet.RecodeVideo = recodeFormat; if (overrideOptions != null) { optionSet = optionSet.OverrideOptions(overrideOptions); } List<string> outputFiles = new List<string>(); YoutubeDLProcess youtubeDLProcess = CreateYoutubeDLProcess(); output?.Report("Arguments: " + youtubeDLProcess.ConvertToArgs(new string[1] { url }, optionSet) + "\n"); youtubeDLProcess.OutputReceived += delegate(object o, DataReceivedEventArgs e) { Match match = rgxFile.Match(e.Data); if (match.Success) { string text = match.Groups[1].ToString().Trim(new char[1] { '"' }); outputFiles.Add(text); progress?.Report(new DownloadProgress(DownloadState.Success, 0f, null, null, null, 1, text)); } output?.Report(e.Data); }; var (num, error) = await runner.RunThrottled(youtubeDLProcess, new string[1] { url }, optionSet, ct, progress); return new RunResult<string[]>(num == 0, error, outputFiles.ToArray()); } public async Task<RunResult<string>> RunAudioDownload(string url, AudioConversionFormat format = AudioConversionFormat.Best, CancellationToken ct = default(CancellationToken), IProgress<DownloadProgress> progress = null, IProgress<string> output = null, OptionSet overrideOptions = null) { OptionSet optionSet = GetDownloadOptions(); optionSet.Format = "bestaudio/best"; optionSet.ExtractAudio = true; optionSet.AudioFormat = format; if (overrideOptions != null) { optionSet = optionSet.OverrideOptions(overrideOptions); } string outputFile = string.Empty; new List<string>(); YoutubeDLProcess youtubeDLProcess = CreateYoutubeDLProcess(); output?.Report("Arguments: " + youtubeDLProcess.ConvertToArgs(new string[1] { url }, optionSet) + "\n"); youtubeDLProcess.OutputReceived += delegate(object o, DataReceivedEventArgs e) { Match match = rgxFile.Match(e.Data); if (match.Success) { outputFile = match.Groups[1].ToString().Trim(new char[1] { '"' }); progress?.Report(new DownloadProgress(DownloadState.Success, 0f, null, null, null, 1, outputFile)); } output?.Report(e.Data); }; var (num, error) = await runner.RunThrottled(youtubeDLProcess, new string[1] { url }, optionSet, ct, progress); return new RunResult<string>(num == 0, error, outputFile); } public async Task<RunResult<string[]>> RunAudioPlaylistDownload(string url, int? start = 1, int? end = null, int[] items = null, AudioConversionFormat format = AudioConversionFormat.Best, CancellationToken ct = default(CancellationToken), IProgress<DownloadProgress> progress = null, IProgress<string> output = null, OptionSet overrideOptions = null) { List<string> outputFiles = new List<string>(); OptionSet optionSet = GetDownloadOptions(); optionSet.NoPlaylist = false; optionSet.PlaylistStart = start; optionSet.PlaylistEnd = end; if (items != null) { optionSet.PlaylistItems = string.Join(",", items); } optionSet.Format = "bestaudio/best"; optionSet.ExtractAudio = true; optionSet.AudioFormat = format; if (overrideOptions != null) { optionSet = optionSet.OverrideOptions(overrideOptions); } YoutubeDLProcess youtubeDLProcess = CreateYoutubeDLProcess(); output?.Report("Arguments: " + youtubeDLProcess.ConvertToArgs(new string[1] { url }, optionSet) + "\n"); youtubeDLProcess.OutputReceived += delegate(object o, DataReceivedEventArgs e) { Match match = rgxFile.Match(e.Data); if (match.Success) { string text = match.Groups[1].ToString().Trim(new char[1] { '"' }); outputFiles.Add(text); progress?.Report(new DownloadProgress(DownloadState.Success, 0f, null, null, null, 1, text)); } output?.Report(e.Data); }; var (num, error) = await runner.RunThrottled(youtubeDLProcess, new string[1] { url }, optionSet, ct, progress); return new RunResult<string[]>(num == 0, error, outputFiles.ToArray()); } protected virtual OptionSet GetDownloadOptions() { return new OptionSet { IgnoreErrors = IgnoreDownloadErrors, IgnoreConfig = true, NoPlaylist = true, Downloader = "m3u8:native", DownloaderArgs = "ffmpeg:-nostats -loglevel 0", Output = Path.Combine(OutputFolder, OutputFileTemplate), RestrictFilenames = RestrictFilenames, ForceOverwrites = OverwriteFiles, NoOverwrites = !OverwriteFiles, NoPart = true, FfmpegLocation = Utils.GetFullPath(FFmpegPath), Progress = true, Print = "after_move:outfile: %(filepath)s" }; } private YoutubeDLProcess CreateYoutubeDLProcess() { return new YoutubeDLProcess(YoutubeDLPath) { PythonPath = PythonInterpreterPath }; } } public class YoutubeDLProcess { private static readonly Regex rgxPlaylist = new Regex("Downloading video (\\d+) of (\\d+)", RegexOptions.Compiled); private static readonly Regex rgxProgress = new Regex("\\[download\\]\\s+(?:(?<percent>[\\d\\.]+)%(?:\\s+of\\s+\\~?\\s*(?<total>[\\d\\.\\w]+))?\\s+at\\s+(?:(?<speed>[\\d\\.\\w]+\\/s)|[\\w\\s]+)\\s+ETA\\s(?<eta>[\\d\\:]+))?", RegexOptions.Compiled); private static readonly Regex rgxPost = new Regex("\\[(\\w+)\\]\\s+", RegexOptions.Compiled); public string PythonPath { get; set; } public string ExecutablePath { get; set; } public event EventHandler<DataReceivedEventArgs> OutputReceived; public event EventHandler<DataReceivedEventArgs> ErrorReceived; public YoutubeDLProcess(string executablePath = "yt-dlp.exe") { ExecutablePath = executablePath; } internal string ConvertToArgs(string[] urls, OptionSet options) { return options.ToString() + " -- " + ((urls != null) ? string.Join(" ", urls.Select((string s) => "\"" + s + "\"")) : string.Empty); } internal void RedirectToError(DataReceivedEventArgs e) { this.ErrorReceived?.Invoke(this, e); } public async Task<int> RunAsync(string[] urls, OptionSet options) { return await RunAsync(urls, options, CancellationToken.None); } public async Task<int> RunAsync(string[] urls, OptionSet options, CancellationToken ct, IProgress<DownloadProgress> progress = null) { TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(); Process process = new Process(); ProcessStartInfo processStartInfo = new ProcessStartInfo { CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }; if (!string.IsNullOrEmpty(PythonPath)) { processStartInfo.FileName = PythonPath; processStartInfo.Arguments = "\"" + ExecutablePath + "\" " + ConvertToArgs(urls, options); } else { processStartInfo.FileName = ExecutablePath; processStartInfo.Arguments = ConvertToArgs(urls, options); } process.EnableRaisingEvents = true; process.StartInfo = processStartInfo; TaskCompletionSource<bool> tcsOut = new TaskCompletionSource<bool>(); bool isDownloading = false; process.OutputDataReceived += delegate(object o, DataReceivedEventArgs e) { if (e.Data == null) { tcsOut.SetResult(result: true); } else { Match match; if ((match = rgxProgress.Match(e.Data)).Success) { if (match.Groups.Count > 1 && match.Groups[1].Length > 0) { float progress2 = float.Parse(match.Groups[1].ToString(), CultureInfo.InvariantCulture) / 100f; Group obj = match.Groups["total"]; string totalDownloadSize = (obj.Success ? obj.Value : null); Group obj2 = match.Groups["speed"]; string downloadSpeed = (obj2.Success ? obj2.Value : null); Group obj3 = match.Groups["eta"]; string eta = (obj3.Success ? obj3.Value : null); progress?.Report(new DownloadProgress(DownloadState.Downloading, progress2, totalDownloadSize, downloadSpeed, eta)); } else { progress?.Report(new DownloadProgress(DownloadState.Downloading)); } isDownloading = true; } else if ((match = rgxPlaylist.Match(e.Data)).Success) { int index = int.Parse(match.Groups[1].Value); progress?.Report(new DownloadProgress(DownloadState.PreProcessing, 0f, null, null, null, index)); isDownloading = false; } else if (isDownloading && (match = rgxPost.Match(e.Data)).Success) { progress?.Report(new DownloadProgress(DownloadState.PostProcessing, 1f)); isDownloading = false; } this.OutputReceived?.Invoke(this, e); } }; TaskCompletionSource<bool> tcsError = new TaskCompletionSource<bool>(); process.ErrorDataReceived += delegate(object o, DataReceivedEventArgs e) { if (e.Data == null) { tcsError.SetResult(result: true); } else { progress?.Report(new DownloadProgress(DownloadState.Error, 0f, null, null, null, 1, e.Data)); this.ErrorReceived?.Invoke(this, e); } }; process.Exited += async delegate { await tcsOut.Task; await tcsError.Task; tcs.TrySetResult(process.ExitCode); process.Dispose(); }; ct.Register(delegate { if (!tcs.Task.IsCompleted) { tcs.TrySetCanceled(); } try { if (!process.HasExited) { process.KillTree(); } } catch { } }); if (!(await Task.Run(() => process.Start()))) { tcs.TrySetException(new InvalidOperationException("Failed to start yt-dlp process.")); } process.BeginOutputReadLine(); process.BeginErrorReadLine(); progress?.Report(new DownloadProgress(DownloadState.PreProcessing)); return await tcs.Task; } } } namespace YoutubeDLSharp.Options { public enum DownloadMergeFormat { Unspecified, Mp4, Mkv, Ogg, Webm, Flv } public enum AudioConversionFormat { Best, Aac, Flac, Mp3, M4a, Opus, Vorbis, Wav } public enum VideoRecodeFormat { None, Mp4, Mkv, Ogg, Webm, Flv, Avi } public interface IOption { string DefaultOptionString { get; } string[] OptionStrings { get; } bool IsSet { get; } bool IsCustom { get; } void SetFromString(string s); IEnumerable<string> ToStringCollection(); } public class MultiOption<T> : IOption { private MultiValue<T> value; public string DefaultOptionString => OptionStrings.Last(); public string[] OptionStrings { get; } public bool IsSet { get; private set; } public bool IsCustom { get; } public MultiValue<T> Value { get { return value; } set { IsSet = !object.Equals(value, default(T)); this.value = value; } } public MultiOption(params string[] optionStrings) { OptionStrings = optionStrings; IsSet = false; } public MultiOption(bool isCustom, params string[] optionStrings) { OptionStrings = optionStrings; IsSet = false; IsCustom = isCustom; } public void SetFromString(string s) { string[] array = s.Split(new char[1] { ' ' }); string text = s.Substring(array[0].Length).Trim(); if (typeof(T) != typeof(StringVals)) { text = text.Trim(new char[1] { '"' }); } if (!OptionStrings.Contains(array[0])) { throw new ArgumentException("Given string does not match required format."); } T val = Utils.OptionValueFromString<T>(text); if (!IsSet) { Value = val; } else { Value.Values.Add(val); } } public override string ToString() { return string.Join(" ", ToStringCollection()); } public IEnumerable<string> ToStringCollection() { if (!IsSet) { return new string[1] { "" }; } List<string> list = new List<string>(); foreach (T value in Value.Values) { list.Add(DefaultOptionString + Utils.OptionValueToString(value)); } return list; } } public class MultiValue<T> { private readonly List<T> values; public List<T> Values => values; public MultiValue(params T[] values) { this.values = values.ToList(); } public static implicit operator MultiValue<T>(T value) { return new MultiValue<T>(value); } public static implicit operator MultiValue<T>(T[] values) { return new MultiValue<T>(values); } public static explicit operator T(MultiValue<T> value) { if (value.Values.Count == 1) { return value.Values[0]; } throw new InvalidCastException($"Cannot cast sequence of values to {typeof(T)}."); } public static explicit operator T[](MultiValue<T> value) { return value.Values.ToArray(); } } public class Option<T> : IOption { private T value; public string DefaultOptionString => OptionStrings.First(); public string[] OptionStrings { get; } public bool IsSet { get; private set; } public T Value { get { return value; } set { IsSet = !object.Equals(value, default(T)); this.value = value; } } public bool IsCustom { get; } public Option(params string[] optionStrings) { OptionStrings = optionStrings; IsSet = false; } public Option(bool isCustom, params string[] optionStrings) { OptionStrings = optionStrings; IsSet = false; IsCustom = isCustom; } public void SetFromString(string s) { string[] array = s.Split(new char[1] { ' ' }); string text = s.Substring(array[0].Length).Trim(); if (typeof(T) != typeof(StringVals)) { text = text.Trim(new char[1] { '"' }); } if (!OptionStrings.Contains(array[0])) { throw new ArgumentException("Given string does not match required format."); } Value = Utils.OptionValueFromString<T>(text); } public override string ToString() { if (!IsSet) { return string.Empty; } string text = Utils.OptionValueToString(Value); return DefaultOptionString + text; } public IEnumerable<string> ToStringCollection() { return new string[1] { ToString() }; } } internal class OptionComparer : IEqualityComparer<IOption> { public bool Equals(IOption x, IOption y) { if (x != null) { if (y != null) { return x.ToString().Equals(y.ToString()); } return false; } return y == null; } public int GetHashCode(IOption obj) { return obj.ToString().GetHashCode(); } } public class OptionSet : ICloneable { private Option<string> username = new Option<string>("-u", "--username"); private Option<string> password = new Option<string>("-p", "--password"); private Option<string> twoFactor = new Option<string>("-2", "--twofactor"); private Option<bool> netrc = new Option<bool>("-n", "--netrc"); private Option<string> netrcLocation = new Option<string>("--netrc-location"); private Option<string> netrcCmd = new Option<string>("--netrc-cmd"); private Option<string> videoPassword = new Option<string>("--video-password"); private Option<string> apMso = new Option<string>("--ap-mso"); private Option<string> apUsername = new Option<string>("--ap-username"); private Option<string> apPassword = new Option<string>("--ap-password"); private Option<bool> apListMso = new Option<bool>("--ap-list-mso"); private Option<string> clientCertificate = new Option<string>("--client-certificate"); private Option<string> clientCertificateKey = new Option<string>("--client-certificate-key"); private Option<string> clientCertificatePassword = new Option<string>("--client-certificate-password"); private static readonly OptionComparer Comparer = new OptionComparer(); public static readonly OptionSet Default = new OptionSet(); private Option<bool> getDescription = new Option<bool>("--get-description"); private Option<bool> getDuration = new Option<bool>("--get-duration"); private Option<bool> getFilename = new Option<bool>("--get-filename"); private Option<bool> getFormat = new Option<bool>("--get-format"); private Option<bool> getId = new Option<bool>("--get-id"); private Option<bool> getThumbnail = new Option<bool>("--get-thumbnail"); private Option<bool> getTitle = new Option<bool>("-e", "--get-title"); private Option<bool> getUrl = new Option<bool>("-g", "--get-url"); private Option<string> matchTitle = new Option<string>("--match-title"); private Option<string> rejectTitle = new Option<string>("--reject-title"); private Option<long?> minViews = new Option<long?>("--min-views"); private Option<long?> maxViews = new Option<long?>("--max-views"); private Option<bool> breakOnReject = new Option<bool>("--break-on-reject"); private Option<string> userAgent = new Option<string>("--user-agent"); private Option<string> referer = new Option<string>("--referer"); private Option<int?> playlistStart = new Option<int?>("--playlist-start"); private Option<int?> playlistEnd = new Option<int?>("--playlist-end"); private Option<bool> playlistReverse = new Option<bool>("--playlist-reverse"); private Option<bool> noColors = new Option<bool>("--no-colors"); private Option<bool> forceGenericExtractor = new Option<bool>("--force-generic-extractor"); private Option<string> execBeforeDownload = new Option<string>("--exec-before-download"); private Option<bool> noExecBeforeDownload = new Option<bool>("--no-exec-before-download"); private Option<bool> allFormats = new Option<bool>("--all-formats"); private Option<bool> allSubs = new Option<bool>("--all-subs"); private Option<bool> printJson = new Option<bool>("--print-json"); private Option<string> autonumberSize = new Option<string>("--autonumber-size"); private Option<int?> autonumberStart = new Option<int?>("--autonumber-start"); private Option<bool> id = new Option<bool>("--id"); private Option<string> metadataFromTitle = new Option<string>("--metadata-from-title"); private Option<bool> hlsPreferNative = new Option<bool>("--hls-prefer-native"); private Option<bool> hlsPreferFfmpeg = new Option<bool>("--hls-prefer-ffmpeg"); private Option<bool> listFormatsOld = new Option<bool>("--list-formats-old", "--no-list-formats-as-table"); private Option<bool> listFormatsAsTable = new Option<bool>("--list-formats-as-table", "--no-list-formats-old"); private Option<bool> youtubeSkipDashManifest = new Option<bool>("--youtube-skip-dash-manifest", "--no-youtube-include-dash-manifest"); private Option<bool> youtubeSkipHlsManifest = new Option<bool>("--youtube-skip-hls-manifest", "--no-youtube-include-hls-manifest"); private Option<bool> geoBypass = new Option<bool>("--geo-bypass"); private Option<bool> noGeoBypass = new Option<bool>("--no-geo-bypass"); private Option<string> geoBypassCountry = new Option<string>("--geo-bypass-country"); private Option<string> geoBypassIpBlock = new Option<string>("--geo-bypass-ip-block"); private Option<int?> concurrentFragments = new Option<int?>("-N", "--concurrent-fragments"); private Option<long?> limitRate = new Option<long?>("-r", "--limit-rate"); private Option<long?> throttledRate = new Option<long?>("--throttled-rate"); private Option<int?> retries = new Option<int?>("-R", "--retries"); private Option<int?> fileAccessRetries = new Option<int?>("--file-access-retries"); private Option<int?> fragmentRetries = new Option<int?>("--fragment-retries"); private MultiOption<string> retrySleep = new MultiOption<string>("--retry-sleep"); private Option<bool> skipUnavailableFragments = new Option<bool>("--skip-unavailable-fragments", "--no-abort-on-unavailable-fragments"); private Option<bool> abortOnUnavailableFragments = new Option<bool>("--abort-on-unavailable-fragments", "--no-skip-unavailable-fragments"); private Option<bool> keepFragments = new Option<bool>("--keep-fragments"); private Option<bool> noKeepFragments = new Option<bool>("--no-keep-fragments"); private Option<long?> bufferSize = new Option<long?>("--buffer-size"); private Option<bool> resizeBuffer = new Option<bool>("--resize-buffer"); private Option<bool> noResizeBuffer = new Option<bool>("--no-resize-buffer"); private Option<long?> httpChunkSize = new Option<long?>("--http-chunk-size"); private Option<bool> playlistRandom = new Option<bool>("--playlist-random"); private Option<bool> lazyPlaylist = new Option<bool>("--lazy-playlist"); private Option<bool> noLazyPlaylist = new Option<bool>("--no-lazy-playlist"); private Option<bool> hlsUseMpegts = new Option<bool>("--hls-use-mpegts"); private Option<bool> noHlsUseMpegts = new Option<bool>("--no-hls-use-mpegts"); private MultiOption<string> downloadSections = new MultiOption<string>("--download-sections"); private MultiOption<string> downloader = new MultiOption<string>("--downloader", "--external-downloader"); private MultiOption<string> downloaderArgs = new MultiOption<string>("--downloader-args", "--external-downloader-args"); private Option<int?> extractorRetries = new Option<int?>("--extractor-retries"); private Option<bool> allowDynamicMpd = new Option<bool>("--allow-dynamic-mpd", "--no-ignore-dynamic-mpd"); private Option<bool> ignoreDynamicMpd = new Option<bool>("--ignore-dynamic-mpd", "--no-allow-dynamic-mpd"); private Option<bool> hlsSplitDiscontinuity = new Option<bool>("--hls-split-discontinuity"); private Option<bool> noHlsSplitDiscontinuity = new Option<bool>("--no-hls-split-discontinuity"); private MultiOption<string> extractorArgs = new MultiOption<string>("--extractor-args"); private Option<string> batchFile = new Option<string>("-a", "--batch-file"); private Option<bool> noBatchFile = new Option<bool>("--no-batch-file"); private MultiOption<string> paths = new MultiOption<string>("-P", "--paths"); private Option<string> output = new Option<string>("-o", "--output"); private Option<string> outputNaPlaceholder = new Option<string>("--output-na-placeholder"); private Option<bool> restrictFilenames = new Option<bool>("--restrict-filenames"); private Option<bool> noRestrictFilenames = new Option<bool>("--no-restrict-filenames"); private Option<bool> windowsFilenames = new Option<bool>("--windows-filenames"); private Option<bool> noWindowsFilenames = new Option<bool>("--no-windows-filenames"); private Option<int?> trimFilenames = new Option<int?>("--trim-filenames"); private Option<bool> noOverwrites = new Option<bool>("-w", "--no-overwrites"); private Option<bool> forceOverwrites = new Option<bool>("--force-overwrites"); private Option<bool> noForceOverwrites = new Option<bool>("--no-force-overwrites"); private Option<bool> doContinue = new Option<bool>("-c", "--continue"); private Option<bool> noContinue = new Option<bool>("--no-continue"); private Option<bool> part = new Option<bool>("--part"); private Option<bool> noPart = new Option<bool>("--no-part"); private Option<bool> mtime = new Option<bool>("--mtime"); private Option<bool> noMtime = new Option<bool>("--no-mtime"); private Option<bool> writeDescription = new Option<bool>("--write-description"); private Option<bool> noWriteDescription = new Option<bool>("--no-write-description"); private Option<bool> writeInfoJson = new Option<bool>("--write-info-json"); private Option<bool> noWriteInfoJson = new Option<bool>("--no-write-info-json"); private Option<bool> writePlaylistMetafiles = new Option<bool>("--write-playlist-metafiles"); private Option<bool> noWritePlaylistMetafiles = new Option<bool>("--no-write-playlist-metafiles"); private Option<bool> cleanInfoJson = new Option<bool>("--clean-info-json"); private Option<bool> noCleanInfoJson = new Option<bool>("--no-clean-info-json"); private Option<bool> writeComments = new Option<bool>("--write-comments", "--get-comments"); private Option<bool> noWriteComments = new Option<bool>("--no-write-comments", "--no-get-comments"); private Option<string> loadInfoJson = new Option<string>("--load-info-json"); private Option<string> cookies = new Option<string>("--cookies"); private Option<bool> noCookies = new Option<bool>("--no-cookies"); private Option<string> cookiesFromBrowser = new Option<string>("--cookies-from-browser"); private Option<bool> noCookiesFromBrowser = new Option<bool>("--no-cookies-from-browser"); private Option<string> cacheDir = new Option<string>("--cache-dir"); private Option<bool> noCacheDir = new Option<bool>("--no-cache-dir"); private Option<bool> removeCacheDir = new Option<bool>("--rm-cache-dir"); private Option<bool> help = new Option<bool>("-h", "--help"); private Option<bool> version = new Option<bool>("--version"); private Option<bool> update = new Option<bool>("-U", "--update"); private Option<bool> noUpdate = new Option<bool>("--no-update"); private Option<string> updateTo = new Option<string>("--update-to"); private Option<bool> ignoreErrors = new Option<bool>("-i", "--ignore-errors"); private Option<bool> noAbortOnError = new Option<bool>("--no-abort-on-error"); private Option<bool> abortOnError = new Option<bool>("--abort-on-error", "--no-ignore-errors"); private Option<bool> listExtractors = new Option<bool>("--list-extractors"); private Option<bool> extractorDescriptions = new Option<bool>("--extractor-descriptions"); private Option<string> useExtractors = new Option<string>("--use-extractors", "--ies"); private Option<string> defaultSearch = new Option<string>("--default-search"); private Option<bool> ignoreConfig = new Option<bool>("--ignore-config", "--no-config"); private Option<bool> noConfigLocations = new Option<bool>("--no-config-locations"); private MultiOption<string> configLocations = new MultiOption<string>("--config-locations"); private MultiOption<string> pluginDirs = new MultiOption<string>("--plugin-dirs"); private Option<bool> noPluginDirs = new Option<bool>("--no-plugin-dirs"); private MultiOption<string> jsRuntimes = new MultiOption<string>("--js-runtimes"); private Option<bool> noJsRuntimes = new Option<bool>("--no-js-runtimes"); private MultiOption<string> remoteComponents = new MultiOption<string>("--remote-components"); private Option<bool> noRemoteComponents = new Option<bool>("--no-remote-components"); private Option<bool> flatPlaylist = new Option<bool>("--flat-playlist"); private Option<bool> noFlatPlaylist = new Option<bool>("--no-flat-playlist"); private Option<bool> liveFromStart = new Option<bool>("--live-from-start"); private Option<bool> noLiveFromStart = new Option<bool>("--no-live-from-start"); private Option<string> waitForVideo = new Option<string>("--wait-for-video"); private Option<bool> noWaitForVideo = new Option<bool>("--no-wait-for-video"); private Option<bool> markWatched = new Option<bool>("--mark-watched"); private Option<bool> noMarkWatched = new Option<bool>("--no-mark-watched"); private MultiOption<string> color = new MultiOption<string>("--color"); private Option<string> compatOptions = new Option<string>("--compat-options"); private MultiOption<StringVals> alias = new MultiOption<StringVals>("--alias"); private MultiOption<string> presetAlias = new MultiOption<string>("-t", "--preset-alias"); private Option<string> geoVerificationProxy = new Option<string>("--geo-verification-proxy"); private Option<string> xff = new Option<string>("--xff"); private Option<bool> writeLink = new Option<bool>("--write-link"); private Option<bool> writeUrlLink = new Option<bool>("--write-url-link"); private Option<bool> writeWeblocLink = new Option<bool>("--write-webloc-link"); private Option<bool> writeDesktopLink = new Option<bool>("--write-desktop-link"); private Option<string> proxy = new Option<string>("--proxy"); private Option<int?> socketTimeout = new Option<int?>("--socket-timeout"); private Option<string> sourceAddress = new Option<string>("--source-address"); private Option<string> impersonate = new Option<string>("--impersonate"); private Option<bool> listImpersonateTargets = new Option<bool>("--list-impersonate-targets"); private Option<bool> forceIPv4 = new Option<bool>("-4", "--force-ipv4"); private Option<bool> forceIPv6 = new Option<bool>("-6", "--force-ipv6"); private Option<bool> enableFileUrls = new Option<bool>("--enable-file-urls"); private Option<bool> extractAudio = new Option<bool>("-x", "--extract-audio"); private Option<AudioConversionFormat> audioFormat = new Option<AudioConversionFormat>("--audio-format"); private Option<byte?> audioQuality = new Option<byte?>("--audio-quality"); private Option<string> remuxVideo = new Option<string>("--remux-video"); private Option<VideoRecodeFormat> recodeVideo = new Option<VideoRecodeFormat>("--recode-video"); private MultiOption<string> postprocessorArgs = new MultiOption<string>("--postprocessor-args", "--ppa"); private Option<bool> keepVideo = new Option<bool>("-k", "--keep-video"); private Option<bool> noKeepVideo = new Option<bool>("--no-keep-video"); private Option<bool> postOverwrites = new Option<bool>("--post-overwrites"); private Option<bool> noPostOverwrites = new Option<bool>("--no-post-overwrites"); private Option<bool> embedSubs = new Option<bool>("--embed-subs"); private Option<bool> noEmbedSubs = new Option<bool>("--no-embed-subs"); private Option<bool> embedThumbnail = new Option<bool>("--embed-thumbnail"); private Option<bool> noEmbedThumbnail = new Option<bool>("--no-embed-thumbnail"); private Option<bool> embedMetadata = new Option<bool>("--embed-metadata", "--add-metadata"); private Option<bool> noEmbedMetadata = new Option<bool>("--no-embed-metadata", "--no-add-metadata"); private Option<bool> embedChapters = new Option<bool>("--embed-chapters", "--add-chapters"); private Option<bool> noEmbedChapters = new Option<bool>("--no-embed-chapters", "--no-add-chapters"); private Option<bool> embedInfoJson = new Option<bool>("--embed-info-json"); private Option<bool> noEmbedInfoJson = new Option<bool>("--no-embed-info-json"); private Option<string> parseMetadata = new Option<string>("--parse-metadata"); private MultiOption<StringVals> replaceInMetadata = new MultiOption<StringVals>("--replace-in-metadata"); private Option<bool> xattrs = new Option<bool>("--xattrs"); private Option<string> concatPlaylist = new Option<string>("--concat-playlist"); private Option<string> fixup = new Option<string>("--fixup"); private Option<string> ffmpegLocation = new Option<string>("--ffmpeg-location"); private MultiOption<string> exec = new MultiOption<string>("--exec"); private Option<bool> noExec = new Option<bool>("--no-exec"); private Option<string> convertSubs = new Option<string>("--convert-subs"); private Option<string> convertThumbnails = new Option<string>("--convert-thumbnails"); private Option<bool> splitChapters = new Option<bool>("--split-chapters"); private Option<bool> noSplitChapters = new Option<bool>("--no-split-chapters"); private MultiOption<string> removeChapters = new MultiOption<string>("--remove-chapters"); private Option<bool> noRemoveChapters = new Option<bool>("--no-remove-chapters"); private Option<bool> forceKeyframesAtCuts = new Option<bool>("--force-keyframes-at-cuts"); private Option<bool> noForceKeyframesAtCuts = new Option<bool>("--no-force-keyframes-at-cuts"); private MultiOption<string> usePostprocessor = new MultiOption<string>("--use-postprocessor"); private Option<string> sponsorblockMark = new Option<string>("--sponsorblock-mark"); private Option<string> sponsorblockRemove = new Option<string>("--sponsorblock-remove"); private Option<string> sponsorblockChapterTitle = new Option<string>("--sponsorblock-chapter-title"); private Option<bool> noSponsorblock = new Option<bool>("--no-sponsorblock"); private Option<string> sponsorblockApi = new Option<string>("--sponsorblock-api"); private Option<bool> writeSubs = new Option<bool>("--write-subs"); private Option<bool> noWriteSubs = new Option<bool>("--no-write-subs"); private Option<bool> writeAutoSubs = new Option<bool>("--write-auto-subs", "--write-automatic-subs"); private Option<bool> noWriteAutoSubs = new Option<bool>("--no-write-auto-subs", "--no-write-automatic-subs"); private Option<bool> listSubs = new Option<bool>("--list-subs"); private Option<string> subFormat = new Option<string>("--sub-format"); private Option<string> subLangs = new Option<string>("--sub-langs"); private Option<bool> writeThumbnail = new Option<bool>("--write-thumbnail"); private Option<bool> noWriteThumbnail = new Option<bool>("--no-write-thumbnail"); private Option<bool> writeAllThumbnails = new Option<bool>("--write-all-thumbnails"); private Option<bool> listThumbnails = new Option<bool>("--list-thumbnails"); private Option<bool> quiet = new Option<bool>("-q", "--quiet"); private Option<bool> noQuiet = new Option<bool>("--no-quiet"); private Option<bool> noWarnings = new Option<bool>("--no-warnings"); private Option<bool> simulate = new Option<bool>("-s", "--simulate"); private Option<bool> noSimulate = new Option<bool>("--no-simulate"); private Option<bool> ignoreNoFormatsError = new Option<bool>("--ignore-no-formats-error"); private Option<bool> noIgnoreNoFormatsError = new Option<bool>("--no-ignore-no-formats-error"); private Option<bool> skipDownload = new Option<bool>("--skip-download", "--no-download"); private MultiOption<string> print = new MultiOption<string>("-O", "--print"); private MultiOption<string> printToFile = new MultiOption<string>("--print-to-file"); private Option<bool> dumpJson = new Option<bool>("-j", "--dump-json"); private Option<bool> dumpSingleJson = new Option<bool>("-J", "--dump-single-json"); private Option<bool> forceWriteArchive = new Option<bool>("--force-write-archive", "--force-download-archive"); private Option<bool> newline = new Option<bool>("--newline"); private Option<bool> noProgress = new Option<bool>("--no-progress"); private Option<bool> progress = new Option<bool>("--progress"); private Option<bool> consoleTitle = new Option<bool>("--console-title"); private Option<string> progressTemplate = new Option<string>("--progress-template"); private Option<string> progressDelta = new Option<string>("--progress-delta"); private Option<bool> verbose = new Option<bool>("-v", "--verbose"); private Option<bool> dumpPages = new Option<bool>("--dump-pages"); private Option<bool> writePages = new Option<bool>("--write-pages"); private Option<bool> printTraffic = new Option<bool>("--print-traffic"); private Option<string> format = new Option<string>("-f", "--format"); private Option<string> formatSort = new Option<string>("-S", "--format-sort"); private Option<bool> formatSortForce = new Option<bool>("--format-sort-force", "--S-force"); private Option<bool> noFormatSortForce = new Option<bool>("--no-format-sort-force"); private Option<bool> videoMultistreams = new Option<bool>("--video-multistreams"); private Option<bool> noVideoMultistreams = new Option<bool>("--no-video-multistreams"); private Option<bool> audioMultistreams = new Option<bool>("--audio-multistreams"); private Option<bool> noAudioMultistreams = new Option<bool>("--no-audio-multistreams"); private Option<bool> preferFreeFormats = new Option<bool>("--prefer-free-formats"); private Option<bool> noPreferFreeFormats = new Option<bool>("--no-prefer-free-formats"); private Option<bool> checkFormats = new Option<bool>("--check-formats"); private Option<bool> checkAllFormats = new Option<bool>("--check-all-formats"); private Option<bool> noCheckFormats = new Option<bool>("--no-check-formats"); private Option<bool> listFormats = new Option<bool>("-F", "--list-formats"); private Option<DownloadMergeFormat> mergeOutputFormat = new Option<DownloadMergeFormat>("--merge-output-format"); private Option<string> playlistItems = new Option<string>("-I", "--playlist-items"); private Option<string> minFilesize = new Option<string>("--min-filesize"); private Option<string> maxFilesize = new Option<string>("--max-filesize"); private Option<DateTime> date = new Option<DateTime>("--date"); private Option<DateTime> dateBefore = new Option<DateTime>("--datebefore"); private Option<DateTime> dateAfter = new Option<DateTime>("--dateafter"); private MultiOption<string> matchFilters = new MultiOption<string>("--match-filters"); private Option<bool> noMatchFilters = new Option<bool>("--no-match-filters"); private Option<string> breakMatchFilters = new Option<string>("--break-match-filters"); private Option<bool> noBreakMatchFilters = new Option<bool>("--no-break-match-filters"); private Option<bool> noPlaylist = new Option<bool>("--no-playlist"); private Option<bool> yesPlaylist = new Option<bool>("--yes-playlist"); private Option<byte?> ageLimit = new Option<byte?>("--age-limit"); private Option<string> downloadArchive = new Option<string>("--download-archive"); private Option<bool> noDownloadArchive = new Option<bool>("--no-download-archive"); private Option<int?> maxDownloads = new Option<int?>("--max-downloads"); private Option<bool> breakOnExisting = new Option<bool>("--break-on-existing"); private Option<bool> noBreakOnExisting = new Option<bool>("--no-break-on-existing"); private Option<bool> breakPerInput = new Option<bool>("--break-per-input"); private Option<bool> noBreakPerInput = new Option<bool>("--no-break-per-input"); private Option<int?> skipPlaylistAfterErrors = new Option<int?>("--skip-playlist-after-errors"); private Option<string> encoding = new Option<string>("--encoding"); private Option<bool> legacyServerConnect = new Option<bool>("--legacy-server-connect"); private Option<bool> noCheckCertificates = new Option<bool>("--no-check-certificates"); private Option<bool> preferInsecure = new Option<bool>("--prefer-insecure"); private MultiOption<string> addHeaders = new MultiOption<string>("--add-headers"); private Option<bool> bidiWorkaround = new Option<bool>("--bidi-workaround"); private Option<int?> sleepRequests = new Option<int?>("--sleep-requests"); private Option<int?> sleepInterval = new Option<int?>("--sleep-interval", "--min-sleep-interval"); private Option<int?> maxSleepInterval = new Option<int?>("--max-sleep-interval"); private Option<int?> sleepSubtitles = new Option<int?>("--sleep-subtitles"); public string Username { get { return username.Value; } set { username.Value = value; } } public string Password { get { return password.Value; } set { password.Value = value; } } public string TwoFactor { get { return twoFactor.Value; } set { twoFactor.Value = value; } } public bool Netrc { get { return netrc.Value; } set { netrc.Value = value; } } public string NetrcLocation { get { return netrcLocation.Value; } set { netrcLocation.Value = value; } } public string NetrcCmd { get { return netrcCmd.Value; } set { netrcCmd.Value = value; } } public string VideoPassword { get { return videoPassword.Value; } set { videoPassword.Value = value; } } public string ApMso { get { return apMso.Value; } set { apMso.Value = value; } } public string ApUsername { get { return apUsername.Value; } set { apUsername.Value = value; } } public string ApPassword { get { return apPassword.Value; } set { apPassword.Value = value; } } public bool ApListMso { get { return apListMso.Value; } set { apListMso.Value = value; } } public string ClientCertificate { get { return clientCertificate.Value; } set { clientCertificate.Value = value; } } public string ClientCertificateKey { get { return clientCertificateKey.Value; } set { clientCertificateKey.Value = value; } } public string ClientCertificatePassword { get { return clientCertificatePassword.Value; } set { clientCertificatePassword.Value = value; } } public IOption[] CustomOptions { get; set; } = new IOption[0]; [Obsolete("Deprecated in favor of: --print description.")] public bool GetDescription { get { return getDescription.Value; } set { getDescription.Value = value; } } [Obsolete("Deprecated in favor of: --print duration_string.")] public bool GetDuration { get { return getDuration.Value; } set { getDuration.Value = value; } } [Obsolete("Deprecated in favor of: --print filename.")] public bool GetFilename { get { return getFilename.Value; } set { getFilename.Value = value; } } [Obsolete("Deprecated in favor of: --print format.")] public bool GetFormat { get { return getFormat.Value; } set { getFormat.Value = value; } } [Obsolete("Deprecated in favor of: --print id.")] public bool GetId { get { return getId.Value; } set { getId.Value = value; } } [Obsolete("Deprecated in favor of: --print thumbnail.")] public bool GetThumbnail { get { return getThumbnail.Value; } set { getThumbnail.Value = value; } } [Obsolete("Deprecated in favor of: --print title.")] public bool GetTitle { get { return getTitle.Value; } set { getTitle.Value = value; } } [Obsolete("Deprecated in favor of: --print urls.")] public bool GetUrl { get { return getUrl.Value; } set { getUrl.Value = value; } } [Obsolete("Deprecated in favor of: --match-filter \"title ~= (?i)REGEX\".")] public string MatchTitle { get { return matchTitle.Value; } set { matchTitle.Value = value; } } [Obsolete("Deprecated in favor of: --match-filter \"title !~= (?i)REGEX\".")] public string RejectTitle { get { return rejectTitle.Value; } set { rejectTitle.Value = value; } } [Obsolete("Deprecated in favor of: --match-filter \"view_count >=? COUNT\".")] public long? MinViews { get { return minViews.Value; } set { minViews.Value = value; } } [Obsolete("Deprecated in favor of: --match-filter \"view_count <=? COUNT\".")] public long? MaxViews { get { return maxViews.Value; } set { maxViews.Value = value; } } [Obsolete("Deprecated in favor of: Use --break-match-filter.")] public bool BreakOnReject { get { return breakOnReject.Value; } set { breakOnReject.Value = value; } } [Obsolete("Deprecated in favor of: --add-header \"User-Agent:UA\".")] public string UserAgent { get { return userAgent.Value; } set { userAgent.Value = value; } } [Obsolete("Deprecated in favor of: --add-header \"Referer:URL\".")] public string Referer { get { return referer.Value; } set { referer.Value = value; } } [Obsolete("Deprecated in favor of: -I NUMBER:.")] public int? PlaylistStart { get { return playlistStart.Value; } set { playlistStart.Value = value; } } [Obsolete("Deprecated in favor of: -I :NUMBER.")] public int? PlaylistEnd { get { return playlistEnd.Value; } set { playlistEnd.Value = value; } } [Obsolete("Deprecated in favor of: -I ::-1.")] public bool PlaylistReverse { get { return playlistReverse.Value; } set { playlistReverse.Value = value; } } [Obsolete("Deprecated in favor of: --color no_color.")] public bool NoColors { get { return noColors.Value; } set { noColors.Value = value; } } [Obsolete("Deprecated in favor of: --ies generic,default.")] public bool ForceGenericExtractor { get { return forceGenericExtractor.Value; } set { forceGenericExtractor.Value = value; } } [Obsolete("Deprecated in favor of: --exec \"before_dl:CMD\".")] public string ExecBeforeDownload { get { return execBeforeDownload.Value; } set { execBeforeDownload.Value = value; } } [Obsolete("Deprecated in favor of: --no-exec.")] public bool NoExecBeforeDownload { get { return noExecBeforeDownload.Value; } set { noExecBeforeDownload.Value = value; } } [Obsolete("Deprecated in favor of: -f all.")] public bool AllFormats { get { return allFormats.Value; } set { allFormats.Value = value; } } [Obsolete("Deprecated in favor of: --sub-langs all --write-subs.")] public bool AllSubs { get { return allSubs.Value; } set { allSubs.Value = value; } } [Obsolete("Deprecated in favor of: -j --no-simulate.")] public bool PrintJson { get { return printJson.Value; } set { printJson.Value = value; } } [Obsolete("Deprecated in favor of: Use string formatting, e.g. %(autonumber)03d.")] public string AutonumberSize { get { return autonumberSize.Value; } set { autonumberSize.Value = value; } } [Obsolete("Deprecated in favor of: Use internal field formatting like %(autonumber+NUMBER)s.")] public int? AutonumberStart { get { return autonumberStart.Value; } set { autonumberStart.Value = value; } } [Obsolete("Deprecated in favor of: -o \"%(id)s.%(ext)s\".")] public bool Id { get { return id.Value; } set { id.Value = value; } } [Obsolete("Deprecated in favor of: --parse-metadata \"%(title)s:FORMAT\".")] public string MetadataFromTitle { get { return metadataFromTitle.Value; } set { metadataFromTitle.Value = value; } } [Obsolete("Deprecated in favor of: --downloader \"m3u8:native\".")] public bool HlsPreferNative { get { return hlsPreferNative.Value; } set { hlsPreferNative.Value = value; } } [Obsolete("Deprecated in favor of: --downloader \"m3u8:ffmpeg\".")] public bool HlsPreferFfmpeg { get { return hlsPreferFfmpeg.Value; } set { hlsPreferFfmpeg.Value = value; } } [Obsolete("Deprecated in favor of: --compat-options list-formats (Alias: --no-list-formats-as-table).")] public bool ListFormatsOld { get { return listFormatsOld.Value; } set { listFormatsOld.Value = value; } } [Obsolete("Deprecated in favor of: --compat-options -list-formats [Default] (Alias: --no-list-formats-old).")] public bool ListFormatsAsTable { get { return listFormatsAsTable.Value; } set { listFormatsAsTable.Value = value; } } [Obsolete("Deprecated in favor of: --extractor-args \"youtube:skip=dash\" (Alias: --no-youtube-include-dash-manifest).")] public bool YoutubeSkipDashManifest { get { return youtubeSkipDashManifest.Value; } set { youtubeSkipDashManifest.Value = value; } } [Obsolete("Deprecated in favor of: --extractor-args \"youtube:skip=hls\" (Alias: --no-youtube-include-hls-manifest).")] public bool YoutubeSkipHlsManifest { get { return youtubeSkipHlsManifest.Value; } set { youtubeSkipHlsManifest.Value = value; } } [Obsolete("Deprecated in favor of: --xff \"default\".")] public bool GeoBypass { get { return geoBypass.Value; } set { geoBypass.Value = value; } } [Obsolete("Deprecated in favor of: --xff \"never\".")] public bool NoGeoBypass { get { return noGeoBypass.Value; } set { noGeoBypass.Value = value; } } [Obsolete("Deprecated in favor of: --xff CODE.")] public string GeoBypassCountry { get { return geoBypassCountry.Value; } set { geoBypassCountry.Value = value; } } [Obsolete("Deprecated in favor of: --xff IP_BLOCK.")] public string GeoBypassIpBlock { get { return geoBypassIpBlock.Value; } set { geoBypassIpBlock.Value = value; } } public int? ConcurrentFragments { get { return concurrentFragments.Value; } set { concurrentFragments.Value = value; } } public long? LimitRate { get { return limitRate.Value; } set { limitRate.Value = value; } } public long? ThrottledRate { get { return throttledRate.Value; } set { throttledRate.Value = value; } } public int? Retries { get { return retries.Value; } set { retries.Value = value; } } public int? FileAccessRetries { get { return fileAccessRetries.Value; } set { fileAccessRetries.Value = value; } } public int? FragmentRetries { get { return fragmentRetries.Value; } set { fragmentRetries.Value = value; } } public MultiValue<string> RetrySleep { get { return retrySleep.Value; } set { retrySleep.Value = value; } } public bool SkipUnavailableFragments { get { return skipUnavailableFragments.Value; } set { skipUnavailableFragments.Value = value; } } public bool AbortOnUnavailableFragments { get { return abortOnUnavailableFragments.Value; } set { abortOnUnavailableFragments.Value = value; } } public bool KeepFragments { get { return keepFragments.Value; } set { keepFragments.Value = value; } } public bool NoKeepFragments { get { return noKeepFragments.Value; } set { noKeepFragments.Value = value; } } public long? BufferSize { get { return bufferSize.Value; } set { bufferSize.Value = value; } } public bool ResizeBuffer { get { return resizeBuffer.Value; } set { resizeBuffer.Value = value; } } public bool NoResizeBuffer { get { return noResizeBuffer.Value; } set { noResizeBuffer.Value = value; } } public long? HttpChunkSize { get { return httpChunkSize.Value; } set { httpChunkSize.Value = value; } } public bool PlaylistRandom { get { return playlistRandom.Value; } set { playlistRandom.Value = value; } } public bool LazyPlaylist { get { return lazyPlaylist.Value; } set { lazyPlaylist.Value = value; } } public bool NoLazyPlaylist { get { return noLazyPlaylist.Value; } set { noLazyPlaylist.Value = value; } } public bool HlsUseMpegts { get { return hlsUseMpegts.Value; } set { hlsUseMpegts.Value = value; } } public bool NoHlsUseMpegts { get { return noHlsUseMpegts.Value; } set { noHlsUseMpegts.Value = value; } } public MultiValue<string> DownloadSections { get { return downloadSections.Value; } set { downloadSections.Value = value; } } public MultiValue<string> Downloader { get { return downloader.Value; } set { downloader.Value = value; } } public MultiValue<string> DownloaderArgs { get { return downloaderArgs.Value; } set { downloaderArgs.Value = value; } } public int? ExtractorRetries { get { return extractorRetries.Value; } set { extractorRetries.Value = value; } } public bool AllowDynamicMpd { get { return allowDynamicMpd.Value; } set { allowDynamicMpd.Value = value; } } public bool IgnoreDynamicMpd { get { return ignoreDynamicMpd.Value; } set { ignoreDynamicMpd.Value = value; } } public bool HlsSplitDiscontinuity { get { return hlsSplitDiscontinuity.Value; } set { hlsSplitDiscontinuity.Value = value; } } public bool NoHlsSplitDiscontinuity { get { return noHlsSplitDiscontinuity.Value; } set { noHlsSplitDiscontinuity.Value = value; } } public MultiValue<string> ExtractorArgs { get { return extractorArgs.Value; } set { extractorArgs.Value = value; } } public string BatchFile { get { return batchFile.Value; } set { batchFile.Value = value; } } public bool NoBatchFile { get { return noBatchFile.Value; } set { noBatchFile.Value = value; } } public MultiValue<string> Paths { get { return paths.Value; } set { paths.Value = value; } } public string Output { get { return output.Value; } set { output.Value = value; } } public string OutputNaPlaceholder { get { return outputNaPlaceholder.Value; } set { outputNaPlaceholder.Value = value; } } public bool RestrictFilenames { get { return restrictFilenames.Value; } set { restrictFilenames.Value = value; } } public bool NoRestrictFilenames { get { return noRestrictFilenames.Value; } set { noRestrictFilenames.Value = value; } } public bool WindowsFilenames { get { return windowsFilenames.Value; } set { windowsFilenames.Value = value; } } public bool NoWindowsFilenames { get { return noWindowsFilenames.Value; } set { noWindowsFilenames.Value = value; } } public int? TrimFilenames { get { return trimFilenames.Value; } set { trimFilenames.Value = value; } } public bool NoOverwrites { get { return noOverwrites.Value; } set { noOverwrites.Value = value; } } public bool ForceOverwrites { get { return forceOverwrites.Value; } set { forceOverwrites.Value = value; } } public bool NoForceOverwrites { get { return noForceOverwrites.Value; } set { noForceOverwrites.Value = value; } } public bool Continue { get { return doContinue.Value; } set { doContinue.Value = value; } } public bool NoContinue { get { return noContinue.Value; } set { noContinue.Value = value; } } public bool Part { get { return part.Value; } set { part.Value = value; } } public bool NoPart { get { return noPart.Value; } set { noPart.Value = value; } } public bool Mtime { get { return mtime.Value; } set { mtime.Value = value; } } public bool NoMtime { get { return noMtime.Value; } set { noMtime.Value = value; } } public bool WriteDescription { get { return writeDescription.Value; } set { writeDescription.Value = value; } } public bool NoWriteDescription { get { return noWriteDescription.Value; } set { noWriteDescription.Value = value; } } public bool WriteInfoJson { get { return writeInfoJson.Value; } set { writeInfoJson.Value = value; } } public bool NoWriteInfoJson { get { return noWriteInfoJson.Value; } set { noWriteInfoJson.Value = value; } } public bool WritePlaylistMetafiles { get { return writePlaylistMetafiles.Value; } set { writePlaylistMetafiles.Value = value; } } public bool NoWritePlaylistMetafiles { get { return noWritePlaylistMetafiles.Value; } set { noWritePlaylistMetafiles.Value = value; } } public bool CleanInfoJson { get { return cleanInfoJson.Value; } set { cleanInfoJson.Value = value; } } public bool NoCleanInfoJson { get { return noCleanInfoJson.Value; } set { noCleanInfoJson.Value = value; } } public bool WriteComments { get { return writeComments.Value; } set { writeComments.Value = value; } } public bool NoWriteComments { get { return noWriteComments.Value; } set { noWriteComments.Value = value; } } public string LoadInfoJson { get { return loadInfoJson.Value; } set { loadInfoJson.Value = value; } } public string Cookies { get { return cookies.Value; } set { cookies.Value = value; } } public bool NoCookies { get { return noCookies.Value; } set { noCookies.Value = value; } } public string CookiesFromBrowser { get { return cookiesFromBrowser.Value; } set { cookiesFromBrowser.Value = value; } } public bool NoCookiesFromBrowser { get { return noCookiesFromBrowser.Value; } set { noCookiesFromBrowser.Value = value; } } public string CacheDir { get { return cacheDir.Value; } set { cacheDir.Value = value; } } public bool NoCacheDir { get { return noCacheDir.Value; } set { noCacheDir.Value = value; } } public bool RemoveCacheDir { get { return removeCacheDir.Value; } set { removeCacheDir.Value = value; } } public bool Help { get { return help.Value; } set { help.Value = value; } } public bool Version { get { return version.Value; } set { version.Value = value; } } public bool Update { get { return update.Value; } set { update.Value = value; } } public bool NoUpdate { get { return noUpdate.Value; } set { noUpdate.Value = value; } } public string UpdateTo { get { return updateTo.Value; } set { updateTo.Value = value; } } public bool IgnoreErrors { get { return ignoreErrors.Value; } set { ignoreErrors.Value = value; } } public bool NoAbortOnError { get { return noAbortOnError.Value; } set { noAbortOnError.Value = value; } } public bool AbortOnError { get { return abortOnError.Value; } set { abortOnError.Value = value; } } public bool ListExtractors { get { return listExtractors.Value; } set { listExtractors.Value = value; } } public bool ExtractorDescriptions { get { return extractorDescriptions.Value; } set { extractorDescriptions.Value = value; } } public string UseExtractors { get { return useExtractors.Value; } set { useExtractors.Value = value; } } public string DefaultSearch { get { return defaultSearch.Value; } set { defaultSearch.Value = value; } } public bool IgnoreConfig { get { return ignoreConfig.Value; } set { ignoreConfig.Value = value; } } public bool NoConfigLocations { get { return noConfigLocations.Value; } set { noConfigLocations.Value = value; } } public MultiValue<string> ConfigLocations { get { return configLocations.Value; } set { configLocations.Value = value; } } public MultiValue<string> PluginDirs { get { return pluginDirs.Value; } set { pluginDirs.Value = value; } } public bool NoPluginDirs { get { return noPluginDirs.Value; } set { noPluginDirs.Value = value; } } public MultiValue<string> JsRuntimes { get { return jsRuntimes.Value; } set { jsRuntimes.Value = value; } } public bool NoJsRuntimes { get { return noJsRuntimes.Value; } set { noJsRuntimes.Value = value; } } public MultiValue<string> RemoteComponents { get { return remoteComponents.Value; } set { remoteComponents.Value = value; } } public bool NoRemoteComponents { get { return noRemoteComponents.Value; } set { noRemoteComponents.Value = value; } } public bool FlatPlaylist { get { return flatPlaylist.Value; } set { flatPlaylist.Value = value; } } public bool NoFlatPlaylist { get { return noFlatPlaylist.Value; } set { noFlatPlaylist.Value = value; } } public bool LiveFromStart { get { return liveFromStart.Value; } set { liveFromStart.Value = value; } } public bool NoLiveFromStart { get { return noLiveFromStart.Value; } set { noLiveFromStart.Value = value; } } public string WaitForVideo { get { return waitForVideo.Value; } set { waitForVideo.Value = value; } } public bool NoWaitForVideo { get { return noWaitForVideo.Value; } set { noWaitForVideo.Value = value; } } public bool MarkWatched { get { return markWatched.Value; } set { markWatched.Value = value; } } public bool NoMarkWatched { get { return noMarkWatched.Value; } set { noMarkWatched.Value = value; } } public MultiValue<string> Color { get { return color.Value; } set { color.Value = value; } } public string CompatOptions { get { return compatOptions.Value; } set { compatOptions.Value = value; } } public MultiValue<StringVals> Alias { get { return alias.Value; } set { alias.Value = value; } } public MultiValue<string> PresetAlias { get { return presetAlias.Value; } set { presetAlias.Value = value; } } public string GeoVerificationProxy { get { return geoVerificationProxy.Value; } set { geoVerificationProxy.Value = value; } } public string Xff { get { return xff.Value; } set { xff.Value = value; } } public bool WriteLink { get { return writeLink.Value; } set { writeLink.Value = value; } } public bool WriteUrlLink { get { return writeUrlLink.Value; } set { writeUrlLink.Value = value; } } public bool WriteWeblocLink { get { return writeWeblocLink.Value; } set { writeWeblocLink.Value = value; } } public bool WriteDesktopLink { get { return writeDesktopLink.Value; } set { writeDesktopLink.Value = value; } } public string Proxy { get { return proxy.Value; } set { proxy.Value = value; } } public int? SocketTimeout { get { return socketTimeout.Value; } set { socketTimeout.Value = value; } } public string SourceAddress { get { return sourceAddress.Value; } set { sourceAddress.Value = value; } } public string Impersonate { get { return impersonate.Value; } set { impersonate.Value = value; } } public bool ListImpersonateTargets { get { return listImpersonateTargets.Value; } set { listImpersonateTargets.Value = value; } } public bool ForceIPv4 { get { return forceIPv4.Value; } set { forceIPv4.Value = value; } } public bool ForceIPv6 { get { return forceIPv6.Value; } set { forceIPv6.Value = value; } } public bool EnableFileUrls { get { return enableFileUrls.Value; } set { enableFileUrls.Value = value; } } public bool ExtractAudio { get { return extractAudio.Value; } set { extractAudio.Value = value; } } public AudioConversionFormat AudioFormat { get { return audioFormat.Value; } set { audioFormat.Value = value; } } public byte? AudioQuality { get { return audioQuality.Value; } set { audioQuality.Value = value; } } public string RemuxVideo { get { return remuxVideo.Value; } set { remuxVideo.Value = value; } } public VideoRecodeFormat RecodeVideo { get { return recodeVideo.Value; } set { recodeVideo.Value = value; } } public MultiValue<string> PostprocessorArgs { get { return postprocessorArgs.Value; } set { postprocessorArgs.Value = value; } } public bool KeepVideo { get { return keepVideo.Value; } set { keepVideo.Value = value; } } public bool NoKeepVideo { get { return noKeepVideo.Value; } set { noKeepVideo.Value = value; } } public bool PostOverwrites { get { return postOverwrites.Value; } set { postOverwrites.Value = value; } } public bool NoPostOverwrites { get { return noPostOverwrites.Value; } set { noPostOverwrites.Value = value; } } public bool EmbedSubs { get { return embedSubs.Value; } set { embedSubs.Value = value; } } public bool NoEmbedSubs { get { return noEmbedSubs.Value; } set { noEmbedSubs.Value = value; } } public bool EmbedThumbnail { get { return embedThumbnail.Value; } set { embedThumbnail.Value = value; } } public bool NoEmbedThumbnail { get { return noEmbedThumbnail.Value; } set { noEmbedThumbnail.Value = value; } } public bool EmbedMetadata { get { return embedMetadata.Value; } set { embedMetadata.Value = value; } } public bool NoEmbedMetadata { get { return noEmbedMetadata.Value; } set { noEmbedMetadata.Value = value; } } public bool EmbedChapters { get { return embedChapters.Value; } set { embedChapters.Value = value; } } public bool NoEmbedChapters { get { return noEmbedChapters.Value; } set { noEmbedChapters.Value = value; } } public bool EmbedInfoJson { get { return embedInfoJson.Value; } set { embedInfoJson.Value = value; } } public bool NoEmbedInfoJson { get { return noEmbedInfoJson.Value; } set { noEmbedInfoJson.Value = value; } } public string ParseMetadata { get { return parseMetadata.Value; } set { parseMetadata.Value = value; } } public MultiValue<StringVals> ReplaceInMetadata { get { return replaceInMetadata.Value; } set { replaceInMetadata.Value = value; } } public bool Xattrs { get { return xattrs.Value; } set { xattrs.Value = value; } } public string ConcatPlaylist { get { return concatPlaylist.Value; } set { concatPlaylist.Value = value; } } public string Fixup { get { return fixup.Value; } set { fixup.Value = value; } } public string FfmpegLocation { get { return ffmpegLocation.Value; } set { ffmpegLocation.Value = value; } } public MultiValue<string> Exec { get { return exec.Value; } set { exec.Value = value; } } public bool NoExec { get { return noExec.Value; } set { noExec.Value = value; } } public string ConvertSubs { get { return convertSubs.Value; } set { convertSubs.Value = value; } } public string ConvertThumbnails { get { return convertThumbnails.Value; } set { convertThumbnails.Value = value; } } public bool SplitChapters { get { return splitChapters.Value; } set { splitChapters.Value = value; } } public bool NoSplitChapters { get { return noSplitChapters.Value; } set { noSplitChapters.Value = value; } } public MultiValue<string> RemoveChapters { get { return removeChapters.Value; } set { removeChapters.Value = value; } } public bool NoRemoveChapters { get { return noRemoveChapters.Value; } set { noRemoveChapters.Value = value; } } public bool ForceKeyframesAtCuts { get { return forceKeyframesAtCuts.Value; } set { forceKeyframesAtCuts.Value = value; } } public bool NoForceKeyframesAtCuts { get { return noForceKeyframesAtCuts.Value; } set { noForceKeyframesAtCuts.Value = value; } } public MultiValue<string> UsePostprocessor { get { return usePostprocessor.Value; } set { usePostprocessor.Value = value; } } public string SponsorblockMark { get { return sponsorblockMark.Value; } set { sponsorblockMark.Value = value; } } public string SponsorblockRemove { get { return sponsorblockRemove.Value; } set { sponsorblockRemove.Value = value; } } public string SponsorblockChapterTitle { get { return sponsorblockChapterTitle.Value; } set { sponsorblockChapterTitle.Value = value; } } public bool NoSponsorblock { get { return noSponsorblock.Value; } set { noSponsorblock.Value = value; } } public string SponsorblockApi { get { return sponsorblockApi.Value; } set { sponsorblockApi.Value = value; } } public bool WriteSubs { get { return writeSubs.Value; } set { writeSubs.Value = value; } } public bool NoWriteSubs { get { return noWriteSubs.Value; } set { noWriteSubs.Value = value; } } public bool WriteAutoSubs { get { return writeAutoSubs.Value; } set { writeAutoSubs.Value = value; } } public bool NoWriteAutoSubs { get { return noWriteAutoSubs.Value; } set { noWriteAutoSubs.Value = value; } } public bool ListSubs { get { return listSubs.Value; } set { listSubs.Value = value; } } public string SubFormat { get { return subFormat.Value; } set { subFormat.Value = value; } } public string SubLangs { get { return subLangs.Value; } set { subLangs.Value = value; } } public bool WriteThumbnail { get { return writeThumbnail.Value; } set { writeThumbnail.Value = value; } } public bool NoWriteThumbnail { get { return noWriteThumbnail.Value; } set { noWriteThumbnail.Value = value; } } public bool WriteAllThumbnails { get { return writeAllThumbnails.Value; } set { writeAllThumbnails.Value = value; } } public bool ListThumbnails { get { return listThumbnails.Value; } set { listThumbnails.Value = value; } } public bool Quiet { get { return quiet.Value; } set { quiet.Value = value; } } public bool NoQuiet { get { return noQuiet.Value; } set { noQuiet.Value = value; } } public bool NoWarnings { get { return noWarnings.Value; } set { noWarnings.Value = value; } } public bool Simulate { get { return simulate.Value; } set { simulate.Value = value; } } public bool NoSimulate { get { return noSimulate.Value; } set { noSimulate.Value = value; } } public bool IgnoreNoFormatsError { get { return ignoreNoFormatsError.Value; } set { ignoreNoFormatsError.Value = value; } } public bool NoIgnoreNoFormatsError { get { return noIgnoreNoFormatsError.Value; } set { noIgnoreNoFormatsError.Value = value; } } public bool SkipDownload { get { return skipDownload.Value; } set { skipDownload.Value = value; } } public MultiValue<string> Print { get { return print.Value; } set { print.Value = value; } } public MultiValue<string> PrintToFile { get { return printToFile.Value; } set { printToFile.Value = value; } } public bool DumpJson { get { return dumpJson.Value; } set { dumpJson.Value = value; } } public bool DumpSingleJson { get { return dumpSingleJson.Value; } set { dumpSingleJson.Value = value; } } public bool ForceWriteArchive { get { return forceWriteArchive.Value; } set { forceWriteArchive.Value = value; } } public bool Newline { get { return newline.Value; } set { newline.Value = value; } } public bool NoProgress { get { return noProgress.Value; } set { noProgress.Value = value; } } public bool Progress { get { return progress.Value; } set { progress.Value = value; } } public bool ConsoleTitle { get { return consoleTitle.Value; } set { consoleTitle.Value = value; } } public string ProgressTemplate { get { return progressTemplate.Value; } set { progressTemplate.Value = value; } } public string ProgressDelta { get { return progressDelta.Value; } set { progressDelta.Value = value; } } public bool Verbose { get { return verbose.Value; } set { verbose.Value = value; } } public bool DumpPages { get { return dumpPages.Value; } set { dumpPages.Value = value; } } public bool WritePages { get { return writePages.Value; } set { writePages.Value = value; } } public bool PrintTraffic { get { return printTraffic.Value; } set { printTraffic.Value = value; } } public string Format { get { return format.Value; } set { format.Value = value; } } public string FormatSort { get { return formatSort.Value; } set { formatSort.Value = value; } } public bool FormatSortForce { get { return formatSortForce.Value; } set { formatSortForce.Value = value; } } public bool NoFormatSortForce { get { return noFormatSortForce.Value; } set { noFormatSortForce.Value = value; } } public bool VideoMultistreams { get { return videoMultistreams.Value; } set { videoMultistreams.Value = value; } } public bool NoVideoMultistreams { get { return noVideoMultistreams.Value; } set { noVideoMultistreams.Value = value; } } public bool AudioMultistreams { get { return audioMultistreams.Value; } set { audioMultistreams.Value = value; } } public bool NoAudioMultistreams { get { return noAudioMultistreams.Value; } set { noAudioMultistreams.Value = value; } } public bool PreferFreeFormats { get { return preferFreeFormats.Value; } set { preferFreeFormats.Value = value; } } public bool NoPreferFreeFormats { get { return noPreferFreeFormats.Value; } set { noPreferFreeFormats.Value = value; } } public bool CheckFormats { get { return checkFormats.Value; } set { checkFormats.Value = value; } } public bool CheckAllFormats { get { return checkAllFormats.Value; } set { checkAllFormats.Value = value; } } public bool NoCheckFormats { get { return noCheckFormats.Value; } set { noCheckFormats.Value = value; } } public bool ListFormats { get { return listFormats.Value; } set { listFormats.Value = value; } } public DownloadMergeFormat MergeOutputFormat { get { return mergeOutputFormat.Value; } set { mergeOutputFormat.Value = value; } } public string PlaylistItems { get { return playlistItems.Value; } set { playlistItems.Value = value; } } public string MinFilesize { get { return minFilesize.Value; } set { minFilesize.Value = value; } } public string MaxFilesize { get { return maxFilesize.Value; } set { maxFilesize.Value = value; } } public DateTime Date { get { return date.Value; } set { date.Value = value; } } public DateTime DateBefore { get { return dateBefore.Value; } set { dateBefore.Value = value; } } public DateTime DateAfter { get { return dateAfter.Value; } set { dateAfter.Value = value; } } public MultiValue<string> MatchFilters { get { return matchFilters.Value; } set { matchFilters.Value = value; } } public bool NoMatchFilters { get { return noMatchFilters.Value; } set { noMatchFilters.Value = value; } } public string BreakMatchFilters { get { return breakMatchFilters.Value; } set { breakMatchFilters.Value = value; } } public bool NoBreakMatchFilters { get { return noBreakMatchFilters.Value; } set { noBreakMatchFilters.Value = value; } } public bool NoPlaylist { get { return noPlaylist.Value; } set { noPlaylist.Value = value; } } public bool YesPlaylist { get { return yesPlaylist.Value; } set { yesPlaylist.Value = value; } } public byte? AgeLimit { get { return ageLimit.Value; } set { ageLimit.Value = value; } } public string DownloadArchive { get { return downloadArchive.Value; } set { downloadArchive.Value = value; } } public bool NoDownloadArchive { get { return noDownloadArchive.Value; } set { noDownloadArchive.Value = value; } } public int? MaxDownloads { get { return maxDownloads.Value; } set { maxDownloads.Value = value; } } public bool BreakOnExisting { get { return breakOnExisting.Value; } set { breakOnExisting.Value = value; } } public bool NoBreakOnExisting { get { return noBreakOnExisting.Value; } set { noBreakOnExisting.Value = value; } } public bool BreakPerInput { get { return breakPerInput.Value; } set { breakPerInput.Value = value; } } public bool NoBreakPerInput { get { return noBreakPerInput.Value; } set { noBreakPerInput.Value = value; } } public int? SkipPlaylistAfterErrors { get { return skipPlaylistAfterErrors.Value; } set { skipPlaylistAfterErrors.Value = value; } } public string Encoding { get { return encoding.Value; } set { encoding.Value = value; } } public bool LegacyServerConnect { get { return legacyServerConnect.Value; } set { legacyServerConnect.Value = value; } } public bool NoCheckCertificates { get { return noCheckCertificates.Value; } set { noCheckCertificates.Value = value; } } public bool PreferInsecure { get { return preferInsecure.Value; } set { preferInsecure.Value = value; } } public MultiValue<string> AddHeaders { get { return addHeaders.Value; } set { addHeaders.Value = value; } } public bool BidiWorkaround { get { return bidiWorkaround.Value; } set { bidiWorkaround.Value = value; } } public int? SleepRequests { get { return sleepRequests.Value; } set { sleepRequests.Value = value; } } public int? SleepInterval { get { return sleepInterval.Value; } set { sleepInterval.Value = value; } } public int? MaxSleepInterval { get { return maxSleepInterval.Value; } set { maxSleepInterval.Value = value; } } public int? SleepSubtitles { get { return sleepSubtitles.Value; } set { sleepSubtitles.Value = value; } } public void WriteConfigFile(string path) { File.WriteAllLines(path, GetOptionFlags()); } public override string ToString() { return " " + string.Join(" ", GetOptionFlags()); } public IEnumerable<string> GetOptionFlags() { return from value in GetKnownOptions().Concat(CustomOptions).SelectMany((IOption opt) => opt.ToStringCollection()) where !string.IsNullOrWhiteSpace(value) select value; } internal IEnumerable<IOption> GetKnownOptions() { return (from p in GetType().GetRuntimeFields() where p.FieldType.IsGenericType && p.FieldType.GetInterfaces().Contains<Type>(typeof(IOption)) select p.GetValue(this)).Cast<IOption>(); } public OptionSet OverrideOptions(OptionSet overrideOptions, bool forceOverride = false) { OptionSet optionSet = (OptionSet)Clone(); optionSet.CustomOptions = optionSet.CustomOptions.Concat(overrideOptions.CustomOptions).Distinct(Comparer).ToArray(); foreach (FieldInfo item in from p in overrideOptions.GetType().GetRuntimeFields() where p.FieldType.IsGenericType && p.FieldType.GetInterfaces().Contains<Type>(typeof(IOption)) select p) { IOption option = (IOption)item.GetValue(overrideOptions); if (forceOverride || option.IsSet) { optionSet.GetType().GetField(item.Name, BindingFlags.Instance | BindingFlags.NonPublic).SetValue(optionSet, option); } } return optionSet; } public static OptionSet FromString(IEnumerable<string> lines) { OptionSet optionSet = new OptionSet(); IOption[] customOptions = (from option in GetOptions(lines, optionSet.GetKnownOptions()) where option.IsCustom select option).ToArray(); optionSet.CustomOptions = customOptions; return optionSet; } private static IEnumerable<IOption> GetOptions(IEnumerable<string> lines, IEnumerable<IOption> options) { IEnumerable<IOption> knownOptions = options.ToList(); foreach (string line in lines) { string text = line.Trim(); if (!text.StartsWith("#") && !string.IsNullOrWhiteSpace(text)) { string[] array = text.Split(new char[1] { ' ' }); string flag = array[0]; IOption option = knownOptions.FirstOrDefault((IOption o) => o.OptionStrings.Contains(flag)