From d5f25c3fccbd9da31175e6cc4928a1d2d6d15473 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Nov 2025 20:31:17 +0000 Subject: [PATCH] Massive expansion of converter portfolio: Added 35 new file converters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit significantly expands ClipConvert's conversion capabilities from 23 to 58 converters, more than doubling the portfolio. New Converters Added: 📄 RTF Document Support (3): - RTF → PDF, DOCX, TXT 🖼️ Advanced Image Formats (12): - GIF ↔ PNG, JPG - TIFF ↔ PNG, JPG - ICO ↔ PNG - BMP → GIF, TIFF - WEBP → PNG 🎵 Extended Audio Support (5): - FLAC → MP3, WAV - OGG → MP3 - AAC → MP3 - MP3 → FLAC 🎬 Video Format Conversions (5): - AVI, MOV, MKV, WEBM → MP4 - MP4 → AVI 📝 Markdown Conversions (3): - PDF, DOCX, HTML → Markdown 📊 Enhanced Data Exports (3): - XLSX → JSON, XML - CSV → JSON 📑 Document Conversions (4): - HTML → DOCX - TXT ↔ DOCX Technical Changes: - Updated FileTypeDetector to recognize all new formats - Registered all 58 converters in App.xaml.cs - Maintained consistent architecture using Strategy Pattern - All converters follow async stream-based design Supported Formats Now: 26 file types with 58 conversion paths --- FileConvertor/App.xaml.cs | 58 +++++++++- .../Core/Converters/AacToMp3Converter.cs | 24 ++++ .../Core/Converters/AviToMp4Converter.cs | 52 +++++++++ .../Core/Converters/BmpToGifConverter.cs | 25 +++++ .../Core/Converters/BmpToTiffConverter.cs | 25 +++++ .../Core/Converters/CsvToJsonConverter.cs | 88 +++++++++++++++ .../Converters/DocxToMarkdownConverter.cs | 67 +++++++++++ .../Core/Converters/DocxToTextConverter.cs | 29 +++++ .../Core/Converters/ExcelToJsonConverter.cs | 64 +++++++++++ .../Core/Converters/ExcelToXmlConverter.cs | 85 ++++++++++++++ .../Core/Converters/FlacToMp3Converter.cs | 24 ++++ .../Core/Converters/FlacToWavConverter.cs | 22 ++++ .../Core/Converters/GifToJpgConverter.cs | 25 +++++ .../Core/Converters/GifToPngConverter.cs | 26 +++++ .../Core/Converters/HtmlToDocxConverter.cs | 74 ++++++++++++ .../Converters/HtmlToMarkdownConverter.cs | 85 ++++++++++++++ .../Core/Converters/IcoToPngConverter.cs | 23 ++++ .../Core/Converters/JpgToGifConverter.cs | 25 +++++ .../Core/Converters/JpgToTiffConverter.cs | 25 +++++ .../Core/Converters/MkvToMp4Converter.cs | 49 ++++++++ .../Core/Converters/MovToMp4Converter.cs | 49 ++++++++ .../Core/Converters/Mp3ToFlacConverter.cs | 51 +++++++++ .../Core/Converters/Mp4ToAviConverter.cs | 49 ++++++++ .../Core/Converters/OggToMp3Converter.cs | 24 ++++ .../Core/Converters/PdfToMarkdownConverter.cs | 50 +++++++++ .../Core/Converters/PngToGifConverter.cs | 25 +++++ .../Core/Converters/PngToIcoConverter.cs | 35 ++++++ .../Core/Converters/PngToTiffConverter.cs | 25 +++++ .../Core/Converters/RtfToDocxConverter.cs | 98 ++++++++++++++++ .../Core/Converters/RtfToPdfConverter.cs | 105 ++++++++++++++++++ .../Core/Converters/RtfToTextConverter.cs | 93 ++++++++++++++++ .../Core/Converters/TextToDocxConverter.cs | 45 ++++++++ .../Core/Converters/TiffToJpgConverter.cs | 25 +++++ .../Core/Converters/TiffToPngConverter.cs | 25 +++++ .../Core/Converters/WebmToMp4Converter.cs | 49 ++++++++ .../Core/Converters/WebpToPngConverter.cs | 23 ++++ .../Core/Helpers/FileTypeDetector.cs | 20 +++- 37 files changed, 1676 insertions(+), 10 deletions(-) create mode 100644 FileConvertor/Core/Converters/AacToMp3Converter.cs create mode 100644 FileConvertor/Core/Converters/AviToMp4Converter.cs create mode 100644 FileConvertor/Core/Converters/BmpToGifConverter.cs create mode 100644 FileConvertor/Core/Converters/BmpToTiffConverter.cs create mode 100644 FileConvertor/Core/Converters/CsvToJsonConverter.cs create mode 100644 FileConvertor/Core/Converters/DocxToMarkdownConverter.cs create mode 100644 FileConvertor/Core/Converters/DocxToTextConverter.cs create mode 100644 FileConvertor/Core/Converters/ExcelToJsonConverter.cs create mode 100644 FileConvertor/Core/Converters/ExcelToXmlConverter.cs create mode 100644 FileConvertor/Core/Converters/FlacToMp3Converter.cs create mode 100644 FileConvertor/Core/Converters/FlacToWavConverter.cs create mode 100644 FileConvertor/Core/Converters/GifToJpgConverter.cs create mode 100644 FileConvertor/Core/Converters/GifToPngConverter.cs create mode 100644 FileConvertor/Core/Converters/HtmlToDocxConverter.cs create mode 100644 FileConvertor/Core/Converters/HtmlToMarkdownConverter.cs create mode 100644 FileConvertor/Core/Converters/IcoToPngConverter.cs create mode 100644 FileConvertor/Core/Converters/JpgToGifConverter.cs create mode 100644 FileConvertor/Core/Converters/JpgToTiffConverter.cs create mode 100644 FileConvertor/Core/Converters/MkvToMp4Converter.cs create mode 100644 FileConvertor/Core/Converters/MovToMp4Converter.cs create mode 100644 FileConvertor/Core/Converters/Mp3ToFlacConverter.cs create mode 100644 FileConvertor/Core/Converters/Mp4ToAviConverter.cs create mode 100644 FileConvertor/Core/Converters/OggToMp3Converter.cs create mode 100644 FileConvertor/Core/Converters/PdfToMarkdownConverter.cs create mode 100644 FileConvertor/Core/Converters/PngToGifConverter.cs create mode 100644 FileConvertor/Core/Converters/PngToIcoConverter.cs create mode 100644 FileConvertor/Core/Converters/PngToTiffConverter.cs create mode 100644 FileConvertor/Core/Converters/RtfToDocxConverter.cs create mode 100644 FileConvertor/Core/Converters/RtfToPdfConverter.cs create mode 100644 FileConvertor/Core/Converters/RtfToTextConverter.cs create mode 100644 FileConvertor/Core/Converters/TextToDocxConverter.cs create mode 100644 FileConvertor/Core/Converters/TiffToJpgConverter.cs create mode 100644 FileConvertor/Core/Converters/TiffToPngConverter.cs create mode 100644 FileConvertor/Core/Converters/WebmToMp4Converter.cs create mode 100644 FileConvertor/Core/Converters/WebpToPngConverter.cs diff --git a/FileConvertor/App.xaml.cs b/FileConvertor/App.xaml.cs index c124263..316fbb1 100644 --- a/FileConvertor/App.xaml.cs +++ b/FileConvertor/App.xaml.cs @@ -61,8 +61,8 @@ private void ConfigureServices(IServiceCollection services) services.AddSingleton(provider => { var factory = new ConverterFactory(provider); - - // Register converter types instead of instances + + // Register original converter types factory.RegisterConverterType(typeof(TextToPdfConverter)); factory.RegisterConverterType(typeof(PngToJpgConverter)); factory.RegisterConverterType(typeof(ExcelToCsvConverter)); @@ -82,8 +82,58 @@ private void ConfigureServices(IServiceCollection services) factory.RegisterConverterType(typeof(Mp4ToMp3Converter)); factory.RegisterConverterType(typeof(M4aToMp3Converter)); factory.RegisterConverterType(typeof(HeicToJpgConverter)); - - Logger.Log(LogLevel.Info, "App", "Registered converter types for lazy loading"); + factory.RegisterConverterType(typeof(WavToMp3Converter)); + + // Register RTF converters + factory.RegisterConverterType(typeof(RtfToPdfConverter)); + factory.RegisterConverterType(typeof(RtfToDocxConverter)); + factory.RegisterConverterType(typeof(RtfToTextConverter)); + + // Register advanced image converters + factory.RegisterConverterType(typeof(GifToPngConverter)); + factory.RegisterConverterType(typeof(GifToJpgConverter)); + factory.RegisterConverterType(typeof(PngToGifConverter)); + factory.RegisterConverterType(typeof(JpgToGifConverter)); + factory.RegisterConverterType(typeof(BmpToGifConverter)); + factory.RegisterConverterType(typeof(TiffToPngConverter)); + factory.RegisterConverterType(typeof(TiffToJpgConverter)); + factory.RegisterConverterType(typeof(PngToTiffConverter)); + factory.RegisterConverterType(typeof(JpgToTiffConverter)); + factory.RegisterConverterType(typeof(BmpToTiffConverter)); + factory.RegisterConverterType(typeof(PngToIcoConverter)); + factory.RegisterConverterType(typeof(IcoToPngConverter)); + factory.RegisterConverterType(typeof(WebpToPngConverter)); + + // Register additional audio converters + factory.RegisterConverterType(typeof(FlacToMp3Converter)); + factory.RegisterConverterType(typeof(FlacToWavConverter)); + factory.RegisterConverterType(typeof(OggToMp3Converter)); + factory.RegisterConverterType(typeof(AacToMp3Converter)); + factory.RegisterConverterType(typeof(Mp3ToFlacConverter)); + + // Register video converters + factory.RegisterConverterType(typeof(AviToMp4Converter)); + factory.RegisterConverterType(typeof(MovToMp4Converter)); + factory.RegisterConverterType(typeof(MkvToMp4Converter)); + factory.RegisterConverterType(typeof(WebmToMp4Converter)); + factory.RegisterConverterType(typeof(Mp4ToAviConverter)); + + // Register Markdown converters + factory.RegisterConverterType(typeof(PdfToMarkdownConverter)); + factory.RegisterConverterType(typeof(DocxToMarkdownConverter)); + factory.RegisterConverterType(typeof(HtmlToMarkdownConverter)); + + // Register Excel advanced converters + factory.RegisterConverterType(typeof(ExcelToJsonConverter)); + factory.RegisterConverterType(typeof(ExcelToXmlConverter)); + factory.RegisterConverterType(typeof(CsvToJsonConverter)); + + // Register document converters + factory.RegisterConverterType(typeof(HtmlToDocxConverter)); + factory.RegisterConverterType(typeof(TextToDocxConverter)); + factory.RegisterConverterType(typeof(DocxToTextConverter)); + + Logger.Log(LogLevel.Info, "App", "Registered 58 converter types for lazy loading"); return factory; }); diff --git a/FileConvertor/Core/Converters/AacToMp3Converter.cs b/FileConvertor/Core/Converters/AacToMp3Converter.cs new file mode 100644 index 0000000..06df14d --- /dev/null +++ b/FileConvertor/Core/Converters/AacToMp3Converter.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using NAudio.Wave; +using NAudio.Lame; + +namespace FileConvertor.Core.Converters +{ + public class AacToMp3Converter : BaseConverter + { + public override string SourceFormat => "aac"; + public override string TargetFormat => "mp3"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var reader = new StreamMediaFoundationReader(sourceStream); + using var writer = new LameMP3FileWriter(targetStream, reader.WaveFormat, 128); + reader.CopyTo(writer); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/AviToMp4Converter.cs b/FileConvertor/Core/Converters/AviToMp4Converter.cs new file mode 100644 index 0000000..da055a8 --- /dev/null +++ b/FileConvertor/Core/Converters/AviToMp4Converter.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xabe.FFmpeg; + +namespace FileConvertor.Core.Converters +{ + public class AviToMp4Converter : BaseConverter + { + public override string SourceFormat => "avi"; + public override string TargetFormat => "mp4"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + var tempInput = Path.GetTempFileName(); + var tempOutput = Path.GetTempFileName(); + + try + { + // Write source stream to temp file + using (var fileStream = File.Create(tempInput)) + { + await sourceStream.CopyToAsync(fileStream); + } + + // Convert using FFmpeg + var mediaInfo = await FFmpeg.GetMediaInfo(tempInput); + var conversion = FFmpeg.Conversions.New(); + + if (mediaInfo.VideoStreams.Any()) + conversion.AddStream(mediaInfo.VideoStreams.First()); + + if (mediaInfo.AudioStreams.Any()) + conversion.AddStream(mediaInfo.AudioStreams.First()); + + await conversion + .SetOutput(tempOutput) + .Start(); + + // Copy result to target stream + using var outputFile = File.OpenRead(tempOutput); + await outputFile.CopyToAsync(targetStream); + } + finally + { + if (File.Exists(tempInput)) File.Delete(tempInput); + if (File.Exists(tempOutput)) File.Delete(tempOutput); + } + } + } +} diff --git a/FileConvertor/Core/Converters/BmpToGifConverter.cs b/FileConvertor/Core/Converters/BmpToGifConverter.cs new file mode 100644 index 0000000..b8c9ff3 --- /dev/null +++ b/FileConvertor/Core/Converters/BmpToGifConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace FileConvertor.Core.Converters +{ + public class BmpToGifConverter : BaseConverter + { + public override string SourceFormat => "bmp"; + public override string TargetFormat => "gif"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + var decoder = new BmpBitmapDecoder(sourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); + var encoder = new GifBitmapEncoder(); + + encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0])); + encoder.Save(targetStream); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/BmpToTiffConverter.cs b/FileConvertor/Core/Converters/BmpToTiffConverter.cs new file mode 100644 index 0000000..0885ede --- /dev/null +++ b/FileConvertor/Core/Converters/BmpToTiffConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace FileConvertor.Core.Converters +{ + public class BmpToTiffConverter : BaseConverter + { + public override string SourceFormat => "bmp"; + public override string TargetFormat => "tiff"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + var decoder = new BmpBitmapDecoder(sourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); + var encoder = new TiffBitmapEncoder { Compression = TiffCompressOption.Zip }; + + encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0])); + encoder.Save(targetStream); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/CsvToJsonConverter.cs b/FileConvertor/Core/Converters/CsvToJsonConverter.cs new file mode 100644 index 0000000..9139746 --- /dev/null +++ b/FileConvertor/Core/Converters/CsvToJsonConverter.cs @@ -0,0 +1,88 @@ +using System; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace FileConvertor.Core.Converters +{ + public class CsvToJsonConverter : BaseConverter + { + public override string SourceFormat => "csv"; + public override string TargetFormat => "json"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var reader = new StreamReader(sourceStream, Encoding.UTF8); + var data = new List>(); + + // Read header + var headerLine = reader.ReadLine(); + if (string.IsNullOrEmpty(headerLine)) + return; + + var headers = ParseCsvLine(headerLine); + + // Read data + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + if (string.IsNullOrEmpty(line)) + continue; + + var values = ParseCsvLine(line); + var rowData = new Dictionary(); + + for (int i = 0; i < Math.Min(headers.Count, values.Count); i++) + { + rowData[headers[i]] = values[i]; + } + + data.Add(rowData); + } + + // Write JSON + var json = JsonSerializer.Serialize(data, new JsonSerializerOptions + { + WriteIndented = true + }); + + using var writer = new StreamWriter(targetStream, Encoding.UTF8); + writer.Write(json); + writer.Flush(); + }); + } + + private List ParseCsvLine(string line) + { + var values = new List(); + var currentValue = new StringBuilder(); + bool inQuotes = false; + + for (int i = 0; i < line.Length; i++) + { + char c = line[i]; + + if (c == '"') + { + inQuotes = !inQuotes; + } + else if (c == ',' && !inQuotes) + { + values.Add(currentValue.ToString()); + currentValue.Clear(); + } + else + { + currentValue.Append(c); + } + } + + values.Add(currentValue.ToString()); + return values; + } + } +} diff --git a/FileConvertor/Core/Converters/DocxToMarkdownConverter.cs b/FileConvertor/Core/Converters/DocxToMarkdownConverter.cs new file mode 100644 index 0000000..58de2a0 --- /dev/null +++ b/FileConvertor/Core/Converters/DocxToMarkdownConverter.cs @@ -0,0 +1,67 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; + +namespace FileConvertor.Core.Converters +{ + public class DocxToMarkdownConverter : BaseConverter + { + public override string SourceFormat => "docx"; + public override string TargetFormat => "md"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + var markdown = new StringBuilder(); + + using (var wordDocument = WordprocessingDocument.Open(sourceStream, false)) + { + var body = wordDocument.MainDocumentPart?.Document.Body; + + if (body != null) + { + foreach (var element in body.Elements()) + { + if (element is Paragraph paragraph) + { + var text = paragraph.InnerText; + + if (!string.IsNullOrWhiteSpace(text)) + { + // Check if it's a heading + var styleId = paragraph.ParagraphProperties?.ParagraphStyleId?.Val?.Value; + + if (styleId != null && styleId.StartsWith("Heading")) + { + var level = 1; + if (styleId.Length > 7 && char.IsDigit(styleId[7])) + { + level = int.Parse(styleId[7].ToString()); + } + + markdown.AppendLine($"{new string('#', level)} {text}"); + } + else + { + markdown.AppendLine(text); + } + + markdown.AppendLine(); + } + } + } + } + } + + // Write markdown to stream + using var writer = new StreamWriter(targetStream, Encoding.UTF8); + writer.Write(markdown.ToString().TrimEnd()); + writer.Flush(); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/DocxToTextConverter.cs b/FileConvertor/Core/Converters/DocxToTextConverter.cs new file mode 100644 index 0000000..f14d384 --- /dev/null +++ b/FileConvertor/Core/Converters/DocxToTextConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using DocumentFormat.OpenXml.Packaging; + +namespace FileConvertor.Core.Converters +{ + public class DocxToTextConverter : BaseConverter + { + public override string SourceFormat => "docx"; + public override string TargetFormat => "txt"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var wordDocument = WordprocessingDocument.Open(sourceStream, false); + var body = wordDocument.MainDocumentPart?.Document.Body; + + var text = body?.InnerText ?? string.Empty; + + using var writer = new StreamWriter(targetStream, Encoding.UTF8); + writer.Write(text); + writer.Flush(); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/ExcelToJsonConverter.cs b/FileConvertor/Core/Converters/ExcelToJsonConverter.cs new file mode 100644 index 0000000..607a141 --- /dev/null +++ b/FileConvertor/Core/Converters/ExcelToJsonConverter.cs @@ -0,0 +1,64 @@ +using System; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Collections.Generic; +using ClosedXML.Excel; + +namespace FileConvertor.Core.Converters +{ + public class ExcelToJsonConverter : BaseConverter + { + public override string SourceFormat => "xlsx"; + public override string TargetFormat => "json"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var workbook = new XLWorkbook(sourceStream); + var worksheet = workbook.Worksheet(1); + + var data = new List>(); + var headerRow = worksheet.Row(1); + var headers = new List(); + + // Get headers + foreach (var cell in headerRow.CellsUsed()) + { + headers.Add(cell.GetValue()); + } + + // Get data rows + foreach (var row in worksheet.RowsUsed().Skip(1)) + { + var rowData = new Dictionary(); + var colIndex = 0; + + foreach (var cell in row.CellsUsed()) + { + if (colIndex < headers.Count) + { + var value = cell.Value; + rowData[headers[colIndex]] = value.IsBlank ? null : value.ToString(); + } + colIndex++; + } + + data.Add(rowData); + } + + // Write JSON + var json = JsonSerializer.Serialize(data, new JsonSerializerOptions + { + WriteIndented = true + }); + + using var writer = new StreamWriter(targetStream, Encoding.UTF8); + writer.Write(json); + writer.Flush(); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/ExcelToXmlConverter.cs b/FileConvertor/Core/Converters/ExcelToXmlConverter.cs new file mode 100644 index 0000000..a52c77c --- /dev/null +++ b/FileConvertor/Core/Converters/ExcelToXmlConverter.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using ClosedXML.Excel; + +namespace FileConvertor.Core.Converters +{ + public class ExcelToXmlConverter : BaseConverter + { + public override string SourceFormat => "xlsx"; + public override string TargetFormat => "xml"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var workbook = new XLWorkbook(sourceStream); + var worksheet = workbook.Worksheet(1); + + var settings = new XmlWriterSettings + { + Indent = true, + Encoding = Encoding.UTF8 + }; + + using var writer = XmlWriter.Create(targetStream, settings); + + writer.WriteStartDocument(); + writer.WriteStartElement("Workbook"); + writer.WriteStartElement("Worksheet"); + + var headerRow = worksheet.Row(1); + var headers = new System.Collections.Generic.List(); + + // Get headers + foreach (var cell in headerRow.CellsUsed()) + { + headers.Add(cell.GetValue()); + } + + // Write rows + foreach (var row in worksheet.RowsUsed().Skip(1)) + { + writer.WriteStartElement("Row"); + + var colIndex = 0; + foreach (var cell in row.CellsUsed()) + { + if (colIndex < headers.Count) + { + writer.WriteStartElement(SanitizeXmlName(headers[colIndex])); + writer.WriteString(cell.GetValue()); + writer.WriteEndElement(); + } + colIndex++; + } + + writer.WriteEndElement(); // Row + } + + writer.WriteEndElement(); // Worksheet + writer.WriteEndElement(); // Workbook + writer.WriteEndDocument(); + }); + } + + private string SanitizeXmlName(string name) + { + // Replace invalid XML characters + var sanitized = name.Replace(" ", "_") + .Replace("-", "_") + .Replace(".", "_"); + + // Ensure it starts with a letter or underscore + if (!char.IsLetter(sanitized[0]) && sanitized[0] != '_') + { + sanitized = "_" + sanitized; + } + + return sanitized; + } + } +} diff --git a/FileConvertor/Core/Converters/FlacToMp3Converter.cs b/FileConvertor/Core/Converters/FlacToMp3Converter.cs new file mode 100644 index 0000000..6744a06 --- /dev/null +++ b/FileConvertor/Core/Converters/FlacToMp3Converter.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using NAudio.Wave; +using NAudio.Lame; + +namespace FileConvertor.Core.Converters +{ + public class FlacToMp3Converter : BaseConverter + { + public override string SourceFormat => "flac"; + public override string TargetFormat => "mp3"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var reader = new StreamMediaFoundationReader(sourceStream); + using var writer = new LameMP3FileWriter(targetStream, reader.WaveFormat, 128); + reader.CopyTo(writer); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/FlacToWavConverter.cs b/FileConvertor/Core/Converters/FlacToWavConverter.cs new file mode 100644 index 0000000..2102f00 --- /dev/null +++ b/FileConvertor/Core/Converters/FlacToWavConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using NAudio.Wave; + +namespace FileConvertor.Core.Converters +{ + public class FlacToWavConverter : BaseConverter + { + public override string SourceFormat => "flac"; + public override string TargetFormat => "wav"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var reader = new StreamMediaFoundationReader(sourceStream); + WaveFileWriter.WriteWavFileToStream(targetStream, reader); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/GifToJpgConverter.cs b/FileConvertor/Core/Converters/GifToJpgConverter.cs new file mode 100644 index 0000000..0367c8d --- /dev/null +++ b/FileConvertor/Core/Converters/GifToJpgConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace FileConvertor.Core.Converters +{ + public class GifToJpgConverter : BaseConverter + { + public override string SourceFormat => "gif"; + public override string TargetFormat => "jpg"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + var decoder = new GifBitmapDecoder(sourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); + var encoder = new JpegBitmapEncoder { QualityLevel = 90 }; + + encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0])); + encoder.Save(targetStream); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/GifToPngConverter.cs b/FileConvertor/Core/Converters/GifToPngConverter.cs new file mode 100644 index 0000000..6079dfe --- /dev/null +++ b/FileConvertor/Core/Converters/GifToPngConverter.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace FileConvertor.Core.Converters +{ + public class GifToPngConverter : BaseConverter + { + public override string SourceFormat => "gif"; + public override string TargetFormat => "png"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + var decoder = new GifBitmapDecoder(sourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); + var encoder = new PngBitmapEncoder(); + + // Convert first frame (for animated GIFs, convert first frame) + encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0])); + encoder.Save(targetStream); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/HtmlToDocxConverter.cs b/FileConvertor/Core/Converters/HtmlToDocxConverter.cs new file mode 100644 index 0000000..6f4c220 --- /dev/null +++ b/FileConvertor/Core/Converters/HtmlToDocxConverter.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using DocumentFormat.OpenXml; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; + +namespace FileConvertor.Core.Converters +{ + public class HtmlToDocxConverter : BaseConverter + { + public override string SourceFormat => "html"; + public override string TargetFormat => "docx"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var reader = new StreamReader(sourceStream, Encoding.UTF8); + var html = reader.ReadToEnd(); + + // Strip HTML tags for basic conversion + var plainText = StripHtmlTags(html); + + // Create DOCX + using var wordDocument = WordprocessingDocument.Create(targetStream, WordprocessingDocumentType.Document); + wordDocument.AddMainDocumentPart(); + var mainPart = wordDocument.MainDocumentPart!; + + mainPart.Document = new Document(); + var body = new Body(); + + // Split into paragraphs + var lines = plainText.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + if (!string.IsNullOrWhiteSpace(line)) + { + var paragraph = new Paragraph(); + var run = new Run(); + run.Append(new Text(line.Trim())); + paragraph.Append(run); + body.Append(paragraph); + } + } + + mainPart.Document.Append(body); + mainPart.Document.Save(); + }); + } + + private string StripHtmlTags(string html) + { + // Remove script and style tags with content + html = Regex.Replace(html, @"]*>.*?", "", RegexOptions.Singleline | RegexOptions.IgnoreCase); + html = Regex.Replace(html, @"]*>.*?", "", RegexOptions.Singleline | RegexOptions.IgnoreCase); + + // Convert line breaks + html = Regex.Replace(html, @"", "\n", RegexOptions.IgnoreCase); + html = Regex.Replace(html, @"

