Example #1
0
        public static IEnumerable <FFmpegCommand> BuildFFmpegCommandSeries(
            FFmpegInput inputVideo,
            FFmpegOutput outputFile,
            DirectoryInfo tempFilesFolder,
            IEnumerable <TaggedBeat> taggedBeats,
            BeatPositioner beatPositioner,
            int approxGlowsPerStage
            )
        {
            ErrorUtils.ThrowIfArgNull(inputVideo, nameof(inputVideo));
            ErrorUtils.ThrowIfArgNull(outputFile, nameof(outputFile));
            ErrorUtils.ThrowIfArgNull(taggedBeats, nameof(taggedBeats));
            ErrorUtils.ThrowIfArgNull(beatPositioner, nameof(beatPositioner));
            ErrorUtils.ThrowIfArgLessThan(approxGlowsPerStage, 1, nameof(approxGlowsPerStage));

            var beatGroupInfos = GetBeatGroupInfos(inputVideo, outputFile, tempFilesFolder, taggedBeats, approxGlowsPerStage);

            return(beatGroupInfos.Select(
                       (x, i) => GlowOverlayCommandBuilder.BuildFFmpegCommand(
                           x.InputFile,
                           x.OutputFile,
                           x.BeatGroup,
                           beatPositioner
                           )
                       ));
        }
Example #2
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="inputVideo"></param>
        /// <param name="outputFile"></param>
        /// <param name="taggedBeats"></param>
        /// <param name="beatPositioner"></param>
        /// <param name="approxGlowsPerStage">
        /// Multiple glows in a single beat cannot be divided, so in these cases this value may be exceeded.
        /// For the last stage, this value will not be reached if there are too few glows remaining.
        /// </param>
        /// <returns></returns>
        public static string BuildBatchCommand(
            FFmpegInput inputVideo,
            FFmpegOutput outputFile,
            IEnumerable <TaggedBeat> taggedBeats,
            BeatPositioner beatPositioner,
            int approxGlowsPerStage
            )
        {
            var tempFilesFolder = new DirectoryInfo(
                Path.Combine(
                    Path.GetTempPath(),
                    Path.GetFileNameWithoutExtension(Path.GetRandomFileName())
                    )
                );

            tempFilesFolder.Create();

            var ffmpegCommands = BuildFFmpegCommandSeries(
                inputVideo,
                outputFile,
                tempFilesFolder,
                taggedBeats,
                beatPositioner,
                approxGlowsPerStage
                );

            return(
                string.Join(
                    " && ",
                    ffmpegCommands
                    )
                + $" & DEL /P \"{tempFilesFolder.FullName}\""                 //Gives the user a prompt before deleing, and empties the folder
                + $" && RMDIR \"{tempFilesFolder.FullName}\""                 //Doesn't prompt, and only deletes the folder if it's empty
                );
        }
Example #3
0
        private static List <BeatGroupInfo> GetBeatGroupInfos(
            FFmpegInput inputVideo,
            FFmpegOutput outputFile,
            DirectoryInfo tempFilesFolder,
            IEnumerable <TaggedBeat> taggedBeats,
            int approxGlowsPerStage
            )
        {
            //	var glows = taggedBeats.SelectMany(
            //		beat => beat.Tags.OfType<Glow>().Select(
            //			tag => (beat, tag)
            //		)
            //	);

            //Make this a list, as the last element needs to be different,
            //but we only know how long it is after iterating through the whole Chunk() query.
            //By using a list it can just be modified after instead of iterating twice (and each
            //iteration creates a whole series of ReadOnlyCollections with backing lists,
            //so there's no extra memory cost or anything)
            var beatGroupInfos = new List <BeatGroupInfo>();

            var beatGroups = taggedBeats.Chunk(
                chunkSize: approxGlowsPerStage,
                counter: beat => beat.Tags.OfType <Glow>().Count()
                );

            FFmpegInput prevFile = inputVideo;

            foreach (var beatGroup in beatGroups)
            {
                FFmpegOutput nextFile = new FFmpegOutput(
                    file: new FileInfo(
                        Path.Combine(
                            tempFilesFolder.FullName,
                            Path.ChangeExtension(
                                Path.GetRandomFileName(),
                                FFmpegOutput.LosslessOutputExtension
                                )
                            )
                        ),
                    modifiers: FFmpegOutput.LosslessOutputOptions
                    );
                beatGroupInfos.Add(new BeatGroupInfo(beatGroup, prevFile, nextFile));

                prevFile = new FFmpegInput(nextFile.File);
            }

            var lastBeatGroupInfo = beatGroupInfos[beatGroupInfos.Count - 1];

            beatGroupInfos[beatGroupInfos.Count - 1] = new BeatGroupInfo(
                beatGroup: lastBeatGroupInfo.BeatGroup,
                inputFile: lastBeatGroupInfo.InputFile,
                outputFile: outputFile
                );

            return(beatGroupInfos);
        }
