private void DownloadOutputDataReceived(DataReceivedEventArgs eventArgs, VideoConfig video)
 {
     _downloadLog += eventArgs.Data + "\r\n";
     Log.Debug(eventArgs.Data);
     ParseDownloadProgress(video, eventArgs);
     DownloadProgress?.Invoke(video);
 }
        private static void ParseDownloadProgress(VideoConfig video, DataReceivedEventArgs dataReceivedEventArgs)
        {
            if (dataReceivedEventArgs.Data == null)
            {
                return;
            }

            var rx    = new Regex(@"(\d*).\d%+");
            var match = rx.Match(dataReceivedEventArgs.Data);

            if (!match.Success)
            {
                if (dataReceivedEventArgs.Data.Contains("Converting video"))
                {
                    video.DownloadState = DownloadState.Converting;
                }
                return;
            }

            var ci = (CultureInfo)CultureInfo.CurrentCulture.Clone();

            ci.NumberFormat.NumberDecimalSeparator = ".";

            video.DownloadProgress =
                float.Parse(match.Value.Substring(0, match.Value.Length - 1), ci) / 100;
        }
示例#3
0
        private static void PrepareClonedScreens(VideoConfig videoConfig)
        {
            var screenCount = PlaybackController.Instance.gameObject.transform.childCount;

            if (screenCount <= 1)
            {
                return;
            }

            Log.Debug($"Screens found: {screenCount}");
            foreach (Transform screen in PlaybackController.Instance.gameObject.transform)
            {
                if (!screen.name.StartsWith("CinemaScreen"))
                {
                    return;
                }

                if (screen.name.Contains("Clone"))
                {
                    PlaybackController.Instance.VideoPlayer.screenController.Screens.Add(screen.gameObject);
                    screen.GetComponent <Renderer>().material = PlaybackController.Instance.VideoPlayer.screenController.Screens[0].GetComponent <Renderer>().material;
                    Object.Destroy(screen.Find("CinemaDirectionalLight").gameObject);
                }

                screen.gameObject.GetComponent <CustomBloomPrePass>().enabled = false;
                Log.Debug("Disabled bloom prepass");
            }

            PlaybackController.Instance.VideoPlayer.SetPlacement(
                Placement.CreatePlacementForConfig(videoConfig, PlaybackController.Scene.SoloGameplay, PlaybackController.Instance.VideoPlayer.GetVideoAspectRatio())
                );
            PlaybackController.Instance.VideoPlayer.screenController.SetShaderParameters(videoConfig);
        }
        private void DownloadProcessExited(Process process, VideoConfig video)
        {
            var exitCode = process.ExitCode;

            if (exitCode != 0)
            {
                Log.Warn(_downloadLog.Length > 0 ? _downloadLog : "Empty youtube-dl log");

                video.DownloadState = DownloadState.Cancelled;
            }
            Log.Info($"Download process exited with code {exitCode}");

            if (video.DownloadState == DownloadState.Cancelled)
            {
                Log.Info("Cancelled download");
                VideoLoader.DeleteVideo(video);
                DownloadFinished?.Invoke(video);
            }
            else
            {
                process.Disposed -= DownloadProcessDisposed;
                _downloadProcesses.TryRemove(video, out _);
                video.DownloadState = DownloadState.Downloaded;
                video.NeedsToSave   = true;
                SharedCoroutineStarter.instance.StartCoroutine(WaitForDownloadToFinishCoroutine(video));
                Log.Info("Download finished");
            }
        }
示例#5
0
        public static bool DeleteConfig(VideoConfig videoConfig, IPreviewBeatmapLevel level)
        {
            if (videoConfig.LevelDir == null)
            {
                Log.Error("LevelDir was null when trying to delete config");
                return(false);
            }

            try
            {
                var cinemaConfigPath = Path.Combine(videoConfig.LevelDir, CONFIG_FILENAME);
                if (File.Exists(cinemaConfigPath))
                {
                    File.Delete(cinemaConfigPath);
                }

                var mvpConfigPath = Path.Combine(videoConfig.LevelDir, CONFIG_FILENAME_MVP);
                if (File.Exists(mvpConfigPath))
                {
                    File.Delete(mvpConfigPath);
                }
            }
            catch (Exception e)
            {
                Log.Error("Failed to delete video config:");
                Log.Error(e);
            }

            RemoveConfigFromCache(level);
            Log.Info("Deleted video config");

            return(true);
        }
