From e65ac557fb98aae4d264d1640ac537b83aec9e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Fri, 6 Jun 2025 18:36:56 +0200 Subject: [PATCH] Added: #2451 Replacing sound stream block ranges --- CHANGELOG.md | 2 + .../flash/importers/SoundImporter.java | 87 ++++++++++++++++--- .../flash/locales/AppResources.properties | 4 +- .../flash/locales/AppResources_cs.properties | 4 +- .../SoundParametersMismatchException.java | 79 +++++++++++++++++ .../flash/timeline/SoundStreamFrameRange.java | 2 +- .../console/CommandLineArgumentParser.java | 3 + .../jpexs/decompiler/flash/gui/MainPanel.java | 19 +++- 8 files changed, 182 insertions(+), 18 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/SoundParametersMismatchException.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b53e9e8cf..1ece7d124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ All notable changes to this project will be documented in this file. - AS3 - navigation to definition in other SWF file and also player/airglobal - [#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 ### Changed - AS1/2 - Single DoAction tag inside frame is now displayed directly as frame node @@ -3849,6 +3850,7 @@ Major version of SWF to XML export changed to 2. [#2412]: https://www.free-decompiler.com/flash/issues/2412 [#1682]: https://www.free-decompiler.com/flash/issues/1682 [#2463]: https://www.free-decompiler.com/flash/issues/2463 +[#2451]: https://www.free-decompiler.com/flash/issues/2451 [#2456]: https://www.free-decompiler.com/flash/issues/2456 [#2459]: https://www.free-decompiler.com/flash/issues/2459 [#2460]: https://www.free-decompiler.com/flash/issues/2460 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 b5abbfc34..4414e108b 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 @@ -27,6 +27,7 @@ import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.SoundImportException; +import com.jpexs.decompiler.flash.tags.base.SoundParametersMismatchException; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.UnsupportedSamplingRateException; @@ -69,6 +70,7 @@ public class SoundImporter { /** * Imports sound from input stream. + * * @param soundTag Sound tag * @param is Input stream * @param newSoundFormat New sound format @@ -236,13 +238,29 @@ public class SoundImporter { /** * Imports sound stream from input stream. + * * @param streamHead Sound stream head * @param is Input stream * @param newSoundFormat New sound format * @return True if sound stream was imported successfully * @throws UnsupportedSamplingRateException On unsupported sampling rate */ - public boolean importSoundStream(SoundStreamHeadTypeTag streamHead, InputStream is, int newSoundFormat) throws UnsupportedSamplingRateException { + public boolean importSoundStream(SoundStreamHeadTypeTag streamHead, InputStream is, int newSoundFormat) throws UnsupportedSamplingRateException, SoundParametersMismatchException { + return importSoundStreamAtFrame(streamHead, is, newSoundFormat, null); + } + + /** + * Imports sound stream from input stream. + * + * @param streamHead Sound stream head + * @param is Input stream + * @param newSoundFormat New sound format + * @param startFrame Starting frame. null = autodetect, replace whole + * timeline + * @return True if sound stream was imported successfully + * @throws UnsupportedSamplingRateException On unsupported sampling rate + */ + public boolean importSoundStreamAtFrame(SoundStreamHeadTypeTag streamHead, InputStream is, int newSoundFormat, Integer startFrame) throws UnsupportedSamplingRateException, SoundParametersMismatchException { List mp3Frames = null; int newSoundRate = -1; boolean newSoundSize = false; @@ -346,28 +364,57 @@ public class SoundImporter { return false; } + if (startFrame != null) { + if (newSoundFormat != streamHead.getSoundFormatId() + || newSoundSize != streamHead.getSoundSize() + || newSoundType != streamHead.getSoundType() + || newSoundRate != streamHead.getSoundRate()) { + throw new SoundParametersMismatchException( + streamHead.getSoundType(), + streamHead.getSoundSize(), + streamHead.getSoundRate(), + streamHead.getSoundFormatId(), + newSoundType, + newSoundSize, + newSoundRate, + newSoundFormat + ); + } + } + ByteArrayInputStream bais = uncompressedSoundData == null ? null : new ByteArrayInputStream(uncompressedSoundData); List ranges = streamHead.getRanges(); List existingBlocks = new ArrayList<>(); - for (SoundStreamFrameRange range : ranges) { - existingBlocks.addAll(range.blocks); - } - - int startFrame = 0; Timelined timelined = streamHead.getTimelined(); - if (!existingBlocks.isEmpty()) { - ReadOnlyTagList tags = timelined.getTags(); - for (Tag t : tags) { - if (t instanceof ShowFrameTag) { - startFrame++; + if (startFrame == null) { + + for (SoundStreamFrameRange range : ranges) { + existingBlocks.addAll(range.blocks); + } + + startFrame = 0; + if (!existingBlocks.isEmpty()) { + ReadOnlyTagList tags = timelined.getTags(); + for (Tag t : tags) { + if (t instanceof ShowFrameTag) { + startFrame++; + } + if (t instanceof SoundStreamBlockTag) { + break; + } } - if (t instanceof SoundStreamBlockTag) { + } + } else { + for (SoundStreamFrameRange range : ranges) { + if (range.startFrame == startFrame) { + existingBlocks.addAll(range.blocks); break; } } } + for (SoundStreamBlockTag block : existingBlocks) { timelined.removeTag(block); } @@ -451,7 +498,18 @@ public class SoundImporter { ReadOnlyTagList tags = timelined.getTags(); int frame = -1; for (int i = 0; i < tags.size(); i++) { + if (blocks.isEmpty()) { + break; + } Tag t = tags.get(i); + if (t instanceof SoundStreamBlockTag) { + if (frame + 1 >= startFrame) { + timelined.removeTag(i); + tags = timelined.getTags(); + i--; + continue; + } + } if (t instanceof ShowFrameTag) { frame++; if (frame >= startFrame && !blocks.isEmpty()) { @@ -490,6 +548,7 @@ public class SoundImporter { /** * Imports sound from input stream. + * * @param soundTag Sound tag * @param is Input stream * @param newSoundFormat New sound format @@ -503,11 +562,15 @@ public class SoundImporter { if (soundTag instanceof SoundStreamHeadTypeTag) { return importSoundStream((SoundStreamHeadTypeTag) soundTag, is, newSoundFormat); } + if (soundTag instanceof SoundStreamFrameRange) { + return importSoundStreamAtFrame(((SoundStreamFrameRange) soundTag).getHead(), is, newSoundFormat, ((SoundStreamFrameRange) soundTag).startFrame); + } return false; } /** * Bulk imports sounds from directory. + * * @param soundDir Sound directory * @param swf SWF * @param printOut Print out diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources.properties b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources.properties index 510337574..43393f9fc 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources.properties +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources.properties @@ -60,4 +60,6 @@ configurationFile.meta.appVersion = Last version of tha app that modified this f configurationFile.meta.showComments = Show configuration comments - set to true (and exit app again to resave) to show configuration titles and descriptions. configurationFile.meta.modifiedOnly = Store modified items only in this file - set to false (and exit app again to resave) to show all. configurationFile.configuration = Section - Actual configuration -configuration.removed = WARNING: This configuration was REMOVED. It is unused. \ No newline at end of file +configuration.removed = WARNING: This configuration was REMOVED. It is unused. + +exception.soundFormat.expected = Required format for import to this sound stream: %expected%\nFormat of selected file:%actual%\nPlease convert the file to the required format and try again. diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources_cs.properties b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources_cs.properties index 82772d5b5..a8f42ce71 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources_cs.properties +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources_cs.properties @@ -61,4 +61,6 @@ configurationFile.meta.appVersion = Posledn\u00ed verze aplikace, kter\u00e1 ten configurationFile.meta.showComments = Zobrazit koment\u00e1\u0159e ke konfiguraci - nastavte to na true (a ukon\u010dete aplikaci pro nov\u00e9 ulo\u017een\u00ed) pro zobrazen\u00ed n\u00e1zv\u016f a popis\u016f konfigurace. configurationFile.meta.modifiedOnly = Ukl\u00e1dat do tohoto souboru pouze zm\u011bn\u011bn\u00e9 hodnoty - nastavte na false (a ukon\u010dete aplikaci pro nov\u00e9 ulo\u017een\u00ed) pro zobrazen\u00ed v\u0161eho. configurationFile.configuration = Sekce - Vlastn\u00ed konfigurace -configuration.removed = VAROV\u00c1N\u00cd: Tato konfigurace byla ODSTRAN\u011aNA. Nepou\u017e\u00edv\u00e1 se. \ No newline at end of file +configuration.removed = VAROV\u00c1N\u00cd: Tato konfigurace byla ODSTRAN\u011aNA. Nepou\u017e\u00edv\u00e1 se. + +exception.soundFormat.expected = Vy\u017eadovan\u00fd form\u00e1t pro import do tohoto zvukov\u00e9ho streamu: %expected%\nForm\u00e1t vybran\u00e9ho souboru: %actual%\nPros\u00edm zkonvertujte soubor do vy\u017eadovan\u00e9ho form\u00e1tu a zkuste to znovu. diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/SoundParametersMismatchException.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/SoundParametersMismatchException.java new file mode 100644 index 000000000..9801d9700 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/SoundParametersMismatchException.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2010-2025 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.tags.base; + +import com.jpexs.decompiler.flash.AppResources; + +/** + * + * @author JPEXS + */ +public class SoundParametersMismatchException extends SoundImportException { + + /** + * + * @param expectedSoundType + * @param expectedSoundSize + * @param expectedSoundRate + * @param expectedSoundFormat + */ + public SoundParametersMismatchException( + boolean expectedSoundType, + boolean expectedSoundSize, + int expectedSoundRate, + int expectedSoundFormat, + boolean actualSoundType, + boolean actualSoundSize, + int actualSoundRate, + int actualSoundFormat) { + super(AppResources.translate("exception.soundFormat.expected").replace("%expected%", + (expectedSoundType ? "stereo" : "mono") + " " + + (expectedSoundSize ? "16bit" : "8bit") + " " + + new int[]{5512, 11025, 22050, 44100}[expectedSoundRate] + " Hz" + + " " + new String[]{ + "uncompressed native endian", + "adpcm", + "mp3", + "uncompressed little endian", + "nellymoser 16 kHz", + "nellymoser 8 kHz", + "nellymoser", + "", + "", + "", + "", + "speex" + }[expectedSoundFormat]).replace("%actual%", + (actualSoundType ? "stereo" : "mono") + " " + + (actualSoundSize ? "16bit" : "8bit") + " " + + new int[]{5512, 11025, 22050, 44100}[actualSoundRate] + " Hz" + + " " + new String[]{ + "uncompressed native endian", + "adpcm", + "mp3", + "uncompressed little endian", + "nellymoser 16 kHz", + "nellymoser 8 kHz", + "nellymoser", + "", + "", + "", + "", + "speex" + }[actualSoundFormat])); + } +} 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 cc9909000..0b439004e 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,7 @@ public class SoundStreamFrameRange implements TreeItem, SoundTag { @Override public boolean importSupported() { - return false; //?? + return true; } @Override diff --git a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java index adcf78ec7..d7b7ba4fc 100644 --- a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java +++ b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java @@ -163,6 +163,7 @@ import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.RenderContext; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.SoundImportException; +import com.jpexs.decompiler.flash.tags.base.SoundParametersMismatchException; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.TextImportErrorHandler; @@ -3055,6 +3056,8 @@ public class CommandLineArgumentParser { } System.err.println("Import FAILED. Input file has unsupported sampling rate (" + usre.getSoundRate() + "). Supported rates for this sound format: " + String.join(", ", supportedRatesStr) + "."); System.exit(2); + } catch (SoundParametersMismatchException ex) { + System.err.println("Import FAILED. Input file has different format that target stream. Target stream format: " + ex.getMessage()); } if (!ok) { System.err.println("Import FAILED. Maybe unsupported media type? Only MP3 and uncompressed WAV are available."); diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 1fff85942..aad832cca 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -172,6 +172,7 @@ import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.SoundImportException; +import com.jpexs.decompiler.flash.tags.base.SoundParametersMismatchException; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.SymbolClassTypeTag; @@ -5120,7 +5121,16 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se TreeItem ti0 = items.get(0); File file = null; if (ti0 instanceof SoundTag) { - file = showImportFileChooser("filter.sounds|*.mp3;*.wav|filter.sounds.mp3|*.mp3|filter.sounds.wav|*.wav", false, "importsound"); + if (ti0 instanceof SoundStreamFrameRange) { + SoundStreamFrameRange r = (SoundStreamFrameRange) ti0; + if (r.getSoundFormatId() == SoundFormat.FORMAT_MP3) { + file = showImportFileChooser("filter.sounds.mp3|*.mp3", false, "importsound"); + } else { + file = showImportFileChooser("filter.sounds.wav|*.wav", false, "importsound"); + } + } else { + file = showImportFileChooser("filter.sounds|*.mp3;*.wav|filter.sounds.mp3|*.mp3|filter.sounds.wav|*.wav", false, "importsound"); + } } if (ti0 instanceof ImageTag) { file = showImportFileChooser("filter.images|*.jpg;*.jpeg;*.gif;*.png;*.bmp", true, "importimage"); @@ -5166,7 +5176,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se boolean ok = false; try { ok = soundImporter.importSound(st, new FileInputStream(selfile), soundFormat); - ((Tag) st).getSwf().clearSoundCache(); + ((SWF) ((TreeItem) st).getOpenable()).clearSoundCache(); } catch (IOException ex) { //ignore } catch (UnsupportedSamplingRateException ex) { @@ -5177,6 +5187,9 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } ViewMessages.showMessageDialog(this, translate("error.sound.rate").replace("%saplingRate%", samplingRateKhz).replace("%supportedRates%", String.join(", ", supportedRatesKhz)), translate("error"), JOptionPane.ERROR_MESSAGE); return; + } catch (SoundParametersMismatchException ex) { + ViewMessages.showMessageDialog(this, ex.getMessage(), translate("error"), JOptionPane.ERROR_MESSAGE); + return; } catch (SoundImportException ex) { //ignore } @@ -5184,7 +5197,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se if (!ok) { ViewMessages.showMessageDialog(this, translate("error.sound.invalid"), translate("error"), JOptionPane.ERROR_MESSAGE); } else { - refreshTree(((Tag) st).getSwf()); + refreshTree((SWF) ((TreeItem) st).getOpenable()); reload(true); } }