From 306abf5e4a25ba5e2d01f3b64f09b6e0235b861e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Tue, 27 Dec 2022 08:31:19 +0100 Subject: [PATCH] Refactor sound importer to separate class --- .../flash/importers/SoundImporter.java | 463 ++++++++++++++++++ .../decompiler/flash/tags/DefineSoundTag.java | 185 +------ .../flash/tags/SoundStreamHead2Tag.java | 12 +- .../flash/tags/SoundStreamHeadTag.java | 12 +- .../tags/base/SoundStreamHeadTypeTag.java | 301 +----------- .../decompiler/flash/tags/base/SoundTag.java | 14 +- .../console/CommandLineArgumentParser.java | 5 +- .../jpexs/decompiler/flash/gui/MainPanel.java | 5 +- 8 files changed, 520 insertions(+), 477 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SoundImporter.java 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 new file mode 100644 index 000000000..a53568020 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SoundImporter.java @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2010-2022 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.importers; + +import com.jpexs.decompiler.flash.ReadOnlyTagList; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.tags.DefineSoundTag; +import com.jpexs.decompiler.flash.tags.ShowFrameTag; +import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag; +import com.jpexs.decompiler.flash.tags.Tag; +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.Timelined; +import com.jpexs.decompiler.flash.types.sound.MP3FRAME; +import com.jpexs.decompiler.flash.types.sound.MP3SOUNDDATA; +import com.jpexs.decompiler.flash.types.sound.SoundFormat; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; + +/** + * + * @author JPEXS + */ +public class SoundImporter { + public boolean importDefineSound(DefineSoundTag soundTag, InputStream is, int newSoundFormat) throws SoundImportException { + int newSoundRate = -1; + boolean newSoundSize = false; + boolean newSoundType = false; + long newSoundSampleCount = -1; + byte[] newSoundData; + switch (newSoundFormat) { + case SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN: + try (AudioInputStream audioIs = AudioSystem.getAudioInputStream(new BufferedInputStream(is))) { + AudioFormat fmt = audioIs.getFormat(); + newSoundType = fmt.getChannels() == 2; + newSoundSize = fmt.getSampleSizeInBits() == 16; + newSoundSampleCount = audioIs.getFrameLength(); + newSoundData = Helper.readStream(audioIs); + newSoundRate = (int) Math.round(fmt.getSampleRate()); + switch (newSoundRate) { + case 5512: + newSoundRate = 0; + break; + case 11025: + newSoundRate = 1; + break; + case 22050: + newSoundRate = 2; + break; + case 44100: + newSoundRate = 3; + break; + default: + throw new UnsupportedSamplingRateException(newSoundRate, new int[]{5512,11025,22050,44100}); + } + } catch (UnsupportedAudioFileException | IOException ex) { + return false; + } + break; + case SoundFormat.FORMAT_MP3: + BufferedInputStream bis = new BufferedInputStream(is); + loadID3v2(bis); + byte[] mp3data = Helper.readStream(bis); + + final int ID3_V1_LENTGH = 128; + final int ID3_V1_EXT_LENGTH = 227; + + if (mp3data.length > ID3_V1_LENTGH) { + //ID3v1 + if (mp3data[mp3data.length - ID3_V1_LENTGH] == 'T' && mp3data[mp3data.length - ID3_V1_LENTGH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_LENTGH + 2] == 'G') { + mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_LENTGH); + if (mp3data.length > ID3_V1_EXT_LENGTH) { + //ID3v1 extended + if (mp3data[mp3data.length - ID3_V1_EXT_LENGTH] == 'T' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 2] == 'G' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 3] == '+') { + mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_EXT_LENGTH); + } + } + } + } + try { + MP3SOUNDDATA snd = new MP3SOUNDDATA(new SWFInputStream(soundTag.getSwf(), mp3data), true); + if (!snd.frames.isEmpty()) { + MP3FRAME fr = snd.frames.get(0); + newSoundRate = fr.getSamplingRate(); + switch (newSoundRate) { + case 11025: + newSoundRate = 1; + break; + case 22050: + newSoundRate = 2; + break; + case 44100: + newSoundRate = 3; + break; + default: + throw new UnsupportedSamplingRateException(newSoundRate, new int[]{11025,22050,44100}); + } + + newSoundSize = true; + newSoundType = fr.isStereo(); + int len = snd.sampleCount(); + if (fr.isStereo()) { + len = len / 2; + } + + newSoundSampleCount = len; + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(baos, SWF.DEFAULT_VERSION, null); + sos.writeSI16(0); //Latency - how to calculate it? + sos.write(mp3data); + newSoundData = baos.toByteArray(); + } catch (IOException ex) { + return false; + } + break; + default: + return false; + } + if (newSoundData != null) { + soundTag.setSoundSize(newSoundSize); + soundTag.setSoundRate(newSoundRate); + soundTag.setSoundSampleCount(newSoundSampleCount); + soundTag.soundData = new ByteArrayRange(newSoundData); + soundTag.setSoundType(newSoundType); + soundTag.setSoundCompression(newSoundFormat); + soundTag.setModified(true); + return true; + } + return false; + } + + private void loadID3v2(InputStream in) { + int size = -1; + try { + // Read ID3v2 header (10 bytes). + in.mark(10); + size = readID3v2Header(in); + } catch (IOException e) { + } finally { + try { + // Unread ID3v2 header (10 bytes). + in.reset(); + } catch (IOException e) { + } + } + // Load ID3v2 tags. + try { + if (size > 0) { + byte[] rawid3v2 = new byte[size]; + in.read(rawid3v2, 0, rawid3v2.length); + } + } catch (IOException e) { + } + } + + /** + * Parse ID3v2 tag header to find out size of ID3v2 frames. + * + * @param in MP3 InputStream + * @return size of ID3v2 frames + header + * @throws IOException + * @author JavaZOOM + */ + private int readID3v2Header(InputStream in) throws IOException { + byte[] id3header = new byte[4]; + int size = -10; + in.read(id3header, 0, 3); + // Look for ID3v2 + if ((id3header[0] == 'I') && (id3header[1] == 'D') && (id3header[2] == '3')) { + in.read(id3header, 0, 3); + int majorVersion = id3header[0]; + int revision = id3header[1]; + in.read(id3header, 0, 4); + size = (int) (id3header[0] << 21) + (id3header[1] << 14) + (id3header[2] << 7) + (id3header[3]); + } + return (size + 10); + } + + public boolean importSoundStream(SoundStreamHeadTypeTag streamHead, InputStream is, int newSoundFormat) throws UnsupportedSamplingRateException { + List mp3Frames = null; + int newSoundRate = -1; + boolean newSoundSize = false; + boolean newSoundType = false; + long newSoundSampleCount = -1; + byte[] uncompressedSoundData = null; + int bytesPerSwfFrame = -1; + SWF swf = streamHead.getSwf(); + int sampleLen = 0; + int soundRateHz = 0; + switch (newSoundFormat) { + case SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN: + try (AudioInputStream audioIs = AudioSystem.getAudioInputStream(new BufferedInputStream(is))) { + AudioFormat fmt = audioIs.getFormat(); + newSoundType = fmt.getChannels() == 2; + newSoundSize = fmt.getSampleSizeInBits() == 16; + //newSoundSampleCount = audioIs.getFrameLength(); + uncompressedSoundData = Helper.readStream(audioIs); + sampleLen = (newSoundType ? 2 : 1) * (newSoundSize ? 2 : 1); + soundRateHz = (int) Math.round(fmt.getSampleRate()); + newSoundSampleCount = (int) Math.ceil(soundRateHz / swf.frameRate); + + bytesPerSwfFrame = (int) Math.ceil(soundRateHz / swf.frameRate) * sampleLen; + switch (soundRateHz) { + case 5512: + newSoundRate = 0; + break; + case 11025: + newSoundRate = 1; + break; + case 22050: + newSoundRate = 2; + break; + case 44100: + newSoundRate = 3; + break; + default: + throw new UnsupportedSamplingRateException(newSoundRate, new int[]{5512, 11025, 22050, 44100}); + } + + } catch (UnsupportedAudioFileException | IOException ex) { + return false; + } + break; + case SoundFormat.FORMAT_MP3: + BufferedInputStream bis = new BufferedInputStream(is); + loadID3v2(bis); + byte[] mp3data = Helper.readStream(bis); + + final int ID3_V1_LENTGH = 128; + final int ID3_V1_EXT_LENGTH = 227; + + if (mp3data.length > ID3_V1_LENTGH) { + //ID3v1 + if (mp3data[mp3data.length - ID3_V1_LENTGH] == 'T' && mp3data[mp3data.length - ID3_V1_LENTGH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_LENTGH + 2] == 'G') { + mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_LENTGH); + if (mp3data.length > ID3_V1_EXT_LENGTH) { + //ID3v1 extended + if (mp3data[mp3data.length - ID3_V1_EXT_LENGTH] == 'T' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 2] == 'G' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 3] == '+') { + mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_EXT_LENGTH); + } + } + } + } + try { + MP3SOUNDDATA snd = new MP3SOUNDDATA(new SWFInputStream(swf, mp3data), true); + if (!snd.frames.isEmpty()) { + MP3FRAME fr = snd.frames.get(0); + soundRateHz = fr.getSamplingRate(); + switch (soundRateHz) { + case 11025: + newSoundRate = 1; + break; + case 22050: + newSoundRate = 2; + break; + case 44100: + newSoundRate = 3; + break; + default: + throw new UnsupportedSamplingRateException(newSoundRate, new int[]{11025, 22050, 44100}); + } + + newSoundSize = true; + newSoundType = fr.isStereo(); + /*int len = snd.sampleCount(); + if (fr.isStereo()) { + len = len / 2; + }*/ + + newSoundSampleCount = (int) Math.ceil(soundRateHz / swf.frameRate); + //newSoundSampleCount = len; + } + + mp3Frames = snd.frames; + } catch (IOException ex) { + return false; + } + break; + default: + return false; + } + + ByteArrayInputStream bais = uncompressedSoundData == null ? null : new ByteArrayInputStream(uncompressedSoundData); + + List existingBlocks = streamHead.getBlocks(); + int startFrame = 0; + Timelined timelined = streamHead.getTimelined(); + if (!existingBlocks.isEmpty()) { + ReadOnlyTagList tags = timelined.getTags(); + for (Tag t : tags) { + if (t instanceof ShowFrameTag) { + startFrame++; + } + if (t instanceof SoundStreamBlockTag) { + break; + } + } + } + for (SoundStreamBlockTag block : existingBlocks) { + timelined.removeTag(block); + } + + List blocks = new ArrayList<>(); + if (bais != null) { //Uncompressed + DataInputStream dais = new DataInputStream(bais); + long pos = 0; + int frame = 0; + long lastNumSamplesLong = 0; + try { + while (dais.available() > 0) { + + float timeAfterFrame = (frame + 1) / swf.frameRate; + float numSamplesAfterFrame = (frame + 1) * soundRateHz / swf.frameRate; + + long numSamplesAfterFrameLong = (long) Math.ceil(numSamplesAfterFrame); + + long deltaNumSamples = numSamplesAfterFrameLong - lastNumSamplesLong; + + lastNumSamplesLong = numSamplesAfterFrameLong; + + if (deltaNumSamples > 0) { + byte buf[] = new byte[(int) deltaNumSamples * sampleLen]; + dais.readFully(buf); + SoundStreamBlockTag block = new SoundStreamBlockTag(swf); + block.streamSoundData = new ByteArrayRange(buf); + blocks.add(block); + } else { + SoundStreamBlockTag block = new SoundStreamBlockTag(swf); + block.streamSoundData = new ByteArrayRange(new byte[0]); + blocks.add(block); + } + frame++; + } + } catch (IOException ex) { + //ignore + } + } + if (mp3Frames != null) { + + int frame = 0; + + int mp3FrameNum = 0; + long lastNumSamplesLong = 0; + while (mp3FrameNum < mp3Frames.size()) { + float timeAfterFrame = (frame + 1) / swf.frameRate; + float numSamplesAfterFrame = (frame + 1) * soundRateHz / swf.frameRate; + long numSamplesBeforeFrameLong = Math.round(frame * soundRateHz / swf.frameRate); + + int seekSamples = (int) (lastNumSamplesLong - numSamplesBeforeFrameLong); + + SoundStreamBlockTag block = new SoundStreamBlockTag(swf); + + List blockMp3Frames = new ArrayList<>(); + int blockSamples = 0; + while(lastNumSamplesLong < numSamplesAfterFrame && mp3FrameNum < mp3Frames.size()) { + MP3FRAME mp3Frame = mp3Frames.get(mp3FrameNum); + lastNumSamplesLong += mp3Frame.getSampleCount(); + blockSamples += mp3Frame.getSampleCount(); + blockMp3Frames.add(mp3Frame); + mp3FrameNum++; + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(baos, SWF.DEFAULT_VERSION, null); + try { + sos.writeUI16(blockSamples); + sos.writeSI16(seekSamples); + for (MP3FRAME mp3Frame:blockMp3Frames) { + sos.write(mp3Frame.getBytes()); + } + } catch (IOException ex) { + Logger.getLogger(SoundStreamHeadTypeTag.class.getName()).log(Level.SEVERE, null, ex); + } + block.streamSoundData = new ByteArrayRange(baos.toByteArray()); + blocks.add(block); + frame++; + } + } + + ReadOnlyTagList tags = timelined.getTags(); + int frame = -1; + for (int i = 0; i < tags.size(); i++) { + Tag t = tags.get(i); + if (t instanceof ShowFrameTag) { + frame++; + if (frame >= startFrame && !blocks.isEmpty()) { + SoundStreamBlockTag block = blocks.remove(0); + block.setTimelined(timelined); + timelined.addTag(i, block); + tags = timelined.getTags(); + i++; + } + } + } + + int framesBefore = timelined.getFrameCount(); + //enlarge timeline when necessary + while (!blocks.isEmpty()) { + SoundStreamBlockTag block = blocks.remove(0); + block.setTimelined(timelined); + timelined.addTag(block); + ShowFrameTag sft = new ShowFrameTag(swf); + sft.setTimelined(timelined); + timelined.addTag(sft); + framesBefore++; + } + timelined.setFrameCount(framesBefore); + streamHead.setSoundCompression(newSoundFormat); + streamHead.setSoundSampleCount((int) newSoundSampleCount); + streamHead.setSoundSize(newSoundSize); + streamHead.setSoundType(newSoundType); + streamHead.setSoundRate(newSoundRate); + + streamHead.setModified(true); + timelined.resetTimeline(); + swf.resetTimeline(); //to reload blocks + return true; + } + + public boolean importSound(SoundTag soundTag, InputStream is, int newSoundFormat) throws SoundImportException { + if (soundTag instanceof DefineSoundTag) { + return importDefineSound((DefineSoundTag)soundTag, is, newSoundFormat); + } + if (soundTag instanceof SoundStreamHeadTypeTag) { + return importSoundStream((SoundStreamHeadTypeTag)soundTag, is, newSoundFormat); + } + return false; + } +} 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 45718ec49..be3a0d38b 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 @@ -165,165 +165,7 @@ public class DefineSoundTag extends CharacterTag implements SoundTag { } return SoundExportFormat.FLV; } - - private void loadID3v2(InputStream in) { - int size = -1; - try { - // Read ID3v2 header (10 bytes). - in.mark(10); - size = readID3v2Header(in); - } catch (IOException e) { - } finally { - try { - // Unread ID3v2 header (10 bytes). - in.reset(); - } catch (IOException e) { - } - } - // Load ID3v2 tags. - try { - if (size > 0) { - byte[] rawid3v2 = new byte[size]; - in.read(rawid3v2, 0, rawid3v2.length); - } - } catch (IOException e) { - } - } - - /** - * Parse ID3v2 tag header to find out size of ID3v2 frames. - * - * @param in MP3 InputStream - * @return size of ID3v2 frames + header - * @throws IOException - * @author JavaZOOM - */ - private int readID3v2Header(InputStream in) throws IOException { - byte[] id3header = new byte[4]; - int size = -10; - in.read(id3header, 0, 3); - // Look for ID3v2 - if ((id3header[0] == 'I') && (id3header[1] == 'D') && (id3header[2] == '3')) { - in.read(id3header, 0, 3); - int majorVersion = id3header[0]; - int revision = id3header[1]; - in.read(id3header, 0, 4); - size = (int) (id3header[0] << 21) + (id3header[1] << 14) + (id3header[2] << 7) + (id3header[3]); - } - return (size + 10); - } - - @Override - public boolean setSound(InputStream is, int newSoundFormat) throws UnsupportedSamplingRateException { - int newSoundRate = -1; - boolean newSoundSize = false; - boolean newSoundType = false; - long newSoundSampleCount = -1; - byte[] newSoundData; - switch (newSoundFormat) { - case SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN: - try (AudioInputStream audioIs = AudioSystem.getAudioInputStream(new BufferedInputStream(is))) { - AudioFormat fmt = audioIs.getFormat(); - newSoundType = fmt.getChannels() == 2; - newSoundSize = fmt.getSampleSizeInBits() == 16; - newSoundSampleCount = audioIs.getFrameLength(); - newSoundData = Helper.readStream(audioIs); - newSoundRate = (int) Math.round(fmt.getSampleRate()); - switch (newSoundRate) { - case 5512: - newSoundRate = 0; - break; - case 11025: - newSoundRate = 1; - break; - case 22050: - newSoundRate = 2; - break; - case 44100: - newSoundRate = 3; - break; - default: - throw new UnsupportedSamplingRateException(newSoundRate, new int[]{5512,11025,22050,44100}); - } - } catch (UnsupportedAudioFileException | IOException ex) { - return false; - } - break; - case SoundFormat.FORMAT_MP3: - BufferedInputStream bis = new BufferedInputStream(is); - loadID3v2(bis); - byte[] mp3data = Helper.readStream(bis); - - final int ID3_V1_LENTGH = 128; - final int ID3_V1_EXT_LENGTH = 227; - - if (mp3data.length > ID3_V1_LENTGH) { - //ID3v1 - if (mp3data[mp3data.length - ID3_V1_LENTGH] == 'T' && mp3data[mp3data.length - ID3_V1_LENTGH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_LENTGH + 2] == 'G') { - mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_LENTGH); - if (mp3data.length > ID3_V1_EXT_LENGTH) { - //ID3v1 extended - if (mp3data[mp3data.length - ID3_V1_EXT_LENGTH] == 'T' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 2] == 'G' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 3] == '+') { - mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_EXT_LENGTH); - } - } - } - } - try { - MP3SOUNDDATA snd = new MP3SOUNDDATA(new SWFInputStream(swf, mp3data), true); - if (!snd.frames.isEmpty()) { - MP3FRAME fr = snd.frames.get(0); - newSoundRate = fr.getSamplingRate(); - switch (newSoundRate) { - case 11025: - newSoundRate = 1; - break; - case 22050: - newSoundRate = 2; - break; - case 44100: - newSoundRate = 3; - break; - default: - throw new UnsupportedSamplingRateException(newSoundRate, new int[]{11025,22050,44100}); - } - - newSoundSize = true; - newSoundType = fr.isStereo(); - int len = snd.sampleCount(); - if (fr.isStereo()) { - len = len / 2; - } - - newSoundSampleCount = len; - } - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(baos, SWF.DEFAULT_VERSION, getCharset()); - sos.writeSI16(0); //Latency - how to calculate it? - sos.write(mp3data); - newSoundData = baos.toByteArray(); - } catch (IOException ex) { - return false; - } - break; - default: - return false; - } - if (newSoundData != null) { - this.soundSize = newSoundSize; - this.soundRate = newSoundRate; - this.soundSampleCount = newSoundSampleCount; - this.soundData = new ByteArrayRange(newSoundData); - this.soundType = newSoundType; - this.soundFormat = newSoundFormat; - setModified(true); - return true; - } - return false; - - } - + @Override public boolean importSupported() { return true; @@ -382,4 +224,29 @@ public class DefineSoundTag extends CharacterTag implements SoundTag { tagInfo.addInfo("general", "stereo", soundFormat.stereo); tagInfo.addInfo("general", "sampleCount", soundSampleCount); } + + @Override + public void setSoundSize(boolean soundSize) { + this.soundSize = soundSize; + } + + @Override + public void setSoundType(boolean soundType) { + this.soundType = soundType; + } + + @Override + public void setSoundSampleCount(long soundSampleCount) { + this.soundSampleCount = soundSampleCount; + } + + @Override + public void setSoundCompression(int soundCompression) { + this.soundFormat = soundCompression; + } + + @Override + public void setSoundRate(int soundRate) { + this.soundRate = soundRate; + } } 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 e93c0ea1e..fe286db31 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 @@ -262,27 +262,27 @@ public class SoundStreamHead2Tag extends SoundStreamHeadTypeTag { //getNeededCharacters intentionally not defined @Override - protected void setSoundSize(boolean soundSize) { + public void setSoundSize(boolean soundSize) { this.streamSoundSize = soundSize; } @Override - protected void setSoundType(boolean soundType) { + public void setSoundType(boolean soundType) { this.streamSoundType = soundType; } @Override - protected void setSoundSampleCount(int soundSampleCount) { - this.streamSoundSampleCount = soundSampleCount; + public void setSoundSampleCount(long soundSampleCount) { + this.streamSoundSampleCount = (int) soundSampleCount; } @Override - protected void setSoundCompression(int soundCompression) { + public void setSoundCompression(int soundCompression) { this.streamSoundCompression = soundCompression; } @Override - protected void setSoundRate(int soundRate) { + public void setSoundRate(int soundRate) { this.streamSoundRate = soundRate; } } 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 51ed15ed2..41979f74b 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 @@ -272,27 +272,27 @@ public class SoundStreamHeadTag extends SoundStreamHeadTypeTag { //getNeededCharacters intentionally not defined @Override - protected void setSoundSize(boolean soundSize) { + public void setSoundSize(boolean soundSize) { this.streamSoundSize = soundSize; } @Override - protected void setSoundType(boolean soundType) { + public void setSoundType(boolean soundType) { this.streamSoundType = soundType; } @Override - protected void setSoundSampleCount(int soundSampleCount) { - this.streamSoundSampleCount = soundSampleCount; + public void setSoundSampleCount(long soundSampleCount) { + this.streamSoundSampleCount = (int) soundSampleCount; } @Override - protected void setSoundCompression(int soundCompression) { + public void setSoundCompression(int soundCompression) { this.streamSoundCompression = soundCompression; } @Override - protected void setSoundRate(int soundRate) { + public void setSoundRate(int soundRate) { this.streamSoundRate = soundRate; } } 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 513b0aaa0..a760835c3 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 @@ -63,305 +63,6 @@ public abstract class SoundStreamHeadTypeTag extends Tag implements CharacterIdT public abstract void setVirtualCharacterId(int ch); - public abstract List getBlocks(); - - protected abstract void setSoundSize(boolean soundSize); - - protected abstract void setSoundType(boolean soundType); - - protected abstract void setSoundSampleCount(int soundSampleCount); - - protected abstract void setSoundCompression(int soundCompression); - - protected abstract void setSoundRate(int soundRate); - - @Override - public boolean setSound(InputStream is, int newSoundFormat) throws UnsupportedSamplingRateException { - - List mp3Frames = null; - int newSoundRate = -1; - boolean newSoundSize = false; - boolean newSoundType = false; - long newSoundSampleCount = -1; - byte[] uncompressedSoundData = null; - int bytesPerSwfFrame = -1; - SWF swf = getSwf(); - int sampleLen = 0; - int soundRateHz = 0; - switch (newSoundFormat) { - case SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN: - try (AudioInputStream audioIs = AudioSystem.getAudioInputStream(new BufferedInputStream(is))) { - AudioFormat fmt = audioIs.getFormat(); - newSoundType = fmt.getChannels() == 2; - newSoundSize = fmt.getSampleSizeInBits() == 16; - //newSoundSampleCount = audioIs.getFrameLength(); - uncompressedSoundData = Helper.readStream(audioIs); - sampleLen = (newSoundType ? 2 : 1) * (newSoundSize ? 2 : 1); - soundRateHz = (int) Math.round(fmt.getSampleRate()); - newSoundSampleCount = (int) Math.ceil(soundRateHz / swf.frameRate); - - bytesPerSwfFrame = (int) Math.ceil(soundRateHz / swf.frameRate) * sampleLen; - switch (soundRateHz) { - case 5512: - newSoundRate = 0; - break; - case 11025: - newSoundRate = 1; - break; - case 22050: - newSoundRate = 2; - break; - case 44100: - newSoundRate = 3; - break; - default: - throw new UnsupportedSamplingRateException(newSoundRate, new int[]{5512, 11025, 22050, 44100}); - } - - } catch (UnsupportedAudioFileException | IOException ex) { - return false; - } - break; - case SoundFormat.FORMAT_MP3: - BufferedInputStream bis = new BufferedInputStream(is); - loadID3v2(bis); - byte[] mp3data = Helper.readStream(bis); - - final int ID3_V1_LENTGH = 128; - final int ID3_V1_EXT_LENGTH = 227; - - if (mp3data.length > ID3_V1_LENTGH) { - //ID3v1 - if (mp3data[mp3data.length - ID3_V1_LENTGH] == 'T' && mp3data[mp3data.length - ID3_V1_LENTGH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_LENTGH + 2] == 'G') { - mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_LENTGH); - if (mp3data.length > ID3_V1_EXT_LENGTH) { - //ID3v1 extended - if (mp3data[mp3data.length - ID3_V1_EXT_LENGTH] == 'T' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 2] == 'G' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 3] == '+') { - mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_EXT_LENGTH); - } - } - } - } - try { - MP3SOUNDDATA snd = new MP3SOUNDDATA(new SWFInputStream(swf, mp3data), true); - if (!snd.frames.isEmpty()) { - MP3FRAME fr = snd.frames.get(0); - soundRateHz = fr.getSamplingRate(); - switch (soundRateHz) { - case 11025: - newSoundRate = 1; - break; - case 22050: - newSoundRate = 2; - break; - case 44100: - newSoundRate = 3; - break; - default: - throw new UnsupportedSamplingRateException(newSoundRate, new int[]{11025, 22050, 44100}); - } - - newSoundSize = true; - newSoundType = fr.isStereo(); - /*int len = snd.sampleCount(); - if (fr.isStereo()) { - len = len / 2; - }*/ - - newSoundSampleCount = (int) Math.ceil(soundRateHz / swf.frameRate); - //newSoundSampleCount = len; - } - - mp3Frames = snd.frames; - } catch (IOException ex) { - return false; - } - break; - default: - return false; - } - - ByteArrayInputStream bais = uncompressedSoundData == null ? null : new ByteArrayInputStream(uncompressedSoundData); - - List existingBlocks = getBlocks(); - int startFrame = 0; - Timelined timelined = getTimelined(); - if (!existingBlocks.isEmpty()) { - ReadOnlyTagList tags = timelined.getTags(); - for (Tag t : tags) { - if (t instanceof ShowFrameTag) { - startFrame++; - } - if (t instanceof SoundStreamBlockTag) { - break; - } - } - } - for (SoundStreamBlockTag block : existingBlocks) { - timelined.removeTag(block); - } - - List blocks = new ArrayList<>(); - if (bais != null) { //Uncompressed - DataInputStream dais = new DataInputStream(bais); - long pos = 0; - int frame = 0; - long lastNumSamplesLong = 0; - try { - while (dais.available() > 0) { - - float timeAfterFrame = (frame + 1) / swf.frameRate; - float numSamplesAfterFrame = (frame + 1) * soundRateHz / swf.frameRate; - - long numSamplesAfterFrameLong = (long) Math.ceil(numSamplesAfterFrame); - - long deltaNumSamples = numSamplesAfterFrameLong - lastNumSamplesLong; - - lastNumSamplesLong = numSamplesAfterFrameLong; - - if (deltaNumSamples > 0) { - byte buf[] = new byte[(int) deltaNumSamples * sampleLen]; - dais.readFully(buf); - SoundStreamBlockTag block = new SoundStreamBlockTag(swf); - block.streamSoundData = new ByteArrayRange(buf); - blocks.add(block); - } else { - SoundStreamBlockTag block = new SoundStreamBlockTag(swf); - block.streamSoundData = new ByteArrayRange(new byte[0]); - blocks.add(block); - } - frame++; - } - } catch (IOException ex) { - //ignore - } - } - if (mp3Frames != null) { - - int frame = 0; - - int mp3FrameNum = 0; - long lastNumSamplesLong = 0; - while (mp3FrameNum < mp3Frames.size()) { - float timeAfterFrame = (frame + 1) / swf.frameRate; - float numSamplesAfterFrame = (frame + 1) * soundRateHz / swf.frameRate; - long numSamplesBeforeFrameLong = Math.round(frame * soundRateHz / swf.frameRate); - - int seekSamples = (int) (lastNumSamplesLong - numSamplesBeforeFrameLong); - - SoundStreamBlockTag block = new SoundStreamBlockTag(swf); - - List blockMp3Frames = new ArrayList<>(); - int blockSamples = 0; - while(lastNumSamplesLong < numSamplesAfterFrame && mp3FrameNum < mp3Frames.size()) { - MP3FRAME mp3Frame = mp3Frames.get(mp3FrameNum); - lastNumSamplesLong += mp3Frame.getSampleCount(); - blockSamples += mp3Frame.getSampleCount(); - blockMp3Frames.add(mp3Frame); - mp3FrameNum++; - } - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(baos, SWF.DEFAULT_VERSION, null); - try { - sos.writeUI16(blockSamples); - sos.writeSI16(seekSamples); - for (MP3FRAME mp3Frame:blockMp3Frames) { - sos.write(mp3Frame.getBytes()); - } - } catch (IOException ex) { - Logger.getLogger(SoundStreamHeadTypeTag.class.getName()).log(Level.SEVERE, null, ex); - } - block.streamSoundData = new ByteArrayRange(baos.toByteArray()); - blocks.add(block); - frame++; - } - } - - ReadOnlyTagList tags = timelined.getTags(); - int frame = -1; - for (int i = 0; i < tags.size(); i++) { - Tag t = tags.get(i); - if (t instanceof ShowFrameTag) { - frame++; - if (frame >= startFrame && !blocks.isEmpty()) { - SoundStreamBlockTag block = blocks.remove(0); - block.setTimelined(timelined); - timelined.addTag(i, block); - tags = timelined.getTags(); - i++; - } - } - } - - int framesBefore = timelined.getFrameCount(); - //enlarge timeline when necessary - while (!blocks.isEmpty()) { - SoundStreamBlockTag block = blocks.remove(0); - block.setTimelined(timelined); - timelined.addTag(block); - ShowFrameTag sft = new ShowFrameTag(swf); - sft.setTimelined(timelined); - timelined.addTag(sft); - framesBefore++; - } - timelined.setFrameCount(framesBefore); - setSoundCompression(newSoundFormat); - setSoundSampleCount((int) newSoundSampleCount); - setSoundSize(newSoundSize); - setSoundType(newSoundType); - setSoundRate(newSoundRate); - - setModified(true); - timelined.resetTimeline(); - swf.resetTimeline(); //to reload blocks - return true; - } - - private void loadID3v2(InputStream in) { - int size = -1; - try { - // Read ID3v2 header (10 bytes). - in.mark(10); - size = readID3v2Header(in); - } catch (IOException e) { - } finally { - try { - // Unread ID3v2 header (10 bytes). - in.reset(); - } catch (IOException e) { - } - } - // Load ID3v2 tags. - try { - if (size > 0) { - byte[] rawid3v2 = new byte[size]; - in.read(rawid3v2, 0, rawid3v2.length); - } - } catch (IOException e) { - } - } - - /** - * Parse ID3v2 tag header to find out size of ID3v2 frames. - * - * @param in MP3 InputStream - * @return size of ID3v2 frames + header - * @throws IOException - * @author JavaZOOM - */ - private int readID3v2Header(InputStream in) throws IOException { - byte[] id3header = new byte[4]; - int size = -10; - in.read(id3header, 0, 3); - // Look for ID3v2 - if ((id3header[0] == 'I') && (id3header[1] == 'D') && (id3header[2] == '3')) { - in.read(id3header, 0, 3); - int majorVersion = id3header[0]; - int revision = id3header[1]; - in.read(id3header, 0, 4); - size = (int) (id3header[0] << 21) + (id3header[1] << 14) + (id3header[2] << 7) + (id3header[3]); - } - return (size + 10); - } + public abstract List getBlocks(); } 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 b9cca2ec3..64137b79a 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 @@ -32,9 +32,7 @@ public interface SoundTag extends TreeItem { public SoundExportFormat getExportFormat(); public boolean importSupported(); - - public boolean setSound(InputStream is, int newSoundFormat) throws SoundImportException; - + public int getSoundRate(); public boolean getSoundType(); @@ -50,4 +48,14 @@ public interface SoundTag extends TreeItem { public String getCharacterExportFileName(); public SoundFormat getSoundFormat(); + + public void setSoundSize(boolean soundSize); + + public void setSoundType(boolean soundType); + + public void setSoundSampleCount(long soundSampleCount); + + public void setSoundCompression(int soundCompression); + + public void setSoundRate(int soundRate); } diff --git a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java index 266a0cb82..2c947fe2f 100644 --- a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java +++ b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java @@ -220,6 +220,7 @@ import com.jpexs.decompiler.flash.exporters.DualPdfGraphics2D; import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; import com.jpexs.decompiler.flash.gui.translator.Translator; import com.jpexs.decompiler.flash.importers.MovieImporter; +import com.jpexs.decompiler.flash.importers.SoundImporter; import com.jpexs.decompiler.flash.importers.SymbolClassImporter; import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; import com.jpexs.decompiler.flash.tags.base.HasSeparateAlphaChannel; @@ -3224,9 +3225,9 @@ public class CommandLineArgumentParser { } boolean ok = false; - + SoundImporter soundImporter = new SoundImporter(); try { - ok = st.setSound(new ByteArrayInputStream(data), soundFormat); + ok = soundImporter.importSound(st, new ByteArrayInputStream(data), soundFormat); } catch (UnsupportedSamplingRateException usre) { List supportedRatesStr = new ArrayList<>(); for (int i : usre.getSupportedRates()) { diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 4292a9243..f9c1f0242 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -127,6 +127,7 @@ import com.jpexs.decompiler.flash.importers.ImageImporter; import com.jpexs.decompiler.flash.importers.MovieImporter; import com.jpexs.decompiler.flash.importers.ScriptImporterProgressListener; import com.jpexs.decompiler.flash.importers.ShapeImporter; +import com.jpexs.decompiler.flash.importers.SoundImporter; import com.jpexs.decompiler.flash.importers.SwfXmlImporter; import com.jpexs.decompiler.flash.importers.SymbolClassImporter; import com.jpexs.decompiler.flash.importers.TextImporter; @@ -4069,10 +4070,12 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se if (selfile.getName().toLowerCase(Locale.ENGLISH).endsWith(".mp3")) { soundFormat = SoundFormat.FORMAT_MP3; } + + SoundImporter soundImporter = new SoundImporter(); boolean ok = false; try { - ok = st.setSound(new FileInputStream(selfile), soundFormat); + ok = soundImporter.importSound(st, new FileInputStream(selfile), soundFormat); ((Tag)st).getSwf().clearSoundCache(); } catch (IOException ex) { //ignore