Example #4
0
            public BeatGroupInfo(ReadOnlyCollection <TaggedBeat> beatGroup, FFmpegInput inputFile, FFmpegOutput outputFile)
            {
                ErrorUtils.ThrowIfArgNull(beatGroup, nameof(beatGroup));
                ErrorUtils.ThrowIfArgNull(inputFile, nameof(inputFile));
                ErrorUtils.ThrowIfArgNull(outputFile, nameof(outputFile));

                this.BeatGroup  = beatGroup;
                this.InputFile  = inputFile;
                this.OutputFile = outputFile;
            }
        private static IEnumerable <FFmpegCommand> BuildCutGlowOverlayCommands(
            FFmpegInput cutVideo,
            double cutStartTime,
            double cutEndTime,
            IEnumerable <TaggedBeat> cutBeats,
            BeatPositioner beatPositioner,
            DirectoryInfo tempFilesFolder,
            int approxGlowsPerSubstage,
            out FFmpegOutput glowOverlayedCutVideo
            )
        {
            glowOverlayedCutVideo = new FFmpegOutput(
                file: new FileInfo(
                    Path.Combine(
                        tempFilesFolder.FullName,
                        Path.ChangeExtension(
                            Path.GetRandomFileName(),
                            FFmpegOutput.LosslessOutputExtension
                            )
                        )
                    ),
                modifiers: FFmpegOutput.LosslessOutputOptions
                );

            //?//	double cutFirstBeatTime = cutBeats.Min(b => beatPositioner.BeatToTime(b.Beat));

            Debug.WriteLine("cutStartTime: " + cutStartTime + ", beatPositioner.FirstBeat: " + beatPositioner.FirstBeat + ", first beat beat-time: " + cutBeats.First().Beat);

            var newBP = new BeatPositioner(
                firstBeat: beatPositioner.FirstBeat - cutStartTime,
                interval: beatPositioner.Interval,
                barLength: beatPositioner.BarLength
                );

            Debug.WriteLine("beatPositioner.FirstBeat - cutStartTime: " + (beatPositioner.FirstBeat - cutStartTime) + ", newBP.FirstBeat: " + newBP.FirstBeat);

            return(StaggeredGlowOverlayCommandBuilder.BuildFFmpegCommandSeries(
                       inputVideo: cutVideo,
                       outputFile: glowOverlayedCutVideo,
                       tempFilesFolder: tempFilesFolder,
                       taggedBeats: cutBeats,
                       beatPositioner: newBP,
                       approxGlowsPerStage: approxGlowsPerSubstage
                       ));
        }
        public static string BuildBatchCommand(
            FFmpegInput inputVideo,
            FFmpegOutput outputFile,
            IEnumerable <TaggedBeat> taggedBeats,
            BeatPositioner beatPositioner,
            int approxGlowsPerCut,
            int approxGlowsPerSubstage
            )
        {
            var tempFilesFolder = new DirectoryInfo(
                Path.Combine(
                    Path.GetTempPath(),
                    Path.GetFileNameWithoutExtension(Path.GetRandomFileName())
                    )
                );

            tempFilesFolder.Create();

            var ffmpegCommands = BuildFFmpegCommandSeries(
                inputVideo,
                outputFile,
                tempFilesFolder,
                taggedBeats,
                beatPositioner,
                approxGlowsPerCut,
                approxGlowsPerSubstage
                );

            return(              // && == proceed if previous command succeeded, & == proceed regardless of success
                "ECHO Started at %DATE% %TIME%"
                + Environment.NewLine
                + string.Join(
                    Environment.NewLine,
                    ffmpegCommands
                    )
                + Environment.NewLine
                + $"ECHO Finished processing at %DATE% %TIME%"            //TODO: Fix the fact that this is evaluated at the start of the command, not when it's relevant
                + Environment.NewLine
                + $"DEL /P \"{tempFilesFolder.FullName}\""                //Gives the user a prompt before deleing, and empties the folder
                + Environment.NewLine
                + $"RMDIR \"{tempFilesFolder.FullName}\""                 //Doesn't prompt, and only deletes the folder if it's empty
                + Environment.NewLine
                + $"ECHO Exited at %DATE% %TIME%"
                );
        }
