diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ece7d124..6a71ebfcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SoundImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SoundImporter.java index 4414e108b..73bc87363 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SoundImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SoundImporter.java @@ -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 soundTags = new ArrayList<>(); + + List> 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 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) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/SoundStreamFrameRange.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/SoundStreamFrameRange.java index 0b439004e..7cd910945 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/SoundStreamFrameRange.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/SoundStreamFrameRange.java @@ -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) + ")"; diff --git a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java index d7b7ba4fc..258c94abc 100644 --- a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java +++ b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java @@ -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 supportedRatesStr = new ArrayList<>(); for (int i : usre.getSupportedRates()) { diff --git a/src/com/jpexs/decompiler/flash/console/help.txt b/src/com/jpexs/decompiler/flash/console/help.txt index 735c5af3e..a6db42ca7 100644 --- a/src/com/jpexs/decompiler/flash/console/help.txt +++ b/src/com/jpexs/decompiler/flash/console/help.txt @@ -98,18 +98,21 @@ alias /? Convert FlashPaper SWF file to PDF . Use -zoom parameter to specify image quality. --replace (|) \ - [nofill] ([][]) \ - [(|) \ - [nofill] ([][])]... +-replace \ + (|) \ + [nofill] [-startFrame ] ([][]) \ + [(|) \ + [nofill] [-startFrame ] ([][])]... 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. + parameter sets sound range start frame (for sound streams) parameter can be specified for Image and Shape tags. valid formats: lossless, lossless2, jpeg2, jpeg3, jpeg4. 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 Replace data using argsfile. Same as -replace command, but the rest of arguments is read as lines from diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index aad832cca..6aa52b306 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -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