private static void QueueMergeJob(BaseJob job, IDbConnection connection, string outputFolder,
                                          string fileNameWithoutExtension, string fileExtension, JobRequest jobRequest)
        {
            ICollection <FfmpegPart> chunks = connection.Query <FfmpegPart>(
                "SELECT Filename, Number, Target, (SELECT VideoSourceFilename FROM FfmpegVideoRequest WHERE JobCorrelationId = @Id) AS VideoSourceFilename, Width, Height, Bitrate FROM FfmpegVideoParts WHERE JobCorrelationId = @Id ORDER BY Target, Number;",
                new { Id = job.JobCorrelationId })
                                              .ToList();

            foreach (IEnumerable <FfmpegPart> chunk in chunks.GroupBy(x => x.Target, x => x, (key, values) => values))
            {
                var FfmpegVideoParts     = chunk as IList <FfmpegPart> ?? chunk.ToList();
                int targetNumber         = FfmpegVideoParts.First().Target;
                DestinationFormat target = jobRequest.Targets[targetNumber];

                string targetFilename =
                    $@"{outputFolder}{Path.DirectorySeparatorChar}{fileNameWithoutExtension}_{target.Width}x{target.Height}_{target.VideoBitrate}_{target.AudioBitrate}{fileExtension}";

                // TODO Implement proper detection if files are already merged
                if (File.Exists(targetFilename))
                {
                    continue;
                }

                string path =
                    $"{outputFolder}{Path.DirectorySeparatorChar}{job.JobCorrelationId.ToString("N")}{Path.DirectorySeparatorChar}{fileNameWithoutExtension}_{targetNumber}.list";

                using (TextWriter tw = new StreamWriter(path))
                {
                    foreach (FfmpegPart part in FfmpegVideoParts.Where(x => x.IsAudio == false))
                    {
                        tw.WriteLine($"file '{part.Filename}'");
                    }
                }
                string audioSource = chunks.Single(x => x.IsAudio && x.Bitrate == target.AudioBitrate).Filename;

                string arguments =
                    $@"-y -f concat -safe 0 -i ""{path}"" -i ""{audioSource}"" -c copy {targetFilename}";

                connection.Execute(
                    "INSERT INTO FfmpegMergeJobs (JobCorrelationId, Arguments, Needed, State, Target) VALUES(@JobCorrelationId, @Arguments, @Needed, @State, @Target);",
                    new
                {
                    JobCorrelationId = job.JobCorrelationId,
                    Arguments        = arguments,
                    Needed           = jobRequest.Needed,
                    State            = TranscodingJobState.Queued,
                    Target           = targetNumber
                });
            }
        }
        private static void QueueMpegDashMergeJob(BaseJob job, string destinationFilename, IDbConnection connection,
                                                  JobRequest jobRequest, string fileNameWithoutExtension, string outputFolder, string fileExtension)
        {
            string destinationFolder = Path.GetDirectoryName(destinationFilename);
            ICollection <TranscodingJobState> totalJobs =
                connection.Query <TranscodingJobState>("SELECT State FROM Mp4boxJobs WHERE JobCorrelationId = @Id;",
                                                       new { Id = jobRequest.JobCorrelationId })
                .ToList();

            // One MPEG DASH merge job is already queued. Do nothing
            if (totalJobs.Any())
            {
                return;
            }

            string arguments =
                $@"-dash 4000 -rap -frag-rap -profile onDemand -out {destinationFolder}{Path.DirectorySeparatorChar}{fileNameWithoutExtension}.mpd";

            var chunks = connection.Query <FfmpegPart>(
                "SELECT Filename, Number, Target, (SELECT VideoSourceFilename FROM FfmpegVideoRequest WHERE JobCorrelationId = @Id) AS VideoSourceFilename FROM FfmpegVideoParts WHERE JobCorrelationId = @Id ORDER BY Target, Number;",
                new { Id = job.JobCorrelationId });

            foreach (var chunk in chunks.GroupBy(x => x.Target, x => x, (key, values) => values))
            {
                int targetNumber         = chunk.First().Target;
                DestinationFormat target = jobRequest.Targets[targetNumber];

                string targetFilename =
                    $@"{outputFolder}{Path.DirectorySeparatorChar}{fileNameWithoutExtension}_{target.Width}x{target
                        .Height}_{target.VideoBitrate}_{target.AudioBitrate}{fileExtension}";

                arguments += $@" {targetFilename}";
            }

            connection.Execute(
                "INSERT INTO Mp4boxJobs (JobCorrelationId, Arguments, Needed, State) VALUES(@JobCorrelationId, @Arguments, @Needed, @State);",
                new
            {
                JobCorrelationId = jobRequest.JobCorrelationId,
                Arguments        = arguments,
                Needed           = jobRequest.Needed,
                State            = TranscodingJobState.Queued
            });
        }
        public void SaveJobs(JobRequest job, ICollection <VideoTranscodingJob> jobs, IDbConnection connection,
                             Guid jobCorrelationId, int chunkDuration)
        {
            if (jobs.Any(x => x.State == TranscodingJobState.Unknown))
            {
                throw new ArgumentException(
                          "One or more jobs have state TranscodingJobState.Unknown. A valid state must be set before saving to database");
            }

            connection.Execute(
                "INSERT INTO FfmpegVideoRequest (JobCorrelationId, VideoSourceFilename, AudioSourceFilename, DestinationFilename, Needed, Created, EnableDash, EnableTwoPass, EnablePsnr) VALUES(@JobCorrelationId, @VideoSourceFilename, @AudioSourceFilename, @DestinationFilename, @Needed, @Created, @EnableDash, @EnableTwoPass, @EnablePsnr);",
                new
            {
                JobCorrelationId = jobCorrelationId,
                job.VideoSourceFilename,
                job.AudioSourceFilename,
                job.DestinationFilename,
                job.Needed,
                Created = DateTime.UtcNow,
                job.EnableDash,
                job.EnableTwoPass, job.EnablePsnr
            });

            foreach (DestinationFormat target in job.Targets)
            {
                connection.Execute(
                    "INSERT INTO FfmpegVideoRequestTargets (JobCorrelationId, Width, Height, VideoBitrate, AudioBitrate, H264Level, H264Profile) VALUES(@JobCorrelationId, @Width, @Height, @VideoBitrate, @AudioBitrate, @Level, @Profile);",
                    new
                {
                    JobCorrelationId = jobCorrelationId,
                    Width            = target.Width,
                    Height           = target.Height,
                    VideoBitrate     = target.VideoBitrate,
                    AudioBitrate     = target.AudioBitrate,
                    Level            = target.Level,
                    Profile          = target.Profile
                });
            }

            foreach (VideoTranscodingJob transcodingJob in jobs)
            {
                connection.Execute(
                    "INSERT INTO FfmpegVideoJobs (JobCorrelationId, Arguments, Needed, VideoSourceFilename, ChunkDuration, State) VALUES(@JobCorrelationId, @Arguments, @Needed, @VideoSourceFilename, @ChunkDuration, @State);",
                    new
                {
                    JobCorrelationId    = jobCorrelationId,
                    Arguments           = String.Join("|", transcodingJob.Arguments),
                    Needed              = transcodingJob.Needed,
                    VideoSourceFilename = transcodingJob.SourceFilename,
                    ChunkDuration       = chunkDuration,
                    State = transcodingJob.State
                });

                int jobId = connection.Query <int>("SELECT @@IDENTITY;")
                            .Single();

                foreach (FfmpegPart part in transcodingJob.Chunks)
                {
                    DestinationFormat format = job.Targets[part.Target];
                    connection.Execute(
                        "INSERT INTO FfmpegVideoParts (JobCorrelationId, Target, Filename, Number, FfmpegVideoJobs_Id, Width, Height, Bitrate) VALUES(@JobCorrelationId, @Target, @Filename, @Number, @FfmpegVideoJobsId, @Width, @Height, @Bitrate);",
                        new
                    {
                        JobCorrelationId  = jobCorrelationId,
                        Target            = part.Target,
                        Filename          = part.Filename,
                        Number            = part.Number,
                        FfmpegVideoJobsId = jobId,
                        Width             = format.Width,
                        Height            = format.Height,
                        Bitrate           = part.IsAudio ? format.AudioBitrate : format.VideoBitrate
                    });
                }
            }
        }
