コード例 #1
0
        /// <summary>
        /// Returns the line from "matching lines" that has the least overlapping rating with "lineInfo".
        /// </summary>
        private static ExtendedLineInfo GetLeastFittingLine(LineInfo lineInfo, LinkedList <ExtendedLineInfo> matchingLines)
        {
            double           leastFittingOverlappingScore = 0.5;
            ExtendedLineInfo leastFittingLine             = null;

            foreach (ExtendedLineInfo matchingLine in matchingLines)
            {
                double currentOverlappingScore = UtilsCommon.OverlappingScore(lineInfo, matchingLine);
                if (leastFittingLine == null || (currentOverlappingScore < leastFittingOverlappingScore))
                {
                    leastFittingOverlappingScore = currentOverlappingScore;
                    leastFittingLine             = matchingLine;
                }
            }

            return(leastFittingLine);
        }
コード例 #2
0
        private static List <LineInfo> ParseSubtitleInVideoFile(Settings settings, string filename, Dictionary <String, String> properties)
        {
            StreamInfo subtitleStreamInfo = UtilsVideo.ChooseStreamInfo(filename, properties, StreamInfo.StreamType.ST_SUBTITLE);

            // new subtitle file
            String videoFileHash       = UtilsCommon.GetDateSizeChecksum(filename);
            String newSubtitleFileName = videoFileHash + "_" + subtitleStreamInfo.StreamIndex + GetExtensionByStreamInfo(subtitleStreamInfo);
            string newSubtitleFilePath = InstanceSettings.temporaryFilesPath + newSubtitleFileName;

            // do not extract again when file was already extracted once
            if (!File.Exists(newSubtitleFilePath))
            {
                UtilsVideo.ExtractStream(filename, subtitleStreamInfo, newSubtitleFilePath);
            }

            return(ParseSubtitle(settings, newSubtitleFilePath, properties));
        }
コード例 #3
0
        public static List <LineInfo> ParseSubtitle(Settings settings, string filename, Dictionary <String, String> properties)
        {
            string mimeType = UtilsCommon.GetMimetypeByFilename(filename);

            // find right parser
            ISubtitleParser parser = null;

            switch (mimeType)
            {
            case "text/x-ass":
            case "text/x-ssa":
                parser = new SubtitleParserASS();
                break;

            case "application/x-subrip":
                parser = new SubtitleParserSRT();
                break;

            case "video/x-matroska":
                return(ParseSubtitleInVideoFile(settings, filename, properties));

            case "":
                throw new Exception("File type/mime type could not be recognized for file \"" + filename + "\"!");

            default:
                throw new Exception("Unsupportet format (" + mimeType + ") for subtitle \"" + filename + "\"!");
            }

            // read encoding string from properties
            String encodingString = "utf-8";

            if (properties.ContainsKey("enc"))
            {
                encodingString = properties["enc"];
            }

            // read all lines
            using (var fileStream = new FileStream(filename, FileMode.Open)) {
                return(parser.parse(settings, fileStream, Encoding.GetEncoding(encodingString)));
            }
        }
コード例 #4
0
        /// <summary>
        /// Gets width and height of video from video StreamInfo, then applies <see cref="UtilsCommon.GetMaxScaling">.
        /// </summary>
        public static double GetMaxScalingByStreamInfo(StreamInfo videoStreamInfo, double maxWidth, double maxHeight, Settings.RescaleModeEnum rescaleMode)
        {
            if (rescaleMode == Settings.RescaleModeEnum.NoRescaling)
            {
                return(1);
            }

            // get size of image in video streams
            Int32?videoWidth  = videoStreamInfo.GetAttributeInt("width");
            Int32?videoHeight = videoStreamInfo.GetAttributeInt("height");

            if (videoWidth == null)
            {
                videoWidth = -1;                                        // ignore this dimension
            }
            if (videoHeight == null)
            {
                videoHeight = -1;                                         // ignore this dimension
            }
            return(UtilsCommon.GetMaxScaling(videoWidth.Value, videoHeight.Value, maxWidth, maxHeight, rescaleMode == Settings.RescaleModeEnum.Downscale));
        }
