diff --git a/libse/ContainerFormats/Ebml/ElementId.cs b/libse/ContainerFormats/Ebml/ElementId.cs index 921a83277..a3881f4b8 100644 --- a/libse/ContainerFormats/Ebml/ElementId.cs +++ b/libse/ContainerFormats/Ebml/ElementId.cs @@ -46,6 +46,8 @@ Chapters = 0x1043A770, EditionEntry = 0x45B9, ChapterAtom = 0xB6, - ChapterTimeStart = 0x91 + ChapterTimeStart = 0x91, + ChapterDisplay = 0x80, + ChapString = 0x85 } } diff --git a/libse/ContainerFormats/Matroska/MatroskaChapter.cs b/libse/ContainerFormats/Matroska/MatroskaChapter.cs new file mode 100644 index 000000000..ffb6935e5 --- /dev/null +++ b/libse/ContainerFormats/Matroska/MatroskaChapter.cs @@ -0,0 +1,9 @@ +namespace Nikse.SubtitleEdit.Core.ContainerFormats.Matroska +{ + public class MatroskaChapter + { + public double StartTime { get; set; } + public string Name { get; set; } + public bool Nested { get; set; } + } +} diff --git a/libse/ContainerFormats/Matroska/MatroskaFile.cs b/libse/ContainerFormats/Matroska/MatroskaFile.cs index 4c307d1c4..c9133947c 100644 --- a/libse/ContainerFormats/Matroska/MatroskaFile.cs +++ b/libse/ContainerFormats/Matroska/MatroskaFile.cs @@ -21,7 +21,7 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Matroska private int _subtitleRipTrackNumber; private readonly List _subtitleRip = new List(); private List _tracks; - private List _chapters; + private List _chapters; private readonly Element _segmentElement; private long _timeCodeScale = 1000000; @@ -384,13 +384,13 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Matroska } } - public List GetChapters() + public List GetChapters() { ReadChapters(); if (_chapters == null) { - return new List(); + return new List(); } return _chapters.Distinct().ToList(); @@ -417,7 +417,7 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Matroska private void ReadChaptersElement(Element chaptersElement) { - _chapters = new List(); + _chapters = new List(); Element element; while (_stream.Position < chaptersElement.EndPosition && (element = ReadElement()) != null) @@ -451,12 +451,18 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Matroska private void ReadChapterTimeStart(Element chpaterAtom) { + var chapter = new MatroskaChapter(); + Element element; while (_stream.Position < chpaterAtom.EndPosition && (element = ReadElement()) != null) { if (element.Id == ElementId.ChapterTimeStart) { - _chapters.Add(ReadUInt((int)element.DataSize) / 1000000000.0); + chapter.StartTime = ReadUInt((int)element.DataSize) / 1000000000.0; + } + else if (element.Id == ElementId.ChapterDisplay) + { + chapter.Name = GetChapterName(element); } else if (element.Id == ElementId.ChapterAtom) { @@ -466,23 +472,54 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Matroska { _stream.Seek(element.DataSize, SeekOrigin.Current); } + + _chapters.Add(chapter); } } private void ReadNestedChaptersTimeStart(Element nestedChpaterAtom) { + var chapter = new MatroskaChapter + { + Nested = true + }; + Element element; while (_stream.Position < nestedChpaterAtom.EndPosition && (element = ReadElement()) != null) { if (element.Id == ElementId.ChapterTimeStart) { - _chapters.Add(ReadUInt((int)element.DataSize) / 1000000000.0); + chapter.StartTime = ReadUInt((int)element.DataSize) / 1000000000.0; + } + else if (element.Id == ElementId.ChapterDisplay) + { + chapter.Name = GetChapterName(element); + } + else + { + _stream.Seek(element.DataSize, SeekOrigin.Current); + } + + _chapters.Add(chapter); + } + } + + private string GetChapterName(Element ChapterDisplay) + { + Element element; + while (_stream.Position < ChapterDisplay.EndPosition && (element = ReadElement()) != null) + { + if (element.Id == ElementId.ChapString) + { + return ReadString((int)element.DataSize, Encoding.UTF8); } else { _stream.Seek(element.DataSize, SeekOrigin.Current); } } + + return null; } diff --git a/src/Controls/AudioVisualizer.cs b/src/Controls/AudioVisualizer.cs index 9126fa8c2..70a3ec820 100644 --- a/src/Controls/AudioVisualizer.cs +++ b/src/Controls/AudioVisualizer.cs @@ -1,4 +1,5 @@ using Nikse.SubtitleEdit.Core; +using Nikse.SubtitleEdit.Core.ContainerFormats.Matroska; using Nikse.SubtitleEdit.Logic; using System; using System.Collections.Generic; @@ -193,9 +194,9 @@ namespace Nikse.SubtitleEdit.Controls } } - private List _chapters = new List(); + private List _chapters = new List(); - public List Chapters + public List Chapters { get => _chapters; set @@ -684,15 +685,33 @@ namespace Nikse.SubtitleEdit.Controls int pos; try { - double time = _chapters[index++]; + double time = _chapters[index].StartTime; pos = SecondsToXPosition(time - _startPositionSeconds); } catch { pos = -1; } - if (pos > 0 && pos < Width) + if (pos >= 0 && pos < Width) { + // draw chapter text + using (var brush = new SolidBrush(Color.White)) + { + if (index + 1 < _chapters.Count && _chapters[index].StartTime == _chapters[index + 1].StartTime) + { + graphics.DrawString(_chapters[index].Name, Font, brush, new PointF(pos + 2, Height / 2 - Font.Height - 8)); + } + else if (_chapters[index].Nested) + { + graphics.DrawString("+ " + _chapters[index].Name, Font, brush, new PointF(pos + 2, Height / 2 - 8)); + } + else + { + graphics.DrawString(_chapters[index].Name, Font, brush, new PointF(pos + 2, Height / 2 - 8)); + } + } + + // draw chapter line if (currentPositionPos == pos) { // chapter and current pos are the same - draw 2 pixels + current pos dotted currentPosDone = true; @@ -738,6 +757,8 @@ namespace Nikse.SubtitleEdit.Controls } } } + + index++; } } catch (Exception) diff --git a/src/Controls/VideoPlayerContainer.cs b/src/Controls/VideoPlayerContainer.cs index 987e83987..ece5560c3 100644 --- a/src/Controls/VideoPlayerContainer.cs +++ b/src/Controls/VideoPlayerContainer.cs @@ -1,4 +1,5 @@ using Nikse.SubtitleEdit.Core; +using Nikse.SubtitleEdit.Core.ContainerFormats.Matroska; using Nikse.SubtitleEdit.Logic.VideoPlayers; using System; using System.Drawing; @@ -118,11 +119,12 @@ namespace Nikse.SubtitleEdit.Controls private readonly PictureBox _pictureBoxVolumeBar = new PictureBox(); private readonly Label _labelTimeCode = new Label(); private readonly Label _labelVideoPlayerName = new Label(); + private readonly ToolTip _chapterToolTip = new ToolTip(); - private List _chapters = new List(); + private List _chapters = new List(); - public List Chapters + public List Chapters { get => _chapters; set @@ -763,7 +765,7 @@ namespace Nikse.SubtitleEdit.Controls { if (_panelControls.Visible) { - _panelSubtitle.Height = _panelSubtitle.Height + _controlsHeight; + _panelSubtitle.Height += _controlsHeight; _panelControls.Visible = false; } if (hideCursor) @@ -778,7 +780,7 @@ namespace Nikse.SubtitleEdit.Controls { _panelControls.Visible = true; _panelControls.BringToFront(); - _panelSubtitle.Height = _panelSubtitle.Height - _controlsHeight; + _panelSubtitle.Height -= _controlsHeight; } ShowCursor(); } @@ -962,8 +964,10 @@ namespace Nikse.SubtitleEdit.Controls _pictureBoxProgressbarBackground.Size = new Size(531, 12); _pictureBoxProgressbarBackground.SizeMode = PictureBoxSizeMode.StretchImage; _pictureBoxProgressbarBackground.TabStop = false; - _pictureBoxProgressbarBackground.Paint += new PaintEventHandler(ProgressbarBackgroundPaint); + _pictureBoxProgressbarBackground.Paint += PictureBoxProgressbarBackgroundPaint; _pictureBoxProgressbarBackground.MouseDown += PictureBoxProgressbarBackgroundMouseDown; + _pictureBoxProgressbarBackground.MouseLeave += PictureBoxProgressbarBackgroundMouseLeave; + _pictureBoxProgressbarBackground.MouseMove += PictureBoxProgressbarBackgroundMouseMove; _panelControls.Controls.Add(_pictureBoxProgressbarBackground); _pictureBoxProgressBar.Image = (Image)_resources.GetObject("pictureBoxProgressBar.Image"); @@ -972,8 +976,10 @@ namespace Nikse.SubtitleEdit.Controls _pictureBoxProgressBar.Size = new Size(318, 4); _pictureBoxProgressBar.SizeMode = PictureBoxSizeMode.StretchImage; _pictureBoxProgressBar.TabStop = false; - _pictureBoxProgressBar.Paint += new PaintEventHandler(ProgressBarPaint); + _pictureBoxProgressBar.Paint += PictureBoxProgressBarPaint; _pictureBoxProgressBar.MouseDown += PictureBoxProgressBarMouseDown; + _pictureBoxProgressBar.MouseLeave += PictureBoxProgressBarMouseLeave; + _pictureBoxProgressBar.MouseMove += PictureBoxProgressBarMouseMove; _panelControls.Controls.Add(_pictureBoxProgressBar); _pictureBoxProgressBar.BringToFront(); @@ -1131,81 +1137,6 @@ namespace Nikse.SubtitleEdit.Controls _pictureBoxPlay.BringToFront(); _labelTimeCode.BringToFront(); return _panelControls; - } - - internal void ProgressbarBackgroundPaint(object sender, PaintEventArgs e) - { - if (_chapters != null) - { - try - { - Graphics graphics = e.Graphics; - int max = _pictureBoxProgressbarBackground.Width - 9; - int index = 0; - while (index < _chapters.Count) - { - int pos; - try - { - double time = _chapters[index++]; - pos = (int)Math.Round(time * max / Duration + 3); - } - catch - { - pos = -1; - } - if (pos > 0 && pos < max) - { - using (var p = new Pen(Color.LightGray)) - { - graphics.DrawLine(p, pos, _pictureBoxProgressBar.Location.Y, pos, _pictureBoxProgressBar.Location.Y + 3); - } - } - } - } - catch (Exception) - { - // ignore - } - } - } - - internal void ProgressBarPaint(object sender, PaintEventArgs e) - { - - if (_chapters != null) - { - try - { - Graphics graphics = e.Graphics; - int max = _pictureBoxProgressbarBackground.Width - 9; - int index = 0; - while (index < _chapters.Count) - { - int pos; - try - { - double time = _chapters[index++]; - pos = (int)Math.Round(time * max / Duration - 1); - } - catch - { - pos = -1; - } - if (pos > 0 && pos < max) - { - using (var p = new Pen(Color.LightGray)) - { - graphics.DrawLine(p, pos, 1, pos, Height); - } - } - } - } - catch (Exception) - { - // ignore - } - } } public void VideoPlayerContainerResize(object sender, EventArgs e) @@ -1693,6 +1624,12 @@ namespace Nikse.SubtitleEdit.Controls CurrentPosition = percent * Duration / 100.0; } + private int SecondsToXPosition(double seconds) + { + int max = _pictureBoxProgressbarBackground.Width - 9; + return (int)Math.Round(seconds * max / Duration); + } + private void PictureBoxProgressbarBackgroundMouseDown(object sender, MouseEventArgs e) { SetProgressBarPosition(e.X - 4); @@ -1705,6 +1642,161 @@ namespace Nikse.SubtitleEdit.Controls OnButtonClicked?.Invoke(sender, e); } + private void PictureBoxProgressbarBackgroundPaint(object sender, PaintEventArgs e) + { + if (_chapters != null) + { + try + { + Graphics graphics = e.Graphics; + int max = _pictureBoxProgressbarBackground.Width - 9; + int index = 0; + while (index < _chapters.Count) + { + int pos; + try + { + double time = _chapters[index++].StartTime; + pos = SecondsToXPosition(time) + 3; + } + catch + { + pos = -1; + } + if (pos > 0 && pos < max) + { + using (var p = new Pen(Color.LightGray)) + { + graphics.DrawLine(p, pos, _pictureBoxProgressBar.Location.Y, pos, _pictureBoxProgressBar.Location.Y + 3); + } + } + } + } + catch (Exception) + { + // ignore + } + } + } + + private void PictureBoxProgressBarPaint(object sender, PaintEventArgs e) + { + if (_chapters != null) + { + try + { + Graphics graphics = e.Graphics; + int max = _pictureBoxProgressbarBackground.Width - 9; + int index = 0; + while (index < _chapters.Count) + { + int pos; + try + { + double time = _chapters[index++].StartTime; + pos = SecondsToXPosition(time) - 1; + } + catch + { + pos = -1; + } + if (pos >= 0 && pos < max) + { + using (var p = new Pen(Color.LightGray)) + { + graphics.DrawLine(p, pos, 1, pos, Height); + } + } + } + } + catch (Exception) + { + // ignore + } + } + } + + private void PictureBoxProgressbarBackgroundMouseMove(object sender, MouseEventArgs e) + { + if (_chapters?.Count > 0) + { + double time, nextTime; + int pos, nextPos; + string toolTiptext; + + for (int index = 0; index < _chapters.Count; index++) + { + time = _chapters[index].StartTime; + pos = SecondsToXPosition(time) + 3; + + nextTime = index + 1 < _chapters.Count ? _chapters[index + 1].StartTime : Duration; + nextPos = SecondsToXPosition(nextTime) + 3; + + if (e.X >= pos && e.X < nextPos) + { + if (_chapters[index].Nested) + { + toolTiptext = "+ " + _chapters[index].Name; + } + else + { + toolTiptext = _chapters[index].Name; + } + _chapterToolTip.Show(toolTiptext, this, pos, Height - 65); + } + } + } + } + + private void PictureBoxProgressbarBackgroundMouseLeave(object sender, EventArgs e) + { + if (_chapters?.Count > 0) + { + _chapterToolTip.Hide(this); + } + } + + private void PictureBoxProgressBarMouseMove(object sender, MouseEventArgs e) + { + if (_chapters?.Count > 0) + { + double time, nextTime; + int pos, nextPos; + string toolTiptext; + + for (int index = 0; index < _chapters.Count; index++) + { + time = _chapters[index].StartTime; + pos = SecondsToXPosition(time) - 1; + + nextTime = index + 1 < _chapters.Count ? _chapters[index + 1].StartTime : Duration; + nextPos = SecondsToXPosition(nextTime) - 1; + + if (e.X >= pos && e.X < nextPos) + { + if (_chapters[index].Nested) + { + toolTiptext = "+ " + _chapters[index].Name; + } + else + { + toolTiptext = _chapters[index].Name; + } + _chapterToolTip.Show(toolTiptext, this, pos, Height - 65); + } + } + } + } + + private void PictureBoxProgressBarMouseLeave(object sender, EventArgs e) + { + if (_chapters?.Count > 0) + { + _chapterToolTip.Hide(this); + } + } + + /// /// Use SMPTE time (drop frame mode) /// See https://blog.frame.io/2017/07/17/timecode-and-frame-rates/ and @@ -1993,7 +2085,7 @@ namespace Nikse.SubtitleEdit.Controls PanelPlayer.Hide(); Pause(); SubtitleText = string.Empty; - Chapters = new List(); + Chapters = new List(); var temp = VideoPlayer; VideoPlayer = null; Application.DoEvents(); diff --git a/src/Forms/Main.cs b/src/Forms/Main.cs index b7b8d6406..8b10c5a24 100644 --- a/src/Forms/Main.cs +++ b/src/Forms/Main.cs @@ -3072,7 +3072,7 @@ namespace Nikse.SubtitleEdit.Forms audioVisualizer.WavePeaks = null; audioVisualizer.SetSpectrogram(null); audioVisualizer.SceneChanges = new List(); - audioVisualizer.Chapters = new List(); + audioVisualizer.Chapters = new List(); } if (Configuration.Settings.General.ShowVideoPlayer || Configuration.Settings.General.ShowAudioVisualizer) @@ -3244,7 +3244,7 @@ namespace Nikse.SubtitleEdit.Forms audioVisualizer.WavePeaks = null; audioVisualizer.SetSpectrogram(null); audioVisualizer.SceneChanges = new List(); - audioVisualizer.Chapters = new List(); + audioVisualizer.Chapters = new List(); Configuration.Settings.RecentFiles.Add(fileName, FirstVisibleIndex, FirstSelectedIndex, VideoFileName, _subtitleAlternateFileName, Configuration.Settings.General.CurrentVideoOffsetInMs); Configuration.Settings.Save(); @@ -4193,7 +4193,7 @@ namespace Nikse.SubtitleEdit.Forms audioVisualizer.WavePeaks = null; audioVisualizer.SetSpectrogram(null); audioVisualizer.SceneChanges = new List(); - audioVisualizer.Chapters = new List(); + audioVisualizer.Chapters = new List(); if (mediaPlayer.VideoPlayer != null) { mediaPlayer.PauseAndDisposePlayer(); @@ -14703,11 +14703,11 @@ namespace Nikse.SubtitleEdit.Forms else if (mediaPlayer.Chapters?.Count > 0 && e.KeyData == _shortcuts.VideoGoToPrevChapter) { var cp = mediaPlayer.CurrentPosition - 0.01; - foreach (var chapter in mediaPlayer.Chapters.Reverse()) + foreach (var chapter in mediaPlayer.Chapters.Reverse()) { - if (chapter < cp) + if (chapter.StartTime < cp) { - mediaPlayer.CurrentPosition = chapter; + mediaPlayer.CurrentPosition = chapter.StartTime; break; } } @@ -14719,9 +14719,9 @@ namespace Nikse.SubtitleEdit.Forms var cp = mediaPlayer.CurrentPosition + 0.01; foreach (var chapter in mediaPlayer.Chapters) { - if (chapter > cp) + if (chapter.StartTime > cp) { - mediaPlayer.CurrentPosition = chapter; + mediaPlayer.CurrentPosition = chapter.StartTime; break; } } @@ -19676,7 +19676,7 @@ namespace Nikse.SubtitleEdit.Forms audioVisualizer.WavePeaks = null; audioVisualizer.SetSpectrogram(null); audioVisualizer.SceneChanges = new List(); - audioVisualizer.Chapters = new List(); + audioVisualizer.Chapters = new List(); } openFileDialog1.InitialDirectory = Path.GetDirectoryName(openFileDialog1.FileName); @@ -23498,7 +23498,7 @@ namespace Nikse.SubtitleEdit.Forms audioVisualizer.WavePeaks = null; audioVisualizer.SetSpectrogram(null); audioVisualizer.SceneChanges = new List(); - audioVisualizer.Chapters = new List(); + audioVisualizer.Chapters = new List(); timeUpDownVideoPositionAdjust.TimeCode = new TimeCode(); timeUpDownVideoPositionAdjust.Enabled = false; timeUpDownVideoPosition.TimeCode = new TimeCode(); @@ -26961,7 +26961,7 @@ namespace Nikse.SubtitleEdit.Forms toolStripMenuItemImportChapters.Enabled = false; ShowStatus(_language.GettingChapters); - var chaps = new List(); + var chaps = new List(); var getChapters = System.Threading.Tasks.Task.Factory.StartNew(() => { using (var matroska = new MatroskaFile(VideoFileName)) @@ -27791,7 +27791,7 @@ namespace Nikse.SubtitleEdit.Forms audioVisualizer.WavePeaks = null; audioVisualizer.SetSpectrogram(null); audioVisualizer.SceneChanges = new List(); - audioVisualizer.Chapters = new List(); + audioVisualizer.Chapters = new List(); } if (!panelVideoPlayer.Visible)