Example #4
0
        /// <summary>
        /// Validate the order instance, may throw OrderException if the order cannot be procesed.
        /// </summary>
        /// <exception cref="OrderException">If the order is unacceptable (:lemon:)</exception>
        public void Validate(IMediaInfoFacade mediaInfoFacade, ITimeProvider timeProvider)
        {
            if (!File.Exists(FilePath))
            {
                throw new OrderException("Filepath does not exist.");
            }

            //Call Media Info for source format and valid source file check
            var arForced   = ForceAspectRatio.HasValue && ForceAspectRatio != AspectRatio.unknown;
            var rForced    = ForceResolution.HasValue && ForceResolution != Resolution.unknown;
            var infoResult = MediaInfoValidation(mediaInfoFacade, FilePath, arForced, rForced);

            if (!arForced)
            {
                AspectRatio = infoResult.AspectRatio;
            }
            CustomFormat = infoResult.CustomFormat;
            Duration     = infoResult.Duration;
            Format       = infoResult.Format;
            if (!rForced)
            {
                Resolution = infoResult.Resolution;
            }


            if (BurnInLogo.GetValueOrDefault(false))
            {
                if (!string.IsNullOrEmpty(LogoPath))
                {
                    if (!File.Exists(LogoPath))
                    {
                        throw new OrderException("LogoPath does not exist.");
                    }

                    if (!(LogoPath.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase) || LogoPath.EndsWith(".jpg", StringComparison.InvariantCultureIgnoreCase) ||
                          LogoPath.EndsWith(".jpeg", StringComparison.InvariantCultureIgnoreCase)))
                    {
                        throw new OrderException("LogoPath is not a supported file type. Must be png/jpg/jpeg");
                    }
                }
            }

            if (!string.IsNullOrEmpty(AlternateAudioPath))
            {
                if (!File.Exists(AlternateAudioPath))
                {
                    throw new OrderException("AlternateAudioPath does not exist.");
                }

                if (!(AlternateAudioPath.EndsWith(".wav", StringComparison.InvariantCultureIgnoreCase)))
                {
                    throw new OrderException("AlternateAudioPath is not a supported file type. Must be wav format");
                }

                var audioInfo = MediaInfoValidation(mediaInfoFacade, AlternateAudioPath);
                if (audioInfo.Format == StateFormat.custom)
                {
                    throw new OrderException("AlternateAudioPath is not a supported encoding.");
                }

                ValidateAlternateAudioToVideo(mediaInfoFacade, AlternateAudioPath);
                if (!FFMpeg.SupportedAudioMuxingVideoSourceFormats.Contains(Format) ||
                    !FFMpeg.SupportedAudioSourceFormats.Contains(audioInfo.Format))
                {
                    throw new OrderException($"Alternative audio format {audioInfo.Format} mux to {Format} is invalid.");
                }
            }

            if (BurnInSubtitles.GetValueOrDefault(false))
            {
                if (!File.Exists(SubtitlesPath))
                {
                    throw new OrderException("SubtitlesPath does not exist.");
                }

                if (!FFMpeg.SupportedSubtitleExtensions.Any(e => SubtitlesPath.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
                {
                    throw new OrderException($"SubtitlesPath is not a supported file type. Must be one of {FFMpeg.SupportedSubtitleExtensions.Aggregate((c,n)=>$"{c}, {n}")}");
                }
            }
            if ((FFMpeg.SupportedAudioDestinationFormats.Contains(DestinationFormat) && !FFMpeg.SupportedAudioSourceFormats.Contains(Format)) ||
                (Wfs.SupportedDestinationFormats.Contains(DestinationFormat) && !Wfs.SupportedSourceFormats.Contains(Format)))
            {
                throw new OrderException($"DestinationFormat {DestinationFormat} is invalid for source format {Format}.");
            }
            if (DestinationFormat == StateFormat.unknown)
            {
                throw new OrderException("DestinationFormat is invalid.");
            }

            if (DestinationFormat.GetType().GetField(DestinationFormat.ToString()).CustomAttributes.ToList()
                .Find(a => a.AttributeType == typeof(ObsoleteAttribute)) != null)
            {
                throw new OrderException($"DestinationFormat is obsolete. {DestinationFormat}");
            }

            if (!Directory.Exists(DestinationPath))
            {
                throw new OrderException("DestinationPath does not exist.");
            }

            IntroDuration = ValidateStichAttachments(IntroFilePath, nameof(IntroFilePath), mediaInfoFacade);
            OutroDuration = ValidateStichAttachments(OutroFilePath, nameof(OutroFilePath), mediaInfoFacade);

            //TODO: Disabled until further notice. This check fails as it doens't check "Everyone". We might need to rethink this entirely. :bomb:
            ////Check if we have write permission on destination
            //var acc = new NTAccount(Environment.UserName);
            //var accessGranted = false;
            //string rights = $"Rights found on {acc.Value}: ";
            //var secId = acc.Translate(typeof(SecurityIdentifier)) as SecurityIdentifier;
            //if (secId == null)
            //    throw new OrderException($"Unable to read SecurityIdentifier from {Environment.UserName}");
            //var dInfo = new DirectoryInfo(DestinationPath);
            //var dSecurity = dInfo.GetAccessControl();
            //var rules = dSecurity.GetAccessRules(true, true, typeof(SecurityIdentifier));

            //foreach (FileSystemAccessRule ar in rules)
            //{
            //    if (secId.CompareTo(ar.IdentityReference as SecurityIdentifier) == 0)
            //    {
            //        Debug.WriteLine($"We found the rights for the user: {ar.FileSystemRights}");
            //        rights += ar.FileSystemRights + " ";
            //        if ((ar.FileSystemRights & FileSystemRights.Write) == FileSystemRights.Write)
            //        {
            //            Debug.WriteLine("ACCESS GRANTED!");
            //            accessGranted = true;
            //        }
            //    }
            //}
            //if (!accessGranted) throw new OrderException("Can't write to destination. Only found following: "+ rights);

            if (!Priority.HasValue)
            {
                Priority = Marvin.Model.Priority.low;
            }

            if (!string.IsNullOrEmpty(CallbackUrl) && !Uri.IsWellFormedUriString(CallbackUrl, UriKind.Absolute))
            {
                throw new OrderException("Callback URL is not valid");
            }

            if (!string.IsNullOrEmpty(DestinationFilename))
            {
                var invalidChars = Path.GetInvalidFileNameChars();
                foreach (var c in DestinationFilename.Where(c => invalidChars.Contains(c)))
                {
                    throw new OrderException($"{DestinationFilename} contains illigal character: {c}");
                }
            }

            Issued = timeProvider.GetUtcNow();
            if (!DueDate.HasValue || DueDate.Value < Issued)
            {
                DueDate = Issued;
            }

            if (string.IsNullOrEmpty(Name))
            {
                Name = new FileInfo(FilePath).Name;
            }

            if (string.IsNullOrEmpty(SourceUrn))
            {
                SourceUrn = null;
            }
            else if (!_urnValidation.IsMatch(SourceUrn))
            {
                throw new OrderException($"Invalid source urn {SourceUrn} supplied");
            }

            Validated = true;
        }