Added: Importing sound stream block ranges

Added: Commandline replacing sound stream block ranges
This commit is contained in:
Jindra Petřík
2025-06-06 20:07:42 +02:00
parent e65ac557fb
commit 549c6dd9a3
6 changed files with 85 additions and 21 deletions

View File

@@ -28,6 +28,8 @@ All notable changes to this project will be documented in this file.
- [#2463] Export subsprites animation context menu on frames
- Open in the Flash Player context menu on graphic/sound tags and frames
- [#2451] Replacing sound stream block ranges
- Importing sound stream block ranges
- Commandline replacing sound stream block ranges
### Changed
- AS1/2 - Single DoAction tag inside frame is now displayed directly as frame node

View File

@@ -370,9 +370,9 @@ public class SoundImporter {
|| newSoundType != streamHead.getSoundType()
|| newSoundRate != streamHead.getSoundRate()) {
throw new SoundParametersMismatchException(
streamHead.getSoundType(),
streamHead.getSoundSize(),
streamHead.getSoundRate(),
streamHead.getSoundType(),
streamHead.getSoundSize(),
streamHead.getSoundRate(),
streamHead.getSoundFormatId(),
newSoundType,
newSoundSize,
@@ -405,8 +405,8 @@ public class SoundImporter {
break;
}
}
}
} else {
}
} else {
for (SoundStreamFrameRange range : ranges) {
if (range.startFrame == startFrame) {
existingBlocks.addAll(range.blocks);
@@ -414,7 +414,7 @@ public class SoundImporter {
}
}
}
for (SoundStreamBlockTag block : existingBlocks) {
timelined.removeTag(block);
}
@@ -552,15 +552,16 @@ public class SoundImporter {
* @param soundTag Sound tag
* @param is Input stream
* @param newSoundFormat New sound format
* @param startFrame Starting frame. null = autodetect, replace all
* @return True if sound was imported successfully
* @throws SoundImportException On sound import error
*/
public boolean importSound(SoundTag soundTag, InputStream is, int newSoundFormat) throws SoundImportException {
public boolean importSound(SoundTag soundTag, InputStream is, int newSoundFormat, Integer startFrame) throws SoundImportException {
if (soundTag instanceof DefineSoundTag) {
return importDefineSound((DefineSoundTag) soundTag, is, newSoundFormat);
}
if (soundTag instanceof SoundStreamHeadTypeTag) {
return importSoundStream((SoundStreamHeadTypeTag) soundTag, is, newSoundFormat);
return importSoundStreamAtFrame((SoundStreamHeadTypeTag) soundTag, is, newSoundFormat, startFrame);
}
if (soundTag instanceof SoundStreamFrameRange) {
return importSoundStreamAtFrame(((SoundStreamFrameRange) soundTag).getHead(), is, newSoundFormat, ((SoundStreamFrameRange) soundTag).startFrame);
@@ -595,16 +596,21 @@ public class SoundImporter {
});
List<SoundTag> soundTags = new ArrayList<>();
List<List<SoundStreamFrameRange>> ranges = new ArrayList<>();
for (int characterId : characters.keySet()) {
CharacterTag tag = characters.get(characterId);
if (tag instanceof DefineSoundTag) {
soundTags.add((DefineSoundTag) tag);
ranges.add(new ArrayList<>());
}
if (tag instanceof DefineSpriteTag) {
DefineSpriteTag sprite = (DefineSpriteTag) tag;
for (Tag subTag : sprite.getTags()) {
if (subTag instanceof SoundStreamHeadTypeTag) {
soundTags.add((SoundStreamHeadTypeTag) subTag);
ranges.add(sprite.getTimeline().getSoundStreamBlocks((SoundStreamHeadTypeTag) subTag));
break;
}
}
}
@@ -612,10 +618,14 @@ public class SoundImporter {
for (Tag tag : swf.getTags()) {
if (tag instanceof SoundStreamHeadTypeTag) {
soundTags.add((SoundStreamHeadTypeTag) tag);
ranges.add(swf.getTimeline().getSoundStreamBlocks((SoundStreamHeadTypeTag) tag));
break;
}
}
for (SoundTag tag : soundTags) {
int pos = -1;
loopChars: for (SoundTag tag : soundTags) {
pos++;
int characterId = tag.getCharacterId();
List<File> existingFilesForSoundTag = new ArrayList<>();
@@ -656,8 +666,38 @@ public class SoundImporter {
continue;
}
if (!ranges.get(pos).isEmpty()) {
for (SoundStreamFrameRange r : ranges.get(pos)) {
for (File sourceFile : existingFilesForSoundTag) {
if (sourceFile.getName().startsWith("" + characterId + "_" + (r.startFrame + 1) + "-")) {
try {
if (printOut) {
System.out.println("Importing character " + characterId + ", start frame " + r.startFrame + " from file " + sourceFile.getName());
}
int soundFormat = SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN;
if (sourceFile.getAbsolutePath().toLowerCase(Locale.ENGLISH).endsWith(".mp3")) {
soundFormat = SoundFormat.FORMAT_MP3;
}
try (FileInputStream fis = new FileInputStream(sourceFile)) {
importSound(tag, fis, soundFormat, r.startFrame);
soundCount++;
}
} catch (IOException | SoundImportException ex) {
Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Cannot import sound " + characterId + " from file " + sourceFile.getName(), ex);
}
if (CancellableWorker.isInterrupted()) {
break loopChars;
}
break;
}
}
}
continue;
}
if (existingFilesForSoundTag.size() > 1) {
Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Multiple matching files for sound tag {0} exists, {1} selected", new Object[]{characterId, existingFilesForSoundTag.get(0).getName()});
Logger.getLogger(SoundImporter.class.getName()).log(Level.WARNING, "Multiple matching files for sound tag {0} exists, {1} selected", new Object[]{characterId, existingFilesForSoundTag.get(0).getName()});
}
File sourceFile = existingFilesForSoundTag.get(0);
@@ -670,7 +710,7 @@ public class SoundImporter {
soundFormat = SoundFormat.FORMAT_MP3;
}
try (FileInputStream fis = new FileInputStream(sourceFile)) {
importSound(tag, fis, soundFormat);
importSound(tag, fis, soundFormat, null);
soundCount++;
}
} catch (IOException | SoundImportException ex) {

View File

@@ -80,7 +80,9 @@ public class SoundStreamFrameRange implements TreeItem, SoundTag {
@Override
public boolean importSupported() {
return true;
return head.getSoundFormatId() == SoundFormat.FORMAT_MP3
|| head.getSoundFormatId() == SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN
|| head.getSoundFormatId() == SoundFormat.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN;
}
@Override
@@ -132,7 +134,6 @@ public class SoundStreamFrameRange implements TreeItem, SoundTag {
return "SoundStreamBlocks";
}
@Override
public SoundFormat getSoundFormat() {
return head.getSoundFormat();
@@ -177,7 +178,6 @@ public class SoundStreamFrameRange implements TreeItem, SoundTag {
return head;
}
@Override
public String toString() {
return "SoundStreamBlocks (frame " + (startFrame + 1) + " - " + (endFrame + 1) + ")";

View File

@@ -3001,7 +3001,7 @@ public class CommandLineArgumentParser {
try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) {
SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset);
while (true) {
String objectToReplace = args.pop();
String objectToReplace = args.pop();
if (objectToReplace.matches("\\d+")) {
// replace character tag
@@ -3104,10 +3104,29 @@ public class CommandLineArgumentParser {
}).importText(textTag, new String(data, Utf8Helper.charset));
} else if (characterTag instanceof SoundTag) {
SoundTag st = (SoundTag) characterTag;
Integer startFrame = null;
if (!args.isEmpty()) {
if (args.peek().toLowerCase(Locale.ENGLISH).equals("-startframe")) {
args.pop();
if (args.isEmpty()) {
System.err.println("Frame number must be specified");
badArguments("replace");
}
try {
startFrame = Integer.parseInt(args.pop());
} catch (NumberFormatException nfe) {
System.err.println("Frame number should be integer");
badArguments("replace");
}
}
}
boolean ok = false;
SoundImporter soundImporter = new SoundImporter();
try {
ok = soundImporter.importSound(st, new ByteArrayInputStream(data), soundFormat);
ok = soundImporter.importSound(st, new ByteArrayInputStream(data), soundFormat, startFrame);
} catch (SoundParametersMismatchException spm) {
System.err.println("Import FAILED. " + spm.getMessage());
System.exit(3);
} catch (UnsupportedSamplingRateException usre) {
List<String> supportedRatesStr = new ArrayList<>();
for (int i : usre.getSupportedRates()) {

View File

@@ -98,18 +98,21 @@ alias /?
Convert FlashPaper SWF file <infile> to PDF <outfile>.
Use -zoom parameter to specify image quality.
-replace <infile> <outfile> (<characterId1>|<scriptName1>) <importDataFile1> \
[nofill] ([<format1>][<methodBodyIndex1>]) \
[(<characterId2>|<scriptName2>) <importDataFile2> \
[nofill] ([<format2>][<methodBodyIndex2>])]...
-replace <infile> <outfile> \
(<characterId1>|<scriptName1>) <importDataFile1> \
[nofill] [-startFrame <startFrame>] ([<format1>][<methodBodyIndex1>]) \
[(<characterId2>|<scriptName2>) <importDataFile2> \
[nofill] [-startFrame <startFrame>] ([<format2>][<methodBodyIndex2>])]...
Replace data.
Replaces the data of the specified BinaryData, Image, Shape, Text,
Sound tag or Script.
nofill parameter can be specified only for shape replace.
<startFrame> parameter sets sound range start frame (for sound streams)
<format> parameter can be specified for Image and Shape tags.
valid formats: lossless, lossless2, jpeg2, jpeg3, jpeg4.
<methodBodyIndexN> parameter should be specified if and only if the imported entity is an AS3 P-Code.
Use -1 as characterId to replace main timeline SoundStreamHead.
-replace <infile> <outfile> <argsfile>
Replace data using argsfile.
Same as -replace command, but the rest of arguments is read as lines from

View File

@@ -5175,7 +5175,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se
boolean ok = false;
try {
ok = soundImporter.importSound(st, new FileInputStream(selfile), soundFormat);
ok = soundImporter.importSound(st, new FileInputStream(selfile), soundFormat, null);
((SWF) ((TreeItem) st).getOpenable()).clearSoundCache();
} catch (IOException ex) {
//ignore