private IEnumerator DownloadVideoCoroutine(VideoConfig video, VideoQuality.Mode quality)
        {
            Log.Info($"Starting download of {video.title}");

            _downloadLog = "";

            var downloadProcess = CreateDownloadProcess(video, quality);

            if (downloadProcess == null)
            {
                yield break;
            }

            video.DownloadState = DownloadState.Downloading;
            DownloadProgress?.Invoke(video);

            Log.Info(
                $"youtube-dl command: \"{downloadProcess.StartInfo.FileName}\" {downloadProcess.StartInfo.Arguments}");

            var timeout = new Timeout(5 * 60);

            downloadProcess.OutputDataReceived += (sender, e) =>
                                                  UnityMainThreadTaskScheduler.Factory.StartNew(delegate { DownloadOutputDataReceived(e, video); });

            downloadProcess.ErrorDataReceived += (sender, e) =>
                                                 UnityMainThreadTaskScheduler.Factory.StartNew(delegate { DownloadErrorDataReceived(e); });

            downloadProcess.Exited += (sender, e) =>
                                      UnityMainThreadTaskScheduler.Factory.StartNew(delegate { DownloadProcessExited((Process)sender, video); });

            downloadProcess.Disposed += (sender, e) =>
                                        UnityMainThreadTaskScheduler.Factory.StartNew(delegate { DownloadProcessDisposed((Process)sender, e); });

            StartProcessThreaded(downloadProcess);
            var startProcessTimeout = new Timeout(10);

            yield return(new WaitUntil(() => IsProcessRunning(downloadProcess) || startProcessTimeout.HasTimedOut));

            startProcessTimeout.Stop();

            yield return(new WaitUntil(() => !IsProcessRunning(downloadProcess) || timeout.HasTimedOut));

            if (timeout.HasTimedOut)
            {
                Log.Warn("Timeout reached, disposing download process");
            }
            else
            {
                //When the download is finished, wait for process to exit instead of immediately killing it
                yield return(new WaitForSeconds(2f));
            }

            timeout.Stop();
            _downloadLog = "";
            DisposeProcess(downloadProcess);
        }
 public void StartDownload(VideoConfig video, VideoQuality.Mode quality)
 {
     SharedCoroutineStarter.instance.StartCoroutine(DownloadVideoCoroutine(video, quality));
 }
        private Process?CreateDownloadProcess(VideoConfig video, VideoQuality.Mode quality)
        {
            if (video.LevelDir == null)
            {
                Log.Error("LevelDir was null during download");
                return(null);
            }

            var success = _downloadProcesses.TryGetValue(video, out _);

            if (success)
            {
                Log.Warn("Existing process not cleaned up yet. Cancelling download attempt.");
                return(null);
            }

            if (!Directory.Exists(video.LevelDir))
            {
                //Needed for OST videos
                Directory.CreateDirectory(video.LevelDir);
            }

            var videoFileName = Util.ReplaceIllegalFilesystemChars(video.title ?? video.videoID ?? "video");

            videoFileName   = Util.ShortenFilename(video.LevelDir, videoFileName);
            video.videoFile = videoFileName + ".mp4";

            string videoUrl;

            if (video.videoUrl != null)
            {
                if (UrlInWhitelist(video.videoUrl))
                {
                    videoUrl = video.videoUrl;
                }
                else
                {
                    Log.Error($"Video hoster for {video.videoUrl} is not allowed");
                    return(null);
                }
            }
            else if (video.videoID != null)
            {
                videoUrl = $"https://www.youtube.com/watch?v={video.videoID}";
            }
            else
            {
                Log.Error("Video config has neither videoID or videoUrl set");
                return(null);
            }

            var videoFormat = VideoQuality.ToYoutubeDLFormat(video, quality);

            videoFormat = $" -f \"{videoFormat}\"";

            var downloadProcessArguments = videoUrl +
                                           videoFormat +
                                           " --no-cache-dir" +        // Don't use temp storage
                                           $" -o \"{videoFileName}.%(ext)s\"" +
                                           " --no-playlist" +         // Don't download playlists, only the first video
                                           " --no-part" +             // Don't store download in parts, write directly to file
                                           " --recode-video mp4" +    //Re-encode to mp4 (will be skipped most of the time, since it's already in an mp4 container)
                                           " --no-mtime" +            //Video last modified will be when it was downloaded, not when it was uploaded to youtube
                                           " --socket-timeout 10";    //Retry if no response in 10 seconds Note: Not if download takes more than 10 seconds but if the time between any 2 messages from the server is 10 seconds

            var process = CreateProcess(downloadProcessArguments, video.LevelDir);

            _downloadProcesses.TryAdd(video, process);
            return(process);
        }