mirror of
https://github.com/SubtitleEdit/subtitleedit.git
synced 2024-11-26 05:02:36 +01:00
Merge branch 'main' of https://github.com/SubtitleEdit/subtitleedit
This commit is contained in:
commit
b0217b3380
@ -425,7 +425,7 @@ namespace Test.Logic
|
||||
[TestMethod]
|
||||
public void TestFirstWithin()
|
||||
{
|
||||
var shotChangesFrames = new List<int>() { 1000, 10000, 20000 };
|
||||
var shotChangesFrames = new List<int>() { 50, 1000, 3000, 5000, 10000, 11000, 14000, 15000, 16000, 20000, 22000 };
|
||||
|
||||
int? First(int start, int end)
|
||||
{
|
||||
@ -455,6 +455,47 @@ namespace Test.Logic
|
||||
Assert.AreEqual(First(20001, 20001), shotChangesFrames.FirstWithin(20001, 20001));
|
||||
Assert.AreEqual(First(30000, 30000), shotChangesFrames.FirstWithin(30000, 30000));
|
||||
Assert.AreEqual(First(30000, 40000), shotChangesFrames.FirstWithin(30000, 40000));
|
||||
Assert.AreEqual(First(25000, 5000), shotChangesFrames.FirstWithin(25000, 5000));
|
||||
Assert.AreEqual(First(50, 60), shotChangesFrames.FirstWithin(50, 60));
|
||||
Assert.AreEqual(First(50, 1100), shotChangesFrames.FirstWithin(50, 1100));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestLastWithin()
|
||||
{
|
||||
var shotChangesFrames = new List<int>() { 50, 1000, 3000, 5000, 10000, 11000, 14000, 15000, 16000, 20000, 22000 };
|
||||
|
||||
int? Last(int start, int end)
|
||||
{
|
||||
try
|
||||
{
|
||||
return shotChangesFrames.Last(x => x >= start && x <= end);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.AreEqual(Last(-100, 100000), shotChangesFrames.LastWithin(-100, 100000));
|
||||
Assert.AreEqual(Last(0, 100), shotChangesFrames.LastWithin(0, 100));
|
||||
Assert.AreEqual(Last(0, 1000), shotChangesFrames.LastWithin(0, 1000));
|
||||
Assert.AreEqual(Last(1000, 1000), shotChangesFrames.LastWithin(1000, 1000));
|
||||
Assert.AreEqual(Last(1000, 1100), shotChangesFrames.LastWithin(1000, 1100));
|
||||
Assert.AreEqual(Last(1100, 900), shotChangesFrames.LastWithin(1100, 900));
|
||||
Assert.AreEqual(Last(900, 15000), shotChangesFrames.LastWithin(900, 15000));
|
||||
Assert.AreEqual(Last(900, 30000), shotChangesFrames.LastWithin(900, 30000));
|
||||
Assert.AreEqual(Last(19999, 19999), shotChangesFrames.LastWithin(19999, 19999));
|
||||
Assert.AreEqual(Last(19999, 20000), shotChangesFrames.LastWithin(19999, 20000));
|
||||
Assert.AreEqual(Last(20000, 20000), shotChangesFrames.LastWithin(20000, 20000));
|
||||
Assert.AreEqual(Last(20000, 20001), shotChangesFrames.LastWithin(20000, 20001));
|
||||
Assert.AreEqual(Last(19999, 20001), shotChangesFrames.LastWithin(19999, 20001));
|
||||
Assert.AreEqual(Last(20001, 20001), shotChangesFrames.LastWithin(20001, 20001));
|
||||
Assert.AreEqual(Last(30000, 30000), shotChangesFrames.LastWithin(30000, 30000));
|
||||
Assert.AreEqual(Last(30000, 40000), shotChangesFrames.LastWithin(30000, 40000));
|
||||
Assert.AreEqual(Last(25000, 5000), shotChangesFrames.LastWithin(25000, 5000));
|
||||
Assert.AreEqual(Last(50, 60), shotChangesFrames.LastWithin(50, 60));
|
||||
Assert.AreEqual(Last(50, 1100), shotChangesFrames.LastWithin(50, 1100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ namespace Nikse.SubtitleEdit.Core.Common
|
||||
}
|
||||
|
||||
|
||||
// First within (int)
|
||||
// First / last within (int)
|
||||
|
||||
public static int? FirstWithin(this List<int> orderedList, int start, int end)
|
||||
{
|
||||
@ -177,5 +177,28 @@ namespace Nikse.SubtitleEdit.Core.Common
|
||||
|
||||
return orderedList[startIndex];
|
||||
}
|
||||
|
||||
public static int? LastWithin(this List<int> orderedList, int start, int end)
|
||||
{
|
||||
int startIndex = orderedList.BinarySearch(start);
|
||||
int endIndex = orderedList.BinarySearch(end);
|
||||
|
||||
if (startIndex < 0)
|
||||
{
|
||||
startIndex = ~startIndex;
|
||||
}
|
||||
|
||||
if (endIndex < 0)
|
||||
{
|
||||
endIndex = ~endIndex - 1;
|
||||
}
|
||||
|
||||
if (startIndex > endIndex || startIndex >= orderedList.Count || orderedList[startIndex] > end || endIndex >= orderedList.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return orderedList[endIndex];
|
||||
}
|
||||
}
|
||||
}
|
@ -2927,6 +2927,7 @@ $HorzAlign = Center
|
||||
public bool AlignTimeCodes { get; set; }
|
||||
public bool ExtractExactTimeCodes { get; set; }
|
||||
public bool SnapToShotChanges { get; set; }
|
||||
public int OverlapThreshold { get; set; }
|
||||
public BeautifyTimeCodesProfile Profile { get; set; }
|
||||
|
||||
public BeautifyTimeCodesSettings()
|
||||
@ -2934,6 +2935,7 @@ $HorzAlign = Center
|
||||
AlignTimeCodes = true;
|
||||
ExtractExactTimeCodes = false;
|
||||
SnapToShotChanges = true;
|
||||
OverlapThreshold = 1000;
|
||||
Profile = new BeautifyTimeCodesProfile();
|
||||
}
|
||||
|
||||
@ -8757,6 +8759,12 @@ $HorzAlign = Center
|
||||
settings.BeautifyTimeCodes.SnapToShotChanges = Convert.ToBoolean(subNode.InnerText, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
subNode = node.SelectSingleNode("OverlapThreshold");
|
||||
if (subNode != null)
|
||||
{
|
||||
settings.BeautifyTimeCodes.OverlapThreshold = Convert.ToInt32(subNode.InnerText, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
var profileNode = node.SelectSingleNode("Profile");
|
||||
if (profileNode != null)
|
||||
{
|
||||
@ -12327,6 +12335,7 @@ $HorzAlign = Center
|
||||
textWriter.WriteElementString("AlignTimeCodes", settings.BeautifyTimeCodes.AlignTimeCodes.ToString(CultureInfo.InvariantCulture));
|
||||
textWriter.WriteElementString("ExtractExactTimeCodes", settings.BeautifyTimeCodes.ExtractExactTimeCodes.ToString(CultureInfo.InvariantCulture));
|
||||
textWriter.WriteElementString("SnapToShotChanges", settings.BeautifyTimeCodes.SnapToShotChanges.ToString(CultureInfo.InvariantCulture));
|
||||
textWriter.WriteElementString("OverlapThreshold", settings.BeautifyTimeCodes.OverlapThreshold.ToString(CultureInfo.InvariantCulture));
|
||||
textWriter.WriteStartElement("Profile", string.Empty);
|
||||
textWriter.WriteElementString("Gap", settings.BeautifyTimeCodes.Profile.Gap.ToString(CultureInfo.InvariantCulture));
|
||||
textWriter.WriteElementString("InCuesGap", settings.BeautifyTimeCodes.Profile.InCuesGap.ToString(CultureInfo.InvariantCulture));
|
||||
|
@ -108,6 +108,25 @@ namespace Nikse.SubtitleEdit.Core.Forms
|
||||
}
|
||||
|
||||
var distance = rightParagraph.StartTime.TotalMilliseconds - leftParagraph.EndTime.TotalMilliseconds;
|
||||
|
||||
// Check if there is an overlap
|
||||
if (distance < 0)
|
||||
{
|
||||
// If an overlap threshold is set, don't connect if threshold exceeded
|
||||
if (Configuration.Settings.BeautifyTimeCodes.OverlapThreshold > 0 && Math.Abs(distance) >= Configuration.Settings.BeautifyTimeCodes.OverlapThreshold)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are continuing, but there is still an overlap, so fix that first
|
||||
leftParagraph.EndTime.TotalMilliseconds = rightParagraph.StartTime.TotalMilliseconds - 1;
|
||||
|
||||
// Re-calculate distance
|
||||
distance = rightParagraph.StartTime.TotalMilliseconds - leftParagraph.EndTime.TotalMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
var subtitlesAreConnected = distance < Configuration.Settings.BeautifyTimeCodes.Profile.ConnectedSubtitlesTreatConnected;
|
||||
|
||||
if (subtitlesAreConnected)
|
||||
@ -126,14 +145,14 @@ namespace Nikse.SubtitleEdit.Core.Forms
|
||||
var leftInCueFrame = MillisecondsToFrames(leftParagraph.StartTime.TotalMilliseconds);
|
||||
var rightOutCueFrame = MillisecondsToFrames(rightParagraph.EndTime.TotalMilliseconds);
|
||||
|
||||
// Check result
|
||||
if (bestLeftOutCueFrameInfo.result == FindBestCueResult.SnappedToRedZone && bestRightInCueFrameInfo.result == FindBestCueResult.SnappedToRedZone)
|
||||
// Define align function for reusing
|
||||
void AlignCuesAroundClosestShotChange(int leftShotChangeFrame, int rightShotChangeFrame)
|
||||
{
|
||||
var fixInfoForLeft = GetFixedConnectedSubtitlesCueFrames(leftParagraph, rightParagraph, bestLeftOutCueFrameInfo.cueFrame);
|
||||
var fixInfoForRight = GetFixedConnectedSubtitlesCueFrames(leftParagraph, rightParagraph, bestRightInCueFrameInfo.cueFrame);
|
||||
var fixInfoForLeft = GetFixedConnectedSubtitlesCueFrames(leftParagraph, rightParagraph, leftShotChangeFrame);
|
||||
var fixInfoForRight = GetFixedConnectedSubtitlesCueFrames(leftParagraph, rightParagraph, rightShotChangeFrame);
|
||||
|
||||
// Both are in red zones! We will use the closest shot change to align the cues around
|
||||
if (Math.Abs(newLeftOutCueFrame - bestLeftOutCueFrameInfo.cueFrame) <= Math.Abs(newRightInCueFrame - bestRightInCueFrameInfo.cueFrame))
|
||||
// Calculate which shot change is closer
|
||||
if (Math.Abs(newLeftOutCueFrame - leftShotChangeFrame) <= Math.Abs(newRightInCueFrame - rightShotChangeFrame))
|
||||
{
|
||||
// Align around the left shot change
|
||||
// Except, when the left subtitle now becomes invalid (negative duration) and the right subtitle won't, we will use the right shot change anyway
|
||||
@ -168,6 +187,13 @@ namespace Nikse.SubtitleEdit.Core.Forms
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check result
|
||||
if (bestLeftOutCueFrameInfo.result == FindBestCueResult.SnappedToRedZone && bestRightInCueFrameInfo.result == FindBestCueResult.SnappedToRedZone)
|
||||
{
|
||||
// Both are in red zones! We will use the closest shot change to align the cues around
|
||||
AlignCuesAroundClosestShotChange(bestLeftOutCueFrameInfo.cueFrame, bestRightInCueFrameInfo.cueFrame);
|
||||
}
|
||||
else if ((bestLeftOutCueFrameInfo.result == FindBestCueResult.SnappedToLeftGreenZone || bestLeftOutCueFrameInfo.result == FindBestCueResult.SnappedToRightGreenZone) &&
|
||||
(bestRightInCueFrameInfo.result == FindBestCueResult.SnappedToLeftGreenZone || bestRightInCueFrameInfo.result == FindBestCueResult.SnappedToRightGreenZone))
|
||||
{
|
||||
@ -273,7 +299,24 @@ namespace Nikse.SubtitleEdit.Core.Forms
|
||||
}
|
||||
else if (bestLeftOutCueFrameInfo.result == FindBestCueResult.SnappedToLeftGreenZone)
|
||||
{
|
||||
throw new InvalidOperationException("The left out cue cannot be snapped to the left side of a green zone while the right in cue is unaffected at the same time.");
|
||||
// The left out cue wants to go backward, while the right in cue requires no action... The "Treat as connected" setting is probably really high.
|
||||
// We'll use this function in case there are any shot changes closer to the right in cue.
|
||||
var lastShotChangeInBetween = GetLastShotChangeFrameInBetween(bestLeftOutCueFrameInfo.cueFrame, bestRightInCueFrameInfo.cueFrame);
|
||||
if (lastShotChangeInBetween != null)
|
||||
{
|
||||
// Derive left out cue shot change
|
||||
var leftOutCueShotChange = bestLeftOutCueFrameInfo.cueFrame + Configuration.Settings.BeautifyTimeCodes.Profile.ConnectedSubtitlesLeftGreenZone;
|
||||
|
||||
// We will use the closest shot change to align the cues around
|
||||
AlignCuesAroundClosestShotChange(leftOutCueShotChange, lastShotChangeInBetween.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There are no shot changes at all between the two cues! That likely means they are overlapping.
|
||||
// Since the left out cue requires a change, we'll accommodate that: put the right in cue on the edge of the green zone, and push the previous subtitle backward.
|
||||
newRightInCueFrame = bestLeftOutCueFrameInfo.cueFrame;
|
||||
newLeftOutCueFrame = newRightInCueFrame - Configuration.Settings.BeautifyTimeCodes.Profile.Gap;
|
||||
}
|
||||
}
|
||||
else if (bestLeftOutCueFrameInfo.result == FindBestCueResult.SnappedToRightGreenZone)
|
||||
{
|
||||
@ -289,7 +332,24 @@ namespace Nikse.SubtitleEdit.Core.Forms
|
||||
}
|
||||
else if (bestRightInCueFrameInfo.result == FindBestCueResult.SnappedToRightGreenZone)
|
||||
{
|
||||
throw new InvalidOperationException("The right in cue cannot be snapped to the right side of a green zone while the left out cue is unaffected at the same time.");
|
||||
// The right in cue wants to go forward, while the left out cue requires no action... The "Treat as connected" setting is probably really high.
|
||||
// We'll use this function in case there are any shot changes closer to the left out cue.
|
||||
var firstShotChangeInBetween = GetFirstShotChangeFrameInBetween(bestLeftOutCueFrameInfo.cueFrame, bestRightInCueFrameInfo.cueFrame);
|
||||
if (firstShotChangeInBetween != null)
|
||||
{
|
||||
// Derive right in cue shot change
|
||||
var rightInCueShotChange = bestRightInCueFrameInfo.cueFrame - Configuration.Settings.BeautifyTimeCodes.Profile.ConnectedSubtitlesRightGreenZone;
|
||||
|
||||
// We will use the closest shot change to align the cues around
|
||||
AlignCuesAroundClosestShotChange(firstShotChangeInBetween.Value, rightInCueShotChange);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There are no shot changes at all between the two cues! That likely means they are overlapping.
|
||||
// Since the right in cue requires a change, we'll accommodate that: put the left out cue on the edge of the green zone, and push the next subtitle forward.
|
||||
newLeftOutCueFrame = bestRightInCueFrameInfo.cueFrame;
|
||||
newRightInCueFrame = newLeftOutCueFrame + Configuration.Settings.BeautifyTimeCodes.Profile.Gap;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -422,6 +482,23 @@ namespace Nikse.SubtitleEdit.Core.Forms
|
||||
return false;
|
||||
}
|
||||
|
||||
var distance = rightParagraph.StartTime.TotalMilliseconds - leftParagraph.EndTime.TotalMilliseconds;
|
||||
|
||||
// Check if there is an overlap
|
||||
if (distance < 0)
|
||||
{
|
||||
// If an overlap threshold is set, don't chain if threshold exceeded
|
||||
if (Configuration.Settings.BeautifyTimeCodes.OverlapThreshold > 0 && Math.Abs(distance) >= Configuration.Settings.BeautifyTimeCodes.OverlapThreshold)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are continuing, but there is still an overlap, so fix that first
|
||||
leftParagraph.EndTime.TotalMilliseconds = rightParagraph.StartTime.TotalMilliseconds - 1;
|
||||
}
|
||||
}
|
||||
|
||||
var newLeftOutCueFrame = MillisecondsToFrames(leftParagraph.EndTime.TotalMilliseconds);
|
||||
var newRightInCueFrame = MillisecondsToFrames(rightParagraph.StartTime.TotalMilliseconds);
|
||||
|
||||
@ -716,8 +793,19 @@ namespace Nikse.SubtitleEdit.Core.Forms
|
||||
var previousParagraph = _subtitle.Paragraphs.ElementAtOrDefault(index - 1);
|
||||
if (previousParagraph != null)
|
||||
{
|
||||
var previousOutCueFrame = MillisecondsToFrames(previousParagraph.EndTime.TotalMilliseconds);
|
||||
newCueFrame = Math.Max(bestCueFrame, previousOutCueFrame + Configuration.Settings.BeautifyTimeCodes.Profile.Gap);
|
||||
var distance = previousParagraph.StartTime.TotalMilliseconds - paragraph.EndTime.TotalMilliseconds;
|
||||
|
||||
// If an overlap threshold is set, don't fix if threshold exceeded
|
||||
if (distance < 0 && Configuration.Settings.BeautifyTimeCodes.OverlapThreshold > 0 && Math.Abs(distance) >= Configuration.Settings.BeautifyTimeCodes.OverlapThreshold)
|
||||
{
|
||||
newCueFrame = bestCueFrame;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else, limit to adjacent subtitle
|
||||
var previousOutCueFrame = MillisecondsToFrames(previousParagraph.EndTime.TotalMilliseconds);
|
||||
newCueFrame = Math.Max(bestCueFrame, previousOutCueFrame + Configuration.Settings.BeautifyTimeCodes.Profile.Gap);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -729,8 +817,19 @@ namespace Nikse.SubtitleEdit.Core.Forms
|
||||
var nextParagraph = _subtitle.Paragraphs.ElementAtOrDefault(index + 1);
|
||||
if (nextParagraph != null)
|
||||
{
|
||||
var nextInCueFrame = MillisecondsToFrames(nextParagraph.StartTime.TotalMilliseconds);
|
||||
newCueFrame = Math.Min(bestCueFrame, nextInCueFrame - Configuration.Settings.BeautifyTimeCodes.Profile.Gap);
|
||||
var distance = paragraph.StartTime.TotalMilliseconds - nextParagraph.EndTime.TotalMilliseconds;
|
||||
|
||||
// If an overlap threshold is set, don't fix if threshold exceeded
|
||||
if (distance < 0 && Configuration.Settings.BeautifyTimeCodes.OverlapThreshold > 0 && Math.Abs(distance) >= Configuration.Settings.BeautifyTimeCodes.OverlapThreshold)
|
||||
{
|
||||
newCueFrame = bestCueFrame;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else, limit to adjacent subtitle
|
||||
var nextInCueFrame = MillisecondsToFrames(nextParagraph.StartTime.TotalMilliseconds);
|
||||
newCueFrame = Math.Min(bestCueFrame, nextInCueFrame - Configuration.Settings.BeautifyTimeCodes.Profile.Gap);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -902,6 +1001,16 @@ namespace Nikse.SubtitleEdit.Core.Forms
|
||||
return _shotChangesFrames.FirstWithin(leftCueFrame, rightCueFrame);
|
||||
}
|
||||
|
||||
private int? GetLastShotChangeFrameInBetween(int leftCueFrame, int rightCueFrame)
|
||||
{
|
||||
if (_shotChangesFrames == null || _shotChangesFrames.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _shotChangesFrames.LastWithin(leftCueFrame, rightCueFrame);
|
||||
}
|
||||
|
||||
private int? GetClosestShotChangeFrame(int cueFrame)
|
||||
{
|
||||
if (_shotChangesFrames == null || _shotChangesFrames.Count == 0)
|
||||
|
@ -1068,6 +1068,7 @@ Command line: {1} {2}
|
||||
<XRequiresALocalWebServer>"{0}" 需要在本地运行的网页服务器!</XRequiresALocalWebServer>
|
||||
<XRequiresAnApiKey>"{0}"需要一个API密钥。</XRequiresAnApiKey>
|
||||
<ReadMore>了解更多?</ReadMore>
|
||||
<Formality>正式程度</Formality>
|
||||
</GoogleTranslate>
|
||||
<GoogleOrMicrosoftTranslate>
|
||||
<Title>谷歌 与 微软 翻译</Title>
|
||||
|
Loading…
Reference in New Issue
Block a user