示例#6
0
        public static void DeleteVideo(VideoConfig videoConfig)
        {
            if (videoConfig.VideoPath == null)
            {
                Log.Warn("Tried to delete video, but its path was null");
                return;
            }

            try
            {
                File.Delete(videoConfig.VideoPath);
                Log.Info("Deleted video at " + videoConfig.VideoPath);
                if (videoConfig.DownloadState != DownloadState.Cancelled)
                {
                    videoConfig.DownloadState = DownloadState.NotDownloaded;
                }

                videoConfig.videoFile = null;
            }
            catch (Exception e)
            {
                Log.Error("Failed to delete video at " + videoConfig.VideoPath);
                Log.Error(e);
            }
        }
示例#7
0
        public static void SaveVideoConfig(VideoConfig videoConfig)
        {
            if (videoConfig.LevelDir == null || !Directory.Exists(videoConfig.LevelDir))
            {
                Log.Warn("Failed to save video. Path " + videoConfig.LevelDir + " does not exist.");
                return;
            }

            if (videoConfig.IsWIPLevel)
            {
                videoConfig.configByMapper = true;
            }

            var videoJsonPath = Path.Combine(videoConfig.LevelDir, CONFIG_FILENAME);

            _ignoreNextEventForPath = videoJsonPath;
            Log.Info($"Saving video config to {videoJsonPath}");

            try
            {
                File.WriteAllText(videoJsonPath, JsonConvert.SerializeObject(videoConfig, Formatting.Indented));
                videoConfig.NeedsToSave = false;
            }
            catch (Exception e)
            {
                Log.Error("Failed to save level data: ");
                Log.Error(e);
            }
        }
        private IEnumerator WaitForDownloadToFinishCoroutine(VideoConfig video)
        {
            var timeout = new Timeout(3);

            yield return(new WaitUntil(() => timeout.HasTimedOut || File.Exists(video.VideoPath)));

            DownloadFinished?.Invoke(video);
        }
示例#9
0
        public static void AddConfigToCache(VideoConfig config, IPreviewBeatmapLevel level)
        {
            var success = CachedConfigs.TryAdd(level.levelID, config);

            if (success)
            {
                Log.Debug($"Adding config for {level.levelID} to cache");
            }
        }
示例#10
0
        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);
        }
示例#11
0
        public void PrepareVideo(VideoConfig video)
        {
            _previewWaitingForVideoPlayer = true;

            if (_prepareVideoCoroutine != null)
            {
                StopCoroutine(_prepareVideoCoroutine);
            }

            _prepareVideoCoroutine = PrepareVideoCoroutine(video);
            StartCoroutine(_prepareVideoCoroutine);
        }
示例#12
0
        public void CancelDownload(VideoConfig video)
        {
            Log.Debug("Cancelling download");
            video.DownloadState = DownloadState.Cancelled;
            DownloadProgress?.Invoke(video);

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

            if (success)
            {
                DisposeProcess(process);
            }
            VideoLoader.DeleteVideo(video);
        }
示例#13
0
        private static void CreateAdditionalScreens(VideoConfig videoConfig)
        {
            if (videoConfig.additionalScreens == null)
            {
                return;
            }

            var screenController = PlaybackController.Instance.VideoPlayer.screenController;
            var i = 0;

            foreach (var _ in videoConfig.additionalScreens)
            {
                var clone = Object.Instantiate(screenController.Screens[0], screenController.Screens[0].transform.parent);
                clone.name += $" ({i++.ToString()})";
            }
        }