コード例 #5
0
        public static void NormalizeAudio(String filename, StreamInfo audioStreamInfo)
        {
            if (!filename.EndsWith("ogg"))
            {
                throw new Exception("Only .ogg files are currently supported for normalizing!");
            }
            double maxVolume    = GetMaxVolume(filename, audioStreamInfo);
            double targetVolume = InstanceSettings.systemSettings.normalizeTargetVolume;

            String tmpFilename = InstanceSettings.temporaryFilesPath + Path.GetFileName(filename);
            String arguments   = String.Format("-y -i \"{0}\" -af \"volume={1}dB\" -c:a libvorbis -vn \"{2}\"", filename, (-maxVolume + targetVolume).ToString(System.Globalization.CultureInfo.InvariantCulture), tmpFilename);

            UtilsCommon.StartProcessAndGetOutput(InstanceSettings.systemSettings.formatConvertCommand, arguments);

            // move new file to original position
            if (File.Exists(tmpFilename))
            {
                File.Delete(filename);
                File.Move(tmpFilename, filename);
            }
        }
コード例 #6
0
 public CardInfo GetCardClone(int index)
 {
     return(UtilsCommon.DeepClone(m_cardInfos[index]));
 }
コード例 #7
0
        public void ExportData(Settings settings, InfoProgress progressInfo)
        {
            var activeCardList = GetActiveCards();

            progressInfo.AddSection("Exporting text file", 1);
            progressInfo.AddSection("Exporting snapshots", activeCardList.Count);
            progressInfo.AddSection("Exporting audio files", activeCardList.Count);
            if (settings.NormalizeAudio)
            {
                progressInfo.AddSection("Normalize audio files", activeCardList.Count);
            }
            progressInfo.Update();

            ExportTextFile(activeCardList, settings, progressInfo);

            progressInfo.ProcessedSteps(1);

            var cardSnapshotNameTupleList = new List <Tuple <CardInfo, String> >(activeCardList.Count);
            var cardAudioNameTupleList    = new List <Tuple <CardInfo, String> >(activeCardList.Count);

            foreach (var cardInfo in activeCardList)
            {
                cardSnapshotNameTupleList.Add(new Tuple <CardInfo, String>(cardInfo, GetSnapshotFileName(settings, cardInfo)));
                cardAudioNameTupleList.Add(new Tuple <CardInfo, String>(cardInfo, GetAudioFileName(settings, cardInfo)));
            }

            if (progressInfo.Cancelled)
            {
                return;
            }

            // extract images
            String snapshotsPath = settings.OutputDirectoryPath + Path.DirectorySeparatorChar + settings.DeckName + "_snapshots" + Path.DirectorySeparatorChar;

            UtilsCommon.ClearDirectory(snapshotsPath);
            WorkerSnapshot.ExtractSnaphots(settings, snapshotsPath, cardSnapshotNameTupleList, progressInfo);

            if (progressInfo.Cancelled)
            {
                return;
            }

            // extract audio
            String audioPath = settings.OutputDirectoryPath + Path.DirectorySeparatorChar + settings.DeckName + "_audio" + Path.DirectorySeparatorChar;

            UtilsCommon.ClearDirectory(audioPath);
            WorkerAudio.ExtractAudio(settings, audioPath, cardAudioNameTupleList, progressInfo);

            if (progressInfo.Cancelled)
            {
                return;
            }

            if (settings.NormalizeAudio)
            {
                // normalize all audio files
                foreach (var entry in cardAudioNameTupleList)
                {
                    if (progressInfo.Cancelled)
                    {
                        return;
                    }
                    progressInfo.ProcessedSteps(1);

                    var cardInfo = entry.Item1;
                    if (!cardInfo.HasAudio())
                    {
                        continue;
                    }

                    var filepath         = audioPath + entry.Item2;
                    var audioStreamInfos = StreamInfo.ReadAllStreams(filepath);
                    audioStreamInfos.RemoveAll(streamInfo => streamInfo.StreamTypeValue != StreamInfo.StreamType.ST_AUDIO);
                    if (audioStreamInfos.Count != 1)
                    {
                        Console.WriteLine("Skipped normalizing file \"{0}\" because it contains {1} audio streams", filepath, audioStreamInfos.Count);
                        continue;
                    }
                    try {
                        UtilsAudio.NormalizeAudio(filepath, audioStreamInfos[0]);
                    } catch (Exception e) {
                        Console.WriteLine(e.ToString());
                        continue;
                    }
                }
            }
        }
