コード例 #1
0
ファイル: SoFakingWorker.cs プロジェクト: ohmy-cz/SoFaking
        private string MovieDownloadDirectory(ITorrentClientTorrent torrent, out string torrentFileNameExtension)
        {
            torrentFileNameExtension = null;
            var fileName = Regexes.FileNamePattern.Match(torrent.Name);

            if (fileName.Success)
            {
                torrentFileNameExtension = fileName.Groups["FileExtension"].Value;
            }

            return(MovieDownloadDirectory(torrent));
        }
コード例 #2
0
ファイル: SoFakingWorker.cs プロジェクト: ohmy-cz/SoFaking
        /// <summary>
        /// Cleanup  method to be called after succesfull download, transcoding and manipulation of files.
        /// </summary>
        protected async Task MovieDownloadedSuccesfulyAsync(ITorrentClientTorrent torrent, Movie movie)
        {
            try
            {
                _logger.LogDebug($"Will delete: {MovieDownloadDirectory(torrent)}");
#if RELEASE
                Directory.Delete(MovieDownloadDirectory(torrent), true);
#endif
            }
            catch (IOException _)
            {
                await _movieService.SetMovieStatus(movie.Id, MovieStatusEnum.CouldNotDeleteDownloadDirectory);
            }
            _logger.LogDebug($"Will remove torrent: {torrent.Id}");
#if RELEASE
            await _torrentClient.RemoveTorrent(torrent.Id);
#endif
            await _movieService.SetMovieStatus(movie.Id, MovieStatusEnum.Finished);

            // Inform Minidlna or other services about the new download.
            if (!string.IsNullOrWhiteSpace(_configuration.FinishedCommandExecutable))
            {
                var process = new Process()
                {
                    StartInfo = new ProcessStartInfo
                    {
                        FileName        = _configuration.FinishedCommandExecutable,
                        Arguments       = _configuration.FinishedCommandArguments,
                        UseShellExecute = false,
                        CreateNoWindow  = true
                    }
                };

                await Task.Run(() =>
                {
                    process.Start();
                    process.WaitForExit();
                });
            }
        }
コード例 #3
0
ファイル: SoFakingWorker.cs プロジェクト: ohmy-cz/SoFaking
        private string MovieDownloadDirectory(ITorrentClientTorrent torrent)
        {
            var fileName = Regexes.FileNamePattern.Match(torrent.Name);

            return(Path.Combine(_configuration.MoviesDownloadDir, fileName.Success ? fileName.Groups["FileName"].Value : torrent.Name));
        }
