/// <summary> /// By calling this function, all references to LineInfo in "listToChange" passed in /// constructor can be changed. Retiming includes removing/introducing commercial breaks. /// /// By using the "noSplitting=true" option, the whole list gets shifted as one. /// </summary> public void Retime(bool noSplitting = false) { var stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); var queue = new Queue <RemainingSlice>(); // initilize first slice (with all lines) RemainingSlice fullSlice = new RemainingSlice(new List <LineInfo>(m_referenceList), new List <LineInfo>(m_listToChange)); queue.Enqueue(fullSlice); // just find best offset if (noSplitting) { double bestOffset = FindBestOffset(fullSlice); UtilsSubtitle.ShiftByTime(m_listToChange, bestOffset); return; } // Only lines that are in the same slice (list of lines on both sides) can be matched. // When processing the slices, a consecutive row of lines is recognized as "good". Two new // slices (one left of the right of the row) are then created. // // This queue will process all these slices that have some elements in them. while (queue.Count > 0) { RemainingSlice slice = queue.Dequeue(); //if(slice.listToChangeLines.Count == 0 || slice.referenceListLines.Count == 0) { // // T O D O: these shouldn't just be moved out of the way but left in another slice // // this is more debug code than anything else // UtilsSubtitle.ShiftByTime(slice.listToChangeLines, 10000); // UtilsSubtitle.ShiftByTime(slice.referenceListLines, 10000); // continue; //} double bestOffset = FindBestOffset(slice); ApplyOffset(slice, bestOffset, queue); } Console.WriteLine(stopwatch.ElapsedMilliseconds); }
/// <summary> /// Find best offset for "listToChange" so it matches "referenceList". There can be /// multiple "peaks" if one subtitle has additional breaks the other does not have. /// Only one of them will be returned. /// </summary> private double FindBestOffset(RemainingSlice slice) { var subtitleMatcherParams = SubtitleMatcher.GetParameterCache(slice.referenceListLines, slice.listToChangeLines); // the tuple is (offset, rating) var firstPassList = new List <OffsetRatingTuple>(5); FindGoodOffsets(subtitleMatcherParams, 0, 0.3, 1000, firstPassList); // find fine grained offsets around approximated offsets var secondPassList = new List <OffsetRatingTuple>(5); foreach (var offsetRatingTuple in firstPassList) { FindGoodOffsets(subtitleMatcherParams, offsetRatingTuple.offset, 0.01, 90, secondPassList); } return(secondPassList[0].offset); }
/// <summary> /// This function will... /// a) move all lines to change in slice by offset /// b) match the lines in "listToChange" and "referenceList" /// c) find a threshold value for matchings /// d) find the longest row of consecutive matchings that are above the threshold /// e) create slices for lines before and lines after this "good row" /// f) reset offset of these remaining lines and put slice into queue /// </summary> private void ApplyOffset(RemainingSlice slice, double offset, Queue <RemainingSlice> queue) { UtilsSubtitle.ShiftByTime(slice.listToChangeLines, offset); // match lines var subtitleMatcherParameters = SubtitleMatcher.GetParameterCache(slice.referenceListLines, slice.listToChangeLines); var biMatchedLinesLinkedList = SubtitleMatcher.MatchSubtitles(subtitleMatcherParameters); var biMatchedLinesList = new List <SubtitleMatcher.BiMatchedLines>(biMatchedLinesLinkedList); biMatchedLinesList.Sort(delegate(SubtitleMatcher.BiMatchedLines x, SubtitleMatcher.BiMatchedLines y) { return(GetStartTime(x) < GetStartTime(y) ? -1 : 1); }); // -------------------------------------------------- // find threshold rating double averageRating = 0; int numRatings = 0; foreach (var biMatchedLines in biMatchedLinesList) { double rating = RateBiMatchedLines(biMatchedLines); if (rating > 0.0001) // ignore "zero" ratings { averageRating += rating; numRatings++; } } averageRating /= numRatings; double thresholdValue = averageRating * 0.8; // -------------------------------------------------- // Find longest row over threshold rating. // // Zero ratings may be inbetween good ratings when some // lines couldn't get matched (for example street-sign // translations that aren't in subtitle file in native language). // These are stepped over: There can be a limited number of zero ratings // ratings in the row except at the beginning and the end (these will // get matched at a different time if possible). int numGoodMatched = 0; int currentRowStart = 0; int allowedZeroRatings = 0; int maxNumGoodMatched = -1; int bestRowStart = 0; for (int index = 0; index < biMatchedLinesList.Count; index++) { var biMatchedLines = biMatchedLinesList[index]; double rating = RateBiMatchedLines(biMatchedLines); if (rating < thresholdValue) { // step over zero ratings if (rating > 0.000001 || allowedZeroRatings-- < 0) { numGoodMatched = 0; // not a zero rating } } else { // update row start/end if (numGoodMatched == 0) { currentRowStart = index; } numGoodMatched = index - currentRowStart + 1; // save best value if (numGoodMatched > maxNumGoodMatched) { maxNumGoodMatched = numGoodMatched; bestRowStart = currentRowStart; } allowedZeroRatings = 4; } } // could good row be found? if (maxNumGoodMatched == -1) { return; } // create new slices left and right RemainingSlice newSlice; // left slice newSlice = GetSubSlice(biMatchedLinesList, 0, bestRowStart); //UtilsSubtitle.ShiftByTime(newSlice.listToChangeLines, -offset); // by disabling this code, remaining slices are now embedded in greater slices queue.Enqueue(newSlice); // right slice newSlice = GetSubSlice(biMatchedLinesList, bestRowStart + maxNumGoodMatched + 1, biMatchedLinesList.Count); //UtilsSubtitle.ShiftByTime(newSlice.listToChangeLines, -offset); queue.Enqueue(newSlice); }