コード例 #8
0
        /** Generates a .tsv file */
        public void ExportTextFile(List <CardInfo> cardInfoList, Settings settings, InfoProgress progressInfo)
        {
            String tsvFilename = settings.OutputDirectoryPath + Path.DirectorySeparatorChar + settings.DeckName + ".tsv";

            Console.WriteLine(tsvFilename);

            // value that will be imported into Anki/SRS-Programs-Field => [sound:???.ogg] and <img src="???.jpg"/>
            var snapshotFields = new List <String>(cardInfoList.Count);
            var audioFields    = new List <String>(cardInfoList.Count);

            foreach (var cardInfo in cardInfoList)
            {
                if (cardInfo.HasImage())
                {
                    var outputSnapshotFilename = GetSnapshotFileName(settings, cardInfo);
                    snapshotFields.Add("<img src=\"" + outputSnapshotFilename + "\"/>"); // TODO: make this flexible
                }
                else
                {
                    snapshotFields.Add("");
                }

                if (cardInfo.HasAudio())
                {
                    var outputAudioFilename = GetAudioFileName(settings, cardInfo);
                    audioFields.Add("[sound:" + outputAudioFilename + "]"); // TODO: make this flexible
                }
                else
                {
                    audioFields.Add("");
                }
            }

            using (var outputStream = new StreamWriter(tsvFilename))
            {
                for (int i = 0; i < cardInfoList.Count; i++)
                {
                    CardInfo cardInfo = cardInfoList[i];

                    // XXX: performance analasys then --- generate a episode-filtered list for context card search (because it has O(n^2) steps)
                    var contextCardsTuple = UtilsSubtitle.GetContextCards(cardInfo.episodeInfo.Index, cardInfo, m_cardInfos);
                    var previousCards     = contextCardsTuple.Item1;
                    var nextCards         = contextCardsTuple.Item2;

                    var previousCardsNativeLanguage = UtilsSubtitle.CardListToMultilineString(previousCards, UtilsCommon.LanguageType.NATIVE);
                    var previousCardsTargetLanguage = UtilsSubtitle.CardListToMultilineString(previousCards, UtilsCommon.LanguageType.TARGET);

                    var nextCardsNativeLanguage = UtilsSubtitle.CardListToMultilineString(nextCards, UtilsCommon.LanguageType.NATIVE);
                    var nextCardsTargetLanguage = UtilsSubtitle.CardListToMultilineString(nextCards, UtilsCommon.LanguageType.TARGET);

                    String keyField   = cardInfo.GetKey();
                    String audioField = audioFields[i];
                    String imageField = snapshotFields[i];
                    String tags       = String.Format("SubtitleMemorize {0} ep{1:000} {2}", settings.DeckNameModified, cardInfo.episodeInfo.Number, InfoLanguages.languages[settings.TargetLanguageIndex].tag);
                    outputStream.WriteLine(UtilsCommon.HTMLify(keyField) + "\t" +
                                           UtilsCommon.HTMLify(imageField) + "\t" +
                                           UtilsCommon.HTMLify(audioField) + "\t" +
                                           UtilsCommon.HTMLify(cardInfo.ToSingleLine(UtilsCommon.LanguageType.TARGET)) + "\t" +
                                           UtilsCommon.HTMLify(cardInfo.ToSingleLine(UtilsCommon.LanguageType.NATIVE)) + "\t" +
                                           UtilsCommon.HTMLify(previousCardsTargetLanguage) + "\t" +
                                           UtilsCommon.HTMLify(previousCardsNativeLanguage) + "\t" +
                                           UtilsCommon.HTMLify(nextCardsTargetLanguage) + "\t" +
                                           UtilsCommon.HTMLify(nextCardsNativeLanguage) + "\t" +
                                           UtilsCommon.HTMLify(tags)
                                           );
                }
            }
        }