Example #7
0
        //Note: Use FFmpegInput and FFmpegOutput instead of FileInfos so that
        //codec information and other options can be easily passed in by the caller.
        public static FFmpegCommand BuildFFmpegCommand(
            FFmpegInput inputVideo,
            FFmpegOutput outputFile,
            IEnumerable <TaggedBeat> taggedBeats,
            BeatPositioner beatPositioner
            )
        {
            ErrorUtils.ThrowIfArgNull(inputVideo, nameof(inputVideo));
            ErrorUtils.ThrowIfArgNull(outputFile, nameof(outputFile));
            ErrorUtils.ThrowIfArgNull(taggedBeats, nameof(taggedBeats));
            ErrorUtils.ThrowIfArgNull(beatPositioner, nameof(beatPositioner));

            var glowTypeInfos = GetGlowTypeInfos(taggedBeats);

            int totalGlows = glowTypeInfos.Sum(x => x.Value.count);

            var inputs = GetInputs(inputVideo, glowTypeInfos).ToImmutableList();


            return(new FFmpegCommand(
                       inputs: GetInputs(inputVideo, glowTypeInfos).ToImmutableList(),
                       filterGraph: new FFmpegComplexFilterGraph(
                           ImmutableList.CreateRange(
                               Enumerable.Concat(
                                   GetGlowDuplicatorFilterChains(glowTypeInfos),
                                   GetGlowModifyAndOverlayFilterChains(glowTypeInfos, taggedBeats, beatPositioner)
                                   )
                               )
                           ),
                       otherMidOptions: ImmutableList.Create(
                           new FFmpegOption("pix_fmt", "yuv420p"),
                           new FFmpegOption("c:a", "copy")
                           //	new FFmpegOption("preset", "ultrafast")
                           ),
                       outputs: ImmutableList.Create(
                           outputFile
                           ),
                       otherFinalOptions: ImmutableList.Create <FFmpegOption>()
                       ));
        }
