├── project.json ├── omnisharp.json ├── Remove leading zeroes in track number.csx ├── omnisharp.rsp ├── Split disc number.csx ├── Split track number.csx ├── MAKE UPPERCASE.csx ├── make lowercase.csx ├── Trim white-space characters.csx ├── Use two digits for track number.csx ├── Remove from value.csx ├── Search & Replace.csx ├── Use sentence case capitalization.csx ├── Autofill disc number.csx ├── Transliterate Cyrillic - Latin characters.csx ├── Convert eASCII characters from Latin1 to UTF8.csx ├── .default.csx ├── README.md ├── .gitignore ├── Use Title Case Capitalization.csx └── LICENSE /project.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /omnisharp.json: -------------------------------------------------------------------------------- 1 | { 2 | "Script": { 3 | "RspFilePath": "omnisharp.rsp" 4 | } 5 | } -------------------------------------------------------------------------------- /Remove leading zeroes in track number.csx: -------------------------------------------------------------------------------- 1 | // This script removes the leading zeroes in the track number, the opposite of "Use two digits for track number" 2 | 3 | using System; 4 | using Metatogger.Data; 5 | 6 | foreach (var file in files) 7 | { 8 | if (Int32.TryParse(file.GetFirstValue(TagName.TrackNumber), out int trackNumber)) 9 | file.SetTag(TagName.TrackNumber, trackNumber.ToString()); 10 | } -------------------------------------------------------------------------------- /omnisharp.rsp: -------------------------------------------------------------------------------- 1 | /r:"C:/Program Files/Metatogger 7.5/Metatogger.dll" 2 | /r:"C:/Program Files/Metatogger 7.4/Metatogger.dll" 3 | /r:"C:/Program Files/Metatogger 7.3/Metatogger.dll" 4 | /r:"C:/Program Files/Metatogger 7.2/Metatogger.dll" 5 | /r:"C:/Program Files/Metatogger 7.1/Metatogger.dll" 6 | /r:"C:/Program Files/Metatogger 7.0/Metatogger.dll" 7 | /r:"E:/Home/Important/Development/Projects/Metatogger/Sources/Metatogger/bin/Debug/Metatogger.dll" 8 | -------------------------------------------------------------------------------- /Split disc number.csx: -------------------------------------------------------------------------------- 1 | // This script replaces disc number format used in iTunes "disc number/total number of discs" ("1/2" -> "1") 2 | 3 | using Metatogger.Data; 4 | 5 | foreach (var file in files) 6 | { 7 | string discNumber = file.GetFirstValue(TagName.DiscNumber); 8 | if (discNumber != null) 9 | { 10 | int index = discNumber.IndexOf('/'); 11 | if (index != -1) 12 | file.SetTag(TagName.DiscNumber, discNumber.Substring(0, index)); 13 | } 14 | } -------------------------------------------------------------------------------- /Split track number.csx: -------------------------------------------------------------------------------- 1 | // This script replaces track number format used in iTunes "track number/total number of tracks" ("3/10" -> "3") 2 | 3 | using Metatogger.Data; 4 | 5 | foreach (var file in files) 6 | { 7 | string trackNumber = file.GetFirstValue(TagName.TrackNumber); 8 | if (trackNumber != null) 9 | { 10 | int index = trackNumber.IndexOf('/'); 11 | if (index != -1) 12 | file.SetTag(TagName.TrackNumber, trackNumber.Substring(0, index)); 13 | } 14 | } -------------------------------------------------------------------------------- /MAKE UPPERCASE.csx: -------------------------------------------------------------------------------- 1 | // This script changes tags values to uppercase ("The Artist" -> "THE ARTIST") 2 | 3 | using System.Linq; 4 | using Metatogger.Data; 5 | 6 | // enter tags name to process => eg. { "TAGNAME1", "TAGNAME2", TagName.TrackNumber } 7 | // leave the array empty to process all tags => { } 8 | string[] tagsToProcess = { }; 9 | 10 | foreach (var file in files) 11 | foreach (var tag in file.GetAllTags().Where(kvp => tagsToProcess.Length == 0 || tagsToProcess.Contains(kvp.Key))) 12 | foreach (string tagValue in tag.Value) 13 | file.SetTagValue(tag.Key, tagValue, tagValue.ToUpper()); -------------------------------------------------------------------------------- /make lowercase.csx: -------------------------------------------------------------------------------- 1 | // This script changes tags values to lowercase ("The Artist" -> "the artist") 2 | 3 | using System.Linq; 4 | using Metatogger.Data; 5 | 6 | // enter tags name to process => eg. { "TAGNAME1", "TAGNAME2", TagName.TrackNumber } 7 | // leave the array empty to process all tags => { } 8 | string[] tagsToProcess = { }; 9 | 10 | foreach (var file in files) 11 | foreach (var tag in file.GetAllTags().Where(kvp => tagsToProcess.Length == 0 || tagsToProcess.Contains(kvp.Key))) 12 | foreach (string tagValue in tag.Value) 13 | file.SetTagValue(tag.Key, tagValue, tagValue.ToLower()); -------------------------------------------------------------------------------- /Trim white-space characters.csx: -------------------------------------------------------------------------------- 1 | // This script removes all leading and trailing white-space characters from tags value (" The Artist " -> "The Artist") 2 | 3 | using System.Linq; 4 | using Metatogger.Data; 5 | 6 | // enter tags name to process => eg. { "TAGNAME1", "TAGNAME2", TagName.TrackNumber } 7 | // leave the array empty to process all tags => { } 8 | string[] tagsToProcess = { }; 9 | 10 | foreach (var file in files) 11 | foreach (var tag in file.GetAllTags().Where(kvp => tagsToProcess.Length == 0 || tagsToProcess.Contains(kvp.Key))) 12 | foreach (string tagValue in tag.Value) 13 | file.SetTagValue(tag.Key, tagValue, tagValue.Trim()); -------------------------------------------------------------------------------- /Use two digits for track number.csx: -------------------------------------------------------------------------------- 1 | // This script add a zero in front of track number if it is less than ten 2 | 3 | using Metatogger.Data; 4 | 5 | foreach (var file in files) 6 | { 7 | string currentTrackNumber = file.GetFirstValue(TagName.TrackNumber); 8 | if (currentTrackNumber != null) 9 | { 10 | string trackNumber = currentTrackNumber; 11 | 12 | // If track number contains '/' (e.g. "1/10"), only takes the text before the '/' 13 | int slashIndex = trackNumber.IndexOf('/'); 14 | if (slashIndex != -1) 15 | trackNumber = trackNumber.Substring(0, slashIndex); 16 | 17 | if (trackNumber.Length == 1) 18 | file.SetTag(TagName.TrackNumber, $"0{currentTrackNumber}"); 19 | } 20 | } -------------------------------------------------------------------------------- /Remove from value.csx: -------------------------------------------------------------------------------- 1 | // This script removes tags that contains specified value 2 | 3 | using System.Linq; 4 | using Metatogger.Data; 5 | 6 | // enter here the tag value that you want to remove 7 | string tagValueToRemove = ""; 8 | 9 | // enter tags name to process => eg. { "TAGNAME1", "TAGNAME2", TagName.TrackNumber } 10 | // leave the array empty to process all tags => { } 11 | string[] tagsToProcess = { }; 12 | 13 | foreach (var file in files) 14 | foreach (var tag in file.GetAllTags().Where(kvp => tagsToProcess.Length == 0 || tagsToProcess.Contains(kvp.Key))) 15 | foreach (string tagValue in tag.Value) 16 | if (tagValue == tagValueToRemove) 17 | file.SetTagValue(tag.Key, tagValue, null); -------------------------------------------------------------------------------- /Search & Replace.csx: -------------------------------------------------------------------------------- 1 | // This script replaces all occurrences of a specified string in tags value with another specified string 2 | 3 | using System.Linq; 4 | using Metatogger.Data; 5 | 6 | // enter what you want to search in changeThis (cannot be left empty) 7 | string changeThis = ""; 8 | 9 | // enter here what you want to enter (empty string or null will simply remove changeThis) 10 | string toThat = ""; 11 | 12 | // enter tags name to process => eg. { "TAGNAME1", "TAGNAME2", TagName.TrackNumber } 13 | // leave the array empty to process all tags => { } 14 | string[] tagsToProcess = { }; 15 | 16 | foreach (var file in files) 17 | foreach (var tag in file.GetAllTags().Where(kvp => tagsToProcess.Length == 0 || tagsToProcess.Contains(kvp.Key))) 18 | foreach (string tagValue in tag.Value) 19 | file.SetTagValue(tag.Key, tagValue, tagValue.Replace(changeThis, toThat)); -------------------------------------------------------------------------------- /Use sentence case capitalization.csx: -------------------------------------------------------------------------------- 1 | // This script convert the first letter of tags value to uppercase ("the artist" -> "The artist") 2 | 3 | using System; 4 | using System.Linq; 5 | using Metatogger.Data; 6 | 7 | // enter tags name to process => eg. { "TAGNAME1", "TAGNAME2", TagName.TrackNumber } 8 | // leave the array empty to process all tags => { } 9 | string[] tagsToProcess = { }; 10 | 11 | string UppercaseFirst(string tagValue) 12 | { 13 | if (tagValue.Length == 0) 14 | return tagValue; 15 | 16 | char[] tagChar = tagValue.ToCharArray(); 17 | tagChar[0] = Char.ToUpper(tagChar[0]); 18 | return new string(tagChar); 19 | } 20 | 21 | foreach (var file in files) 22 | foreach (var tag in file.GetAllTags().Where(kvp => tagsToProcess.Length == 0 || tagsToProcess.Contains(kvp.Key))) 23 | foreach (string tagValue in tag.Value) 24 | file.SetTagValue(tag.Key, tagValue, UppercaseFirst(tagValue)); -------------------------------------------------------------------------------- /Autofill disc number.csx: -------------------------------------------------------------------------------- 1 | // This script checks for the values of the "Album" tag and the "Disc Number" tag. 2 | // If the "Album" tag contains "CD#" or "CD #" where # is a number between 1-99, 3 | // it populates the "Disc Number" tag with that number, if the "Disc Number" tag is empty. 4 | // Optionally, it can also remove the disc number from the album tag. 5 | 6 | using System; 7 | using System.Text.RegularExpressions; 8 | using Metatogger.Data; 9 | 10 | bool cleanAlbumTag = true; 11 | var cdPattern = new Regex(@"\bCD\s*(\d{1,2})\b", RegexOptions.IgnoreCase); 12 | 13 | foreach (var file in files) 14 | { 15 | string album = file.GetFirstValue(TagName.Album); 16 | if (album != null) 17 | { 18 | Match match = cdPattern.Match(album); 19 | if (match.Success && Int32.TryParse(match.Groups[1].Value, out int discNumber)) 20 | { 21 | file.SetTag(TagName.DiscNumber, discNumber.ToString(), false); 22 | if (cleanAlbumTag) 23 | file.SetTagValue(TagName.Album, album, album.Replace(match.Value, null).Replace(" ", " ")); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Transliterate Cyrillic - Latin characters.csx: -------------------------------------------------------------------------------- 1 | // This script allows to transliterate cyrillic characters to latin and vice versa 2 | 3 | #load ".CyrillicTransliteration.csx" 4 | 5 | using System.Linq; 6 | using Metatogger.Data; 7 | 8 | // enter tags name to process => eg. { "TAGNAME1", "TAGNAME2", TagName.TrackNumber } 9 | // leave the array empty to process all tags => { } 10 | 11 | string[] tagsToProcess = { }; 12 | 13 | // Available transliteration methods: 14 | // Transliteration.CyrillicToLatin(string cyrillicSource, Language language = Language.Unknown) 15 | // Transliteration.LatinToCyrillic(string latinSource, Language language = Language.Unknown) 16 | 17 | // Available Language enumeration: 18 | // Unknown (Most common rules will be used for transliteration), Russian, Belorussian, Ukrainian, Bulgarian, Macedonian 19 | 20 | foreach (var file in files) 21 | foreach (var tag in file.GetAllTags().Where(kvp => tagsToProcess.Length == 0 || tagsToProcess.Contains(kvp.Key))) 22 | foreach (string tagValue in tag.Value) 23 | file.SetTagValue(tag.Key, tagValue, Transliteration.CyrillicToLatin(tagValue, Language.Unknown)); -------------------------------------------------------------------------------- /Convert eASCII characters from Latin1 to UTF8.csx: -------------------------------------------------------------------------------- 1 | // This script assumes that tags containing Extended ASCII characters 2 | // are poorly stored as Latin1 and converts their encoding to UTF-8. 3 | 4 | using System; 5 | using System.Text; 6 | using System.Linq; 7 | using System.Collections.Generic; 8 | using Metatogger.Data; 9 | 10 | // enter tags name to process => eg. { "TAGNAME1", "TAGNAME2", TagName.TrackNumber } 11 | // leave the array empty to process all tags => { } 12 | string[] tagsToProcess = { }; 13 | 14 | IEnumerable NonASCII(IEnumerable chars) => chars.Where(c => c > 127); 15 | IEnumerable NonEASCII(IEnumerable chars) => chars.Where(c => c > 255); 16 | 17 | string ConvertToUTF8(string oldValue) 18 | { 19 | Encoding srcEncoding = Encoding.Default; 20 | // if your tags have not been encoded with your system's default codepage, 21 | // you have to set the codepage that has been used, 22 | // e.g. for Cyrillic character encoding: 23 | // srcEncoding = Encoding.GetEncoding(1251); 24 | 25 | byte[] oData = Encoding.GetEncoding("iso-8859-1").GetBytes(oldValue); 26 | byte[] nData = Encoding.Convert(srcEncoding, Encoding.UTF8, oData); 27 | return Encoding.UTF8.GetString(nData); 28 | } 29 | 30 | foreach (var file in files) 31 | foreach (var tag in file.GetAllTags().Where(kvp => tagsToProcess.Length == 0 || tagsToProcess.Contains(kvp.Key))) 32 | foreach (string tagValue in tag.Value) 33 | { 34 | var na = NonASCII(tagValue); 35 | if(na.Any() && !NonEASCII(na).Any()) 36 | file.SetTagValue(tag.Key, tagValue, ConvertToUTF8(tagValue)); 37 | } -------------------------------------------------------------------------------- /.default.csx: -------------------------------------------------------------------------------- 1 | // Describe here what the script does 2 | 3 | using System.Linq; 4 | using Metatogger.Data; 5 | 6 | // Uncomment the following line to get better coding experience with VS Code, but DO NOT forget to comment it before running the script into host 7 | //System.Collections.Generic.IEnumerable files = new System.Collections.Generic.List(); // list of checked audio files loaded in the workspace 8 | 9 | // enter tags name to process => eg. { "TAGNAME1", "TAGNAME2", TagName.TrackNumber } 10 | // leave the array empty to process all tags => { } 11 | string[] tagsToProcess = { }; 12 | 13 | string NewValue(string oldValue) 14 | { 15 | return oldValue; // change here to return a custom tag value 16 | } 17 | 18 | foreach (var file in files) 19 | foreach (var tag in file.GetAllTags().Where(kvp => tagsToProcess.Length == 0 || tagsToProcess.Contains(kvp.Key))) 20 | foreach (string tagValue in tag.Value) 21 | file.SetTagValue(tag.Key, tagValue, NewValue(tagValue)); 22 | 23 | 24 | // The "files" variable contains the collection of audio files checked in Metatogger 25 | // Dictionary> AudioFile.GetAllTags() => returns all tags of an audio file 26 | // List AudioFile.GetValues(string tag) => returns all values of a tag 27 | // string AudioFile.GetFirstValue(string tag) => returns first tag value for a tag 28 | // AudioFile.SetTag(string tag, string value, bool overwrite = true, bool addIfExists = false) => add, overwrite or remove tag values (if value = null) 29 | // AudioFile.SetTagValue(string tag, string oldValue, string newValue) => replaces the value of a tag by a new value -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scripts repository for Metatogger 2 | 3 | Scripts for Metatogger are written is C# and executed by Roslyn. 4 | 5 | The coding experience is much better under Visual Studio Code (code completion, refactoring,...). 6 | 7 | The API documentation for the main classes available for scripting in Metatogger can be read online at [luminescence-software.github.io/scripts-doc/](https://luminescence-software.github.io/scripts-doc/). 8 | 9 | Here is a basic script template: 10 | 11 | ```cs 12 | // Describe here what the script does 13 | 14 | using System.Linq; 15 | using Metatogger.Data; 16 | 17 | // enter tags name to process => eg. { "TAGNAME1", "TAGNAME2", TagName.TrackNumber } 18 | // leave the array empty to process all tags => { } 19 | string[] tagsToProcess = { }; 20 | 21 | string NewValue(string oldValue) 22 | { 23 | return oldValue; // change here to return a custom tag value 24 | } 25 | 26 | foreach (var file in files) 27 | foreach (var tag in file.GetAllTags().Where(kvp => tagsToProcess.Length == 0 || tagsToProcess.Contains(kvp.Key))) 28 | foreach (string tagValue in tag.Value) 29 | file.SetTagValue(tag.Key, tagValue, NewValue(tagValue)); 30 | 31 | 32 | // The "files" variable contains the collection of audio files checked in Metatogger 33 | // Dictionary> AudioFile.GetAllTags() => returns all tags of an audio file 34 | // List AudioFile.GetValues(string tag) => returns all values of a tag 35 | // string AudioFile.GetFirstValue(string tag) => returns first tag value for a tag 36 | // AudioFile.SetTag(string tag, string value, bool overwrite = true, bool addIfExists = false) => add, overwrite or remove tag values (if value = null) 37 | // AudioFile.SetTagValue(string tag, string oldValue, string newValue) => replaces the value of a tag by a new value 38 | ``` 39 | 40 | Feel free to submit improvement or new script with "pull request". 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | [Rr]eleases/ 14 | x64/ 15 | x86/ 16 | build/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Roslyn cache directories 22 | *.ide/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | #NUNIT 29 | *.VisualState.xml 30 | TestResult.xml 31 | 32 | # Build Results of an ATL Project 33 | [Dd]ebugPS/ 34 | [Rr]eleasePS/ 35 | dlldata.c 36 | 37 | *_i.c 38 | *_p.c 39 | *_i.h 40 | *.ilk 41 | *.meta 42 | *.obj 43 | *.pch 44 | *.pdb 45 | *.pgc 46 | *.pgd 47 | #*.rsp 48 | *.sbr 49 | *.tlb 50 | *.tli 51 | *.tlh 52 | *.tmp 53 | *.tmp_proj 54 | *.log 55 | *.vspscc 56 | *.vssscc 57 | .builds 58 | *.pidb 59 | *.svclog 60 | *.scc 61 | 62 | # Chutzpah Test files 63 | _Chutzpah* 64 | 65 | # Visual C++ cache files 66 | ipch/ 67 | *.aps 68 | *.ncb 69 | *.opensdf 70 | *.sdf 71 | *.cachefile 72 | 73 | # Visual Studio profiler 74 | *.psess 75 | *.vsp 76 | *.vspx 77 | 78 | # TFS 2012 Local Workspace 79 | $tf/ 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | *.DotSettings.user 88 | 89 | # JustCode is a .NET coding addin-in 90 | .JustCode 91 | 92 | # TeamCity is a build add-in 93 | _TeamCity* 94 | 95 | # DotCover is a Code Coverage Tool 96 | *.dotCover 97 | 98 | # NCrunch 99 | _NCrunch_* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.[Pp]ublish.xml 127 | *.azurePubxml 128 | # TODO: Comment the next line if you want to checkin your web deploy settings 129 | # but database connection strings (with potential passwords) will be unencrypted 130 | *.pubxml 131 | *.publishproj 132 | 133 | # NuGet Packages 134 | *.nupkg 135 | # The packages folder can be ignored because of Package Restore 136 | **/packages/* 137 | # except build/, which is used as an MSBuild target. 138 | !**/packages/build/ 139 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 140 | #!**/packages/repositories.config 141 | 142 | # Windows Azure Build Output 143 | csx/ 144 | *.build.csdef 145 | 146 | # Windows Store app package directory 147 | AppPackages/ 148 | 149 | # Others 150 | sql/ 151 | *.Cache 152 | ClientBin/ 153 | [Ss]tyle[Cc]op.* 154 | ~$* 155 | *~ 156 | *.dbmdl 157 | *.dbproj.schemaview 158 | *.pfx 159 | *.publishsettings 160 | node_modules/ 161 | 162 | # RIA/Silverlight projects 163 | Generated_Code/ 164 | 165 | # Backup & report files from converting an old project file 166 | # to a newer Visual Studio version. Backup files are not needed, 167 | # because we have git ;-) 168 | _UpgradeReport_Files/ 169 | Backup*/ 170 | UpgradeLog*.XML 171 | UpgradeLog*.htm 172 | 173 | # SQL Server files 174 | *.mdf 175 | *.ldf 176 | 177 | # Business Intelligence projects 178 | *.rdl.data 179 | *.bim.layout 180 | *.bim_*.settings 181 | 182 | # Microsoft Fakes 183 | FakesAssemblies/ 184 | 185 | # Visual Studio Code settings 186 | .vscode/ -------------------------------------------------------------------------------- /Use Title Case Capitalization.csx: -------------------------------------------------------------------------------- 1 | // This script capitalize your tag value with the capitalization rules from the common English title case style ("a day in the life" -> "A Day in the Life") 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using System.Linq; 7 | using Metatogger.Data; 8 | 9 | // enter tags name to process => eg. { "TAGNAME1", "TAGNAME2", TagName.TrackNumber } 10 | // leave the array empty to process all tags => { } 11 | string[] tagsToProcess = { }; 12 | 13 | public static class EnglishTitleCapitalizer 14 | { 15 | public readonly static string[] Prepositions = new[] { "about", "above", "across", "after", "against", "along", "among", "around", "at", "before", "behind", "below", "beneath", "beside", "between", "beyond", "but", "by", "despite", "down", "during", "except", "for", "from", "in", "inside", "into", "like", "near", "of", "off", "on", "onto", "out", "outside", "over", "past", "per", "since", "through", "throughout", "till", "to", "toward", "under", "underneath", "until", "up", "upon", "via", "with", "within", "without", "versus" }; 16 | public readonly static string[] Articles = new[] { "a", "an", "the" }; 17 | public readonly static string[] Conjunctions = new[] { "and", "but", "or", "nor", "for", "yet", "so" }; 18 | public readonly static char[] EndingPunctuations = new[] { '.', '?', '!', ':' }; 19 | 20 | public readonly static HashSet MustBeUpperCase = new HashSet(StringComparer.InvariantCultureIgnoreCase) { "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII", "DJ", "MC", "TV", "MTV", "EP", "LP", "YMCA", "NYC", "NY", "USSR", "USA", "R&B", "BBC", "FM", "AC", "DC", "AC/DC", "UK", "BPM", "OK", "Q&A", "FAQ" }; 21 | public readonly static HashSet MustBeLowerCase = new HashSet(StringComparer.InvariantCultureIgnoreCase) { "ft", "ft.", "feat", "feat.", "n'", "'n'", "no." }; 22 | public readonly static HashSet TheGroups = new HashSet(StringComparer.InvariantCultureIgnoreCase) { "Beatles", "Police", "Corrs", "Doors", "Beach Boys" }; 23 | public readonly static Dictionary SpecialMixedCase = new Dictionary(StringComparer.InvariantCultureIgnoreCase) 24 | { 25 | ["rock'n'roll"] = "Rock 'n' Roll", 26 | ["o'clock"] = "O'Clock", 27 | ["djs"] = "DJs" 28 | }; 29 | 30 | private static int GetFirstCharIndex(string s) 31 | { 32 | for (int i = 0; i < s.Length; i++) 33 | { 34 | if (Char.IsLetterOrDigit(s[i])) 35 | return i; 36 | } 37 | 38 | return -1; 39 | } 40 | 41 | private static int GetLastCharIndex(string s) 42 | { 43 | for (int i = s.Length - 1; i >= 0; i--) 44 | { 45 | if (Char.IsLetterOrDigit(s[i])) 46 | return i; 47 | } 48 | 49 | return -1; 50 | } 51 | 52 | private static string GetWordCore(string s) 53 | { 54 | int firstCharIndex = GetFirstCharIndex(s); 55 | if (firstCharIndex == -1) 56 | return s; 57 | 58 | return s.Substring(firstCharIndex, GetLastCharIndex(s) + 1 - firstCharIndex); 59 | } 60 | 61 | private static string UpperCase(string s) 62 | { 63 | int firstCharIndex = GetFirstCharIndex(s); 64 | if (firstCharIndex == -1) 65 | return s; 66 | 67 | if (SpecialMixedCase.ContainsKey(s)) 68 | return SpecialMixedCase[s]; 69 | 70 | var sb = s.ToCharArray(); 71 | sb[firstCharIndex] = Char.ToUpper(sb[firstCharIndex]); 72 | 73 | for (int i = 0; i < sb.Length - 1; i++) 74 | { 75 | if (Char.GetUnicodeCategory(sb[i]) == UnicodeCategory.DashPunctuation) 76 | sb[i + 1] = Char.ToUpper(sb[i + 1]); 77 | } 78 | 79 | return new string(sb); 80 | } 81 | 82 | public static string Capitalize(string title, bool resetUpperCase = false, bool useTitleCase = true, int maxPrepositionLength = 3) 83 | { 84 | if (String.IsNullOrWhiteSpace(title)) 85 | return null; 86 | 87 | var lowerCaseWords = new HashSet(StringComparer.InvariantCultureIgnoreCase); 88 | if (useTitleCase) 89 | { 90 | lowerCaseWords.UnionWith(Prepositions.Where(word => word.Length <= maxPrepositionLength)); 91 | lowerCaseWords.UnionWith(Articles.Where(word => word.Length <= maxPrepositionLength)); 92 | lowerCaseWords.UnionWith(Conjunctions.Where(word => word.Length <= maxPrepositionLength)); 93 | } 94 | 95 | if (resetUpperCase) 96 | title = title.ToLower(); 97 | 98 | string[] words = title.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); 99 | 100 | for (int i = 0; i < words.Length; i++) 101 | { 102 | string word = words[i]; 103 | string wordCore = GetWordCore(word); 104 | 105 | if (MustBeUpperCase.Contains(wordCore) || MustBeUpperCase.Contains(word.Replace(".", null))) 106 | { 107 | words[i] = word.ToUpper(); 108 | } 109 | else if (MustBeLowerCase.Contains(word) || MustBeLowerCase.Contains(wordCore)) 110 | { 111 | words[i] = resetUpperCase ? word : word.ToLower(); 112 | } 113 | else if (lowerCaseWords.Contains(wordCore)) // the word should be lower case... 114 | { 115 | if (i == 0 // ...unless it's the first word 116 | || i == words.Length - 1 // ...unless it's the last word 117 | || Char.GetUnicodeCategory(word[0]) == UnicodeCategory.OpenPunctuation // ...unless the word begins with '(' 118 | || word[0] == '"' // ...unless the word begins with '"' 119 | || Char.GetUnicodeCategory(word[word.Length - 1]) == UnicodeCategory.ClosePunctuation // ...unless the word ends with ')' 120 | || word[word.Length - 1] == '"' // ...unless the word ends with '"' 121 | || Char.GetUnicodeCategory(words[i - 1][words[i - 1].Length - 1]) == UnicodeCategory.ClosePunctuation // ...unless the previous word ends with ')' 122 | || Char.GetUnicodeCategory(words[i - 1][words[i - 1].Length - 1]) == UnicodeCategory.DashPunctuation // ...unless the previous word ends with '-' 123 | || EndingPunctuations.Contains(words[i - 1][words[i - 1].Length - 1]) // ...unless the previous word ends with '.' 124 | || (String.Equals(wordCore, "The", StringComparison.InvariantCultureIgnoreCase) && TheGroups.Contains(GetWordCore(words[i + 1])))) // ...unless "the" is a part of band's name 125 | words[i] = UpperCase(word); 126 | else 127 | words[i] = resetUpperCase ? word : word.ToLower(); 128 | } 129 | else 130 | { 131 | words[i] = UpperCase(word); 132 | } 133 | } 134 | 135 | return String.Join(" ", words); 136 | } 137 | } 138 | 139 | foreach (var file in files) 140 | foreach (var tag in file.GetAllTags().Where(kvp => tagsToProcess.Length == 0 || tagsToProcess.Contains(kvp.Key))) 141 | foreach (string tagValue in tag.Value) 142 | file.SetTagValue(tag.Key, tagValue, EnglishTitleCapitalizer.Capitalize(tagValue)); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. "Contributor Version" 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. "Covered Software" 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the terms of 34 | a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. "Larger Work" 41 | 42 | means a work that combines Covered Software with other material, in a 43 | separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | 47 | means this document. 48 | 49 | 1.9. "Licensable" 50 | 51 | means having the right to grant, to the maximum extent possible, whether 52 | at the time of the initial grant or subsequently, any and all of the 53 | rights conveyed by this License. 54 | 55 | 1.10. "Modifications" 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, 60 | deletion from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. "Patent Claims" of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, 67 | process, and apparatus claims, in any patent Licensable by such 68 | Contributor that would be infringed, but for the grant of the License, 69 | by the making, using, selling, offering for sale, having made, import, 70 | or transfer of either its Contributions or its Contributor Version. 71 | 72 | 1.12. "Secondary License" 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. "Source Code Form" 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. "You" (or "Your") 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, "You" includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, "control" means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or 104 | as part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its 108 | Contributions or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution 113 | become effective for each Contribution on the date the Contributor first 114 | distributes such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under 119 | this License. No additional rights or licenses will be implied from the 120 | distribution or licensing of Covered Software under this License. 121 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 122 | Contributor: 123 | 124 | a. for any code that a Contributor has removed from Covered Software; or 125 | 126 | b. for infringements caused by: (i) Your and any other third party's 127 | modifications of Covered Software, or (ii) the combination of its 128 | Contributions with other software (except as part of its Contributor 129 | Version); or 130 | 131 | c. under Patent Claims infringed by Covered Software in the absence of 132 | its Contributions. 133 | 134 | This License does not grant any rights in the trademarks, service marks, 135 | or logos of any Contributor (except as may be necessary to comply with 136 | the notice requirements in Section 3.4). 137 | 138 | 2.4. Subsequent Licenses 139 | 140 | No Contributor makes additional grants as a result of Your choice to 141 | distribute the Covered Software under a subsequent version of this 142 | License (see Section 10.2) or under the terms of a Secondary License (if 143 | permitted under the terms of Section 3.3). 144 | 145 | 2.5. Representation 146 | 147 | Each Contributor represents that the Contributor believes its 148 | Contributions are its original creation(s) or it has sufficient rights to 149 | grant the rights to its Contributions conveyed by this License. 150 | 151 | 2.6. Fair Use 152 | 153 | This License is not intended to limit any rights You have under 154 | applicable copyright doctrines of fair use, fair dealing, or other 155 | equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under 169 | the terms of this License. You must inform recipients that the Source 170 | Code Form of the Covered Software is governed by the terms of this 171 | License, and how they can obtain a copy of this License. You may not 172 | attempt to alter or restrict the recipients' rights in the Source Code 173 | Form. 174 | 175 | 3.2. Distribution of Executable Form 176 | 177 | If You distribute Covered Software in Executable Form then: 178 | 179 | a. such Covered Software must also be made available in Source Code Form, 180 | as described in Section 3.1, and You must inform recipients of the 181 | Executable Form how they can obtain a copy of such Source Code Form by 182 | reasonable means in a timely manner, at a charge no more than the cost 183 | of distribution to the recipient; and 184 | 185 | b. You may distribute such Executable Form under the terms of this 186 | License, or sublicense it under different terms, provided that the 187 | license for the Executable Form does not attempt to limit or alter the 188 | recipients' rights in the Source Code Form under this License. 189 | 190 | 3.3. Distribution of a Larger Work 191 | 192 | You may create and distribute a Larger Work under terms of Your choice, 193 | provided that You also comply with the requirements of this License for 194 | the Covered Software. If the Larger Work is a combination of Covered 195 | Software with a work governed by one or more Secondary Licenses, and the 196 | Covered Software is not Incompatible With Secondary Licenses, this 197 | License permits You to additionally distribute such Covered Software 198 | under the terms of such Secondary License(s), so that the recipient of 199 | the Larger Work may, at their option, further distribute the Covered 200 | Software under the terms of either this License or such Secondary 201 | License(s). 202 | 203 | 3.4. Notices 204 | 205 | You may not remove or alter the substance of any license notices 206 | (including copyright notices, patent notices, disclaimers of warranty, or 207 | limitations of liability) contained within the Source Code Form of the 208 | Covered Software, except that You may alter any license notices to the 209 | extent required to remedy known factual inaccuracies. 210 | 211 | 3.5. Application of Additional Terms 212 | 213 | You may choose to offer, and to charge a fee for, warranty, support, 214 | indemnity or liability obligations to one or more recipients of Covered 215 | Software. However, You may do so only on Your own behalf, and not on 216 | behalf of any Contributor. You must make it absolutely clear that any 217 | such warranty, support, indemnity, or liability obligation is offered by 218 | You alone, and You hereby agree to indemnify every Contributor for any 219 | liability incurred by such Contributor as a result of warranty, support, 220 | indemnity or liability terms You offer. You may include additional 221 | disclaimers of warranty and limitations of liability specific to any 222 | jurisdiction. 223 | 224 | 4. Inability to Comply Due to Statute or Regulation 225 | 226 | If it is impossible for You to comply with any of the terms of this License 227 | with respect to some or all of the Covered Software due to statute, 228 | judicial order, or regulation then You must: (a) comply with the terms of 229 | this License to the maximum extent possible; and (b) describe the 230 | limitations and the code they affect. Such description must be placed in a 231 | text file included with all distributions of the Covered Software under 232 | this License. Except to the extent prohibited by statute or regulation, 233 | such description must be sufficiently detailed for a recipient of ordinary 234 | skill to be able to understand it. 235 | 236 | 5. Termination 237 | 238 | 5.1. The rights granted under this License will terminate automatically if You 239 | fail to comply with any of its terms. However, if You become compliant, 240 | then the rights granted under this License from a particular Contributor 241 | are reinstated (a) provisionally, unless and until such Contributor 242 | explicitly and finally terminates Your grants, and (b) on an ongoing 243 | basis, if such Contributor fails to notify You of the non-compliance by 244 | some reasonable means prior to 60 days after You have come back into 245 | compliance. Moreover, Your grants from a particular Contributor are 246 | reinstated on an ongoing basis if such Contributor notifies You of the 247 | non-compliance by some reasonable means, this is the first time You have 248 | received notice of non-compliance with this License from such 249 | Contributor, and You become compliant prior to 30 days after Your receipt 250 | of the notice. 251 | 252 | 5.2. If You initiate litigation against any entity by asserting a patent 253 | infringement claim (excluding declaratory judgment actions, 254 | counter-claims, and cross-claims) alleging that a Contributor Version 255 | directly or indirectly infringes any patent, then the rights granted to 256 | You by any and all Contributors for the Covered Software under Section 257 | 2.1 of this License shall terminate. 258 | 259 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 260 | license agreements (excluding distributors and resellers) which have been 261 | validly granted by You or Your distributors under this License prior to 262 | termination shall survive termination. 263 | 264 | 6. Disclaimer of Warranty 265 | 266 | Covered Software is provided under this License on an "as is" basis, 267 | without warranty of any kind, either expressed, implied, or statutory, 268 | including, without limitation, warranties that the Covered Software is free 269 | of defects, merchantable, fit for a particular purpose or non-infringing. 270 | The entire risk as to the quality and performance of the Covered Software 271 | is with You. Should any Covered Software prove defective in any respect, 272 | You (not any Contributor) assume the cost of any necessary servicing, 273 | repair, or correction. This disclaimer of warranty constitutes an essential 274 | part of this License. No use of any Covered Software is authorized under 275 | this License except under this disclaimer. 276 | 277 | 7. Limitation of Liability 278 | 279 | Under no circumstances and under no legal theory, whether tort (including 280 | negligence), contract, or otherwise, shall any Contributor, or anyone who 281 | distributes Covered Software as permitted above, be liable to You for any 282 | direct, indirect, special, incidental, or consequential damages of any 283 | character including, without limitation, damages for lost profits, loss of 284 | goodwill, work stoppage, computer failure or malfunction, or any and all 285 | other commercial damages or losses, even if such party shall have been 286 | informed of the possibility of such damages. This limitation of liability 287 | shall not apply to liability for death or personal injury resulting from 288 | such party's negligence to the extent applicable law prohibits such 289 | limitation. Some jurisdictions do not allow the exclusion or limitation of 290 | incidental or consequential damages, so this exclusion and limitation may 291 | not apply to You. 292 | 293 | 8. Litigation 294 | 295 | Any litigation relating to this License may be brought only in the courts 296 | of a jurisdiction where the defendant maintains its principal place of 297 | business and such litigation shall be governed by laws of that 298 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 299 | in this Section shall prevent a party's ability to bring cross-claims or 300 | counter-claims. 301 | 302 | 9. Miscellaneous 303 | 304 | This License represents the complete agreement concerning the subject 305 | matter hereof. If any provision of this License is held to be 306 | unenforceable, such provision shall be reformed only to the extent 307 | necessary to make it enforceable. Any law or regulation which provides that 308 | the language of a contract shall be construed against the drafter shall not 309 | be used to construe this License against a Contributor. 310 | 311 | 312 | 10. Versions of the License 313 | 314 | 10.1. New Versions 315 | 316 | Mozilla Foundation is the license steward. Except as provided in Section 317 | 10.3, no one other than the license steward has the right to modify or 318 | publish new versions of this License. Each version will be given a 319 | distinguishing version number. 320 | 321 | 10.2. Effect of New Versions 322 | 323 | You may distribute the Covered Software under the terms of the version 324 | of the License under which You originally received the Covered Software, 325 | or under the terms of any subsequent version published by the license 326 | steward. 327 | 328 | 10.3. Modified Versions 329 | 330 | If you create software not governed by this License, and you want to 331 | create a new license for such software, you may create and use a 332 | modified version of this License if you rename the license and remove 333 | any references to the name of the license steward (except to note that 334 | such modified license differs from this License). 335 | 336 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 337 | Licenses If You choose to distribute Source Code Form that is 338 | Incompatible With Secondary Licenses under the terms of this version of 339 | the License, the notice described in Exhibit B of this License must be 340 | attached. 341 | 342 | Exhibit A - Source Code Form License Notice 343 | 344 | This Source Code Form is subject to the 345 | terms of the Mozilla Public License, v. 346 | 2.0. If a copy of the MPL was not 347 | distributed with this file, You can 348 | obtain one at 349 | http://mozilla.org/MPL/2.0/. 350 | 351 | If it is not possible or desirable to put the notice in a particular file, 352 | then You may include the notice in a location (such as a LICENSE file in a 353 | relevant directory) where a recipient would be likely to look for such a 354 | notice. 355 | 356 | You may add additional accurate notices of copyright ownership. 357 | 358 | Exhibit B - "Incompatible With Secondary Licenses" Notice 359 | 360 | This Source Code Form is "Incompatible 361 | With Secondary Licenses", as defined by 362 | the Mozilla Public License, v. 2.0. 363 | 364 | --------------------------------------------------------------------------------