Added: #2451 Replacing sound stream block ranges

This commit is contained in:
Jindra Petřík
2025-06-06 18:36:56 +02:00
parent 39cd80ffc2
commit e65ac557fb
8 changed files with 182 additions and 18 deletions

View File

@@ -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

View File

@@ -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<MP3FRAME> 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<SoundStreamFrameRange> ranges = streamHead.getRanges();
List<SoundStreamBlockTag> 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

View File

@@ -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.
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.

View File

@@ -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.
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.

View File

@@ -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]));
}
}

View File

@@ -80,7 +80,7 @@ public class SoundStreamFrameRange implements TreeItem, SoundTag {
@Override
public boolean importSupported() {
return false; //??
return true;
}
@Override

View File

@@ -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.");

View File

@@ -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);
}
}