コード例 #9
0
        /// <summary>
        /// XXX: Wrong documentation = Selects preview lines in Gtk.TreeView based on a condtion like "episode=3 and contains(sub1, 'Bye')".
        /// </summary>
        /// <param name="conditionExpr">Condition expr.</param>
        /// <param name="isIncrementalSearch">Only change selection state for lines with matching expressions.</param>
        /// <param name="selectAction">true -> select matching lines, false -> deselect matching lines</param>
        public List <bool> EvaluateForEveryLine(String conditionExpr)
        {
            // select all if expression is null
            var resultsList = Enumerable.Repeat(false, m_cardInfos.Count).ToList();

            if (String.IsNullOrWhiteSpace(conditionExpr))
            {
                return(resultsList);
            }


            int        currentCardIndex = 0; // card index which will be used for evaluation an expression in delegate
            Expression expr             = new Expression(conditionExpr);

            // resolve certain parameters in expression
            expr.EvaluateParameter += delegate(string name, ParameterArgs args)
            {
                CardInfo infoSourceCard = m_cardInfos[currentCardIndex];
                switch (name)
                {
                case "isActive":     // fallthrough
                case "active": args.Result = infoSourceCard.isActive; break;

                case "number":
                case "episodeNumber":
                case "episode": args.Result = infoSourceCard.episodeInfo.Number; break;

                case "text":
                case "sub": args.Result = infoSourceCard.ToSingleLine(UtilsCommon.LanguageType.TARGET) + " " + infoSourceCard.ToSingleLine(UtilsCommon.LanguageType.NATIVE); break;

                case "sub1":
                case "text1": args.Result = infoSourceCard.ToSingleLine(UtilsCommon.LanguageType.TARGET); break;

                case "sub2":
                case "text2": args.Result = infoSourceCard.ToSingleLine(UtilsCommon.LanguageType.NATIVE); break;

                case "actor":
                case "actors":
                case "name":
                case "names": args.Result = infoSourceCard.GetActorString(); break;

                case "start": args.Result = infoSourceCard.startTimestamp; break;

                case "end": args.Result = infoSourceCard.endTimestamp; break;

                case "duration": args.Result = infoSourceCard.Duration; break;

                case "next_start":
                    args.Result = currentCardIndex + 1 >= m_cardInfos.Count ? 1000000.0 : m_cardInfos[currentCardIndex + 1].StartTime;
                    break;

                case "next_end":
                    args.Result = currentCardIndex + 1 >= m_cardInfos.Count ? 1000000.0 : m_cardInfos[currentCardIndex + 1].EndTime;
                    break;

                case "next_duration":
                    args.Result = currentCardIndex + 1 >= m_cardInfos.Count ? 0 : m_cardInfos[currentCardIndex + 1].Duration;
                    break;

                case "prev_start":
                    args.Result = currentCardIndex <= 0 ? -1000000.0 : m_cardInfos[currentCardIndex - 1].StartTime;
                    break;

                case "prev_end":
                    args.Result = currentCardIndex <= 0 ? -1000000.0 : m_cardInfos[currentCardIndex - 1].EndTime;
                    break;

                case "prev_duration":
                    args.Result = currentCardIndex <= 0 ? 0 : m_cardInfos[currentCardIndex - 1].Duration;
                    break;
                }
            };
            // resolve certain functions in expression
            expr.EvaluateFunction += delegate(string name, FunctionArgs args)
            {
                CardInfo infoSourceCard = m_cardInfos[currentCardIndex];
                switch (name)
                {
                // an exmple for this function is "contains(sub1, 'some text')" that selects all lines, where sub1 contains 'some text'
                case "c":
                case "contains":
                {
                    // two string parameters are expected
                    if (args.Parameters.Length < 1)
                    {
                        return;
                    }
                    object[] arguments = args.EvaluateParameters();
                    foreach (var argument in arguments)
                    {
                        if (!(argument is String))
                        {
                            return;
                        }
                    }

                    String substring = (String)arguments[arguments.Length - 1];
                    args.HasResult = true;
                    args.Result    = false;

                    bool result = false;
                    if (arguments.Length == 1)
                    {
                        result = infoSourceCard.ToSingleLine(UtilsCommon.LanguageType.NATIVE).Contains(substring);
                        if (result)
                        {
                            args.Result = result; goto contains_finished;
                        }

                        result = infoSourceCard.ToSingleLine(UtilsCommon.LanguageType.TARGET).Contains(substring);
                        if (result)
                        {
                            args.Result = result; goto contains_finished;
                        }
                    }
                    else
                    {
                        // evaluate function
                        for (int i = 0; i < arguments.Length - 1; i++)
                        {
                            String string0 = (String)arguments[i];
                            result = string0.Contains(substring);
                            if (result)
                            {
                                args.Result = result; goto contains_finished;
                            }
                        }
                    }

                    contains_finished :;
                }
                break;

                // search for regex; for example: "r(text1, 'hi|hello')"
                // 'r('hi|hello')' searches both in sub1 and sub2
                case "r":
                case "regex":
                {
                    // two string parameters are expected
                    if (args.Parameters.Length < 1)
                    {
                        return;
                    }
                    object[] arguments = args.EvaluateParameters();
                    foreach (var argument in arguments)
                    {
                        if (!(argument is String))
                        {
                            return;
                        }
                    }

                    String regex = (String)arguments[arguments.Length - 1];
                    args.HasResult = true;
                    args.Result    = false;

                    if (arguments.Length == 1)
                    {
                        try
                        {
                            var match = Regex.Match(infoSourceCard.ToSingleLine(UtilsCommon.LanguageType.NATIVE), regex, RegexOptions.Compiled | RegexOptions.IgnoreCase);
                            args.Result = match.Success;
                            if (match.Success)
                            {
                                goto finish_regex;
                            }
                        }
                        catch { }

                        try
                        {
                            var match = Regex.Match(infoSourceCard.ToSingleLine(UtilsCommon.LanguageType.TARGET), regex, RegexOptions.Compiled | RegexOptions.IgnoreCase);
                            args.Result = match.Success;
                            if (match.Success)
                            {
                                goto finish_regex;
                            }
                        }
                        catch { }
                    }
                    else if (arguments.Length > 1)
                    {
                        // try to match any text before regex argument
                        for (int i = 0; i < arguments.Length - 1; i++)
                        {
                            try
                            {
                                String str   = (String)arguments[0];
                                var    match = Regex.Match(str, regex, RegexOptions.Compiled | RegexOptions.IgnoreCase);
                                args.Result = match.Success;
                                if (match.Success)
                                {
                                    goto finish_regex;
                                }
                            }
                            catch { }
                        }
                    }

                    finish_regex :;
                }
                break;

                // example: time('25:10.50') returns the number of seconds of 25min 10secs 50hsecs
                case "time":
                {
                    if (args.Parameters.Length != 1)
                    {
                        return;
                    }
                    object argumentObject = args.EvaluateParameters()[0];
                    if (!(argumentObject is String))
                    {
                        return;
                    }
                    String timeString = (String)argumentObject;

                    args.Result    = UtilsCommon.ParseTimeString(timeString);
                    args.HasResult = true;
                }
                break;
                }
            };

            for (int i = 0; i < m_cardInfos.Count; i++)
            {
                // provide infos for expr.Evaluate()
                currentCardIndex = i;
                object result = expr.Evaluate();
                if (result is bool && result != null)
                {
                    resultsList[i] = (bool)result;
                }
            }

            return(resultsList);
        }