コード例 #4
0
ファイル: SoFakingWorker.cs プロジェクト: ohmy-cz/SoFaking
        private async Task MoveVideoFilesToFinishedDir(Movie movie, ITorrentClientTorrent torrent, string[] videoFilesToMove, string coverImageJpg = null)
        {
            if (movie == null)
            {
                throw new ArgumentNullException(nameof(movie));
            }
            if (torrent == null)
            {
                throw new ArgumentNullException(nameof(torrent));
            }
            if (videoFilesToMove == null || !videoFilesToMove.Any())
            {
                _logger.LogError($"Cannot move video files: {nameof(videoFilesToMove)} was empty.");
                throw new ArgumentNullException(nameof(videoFilesToMove));
            }

            _logger.LogWarning($"Moving video files:\n{string.Join("\n ", videoFilesToMove)}");

            var finishedMovieDirectory = MovieFinishedDirectory(movie);

            if (!Directory.Exists(finishedMovieDirectory))
            {
                Directory.CreateDirectory(finishedMovieDirectory);
            }

            // Move existing Cover image, or download a new one if null.
            var finishedCoverImageJpg = Path.Combine(finishedMovieDirectory, "Cover.jpg");

            try
            {
                if (!File.Exists(finishedCoverImageJpg))
                {
                    if (coverImageJpg != null && File.Exists(coverImageJpg))
                    {
                        File.Move(coverImageJpg, finishedCoverImageJpg);
                    }
                    else
                    {
                        try
                        {
                            await Download.GetFile(movie.ImageUrl, finishedCoverImageJpg);
                        }
                        catch (Exception ex)
                        {
                            finishedCoverImageJpg = null;
                            _logger.LogError($"Could not create a Cover image. {ex.Message}", ex);
                        }
                    }

                    if (finishedCoverImageJpg != null)
                    {
                        await WindowsFolder.SetFolderPictureAsync(finishedCoverImageJpg);

                        File.SetAttributes(finishedCoverImageJpg, File.GetAttributes(finishedCoverImageJpg) | FileAttributes.Hidden);
                    }
                }
            }
            catch (Exception e)
            {
                _logger.LogInformation($"Could create a cover image: {e.Message}.", e);
            }

            try
            {
                // Here we're assuming that the  first, largest file will be the main movie file.
                var mainMovieFile = videoFilesToMove[0];
                var destinationFileNameWithoutExtension = Regexes.FileSystemSafeName.Replace($"{movie.Year} {movie.Title}", string.Empty);
                _logger.LogWarning($"Moving MAIN movie file: {mainMovieFile} to {Path.Combine(finishedMovieDirectory, destinationFileNameWithoutExtension + Regexes.FileNamePattern.Match(mainMovieFile).Groups["FileExtension"].Value)}");
                File.Move(mainMovieFile, Path.Combine(finishedMovieDirectory, destinationFileNameWithoutExtension + Regexes.FileNamePattern.Match(mainMovieFile).Groups["FileExtension"].Value));

                if (videoFilesToMove.Length > 1)
                {
                    foreach (var videoFile in videoFilesToMove.Skip(1))
                    {
                        _logger.LogInformation($"Moving video file: {videoFile} to {Path.Combine(finishedMovieDirectory, Path.GetFileName(videoFile))}");
                        File.Move(videoFile, Path.Combine(finishedMovieDirectory, Path.GetFileName(videoFile)));
                    }
                }
            }
            catch (Exception e)
            {
                await _movieService.SetMovieStatus(movie.Id, MovieStatusEnum.FileInUse);

                _logger.LogDebug($"Will delete torrent: {torrent.Id}");
#if RELEASE
                await _torrentClient.RemoveTorrent(torrent.Id);
#endif
                _logger.LogInformation($"Could not move the final downloaded files: {e.Message}. Is FFMPEG still running?", e);
            }
        }
