From 8e40d2412c2da6c82fa75e239ed13b358af8baec Mon Sep 17 00:00:00 2001 From: YourMJK Date: Fri, 2 Feb 2024 00:55:41 +0100 Subject: [PATCH 1/2] Add dash prefix for cli options & Completed and expanded usage help --- README.md | 72 ++++++++++++++++++++++++++++++++++--------------------- main.cpp | 47 +++++++++++++++++++++++++----------- 2 files changed, 78 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 3acf823..832ec04 100644 --- a/README.md +++ b/README.md @@ -2,45 +2,63 @@ SupMover - Shift timings and Screen Area of PGS/Sup subtitle # Usage -`SupMover ( ) [delay (ms)] [move ( )] [crop ( )] [resync (/ | multFactor)] [add_zero] [tonemap ] [cut_merge ]` +``` +Usage: SupMover [OPTIONS ...] + +OPTIONS: + --delay + --move + --crop + --resync (/ | ) + --add_zero + --tonemap + --cut_merge [CUT&MERGE OPTIONS ...] + +CUT&MERGE OPTIONS: + --list + --format (secut | (vapoursynth | vs) | (avisynth | avs) | remap) + --timemode (ms | frame | timestamp) + --fixmode (cut | (delete | del)) +``` -`SupMover ( )` old syntax, kept for backward compatibility +Old syntax, kept for backward compatibility: +`SupMover ` -# Modes -* delay +# Options +* `--delay` * Apply a milliseconds delay, positive or negative, to all the subpic of the subtitle, it can be fractional as the SUP speficication have a precision of 1/90ms -* resync +* `--resync` * Multiply all the timestamp by this factor, this can also be supplied as a fraction -* move +* `--move` * Shift the windows position of all subpic by the inputed parameters (the image data is left untouched). * Position is clamped to the screen edges so that windows are always fully contained within the screen area. -* crop +* `--crop` * Crop the windows area of all subpic by the inputed parameters. * This is done losslessly by only shifting the windows position (the image data is left untouched). * Crop functionality is not exstensivelly tested when multiple Composition Object or Windows are present or when the windows are is outside the new screen area, a warning is issued if that's the case and i strongly advise to check the resulting subtitle with a video player, also handling of the Object Cropped flag and windows area bigger than the new screen area is not implemented, a warning is issued if needed - * If both move and crop are selected, the crop is performed after the move. -* delay + resync - * If both modes are selected the delay will be adjusted if it comes before the resync parameter, for example if the program is launched with `delay 1000 resync 1.001` it will be internally adjusted to 1001ms, instead if it's launched with `resync 1.001 delay 1000` it will not -* add_zero + * If both `--move` and `--crop` are selected, the crop is performed after the move. +* `--delay` + `--resync` + * If both modes are selected the delay will be adjusted if it comes before the resync parameter, for example if the program is launched with `--delay 1000 --resync 1.001` it will be internally adjusted to 1001ms, instead if it's launched with `--resync 1.001 --delay 1000` it will not +* `--add_zero` * Some media players (especially Plex) don't correctly sync `*.sup` subtitles. They seem to ignore any delay before the first 'display set'. This option adds a dummy 'display set' at time 0 so subsequent timestamps are correctly interpreted. -* tonemap +* `--tonemap` * Change the brightness of the subtitle applying the specified percentage factor to all the palette's luminance value, similar to https://github.com/quietvoid/subtitle_tonemap , the percentage must be specified as a decimal value with 1 as 100%, it can be bigger than 1 to increase brightness -* cut_merge +* `--cut_merge` * allows to cut subtitle and optionally, if more sections are specified, to merge the cuts into a single subtitle file with the subsequent cuts shifted to have them begin at the end of the previous section. It is possible to personalize its functionality with some options - * list: specifies the space-separated list of sections, `format` and `timemode` can be used to further configure the parsing of this list. The list must be contained inside double quotes - * format: the format type of the list - * secut: uses the same format as SECut. eg `1000-2000;3000-4000` - * vapoursynt or vs: uses the same format as vapoursynth split sintax, additionally if `timemode` is set as `frame` the range will be treated inclusively at the start and exclusively at the end. eg `[1000:2001] [3000:4001]` - * avisynth or avs: uses the same format as avisynth trim sintax. Eg `(1000,2000) (3000,4000)` - * remap: uses the same format as [Vapoursynth-RemapFrames ReplaceFrameSimple](https://github.com/Irrational-Encoding-Wizardry/Vapoursynth-RemapFrames#replaceframessimple). Eg `[1000 2000] [3000 4000]` - * timemode: allow to specifies how to read the values of the sections - * ms - * frame: if selected a framerate MUST be specified, as a fraction like `24000/1001` or as a number like `23.976` - * timestamp: if selected the sections MUST be in the format hh:mm:ss.ms and can't be used with `format vapoursynth` - * fixmode: allow to specify how to treat subtitles which are not fully contained in a section - * del or delete: delete the subtitle if not fully contained inside a section - * cut: cut the subtitle so that it is fully contained in the section - * if no further option is specified it will works like secut so like the following command line `format secut timemode ms fixmode delete` + * `--list`: specifies the space-separated list of sections, `--format` and `--timemode` can be used to further configure the parsing of this list. The list must be contained inside double quotes + * `--format`: the format type of the list + * `secut`: uses the same format as SECut. eg `1000-2000;3000-4000` + * `vapoursynt` or `vs`: uses the same format as vapoursynth split sintax, additionally if `--timemode` is set as `frame` the range will be treated inclusively at the start and exclusively at the end. eg `[1000:2001] [3000:4001]` + * `avisynth` or `avs`: uses the same format as avisynth trim sintax. Eg `(1000,2000) (3000,4000)` + * `remap`: uses the same format as [Vapoursynth-RemapFrames ReplaceFrameSimple](https://github.com/Irrational-Encoding-Wizardry/Vapoursynth-RemapFrames#replaceframessimple). Eg `[1000 2000] [3000 4000]` + * `--timemode`: allow to specifies how to read the values of the sections + * `ms` + * `frame`: if selected a framerate MUST be specified, as a fraction like `24000/1001` or as a number like `23.976` + * `timestamp`: if selected the sections MUST be in the format hh:mm:ss.ms and can't be used with `--format vapoursynth` + * `--fixmode`: allow to specify how to treat subtitles which are not fully contained in a section + * `delete` or `del`: delete the subtitle if not fully contained inside a section + * `cut`: cut the subtitle so that it is fully contained in the section + * if no further option is specified it will works like secut so like the following command line `--format secut --timemode ms --fixmode delete` # Build instruction diff --git a/main.cpp b/main.cpp index 94f4b1e..617c211 100644 --- a/main.cpp +++ b/main.cpp @@ -532,7 +532,7 @@ bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) { std::string command = argv[i]; toLower(command); - if (command == "delay") { + if (command == "delay" || command == "--delay") { cmd.delay = (int32_t)round(atof(argv[i + 1]) * MS_TO_PTS_MULT); i += 2; @@ -546,19 +546,19 @@ bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) { */ } } - else if (command == "move") { + else if (command == "move" || command == "--move") { cmd.move.deltaX = atoi(argv[i + 1]); cmd.move.deltaY = atoi(argv[i + 2]); i += 3; } - else if (command == "crop") { + else if (command == "crop" || command == "--crop") { cmd.crop.left = atoi(argv[i + 1]); cmd.crop.top = atoi(argv[i + 2]); cmd.crop.right = atoi(argv[i + 3]); cmd.crop.bottom = atoi(argv[i + 4]); i += 5; } - else if (command == "resync") { + else if (command == "resync" || command == "--resync") { std::string strFactor = argv[i + 1]; size_t idx = strFactor.find("/"); if (idx != SIZE_MAX) { @@ -585,19 +585,19 @@ bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) { i += 2; } - else if (command == "add_zero") { + else if (command == "add_zero" || command == "--add_zero") { cmd.addZero = true; i += 1; } - else if (command == "tonemap") { + else if (command == "tonemap" || command == "--tonemap") { cmd.tonemap = std::atof(argv[i + 1]); i += 2; } - else if (command == "cut_merge") { + else if (command == "cut_merge" || command == "--cut_merge") { cmd.cutMerge.doCutMerge = true; i++; } - else if (command == "format") { + else if (command == "format" || command == "--format") { std::string formatMode = argv[i + 1]; toLower(formatMode); @@ -618,7 +618,7 @@ bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) { } i += 2; } - else if (command == "list") { + else if (command == "list" || command == "--list") { std::string list = argv[i + 1]; toLower(list); @@ -626,7 +626,7 @@ bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) { i += 2; } - else if (command == "timemode") { + else if (command == "timemode" || command == "--timemode") { std::string timemode = argv[i + 1]; toLower(timemode); @@ -658,7 +658,7 @@ bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) { i += 2; } - else if (command == "fixmode") { + else if (command == "fixmode" || command == "--fixmode") { std::string fixmode = argv[i + 1]; toLower(fixmode); @@ -692,6 +692,26 @@ bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) { return true; } +const char* usageHelp = R"(Usage: supmover [OPTIONS ...] + +OPTIONS: + --delay + --move + --crop + --resync (/ | ) + --add_zero + --tonemap + --cut_merge [CUT&MERGE OPTIONS ...] + +CUT&MERGE OPTIONS: + --list + --format (secut | (vapoursynth | vs) | (avisynth | avs) | remap) + --timemode (ms | frame | timestamp) + --fixmode (cut | (delete | del)) + +Delay and resync command are executed in the order supplied. +)"; + int main(int32_t argc, char** argv) { size_t size, newSize; @@ -699,9 +719,8 @@ int main(int32_t argc, char** argv) if (argc < 4) { - std::printf("Usage: SupMover ( ) [delay (ms)] [move ( )] [crop ( )] [resync (/ | multFactor)] [add_zero] [tonemap ]\r\n"); - std::printf("delay and resync command are executed in the order supplied\r\n"); - return 0; + std::printf("%s", usageHelp); + return -1; } t_cmd cmd = {}; From 617f4e56a7c29a92e2521598914b955ba1bb5ac6 Mon Sep 17 00:00:00 2001 From: YourMJK Date: Fri, 2 Feb 2024 01:31:16 +0100 Subject: [PATCH 2/2] Print errors and warnings to `stderr` --- main.cpp | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/main.cpp b/main.cpp index 617c211..ef2a460 100644 --- a/main.cpp +++ b/main.cpp @@ -469,11 +469,11 @@ bool parseCutMerge(t_cutMerge* cutMerge) { beg = timestampToMs(strBeg); end = timestampToMs(strEnd); if (beg == -1) { - std::printf("Timestamp %s is invalid\n", strBeg); + std::fprintf(stderr, "Timestamp %s is invalid\n", strBeg); return false; } if (beg == -1) { - std::printf("Timestamp %s is invalid\n", strEnd); + std::fprintf(stderr, "Timestamp %s is invalid\n", strEnd); return false; } @@ -523,7 +523,7 @@ bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) { //backward compatibility cmd.delay = (int32_t)std::round(atof(argv[3]) * MS_TO_PTS_MULT); if (cmd.delay != 0) { - std::printf("Running in backwards-compatibility mode\n"); + std::fprintf(stderr, "Running in backwards-compatibility mode\n"); return true; } } @@ -537,7 +537,7 @@ bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) { i += 2; if (cmd.cutMerge.doCutMerge) { - std::printf("Delay parameter will NOT be applied to Cut&Merge\n"); + std::fprintf(stderr, "Delay parameter will NOT be applied to Cut&Merge\n"); /* for (int i = 0; i < cmd.cutMerge.section.size(); i++) { cmd.cutMerge.section[i].begin += cmd.delay; @@ -574,7 +574,7 @@ bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) { cmd.delay = (int32_t)std::round(((double)cmd.delay * cmd.resync)); if (cmd.cutMerge.doCutMerge) { - std::printf("Resync parameter will NOT be applied to Cut&Merge\n"); + std::fprintf(stderr, "Resync parameter will NOT be applied to Cut&Merge\n"); /* for (int i = 0; i < cmd.cutMerge.section.size(); i++) { cmd.cutMerge.section[i].begin *= cmd.resync; @@ -679,7 +679,7 @@ bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) { if (cmd.cutMerge.doCutMerge) { if ( cmd.cutMerge.format == e_cutMergeFormat::vapoursynth && cmd.cutMerge.timeMode == e_cutMergeTimeMode::timestamp) { - std::printf("Compat mode VapourSynth cannot be used alongside timestamp time mode\n"); + std::fprintf(stderr, "Compat mode VapourSynth cannot be used alongside timestamp time mode\n"); return false; } @@ -719,13 +719,13 @@ int main(int32_t argc, char** argv) if (argc < 4) { - std::printf("%s", usageHelp); + std::fprintf(stderr, "%s", usageHelp); return -1; } t_cmd cmd = {}; if (!ParseCMD(argc, argv, cmd)) { - std::printf("Error parsing input\r\n"); + std::fprintf(stderr, "Error parsing input\n"); return -1; } @@ -740,12 +740,12 @@ int main(int32_t argc, char** argv) FILE* input = std::fopen(argv[1], "rb"); if (input == nullptr) { - std::printf("Unable to open input file!"); + std::fprintf(stderr, "Unable to open input file!\n"); return -1; } FILE* output = std::fopen(argv[2], "wb"); if (output == nullptr) { - std::printf("Unable to open output file!"); + std::fprintf(stderr, "Unable to open output file!\n"); std::fclose(input); return -1; } @@ -792,7 +792,7 @@ int main(int32_t argc, char** argv) for (start = 0; start < size; start = start + HEADER_SIZE + header.dataLength) { header = ReadHeader(&buffer[start]); if (header.header != 0x5047) { - std::printf("Correct header not found at position %zd, abort!", start); + std::fprintf(stderr, "Correct header not found at position %zd, abort!\n", start); std::fclose(input); std::fclose(output); return -1; @@ -848,13 +848,13 @@ int main(int32_t argc, char** argv) if (pcs.numCompositionObject > 1) { t_timestamp timestamp = PTStoTimestamp(header.pts1); - std::printf("Multiple composition object at timestamp %lu:%02lu:%02lu.%03lu! Please Check!\r\n", timestamp.hh, timestamp.mm, timestamp.ss, timestamp.ms); + std::fprintf(stderr, "Multiple composition object at timestamp %lu:%02lu:%02lu.%03lu! Please Check!\n", timestamp.hh, timestamp.mm, timestamp.ss, timestamp.ms); } for (int i = 0; i < pcs.numCompositionObject; i++) { if (pcs.compositionObject[i].objectCroppedFlag == 0x40) { t_timestamp timestamp = PTStoTimestamp(header.pts1); - std::printf("Object Cropped Flag set at timestamp %lu:%02lu:%02lu.%03lu! Implement it!\r\n", timestamp.hh, timestamp.mm, timestamp.ss, timestamp.ms); + std::fprintf(stderr, "Object Cropped Flag set at timestamp %lu:%02lu:%02lu.%03lu! Implement it!\n", timestamp.hh, timestamp.mm, timestamp.ss, timestamp.ms); } if (cmd.crop.left > pcs.compositionObject[i].objectHorPos) { @@ -909,7 +909,7 @@ int main(int32_t argc, char** argv) WriteHeader(zeroHeader, &zeroBuffer[pos]); pos += 13; - std::printf("Writing %d bytes as first display set\n", pos); + std::fprintf(stderr, "Writing %d bytes as first display set\n", pos); std::fwrite(zeroBuffer, pos, 1, output); //For Cut&Merge functionality we don't need to save the added segment as it @@ -934,14 +934,14 @@ int main(int32_t argc, char** argv) } break; case 0x17: - //std::printf("WDS\r\n"); + //std::fprintf(stderr, "WDS\r\n"); fixPCS = false; if (doMove || doCrop) { wds = ReadWDS(&buffer[start + HEADER_SIZE]); if (wds.numberOfWindows > 1) { t_timestamp timestamp = PTStoTimestamp(header.pts1); - std::printf("Multiple windows at timestamp %lu:%02lu:%02lu.%03lu! Please Check!\r\n", timestamp.hh, timestamp.mm, timestamp.ss, timestamp.ms); + std::fprintf(stderr, "Multiple windows at timestamp %lu:%02lu:%02lu.%03lu! Please Check!\n", timestamp.hh, timestamp.mm, timestamp.ss, timestamp.ms); } if (doMove) { @@ -963,7 +963,7 @@ int main(int32_t argc, char** argv) if (object->objectCroppedFlag == 0x40) { t_timestamp timestamp = PTStoTimestamp(header.pts1); - std::printf("Object Cropped Flag set at timestamp %lu:%02lu:%02lu.%03lu! Crop fields are not supported yet.\r\n", timestamp.hh, timestamp.mm, timestamp.ss, timestamp.ms); + std::fprintf(stderr, "Object Cropped Flag set at timestamp %lu:%02lu:%02lu.%03lu! Crop fields are not supported yet.\n", timestamp.hh, timestamp.mm, timestamp.ss, timestamp.ms); /* object->objCropHorPos += clampedDeltaX; object->objCropVerPos += clampedDeltaY; @@ -990,8 +990,8 @@ int main(int32_t argc, char** argv) if (wndRect.width > screenRect.width || wndRect.height > screenRect.height) { t_timestamp timestamp = PTStoTimestamp(header.pts1); - std::printf("Window is bigger then new screen area at timestamp %lu:%02lu:%02lu.%03lu\r\n", timestamp.hh, timestamp.mm, timestamp.ss, timestamp.ms); - std::printf("Implement it!\r\n"); + std::fprintf(stderr, "Window is bigger then new screen area at timestamp %lu:%02lu:%02lu.%03lu\n", timestamp.hh, timestamp.mm, timestamp.ss, timestamp.ms); + std::fprintf(stderr, "Implement it!\n"); /* pcs.width = wndRect.width; pcs.height = wndRect.height; @@ -1001,7 +1001,7 @@ int main(int32_t argc, char** argv) else { if (!rectIsContained(screenRect, wndRect)) { t_timestamp timestamp = PTStoTimestamp(header.pts1); - std::printf("Window is outside new screen area at timestamp %lu:%02lu:%02lu.%03lu\r\n", timestamp.hh, timestamp.mm, timestamp.ss, timestamp.ms); + std::fprintf(stderr, "Window is outside new screen area at timestamp %lu:%02lu:%02lu.%03lu\n", timestamp.hh, timestamp.mm, timestamp.ss, timestamp.ms); uint16_t wndRightPoint = wndRect.x + wndRect.width; uint16_t screenRightPoint = screenRect.x + screenRect.width; @@ -1016,7 +1016,7 @@ int main(int32_t argc, char** argv) } if (corrHor + corrVer != 0) { - std::printf("Please check\r\n"); + std::fprintf(stderr, "Please check\n"); } } }