コード例 #10
0
        /// <summary>
        /// Returns the longest list of line containers, for which no line containers overlap. Addtionaly
        /// these containers are sorted by start time.
        /// </summary>
        public static List <LineContainer <T> > GetNonOverlappingTimeSpans <T>(LinkedList <T> lines, double threshold = 0) where T : ITimeSpan
        {
            var containers       = new LinkedList <LineContainer <T> >();
            var lineAlreadyAdded = new bool[lines.Count];
            var lineANode        = lines.First;
            var lineBNode        = lines.First;
            int lineAindex       = 0;
            int lineBindex       = 0;

            while (lineANode != null)
            {
                if (lineAlreadyAdded [lineAindex])
                {
                    lineAindex++;
                    lineANode = lineANode.Next;
                    continue;
                }

                // create new container for this line
                var lineContainer = new LineContainer <T>();
                lineContainer.AddLine(lineANode.Value);
                lineAlreadyAdded[lineAindex] = true;
                containers.AddLast(lineContainer);

restartLoop:
                lineBNode  = lineANode.Next;
                lineBindex = lineAindex + 1;
                while (lineBNode != null)
                {
                    if (lineAlreadyAdded [lineBindex])
                    {
                        lineBindex++;
                        lineBNode = lineBNode.Next;
                        continue;
                    }

                    // just test treshold if line collides with container
                    if (UtilsCommon.IsOverlapping(lineBNode.Value, lineContainer))
                    {
                        foreach (ITimeSpan timeSpanInContainer in lineContainer.TimeSpans)
                        {
                            if (UtilsCommon.OverlappingScore(lineBNode.Value, timeSpanInContainer) > threshold)
                            {
                                lineContainer.AddLine(lineBNode.Value);
                                lineAlreadyAdded[lineBindex] = true;
                                goto restartLoop;
                            }
                        }
                    }

                    lineBindex++;
                    lineBNode = lineBNode.Next;
                }

                lineAindex++;
                lineANode = lineANode.Next;
            }

            // XXX: is sort necessary
            var containerList = containers.ToList();

            containerList.Sort();

            return(containerList);
        }