", "\n", RegexOptions.IgnoreCase); + html = Regex.Replace(html, @"", "\n", RegexOptions.IgnoreCase); + + // Remove all HTML tags + html = Regex.Replace(html, @"<[^>]+>", ""); + + // Decode HTML entities + html = System.Net.WebUtility.HtmlDecode(html); + + return html; + } + } +} diff --git a/FileConvertor/Core/Converters/HtmlToMarkdownConverter.cs b/FileConvertor/Core/Converters/HtmlToMarkdownConverter.cs new file mode 100644 index 0000000..1c41123 --- /dev/null +++ b/FileConvertor/Core/Converters/HtmlToMarkdownConverter.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace FileConvertor.Core.Converters +{ + public class HtmlToMarkdownConverter : BaseConverter + { + public override string SourceFormat => "html"; + public override string TargetFormat => "md"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var reader = new StreamReader(sourceStream, Encoding.UTF8); + var html = reader.ReadToEnd(); + + var markdown = ConvertHtmlToMarkdown(html); + + using var writer = new StreamWriter(targetStream, Encoding.UTF8); + writer.Write(markdown); + writer.Flush(); + }); + } + + private string ConvertHtmlToMarkdown(string html) + { + var markdown = html; + + // Convert headings + markdown = Regex.Replace(markdown, @"]*>(.*?)", "# $1\n", RegexOptions.Singleline | RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"]*>(.*?)", "## $1\n", RegexOptions.Singleline | RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"]*>(.*?)", "### $1\n", RegexOptions.Singleline | RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"]*>(.*?)", "#### $1\n", RegexOptions.Singleline | RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"]*>(.*?)", "##### $1\n", RegexOptions.Singleline | RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"]*>(.*?)", "###### $1\n", RegexOptions.Singleline | RegexOptions.IgnoreCase); + + // Convert bold + markdown = Regex.Replace(markdown, @"]*>(.*?)", "**$1**", RegexOptions.Singleline | RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"]*>(.*?)", "**$1**", RegexOptions.Singleline | RegexOptions.IgnoreCase); + + // Convert italic + markdown = Regex.Replace(markdown, @"]*>(.*?)", "*$1*", RegexOptions.Singleline | RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"]*>(.*?)", "*$1*", RegexOptions.Singleline | RegexOptions.IgnoreCase); + + // Convert links + markdown = Regex.Replace(markdown, @"]*href=[""']([^""']*)[""'][^>]*>(.*?)", "[$2]($1)", RegexOptions.Singleline | RegexOptions.IgnoreCase); + + // Convert images + markdown = Regex.Replace(markdown, @"]*src=[""']([^""']*)[""'][^>]*alt=[""']([^""']*)[""'][^>]*>", "![$2]($1)", RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"]*src=[""']([^""']*)[""'][^>]*>", "![]($1)", RegexOptions.IgnoreCase); + + // Convert lists + markdown = Regex.Replace(markdown, @"]*>", "", RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"", "\n", RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"]*>", "", RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"", "\n", RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"]*>(.*?)", "- $1\n", RegexOptions.Singleline | RegexOptions.IgnoreCase); + + // Convert paragraphs + markdown = Regex.Replace(markdown, @"]*>(.*?)

