Skip to content

Commit 98bbf66

Browse files
committed
Add FileOperations example project
- Demonstrate auto-format detection with MediaFile - Show batch processing with progress reporting - Include tag validation examples - Demonstrate cross-format tag copying - Show safe file operation patterns
1 parent b3fc07d commit 98bbf66

3 files changed

Lines changed: 282 additions & 0 deletions

File tree

TagLibSharp2.slnx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<Solution>
2+
<Folder Name="/examples/">
3+
<Project Path="examples/FileOperations/FileOperations.csproj" />
4+
</Folder>
5+
<Folder Name="/src/" />
26
<Project Path="src/TagLibSharp2/TagLibSharp2.csproj" />
37
<Project Path="tests/TagLibSharp2.Tests/TagLibSharp2.Tests.csproj" />
48
</Solution>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<!-- Disable globalization warnings for example code -->
9+
<NoWarn>$(NoWarn);CA1303</NoWarn>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\..\src\TagLibSharp2\TagLibSharp2.csproj" />
14+
</ItemGroup>
15+
16+
</Project>

examples/FileOperations/Program.cs

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
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

Comments
 (0)