Show chapter name on waveform and on a tooltip for the video

This commit is contained in:
OmrSi 2020-10-27 10:57:04 +03:00
parent cac169ea34
commit eeb66f31d5
6 changed files with 266 additions and 105 deletions

View File

@ -46,6 +46,8 @@
Chapters = 0x1043A770,
EditionEntry = 0x45B9,
ChapterAtom = 0xB6,
ChapterTimeStart = 0x91
ChapterTimeStart = 0x91,
ChapterDisplay = 0x80,
ChapString = 0x85
}
}

View File

@ -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; }
}
}

View File

@ -21,7 +21,7 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Matroska
private int _subtitleRipTrackNumber;
private readonly List<MatroskaSubtitle> _subtitleRip = new List<MatroskaSubtitle>();
private List<MatroskaTrackInfo> _tracks;
private List<double> _chapters;
private List<MatroskaChapter> _chapters;
private readonly Element _segmentElement;
private long _timeCodeScale = 1000000;
@ -384,13 +384,13 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Matroska
}
}
public List<double> GetChapters()
public List<MatroskaChapter> GetChapters()
{
ReadChapters();
if (_chapters == null)
{
return new List<double>();
return new List<MatroskaChapter>();
}
return _chapters.Distinct().ToList();
@ -417,7 +417,7 @@ namespace Nikse.SubtitleEdit.Core.ContainerFormats.Matroska
private void ReadChaptersElement(Element chaptersElement)
{
_chapters = new List<double>();
_chapters = new List<MatroskaChapter>();
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;
}

View File

@ -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<double> _chapters = new List<double>();
private List<MatroskaChapter> _chapters = new List<MatroskaChapter>();
public List<double> Chapters
public List<MatroskaChapter> 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)

View File

@ -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<double> _chapters = new List<double>();
private List<MatroskaChapter> _chapters = new List<MatroskaChapter>();
public List<double> Chapters
public List<MatroskaChapter> 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);
}
}
/// <summary>
/// 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<double>();
Chapters = new List<MatroskaChapter>();
var temp = VideoPlayer;
VideoPlayer = null;
Application.DoEvents();

View File

@ -3072,7 +3072,7 @@ namespace Nikse.SubtitleEdit.Forms
audioVisualizer.WavePeaks = null;
audioVisualizer.SetSpectrogram(null);
audioVisualizer.SceneChanges = new List<double>();
audioVisualizer.Chapters = new List<double>();
audioVisualizer.Chapters = new List<MatroskaChapter>();
}
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<double>();
audioVisualizer.Chapters = new List<double>();
audioVisualizer.Chapters = new List<MatroskaChapter>();
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<double>();
audioVisualizer.Chapters = new List<double>();
audioVisualizer.Chapters = new List<MatroskaChapter>();
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<double>())
foreach (var chapter in mediaPlayer.Chapters.Reverse<MatroskaChapter>())
{
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<double>();
audioVisualizer.Chapters = new List<double>();
audioVisualizer.Chapters = new List<MatroskaChapter>();
}
openFileDialog1.InitialDirectory = Path.GetDirectoryName(openFileDialog1.FileName);
@ -23498,7 +23498,7 @@ namespace Nikse.SubtitleEdit.Forms
audioVisualizer.WavePeaks = null;
audioVisualizer.SetSpectrogram(null);
audioVisualizer.SceneChanges = new List<double>();
audioVisualizer.Chapters = new List<double>();
audioVisualizer.Chapters = new List<MatroskaChapter>();
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<double>();
var chaps = new List<MatroskaChapter>();
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<double>();
audioVisualizer.Chapters = new List<double>();
audioVisualizer.Chapters = new List<MatroskaChapter>();
}
if (!panelVideoPlayer.Visible)