/// <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 ); }
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 ) )); }
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); }
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%" ); }
//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>() )); }
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 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>() )); }