Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add move functionality #12

Merged
merged 3 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
SupMover - Shift timings and Screen Area of PGS/Sup subtitle

# Usage
`SupMover (<input.sup> <output.sup>) [delay (ms)] [crop (<left> <top> <right> <bottom>)] [resync (<num>/<den> | multFactor)] [add_zero] [tonemap <perc>] [cut_merge <Cut&Merge option>]`
`SupMover (<input.sup> <output.sup>) [delay (ms)] [move (<delta x> <delta y>)] [crop (<left> <top> <right> <bottom>)] [resync (<num>/<den> | multFactor)] [add_zero] [tonemap <perc>] [cut_merge <Cut&Merge option>]`

`SupMover (<input.sup> <output.sup> <ms>)` old syntax, kept for backward compatibility

Expand All @@ -11,10 +11,14 @@ SupMover - Shift timings and Screen Area of PGS/Sup subtitle
* 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
* Multiply all the timestamp by this factor, this can also be supplied as a fraction
* 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 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
Expand Down
165 changes: 106 additions & 59 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ struct t_timestamp {
unsigned long ms;
};

struct t_move {
int16_t deltaX;
int16_t deltaY;
};

struct t_crop {
uint16_t left;
uint16_t top;
Expand Down Expand Up @@ -138,6 +143,7 @@ struct t_PDS {

struct t_cmd {
int32_t delay = 0;
t_move move = {};
t_crop crop = {};
double resync = 1;
bool addZero = false;
Expand Down Expand Up @@ -504,6 +510,7 @@ bool parseCutMerge(t_cutMerge* cutMerge) {

bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) {
cmd.delay = 0;
cmd.move = {};
cmd.crop = {};
cmd.resync = 1;
cmd.addZero = false;
Expand Down Expand Up @@ -539,6 +546,11 @@ bool ParseCMD(int32_t argc, char** argv, t_cmd& cmd) {
*/
}
}
else if (command == "move") {
cmd.move.deltaX = atoi(argv[i + 1]);
cmd.move.deltaY = atoi(argv[i + 2]);
i += 3;
}
else if (command == "crop") {
cmd.crop.left = atoi(argv[i + 1]);
cmd.crop.top = atoi(argv[i + 2]);
Expand Down Expand Up @@ -687,7 +699,7 @@ int main(int32_t argc, char** argv)


if (argc < 4) {
std::printf("Usage: SupMover (<input.sup> <output.sup>) [delay (ms)] [crop (<left> <top> <right> <bottom>)] [resync (<num>/<den> | multFactor)] [add_zero] [tonemap <perc>]\r\n");
std::printf("Usage: SupMover (<input.sup> <output.sup>) [delay (ms)] [move (<delta x> <delta y>)] [crop (<left> <top> <right> <bottom>)] [resync (<num>/<den> | multFactor)] [add_zero] [tonemap <perc>]\r\n");
std::printf("delay and resync command are executed in the order supplied\r\n");
return 0;
}
Expand All @@ -700,11 +712,12 @@ int main(int32_t argc, char** argv)


bool doDelay = cmd.delay != 0;
bool doMove = cmd.move.deltaX != 0 || cmd.move.deltaY != 0;
bool doCrop = (cmd.crop.left + cmd.crop.top + cmd.crop.right + cmd.crop.bottom) > 0;
bool doResync = cmd.resync != 1;
bool doTonemap = cmd.tonemap != 1;

bool doSomething = doDelay || doCrop || doResync || cmd.addZero || doTonemap || cmd.cutMerge.doCutMerge;
bool doSomething = doDelay || doMove || doCrop || doResync || cmd.addZero || doTonemap || cmd.cutMerge.doCutMerge;

FILE* input = std::fopen(argv[1], "rb");
if (input == nullptr) {
Expand Down Expand Up @@ -801,7 +814,7 @@ int main(int32_t argc, char** argv)
break;
case 0x16:
//std::printf("PCS\r\n");
if (doCrop || cmd.addZero || cmd.cutMerge.doCutMerge) {
if (doMove | doCrop || cmd.addZero || cmd.cutMerge.doCutMerge) {
pcs = ReadPCS(&buffer[start + HEADER_SIZE]);
offsetCurrPCS = start;

Expand Down Expand Up @@ -904,79 +917,113 @@ int main(int32_t argc, char** argv)
case 0x17:
//std::printf("WDS\r\n");
fixPCS = false;
if (doCrop) {
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);
}

for (int i = 0; i < wds.numberOfWindows; i++) {
t_rect wndRect;
uint16_t corrHor = 0;
uint16_t corrVer = 0;

wndRect.x = wds.windows[i].WindowsHorPos;
wndRect.y = wds.windows[i].WindowsVerPos;
wndRect.width = wds.windows[i].WindowsWidth;
wndRect.height = wds.windows[i].WindowsHeight;
if (doMove) {
for (int i = 0; i < wds.numberOfWindows; i++) {
t_window *window = &wds.windows[i];
int16_t minDeltaX = -(int16_t)window->WindowsHorPos;
int16_t minDeltaY = -(int16_t)window->WindowsVerPos;
int16_t maxDeltaX = pcs.width - (window->WindowsHorPos + window->WindowsWidth);
int16_t maxDeltaY = pcs.height - (window->WindowsVerPos + window->WindowsHeight);
int16_t clampedDeltaX = std::min(std::max(cmd.move.deltaX, minDeltaX), maxDeltaX);
int16_t clampedDeltaY = std::min(std::max(cmd.move.deltaY, minDeltaY), maxDeltaY);

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");
/*
pcs.width = wndRect.width;
pcs.height = wndRect.height;
fixPCS = true;
*/
}
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);
window->WindowsHorPos += clampedDeltaX;
window->WindowsVerPos += clampedDeltaY;

uint16_t wndRightPoint = wndRect.x + wndRect.width;
uint16_t screenRightPoint = screenRect.x + screenRect.width;
if (wndRightPoint > screenRightPoint) {
corrHor = wndRightPoint - screenRightPoint;
}

uint16_t wndBottomPoint = wndRect.y + wndRect.height;
uint16_t screenBottomPoint = screenRect.y + screenRect.height;
if (wndBottomPoint > screenBottomPoint) {
corrVer = wndBottomPoint - screenBottomPoint;
for (int j = 0; j < pcs.numCompositionObject; j++) {
t_compositionObject *object = &pcs.compositionObject[j];
if (object->windowID != window->windowID) continue;

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);
/*
object->objCropHorPos += clampedDeltaX;
object->objCropVerPos += clampedDeltaY;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never seen objCropHorPos and objCropVerPos used in the wild, always being filled with a fixed value, I think you should check if those field are at a default and only change them otherwise, personally I'd also output a warning when changing those

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that the objectCroppedFlag being set to enabled (0x40) indicated that these aren't set to default values (0?) and should be respected.
In that case, they are some absolute coordinates which should be translated like the subpic itself to keep their relative positions and thus leave the crop unchanged.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my experience that flag is set when the subpic is forced and if you check in ReadPCS and WritePCS the handling of those fields is commented out, i can share you this file which was sent to me to debug this flag behavior, if I remember correctly I never found those field populated, but I may be wrong
testcropped.zip

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed that these fields aren't even read/written currently. Then it's better to leave this to a different PR, perhaps.
I will print a warning instead.

*/
}
object->objectHorPos += clampedDeltaX;
object->objectVerPos += clampedDeltaY;
fixPCS = true;
}
}
}

if (corrHor + corrVer != 0) {
std::printf("Please check\r\n");
if (doCrop) {
for (int i = 0; i < wds.numberOfWindows; i++) {
t_rect wndRect;
uint16_t corrHor = 0;
uint16_t corrVer = 0;

wndRect.x = wds.windows[i].WindowsHorPos;
wndRect.y = wds.windows[i].WindowsVerPos;
wndRect.width = wds.windows[i].WindowsWidth;
wndRect.height = wds.windows[i].WindowsHeight;

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");
/*
pcs.width = wndRect.width;
pcs.height = wndRect.height;
fixPCS = true;
*/
}
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);

uint16_t wndRightPoint = wndRect.x + wndRect.width;
uint16_t screenRightPoint = screenRect.x + screenRect.width;
if (wndRightPoint > screenRightPoint) {
corrHor = wndRightPoint - screenRightPoint;
}

uint16_t wndBottomPoint = wndRect.y + wndRect.height;
uint16_t screenBottomPoint = screenRect.y + screenRect.height;
if (wndBottomPoint > screenBottomPoint) {
corrVer = wndBottomPoint - screenBottomPoint;
}

if (corrHor + corrVer != 0) {
std::printf("Please check\r\n");
}
}
}
}

if (cmd.crop.left > wds.windows[i].WindowsHorPos) {
wds.windows[i].WindowsHorPos = 0;
}
else {
wds.windows[i].WindowsHorPos -= (cmd.crop.left + corrHor);
}
if (cmd.crop.left > wds.windows[i].WindowsHorPos) {
wds.windows[i].WindowsHorPos = 0;
}
else {
wds.windows[i].WindowsHorPos -= (cmd.crop.left + corrHor);
}

if (cmd.crop.top > wds.windows[i].WindowsVerPos) {
wds.windows[i].WindowsVerPos = 0;
}
else {
wds.windows[i].WindowsVerPos -= (cmd.crop.top + corrVer);
}
if (cmd.crop.top > wds.windows[i].WindowsVerPos) {
wds.windows[i].WindowsVerPos = 0;
}
else {
wds.windows[i].WindowsVerPos -= (cmd.crop.top + corrVer);
}

if (corrVer != 0 || corrHor != 0) {
for (int j = 0; j < pcs.numCompositionObject; j++) {
if (pcs.compositionObject[j].windowID != wds.windows[i].windowID) continue;
pcs.compositionObject[j].objectVerPos -= corrVer;
pcs.compositionObject[j].objectHorPos -= corrHor;
if (corrVer != 0 || corrHor != 0) {
for (int j = 0; j < pcs.numCompositionObject; j++) {
if (pcs.compositionObject[j].windowID != wds.windows[i].windowID) continue;
pcs.compositionObject[j].objectVerPos -= corrVer;
pcs.compositionObject[j].objectHorPos -= corrHor;
}
fixPCS = true;
}
fixPCS = true;
}
}

Expand Down
Loading