/// <summary> /// Process one song. /// Song in this context is a song file. /// Some songs have multiple song files (an sm and an ssc version). /// Will add charts, copy the charts and non-chart files to the output directory, /// and write visualizations for the conversion. /// </summary> /// <param name="songArgs">SongArgs for the song file.</param> private static async Task ProcessSong(SongArgs songArgs) { // Load the song. Song song; try { var reader = Reader.CreateReader(songArgs.FileInfo); if (reader == null) { LogError("Unsupported file format. Cannot parse.", songArgs.FileInfo, songArgs.RelativePath); return; } song = await reader.Load(); } catch (Exception e) { LogError($"Failed to load file. {e}", songArgs.FileInfo, songArgs.RelativePath); return; } // Add new charts. AddCharts(song, songArgs); // Save var saveFile = Fumen.Path.GetWin32FileSystemFullPath(Fumen.Path.Combine(songArgs.SaveDir, songArgs.FileInfo.Name)); var config = new SMWriterBase.SMWriterBaseConfig { FilePath = saveFile, Song = song, MeasureSpacingBehavior = SMWriterBase.MeasureSpacingBehavior.UseSubDivisionDenominatorAsMeasureSpacing, PropertyEmissionBehavior = SMWriterBase.PropertyEmissionBehavior.MatchSource }; var fileFormat = FileFormat.GetFileFormatByExtension(songArgs.FileInfo.Extension); switch (fileFormat.Type) { case FileFormatType.SM: new SMWriter(config).Save(); break; case FileFormatType.SSC: new SSCWriter(config).Save(); break; default: LogError("Unsupported file format. Cannot save.", songArgs.FileInfo, songArgs.RelativePath); break; } // Copy the non-chart files. CopyNonChartFiles(songArgs.CurrentDir, songArgs.SaveDir); }
/// <summary> /// Factory method to create the appropriate Reader based on the given file. /// </summary> /// <param name="fileInfo">FileInfo for the file to read.</param> /// <returns> /// New Reader for reading the given or null if no appropriate Reader exists. /// </returns> public static Reader CreateReader(FileInfo fileInfo) { var fileFormat = FileFormat.GetFileFormatByExtension(fileInfo.Extension.ToLower()); if (fileFormat == null) { return(null); } var fullName = Fumen.Path.GetWin32FileSystemFullPath(fileInfo.FullName); switch (fileFormat.Type) { case FileFormatType.SM: return(new SMReader(fullName)); case FileFormatType.SSC: return(new SSCReader(fullName)); } return(null); }
/// <summary> /// Copies the non-chart files from the given song directory into the given save directory. /// </summary> /// <remarks> /// Idempotent. /// Will not copy if already invoked with the same song directory. /// Will not copy if not appropriate based on Config.NonChartFileCopyBehavior. /// Expects that saveDir exists and is writable. /// Will log errors and warnings on failures. /// </remarks> /// <param name="songDir"> /// Directory of the song to copy the non-chart files from. /// </param> /// <param name="saveDir"> /// Directory to copy the non-chart files into. /// </param> private static void CopyNonChartFiles(string songDir, string saveDir) { if (Config.Instance.NonChartFileCopyBehavior == CopyBehavior.DoNotCopy || Config.Instance.IsOutputDirectorySameAsInputDirectory()) { return; } // Only copy the non-chart files once per song. lock (CopiedDirectories) { if (CopiedDirectories.Contains(songDir)) { return; } CopiedDirectories.Add(songDir); } // Get the files in the song directory. string[] files; try { files = Directory.GetFiles(songDir); } catch (Exception e) { LogWarn($"Could not get files in \"{songDir}\". {e}"); return; } // Check each file for copying foreach (var file in files) { // Get the FileInfo for this file so we can check its name. FileInfo fi; try { fi = new FileInfo(file); } catch (Exception e) { LogWarn($"Could not get file info for \"{file}\". {e}"); continue; } // Skip this file if it is a chart. var fileFormat = FileFormat.GetFileFormatByExtension(fi.Extension); if (fileFormat != null && SupportedFileFormats.Contains(fileFormat.Type)) { continue; } // Skip this file if it is not newer than the destination file and we // should only copy if newer. var destFilePath = saveDir + fi.Name; if (Config.Instance.NonChartFileCopyBehavior == CopyBehavior.IfNewer) { FileInfo dfi; try { dfi = new FileInfo(destFilePath); } catch (Exception e) { LogWarn($"Could not get file info for \"{destFilePath}\". {e}"); continue; } if (dfi.Exists && fi.LastWriteTime <= dfi.LastWriteTime) { continue; } } // Copy the file. try { File.Copy(Fumen.Path.GetWin32FileSystemFullPath(fi.FullName), Fumen.Path.GetWin32FileSystemFullPath(destFilePath), true); } catch (Exception e) { LogWarn($"Failed to copy \"{fi.FullName}\" to \"{destFilePath}\". {e}"); } } }
/// <summary> /// Searches for songs matching Config parameters and processes each. /// Will add charts, copy the charts and non-chart files to the output directory, /// and write visualizations for the conversion. /// </summary> private static async Task FindAndProcessCharts() { if (!Directory.Exists(Config.Instance.InputDirectory)) { LogError($"Could not find InputDirectory \"{Config.Instance.InputDirectory}\"."); return; } var songTasks = new List <Task>(); var pathSep = System.IO.Path.DirectorySeparatorChar.ToString(); // Search through the configured InputDirectory and all subdirectories. var dirs = new Stack <string>(); dirs.Push(Config.Instance.InputDirectory); while (dirs.Count > 0) { // Get the directory to process. var currentDir = dirs.Pop(); // Get sub directories for the next loop. try { var subDirs = Directory.GetDirectories(currentDir); // Reverse sort the subdirectories since we use a queue to pop. // Sorting helps the user get a rough idea of progress, and makes it easier to tell if a song pack is complete. Array.Sort(subDirs, (a, b) => String.Compare(b, a, StringComparison.CurrentCultureIgnoreCase)); foreach (var str in subDirs) { dirs.Push(str); } } catch (Exception e) { LogWarn($"Could not get directories in \"{currentDir}\". {e}"); continue; } // Get all files in this directory. string[] files; try { files = Directory.GetFiles(currentDir); } catch (Exception e) { LogWarn($"Could not get files in \"{currentDir}\". {e}"); continue; } // Cache some paths needed for processing the charts. var relativePath = currentDir.Substring( Config.Instance.InputDirectory.Length, currentDir.Length - Config.Instance.InputDirectory.Length); if (relativePath.StartsWith(pathSep)) { relativePath = relativePath.Substring(1, relativePath.Length - 1); } if (!relativePath.EndsWith(pathSep)) { relativePath += pathSep; } var saveDir = Fumen.Path.Combine(Config.Instance.OutputDirectory, relativePath); // Check each file. var hasSong = false; foreach (var file in files) { // Get the FileInfo for this file so we can check its name. FileInfo fi; try { fi = new FileInfo(file); } catch (Exception e) { LogWarn($"Could not get file info for \"{file}\". {e}"); continue; } // Check that this is a supported file format. var fileFormat = FileFormat.GetFileFormatByExtension(fi.Extension); if (fileFormat == null || !SupportedFileFormats.Contains(fileFormat.Type)) { continue; } // Check if the matches the expression for files to convert. if (!Config.Instance.InputNameMatches(fi.Name)) { continue; } // Create the save directory before starting any Tasks which write into it. if (!hasSong) { hasSong = true; Directory.CreateDirectory(saveDir); } // Process the song. songTasks.Add(ProcessSong(new SongArgs { FileInfo = fi, CurrentDir = currentDir, RelativePath = relativePath, SaveDir = saveDir })); } // TODO: Copy the song's pack assets. } // Allow the song tasks to complete. await Task.WhenAll(songTasks.ToArray()); }