From e7f6147ce57f1b1abcaeef8c18499bd9cb6236f1 Mon Sep 17 00:00:00 2001 From: Maxime Gervais Date: Mon, 29 Jul 2024 22:39:06 +0200 Subject: [PATCH] Allows per output xml and cc files Signed-off-by: Maxime Gervais --- Source/CLI/CommandLine_Parser.cpp | 53 +++++--- Source/Common/Core.cpp | 41 ++++--- Source/Common/Core.h | 14 ++- Source/Common/Output_Captions_Decode.cpp | 46 ++++++- Source/Common/Output_Captions_Decode.h | 3 +- Source/Common/Output_Captions_Scc.cpp | 44 ++++++- Source/Common/Output_Captions_Scc.h | 3 +- Source/Common/Output_Xml.cpp | 149 +++++++++++++++++------ Source/Common/Output_Xml.h | 3 +- 9 files changed, 271 insertions(+), 85 deletions(-) diff --git a/Source/CLI/CommandLine_Parser.cpp b/Source/CLI/CommandLine_Parser.cpp index d8441c73c..0cc5db8c9 100644 --- a/Source/CLI/CommandLine_Parser.cpp +++ b/Source/CLI/CommandLine_Parser.cpp @@ -172,15 +172,14 @@ return_value Parse(Core &C, int argc, const char* argv_ansi[], const MediaInfoNa return_value ReturnValue = ReturnValue_OK; bool ClearInput = false; caption_kind CaptionKind = Caption_Unknown; - const char* Xml_OutputFileName = nullptr; const char* Webvtt_OutputFileName = nullptr; const char* MergeInfo_OutputFileName = nullptr; const char* Merge_Rewind_BaseName = nullptr; bitset Flags; - bool OutputFrames_Speed = false; - bool OutputFrames_Speed_StdOut = false; - bool OutputFrames_Concealed = false; - bool OutputFrames_Concealed_StdOut = false; + bool OutputFrames_Speed = true; + bool OutputFrames_Speed_StdOut = true; + bool OutputFrames_Concealed = true; + bool OutputFrames_Concealed_StdOut = true; // Commands in priority for (int i = 1; i < argc; i++) @@ -318,7 +317,9 @@ return_value Parse(Core &C, int argc, const char* argv_ansi[], const MediaInfoNa CurrentCaptionKind = CaptionKind; CaptionKind = Caption_Unknown; } - C.CaptionsFileNames[CurrentCaptionKind] = move(CurrentFileName); + Core::OutFile CaptionFile; + CaptionFile.Name = move(CurrentFileName); + C.CaptionsFileNames[CurrentCaptionKind].push_back(CaptionFile); } else if (!strcmp(argv_ansi[i], "--capture") || !strcmp(argv_ansi[i], "-capture")) { @@ -357,6 +358,15 @@ return_value Parse(Core &C, int argc, const char* argv_ansi[], const MediaInfoNa OutputFrames_Speeds.push_back(OutputFrames_Speed); OutputFrames_Concealeds.push_back(OutputFrames_Concealed); } + + if (!C.XmlFiles.empty() && C.XmlFiles.back().Merge_OutputFileName.empty()) + C.XmlFiles.back().Merge_OutputFileName = argv_ansi[i]; + + for (auto& CaptionFileNames : C.CaptionsFileNames) + { + if (CaptionFileNames.second.back().Merge_OutputFileName.empty()) + CaptionFileNames.second.back().Merge_OutputFileName = argv_ansi[i]; + } } else if (!strcmp(argv_ansi[i], "--merge-log")) { @@ -719,7 +729,10 @@ return_value Parse(Core &C, int argc, const char* argv_ansi[], const MediaInfoNa *C.Err << "Error: missing XML output file name after " << argv_ansi[i-1] << ".\n"; return ReturnValue_ERROR; } - Xml_OutputFileName = argv_ansi[i]; + + Core::OutFile XmlFile; + XmlFile.Name = argv_ansi[i]; + C.XmlFiles.push_back(XmlFile); } else if (!strcmp(argv_ansi[i], "-n")) { @@ -779,7 +792,7 @@ return_value Parse(Core &C, int argc, const char* argv_ansi[], const MediaInfoNa } else { - if (Webvtt_OutputFileName || Xml_OutputFileName || !Merge_OutputFileNames.empty() || Merge_OutputFileNames_IncludesStdOut) + if (Webvtt_OutputFileName || !C.XmlFiles.empty() || !Merge_OutputFileNames.empty() || Merge_OutputFileNames_IncludesStdOut) { if (C.Err) *C.Err << "Error: in order to avoid mistakes, provide output file names after input file names.\n"; @@ -904,16 +917,13 @@ return_value Parse(Core &C, int argc, const char* argv_ansi[], const MediaInfoNa Merge_Out.push_back(Temp); } + for (Core::OutFile& OutputFile : C.XmlFiles) + OutputFiles_OpenError |= OpenTruncateFile(OutputFile.File, OutputFile.Name.c_str(), C, Flags); + if ((OutputFiles_OpenError) - || (Xml_OutputFileName && OpenTruncateFile(C.XmlFile, Xml_OutputFileName, C, Flags)) || (Webvtt_OutputFileName && OpenTruncateFile(C.WebvttFile, Webvtt_OutputFileName, C, Flags)) || (MergeInfo_OutputFileName && OpenTruncateFile(MergeInfo_Out, MergeInfo_OutputFileName, C, Flags))) { - if (C.XmlFile) - { - delete C.XmlFile; - remove(Xml_OutputFileName); - } if (C.WebvttFile) { delete C.WebvttFile; @@ -925,6 +935,14 @@ return_value Parse(Core &C, int argc, const char* argv_ansi[], const MediaInfoNa remove(MergeInfo_OutputFileName); } + for (Core::OutFile& OutputFile : C.XmlFiles) + { + if (OutputFile.File && OutputFile.File != C.Out) + delete OutputFile.File; + remove(OutputFile.Name.c_str()); + } + C.XmlFiles.clear(); + for (FILE* OutputFile : Merge_Out) fclose(OutputFile); Merge_Out.clear(); @@ -986,6 +1004,9 @@ void Clean(Core& C) // We previously set some output file pointers, deleting them if (C.WebvttFile != C.Out) delete C.WebvttFile; - if (C.XmlFile != C.Out) - delete C.XmlFile; + for (Core::OutFile& OutputFile : C.XmlFiles) + { + if (OutputFile.File && OutputFile.File != C.Out) + delete OutputFile.File; + } } \ No newline at end of file diff --git a/Source/Common/Core.cpp b/Source/Common/Core.cpp index b9cb94744..7ff66fba6 100644 --- a/Source/Common/Core.cpp +++ b/Source/Common/Core.cpp @@ -81,18 +81,23 @@ return_value Core::Process() if (!Merge_Out.empty()) { PerFile[0]->Merge_Finish(); - if (!XmlFile) + if (XmlFiles.empty()) return ToReturn; } // Set output defaults - if (!XmlFile) - XmlFile = Out; + if (XmlFiles.empty()) + { + //TODO: select first output, last output or input in that case? + OutFile XmlFile; + XmlFile.File = Out; + XmlFiles.push_back(XmlFile); + } // XML - if (XmlFile) + for (OutFile& XmlFile : XmlFiles) { - if (auto ToReturn2 = Output_Xml(*XmlFile, PerFile, Options, Err)) + if (auto ToReturn2 = Output_Xml(XmlFile, PerFile, Options, Err)) ToReturn = ToReturn2; } @@ -104,23 +109,23 @@ return_value Core::Process() } // Closed Captions - if (!CaptionsFileNames.empty()) + for (const auto& Caption : CaptionsFileNames) { - // SCC - auto SccFileName = CaptionsFileNames.find(Caption_Scc); - if (SccFileName != CaptionsFileNames.end()) + if (Caption.first == Caption_Scc) { - if (auto ToReturn2 = Output_Captions_Scc(SccFileName->second, OffsetTimeCode, PerFile, Err)) - ToReturn = ToReturn2; + for (const auto& OutFile : Caption.second) + { + if (auto ToReturn2 = Output_Captions_Scc(OutFile, OffsetTimeCode, PerFile, Err)) + ToReturn = ToReturn2; + } } - - // Decode (Screen or SRT) - auto ScreenFileName = CaptionsFileNames.find(Caption_Screen); - auto SrtFileName = CaptionsFileNames.find(Caption_Srt); - if (ScreenFileName != CaptionsFileNames.end() || SrtFileName != CaptionsFileNames.end()) + else if (Caption.first == Caption_Screen || Caption.first == Caption_Srt) { - if (auto ToReturn2 = Output_Captions_Caption(ScreenFileName != CaptionsFileNames.end() ? ScreenFileName->second : string(), SrtFileName != CaptionsFileNames.end() ? SrtFileName->second : string(), OffsetTimeCode, PerFile, Err)) - ToReturn = ToReturn2; + for (const auto& OutFile : Caption.second) + { + if (auto ToReturn2 = Output_Captions_Caption(Caption.first == Caption_Screen ? OutFile : Core::OutFile(), Caption.first == Caption_Srt ? OutFile : Core::OutFile(), OffsetTimeCode, PerFile, Err)) + ToReturn = ToReturn2; + } } } diff --git a/Source/Common/Core.h b/Source/Common/Core.h index c4e35da84..29a51e2ae 100644 --- a/Source/Common/Core.h +++ b/Source/Common/Core.h @@ -36,16 +36,26 @@ enum caption_kind class Core { public: + //Object + struct OutFile + { + ostream* File = nullptr; + string Name; + + // Reference file, input if empty + string Merge_OutputFileName; + }; + // Constructor/Destructor Core(); ~Core(); // Input vector Inputs; - map CaptionsFileNames; // We don't directly open an ostream because file name may change if cc are not same and/or 2nd field and/or no cc + map> CaptionsFileNames; // We don't directly open an ostream because file name may change if cc are not same and/or 2nd field and/or no cc TimeCode* OffsetTimeCode = nullptr; ostream* WebvttFile = nullptr; - ostream* XmlFile = nullptr; + vector XmlFiles; ostream* Out = nullptr; ostream* Err = nullptr; bitset Options; diff --git a/Source/Common/Output_Captions_Decode.cpp b/Source/Common/Output_Captions_Decode.cpp index 28da6407f..f49619f04 100644 --- a/Source/Common/Output_Captions_Decode.cpp +++ b/Source/Common/Output_Captions_Decode.cpp @@ -11,6 +11,7 @@ #include "ccdecoder_line21.h" #include "ccdecoder_subrip.h" #include "ccdecoder_onscreen.h" +#include #include //--------------------------------------------------------------------------- @@ -172,7 +173,7 @@ struct decoded_data //*************************************************************************** //--------------------------------------------------------------------------- -static return_value Output_Captions_Decode(const string& ScreenOutName, const string& SrtOutName, const vector& PerFrame_Captions, int Field, ostream* Err) +static return_value Output_Captions_Decode(const string& ScreenOutName, const string& SrtOutName, const vector& PerFrame_Captions, const vector& IgnoredFrames, int Field, ostream* Err) { auto ToReturn = ReturnValue_OK; @@ -185,6 +186,9 @@ static return_value Output_Captions_Decode(const string& ScreenOutName, const st // By Frame - For each line for (const auto& Frame : PerFrame_Captions) { + if (find(IgnoredFrames.begin(), IgnoredFrames.end(), Frame.StartFrameNumber) != IgnoredFrames.end()) + continue; + for (size_t i = 0; i < Frame.Captions.size(); i++) { const auto& Caption = Frame.Captions[i]; @@ -279,28 +283,60 @@ static return_value Output_Captions_Decode(const string& ScreenOutName, const st } //--------------------------------------------------------------------------- -return_value Output_Captions_Caption(const string& ScreenOutName, const string& SrtOutName, const TimeCode* OffsetTimeCode, std::vector& PerFile, ostream* Err) +return_value Output_Captions_Caption(const Core::OutFile& ScreenOut, const Core::OutFile& SrtOut, const TimeCode* OffsetTimeCode, std::vector& PerFile, ostream* Err) { auto ToReturn = ReturnValue_OK; + bool OutputFrames_Speed = true; + bool OutputFrames_Concealed = true; + + auto It = find(Merge_OutputFileNames.begin(), Merge_OutputFileNames.end(), ScreenOut.Merge_OutputFileName.size() ? ScreenOut.Merge_OutputFileName : SrtOut.Merge_OutputFileName); + if (It != Merge_OutputFileNames.end()) + { + size_t Pos = It - Merge_OutputFileNames.begin(); + OutputFrames_Speed = OutputFrames_Speeds[Pos]; + OutputFrames_Concealed = OutputFrames_Concealeds[Pos]; + } + for (const auto& File : PerFile) { if (File->PerFrame_Captions_PerSeq_PerField.empty()) continue; // Show the file only if there is some captions content + // filter + vector IgnoredFrames; + if (!OutputFrames_Speed || !OutputFrames_Concealed) + { + for (auto Frame = File->PerFrame.begin(); Frame < File->PerFrame.end(); ++Frame) + { + coherency_flags Coherency(*Frame); + if (!OutputFrames_Concealed && Coherency.full_conceal()) + { + IgnoredFrames.push_back(Frame - File->PerFrame.begin()); + continue; + } + + if (!OutputFrames_Speed && !GetDvSpeedIsNormalPlayback(GetDvSpeed(**Frame))) + { + IgnoredFrames.push_back(Frame - File->PerFrame.begin()); + continue; + } + } + } + // Per Dseq for (size_t i = 0; i < File->PerFrame_Captions_PerSeq_PerField.size(); i++) // Per Dseq { for (int j = 0; j < 2; j++) // Per field { - string ScreenOutNameWithDseq(ScreenOutName); + string ScreenOutNameWithDseq(ScreenOut.Name); if (!ScreenOutNameWithDseq.empty() && File->PerFrame_Captions_PerSeq_PerField.size() > 1) InjectBeforeExtension(ScreenOutNameWithDseq, ".dseq", i); - string SrtOutNameWithDseq(SrtOutName); + string SrtOutNameWithDseq(SrtOut.Name); if (!SrtOutNameWithDseq.empty() && File->PerFrame_Captions_PerSeq_PerField.size() > 1) InjectBeforeExtension(SrtOutNameWithDseq, ".dseq", i); - if (!File->PerFrame_Captions_PerSeq_PerField[i].FieldData[j].empty() && !Output_Captions_Decode(ScreenOutNameWithDseq, SrtOutNameWithDseq, File->PerFrame_Captions_PerSeq_PerField[i].FieldData[j], j, Err)) + if (!File->PerFrame_Captions_PerSeq_PerField[i].FieldData[j].empty() && !Output_Captions_Decode(ScreenOutNameWithDseq, SrtOutNameWithDseq, File->PerFrame_Captions_PerSeq_PerField[i].FieldData[j], IgnoredFrames, j, Err)) ToReturn = ReturnValue_ERROR; } } diff --git a/Source/Common/Output_Captions_Decode.h b/Source/Common/Output_Captions_Decode.h index 4d6b87fdd..d33735f8d 100644 --- a/Source/Common/Output_Captions_Decode.h +++ b/Source/Common/Output_Captions_Decode.h @@ -8,8 +8,9 @@ #pragma once #include #include +#include "Common/Core.h" using namespace std; class file; //--------------------------------------------------------------------------- -return_value Output_Captions_Caption(const string& ScreenOutName, const string& SrtOutName, const TimeCode* OffsetTimeCode, vector& PerFile, ostream* Err = nullptr); +return_value Output_Captions_Caption(const Core::OutFile& ScreenOut, const Core::OutFile& SrtOut, const TimeCode* OffsetTimeCode, vector& PerFile, ostream* Err = nullptr); diff --git a/Source/Common/Output_Captions_Scc.cpp b/Source/Common/Output_Captions_Scc.cpp index 976c276a1..0d38d943f 100644 --- a/Source/Common/Output_Captions_Scc.cpp +++ b/Source/Common/Output_Captions_Scc.cpp @@ -10,6 +10,7 @@ #include "Common/ProcessFile.h" #include "TimeCode.h" #include +#include #include //--------------------------------------------------------------------------- @@ -41,7 +42,7 @@ static void InjectBeforeExtension(string& Name, const char* ToInject, size_t Ind //*************************************************************************** //--------------------------------------------------------------------------- -static return_value Output_Captions_Scc(const string& OutName, const TimeCode& TC_Base, const vector& PerFrame_Captions, ostream* Err) +static return_value Output_Captions_Scc(const string& OutName, const TimeCode& TC_Base, const vector& PerFrame_Captions, const vector& IgnoredFrames, ostream* Err) { auto ToReturn = ReturnValue_OK; @@ -65,6 +66,9 @@ static return_value Output_Captions_Scc(const string& OutName, const TimeCode& T // By Frame - For each line for (const auto& Frame : PerFrame_Captions) { + if (find(IgnoredFrames.begin(), IgnoredFrames.end(), Frame.StartFrameNumber) != IgnoredFrames.end()) + continue; + TimeCode TC = TC_Base + Frame.StartFrameNumber; Text += TC.ToString(); @@ -94,15 +98,47 @@ static return_value Output_Captions_Scc(const string& OutName, const TimeCode& T } //--------------------------------------------------------------------------- -return_value Output_Captions_Scc(const string& OutName, const TimeCode* OffsetTimeCode, std::vector& PerFile, ostream* Err) +return_value Output_Captions_Scc(const Core::OutFile& Out, const TimeCode* OffsetTimeCode, std::vector& PerFile, ostream* Err) { auto ToReturn = ReturnValue_OK; + bool OutputFrames_Speed = true; + bool OutputFrames_Concealed = true; + + auto It = find(Merge_OutputFileNames.begin(), Merge_OutputFileNames.end(), Out.Merge_OutputFileName); + if (It != Merge_OutputFileNames.end()) + { + size_t Pos = It - Merge_OutputFileNames.begin(); + OutputFrames_Speed = OutputFrames_Speeds[Pos]; + OutputFrames_Concealed = OutputFrames_Concealeds[Pos]; + } + for (const auto& File : PerFile) { if (File->PerFrame_Captions_PerSeq_PerField.empty()) continue; // Show the file only if there is some captions content + // filter + vector IgnoredFrames; + if (!OutputFrames_Speed || !OutputFrames_Concealed) + { + for (auto Frame = File->PerFrame.begin(); Frame < File->PerFrame.end(); ++Frame) + { + coherency_flags Coherency(*Frame); + if (!OutputFrames_Concealed && Coherency.full_conceal()) + { + IgnoredFrames.push_back(Frame - File->PerFrame.begin()); + continue; + } + + if (!OutputFrames_Speed && !GetDvSpeedIsNormalPlayback(GetDvSpeed(**Frame))) + { + IgnoredFrames.push_back(Frame - File->PerFrame.begin()); + continue; + } + } + } + // Init time code TimeCode TC_Base; if (OffsetTimeCode) @@ -133,13 +169,13 @@ return_value Output_Captions_Scc(const string& OutName, const TimeCode* OffsetTi { for (size_t j = 0; j < 2; j++) // Per field { - string OutNameWithDseq(OutName); + string OutNameWithDseq(Out.Name); if (File->PerFrame_Captions_PerSeq_PerField.size() > 1) InjectBeforeExtension(OutNameWithDseq, ".dseq", i); if (!File->PerFrame_Captions_PerSeq_PerField[i].FieldData[0].empty() && !File->PerFrame_Captions_PerSeq_PerField[i].FieldData[1].empty()) InjectBeforeExtension(OutNameWithDseq, ".field", i + 1); - if (!File->PerFrame_Captions_PerSeq_PerField[i].FieldData[j].empty() && !Output_Captions_Scc(OutNameWithDseq, TC_Base, File->PerFrame_Captions_PerSeq_PerField[i].FieldData[j], Err)) + if (!File->PerFrame_Captions_PerSeq_PerField[i].FieldData[j].empty() && !Output_Captions_Scc(OutNameWithDseq, TC_Base, File->PerFrame_Captions_PerSeq_PerField[i].FieldData[j], IgnoredFrames, Err)) ToReturn = ReturnValue_ERROR; } } diff --git a/Source/Common/Output_Captions_Scc.h b/Source/Common/Output_Captions_Scc.h index de0379e8b..872443295 100644 --- a/Source/Common/Output_Captions_Scc.h +++ b/Source/Common/Output_Captions_Scc.h @@ -8,9 +8,10 @@ #pragma once #include #include +#include "Common/Core.h" using namespace std; class file; class TimeCode; //--------------------------------------------------------------------------- -return_value Output_Captions_Scc(const string& OutName, const TimeCode* OffsetTimeCode, vector& PerFile, ostream* Err = nullptr); +return_value Output_Captions_Scc(const Core::OutFile& Out, const TimeCode* OffsetTimeCode, vector& PerFile, ostream* Err = nullptr); diff --git a/Source/Common/Output_Xml.cpp b/Source/Common/Output_Xml.cpp index 312c26879..6690e9f15 100644 --- a/Source/Common/Output_Xml.cpp +++ b/Source/Common/Output_Xml.cpp @@ -13,6 +13,7 @@ #include "ZenLib/Ztring.h" #include #include +#include #include #include using namespace ZenLib; @@ -210,7 +211,7 @@ string decklink_timecodeformat_to_string(uint8_t value) //*************************************************************************** //--------------------------------------------------------------------------- -return_value Output_Xml(ostream& Out, std::vector& PerFile, bitset Options, ostream* Err) +return_value Output_Xml(Core::OutFile& Out, std::vector& PerFile, bitset Options, ostream* Err) { string Text; @@ -223,15 +224,27 @@ return_value Output_Xml(ostream& Out, std::vector& PerFile, bitset