コード例 #5
0
ファイル: SoFakingWorker.cs プロジェクト: ohmy-cz/SoFaking
        // TODO: Split into Analyse and Transcode, so the Analysis part doesn't get called when run from Queued
        private async Task <TranscodeResult> Transcode(int movieJobId, ITorrentClientTorrent torrent, CancellationToken cancellationToken)
        {
            var sourcePath = MovieDownloadDirectory(torrent);
            var videoFiles = GetVideoFilesInDir(sourcePath);

            if (videoFiles == null || videoFiles.Length == 0)
            {
                await _movieService.SetMovieStatus(movieJobId, MovieStatusEnum.TranscodingError);

                _logger.LogWarning($"{torrent.Name} has no compatible video files.");
                return(new TranscodeResult(TranscodeResultEnum.NoVideoFiles));
            }

            var movie = (await _movieService.GetMoviesAsync()).Where(x => x.Id == movieJobId).FirstOrDefault();
            await _movieService.SetMovieStatus(movieJobId, MovieStatusEnum.AnalysisStarted);

            var pendingTranscodingJobs = new List <ITranscodingJob>();
            var filesToMove            = new List <string>();

            // Check if any files need transcoding
            foreach (var videoFile in videoFiles)
            {
                IMediaInfo mediaInfo = null;
                _logger.LogInformation($"Analyzing {Path.GetFileName(videoFile)}");
                try
                {
                    using (var encoder = new FFMPEGEncoderService(_loggerEnc, _encoderConfiguration, _sofakingConfiguration))
                    {
                        mediaInfo = await encoder.GetMediaInfo(videoFile);
                    }
                }
                catch (Exception ex) {
                    _logger.LogError($"{nameof(FFMPEGEncoderService.GetMediaInfo)} failed with: {ex.Message}", ex);
                }

                if (mediaInfo == null)
                {
                    _logger.LogWarning($"File {Path.GetFileName(videoFile)} returned no {nameof(mediaInfo)}");
                    continue;
                }

                var flags = EncodingTargetFlags.None;

                try
                {
                    if (!HasAcceptableVideo(mediaInfo))
                    {
                        flags |= EncodingTargetFlags.NeedsNewVideo;
                    }
                }
                catch (ArgumentException ex)
                {
                    _logger.LogError($"{Path.GetFileName(videoFile)}", ex);
                    _logger.LogError($"{ex.ParamName}: {ex.Message}");
                    await _movieService.SetMovieStatus(movieJobId, MovieStatusEnum.AnalysisVideoFailed);

                    continue;
                }

                try
                {
                    if (!HasAcceptableAudio(mediaInfo))
                    {
                        flags |= EncodingTargetFlags.NeedsNewAudio;
                    }
                }
                catch (ArgumentException ex)
                {
                    _logger.LogError($"{Path.GetFileName(videoFile)}", ex);
                    _logger.LogError($"{ex.ParamName}: {ex.Message}");
                    await _movieService.SetMovieStatus(movieJobId, MovieStatusEnum.AnalysisAudioFailed);

                    continue;
                }

                if (movie.Genres.HasFlag(GenreFlags.Animation))
                {
                    flags |= EncodingTargetFlags.VideoIsAnimation;
                }

                if (flags == EncodingTargetFlags.None)
                {
                    _logger.LogInformation($"Video file {videoFile} doesn't need transcoding, adding to files to move.");
                    filesToMove.Add(videoFile);
                    continue;
                }

                _logger.LogInformation($"Adding {videoFile} to transcoding jobs.");
                pendingTranscodingJobs.Add(new TranscodingJob
                {
                    SourceFile        = videoFile,
                    Action            = flags,
                    Duration          = mediaInfo.Duration,
                    CancellationToken = cancellationToken,
                    Metadata          = new Dictionary <FFMPEGMetadataEnum, string> {
                        { FFMPEGMetadataEnum.title, movie.Title },
                        { FFMPEGMetadataEnum.year, movie.Year.ToString() },
                        { FFMPEGMetadataEnum.director, movie.Director },
                        { FFMPEGMetadataEnum.description, movie.Description },
                        { FFMPEGMetadataEnum.episode_id, movie.EpisodeId },
                        { FFMPEGMetadataEnum.IMDBRating, movie.ImdbScore.ToString() },
                        { FFMPEGMetadataEnum.genre, movie.Genres.ToString() },
                        { FFMPEGMetadataEnum.show, movie.Show }
                    }
                });
            }

            // No transcoding needed for any video files in the folder
            // Using this pendingTranscodingJobsList allows us to return for movies that need no transcoding, so they won't wait for the transcoding to be complete and can be simply copied  over to the resulting folder.
            if (!pendingTranscodingJobs.Any())
            {
                _logger.LogInformation($"Transcoding not needed, no pending jobs.");
                return(new TranscodeResult(TranscodeResultEnum.TranscodingNotNeeded, filesToMove.ToArray()));
            }

            // If something else is transcoding, then queue
            var tjmem = _transcodingJobs.Any();
            var tjdb  = (await _movieService.GetMoviesAsync()).Where(x => x.Status == MovieStatusEnum.TranscodingStarted).Any();

            if (tjmem || tjdb)
            {
                _logger.LogInformation($"Queuing {movie.TorrentName}");
                await _movieService.SetMovieStatus(movieJobId, MovieStatusEnum.TranscodingQueued);

                return(new TranscodeResult(TranscodeResultEnum.Queued));
            }

            // Set the a cover image for the first file (the largest = the movie), so it will be attached to the output file.
            string coverImageJpg = null;

            try
            {
                coverImageJpg = Path.Combine(_sofakingConfiguration.TempFolder, movie.Id + "-Cover.jpg");
                await Download.GetFile(movie.ImageUrl, coverImageJpg);

                pendingTranscodingJobs[0].CoverImageJpg = coverImageJpg;
            }
            catch (Exception ex)
            {
                coverImageJpg = null;
                _logger.LogError($"Could not download a Cover image. {ex.Message}", ex);
            }

            // Start transcoding by copying our pending tasks into the static global queue
            _logger.LogInformation($"Setting movie status to TranscodingStarted.");
            await _movieService.SetMovieStatus(movieJobId, MovieStatusEnum.TranscodingStarted);

            foreach (var transcodingJob in pendingTranscodingJobs)
            {
                int  attempts = 0;
                bool success  = false;
                while (attempts <= 10)
                {
                    var id = _transcodingJobs.IsEmpty ? 0 : _transcodingJobs.Select(x => x.Key).Max() + 1;
                    _logger.LogInformation($"Trying to add transconding job {id} to the global queue");
                    if (_transcodingJobs.TryAdd(id, transcodingJob))
                    {
                        success = true;
                        break;
                    }

                    attempts++;
                    Thread.Sleep(TimeSpan.FromSeconds(3));
                }

                if (!success)
                {
                    _logger.LogError($"Couldn't add transcoding job {transcodingJob.SourceFile} to the global queue.");
                }
            }

            // Get the first job from the stack, then drop it when done
            _ = Task.Run(async() =>
            {
                while (_transcodingJobs.Any() && _transcodingJobs.TryGetValue(_transcodingJobs.First().Key, out var transcodingJob))
                {
                    if (_encoderTranscodingInstance != null)
                    {
                        continue;
                    }

                    try
                    {
                        // Do this as the first thing, so no other encoding gets started
                        _encoderTranscodingInstance = new FFMPEGEncoderService(_loggerEnc, _encoderConfiguration, _sofakingConfiguration);
                        _logger.LogWarning($"Preparing transcoding of {transcodingJob.SourceFile}");

                        Action FirstOut = () => {
                            if (!_transcodingJobs.Any())
                            {
                                _logger.LogInformation("No transcoding jobs left to remove");
                                return;
                            }

                            var removed = _transcodingJobs.TryRemove(_transcodingJobs.First().Key, out _);
                            _logger.LogWarning($"Removing first from the queue, result: {removed}.");
                        };

                        // TODO: Use a factory
                        _encoderTranscodingInstance.OnStart += (object sender, EventArgs e) => {
                            _logger.LogInformation("Transcoding started");
                        };

                        _encoderTranscodingInstance.OnProgress += (object sender, EncodingProgressEventArgs e) => {
                            _logger.LogDebug($"Transcoding progress: {e.ProgressPercent:0.##}%");
                        };

                        _encoderTranscodingInstance.OnError += async(object sender, EncodingErrorEventArgs e) => {
                            FirstOut();
                            _encoderTranscodingInstance.Dispose();
                            _encoderTranscodingInstance = null;

                            if (_transcodingJobs.Count == 0)
                            {
                                await _movieService.SetMovieStatus(movieJobId, MovieStatusEnum.TranscodingError);
                            }

                            _logger.LogInformation($"Transcoding failed: {e.Error}");
                        };

                        _encoderTranscodingInstance.OnCancelled += async(object sender, EventArgs e) => {
                            FirstOut();
                            _encoderTranscodingInstance.Dispose();
                            _encoderTranscodingInstance = null;

                            if (_transcodingJobs.Count == 0)
                            {
                                await _movieService.SetMovieStatus(movieJobId, MovieStatusEnum.TranscodingCancelled);
                            }

                            _logger.LogInformation("Transcoding cancelled");
                        };

                        _encoderTranscodingInstance.OnSuccess += async(object sender, EncodingSuccessEventArgs e) => {
                            FirstOut();

                            _logger.LogWarning($"Adding {e.FinishedFile} to the list of files to move ({filesToMove.Count()})");
                            filesToMove.Add(e.FinishedFile);

                            if (_transcodingJobs.Count == 0)
                            {
                                _logger.LogWarning("All transcoding done.");
                                await MoveVideoFilesToFinishedDir(movie, torrent, filesToMove.ToArray(), coverImageJpg);
                                await MovieDownloadedSuccesfulyAsync(torrent, movie);
                            }

                            // Do this as the last thing, so no other encoding gets started
                            _encoderTranscodingInstance.Kill();                             // Note: .Dispose here leads to "Broken pipe" Exception
                            _encoderTranscodingInstance.CleanTempData();
                            _encoderTranscodingInstance = null;
                        };

                        _logger.LogWarning($"Starting transcoding of {transcodingJob.SourceFile}");
                        await _encoderTranscodingInstance.StartTranscodingAsync(transcodingJob, cancellationToken);
                    }
                    catch (Exception e)
                    {
                        if (_transcodingJobs.Any())
                        {
                            var removed = _transcodingJobs.TryRemove(_transcodingJobs.First().Key, out _);
                            _logger.LogWarning($"Removing first from the queue, result: {removed}.");
                        }

                        await _movieService.SetMovieStatus(movieJobId, MovieStatusEnum.TranscodingError);
                        _logger.LogError(e.Message);

                        _encoderTranscodingInstance.DisposeAndKeepFiles();
                        _encoderTranscodingInstance = null;
                    }
                }
            });

            return(new TranscodeResult(TranscodeResultEnum.Transcoding, filesToMove.ToArray()));
        }