/// <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); }
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)); }
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))); } }
/// <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)); }
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); } }
public CardInfo GetCardClone(int index) { return(UtilsCommon.DeepClone(m_cardInfos[index])); }
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; } } } }
/** 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) ); } } }
/// <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); }
/// <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); }
/// <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); }
/// <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]", "_")); }
/// <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); * } */ }