Added: #2556 CLI subsprite animation export (-sublength <length> option for export)

Added: #2556 Option to select arbitrary frames of sprites export via `-select`.
  Check `--help -select` for details.
This commit is contained in:
Jindra Petřík
2025-11-09 12:20:25 +01:00
parent f04fcb151e
commit 840ae0376e
16 changed files with 669 additions and 26 deletions
@@ -517,6 +517,7 @@ public class CommandLineArgumentParser {
List<String> selectionClasses = null;
String nextParam = null;
String nextParamOriginal = null;
int subLength = 1;
OUTER:
while (true) {
nextParamOriginal = args.pop();
@@ -534,14 +535,17 @@ public class CommandLineArgumentParser {
cliMode = true;
break;
case "-selectid":
selectionIds = parseSelect(args);
selectionIds = parseSelect(args, false, "selectid");
break;
case "-select":
selection = parseSelect(args);
selection = parseSelect(args, true, "select");
break;
case "-selectclass":
selectionClasses = parseSelectClass(args);
break;
case "-sublength":
subLength = parseSubLength(args);
break;
case "-exportembed":
exportEmbed = true;
break;
@@ -677,7 +681,7 @@ public class CommandLineArgumentParser {
} else if (command.equals("proxy")) {
parseProxy(args);
} else if (command.equals("export")) {
parseExport(selectionClasses, selection, selectionIds, args, handler, traceLevel, format, zoom, charset, exportEmbed, resampleWav, transparentBackground, urlResolver);
parseExport(selectionClasses, selection, selectionIds, args, handler, traceLevel, format, zoom, charset, exportEmbed, resampleWav, transparentBackground, urlResolver, subLength);
System.exit(0);
} else if (command.equals("compress")) {
parseCompress(args);
@@ -1584,16 +1588,29 @@ public class CommandLineArgumentParser {
private static class Range {
public Integer prefix;
public Integer min;
public Integer max;
public Range(Integer min, Integer max) {
public Range(Integer prefix, Integer min, Integer max) {
this.prefix = prefix;
this.min = min;
this.max = max;
}
public boolean contains(int index) {
return contains(0, index);
}
public boolean contains(int prefix, int index) {
if (this.prefix == null && prefix == 0) {
return false;
}
if (this.prefix != null && this.prefix != prefix) {
return false;
}
int minimum = min == null ? Integer.MIN_VALUE : min;
int maximum = max == null ? Integer.MAX_VALUE : max;
@@ -1607,7 +1624,7 @@ public class CommandLineArgumentParser {
public Selection() {
this.ranges = new ArrayList<>();
this.ranges.add(new Range(null, null));
this.ranges.add(new Range(0, null, null));
}
public Selection(List<Range> ranges) {
@@ -1615,36 +1632,76 @@ public class CommandLineArgumentParser {
}
public boolean contains(int index) {
return contains(0, index);
}
public boolean contains(int prefix, int index) {
boolean prefixFound = false;
for (Range r : ranges) {
if (r.contains(index)) {
if (r.prefix == prefix || (prefix != 0 && r.prefix == null)) {
prefixFound = true;
}
if (r.contains(prefix, index)) {
return true;
}
}
if (!prefixFound) {
return true;
}
return false;
}
}
private static Selection parseSelect(Stack<String> args) {
private static Selection parseSelect(Stack<String> args, boolean allowPrefix, String command) {
List<Range> ret = new ArrayList<>();
if (args.isEmpty()) {
System.err.println("range parameter expected");
badArguments("select");
badArguments(command);
}
String range = args.pop();
String[] ranges;
if (range.contains(",")) {
ranges = range.split(",");
ranges = range.split(",", -1);
} else {
ranges = new String[]{range};
}
Integer prefix = 0;
for (String r : ranges) {
Integer min = null;
Integer max = null;
if (r.contains("-")) {
String[] ps = r.split("\\-");
if (r.contains(":")) {
if (!allowPrefix) {
System.err.println("invalid range");
badArguments(command);
}
String[] ps = r.split(":", -1);
if (ps.length != 2) {
System.err.println("invalid range");
badArguments("select");
badArguments(command);
}
if ("all".equals(ps[0])) {
prefix = null;
} else {
try {
prefix = Integer.parseInt(ps[0]);
} catch (NumberFormatException nfe) {
System.err.println("invalid range");
badArguments(command);
}
if (prefix < 0) {
System.err.println("invalid range");
badArguments(command);
}
}
r = ps[1];
}
if (r.contains("-")) {
String[] ps = r.split("\\-", -1);
if (ps.length != 2) {
System.err.println("invalid range");
badArguments(command);
}
try {
if (!"".equals(ps[0])) {
@@ -1655,7 +1712,7 @@ public class CommandLineArgumentParser {
}
} catch (NumberFormatException nfe) {
System.err.println("invalid range");
badArguments("select");
badArguments(command);
}
} else {
try {
@@ -1663,13 +1720,33 @@ public class CommandLineArgumentParser {
max = min;
} catch (NumberFormatException nfe) {
System.err.println("invalid range");
badArguments("select");
badArguments(command);
}
}
ret.add(new Range(min, max));
ret.add(new Range(prefix, min, max));
}
return new Selection(ret);
}
private static int parseSubLength(Stack<String> args) {
if (args.isEmpty()) {
System.err.println("sub length parameter expected");
badArguments("sublength");
}
try {
int subLen = Integer.parseInt(args.pop());
if (subLen < 1) {
System.err.println("Minimum for sub length is 1");
badArguments("sublength");
subLen = 1;
}
return subLen;
} catch (NumberFormatException nre) {
System.err.println("Invalid sub length");
badArguments("sublength");
}
return 1;
}
private static double parseZoom(Stack<String> args) {
if (args.isEmpty()) {
@@ -2033,7 +2110,21 @@ public class CommandLineArgumentParser {
}
private static void parseExport(List<String> selectionClasses, Selection selection, Selection selectionIds, Stack<String> args, AbortRetryIgnoreHandler handler, Level traceLevel, Map<String, String> formats, double zoom, String charset, boolean exportEmbed, boolean resampleWav, boolean transparentBackground, UrlResolver urlResolver) {
private static void parseExport(
List<String> selectionClasses,
Selection selection,
Selection selectionIds,
Stack<String> args,
AbortRetryIgnoreHandler handler,
Level traceLevel,
Map<String, String> formats,
double zoom,
String charset,
boolean exportEmbed,
boolean resampleWav,
boolean transparentBackground,
UrlResolver urlResolver,
int subFrameLength) {
if (args.size() < 3) {
badArguments("export");
}
@@ -2258,20 +2349,27 @@ public class CommandLineArgumentParser {
System.out.println("Exporting frames...");
List<Integer> frames = new ArrayList<>();
for (int i = 0; i < swf.frameCount; i++) {
if (selection.contains(i + 1)) {
if (selection.contains(0, i + 1)) {
frames.add(i);
}
}
FrameExportSettings fes = new FrameExportSettings(enumFromStr(formats.get("frame"), FrameExportMode.class), zoom, transparentBackground);
frameExporter.exportFrames(handler, outDir + (multipleExportTypes ? File.separator + FrameExportSettings.EXPORT_FOLDER_NAME : ""), swf, 0, frames, 1, fes, evl);
frameExporter.exportFrames(handler, outDir + (multipleExportTypes ? File.separator + FrameExportSettings.EXPORT_FOLDER_NAME : ""), swf, 0, frames, subFrameLength, fes, evl);
}
if (exportAll || exportFormats.contains("sprite")) {
System.out.println("Exporting sprite...");
SpriteExportSettings ses = new SpriteExportSettings(enumFromStr(formats.get("sprite"), SpriteExportMode.class), zoom);
for (Tag t : extags) {
if (t instanceof DefineSpriteTag) {
frameExporter.exportSpriteFrames(handler, outDir + (multipleExportTypes ? File.separator + SpriteExportSettings.EXPORT_FOLDER_NAME : ""), swf, ((DefineSpriteTag) t).getCharacterId(), null, 1, ses, evl);
if (t instanceof DefineSpriteTag) {
List<Integer> frames = new ArrayList<>();
int spriteId = ((DefineSpriteTag) t).getCharacterId();
for (int i = 0; i < ((DefineSpriteTag) t).getFrameCount(); i++) {
if (selection.contains(spriteId, i + 1)) {
frames.add(i);
}
}
frameExporter.exportSpriteFrames(handler, outDir + (multipleExportTypes ? File.separator + SpriteExportSettings.EXPORT_FOLDER_NAME : ""), swf, spriteId, frames, subFrameLength, ses, evl);
}
}
}
@@ -378,15 +378,30 @@ Pre-options:
-select <ranges>
Applies to: -export
Select frames/pages for export.
Comma separated ranges in format <min>-<max> (<max> is optional) or <num>.
If it has prefix "<characterId>:" then all following ranges apply
to the character with specific id.
Use "all" as characterId to apply for every character.
Use "0" (default) as characterId to apply for main timeline.
Example <ranges> formats:
1-5
2,3
2-5,7,9-
DO NOT PUT space between comma (,) and next ramge.
1-5 ... exports frames 1,2,3,4,5
2,3 ... exports frames 2 and 3
2-5,7,9- ... exports frames 2,3,4,5,7,9 and all after
57:5-10,12,62:5 ... exports frames 5,6,7,8,9,10,12
of character 57 and frame 5 of character 62
all:12-29 ... exports frames 12 to 29 of all characters
12:3,4,0:1-12 ... exports frames 3 and 4 of character 12
and first 12 frames of main timeline
DO NOT PUT space between comma (,) and next range.
-selectid <ranges>
-selectid <charRanges>
Applies to: -export
Select characters for export by character id.
<ranges> format is same as in -select
Comma separated ranges in format <min>-<max> (<max> is optional) or <characterId>.
Example <charRanges> formats:
1-5 ... exports characters 1,2,3,4,5
2,3 ... exports characters 2 and 3
2-5,7,9- ... exports characters 2,3,4,5,7,9 and all ids after
-selectclass <classnames>
Applies to: -export
@@ -398,6 +413,11 @@ Pre-options:
subpackages, class net.company.MyClass)
DO NOT PUT space between comma (,) and next class.
-sublength <length>
Applies to: -export
Export subsprite animation of length <length>.
<length> is measured in number of subframes. Must be >= 1.
-exportembed
Applies to: -export
Allow exporting embedded assets via [Embed tag].