diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/MovieImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/MovieImporter.java index 6d43b8c8a..27a0035ee 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/MovieImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/MovieImporter.java @@ -26,30 +26,101 @@ import com.jpexs.decompiler.flash.tags.PlaceObject2Tag; import com.jpexs.decompiler.flash.tags.ShowFrameTag; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.VideoFrameTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.timeline.Timelined; import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; import com.jpexs.helpers.Reference; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FilenameFilter; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; /** * * @author JPEXS */ public class MovieImporter { - public void importMovie(DefineVideoStreamTag movie, InputStream fis) throws IOException { + + public int bulkImport(File moviesDir, SWF swf, boolean printOut) { + Map characters = swf.getCharacters(); + int movieCount = 0; + List extensions = Arrays.asList("flv"); + File allFiles[] = moviesDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + String nameLower = name.toLowerCase(); + for (String ext : extensions) { + if (nameLower.endsWith("." + ext)) { + return true; + } + } + return false; + } + }); + for (int characterId : characters.keySet()) { + CharacterTag tag = characters.get(characterId); + if (tag instanceof DefineVideoStreamTag) { + DefineVideoStreamTag movieTag = (DefineVideoStreamTag) tag; + List existingFilesForMovieTag = new ArrayList<>(); + for (File f : allFiles) { + if (f.getName().startsWith("" + characterId + ".") || f.getName().startsWith("" + characterId + "_")) { + existingFilesForMovieTag.add(f); + } + } + existingFilesForMovieTag.sort(new Comparator() { + @Override + public int compare(File o1, File o2) { + String ext1 = o1.getName().substring(o1.getName().lastIndexOf(".") + 1); + String ext2 = o2.getName().substring(o2.getName().lastIndexOf(".") + 1); + int ret = extensions.indexOf(ext1) - extensions.indexOf(ext2); + if (ret == 0) { + return o1.getName().compareTo(o2.getName()); + } + return ret; + } + }); + + if (existingFilesForMovieTag.isEmpty()) { + continue; + } + + if (existingFilesForMovieTag.size() > 1) { + Logger.getLogger(MovieImporter.class.getName()).log(Level.WARNING, "Multiple matching files for movie tag {0} exists, {1} selected", new Object[]{characterId, existingFilesForMovieTag.get(0).getName()}); + } + File sourceFile = existingFilesForMovieTag.get(0); + + try { + if (printOut) { + System.out.println("Importing character " + characterId + " from file " + sourceFile.getName()); + } + importMovie(movieTag, Helper.readFile(sourceFile.getAbsolutePath())); + movieCount++; + } catch (IOException ex) { + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Cannot import movie " + characterId + " from file " + sourceFile.getName(), ex); + } + if (Thread.currentThread().isInterrupted()) { + break; + } + } + } + return movieCount; + } + + public void importMovie(DefineVideoStreamTag movie, byte[] data) throws IOException { List videoTags = new ArrayList<>(); - FLVInputStream flvIs = new FLVInputStream(fis); + FLVInputStream flvIs = new FLVInputStream(new ByteArrayInputStream(data)); Reference audioPresent = new Reference<>(false); Reference videoPresent = new Reference<>(false); flvIs.readHeader(audioPresent, videoPresent); diff --git a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java index fda1ac84f..b058fe9fe 100644 --- a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java +++ b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java @@ -592,6 +592,12 @@ public class CommandLineArgumentParser { out.println(" ...imports Symbol-Class mapping to and saves the result to "); } + if (filter == null || filter.equals("importmovies")) { + out.println(" " + (cnt++) + ") -importMovies "); + out.println(" ...imports movies to and saves the result to "); + } + + if (filter == null || filter.equals("importshapes")) { out.println(" " + (cnt++) + ") -importShapes [nofill] "); out.println(" ...imports shapes to and saves the result to "); @@ -1031,6 +1037,8 @@ public class CommandLineArgumentParser { parseDoc(args); } else if (command.equals("importsymbolclass")) { parseImportSymbolClass(args, charset); + } else if (command.equals("importmovies")) { + parseImportMovies(args, charset); } else if (command.equals("importshapes")) { parseImportShapes(args, charset); } else if (command.equals("importimages")) { @@ -3215,7 +3223,7 @@ public class CommandLineArgumentParser { } else if (characterTag instanceof DefineVideoStreamTag) { DefineVideoStreamTag movie = (DefineVideoStreamTag)characterTag; try { - new MovieImporter().importMovie(movie, new ByteArrayInputStream(data)); + new MovieImporter().importMovie(movie, data); } catch (IOException iex) { System.err.println("Import FAILED: "+iex.getMessage()); System.exit(1); @@ -3805,6 +3813,42 @@ public class CommandLineArgumentParser { System.exit(2); } } + + private static void parseImportMovies(Stack args, String charset) { + if (args.size() < 3) { + badArguments("importmovies"); + } + + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + + try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get(), charset); + System.out.println("Source file opened"); + String selFile = args.pop(); + + File moviesDir = new File(Path.combine(selFile, MovieExportSettings.EXPORT_FOLDER_NAME)); + if (moviesDir.exists()) { + System.out.println("Using the directory: " + moviesDir.getAbsolutePath()); + } else { + moviesDir = new File(selFile); + } + if (!moviesDir.exists()) { + System.err.println("Movies directory does not exist: " + moviesDir.getAbsolutePath()); + System.exit(1); + } + MovieImporter movieImporter = new MovieImporter(); + int movieCount = movieImporter.bulkImport(moviesDir, swf, true); + System.out.println("Writing outfile"); + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { + swf.saveTo(fos); + } + System.out.println("" + movieCount + " movies successfully imported"); + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during writing"); + System.exit(2); + } + } private static void parseImportImages(Stack args, String charset) { if (args.size() < 3) { diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 3ae6edd08..cf42a78e9 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -3067,7 +3067,60 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } public void importMovie(final SWF swf) { - + ViewMessages.showMessageDialog(MainPanel.this, translate("message.info.importMovies2"), translate("message.info"), JOptionPane.INFORMATION_MESSAGE, Configuration.showImportShapeInfo); + JFileChooser chooser = new JFileChooser(); + chooser.setCurrentDirectory(new File(Configuration.lastExportDir.get())); + chooser.setDialogTitle(translate("import.select.directory")); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setAcceptAllFileFilterUsed(false); + if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + String selFile = Helper.fixDialogFile(chooser.getSelectedFile()).getAbsolutePath(); + File moviesDir = new File(Path.combine(selFile, MovieExportSettings.EXPORT_FOLDER_NAME)); + if (!moviesDir.exists()) { + moviesDir = new File(selFile); + } + final File fMoviesDir = moviesDir; + MovieImporter movieImporter = new MovieImporter(); + + final long timeBefore = System.currentTimeMillis(); + new CancellableWorker() { + + private int count = 0; + + @Override + public Void doInBackground() throws Exception { + try { + count = movieImporter.bulkImport(fMoviesDir, swf, false); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Error during import", ex); + ViewMessages.showMessageDialog(null, translate("error.import") + ": " + ex.getClass().getName() + " " + ex.getLocalizedMessage()); + } + return null; + } + + @Override + protected void onStart() { + Main.startWork(translate("work.importing") + "...", this); + } + + @Override + protected void done() { + Main.stopWork(); + long timeAfter = System.currentTimeMillis(); + final long timeMs = timeAfter - timeBefore; + + View.execInEventDispatch(() -> { + refreshTree(swf); + setStatus(translate("import.finishedin").replace("%time%", Helper.formatTimeSec(timeMs))); + + ViewMessages.showMessageDialog(MainPanel.this, translate("import.movie.result").replace("%count%", Integer.toString(count))); + if (count != 0) { + reload(true); + } + }); + } + }.execute(); + } } public void importShape(final SWF swf, boolean noFill) { ViewMessages.showMessageDialog(MainPanel.this, translate("message.info.importShapes2"), translate("message.info"), JOptionPane.INFORMATION_MESSAGE, Configuration.showImportShapeInfo); @@ -4105,8 +4158,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se previewPanel.clear(); DefineVideoStreamTag movie = (DefineVideoStreamTag) item; File selfile = Helper.fixDialogFile(selectedFile); - try (FileInputStream fis = new FileInputStream(selfile)) { - new MovieImporter().importMovie(movie, fis); + try { + new MovieImporter().importMovie(movie, Helper.readFile(selfile.getAbsolutePath())); refreshTree(); } catch (IOException ex) { ViewMessages.showMessageDialog(MainPanel.this, translate("error.movie.invalid") + ": "+ex.getMessage(), translate("error"), JOptionPane.ERROR_MESSAGE); diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 2acd86a40..c1c51048a 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1091,4 +1091,11 @@ error.sound.rate = The input sound has unsupported sampling rate: %saplingRate%. filter.movies = Supported movie formats (%extensions%) error.movie.invalid = Invalid movie. -menu.file.import.movie = Import movies \ No newline at end of file +menu.file.import.movie = Import movies + +message.info.importMovies2 = During importing movies, you need to select a FOLDER.\r\n \ + Filenames inside the folder must match existing movies in current selected SWF.\r\n \ + If there exist "movies" folder inside, it is selected instead.\r\n \ + The best way to get the structure right is to export images in current SWF file first. + +import.movie.result = %count% movies imported. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index e1a1cbabc..0ae033f28 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -1071,4 +1071,16 @@ status.editing.autosave = Jste v EDITA\u010cN\u00cdM re\u017eimu. Prove\u010fte error.sound.rate = Vstupn\u00ed zvuk m\u00e1 nepodporovanou vzorkovac\u00ed frekvenci: %saplingRate%.\r\n\ P\u0159ed importem je t\u0159eba zvuk zkonvertovat n\u011bjak\u00fdm editorem zvuku\r\n\ - do jedn\u00e9 z podporovan\u00fdch frekvenc\u00ed (%supportedRates%). \ No newline at end of file + do jedn\u00e9 z podporovan\u00fdch frekvenc\u00ed (%supportedRates%). + +filter.movies = Podporovan\u00e9 form\u00e1ty videa (%extensions%) +error.movie.invalid = Neplatn\u00e9 video. + +menu.file.import.movie = Importovat videa + +message.info.importMovies2 = B\u011bhem importu vide\u00ed mus\u00edte vybrat SLO\u017dKU.\r\n \ + N\u00e1zvy soubor\u016f uvnit\u0159 mus\u00ed souhlasit s existuj\u00edc\u00edmi videi v pr\u00e1v\u011b vybran\u00e9m SWF.\r\n \ + Pokud je uvnit\u0159 slo\u017eka "movies", pak je vybr\u00e1na m\u00edsto n\u00ed.\r\n \ + Nejlep\u0161\u00ed zp\u016fsob jak m\u00edt tuto strukturu spr\u00e1vn\u011b je nejprve exportovat videa v aktu\u00e1ln\u00edm SWF souboru. + +import.movie.result = %count% vide\u00ed importov\u00e1no. \ No newline at end of file