示例#14
0
        private IEnumerator PrepareVideoCoroutine(VideoConfig video)
        {
            VideoConfig = video;

            VideoPlayer.Pause();
            if (!(VideoConfig.DownloadState == DownloadState.Downloaded || VideoConfig.IsStreamable))
            {
                Log.Debug("Video is not downloaded, stopping prepare");
                VideoPlayer.FadeOut();
                yield break;
            }

            VideoPlayer.LoopVideo(video.loop == true);
            VideoPlayer.screenController.SetShaderParameters(video);
            VideoPlayer.SetBloomIntensity(video.bloom);

            if (video.VideoPath == null)
            {
                Log.Debug("Video path was null, stopping prepare");
                yield break;
            }
            var videoPath = video.VideoPath;

            Log.Info($"Loading video: {videoPath}");

            if (VideoConfig.IsLocal)
            {
                var videoFileInfo = new FileInfo(videoPath);
                var timeout       = new Timeout(0.25f);
                if (VideoPlayer.Url != videoPath)
                {
                    yield return(new WaitUntil(() =>
                                               !Util.IsFileLocked(videoFileInfo) || timeout.HasTimedOut));
                }

                timeout.Stop();
                if (timeout.HasTimedOut && Util.IsFileLocked(videoFileInfo))
                {
                    Log.Warn("Video file locked");
                }
            }

            VideoPlayer.Url = videoPath;
            VideoPlayer.Prepare();
        }
示例#15
0
        private static VideoConfig?LoadConfig(string configPath)
        {
            if (!File.Exists(configPath))
            {
                Log.Warn($"Config file {configPath} does not exist");
                return(null);
            }

            VideoConfig?videoConfig;

            try
            {
                var json = File.ReadAllText(configPath);
                if (configPath.EndsWith("\\" + CONFIG_FILENAME_MVP))
                {
                    //Back compatiblity with MVP configs
                    var videoConfigListBackCompat = JsonConvert.DeserializeObject <VideoConfigListBackCompat>(json);
                    videoConfig = new VideoConfig(videoConfigListBackCompat);
                }
                else
                {
                    videoConfig = JsonConvert.DeserializeObject <VideoConfig>(json);
                }
            }
            catch (Exception e)
            {
                Log.Error($"Error parsing video json {configPath}:");
                Log.Error(e);
                return(null);
            }

            // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
            if (videoConfig != null)
            {
                videoConfig.LevelDir = Path.GetDirectoryName(configPath);
                videoConfig.UpdateDownloadState();
            }
            else
            {
                Log.Warn($"Deserializing video config at {configPath} failed");
            }

            return(videoConfig);
        }
示例#16
0
        public static string ToYoutubeDLFormat(VideoConfig config, Mode quality)
        {
            string?qualityString;

            if (config.videoUrl == null || config.videoUrl.StartsWith("https://www.youtube.com/watch"))
            {
                qualityString = $"bestvideo[height<={(int) quality}][vcodec*=avc1]+bestaudio[acodec*=mp4]";
            }
            else if (config.videoUrl.StartsWith("https://vimeo.com/"))
            {
                qualityString = $"bestvideo[height<={(int) quality}][vcodec*=avc1]+bestaudio[acodec*=mp4]";
            }
            else if (config.videoUrl.StartsWith("https://www.facebook.com"))
            {
                qualityString = "mp4";
            }
            else
            {
                qualityString = $"best[height<={(int) quality}][vcodec*=avc1]";
            }

            return(qualityString);
        }