コード例 #11
0
        /// <summary>
        /// Uses ffprobe/avprobe to query all audio, video and subtitle streams in a container file.
        /// </summary>
        public static List <StreamInfo> ReadAllStreams(String filename)
        {
            // use ffprobe/avprobe(?) to get nice XML-description of contents
            String stdout = UtilsCommon.StartProcessAndGetOutput(InstanceSettings.systemSettings.formatProberCommand, @"-v quiet -print_format xml -show_streams """ + filename + @"""");

            if (stdout == null)
            {
                throw new IOException("Calling ffprobe/avprobe failed! Is it installed?");
            }

            List <StreamInfo> allStreamInfos = new List <StreamInfo> ();
            StreamInfo        lastStreamInfo = null;

            // use XmlReader to read all informations from "stream"-tags and "tag"-tags
            using (XmlReader reader = XmlReader.Create(new StringReader(stdout))) {
                while (reader.Read())
                {
                    if (reader.NodeType != XmlNodeType.Element)
                    {
                        continue;
                    }

                    // the "stream"-tag contains most of the information needed as attributes
                    if (reader.Name == "stream")
                    {
                        // get stream type
                        StreamType streamType;
                        switch (reader["codec_type"])
                        {
                        case "video": streamType = StreamType.ST_VIDEO; break;

                        case "audio": streamType = StreamType.ST_AUDIO; break;

                        case "subtitle": streamType = StreamType.ST_SUBTITLE; break;

                        default: streamType = StreamType.ST_UNKNOWN; break;
                        }

                        // read all other information into dictionary
                        var attributeDictionary = new Dictionary <String, String>();
                        for (int i = 0; i < reader.AttributeCount; i++)
                        {
                            reader.MoveToNextAttribute();
                            attributeDictionary.Add(reader.Name, reader.Value);
                        }

                        StreamInfo streamInfo = new StreamInfo(Int32.Parse(reader["index"]), streamType, reader["codec_name"], attributeDictionary);
                        allStreamInfos.Add(streamInfo);
                        lastStreamInfo = streamInfo;
                    }

                    // the "tag"-tag provides additonal information (mainly language)
                    if (reader.Name == "tag")
                    {
                        if (lastStreamInfo == null)
                        {
                            continue;
                        }

                        switch (reader ["key"])
                        {
                        case "language":
                            lastStreamInfo.m_language = reader ["value"] ?? "";
                            break;

                        default:
                            break;
                        }
                    }
                }
            }

            return(allStreamInfos);
        }
コード例 #12
0
        /// <summary>
        /// Returns some string that identifies this card information.
        /// </summary>
        /// <returns>The key.</returns>
        public String GetKey()
        {
            String str = String.Format("{0:000.}", episodeInfo.Number) + "__" + UtilsCommon.ToTimeArg(startTimestamp) + "__" + UtilsCommon.ToTimeArg(endTimestamp);

            return(Regex.Replace(str, "[^a-zA-Z0-9]", "_"));
        }
コード例 #13
0
        /// <summary>
        /// This creates a list of good matching lines between primaryList and secondaryList.
        /// </summary>
        /// <param name="list1">List1.</param>
        /// <param name="list2">List2.</param>
        private static void FindMatching(List <ExtendedLineInfo> list1, List <ExtendedLineInfo> list2)
        {
            int i1 = 0, i2 = 0;

            foreach (var line in list1)
            {
                line.matchingLines.Clear();
                line.alreadyUsedInBidirectionalSearch = false;
            }

            foreach (var line in list2)
            {
                line.matchingLines.Clear();
                line.alreadyUsedInBidirectionalSearch = false;
            }

            while (i1 < list1.Count)
            {
                int i2_reset = i2;

                while (i2 < list2.Count)
                {
                    // node2 does not overlap with node1? Then proceed with next node1.
                    if (list1[i1].EndTime < list2[i2].StartTime)
                    {
                        break;
                    }

                    // node1 and node2 overlap!
                    if (UtilsCommon.OverlappingScore(list1[i1], list2[i2]) >= 0.4)
                    {
                        list1[i1].matchingLines.AddLast(list2[i2]);
                        list2[i2].matchingLines.AddLast(list1[i1]);
                    }

                    // try combination (node1, next node2) in next inner iteration
                    i2++;
                }

                // do node1 and next node1 overlap? Then all node2's we handled in this iteration might
                // overlap with next node1 -> reset node2 to start of this iteration.
                if (i1 + 1 != list1.Count && list1[i1 + 1].StartTime <= list1[i1].EndTime)
                {
                    i2 = i2_reset;
                }
                i1++;
            }

/*
 *                      foreach(var primaryListLine in primaryList) {
 *                              primaryListLine.matchingLines.Clear();
 *                              primaryListLine.alreadyUsedInBidirectionalSearch = false;
 *
 *                              Action<ExtendedLineInfo, ExtendedLineInfo> matchLines = delegate(ExtendedLineInfo thisLine, ExtendedLineInfo secondaryListLine) {
 *                                      if (UtilsCommon.OverlappingScore(thisLine, secondaryListLine) < 0.4) return;
 *                                      thisLine.matchingLines.AddLast (secondaryListLine);
 *                              };
 *
 *                              secondaryListCache.DoForOverlapping(primaryListLine, matchLines);
 *                      }
 */
        }