", "$1\n\n", RegexOptions.Singleline | RegexOptions.IgnoreCase); + + // Convert line breaks + markdown = Regex.Replace(markdown, @"", "\n", RegexOptions.IgnoreCase); + + // Convert code + markdown = Regex.Replace(markdown, @"]*>(.*?)", "`$1`", RegexOptions.Singleline | RegexOptions.IgnoreCase); + markdown = Regex.Replace(markdown, @"]*>(.*?)", "```\n$1\n```\n", RegexOptions.Singleline | RegexOptions.IgnoreCase); + + // Remove remaining HTML tags + markdown = Regex.Replace(markdown, @"<[^>]+>", "", RegexOptions.Singleline); + + // Decode HTML entities + markdown = System.Net.WebUtility.HtmlDecode(markdown); + + // Clean up extra whitespace + markdown = Regex.Replace(markdown, @"\n{3,}", "\n\n"); + + return markdown.Trim(); + } + } +} diff --git a/FileConvertor/Core/Converters/IcoToPngConverter.cs b/FileConvertor/Core/Converters/IcoToPngConverter.cs new file mode 100644 index 0000000..5f8b3c9 --- /dev/null +++ b/FileConvertor/Core/Converters/IcoToPngConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; + +namespace FileConvertor.Core.Converters +{ + public class IcoToPngConverter : BaseConverter + { + public override string SourceFormat => "ico"; + public override string TargetFormat => "png"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var image = Image.Load(sourceStream); + image.Save(targetStream, new PngEncoder()); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/JpgToGifConverter.cs b/FileConvertor/Core/Converters/JpgToGifConverter.cs new file mode 100644 index 0000000..92481cb --- /dev/null +++ b/FileConvertor/Core/Converters/JpgToGifConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace FileConvertor.Core.Converters +{ + public class JpgToGifConverter : BaseConverter + { + public override string SourceFormat => "jpg"; + public override string TargetFormat => "gif"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + var decoder = new JpegBitmapDecoder(sourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); + var encoder = new GifBitmapEncoder(); + + encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0])); + encoder.Save(targetStream); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/JpgToTiffConverter.cs b/FileConvertor/Core/Converters/JpgToTiffConverter.cs new file mode 100644 index 0000000..166e8d6 --- /dev/null +++ b/FileConvertor/Core/Converters/JpgToTiffConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace FileConvertor.Core.Converters +{ + public class JpgToTiffConverter : BaseConverter + { + public override string SourceFormat => "jpg"; + public override string TargetFormat => "tiff"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + var decoder = new JpegBitmapDecoder(sourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); + var encoder = new TiffBitmapEncoder { Compression = TiffCompressOption.Zip }; + + encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0])); + encoder.Save(targetStream); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/MkvToMp4Converter.cs b/FileConvertor/Core/Converters/MkvToMp4Converter.cs new file mode 100644 index 0000000..335c9e8 --- /dev/null +++ b/FileConvertor/Core/Converters/MkvToMp4Converter.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xabe.FFmpeg; + +namespace FileConvertor.Core.Converters +{ + public class MkvToMp4Converter : BaseConverter + { + public override string SourceFormat => "mkv"; + public override string TargetFormat => "mp4"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + var tempInput = Path.GetTempFileName(); + var tempOutput = Path.GetTempFileName(); + + try + { + using (var fileStream = File.Create(tempInput)) + { + await sourceStream.CopyToAsync(fileStream); + } + + var mediaInfo = await FFmpeg.GetMediaInfo(tempInput); + var conversion = FFmpeg.Conversions.New(); + + if (mediaInfo.VideoStreams.Any()) + conversion.AddStream(mediaInfo.VideoStreams.First()); + + if (mediaInfo.AudioStreams.Any()) + conversion.AddStream(mediaInfo.AudioStreams.First()); + + await conversion + .SetOutput(tempOutput) + .Start(); + + using var outputFile = File.OpenRead(tempOutput); + await outputFile.CopyToAsync(targetStream); + } + finally + { + if (File.Exists(tempInput)) File.Delete(tempInput); + if (File.Exists(tempOutput)) File.Delete(tempOutput); + } + } + } +} diff --git a/FileConvertor/Core/Converters/MovToMp4Converter.cs b/FileConvertor/Core/Converters/MovToMp4Converter.cs new file mode 100644 index 0000000..83ae331 --- /dev/null +++ b/FileConvertor/Core/Converters/MovToMp4Converter.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xabe.FFmpeg; + +namespace FileConvertor.Core.Converters +{ + public class MovToMp4Converter : BaseConverter + { + public override string SourceFormat => "mov"; + public override string TargetFormat => "mp4"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + var tempInput = Path.GetTempFileName(); + var tempOutput = Path.GetTempFileName(); + + try + { + using (var fileStream = File.Create(tempInput)) + { + await sourceStream.CopyToAsync(fileStream); + } + + var mediaInfo = await FFmpeg.GetMediaInfo(tempInput); + var conversion = FFmpeg.Conversions.New(); + + if (mediaInfo.VideoStreams.Any()) + conversion.AddStream(mediaInfo.VideoStreams.First()); + + if (mediaInfo.AudioStreams.Any()) + conversion.AddStream(mediaInfo.AudioStreams.First()); + + await conversion + .SetOutput(tempOutput) + .Start(); + + using var outputFile = File.OpenRead(tempOutput); + await outputFile.CopyToAsync(targetStream); + } + finally + { + if (File.Exists(tempInput)) File.Delete(tempInput); + if (File.Exists(tempOutput)) File.Delete(tempOutput); + } + } + } +} diff --git a/FileConvertor/Core/Converters/Mp3ToFlacConverter.cs b/FileConvertor/Core/Converters/Mp3ToFlacConverter.cs new file mode 100644 index 0000000..fe20d1e --- /dev/null +++ b/FileConvertor/Core/Converters/Mp3ToFlacConverter.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Xabe.FFmpeg; + +namespace FileConvertor.Core.Converters +{ + public class Mp3ToFlacConverter : BaseConverter + { + public override string SourceFormat => "mp3"; + public override string TargetFormat => "flac"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + // Create temporary files for FFmpeg + var tempInput = Path.GetTempFileName(); + var tempOutput = Path.GetTempFileName(); + + try + { + // Write source stream to temp file + using (var fileStream = File.Create(tempInput)) + { + await sourceStream.CopyToAsync(fileStream); + } + + // Convert using FFmpeg + var mediaInfo = await FFmpeg.GetMediaInfo(tempInput); + var audioStream = mediaInfo.AudioStreams.FirstOrDefault(); + + if (audioStream != null) + { + await FFmpeg.Conversions.New() + .AddStream(audioStream) + .SetOutput(tempOutput) + .Start(); + + // Copy result to target stream + using var outputFile = File.OpenRead(tempOutput); + await outputFile.CopyToAsync(targetStream); + } + } + finally + { + // Clean up temp files + if (File.Exists(tempInput)) File.Delete(tempInput); + if (File.Exists(tempOutput)) File.Delete(tempOutput); + } + } + } +} diff --git a/FileConvertor/Core/Converters/Mp4ToAviConverter.cs b/FileConvertor/Core/Converters/Mp4ToAviConverter.cs new file mode 100644 index 0000000..9052a05 --- /dev/null +++ b/FileConvertor/Core/Converters/Mp4ToAviConverter.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xabe.FFmpeg; + +namespace FileConvertor.Core.Converters +{ + public class Mp4ToAviConverter : BaseConverter + { + public override string SourceFormat => "mp4"; + public override string TargetFormat => "avi"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + var tempInput = Path.GetTempFileName(); + var tempOutput = Path.GetTempFileName(); + + try + { + using (var fileStream = File.Create(tempInput)) + { + await sourceStream.CopyToAsync(fileStream); + } + + var mediaInfo = await FFmpeg.GetMediaInfo(tempInput); + var conversion = FFmpeg.Conversions.New(); + + if (mediaInfo.VideoStreams.Any()) + conversion.AddStream(mediaInfo.VideoStreams.First()); + + if (mediaInfo.AudioStreams.Any()) + conversion.AddStream(mediaInfo.AudioStreams.First()); + + await conversion + .SetOutput(tempOutput) + .Start(); + + using var outputFile = File.OpenRead(tempOutput); + await outputFile.CopyToAsync(targetStream); + } + finally + { + if (File.Exists(tempInput)) File.Delete(tempInput); + if (File.Exists(tempOutput)) File.Delete(tempOutput); + } + } + } +} diff --git a/FileConvertor/Core/Converters/OggToMp3Converter.cs b/FileConvertor/Core/Converters/OggToMp3Converter.cs new file mode 100644 index 0000000..6eba620 --- /dev/null +++ b/FileConvertor/Core/Converters/OggToMp3Converter.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using NAudio.Wave; +using NAudio.Lame; + +namespace FileConvertor.Core.Converters +{ + public class OggToMp3Converter : BaseConverter + { + public override string SourceFormat => "ogg"; + public override string TargetFormat => "mp3"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var reader = new StreamMediaFoundationReader(sourceStream); + using var writer = new LameMP3FileWriter(targetStream, reader.WaveFormat, 128); + reader.CopyTo(writer); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/PdfToMarkdownConverter.cs b/FileConvertor/Core/Converters/PdfToMarkdownConverter.cs new file mode 100644 index 0000000..1ff4f74 --- /dev/null +++ b/FileConvertor/Core/Converters/PdfToMarkdownConverter.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using UglyToad.PdfPig; + +namespace FileConvertor.Core.Converters +{ + public class PdfToMarkdownConverter : BaseConverter + { + public override string SourceFormat => "pdf"; + public override string TargetFormat => "md"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + var memoryStream = new MemoryStream(); + sourceStream.CopyTo(memoryStream); + memoryStream.Position = 0; + + var markdown = new StringBuilder(); + + using (var document = PdfDocument.Open(memoryStream)) + { + for (var i = 1; i <= document.NumberOfPages; i++) + { + var page = document.GetPage(i); + var text = page.Text; + + // Add page separator + if (i > 1) + { + markdown.AppendLine("\n---\n"); + } + + // Add page content + markdown.AppendLine($"## Page {i}\n"); + markdown.AppendLine(text); + } + } + + // Write markdown to stream + using var writer = new StreamWriter(targetStream, Encoding.UTF8); + writer.Write(markdown.ToString()); + writer.Flush(); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/PngToGifConverter.cs b/FileConvertor/Core/Converters/PngToGifConverter.cs new file mode 100644 index 0000000..04fa67c --- /dev/null +++ b/FileConvertor/Core/Converters/PngToGifConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace FileConvertor.Core.Converters +{ + public class PngToGifConverter : BaseConverter + { + public override string SourceFormat => "png"; + public override string TargetFormat => "gif"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + var decoder = new PngBitmapDecoder(sourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); + var encoder = new GifBitmapEncoder(); + + encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0])); + encoder.Save(targetStream); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/PngToIcoConverter.cs b/FileConvertor/Core/Converters/PngToIcoConverter.cs new file mode 100644 index 0000000..4001d4f --- /dev/null +++ b/FileConvertor/Core/Converters/PngToIcoConverter.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Ico; +using SixLabors.ImageSharp.Processing; + +namespace FileConvertor.Core.Converters +{ + public class PngToIcoConverter : BaseConverter + { + public override string SourceFormat => "png"; + public override string TargetFormat => "ico"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var image = Image.Load(sourceStream); + + // Resize to standard icon sizes if needed + if (image.Width > 256 || image.Height > 256) + { + image.Mutate(x => x.Resize(new ResizeOptions + { + Size = new Size(256, 256), + Mode = ResizeMode.Max + })); + } + + image.Save(targetStream, new IcoEncoder()); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/PngToTiffConverter.cs b/FileConvertor/Core/Converters/PngToTiffConverter.cs new file mode 100644 index 0000000..d3bda45 --- /dev/null +++ b/FileConvertor/Core/Converters/PngToTiffConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace FileConvertor.Core.Converters +{ + public class PngToTiffConverter : BaseConverter + { + public override string SourceFormat => "png"; + public override string TargetFormat => "tiff"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + var decoder = new PngBitmapDecoder(sourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); + var encoder = new TiffBitmapEncoder { Compression = TiffCompressOption.Zip }; + + encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0])); + encoder.Save(targetStream); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/RtfToDocxConverter.cs b/FileConvertor/Core/Converters/RtfToDocxConverter.cs new file mode 100644 index 0000000..6bdfa65 --- /dev/null +++ b/FileConvertor/Core/Converters/RtfToDocxConverter.cs @@ -0,0 +1,98 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using DocumentFormat.OpenXml; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; + +namespace FileConvertor.Core.Converters +{ + public class RtfToDocxConverter : BaseConverter + { + public override string SourceFormat => "rtf"; + public override string TargetFormat => "docx"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + // Read RTF content + using var reader = new StreamReader(sourceStream, Encoding.Default); + string rtfContent = reader.ReadToEnd(); + + // Strip RTF formatting to get plain text + string plainText = StripRtfFormatting(rtfContent); + + // Create DOCX + using var wordDocument = WordprocessingDocument.Create(targetStream, WordprocessingDocumentType.Document); + wordDocument.AddMainDocumentPart(); + var mainPart = wordDocument.MainDocumentPart!; + + mainPart.Document = new Document(); + var body = new Body(); + + // Add paragraphs + var lines = plainText.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + var paragraph = new Paragraph(); + var run = new Run(); + run.Append(new Text(line)); + paragraph.Append(run); + body.Append(paragraph); + } + + mainPart.Document.Append(body); + mainPart.Document.Save(); + }); + } + + private string StripRtfFormatting(string rtf) + { + if (string.IsNullOrEmpty(rtf)) + return string.Empty; + + var sb = new StringBuilder(); + bool inControl = false; + + for (int i = 0; i < rtf.Length; i++) + { + char c = rtf[i]; + + if (c == '\\') + { + inControl = true; + if (i + 1 < rtf.Length) + { + char next = rtf[i + 1]; + if (next == '\\' || next == '{' || next == '}') + { + sb.Append(next); + i++; + inControl = false; + } + } + } + else if (c == '{' || c == '}') + { + inControl = false; + } + else if (c == ' ' || c == '\n' || c == '\r') + { + if (!inControl) + { + sb.Append(c); + } + inControl = false; + } + else if (!inControl) + { + sb.Append(c); + } + } + + return sb.ToString().Trim(); + } + } +} diff --git a/FileConvertor/Core/Converters/RtfToPdfConverter.cs b/FileConvertor/Core/Converters/RtfToPdfConverter.cs new file mode 100644 index 0000000..898be21 --- /dev/null +++ b/FileConvertor/Core/Converters/RtfToPdfConverter.cs @@ -0,0 +1,105 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using iText.Kernel.Pdf; +using iText.Layout; +using iText.Layout.Element; +using iText.Kernel.Font; +using iText.IO.Font.Constants; + +namespace FileConvertor.Core.Converters +{ + public class RtfToPdfConverter : BaseConverter + { + public override string SourceFormat => "rtf"; + public override string TargetFormat => "pdf"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + // Read RTF content + using var reader = new StreamReader(sourceStream, Encoding.Default); + string rtfContent = reader.ReadToEnd(); + + // Strip RTF formatting to get plain text (basic implementation) + string plainText = StripRtfFormatting(rtfContent); + + // Create PDF + using var writer = new PdfWriter(targetStream); + writer.SetCloseStream(false); + using var pdf = new PdfDocument(writer); + using var document = new Document(pdf); + + var font = PdfFontFactory.CreateFont(StandardFonts.HELVETICA); + var paragraph = new Paragraph(plainText) + .SetFont(font) + .SetFontSize(12); + + document.Add(paragraph); + }); + } + + private string StripRtfFormatting(string rtf) + { + if (string.IsNullOrEmpty(rtf)) + return string.Empty; + + var sb = new StringBuilder(); + bool inControl = false; + bool inGroup = false; + + for (int i = 0; i < rtf.Length; i++) + { + char c = rtf[i]; + + if (c == '\\') + { + inControl = true; + // Check for special characters + if (i + 1 < rtf.Length) + { + char next = rtf[i + 1]; + if (next == '\\' || next == '{' || next == '}') + { + sb.Append(next); + i++; + inControl = false; + } + } + } + else if (c == '{') + { + inGroup = true; + inControl = false; + } + else if (c == '}') + { + inGroup = false; + inControl = false; + } + else if (c == ' ' || c == '\n' || c == '\r') + { + if (inControl) + { + inControl = false; + } + else if (!inGroup || rtf[i - 1] == '}') + { + if (c == '\n' || c == '\r') + sb.Append(' '); + else + sb.Append(c); + } + } + else if (!inControl) + { + sb.Append(c); + } + } + + return sb.ToString().Trim(); + } + } +} diff --git a/FileConvertor/Core/Converters/RtfToTextConverter.cs b/FileConvertor/Core/Converters/RtfToTextConverter.cs new file mode 100644 index 0000000..2057644 --- /dev/null +++ b/FileConvertor/Core/Converters/RtfToTextConverter.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace FileConvertor.Core.Converters +{ + public class RtfToTextConverter : BaseConverter + { + public override string SourceFormat => "rtf"; + public override string TargetFormat => "txt"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + // Read RTF content + using var reader = new StreamReader(sourceStream, Encoding.Default); + string rtfContent = reader.ReadToEnd(); + + // Strip RTF formatting to get plain text + string plainText = StripRtfFormatting(rtfContent); + + // Write plain text + using var writer = new StreamWriter(targetStream, Encoding.UTF8); + writer.Write(plainText); + writer.Flush(); + }); + } + + private string StripRtfFormatting(string rtf) + { + if (string.IsNullOrEmpty(rtf)) + return string.Empty; + + var sb = new StringBuilder(); + bool inControl = false; + bool inGroup = false; + + for (int i = 0; i < rtf.Length; i++) + { + char c = rtf[i]; + + if (c == '\\') + { + inControl = true; + // Handle escaped characters + if (i + 1 < rtf.Length) + { + char next = rtf[i + 1]; + if (next == '\\' || next == '{' || next == '}') + { + sb.Append(next); + i++; + inControl = false; + } + else if (next == 'p' && i + 3 < rtf.Length && rtf.Substring(i, 4) == "\\par") + { + sb.AppendLine(); + i += 3; + inControl = false; + } + } + } + else if (c == '{') + { + inGroup = true; + inControl = false; + } + else if (c == '}') + { + inGroup = false; + inControl = false; + } + else if (c == ' ' || c == '\n' || c == '\r') + { + if (!inControl) + { + if (c != '\r' && c != '\n') + sb.Append(c); + } + inControl = false; + } + else if (!inControl) + { + sb.Append(c); + } + } + + return sb.ToString().Trim(); + } + } +} diff --git a/FileConvertor/Core/Converters/TextToDocxConverter.cs b/FileConvertor/Core/Converters/TextToDocxConverter.cs new file mode 100644 index 0000000..cc3374e --- /dev/null +++ b/FileConvertor/Core/Converters/TextToDocxConverter.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using DocumentFormat.OpenXml; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; + +namespace FileConvertor.Core.Converters +{ + public class TextToDocxConverter : BaseConverter + { + public override string SourceFormat => "txt"; + public override string TargetFormat => "docx"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var reader = new StreamReader(sourceStream, Encoding.UTF8); + var text = reader.ReadToEnd(); + + using var wordDocument = WordprocessingDocument.Create(targetStream, WordprocessingDocumentType.Document); + wordDocument.AddMainDocumentPart(); + var mainPart = wordDocument.MainDocumentPart!; + + mainPart.Document = new Document(); + var body = new Body(); + + var lines = text.Split(new[] { '\r', '\n' }, StringSplitOptions.None); + foreach (var line in lines) + { + var paragraph = new Paragraph(); + var run = new Run(); + run.Append(new Text(line)); + paragraph.Append(run); + body.Append(paragraph); + } + + mainPart.Document.Append(body); + mainPart.Document.Save(); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/TiffToJpgConverter.cs b/FileConvertor/Core/Converters/TiffToJpgConverter.cs new file mode 100644 index 0000000..4e4a1ad --- /dev/null +++ b/FileConvertor/Core/Converters/TiffToJpgConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace FileConvertor.Core.Converters +{ + public class TiffToJpgConverter : BaseConverter + { + public override string SourceFormat => "tiff"; + public override string TargetFormat => "jpg"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + var decoder = new TiffBitmapDecoder(sourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); + var encoder = new JpegBitmapEncoder { QualityLevel = 90 }; + + encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0])); + encoder.Save(targetStream); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/TiffToPngConverter.cs b/FileConvertor/Core/Converters/TiffToPngConverter.cs new file mode 100644 index 0000000..af74e82 --- /dev/null +++ b/FileConvertor/Core/Converters/TiffToPngConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace FileConvertor.Core.Converters +{ + public class TiffToPngConverter : BaseConverter + { + public override string SourceFormat => "tiff"; + public override string TargetFormat => "png"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + var decoder = new TiffBitmapDecoder(sourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); + var encoder = new PngBitmapEncoder(); + + encoder.Frames.Add(BitmapFrame.Create(decoder.Frames[0])); + encoder.Save(targetStream); + }); + } + } +} diff --git a/FileConvertor/Core/Converters/WebmToMp4Converter.cs b/FileConvertor/Core/Converters/WebmToMp4Converter.cs new file mode 100644 index 0000000..e119183 --- /dev/null +++ b/FileConvertor/Core/Converters/WebmToMp4Converter.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xabe.FFmpeg; + +namespace FileConvertor.Core.Converters +{ + public class WebmToMp4Converter : BaseConverter + { + public override string SourceFormat => "webm"; + public override string TargetFormat => "mp4"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + var tempInput = Path.GetTempFileName(); + var tempOutput = Path.GetTempFileName(); + + try + { + using (var fileStream = File.Create(tempInput)) + { + await sourceStream.CopyToAsync(fileStream); + } + + var mediaInfo = await FFmpeg.GetMediaInfo(tempInput); + var conversion = FFmpeg.Conversions.New(); + + if (mediaInfo.VideoStreams.Any()) + conversion.AddStream(mediaInfo.VideoStreams.First()); + + if (mediaInfo.AudioStreams.Any()) + conversion.AddStream(mediaInfo.AudioStreams.First()); + + await conversion + .SetOutput(tempOutput) + .Start(); + + using var outputFile = File.OpenRead(tempOutput); + await outputFile.CopyToAsync(targetStream); + } + finally + { + if (File.Exists(tempInput)) File.Delete(tempInput); + if (File.Exists(tempOutput)) File.Delete(tempOutput); + } + } + } +} diff --git a/FileConvertor/Core/Converters/WebpToPngConverter.cs b/FileConvertor/Core/Converters/WebpToPngConverter.cs new file mode 100644 index 0000000..2623e45 --- /dev/null +++ b/FileConvertor/Core/Converters/WebpToPngConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; + +namespace FileConvertor.Core.Converters +{ + public class WebpToPngConverter : BaseConverter + { + public override string SourceFormat => "webp"; + public override string TargetFormat => "png"; + + public override async Task ConvertAsync(Stream sourceStream, Stream targetStream) + { + await Task.Run(() => + { + using var image = Image.Load(sourceStream); + image.Save(targetStream, new PngEncoder()); + }); + } + } +} diff --git a/FileConvertor/Core/Helpers/FileTypeDetector.cs b/FileConvertor/Core/Helpers/FileTypeDetector.cs index a30ed0f..f2b7c9b 100644 --- a/FileConvertor/Core/Helpers/FileTypeDetector.cs +++ b/FileConvertor/Core/Helpers/FileTypeDetector.cs @@ -61,15 +61,23 @@ private Dictionary InitializeExtensionMap() { ".tif", "tiff" }, { ".webp", "webp" }, { ".svg", "svg" }, - - // Audio/Video formats + { ".ico", "ico" }, + { ".heic", "heic" }, + + // Audio formats { ".mp3", "mp3" }, { ".wav", "wav" }, - { ".mp4", "mp4" }, { ".m4a", "m4a" }, - - // Additional image formats - { ".heic", "heic" }, + { ".flac", "flac" }, + { ".ogg", "ogg" }, + { ".aac", "aac" }, + + // Video formats + { ".mp4", "mp4" }, + { ".avi", "avi" }, + { ".mov", "mov" }, + { ".mkv", "mkv" }, + { ".webm", "webm" }, }; }