Example #8
0
 private static IEnumerable <FFmpegInput> GetInputs(FFmpegInput inputVideo, Dictionary <GlowType, (int index, int count)> glowTypeInfos)
        public static IEnumerable <FFmpegCommand> BuildFFmpegCommandSeries(
            FFmpegInput inputVideo,
            FFmpegOutput outputFile,
            DirectoryInfo tempFilesFolder,
            IEnumerable <TaggedBeat> taggedBeats,
            BeatPositioner beatPositioner,
            int approxGlowsPerCut,
            int approxGlowsPerSubstage
            )
        {
            List <IndependantGlowSeries> cuts = IndependantGlowSeries.Split(taggedBeats, beatPositioner, approxGlowsPerCut);

            var prevResult = inputVideo;

            var glowOverlayedCutVideos = new List <(FFmpegOutput file, double cutStartTime, double cutEndTime)>();

            for (int i = 0; i < cuts.Count; i++)
            {
                bool isLastCut = i < cuts.Count - 1;

                double cutStartTime = cuts[i].GlowStartTime;
                double cutEndTime   = cuts[i].GlowEndTime;

                //Build a command that cuts the relevant region out of the source video
                yield return(BuildCutCommand(
                                 originalInputVideo: inputVideo,
                                 cutStartTime: cutStartTime,
                                 cutEndTime: cutEndTime,
                                 tempFilesFolder: tempFilesFolder,
                                 cutVideo: out FFmpegOutput cutVideo
                                 ));


                //Build commands that overlay glows onto the cut region
                IEnumerable <FFmpegCommand> glowOverlayCommands = BuildCutGlowOverlayCommands(
                    cutVideo: new FFmpegInput(cutVideo.File),
                    cutStartTime: cutStartTime,
                    cutEndTime: cutEndTime,
                    cutBeats: cuts[i].Beats,
                    beatPositioner: beatPositioner,
                    tempFilesFolder: tempFilesFolder,
                    approxGlowsPerSubstage: approxGlowsPerSubstage,
                    glowOverlayedCutVideo: out FFmpegOutput glowOverlayedCutVideo
                    );
                glowOverlayedCutVideos.Add((
                                               file: glowOverlayedCutVideo,
                                               cutStartTime: cutStartTime,
                                               cutEndTime: cutEndTime
                                               ));
                foreach (var goc in glowOverlayCommands)
                {
                    yield return(goc);
                }
            }

            //Build a command that overlays each cut region ontop of the source video, at their original times
            yield return(BuildReoverlayCommand(
                             originalInputVideo: inputVideo,
                             glowOverlayedCutVideos: glowOverlayedCutVideos,
                             outputFile: outputFile
                             ));
        }
 private static FFmpegCommand BuildReoverlayCommand(
     FFmpegInput originalInputVideo,
     IReadOnlyCollection <(FFmpegOutput file, double cutStartTime, double cutEndTime)> glowOverlayedCutVideos,
 private static FFmpegCommand BuildCutCommand(
     FFmpegInput originalInputVideo,
     double cutStartTime,
     double cutEndTime,
     DirectoryInfo tempFilesFolder,
     out FFmpegOutput cutVideo
     )
 {
     cutVideo = new FFmpegOutput(
         file: new FileInfo(
             Path.Combine(
                 tempFilesFolder.FullName,
                 Path.ChangeExtension(
                     Path.GetRandomFileName(),
                     FFmpegOutput.LosslessOutputExtension
                     )
                 )
             ),
         modifiers: FFmpegOutput.LosslessOutputOptions
         );
     return(new FFmpegCommand(
                inputs: ImmutableList.Create(originalInputVideo),
                filterGraph: new FFmpegComplexFilterGraph(
                    new FFmpegFilterChain(
                        new FFmpegFilterChainItem(
                            inputStreams: ImmutableList.Create(
                                new FFmpegPad("0:v")
                                ),
                            filter: new FFmpegFilter(
                                "trim",
                                //Format values to avoid scientific notation, which FFmpeg can't parse
                                new FFmpegFilterOption("start", cutStartTime.ToString("F10")),
                                new FFmpegFilterOption("end", cutEndTime.ToString("F10"))
                                )
                            ),
                        new FFmpegFilterChainItem(
                            new FFmpegFilter(
                                "setpts",
                                new FFmpegFilterOption("expr", $"PTS-STARTPTS-1.433367")
                                //Idk where 1.433367 comes from but every output file was saying
                                //"start: 1.433367" when read by ffmpeg and I needed a quick fix.
                                )
                            )
                        ),
                    new FFmpegFilterChain(
                        new FFmpegFilterChainItem(
                            inputStreams: ImmutableList.Create(
                                new FFmpegPad("0:a")
                                ),
                            new FFmpegFilter(
                                "atrim",
                                //Format values to avoid scientific notation, which FFmpeg can't parse
                                new FFmpegFilterOption("start", cutStartTime.ToString("F10")),
                                new FFmpegFilterOption("end", cutEndTime.ToString("F10"))
                                )
                            ),
                        new FFmpegFilterChainItem(
                            new FFmpegFilter(
                                "asetpts",
                                new FFmpegFilterOption("expr", $"PTS-STARTPTS")
                                )
                            )
                        )
                    ),
                otherMidOptions: ImmutableList.Create(
                    new FFmpegOption("pix_fmt", "yuv420p")
                    //new FFmpegOption("c:a", "copy")
                    //	new FFmpegOption("preset", "ultrafast")
                    ),
                outputs: ImmutableList.Create(cutVideo),
                otherFinalOptions: ImmutableList.Create <FFmpegOption>()
                ));
 }