diff --git a/src/Commands/Source/InspectCommand.cs b/src/Commands/Source/InspectCommand.cs new file mode 100755 index 0000000..5b5a7c5 --- /dev/null +++ b/src/Commands/Source/InspectCommand.cs @@ -0,0 +1,79 @@ +using System.CodeDom.Compiler; +using System.Text; + +using ConsoleAppFramework; + +using MediaOrganizer.Tasks; +using MediaOrganizer.Tasks.Inspection; + +namespace MediaOrganizer.Commands.Source; + +[RegisterCommands("source")] +internal class InspectCommand +{ + /// Provides detailed information about a media source. + /// Path to DVD image (.iso), BluRay folder (parent of BDMV), FFmpeg bluray-protocol (bluray:), or BluRay Movie Playlist File (mpls). + /// -p|--playlist|-t|--title, Specify Title, or Playlist within a DVD image, or BluRay folder. + [Command("inspect")] + public async Task Execute( + string sourceFile, + int? index = null) + { + IInspector inspector = InspectorFactory.CreateInspector(sourceFile); + if (index is not null) + { + inspector.Index = index; + } + + var result = await inspector.RunAsync(); + using IndentedTextWriter writer = new(Console.Out); + writer.WriteLine($"Inspection Result for {inspector.Source}:"); + + foreach (var track in result.Tracks) + { + writer.WriteLine(TrackIndexFormat, track.Index); + writer.Indent += 1; + writer.WriteLine(DurationFormat, track.Duration); + writer.WriteLine(FPSFormat, track.FPS); + foreach (var audio in track.Audio) + { + writer.WriteLine(AudioTrackFormat, audio.Index); + writer.Indent += 1; + writer.WriteLine(AudioLanguageFormat, audio.Language, audio.LangCode); + writer.WriteLine(AudioCodecFormat, audio.Codec); + writer.Indent -= 1; + } + + writer.Indent -= 1; + } + } + + /// + /// Track {0} + /// + private static CompositeFormat TrackIndexFormat => field ??= CompositeFormat.Parse("Track {0}"); + /// + /// Resolution: {0}x{1} + /// + private static CompositeFormat ResolutionFormat => field ??= CompositeFormat.Parse("Resolution: {0}x{1}"); + /// + /// Duration: {0} + /// + private static CompositeFormat DurationFormat => field ??= CompositeFormat.Parse("Duration: {0}"); + /// + /// FPSFormat: {0} + /// + private static CompositeFormat FPSFormat => field ??= CompositeFormat.Parse("FPS: {0}"); + /// + /// Audio Track {0} + /// + private static CompositeFormat AudioTrackFormat => field ??= CompositeFormat.Parse("Audio Track {0}"); + /// + /// Language: {0} ({1}) + /// + private static CompositeFormat AudioLanguageFormat => field ??= CompositeFormat.Parse("Language: {0} ({1})"); + /// + /// Codec: {0} + /// + private static CompositeFormat AudioCodecFormat => field ??= CompositeFormat.Parse("Codec: {0}"); +} diff --git a/src/System/IO/CompositeFormatTextWriter.cs b/src/System/IO/CompositeFormatTextWriter.cs new file mode 100644 index 0000000..5723ac4 --- /dev/null +++ b/src/System/IO/CompositeFormatTextWriter.cs @@ -0,0 +1,26 @@ +using System.Text; + +namespace System.IO; + +public static class TextWriterCompositeFormat +{ + public static void WriteLine(this TextWriter writer, CompositeFormat format, params object?[] args) + { + WriteLine(writer, null, format, args); + } + + public static void WriteLine(this TextWriter writer, IFormatProvider? provider, CompositeFormat format, params object?[] args) + { + writer.WriteLine(string.Format(provider, format, args)); + } + + public static Task WriteLineAsync(this TextWriter writer, CompositeFormat format, params object?[] args) + { + return WriteLineAsync(writer, null, format, args); + } + + public static Task WriteLineAsync(this TextWriter writer, IFormatProvider? provider, CompositeFormat format, params object?[] args) + { + return writer.WriteLineAsync(string.Format(provider, format, args)); + } +} diff --git a/src/Tasks/InspectFactory.cs b/src/Tasks/InspectFactory.cs new file mode 100644 index 0000000..1782fe4 --- /dev/null +++ b/src/Tasks/InspectFactory.cs @@ -0,0 +1,54 @@ +using MediaOrganizer.Tasks.Inspection; + +namespace MediaOrganizer.Tasks; + +public class InspectorFactory +{ + private static ReadOnlySpan BluRayProtocol => "bluray:"; + private static ReadOnlySpan IsoExtension => ".iso"; + private static ReadOnlySpan MplsExtension => ".mpls"; + + public static IInspector CreateInspector(string path) + { + if (path.StartsWith(BluRayProtocol, StringComparison.OrdinalIgnoreCase)) + { + return FindBluRayInspector(path[BluRayProtocol.Length..]); + } + + if (!Path.Exists(path)) + { + throw new FileNotFoundException(null, path); + } + + if ((File.GetAttributes(path) & FileAttributes.Directory) != 0) + { + return FindBluRayInspector(path[BluRayProtocol.Length..]); + } + + if (path.EndsWith(IsoExtension, StringComparison.OrdinalIgnoreCase)) + { + return new LsDvdInspector(path); + } + else if (path.EndsWith(MplsExtension, StringComparison.OrdinalIgnoreCase)) + { + throw new NotImplementedException(); + } + + throw new ArgumentException(null, nameof(path)); + + static IInspector FindBluRayInspector(string path) + { + if (!Directory.Exists(path)) + { + throw new FileNotFoundException(path); + } + + if (!Directory.Exists(Path.Join(path, "BDMV"))) + { + throw new FileNotFoundException("Specified directory doesn't contain BDMV folder", path); + } + + throw new NotImplementedException(); + } + } +} diff --git a/src/Tasks/Inspection/BluRayInspector.cs b/src/Tasks/Inspection/BluRayInspector.cs new file mode 100644 index 0000000..4353afa --- /dev/null +++ b/src/Tasks/Inspection/BluRayInspector.cs @@ -0,0 +1,11 @@ + +namespace MediaOrganizer.Tasks.Inspection; + +public class BluRayInspector(string path) : IInspector +{ + public int? Index { get; set; } + + public string Source => path; + + public ValueTask RunAsync() => ValueTask.FromResult(default(InspectionResult)!); +} diff --git a/src/Tasks/Inspection/IInspector.cs b/src/Tasks/Inspection/IInspector.cs new file mode 100644 index 0000000..e76327d --- /dev/null +++ b/src/Tasks/Inspection/IInspector.cs @@ -0,0 +1,13 @@ +using System.Diagnostics.CodeAnalysis; + +namespace MediaOrganizer.Tasks.Inspection; + +public interface IInspector +{ + [DisallowNull] + int? Index { get; set; } + + string Source { get; } + + ValueTask RunAsync(); +} diff --git a/src/Tasks/Inspection/InspectionResult.cs b/src/Tasks/Inspection/InspectionResult.cs new file mode 100644 index 0000000..bf5bc34 --- /dev/null +++ b/src/Tasks/Inspection/InspectionResult.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace MediaOrganizer.Tasks.Inspection; + +public partial record class InspectionResult +{ + public required IReadOnlyList Tracks { get; set; } + + [JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata + )] + [JsonSerializable(typeof(InspectionResult))] + internal partial class InspectionResultSerializerContext : JsonSerializerContext; +} diff --git a/src/Tasks/Inspection/InspectionTrack.cs b/src/Tasks/Inspection/InspectionTrack.cs new file mode 100644 index 0000000..0aa33b4 --- /dev/null +++ b/src/Tasks/Inspection/InspectionTrack.cs @@ -0,0 +1,16 @@ +namespace MediaOrganizer.Tasks.Inspection; + +public sealed record class InspectionTrack +{ + public int Index { get; set; } + + public TimeSpan Duration { get; set; } + + public double? FPS { get; set; } + + public required IReadOnlyList Playlist { get; set; } + + public required IReadOnlyList Audio { get; set; } + + public required IReadOnlyList Subtitles { get; set; } +} diff --git a/src/Tasks/Inspection/LsDvdInspector.cs b/src/Tasks/Inspection/LsDvdInspector.cs new file mode 100644 index 0000000..a86a76e --- /dev/null +++ b/src/Tasks/Inspection/LsDvdInspector.cs @@ -0,0 +1,83 @@ +using MediaOrganizer.Tools; +using MediaOrganizer.Tools.Types.LsDvd; + +namespace MediaOrganizer.Tasks.Inspection; + +public class LsDvdInspector(string path) : IInspector +{ + public int? Index { get; set; } + + public string Source => path; + + public async ValueTask RunAsync() + { + LsDvd lsDvd = new(path) + { + Title = Index + }; + + return ToResult(await lsDvd.RunAsync()); + } + + private static List ToAudio(List