|
| 1 | +// FileOperations Example |
| 2 | +// Demonstrates file I/O patterns with TagLibSharp2 |
| 3 | + |
| 4 | +using TagLibSharp2.Core; |
| 5 | +using TagLibSharp2.Id3.Id3v2; |
| 6 | +using TagLibSharp2.Mpeg; |
| 7 | +using TagLibSharp2.Xiph; |
| 8 | +using TagLibSharp2.Ogg; |
| 9 | + |
| 10 | +Console.WriteLine("TagLibSharp2 File Operations Examples"); |
| 11 | +Console.WriteLine(new string('=', 50)); |
| 12 | + |
| 13 | +// Example 1: Auto-detect and read any supported format |
| 14 | +Console.WriteLine("\n1. Auto-Detect Format"); |
| 15 | +Console.WriteLine("-".PadRight(30, '-')); |
| 16 | + |
| 17 | +await DemoAutoDetect ().ConfigureAwait (false); |
| 18 | + |
| 19 | +// Example 2: Batch processing with progress |
| 20 | +Console.WriteLine("\n2. Batch Processing"); |
| 21 | +Console.WriteLine("-".PadRight(30, '-')); |
| 22 | + |
| 23 | +await DemoBatchProcessing ().ConfigureAwait (false); |
| 24 | + |
| 25 | +// Example 3: Tag validation |
| 26 | +Console.WriteLine("\n3. Tag Validation"); |
| 27 | +Console.WriteLine("-".PadRight(30, '-')); |
| 28 | + |
| 29 | +DemoTagValidation (); |
| 30 | + |
| 31 | +// Example 4: Cross-format tag copying |
| 32 | +Console.WriteLine("\n4. Cross-Format Tag Copying"); |
| 33 | +Console.WriteLine("-".PadRight(30, '-')); |
| 34 | + |
| 35 | +DemoTagCopy (); |
| 36 | + |
| 37 | +// Example 5: Safe file operations |
| 38 | +Console.WriteLine("\n5. Safe File Operations"); |
| 39 | +Console.WriteLine("-".PadRight(30, '-')); |
| 40 | + |
| 41 | +await DemoSafeFileOps ().ConfigureAwait (false); |
| 42 | + |
| 43 | +Console.WriteLine("\nAll examples completed!"); |
| 44 | + |
| 45 | +// --- Example Implementations --- |
| 46 | + |
| 47 | +static async Task DemoAutoDetect () |
| 48 | +{ |
| 49 | + // MediaFile.Open auto-detects the format based on magic bytes |
| 50 | + var formats = new[] { "song.mp3", "song.flac", "song.ogg" }; |
| 51 | + |
| 52 | + foreach (var filename in formats) { |
| 53 | + // Create sample data for demo (in real use, these would be actual files) |
| 54 | + var sampleData = CreateSampleData (filename); |
| 55 | + var result = MediaFile.OpenFromData (sampleData, filename); |
| 56 | + |
| 57 | + if (result.IsSuccess) { |
| 58 | + Console.WriteLine ($" {filename}: Format={result.Format}, Title={result.Tag?.Title ?? "(no title)"}"); |
| 59 | + } else { |
| 60 | + Console.WriteLine ($" {filename}: {result.Error}"); |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + // Async variant |
| 65 | + Console.WriteLine ("\n Async file reading:"); |
| 66 | + Console.WriteLine (" await MediaFile.OpenAsync(path) - reads file asynchronously"); |
| 67 | +} |
| 68 | + |
| 69 | +static async Task DemoBatchProcessing () |
| 70 | +{ |
| 71 | + // Simulate processing multiple files |
| 72 | + var paths = Enumerable.Range (1, 10) |
| 73 | + .Select (i => $"track{i:D2}.mp3") |
| 74 | + .ToList (); |
| 75 | + |
| 76 | + Console.WriteLine ($" Processing {paths.Count} files in parallel...\n"); |
| 77 | + |
| 78 | + var results = await BatchProcessor.ProcessAsync ( |
| 79 | + paths, |
| 80 | + async (path, ct) => { |
| 81 | + // Simulate file processing |
| 82 | + await Task.Delay (50, ct).ConfigureAwait (false); |
| 83 | + return $"Processed: {path}"; |
| 84 | + }, |
| 85 | + maxDegreeOfParallelism: 4, |
| 86 | + progress: new Progress<BatchProgress> (p => { |
| 87 | + // Progress callback |
| 88 | + var bar = new string ('#', (int)(p.PercentComplete / 10)); |
| 89 | + Console.Write ($"\r [{bar.PadRight (10)}] {p.PercentComplete:F0}% - {p.CurrentPath}"); |
| 90 | + }) |
| 91 | + ).ConfigureAwait (false); |
| 92 | + |
| 93 | + Console.WriteLine ("\n"); |
| 94 | + |
| 95 | + // Use extension methods to analyze results |
| 96 | + var successCount = results.SuccessCount (); |
| 97 | + var failureCount = results.FailureCount (); |
| 98 | + |
| 99 | + Console.WriteLine ($" Results: {successCount} succeeded, {failureCount} failed"); |
| 100 | + |
| 101 | + // Get specific results |
| 102 | + foreach (var success in results.WhereSucceeded ().Take (3)) { |
| 103 | + Console.WriteLine ($" {success.Value}"); |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +static void DemoTagValidation () |
| 108 | +{ |
| 109 | + // Create a tag with some issues |
| 110 | + var tag = new Id3v2Tag { |
| 111 | + Title = " Untrimmed Title ", // Has whitespace |
| 112 | + Year = "not-a-year", // Invalid year format |
| 113 | + Track = 15, |
| 114 | + TotalTracks = 10 // Track > TotalTracks |
| 115 | + }; |
| 116 | + tag.Isrc = "INVALID"; // Invalid ISRC |
| 117 | + |
| 118 | + var result = tag.Validate (); |
| 119 | + |
| 120 | + Console.WriteLine ($" Validation found {result.AllIssues.Count} issues:\n"); |
| 121 | + |
| 122 | + foreach (var issue in result.AllIssues) { |
| 123 | + var icon = issue.Severity switch { |
| 124 | + ValidationSeverity.Error => "[ERROR]", |
| 125 | + ValidationSeverity.Warning => "[WARN ]", |
| 126 | + _ => "[INFO ]" |
| 127 | + }; |
| 128 | + Console.WriteLine ($" {icon} {issue.Field}: {issue.Message}"); |
| 129 | + } |
| 130 | + |
| 131 | + Console.WriteLine ($"\n IsValid: {result.IsValid}"); |
| 132 | + Console.WriteLine ($" Error count: {result.ErrorCount}"); |
| 133 | + Console.WriteLine ($" Warning count: {result.WarningCount}"); |
| 134 | +} |
| 135 | + |
| 136 | +static void DemoTagCopy () |
| 137 | +{ |
| 138 | + // Create source tag with various metadata |
| 139 | + var source = new Id3v2Tag { |
| 140 | + Title = "Original Song", |
| 141 | + Artist = "Original Artist", |
| 142 | + Album = "Original Album", |
| 143 | + Year = "2024", |
| 144 | + Track = 5, |
| 145 | + TotalTracks = 12, |
| 146 | + Composer = "Composer Name", |
| 147 | + AlbumSort = "Album, The" |
| 148 | + }; |
| 149 | + source.MusicBrainzTrackId = "abc123"; |
| 150 | + |
| 151 | + // Copy to different tag types |
| 152 | + var id3Target = new Id3v2Tag (); |
| 153 | + var vorbisTarget = new VorbisComment (); |
| 154 | + |
| 155 | + // Full copy |
| 156 | + source.CopyTo (id3Target); |
| 157 | + Console.WriteLine (" Full copy to ID3v2:"); |
| 158 | + Console.WriteLine ($" Title: {id3Target.Title}"); |
| 159 | + Console.WriteLine ($" Composer: {id3Target.Composer}"); |
| 160 | + Console.WriteLine ($" MusicBrainzTrackId: {id3Target.MusicBrainzTrackId}"); |
| 161 | + |
| 162 | + // Selective copy - Basic only |
| 163 | + source.CopyTo (vorbisTarget, TagCopyOptions.Basic); |
| 164 | + Console.WriteLine ("\n Basic-only copy to VorbisComment:"); |
| 165 | + Console.WriteLine ($" Title: {vorbisTarget.Title}"); |
| 166 | + Console.WriteLine ($" Composer: {vorbisTarget.Composer ?? "(not copied)"}"); |
| 167 | + |
| 168 | + // Copy with multiple options |
| 169 | + var selective = new VorbisComment (); |
| 170 | + source.CopyTo (selective, TagCopyOptions.Basic | TagCopyOptions.MusicBrainz); |
| 171 | + Console.WriteLine ("\n Basic + MusicBrainz copy:"); |
| 172 | + Console.WriteLine ($" Title: {selective.Title}"); |
| 173 | + Console.WriteLine ($" MusicBrainzTrackId: {selective.MusicBrainzTrackId}"); |
| 174 | +} |
| 175 | + |
| 176 | +static async Task DemoSafeFileOps () |
| 177 | +{ |
| 178 | + Console.WriteLine (" Safe file operation patterns:\n"); |
| 179 | + |
| 180 | + // Pattern 1: Always check result |
| 181 | + Console.WriteLine (" 1. Result-based error handling:"); |
| 182 | + Console.WriteLine (@" var result = Mp3File.ReadFromFile(path); |
| 183 | + if (!result.IsSuccess) { |
| 184 | + Console.WriteLine($""Error: {result.Error}""); |
| 185 | + return; |
| 186 | + } |
| 187 | + var mp3 = result.File!;"); |
| 188 | + |
| 189 | + // Pattern 2: Atomic saves |
| 190 | + Console.WriteLine ("\n 2. Atomic file saves:"); |
| 191 | + Console.WriteLine (@" // Read original, modify, save atomically |
| 192 | + var originalData = await File.ReadAllBytesAsync(path); |
| 193 | + mp3.Title = ""New Title""; |
| 194 | + await mp3.SaveToFileAsync(path, originalData);"); |
| 195 | + |
| 196 | + // Pattern 3: Use IFileSystem for testing |
| 197 | + Console.WriteLine ("\n 3. Testable file operations:"); |
| 198 | + Console.WriteLine (@" // Production code |
| 199 | + var result = Mp3File.ReadFromFile(path, DefaultFileSystem.Instance); |
| 200 | +
|
| 201 | + // Test code |
| 202 | + var mockFs = new MockFileSystem(); |
| 203 | + mockFs.AddFile(""test.mp3"", testData); |
| 204 | + var result = Mp3File.ReadFromFile(""test.mp3"", mockFs);"); |
| 205 | + |
| 206 | + // Pattern 4: Cancellation support |
| 207 | + Console.WriteLine ("\n 4. Cancellation support:"); |
| 208 | + Console.WriteLine (@" using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); |
| 209 | + var result = await Mp3File.ReadFromFileAsync(path, ct: cts.Token);"); |
| 210 | + |
| 211 | + await Task.CompletedTask.ConfigureAwait (false); |
| 212 | +} |
| 213 | + |
| 214 | +static byte[] CreateSampleData (string filename) |
| 215 | +{ |
| 216 | + var ext = Path.GetExtension (filename).ToUpperInvariant (); |
| 217 | + |
| 218 | + return ext switch { |
| 219 | + ".MP3" => CreateMinimalMp3 (), |
| 220 | + ".FLAC" => CreateMinimalFlac (), |
| 221 | + ".OGG" => CreateMinimalOgg (), |
| 222 | + _ => [] |
| 223 | + }; |
| 224 | +} |
| 225 | + |
| 226 | +static byte[] CreateMinimalMp3 () |
| 227 | +{ |
| 228 | + // ID3v2.4 header with minimal TIT2 frame |
| 229 | + var header = new byte[] { |
| 230 | + 0x49, 0x44, 0x33, // "ID3" |
| 231 | + 0x04, 0x00, // Version 2.4.0 |
| 232 | + 0x00, // Flags |
| 233 | + 0x00, 0x00, 0x00, 0x10 // Size (syncsafe): 16 bytes |
| 234 | + }; |
| 235 | + |
| 236 | + // TIT2 frame with "Demo" |
| 237 | + var frame = new byte[] { |
| 238 | + 0x54, 0x49, 0x54, 0x32, // "TIT2" |
| 239 | + 0x00, 0x00, 0x00, 0x05, // Size: 5 bytes (syncsafe) |
| 240 | + 0x00, 0x00, // Flags |
| 241 | + 0x03, // UTF-8 encoding |
| 242 | + 0x44, 0x65, 0x6D, 0x6F // "Demo" |
| 243 | + }; |
| 244 | + |
| 245 | + return [.. header, .. frame]; |
| 246 | +} |
| 247 | + |
| 248 | +static byte[] CreateMinimalFlac () |
| 249 | +{ |
| 250 | + // fLaC magic + STREAMINFO |
| 251 | + var data = new byte[4 + 4 + 34]; |
| 252 | + data[0] = 0x66; data[1] = 0x4C; data[2] = 0x61; data[3] = 0x43; // fLaC |
| 253 | + data[4] = 0x80; // STREAMINFO, last block |
| 254 | + data[5] = 0x00; data[6] = 0x00; data[7] = 0x22; // Size: 34 |
| 255 | + return data; |
| 256 | +} |
| 257 | + |
| 258 | +static byte[] CreateMinimalOgg () |
| 259 | +{ |
| 260 | + // Just enough to be recognized as OggS |
| 261 | + return [0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00]; |
| 262 | +} |
0 commit comments