diff --git a/CHANGELOG.md b/CHANGELOG.md index 25daf13c5..4ac8e2d26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ All notable changes to this project will be documented in this file. - [#2131] Breakpoint list dialog - ExportAssets tag - show first item as description in the tree when there is only single item - [#2134] FLA Export - split main timeline into scenes when DefineSceneAndFrameLabelData tag is present +- [#2132] Show and export streamed sound (SoundStreamHead/SoundStreamBlock) in frame ranges ### Fixed - [#2021], [#2000] Caret position in editors when using tabs and / or unicode @@ -64,6 +65,7 @@ All notable changes to this project will be documented in this file. - Wordrapping long words in DefineEditText - [#2133] Linux/Mac - ffdec.sh not correctly parsing java build number on javas without it - [#2135] FLA Export - framescripts handling when addFrameScript uses Multinames instead of QNames +- [#1194] FLA Export - stream sound export ### Changed - [#2120] Exported assets no longer take names from assigned classes if there is more than 1 assigned class @@ -3324,6 +3326,7 @@ Major version of SWF to XML export changed to 2. [#2131]: https://www.free-decompiler.com/flash/issues/2131 [#2124]: https://www.free-decompiler.com/flash/issues/2124 [#2134]: https://www.free-decompiler.com/flash/issues/2134 +[#2132]: https://www.free-decompiler.com/flash/issues/2132 [#2021]: https://www.free-decompiler.com/flash/issues/2021 [#2000]: https://www.free-decompiler.com/flash/issues/2000 [#2116]: https://www.free-decompiler.com/flash/issues/2116 @@ -3335,6 +3338,7 @@ Major version of SWF to XML export changed to 2. [#2053]: https://www.free-decompiler.com/flash/issues/2053 [#2133]: https://www.free-decompiler.com/flash/issues/2133 [#2135]: https://www.free-decompiler.com/flash/issues/2135 +[#1194]: https://www.free-decompiler.com/flash/issues/1194 [#2120]: https://www.free-decompiler.com/flash/issues/2120 [#1130]: https://www.free-decompiler.com/flash/issues/1130 [#1220]: https://www.free-decompiler.com/flash/issues/1220 @@ -3765,7 +3769,6 @@ Major version of SWF to XML export changed to 2. [#1200]: https://www.free-decompiler.com/flash/issues/1200 [#1198]: https://www.free-decompiler.com/flash/issues/1198 [#1205]: https://www.free-decompiler.com/flash/issues/1205 -[#1194]: https://www.free-decompiler.com/flash/issues/1194 [#1210]: https://www.free-decompiler.com/flash/issues/1210 [#1217]: https://www.free-decompiler.com/flash/issues/1217 [#1244]: https://www.free-decompiler.com/flash/issues/1244 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/PreviewExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/PreviewExporter.java index ebef89aa0..55faf3431 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/PreviewExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/PreviewExporter.java @@ -57,6 +57,7 @@ import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; import com.jpexs.decompiler.flash.tags.gfx.DefineCompactedFont; import com.jpexs.decompiler.flash.timeline.DepthState; import com.jpexs.decompiler.flash.timeline.Frame; +import com.jpexs.decompiler.flash.timeline.SoundStreamFrameRange; import com.jpexs.decompiler.flash.timeline.Timelined; import com.jpexs.decompiler.flash.treeitems.TreeItem; import com.jpexs.decompiler.flash.types.BUTTONCONDACTION; @@ -261,7 +262,13 @@ public class PreviewExporter { List soundFrames = new ArrayList<>(); if (treeItem instanceof SoundStreamHeadTypeTag) { - soundFrames = ((SoundStreamHeadTypeTag) treeItem).getBlocks(); + SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) treeItem; + for (SoundStreamFrameRange range : head.getRanges()) { + soundFrames.addAll(range.blocks); + } + } + if (treeItem instanceof SoundStreamFrameRange) { + soundFrames = ((SoundStreamFrameRange) treeItem).blocks; frameCount = soundFrames.size(); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/SoundExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/SoundExporter.java index 21279b100..dd6f2bd3d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/SoundExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/SoundExporter.java @@ -34,6 +34,7 @@ import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.gfx.DefineExternalSound; import com.jpexs.decompiler.flash.tags.gfx.DefineExternalStreamSound; +import com.jpexs.decompiler.flash.timeline.SoundStreamFrameRange; import com.jpexs.decompiler.flash.types.sound.SoundExportFormat; import com.jpexs.decompiler.flash.types.sound.SoundFormat; import com.jpexs.helpers.ByteArrayRange; @@ -58,8 +59,17 @@ import java.util.Set; * @author JPEXS */ public class SoundExporter { - public List exportSounds(AbortRetryIgnoreHandler handler, String outdir, ReadOnlyTagList tags, final SoundExportSettings settings, EventListener evl) throws IOException, InterruptedException { + List sounds = new ArrayList<>(); + for (Tag t : tags) { + if (t instanceof SoundTag) { + sounds.add((SoundTag) t); + } + } + return exportSounds(handler, outdir, sounds, settings, evl); + } + + public List exportSounds(AbortRetryIgnoreHandler handler, String outdir, List tags, final SoundExportSettings settings, EventListener evl) throws IOException, InterruptedException { List ret = new ArrayList<>(); if (Thread.currentThread().isInterrupted()) { return ret; @@ -72,75 +82,64 @@ public class SoundExporter { File foutdir = new File(outdir); Path.createDirectorySafe(foutdir); - int count = 0; - for (Tag t : tags) { - if (t instanceof SoundTag) { - count++; - } - } - - if (count == 0) { + if (tags.isEmpty()) { return ret; } int currentIndex = 1; - for (Tag t : tags) { - if (t instanceof SoundTag) { - if (evl != null) { - evl.handleExportingEvent("sound", currentIndex, count, t.getName()); - } - - final SoundTag st = (SoundTag) t; - - String ext = ".wav"; - SoundFormat fmt = st.getSoundFormat(); - switch (fmt.getNativeExportFormat()) { - case MP3: - if (settings.mode.hasMP3()) { - ext = ".mp3"; - } - break; - case FLV: - if (settings.mode.hasFlv()) { - ext = ".flv"; - } - break; - } - if (settings.mode == SoundExportMode.FLV) { - ext = ".flv"; - } - - final File file = new File(outdir + File.separator + Helper.makeFileName(st.getCharacterExportFileName()) + ext); - new RetryTask(() -> { - try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) { - exportSound(os, st, settings.mode); - } - }, handler).run(); - - Set classNames = (st instanceof CharacterTag) ? ((CharacterTag) st).getClassNames() : new HashSet<>(); - if (Configuration.as3ExportNamesUseClassNamesOnly.get() && !classNames.isEmpty()) { - for (String className : classNames) { - File classFile = new File(outdir + File.separator + Helper.makeFileName(className + ext)); - new RetryTask(() -> { - Files.copy(file.toPath(), classFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - }, handler).run(); - ret.add(classFile); - } - file.delete(); - } else { - ret.add(file); - } - - if (Thread.currentThread().isInterrupted()) { - break; - } - - if (evl != null) { - evl.handleExportedEvent("sound", currentIndex, count, t.getName()); - } - - currentIndex++; + for (SoundTag st : tags) { + if (evl != null) { + evl.handleExportingEvent("sound", currentIndex, tags.size(), st.getName()); } + + String ext = ".wav"; + SoundFormat fmt = st.getSoundFormat(); + switch (fmt.getNativeExportFormat()) { + case MP3: + if (settings.mode.hasMP3()) { + ext = ".mp3"; + } + break; + case FLV: + if (settings.mode.hasFlv()) { + ext = ".flv"; + } + break; + } + if (settings.mode == SoundExportMode.FLV) { + ext = ".flv"; + } + + final File file = new File(outdir + File.separator + Helper.makeFileName(st.getCharacterExportFileName()) + ext); + new RetryTask(() -> { + try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) { + exportSound(os, st, settings.mode); + } + }, handler).run(); + + Set classNames = (st instanceof CharacterTag) ? ((CharacterTag) st).getClassNames() : new HashSet<>(); + if (Configuration.as3ExportNamesUseClassNamesOnly.get() && !classNames.isEmpty()) { + for (String className : classNames) { + File classFile = new File(outdir + File.separator + Helper.makeFileName(className + ext)); + new RetryTask(() -> { + Files.copy(file.toPath(), classFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + }, handler).run(); + ret.add(classFile); + } + file.delete(); + } else { + ret.add(file); + } + + if (Thread.currentThread().isInterrupted()) { + break; + } + + if (evl != null) { + evl.handleExportedEvent("sound", currentIndex, tags.size(), st.getName()); + } + + currentIndex++; } return ret; } @@ -168,12 +167,22 @@ public class SoundExporter { for (ByteArrayRange data : datas) { flv.writeTag(new FLVTAG(0, new AUDIODATA(st.getSoundFormatId(), st.getSoundRate(), st.getSoundSize(), st.getSoundType(), data.getRangeData()))); } - } else if (st instanceof SoundStreamHeadTypeTag) { - SoundStreamHeadTypeTag sh = (SoundStreamHeadTypeTag) st; + } else if ((st instanceof SoundStreamFrameRange) || (st instanceof SoundStreamHeadTypeTag)) { + List blocks; + if (st instanceof SoundStreamHeadTypeTag) { + blocks = new ArrayList<>(); + SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) st; + for (SoundStreamFrameRange range : head.getRanges()) { + blocks.addAll(range.blocks); + } + } else { + blocks = ((SoundStreamFrameRange) st).blocks; + } + + SoundStreamFrameRange sh = (SoundStreamFrameRange) st; FLVOutputStream flv = new FLVOutputStream(fos); flv.writeHeader(true, false); - List blocks = sh.getBlocks(); - + int ms = (int) (1000.0 / ((Tag) st).getSwf().frameRate); for (int b = 0; b < blocks.size(); b++) { byte[] data = blocks.get(b).streamSoundData.getRangeData(); 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 2aad805ef..98a6c8f3e 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 @@ -30,6 +30,7 @@ import com.jpexs.decompiler.flash.tags.base.SoundImportException; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.UnsupportedSamplingRateException; +import com.jpexs.decompiler.flash.timeline.SoundStreamFrameRange; import com.jpexs.decompiler.flash.timeline.Timelined; import com.jpexs.decompiler.flash.types.sound.MP3FRAME; import com.jpexs.decompiler.flash.types.sound.MP3SOUNDDATA; @@ -329,7 +330,14 @@ public class SoundImporter { ByteArrayInputStream bais = uncompressedSoundData == null ? null : new ByteArrayInputStream(uncompressedSoundData); - List existingBlocks = streamHead.getBlocks(); + 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()) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java index ff2cc1421..021adf905 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java @@ -237,4 +237,9 @@ public class DefineSoundTag extends CharacterTag implements SoundTag { public void setSoundRate(int soundRate) { this.soundRate = soundRate; } + + @Override + public String getFlaExportName() { + return "sound" + getCharacterId(); + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java index 4bfffd816..6fd88c3b1 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java @@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; +import com.jpexs.decompiler.flash.timeline.SoundStreamFrameRange; import com.jpexs.decompiler.flash.timeline.Timeline; import com.jpexs.decompiler.flash.types.BasicType; import com.jpexs.decompiler.flash.types.annotations.Conditional; @@ -191,9 +192,9 @@ public class SoundStreamHead2Tag extends SoundStreamHeadTypeTag { } @Override - public List getBlocks() { + public List getRanges() { Timeline timeline = swf.getTimeline(); - List ret = timeline.getSoundStreamBlocks(this); + List ret = timeline.getSoundStreamBlocks(this); return ret; } @@ -206,14 +207,16 @@ public class SoundStreamHead2Tag extends SoundStreamHeadTypeTag { @Override public List getRawSoundData() { List ret = new ArrayList<>(); - List blocks = getBlocks(); - if (blocks != null) { - for (SoundStreamBlockTag block : blocks) { - ByteArrayRange data = block.streamSoundData; - if (streamSoundCompression == SoundFormat.FORMAT_MP3) { - ret.add(data.getSubRange(4, data.getLength() - 4)); - } else { - ret.add(data); + List frameRanges = getRanges(); + if (frameRanges != null) { + for (SoundStreamFrameRange range : frameRanges) { + for (SoundStreamBlockTag block : range.blocks) { + ByteArrayRange data = block.streamSoundData; + if (streamSoundCompression == SoundFormat.FORMAT_MP3) { + ret.add(data.getSubRange(4, data.getLength() - 4)); + } else { + ret.add(data); + } } } } @@ -222,7 +225,11 @@ public class SoundStreamHead2Tag extends SoundStreamHeadTypeTag { @Override public long getTotalSoundSampleCount() { - return getBlocks().size() * streamSoundSampleCount; + int blockCount = 0; + for (SoundStreamFrameRange range : getRanges()) { + blockCount += range.blocks.size(); + } + return blockCount * streamSoundSampleCount; } @Override @@ -278,4 +285,9 @@ public class SoundStreamHead2Tag extends SoundStreamHeadTypeTag { public void setSoundRate(int soundRate) { this.streamSoundRate = soundRate; } + + @Override + public String getFlaExportName() { + return "sound" + getCharacterId(); + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java index e68c762fb..f958aa82e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java @@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; +import com.jpexs.decompiler.flash.timeline.SoundStreamFrameRange; import com.jpexs.decompiler.flash.timeline.Timeline; import com.jpexs.decompiler.flash.types.BasicType; import com.jpexs.decompiler.flash.types.annotations.Conditional; @@ -201,11 +202,10 @@ public class SoundStreamHeadTag extends SoundStreamHeadTypeTag { } @Override - public List getBlocks() { + public List getRanges() { Timeline timeline = swf.getTimeline(); - List ret = timeline.getSoundStreamBlocks(this); + List ret = timeline.getSoundStreamBlocks(this); return ret; - } @Override @@ -216,14 +216,16 @@ public class SoundStreamHeadTag extends SoundStreamHeadTypeTag { @Override public List getRawSoundData() { List ret = new ArrayList<>(); - List blocks = getBlocks(); - if (blocks != null) { - for (SoundStreamBlockTag block : blocks) { - ByteArrayRange data = block.streamSoundData; - if (streamSoundCompression == SoundFormat.FORMAT_MP3) { - ret.add(data.getSubRange(4, data.getLength() - 4)); - } else { - ret.add(data); + List frameRanges = getRanges(); + if (frameRanges != null) { + for (SoundStreamFrameRange range : frameRanges) { + for (SoundStreamBlockTag block : range.blocks) { + ByteArrayRange data = block.streamSoundData; + if (streamSoundCompression == SoundFormat.FORMAT_MP3) { + ret.add(data.getSubRange(4, data.getLength() - 4)); + } else { + ret.add(data); + } } } } @@ -232,7 +234,11 @@ public class SoundStreamHeadTag extends SoundStreamHeadTypeTag { @Override public long getTotalSoundSampleCount() { - return getBlocks().size() * streamSoundSampleCount; + int blockCount = 0; + for (SoundStreamFrameRange range : getRanges()) { + blockCount += range.blocks.size(); + } + return blockCount * streamSoundSampleCount; } @Override @@ -288,4 +294,9 @@ public class SoundStreamHeadTag extends SoundStreamHeadTypeTag { public void setSoundRate(int soundRate) { this.streamSoundRate = soundRate; } + + @Override + public String getFlaExportName() { + return "sound" + getCharacterId(); + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/SoundStreamHeadTypeTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/SoundStreamHeadTypeTag.java index c7be27531..525665738 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/SoundStreamHeadTypeTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/SoundStreamHeadTypeTag.java @@ -19,6 +19,7 @@ package com.jpexs.decompiler.flash.tags.base; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag; import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.timeline.SoundStreamFrameRange; import com.jpexs.helpers.ByteArrayRange; import java.util.List; @@ -37,6 +38,6 @@ public abstract class SoundStreamHeadTypeTag extends Tag implements CharacterIdT public abstract long getSoundSampleCount(); - public abstract List getBlocks(); + public abstract List getRanges(); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/SoundTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/SoundTag.java index cd16af0b5..622967f67 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/SoundTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/SoundTag.java @@ -59,4 +59,10 @@ public interface SoundTag extends TreeItem { public void setSoundRate(int soundRate); public int getCharacterId(); + + public boolean isReadOnly(); + + public String getName(); + + public String getFlaExportName(); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalSound.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalSound.java index c77cfe49b..161d22e32 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalSound.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalSound.java @@ -243,4 +243,9 @@ public class DefineExternalSound extends CharacterTag implements SoundTag { tagInfo.addInfo("general", "stereo", soundFormat.stereo); tagInfo.addInfo("general", "sampleCount", sampleCount); } + + @Override + public String getFlaExportName() { + return "sound" + getCharacterId(); + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalStreamSound.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalStreamSound.java index ac075ff48..b32671b9b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalStreamSound.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineExternalStreamSound.java @@ -255,4 +255,9 @@ public class DefineExternalStreamSound extends Tag implements CharacterIdTag, So tagInfo.addInfo("general", "stereo", soundFormat.stereo); tagInfo.addInfo("general", "sampleCount", sampleCount); } + + @Override + public String getFlaExportName() { + return "sound" + getCharacterId(); + } } 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 new file mode 100644 index 000000000..5d91bb295 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/SoundStreamFrameRange.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2010-2023 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.timeline; + +import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag; +import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; +import com.jpexs.decompiler.flash.treeitems.Openable; +import com.jpexs.decompiler.flash.treeitems.TreeItem; +import com.jpexs.decompiler.flash.types.sound.SoundExportFormat; +import com.jpexs.decompiler.flash.types.sound.SoundFormat; +import com.jpexs.helpers.ByteArrayRange; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class SoundStreamFrameRange implements TreeItem, SoundTag { + public int startFrame; + public int endFrame; + public List blocks = new ArrayList<>(); + + private final SoundStreamHeadTypeTag head; + + public SoundStreamFrameRange(SoundStreamHeadTypeTag head) { + this.head = head; + } + + @Override + public Openable getOpenable() { + return head.getOpenable(); + } + + @Override + public boolean isModified() { + return false; + } + + @Override + public SoundExportFormat getExportFormat() { + return head.getExportFormat(); + } + + @Override + public boolean importSupported() { + return false; //?? + } + + @Override + public int getSoundRate() { + return head.getSoundRate(); + } + + @Override + public boolean getSoundType() { + return head.getSoundType(); + } + + @Override + public List getRawSoundData() { + List ret = new ArrayList<>(); + for (SoundStreamBlockTag block : blocks) { + ByteArrayRange data = block.streamSoundData; + if (getSoundFormatId() == SoundFormat.FORMAT_MP3) { + ret.add(data.getSubRange(4, data.getLength() - 4)); + } else { + ret.add(data); + } + } + return ret; + } + + @Override + public int getSoundFormatId() { + return head.getSoundFormatId(); + } + + @Override + public long getTotalSoundSampleCount() { + return blocks.size() * head.getSoundSampleCount(); + } + + @Override + public boolean getSoundSize() { + return head.getSoundSize(); + } + + @Override + public String getCharacterExportFileName() { + return head.getCharacterExportFileName() + "_" + (startFrame + 1) + "-" + (endFrame + 1); + } + + @Override + public String getName() { + return "SoundStreamBlocks"; + } + + @Override + public SoundFormat getSoundFormat() { + return head.getSoundFormat(); + } + + @Override + public void setSoundSize(boolean soundSize) { + //? + } + + @Override + public void setSoundType(boolean soundType) { + //? + } + + @Override + public void setSoundSampleCount(long soundSampleCount) { + //? + } + + @Override + public void setSoundCompression(int soundCompression) { + //? + } + + @Override + public void setSoundRate(int soundRate) { + //? + } + + @Override + public int getCharacterId() { + return head.getCharacterId(); + } + + public SoundStreamHeadTypeTag getHead() { + return head; + } + + @Override + public String toString() { + return "SoundStreamBlocks (frame " + (startFrame + 1) + " - " + (endFrame + 1) + ")"; + } + + @Override + public boolean isReadOnly() { + return head.isReadOnly(); + } + + @Override + public String getFlaExportName() { + return head.getFlaExportName() + "_" + (startFrame + 1) + "-" + (endFrame + 1); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java index 57c538fb3..4d73db66f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java @@ -118,7 +118,7 @@ public class Timeline { private final Map actionFrames = new HashMap<>(); - private final Map> soundStramBlocks = new LinkedHashMap<>(); + private final Map> soundStreamRanges = new LinkedHashMap<>(); private AS2Package as2RootPackage; @@ -174,9 +174,9 @@ public class Timeline { return depthMaxFrame; } - public List getSoundStreamBlocks(SoundStreamHeadTypeTag head) { + public List getSoundStreamBlocks(SoundStreamHeadTypeTag head) { ensureInitialized(); - return soundStramBlocks.get(head.getCharacterId()); + return soundStreamRanges.get(head.getCharacterId()); } public Tag getParentTag() { @@ -194,7 +194,7 @@ public class Timeline { asmSources.clear(); asmSourceContainers.clear(); actionFrames.clear(); - soundStramBlocks.clear(); + soundStreamRanges.clear(); otherTags.clear(); this.id = id; this.swf = swf; @@ -576,13 +576,23 @@ public class Timeline { } private void populateSoundStreamBlocks(int containerId, Iterable tags) { - List blocks = null; + List ranges = null; + SoundStreamFrameRange range = null; + final int MIN_NUM_FRAMES_NO_SOUND = 2; + int numFramesNoSound = MIN_NUM_FRAMES_NO_SOUND; + boolean frameHasSound = false; + int frame = 0; + SoundStreamHeadTypeTag head = null; for (Tag t : tags) { if (t instanceof SoundStreamHeadTypeTag) { - SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) t; + head = (SoundStreamHeadTypeTag) t; head.setCharacterId(containerId); - blocks = new ArrayList<>(); - soundStramBlocks.put(containerId, blocks); + ranges = new ArrayList<>(); + range = new SoundStreamFrameRange(head); + range.startFrame = -1; + range.endFrame = -1; + numFramesNoSound = MIN_NUM_FRAMES_NO_SOUND; + soundStreamRanges.put(containerId, ranges); continue; } if (t instanceof DefineExternalStreamSound) { @@ -595,14 +605,41 @@ public class Timeline { DefineSpriteTag sprite = (DefineSpriteTag) t; populateSoundStreamBlocks(sprite.getCharacterId(), sprite.getTags()); } + + if (t instanceof ShowFrameTag) { + frame++; + if (frameHasSound) { + numFramesNoSound = 0; + } else { + numFramesNoSound++; + } + frameHasSound = false; + } - if (blocks == null) { + if (ranges == null) { + continue; + } + if (range == null) { continue; } - if (t instanceof SoundStreamBlockTag) { - blocks.add((SoundStreamBlockTag) t); - } + if (t instanceof SoundStreamBlockTag) { + if (numFramesNoSound >= MIN_NUM_FRAMES_NO_SOUND && range.endFrame > -1) { + ranges.add(range); + range = new SoundStreamFrameRange(head); + range.startFrame = -1; + range.endFrame = -1; + } + range.blocks.add((SoundStreamBlockTag) t); + if (range.startFrame == -1) { + range.startFrame = frame; + } + range.endFrame = frame; + frameHasSound = true; + } + } + if (range != null && ranges != null && range.endFrame > -1) { + ranges.add(range); } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java index 5fc0374bf..20c872c01 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java @@ -102,6 +102,7 @@ import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.tags.enums.ImageFormat; import com.jpexs.decompiler.flash.tags.font.CharacterRanges; +import com.jpexs.decompiler.flash.timeline.SoundStreamFrameRange; import com.jpexs.decompiler.flash.timeline.Timelined; import com.jpexs.decompiler.flash.types.BUTTONCONDACTION; import com.jpexs.decompiler.flash.types.BUTTONRECORD; @@ -1949,6 +1950,158 @@ public class XFLConverter { writer.writeEndElement(); } + private void convertSoundMedia(SWF swf, ReadOnlyTagList tags, SoundTag symbol, XFLXmlWriter writer, HashMap files) throws XMLStreamException { + int soundFormat = 0; + int soundRate = 0; + boolean soundType = false; + boolean soundSize = false; + long soundSampleCount = 0; + byte[] soundData = SWFInputStream.BYTE_ARRAY_EMPTY; + int[] rateMap = {5, 11, 22, 44}; + String exportFormat = "flv"; + if (symbol instanceof SoundStreamFrameRange) { + SoundStreamHeadTypeTag head = ((SoundStreamFrameRange) symbol).getHead(); + soundFormat = head.getSoundFormatId(); + soundRate = head.getSoundRate(); + soundType = head.getSoundType(); + soundSize = head.getSoundSize(); + soundSampleCount = head.getSoundSampleCount(); + boolean found = false; + for (Tag t : tags) { + if (found && (t instanceof SoundStreamBlockTag)) { + SoundStreamBlockTag bl = (SoundStreamBlockTag) t; + soundData = bl.streamSoundData.getRangeData(); + break; + } + if (t == head) { + found = true; + } + } + } else if (symbol instanceof DefineSoundTag) { + DefineSoundTag sound = (DefineSoundTag) symbol; + soundFormat = sound.soundFormat; + soundRate = sound.soundRate; + soundType = sound.soundType; + soundData = sound.soundData.getRangeData(); + soundSize = sound.soundSize; + soundSampleCount = sound.soundSampleCount; + } + int format = 0; + int bits = 0; + if ((soundFormat == SoundFormat.FORMAT_ADPCM) + || (soundFormat == SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN) + || (soundFormat == SoundFormat.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN)) { + exportFormat = "wav"; + if (soundType) { //stereo + format += 1; + } + switch (soundRate) { + case 0: + format += 2; + break; + case 1: + format += 6; + break; + case 2: + format += 10; + break; + case 3: + format += 14; + break; + } + } + if (soundFormat == SoundFormat.FORMAT_SPEEX) { + bits = 18; + } + if (soundFormat == SoundFormat.FORMAT_ADPCM) { + exportFormat = "wav"; + try { + SWFInputStream sis = new SWFInputStream(swf, soundData); + int adpcmCodeSize = (int) sis.readUB(2, "adpcmCodeSize"); + bits = 2 + adpcmCodeSize; + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + if (soundFormat == SoundFormat.FORMAT_MP3) { + exportFormat = "mp3"; + if (!soundType) { //mono + format += 1; + } + format += 4; //quality best + try { + SWFInputStream sis = new SWFInputStream(swf, soundData); + MP3SOUNDDATA s = new MP3SOUNDDATA(sis, false); + if (!s.frames.isEmpty()) { + MP3FRAME frame = s.frames.get(0); + int bitRate = frame.getBitRate(); + + switch (bitRate) { + case 8: + bits = 6; + break; + case 16: + bits = 7; + break; + case 20: + bits = 8; + break; + case 24: + bits = 9; + break; + case 32: + bits = 10; + break; + case 48: + bits = 11; + break; + case 56: + bits = 12; + break; + case 64: + bits = 13; + break; + case 80: + bits = 14; + break; + case 112: + bits = 15; + break; + case 128: + bits = 16; + break; + case 160: + bits = 17; + break; + + } + } + } catch (IOException | IndexOutOfBoundsException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + SoundTag st = (SoundTag) symbol; + SoundFormat fmt = st.getSoundFormat(); + byte[] data = SWFInputStream.BYTE_ARRAY_EMPTY; + try { + data = new SoundExporter().exportSound(st, SoundExportMode.MP3_WAV); + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + String symbolFile = symbol.getFlaExportName() + "." + exportFormat; + files.put(symbolFile, data); + writer.writeStartElement("DOMSoundItem", new String[]{ + "name", symbolFile, + "sourceLastImported", Long.toString(getTimestamp(swf)), + "externalFileSize", Integer.toString(data.length)}); + writer.writeAttribute("href", symbolFile); + writer.writeAttribute("format", rateMap[soundRate] + "kHz" + " " + (soundSize ? "16bit" : "8bit") + " " + (soundType ? "Stereo" : "Mono")); + writer.writeAttribute("exportFormat", format); + writer.writeAttribute("exportBits", bits); + writer.writeAttribute("sampleCount", soundSampleCount); + } + private void convertMedia(SWF swf, Map characterVariables, Map characterClasses, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap characters, HashMap files, HashMap datfiles, FLAVersion flaVersion, XFLXmlWriter writer, StatusStack statusStack) throws XMLStreamException { boolean hasMedia = false; for (int ch : characters.keySet()) { @@ -1967,7 +2120,8 @@ public class XFLConverter { int mediaCount = 0; writer.writeStartElement("media"); - + + for (int ch : characters.keySet()) { CharacterTag symbol = characters.get(ch); if (symbol instanceof ImageTag) { @@ -2060,157 +2214,9 @@ public class XFLConverter { writer.writeEndElement(); mediaCount++; statusStack.popStatus(); - } else if (/*(symbol instanceof SoundStreamHeadTypeTag) || FIXME */(symbol instanceof DefineSoundTag)) { + } else if (symbol instanceof DefineSoundTag) { statusStack.pushStatus(symbol.toString()); - int soundFormat = 0; - int soundRate = 0; - boolean soundType = false; - boolean soundSize = false; - long soundSampleCount = 0; - byte[] soundData = SWFInputStream.BYTE_ARRAY_EMPTY; - int[] rateMap = {5, 11, 22, 44}; - String exportFormat = "flv"; - if (false) { //FIXME symbol instanceof SoundStreamHeadTypeTag) { - SoundStreamHeadTypeTag sstream = null; //(SoundStreamHeadTypeTag) symbol; - soundFormat = sstream.getSoundFormatId(); - soundRate = sstream.getSoundRate(); - soundType = sstream.getSoundType(); - soundSize = sstream.getSoundSize(); - soundSampleCount = sstream.getSoundSampleCount(); - boolean found = false; - for (Tag t : tags) { - if (found && (t instanceof SoundStreamBlockTag)) { - SoundStreamBlockTag bl = (SoundStreamBlockTag) t; - soundData = bl.streamSoundData.getRangeData(); - break; - } - if (t == symbol) { - found = true; - } - } - } else if (symbol instanceof DefineSoundTag) { - DefineSoundTag sound = (DefineSoundTag) symbol; - soundFormat = sound.soundFormat; - soundRate = sound.soundRate; - soundType = sound.soundType; - soundData = sound.soundData.getRangeData(); - soundSize = sound.soundSize; - soundSampleCount = sound.soundSampleCount; - } - int format = 0; - int bits = 0; - if ((soundFormat == SoundFormat.FORMAT_ADPCM) - || (soundFormat == SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN) - || (soundFormat == SoundFormat.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN)) { - exportFormat = "wav"; - if (soundType) { //stereo - format += 1; - } - switch (soundRate) { - case 0: - format += 2; - break; - case 1: - format += 6; - break; - case 2: - format += 10; - break; - case 3: - format += 14; - break; - } - } - if (soundFormat == SoundFormat.FORMAT_SPEEX) { - bits = 18; - } - if (soundFormat == SoundFormat.FORMAT_ADPCM) { - exportFormat = "wav"; - try { - SWFInputStream sis = new SWFInputStream(swf, soundData); - int adpcmCodeSize = (int) sis.readUB(2, "adpcmCodeSize"); - bits = 2 + adpcmCodeSize; - } catch (IOException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - if (soundFormat == SoundFormat.FORMAT_MP3) { - exportFormat = "mp3"; - if (!soundType) { //mono - format += 1; - } - format += 4; //quality best - try { - SWFInputStream sis = new SWFInputStream(swf, soundData); - MP3SOUNDDATA s = new MP3SOUNDDATA(sis, false); - if (!s.frames.isEmpty()) { - MP3FRAME frame = s.frames.get(0); - int bitRate = frame.getBitRate(); - - switch (bitRate) { - case 8: - bits = 6; - break; - case 16: - bits = 7; - break; - case 20: - bits = 8; - break; - case 24: - bits = 9; - break; - case 32: - bits = 10; - break; - case 48: - bits = 11; - break; - case 56: - bits = 12; - break; - case 64: - bits = 13; - break; - case 80: - bits = 14; - break; - case 112: - bits = 15; - break; - case 128: - bits = 16; - break; - case 160: - bits = 17; - break; - - } - } - } catch (IOException | IndexOutOfBoundsException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - SoundTag st = (SoundTag) symbol; - SoundFormat fmt = st.getSoundFormat(); - byte[] data = SWFInputStream.BYTE_ARRAY_EMPTY; - try { - data = new SoundExporter().exportSound(st, SoundExportMode.MP3_WAV); - } catch (IOException ex) { - logger.log(Level.SEVERE, null, ex); - } - - String symbolFile = "sound" + symbol.getCharacterId() + "." + exportFormat; - files.put(symbolFile, data); - writer.writeStartElement("DOMSoundItem", new String[]{ - "name", symbolFile, - "sourceLastImported", Long.toString(getTimestamp(swf)), - "externalFileSize", Integer.toString(data.length)}); - writer.writeAttribute("href", symbolFile); - writer.writeAttribute("format", rateMap[soundRate] + "kHz" + " " + (soundSize ? "16bit" : "8bit") + " " + (soundType ? "Stereo" : "Mono")); - writer.writeAttribute("exportFormat", format); - writer.writeAttribute("exportBits", bits); - writer.writeAttribute("sampleCount", soundSampleCount); + convertSoundMedia(swf, tags, (DefineSoundTag) symbol, writer, files); boolean linkageExportForAS = false; if (characterClasses.containsKey(symbol.getCharacterId())) { @@ -2314,6 +2320,35 @@ public class XFLConverter { } } + for (Tag t : tags) { + if (t instanceof SoundStreamHeadTypeTag) { + SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) t; + for (SoundStreamFrameRange range : head.getRanges()) { + statusStack.pushStatus(range.toString()); + convertSoundMedia(swf, tags, range, writer, files); + writer.writeEndElement(); + mediaCount++; + statusStack.popStatus(); + } + } + if (t instanceof DefineSpriteTag) { + DefineSpriteTag sprite = (DefineSpriteTag) t; + for (Tag st : sprite.getTags()) { + if (st instanceof SoundStreamHeadTypeTag) { + SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) st; + for (SoundStreamFrameRange range : head.getRanges()) { + statusStack.pushStatus(range.toString()); + convertSoundMedia(swf, sprite.getTags(), range, writer, files); + writer.writeEndElement(); + mediaCount++; + statusStack.popStatus(); + } + break; + } + } + } + } + writer.writeEndElement(); } @@ -2420,7 +2455,7 @@ public class XFLConverter { } } - private static void convertFrame(boolean shapeTween, SoundStreamHeadTypeTag soundStreamHead, StartSoundTag startSound, int frame, int duration, String actionScript, String elements, HashMap files, XFLXmlWriter writer) throws XMLStreamException { + private static void convertFrame(Scene scene, boolean shapeTween, SoundStreamFrameRange soundStreamRange, StartSoundTag startSound, int frame, int duration, String actionScript, String elements, HashMap files, XFLXmlWriter writer) throws XMLStreamException { DefineSoundTag sound = null; if (startSound != null) { SWF swf = startSound.getSwf(); @@ -2438,13 +2473,14 @@ public class XFLConverter { } else { writer.writeAttribute("keyMode", KEY_MODE_NORMAL); } - if (soundStreamHead != null && startSound == null) { - String soundName = "sound" + soundStreamHead.getCharacterId() + "." + soundStreamHead.getExportFormat().toString().toLowerCase(); + if (soundStreamRange != null && startSound == null) { + String soundName = soundStreamRange.getFlaExportName() + "." + soundStreamRange.getExportFormat().toString().toLowerCase(); writer.writeAttribute("soundName", soundName); writer.writeAttribute("soundSync", "stream"); writer.writeStartElement("SoundEnvelope"); writer.writeEmptyElement("SoundEnvelopePoint", new String[]{"level0", "32768", "level1", "32768"}); writer.writeEndElement(); + } if (startSound != null && sound != null) { convertSoundUsage(writer, sound, startSound.soundInfo); @@ -2497,7 +2533,7 @@ public class XFLConverter { writer.writeEndElement(); } - private static void convertFrames(SWF swf, List onlyFrames, int startFrame, int endFrame, String prevStr, String afterStr, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, int depth, FLAVersion flaVersion, HashMap files, XFLXmlWriter writer, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { + private static void convertFrames(Scene scene, SWF swf, List onlyFrames, int startFrame, int endFrame, String prevStr, String afterStr, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, int depth, FLAVersion flaVersion, HashMap files, XFLXmlWriter writer, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { boolean lastIn = true; XFLXmlWriter writer2 = new XFLXmlWriter(); prevStr += ""; @@ -2669,7 +2705,7 @@ public class XFLConverter { SHAPEWITHSTYLE endShape = m.getShapeAtRatio(65535); //lastTweenRatio); convertShape(swf, characters, matrix, m.getShapeNum() == 1 ? 3 : 4, endShape.shapeRecords, m.getFillStyles().getFillStylesAt(lastTweenRatio), m.getLineStyles().getLineStylesAt(m.getShapeNum(), lastTweenRatio), true, false, addLastWriter); //duration--; - convertFrame(true, null, null, frame - duration, duration, "", lastElements, files, writer2); + convertFrame(scene, true, null, null, frame - duration, duration, "", lastElements, files, writer2); duration = 1; lastElements = addLastWriter.toString(); lastCharacter = m; @@ -2730,7 +2766,7 @@ public class XFLConverter { frame++; String elements = elementsWriter.toString(); if (!elements.equals(lastElements) && frame > 0) { - convertFrame(false, null, null, frame - duration, duration, "", lastElements, files, writer2); + convertFrame(scene, false, null, null, frame - duration, duration, "", lastElements, files, writer2); duration = 1; } else if (frame == 0) { duration = 1; @@ -2768,7 +2804,7 @@ public class XFLConverter { } if (!lastElements.isEmpty() || writer2.length() > 0) { frame++; - convertFrame(false, null, null, (frame - duration < 0 ? 0 : frame - duration), duration, "", lastElements, files, writer2); + convertFrame(scene, false, null, null, (frame - duration < 0 ? 0 : frame - duration), duration, "", lastElements, files, writer2); } afterStr = "" + afterStr; @@ -3140,13 +3176,12 @@ public class XFLConverter { return hasLabel; } - private void convertSoundLayer(ReadOnlyTagList timeLineTags, HashMap files, XFLXmlWriter writer) throws XMLStreamException { + private void convertSoundLayer(Scene scene, ReadOnlyTagList timeLineTags, HashMap files, XFLXmlWriter writer) throws XMLStreamException { int soundLayerIndex = 0; XFLXmlWriter writer2 = new XFLXmlWriter(); List startSounds = new ArrayList<>(); List startSoundFrameNumbers = new ArrayList<>(); - List soundStreamHeads = new ArrayList<>(); - List soundStreamHeadFrameNumbers = new ArrayList<>(); + List soundStreamRanges = new ArrayList<>(); int frame = 0; for (Tag t : timeLineTags) { if (t instanceof StartSoundTag) { @@ -3168,32 +3203,30 @@ public class XFLConverter { } } else if (t instanceof SoundStreamHeadTypeTag) { SoundStreamHeadTypeTag soundStreamHead = (SoundStreamHeadTypeTag) t; - if (!files.containsKey("sound" + soundStreamHead.getCharacterId() + "." + soundStreamHead.getExportFormat().toString().toLowerCase())) { //Sound was not exported - soundStreamHead = null; // ignore - } - - if (soundStreamHead != null) { - soundStreamHeads.add(soundStreamHead); - soundStreamHeadFrameNumbers.add(frame); + + for (SoundStreamFrameRange range : soundStreamHead.getRanges()) { + if (files.containsKey(range.getFlaExportName() + "." + soundStreamHead.getExportFormat().toString().toLowerCase())) { //Sound was really exported + soundStreamRanges.add(range); + } } } else if (t instanceof ShowFrameTag) { frame++; } } - for (int i = 0; i < soundStreamHeads.size(); i++) { + for (int i = 0; i < soundStreamRanges.size(); i++) { writer.writeStartElement("DOMLayer", new String[]{"name", "Sound Layer " + (soundLayerIndex++), "color", randomOutlineColor()}); writer.writeStartElement("frames"); - int startFrame = soundStreamHeadFrameNumbers.get(i); + int startFrame = soundStreamRanges.get(i).startFrame - scene.startFrame; int duration = frame - startFrame; if (startFrame != 0) { // empty frames should be added - convertFrame(false, null, null, 0, startFrame, "", "", files, writer); + convertFrame(scene, false, null, null, 0, startFrame, "", "", files, writer); } - convertFrame(false, soundStreamHeads.get(i), null, startFrame, duration, "", "", files, writer); + convertFrame(scene, false, soundStreamRanges.get(i), null, startFrame, duration, "", "", files, writer); writer.writeEndElement(); writer.writeEndElement(); @@ -3208,10 +3241,10 @@ public class XFLConverter { if (startFrame != 0) { // empty frames should be added - convertFrame(false, null, null, 0, startFrame, "", "", files, writer); + convertFrame(scene, false, null, null, 0, startFrame, "", "", files, writer); } - convertFrame(false, null, startSounds.get(i), startFrame, duration, "", "", files, writer); + convertFrame(scene, false, null, startSounds.get(i), startFrame, duration, "", "", files, writer); writer.writeEndElement(); writer.writeEndElement(); @@ -4076,7 +4109,7 @@ public class XFLConverter { "color", randomOutlineColor(), "layerType", "mask", "locked", "true"}); - convertFrames(swf, depthToFramesList.get(po.getDepth()), clipFrame, lastFrame, "", "", nonLibraryShapes, tags, sceneTimelineTags, characters, po.getDepth(), flaVersion, files, writer, multiUsageMorphShapes, statusStack); + convertFrames(scene, swf, depthToFramesList.get(po.getDepth()), clipFrame, lastFrame, "", "", nonLibraryShapes, tags, sceneTimelineTags, characters, po.getDepth(), flaVersion, files, writer, multiUsageMorphShapes, statusStack); writer.writeEndElement(); int parentIndex = index; @@ -4165,7 +4198,7 @@ public class XFLConverter { } for (int nd = po.getClipDepth() - 1; nd > po.getDepth(); nd--) { - boolean nonEmpty = writeLayer(swf, index, depthToFramesList.get(nd), nd, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, tags, sceneTimelineTags, characters, flaVersion, files, multiUsageMorphShapes, statusStack); + boolean nonEmpty = writeLayer(scene, swf, index, depthToFramesList.get(nd), nd, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, tags, sceneTimelineTags, characters, flaVersion, files, multiUsageMorphShapes, statusStack); for (int i = clipFrame; i <= lastFrame; i++) { depthToFramesList.get(nd).remove((Integer) i); } @@ -4179,7 +4212,7 @@ public class XFLConverter { } } - boolean nonEmpty = writeLayer(swf, index, depthToFramesList.get(d), d, 0, Integer.MAX_VALUE, -1, writer, nonLibraryShapes, tags, sceneTimelineTags, characters, flaVersion, files, multiUsageMorphShapes, statusStack); + boolean nonEmpty = writeLayer(scene, swf, index, depthToFramesList.get(d), d, 0, Integer.MAX_VALUE, -1, writer, nonLibraryShapes, tags, sceneTimelineTags, characters, flaVersion, files, multiUsageMorphShapes, statusStack); if (nonEmpty) { index++; } @@ -4190,7 +4223,7 @@ public class XFLConverter { index++; } - convertSoundLayer(sceneTimelineTags, files, writer); + convertSoundLayer(scene, sceneTimelineTags, files, writer); writer.writeEndElement(); //layers writer.writeEndElement(); //DOMTimeline } @@ -4218,7 +4251,7 @@ public class XFLConverter { writer.writeEndElement(); //DOMLayer } - private boolean writeLayer(SWF swf, int index, List onlyFrames, int d, int startFrame, int endFrame, int parentLayer, XFLXmlWriter writer, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, FLAVersion flaVersion, HashMap files, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { + private boolean writeLayer(Scene scene, SWF swf, int index, List onlyFrames, int d, int startFrame, int endFrame, int parentLayer, XFLXmlWriter writer, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, FLAVersion flaVersion, HashMap files, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { XFLXmlWriter layerPrev = new XFLXmlWriter(); statusStack.pushStatus("layer " + (index + 1)); //System.err.println("- writing layer " + (index + 1) + (startFrame == 0 && endFrame == Integer.MAX_VALUE ? ", all frames": ", frame " + startFrame + " to " + endFrame)); @@ -4237,7 +4270,7 @@ public class XFLConverter { layerPrev.writeCharacters(""); // todo honfika: hack to close start tag String layerAfter = ""; int prevLength = writer.length(); - convertFrames(swf, onlyFrames, startFrame, endFrame, layerPrev.toString(), layerAfter, nonLibraryShapes, tags, timelineTags, characters, d, flaVersion, files, writer, multiUsageMorphShapes, statusStack); + convertFrames(scene, swf, onlyFrames, startFrame, endFrame, layerPrev.toString(), layerAfter, nonLibraryShapes, tags, timelineTags, characters, d, flaVersion, files, writer, multiUsageMorphShapes, statusStack); statusStack.popStatus(); return writer.length() != prevLength; } diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index f0a2cc57e..6adc89523 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -192,6 +192,7 @@ import com.jpexs.decompiler.flash.tags.text.TextParseException; import com.jpexs.decompiler.flash.timeline.AS3Package; import com.jpexs.decompiler.flash.timeline.DepthState; import com.jpexs.decompiler.flash.timeline.Frame; +import com.jpexs.decompiler.flash.timeline.SoundStreamFrameRange; import com.jpexs.decompiler.flash.timeline.TagScript; import com.jpexs.decompiler.flash.timeline.Timeline; import com.jpexs.decompiler.flash.timeline.Timelined; @@ -2038,7 +2039,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se List sprites = new ArrayList<>(); List buttons = new ArrayList<>(); List movies = new ArrayList<>(); - List sounds = new ArrayList<>(); + List sounds = new ArrayList<>(); List texts = new ArrayList<>(); List as12scripts = new ArrayList<>(); List binaryData = new ArrayList<>(); @@ -2060,8 +2061,15 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se as12scripts.add(d); } } + + if (d instanceof SoundStreamHeadTypeTag) { + continue; + } - if (d instanceof Tag || d instanceof ASMSource || d instanceof BinaryDataInterface) { + if (d instanceof Tag + || d instanceof ASMSource + || d instanceof BinaryDataInterface + || d instanceof SoundStreamFrameRange) { TreeNodeType nodeType = TagTree.getTreeNodeType(d); if (nodeType == TreeNodeType.IMAGE) { images.add((Tag) d); @@ -2090,7 +2098,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se movies.add((Tag) d); } if (nodeType == TreeNodeType.SOUND) { - sounds.add((Tag) d); + sounds.add((SoundTag) d); } if (nodeType == TreeNodeType.BINARY_DATA) { binaryData.add((BinaryDataInterface) d); @@ -2183,7 +2191,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } if (export.isOptionEnabled(SoundExportMode.class)) { - ret.addAll(new SoundExporter().exportSounds(handler, selFile2 + File.separator + SoundExportSettings.EXPORT_FOLDER_NAME, new ReadOnlyTagList(sounds), + ret.addAll(new SoundExporter().exportSounds(handler, selFile2 + File.separator + SoundExportSettings.EXPORT_FOLDER_NAME, sounds, new SoundExportSettings(export.getValue(SoundExportMode.class)), evl)); } @@ -5554,15 +5562,17 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se previewPanel.showImagePanel(timelinedContainer, swf, frame, true, Configuration.autoPlayPreviews.get(), !Configuration.animateSubsprites.get(), false, !Configuration.playFrameSounds.get(), true, false); } else if ((treeItem instanceof SoundTag)) { //&& isInternalFlashViewerSelected() && (Arrays.asList("mp3", "wav").contains(((SoundTag) tagObj).getExportFormat())))) { previewPanel.showImagePanel(new SerializableImage(View.loadImage("sound32"))); - previewPanel.setImageReplaceButtonVisible(false, false, false, !((Tag) treeItem).isReadOnly() && ((SoundTag) treeItem).importSupported(), false, false, false); - try { - SoundTagPlayer soundThread = new SoundTagPlayer(null, (SoundTag) treeItem, Configuration.loopMedia.get() ? Integer.MAX_VALUE : 1, true); - if (!Configuration.autoPlaySounds.get()) { - soundThread.pause(); + previewPanel.setImageReplaceButtonVisible(false, false, false, !((SoundTag) treeItem).isReadOnly() && ((SoundTag) treeItem).importSupported(), false, false, false); + if (!(treeItem instanceof SoundStreamHeadTypeTag)) { + try { + SoundTagPlayer soundThread = new SoundTagPlayer(null, (SoundTag) treeItem, Configuration.loopMedia.get() ? Integer.MAX_VALUE : 1, true); + if (!Configuration.autoPlaySounds.get()) { + soundThread.pause(); + } + previewPanel.setMedia(soundThread); + } catch (LineUnavailableException | IOException | UnsupportedAudioFileException ex) { + logger.log(Level.SEVERE, null, ex); } - previewPanel.setMedia(soundThread); - } catch (LineUnavailableException | IOException | UnsupportedAudioFileException ex) { - logger.log(Level.SEVERE, null, ex); } } else if ((treeItem instanceof FontTag) && internalViewer) { previewPanel.showFontPanel((FontTag) treeItem); diff --git a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java index 4063fc44d..9420a0dcd 100644 --- a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java @@ -1529,6 +1529,7 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel public void showImagePanel(SerializableImage image) { showCardLeft(DRAW_PREVIEW_CARD); + imageTransformButton.setVisible(false); parametersPanel.setVisible(false); imagePlayControls.setMedia(imagePanel); imagePanel.setImage(image); diff --git a/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java b/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java index a6a48d1a4..5e009cc49 100644 --- a/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java +++ b/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java @@ -187,7 +187,7 @@ public class SoundTagPlayer implements MediaDisplay { } private void openSound(SOUNDINFO soundInfo, SoundTag tag) throws IOException, LineUnavailableException, UnsupportedAudioFileException { - SWF swf = ((Tag) tag).getSwf(); + SWF swf = (SWF) tag.getOpenable(); wavData = swf.getFromCache(soundInfo, tag); if (wavData == null) { List soundData = tag.getRawSoundData(); diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java b/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java index 0a4091cf1..a067b7c6a 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java @@ -80,6 +80,7 @@ import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.RemoveTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; import com.jpexs.decompiler.flash.tags.base.SymbolClassTypeTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.tags.gfx.DefineCompactedFont; @@ -89,6 +90,7 @@ import com.jpexs.decompiler.flash.timeline.AS2Package; import com.jpexs.decompiler.flash.timeline.AS3Package; import com.jpexs.decompiler.flash.timeline.Frame; import com.jpexs.decompiler.flash.timeline.FrameScript; +import com.jpexs.decompiler.flash.timeline.SoundStreamFrameRange; import com.jpexs.decompiler.flash.timeline.TagScript; import com.jpexs.decompiler.flash.treeitems.FolderItem; import com.jpexs.decompiler.flash.treeitems.HeaderItem; @@ -174,6 +176,11 @@ public abstract class AbstractTagTree extends JTree { } public static Icon getIconFor(TreeItem val, boolean folderExpanded) { + + if (val instanceof SoundStreamHeadTypeTag) { + return View.getIcon("foldersounds16"); + } + TreeNodeType type = getTreeNodeType(val); if (type == TreeNodeType.FOLDER && folderExpanded) { @@ -289,7 +296,8 @@ public abstract class AbstractTagTree extends JTree { || (t instanceof SoundStreamHeadTag) || (t instanceof SoundStreamHead2Tag) || (t instanceof DefineExternalSound) - || (t instanceof DefineExternalStreamSound)) { + || (t instanceof DefineExternalStreamSound) + || (t instanceof SoundStreamFrameRange)) { return TreeNodeType.SOUND; } @@ -595,7 +603,11 @@ public abstract class AbstractTagTree extends JTree { } } - if (d instanceof Tag || d instanceof ASMSource || d instanceof BinaryDataInterface) { + if (d instanceof Tag + || d instanceof ASMSource + || d instanceof BinaryDataInterface + || d instanceof SoundStreamFrameRange + ) { TreeNodeType nodeType = TagTree.getTreeNodeType(d); if (nodeType == TreeNodeType.IMAGE) { ret.add(d); diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index 5539edc98..81c0aec25 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -1180,7 +1180,7 @@ public class TagTreeContextMenu extends JPopupMenu { replaceMenuItem.setVisible(true); } - if (canReplace.test(it -> it instanceof SoundTag)) { + if (canReplace.test(it -> it instanceof SoundTag && ((SoundTag) it).importSupported())) { replaceMenuItem.setVisible(true); } diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java index 6ad49942f..2a20ed879 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java @@ -40,6 +40,7 @@ import com.jpexs.decompiler.flash.timeline.AS2Package; import com.jpexs.decompiler.flash.timeline.AS3Package; import com.jpexs.decompiler.flash.timeline.Frame; import com.jpexs.decompiler.flash.timeline.FrameScript; +import com.jpexs.decompiler.flash.timeline.SoundStreamFrameRange; import com.jpexs.decompiler.flash.timeline.TagScript; import com.jpexs.decompiler.flash.timeline.Timeline; import com.jpexs.decompiler.flash.timeline.Timelined; @@ -264,8 +265,8 @@ public class TagTreeModel extends AbstractTagTreeModel { for (int i = sounds.size() - 1; i >= 0; i--) { TreeItem sound = sounds.get(i); if (sound instanceof SoundStreamHeadTypeTag) { - List blocks = ((SoundStreamHeadTypeTag) sound).getBlocks(); - if (blocks == null || blocks.isEmpty()) { + List ranges = ((SoundStreamHeadTypeTag) sound).getRanges(); + if (ranges == null || ranges.isEmpty()) { sounds.remove(i); } } @@ -580,6 +581,9 @@ public class TagTreeModel extends AbstractTagTreeModel { } else if (parentNode instanceof ABC) { ClassesListTreeModel classesTreeModel = getClassesListTreeModel((ABC) parentNode); return classesTreeModel.getAllChildren(classesTreeModel.getRoot()); + } else if (parentNode instanceof SoundStreamHeadTypeTag) { + SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) parentNode; + return head.getRanges(); } return result; @@ -662,7 +666,10 @@ public class TagTreeModel extends AbstractTagTreeModel { } else if (parentNode instanceof ABC) { ClassesListTreeModel classesTreeModel = getClassesListTreeModel((ABC) parentNode); return classesTreeModel.getChild(classesTreeModel.getRoot(), index); - } + } else if (parentNode instanceof SoundStreamHeadTypeTag) { + SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) parentNode; + return head.getRanges().get(index); + } throw new Error("Unsupported parent type: " + parentNode.getClass().getName()); } @@ -713,6 +720,9 @@ public class TagTreeModel extends AbstractTagTreeModel { } else if (parentNode instanceof ABC) { ClassesListTreeModel classesTreeModel = getClassesListTreeModel((ABC) parentNode); return classesTreeModel.getChildCount(classesTreeModel.getRoot()); + } else if (parentNode instanceof SoundStreamHeadTypeTag) { + SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) parentNode; + return head.getRanges().size(); } return 0; @@ -787,6 +797,9 @@ public class TagTreeModel extends AbstractTagTreeModel { } else if (parentNode instanceof ABC) { ClassesListTreeModel classesTreeModel = getClassesListTreeModel((ABC) parentNode); return indexOfAdd(baseIndex, classesTreeModel.getIndexOfChild(classesTreeModel.getRoot(), childNode)); + } else if (parentNode instanceof SoundStreamHeadTypeTag) { + SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) parentNode; + return indexOfAdd(baseIndex, head.getRanges().indexOf(childNode)); } return -1;