示例#17
0
        private void PlayVideo(float startTime)
        {
            if (VideoConfig == null)
            {
                Log.Warn("VideoConfig null in PlayVideo");
                return;
            }

            VideoPlayer.IsSyncing = false;

            // Always hide screen body in the menu, since the drawbacks of the body being visible are large
            if (VideoConfig.TransparencyEnabled && _activeScene != Scene.Menu)
            {
                VideoPlayer.ShowScreenBody();
            }
            else
            {
                VideoPlayer.HideScreenBody();
            }

            var totalOffset = VideoConfig.GetOffsetInSec();
            var songSpeed   = 1f;

            if (BS_Utils.Plugin.LevelData.IsSet)
            {
                songSpeed = BS_Utils.Plugin.LevelData.GameplayCoreSceneSetupData.gameplayModifiers.songSpeedMul;

                if (BS_Utils.Plugin.LevelData.GameplayCoreSceneSetupData?.practiceSettings != null)
                {
                    songSpeed = BS_Utils.Plugin.LevelData.GameplayCoreSceneSetupData.practiceSettings.songSpeedMul;
                    if ((totalOffset + startTime) < 0)
                    {
                        totalOffset /= (songSpeed * VideoConfig.PlaybackSpeed);
                    }
                }
            }

            VideoPlayer.PlaybackSpeed = songSpeed * VideoConfig.PlaybackSpeed;
            totalOffset += startTime;             //This must happen after song speed adjustment

            if ((songSpeed * VideoConfig.PlaybackSpeed) < 1f && totalOffset > 0f)
            {
                //Unity crashes if the playback speed is less than 1 and the video time at the start of playback is greater than 0
                Log.Warn("Video playback disabled to prevent Unity crash");
                VideoPlayer.Hide();
                StopPlayback();
                return;
            }

            //Video seemingly always lags behind. A fixed offset seems to work well enough
            if (!IsPreviewPlaying)
            {
                totalOffset += 0.0667f;
            }

            if (VideoConfig.endVideoAt != null && totalOffset > VideoConfig.endVideoAt)
            {
                totalOffset = VideoConfig.endVideoAt.Value;
            }

            //This will fail if the video is not prepared yet
            if (VideoPlayer.VideoDuration > 0)
            {
                totalOffset %= VideoPlayer.VideoDuration;
            }

            //This fixes an issue where the Unity video player sometimes ignores a change in the .time property if the time is very small and the player is currently playing
            if (Math.Abs(totalOffset) < 0.001f)
            {
                totalOffset = 0;
                Log.Debug("Set very small offset to 0");
            }

            Log.Debug($"Total offset: {totalOffset}, startTime: {startTime}, songSpeed: {songSpeed}, player time: {VideoPlayer.Player.time}");

            StopAllCoroutines();

            if (_activeAudioSource != null)
            {
                _lastKnownAudioSourceTime = _activeAudioSource.time;
            }

            if (totalOffset < 0)
            {
                if (!IsPreviewPlaying)
                {
                    //Negate the offset to turn it into a positive delay
                    StartCoroutine(PlayVideoDelayedCoroutine(-(totalOffset)));
                }
                else
                {
                    //In menus we don't need to wait, instead the preview player starts earlier
                    VideoPlayer.Play();
                }
            }
            else
            {
                VideoPlayer.Play();
                if (!VideoPlayer.Player.isPrepared)
                {
                    _audioSourceStartTime = DateTime.Now;
                    _offsetAfterPrepare   = totalOffset;
                }
                else
                {
                    VideoPlayer.Player.time = totalOffset;
                }
            }
        }
示例#18
0
        public async void StartPreview()
        {
            if (VideoConfig == null || _currentLevel == null)
            {
                Log.Warn("No video or level selected in OnPreviewAction");
                return;
            }
            if (IsPreviewPlaying)
            {
                Log.Debug("Stopping preview");
                StopPreview(true);
            }
            else
            {
                Log.Debug("Starting preview");
                IsPreviewPlaying = true;

                if (VideoPlayer.IsPlaying)
                {
                    StopPlayback();
                }

                if (!VideoPlayer.IsPrepared)
                {
                    Log.Debug("Video not prepared yet");
                }

                //Start the preview at the point the video kicks in
                var startTime = 0f;
                if (VideoConfig.offset < 0)
                {
                    startTime = -VideoConfig.GetOffsetInSec();
                }

                if (SongPreviewPlayerController.SongPreviewPlayer == null)
                {
                    Log.Error("Failed to get reference to SongPreviewPlayer during preview");
                    return;
                }

                try
                {
                    Log.Debug($"Preview start time: {startTime}, offset: {VideoConfig.GetOffsetInSec()}");
                    var audioClip = await VideoLoader.GetAudioClipForLevel(_currentLevel);

                    if (audioClip != null)
                    {
                        SongPreviewPlayerController.SongPreviewPlayer.CrossfadeTo(audioClip, -5f, startTime, _currentLevel.songDuration, null);
                    }
                    else
                    {
                        Log.Error("AudioClip for level failed to load");
                    }
                }
                catch (Exception e)
                {
                    Log.Error(e);
                    IsPreviewPlaying = false;
                    return;
                }

                //+1.0 is hard right. only pan "mostly" right, because for some reason the video player audio doesn't
                //pan hard left either. Also, it sounds a bit more comfortable.
                SetAudioSourcePanning(0.9f);
                StartCoroutine(PlayVideoAfterAudioSourceCoroutine(true));
                VideoPlayer.PanStereo = -1f;                 // -1 is hard left
                VideoPlayer.Unmute();
            }
        }
示例#19
0
        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);
        }
示例#20
0
 public void StartDownload(VideoConfig video, VideoQuality.Mode quality)
 {
     SharedCoroutineStarter.instance.StartCoroutine(DownloadVideoCoroutine(video, quality));
 }