From 51a7fe0c7b1594cd4dcfb975f4cd3058d435c59b Mon Sep 17 00:00:00 2001 From: ema Date: Thu, 19 Dec 2024 03:35:55 +0800 Subject: [PATCH] Support .MID audio limited #931 --- .../AudioTrack/MidiPlayer.cs | 183 ++++++++++++++++++ .../QuickLook.Plugin.VideoViewer.csproj | 1 + .../ViewerPanel.xaml.cs | 19 ++ 3 files changed, 203 insertions(+) create mode 100644 QuickLook.Plugin/QuickLook.Plugin.VideoViewer/AudioTrack/MidiPlayer.cs diff --git a/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/AudioTrack/MidiPlayer.cs b/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/AudioTrack/MidiPlayer.cs new file mode 100644 index 00000000..a93ffd02 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/AudioTrack/MidiPlayer.cs @@ -0,0 +1,183 @@ +using Melanchall.DryWetMidi.Core; +using Melanchall.DryWetMidi.Interaction; +using Melanchall.DryWetMidi.Multimedia; +using QuickLook.Common.Helpers; +using QuickLook.Common.Plugin; +using System; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +namespace QuickLook.Plugin.VideoViewer.AudioTrack; + +internal class MidiPlayer : IDisposable +{ + private ViewerPanel _vp; + private ContextObject _context; + private MidiFile _midiFile; + private OutputDevice _outputDevice; + private Playback _playback; + private TimeSpan _duration; + private MethodInfo _setShouldLoop; // _vp.set_ShouldLoop() + + public MidiPlayer(ViewerPanel panle, ContextObject context) + { + _vp = panle; + _context = context; + } + + public void Dispose() + { + _vp = null; + _context = null; + _outputDevice?.Dispose(); + _playback?.Stop(); + _playback?.Dispose(); + } + + public void LoadAndPlay(string path, MediaInfo.MediaInfo info) + { + _midiFile = MidiFile.Read(path); + _vp.metaTitle.Text = Path.GetFileName(path); + _vp.metaArtists.Text = _midiFile.OriginalFormat.ToString(); + _vp.metaAlbum.Text = _midiFile.TimeDivision.ToString(); + + _outputDevice = OutputDevice.GetByName("Microsoft GS Wavetable Synth"); + _playback = _midiFile.GetPlayback(_outputDevice); + + if (_playback.GetDuration(TimeSpanType.Metric) is MetricTimeSpan metricTimeSpan) + { + _duration = TimeSpan.FromMilliseconds(metricTimeSpan.TotalMilliseconds); + var durationString = new TimeTickToShortStringConverter().Convert(_duration.Ticks, typeof(string), null, CultureInfo.InvariantCulture).ToString(); + + if (_vp.buttonTime.Content is TextBlock timeText) + { + timeText.Text = "00:00"; + _vp.metaLength.Text = durationString; + _vp.sliderProgress.Maximum = _duration.Ticks; // Unbinding + _vp.sliderProgress.Value = 0L; // Unbinding + } + } + + _vp.buttonPlayPause.Click += (_, _) => + { + if (_playback.IsRunning) + { + _playback.Stop(); + _vp.buttonPlayPause.Content = "\xE768"; + } + else + { + _playback.Start(); + _vp.buttonPlayPause.Content = "\xE769"; + } + }; + + if (_vp.GetType().GetProperty("ShouldLoop", BindingFlags.Instance | BindingFlags.Public) is PropertyInfo propertyShouldLoop) + { + _setShouldLoop = propertyShouldLoop.GetSetMethod(nonPublic: true); + _setShouldLoop.Invoke(_vp, [SettingHelper.Get("ShouldLoop", false, "QuickLook.Plugin.VideoViewer")]); + } + + _vp.buttonLoop.Click += (_, _) => + { + _setShouldLoop.Invoke(_vp, [!_vp.ShouldLoop]); + _playback.Loop = _vp.ShouldLoop; + }; + + //_vp.sliderProgress.ValueChanged += (_, _) => + //{ + // if (!_isValueHandling) + // { + // _playback?.Stop(); + + // double seekPercent = _vp.sliderProgress.Value / _duration.Ticks; + // long moveTime = (long)(_duration.Ticks * seekPercent); + // TimeSpan timeSpan = TimeSpan.FromTicks(moveTime); + // _playback?.MoveToTime(new MetricTimeSpan(timeSpan)); + + // _playback.Start(); + // } + //}; + + _vp.sliderProgress.PreviewMouseDown += (_, e) => + { + _playback?.Stop(); + + Point mousePosition = e.GetPosition(_vp.sliderProgress); + double newValue = mousePosition.X / _vp.sliderProgress.ActualWidth * (_vp.sliderProgress.Maximum - _vp.sliderProgress.Minimum) + _vp.sliderProgress.Minimum; + double seekPercent = newValue / _duration.Ticks; + long moveTime = (long)(_duration.Ticks * seekPercent); + TimeSpan timeSpan = TimeSpan.FromTicks(moveTime); + _playback?.MoveToTime(new MetricTimeSpan(timeSpan)); + + _playback.Start(); + }; + _vp.sliderProgress.IsSelectionRangeEnabled = false; + _vp.sliderProgress.PreviewMouseUp += (_, _) => + { + //_playback.Start(); + }; + + // Disable unsupported functionality + { + _vp.buttonMute.IsEnabled = false; + _vp.buttonMute.Opacity = 0.5d; + } + + _playback.Loop = _vp.ShouldLoop; + _playback.EventPlayed += (_, _) => + { + _vp?.Dispatcher.Invoke(() => + { + if ((string)_vp?.buttonTime?.Tag == "Time") + { + if (_playback?.GetCurrentTime(TimeSpanType.Metric) is MetricTimeSpan metricTimeSpan) + { + var current = TimeSpan.FromMilliseconds(metricTimeSpan.TotalMilliseconds); + var currentString = new TimeTickToShortStringConverter().Convert(current.Ticks, typeof(string), null, CultureInfo.InvariantCulture).ToString(); + + _vp.sliderProgress.Value = current.Ticks; + + if (_vp?.buttonTime?.Content is TextBlock timeText) + { + timeText.Text = currentString; + } + } + } + else if ((string)_vp?.buttonTime?.Tag == "Length") + { + if (_playback?.GetCurrentTime(TimeSpanType.Metric) is MetricTimeSpan metricTimeSpan) + { + var current = TimeSpan.FromMilliseconds(metricTimeSpan.TotalMilliseconds); + var subtractString = new TimeTickToShortStringConverter().Convert(_duration.Ticks - current.Ticks, typeof(string), null, CultureInfo.InvariantCulture).ToString(); + + _vp.sliderProgress.Value = current.Ticks; + + if (_vp?.buttonTime?.Content is TextBlock timeText) + { + timeText.Text = subtractString; + } + } + } + }); + }; + _playback.Finished += (_, _) => + { + if (!_playback.Loop) + { + _playback.MoveToStart(); + _vp.Dispatcher.Invoke(() => + { + _vp.buttonPlayPause.Content = "\xE768"; + }); + } + }; + _ = Task.Run(() => _playback?.Play()); + _vp.buttonPlayPause.Content = "\xE769"; + _context.IsBusy = false; + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/QuickLook.Plugin.VideoViewer.csproj b/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/QuickLook.Plugin.VideoViewer.csproj index 05b70c84..e09fbdf2 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/QuickLook.Plugin.VideoViewer.csproj +++ b/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/QuickLook.Plugin.VideoViewer.csproj @@ -45,6 +45,7 @@ Mediakit\Assemblies\DirectShowLib-2005.dll + diff --git a/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/ViewerPanel.xaml.cs b/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/ViewerPanel.xaml.cs index 1b268f82..87511c09 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/ViewerPanel.xaml.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.VideoViewer/ViewerPanel.xaml.cs @@ -19,6 +19,7 @@ using QuickLook.Common.Annotations; using QuickLook.Common.Helpers; using QuickLook.Common.Plugin; +using QuickLook.Plugin.VideoViewer.AudioTrack; using QuickLook.Plugin.VideoViewer.Extensions; using QuickLook.Plugin.VideoViewer.LyricTrack; using System; @@ -51,6 +52,7 @@ public partial class ViewerPanel : UserControl, IDisposable, INotifyPropertyChan private BitmapSource _coverArt; private DispatcherTimer _lyricTimer; private LrcLine[] _lyricLines; + private MidiPlayer _midiPlayer; private bool _hasVideo; private bool _isPlaying; @@ -173,6 +175,8 @@ public void Dispose() _lyricTimer?.Stop(); _lyricTimer = null; _lyricLines = null; + _midiPlayer?.Dispose(); + _midiPlayer = null; } public event PropertyChangedEventHandler PropertyChanged; @@ -380,6 +384,21 @@ private void ToggleShouldLoop(object sender, EventArgs e) public void LoadAndPlay(string path, MediaInfo.MediaInfo info) { + if (!HasVideo) + { + if (info != null) + { + string audioCodec = info.Get(StreamKind.Audio, 0, "Format"); + + if (audioCodec == "MIDI") + { + _midiPlayer = new MidiPlayer(this, _context); + _midiPlayer.LoadAndPlay(path, info); + return; + } + } + } + UpdateMeta(path, info); // detect rotation