Esempio n. 1
0
 public TransfersService(
     ITransfersRepository transfersRepository,
     IOperationsService operationsService,
     IEncoderService encoderService,
     ISettingsService settingsService,
     IPrivateBlockchainFacadeClient privateBlockchainFacadeClient,
     ILogFactory logFactory)
 {
     _transfersRepository           = transfersRepository;
     _operationsService             = operationsService;
     _encoderService                = encoderService;
     _settingsService               = settingsService;
     _privateBlockchainFacadeClient = privateBlockchainFacadeClient;
     _log = logFactory.CreateLog(this);
 }
Esempio n. 2
0
        // 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()));
        }
Esempio n. 3
0
 public EncodeController(IEncoderService encoderService)
 {
     EncoderService = encoderService;
 }