diff --git a/src/application/Settings.java b/src/application/Settings.java index 461b35de..ada1353f 100644 --- a/src/application/Settings.java +++ b/src/application/Settings.java @@ -1776,6 +1776,20 @@ else if (eElement.getElementsByTagName("Name").item(0).getFirstChild().getTextCo { FFMPEG.PathToFFMPEG = txtCustomFFmpegPath.getText(); } + + //warning values + if (eElement.getElementsByTagName("Name").item(0).getFirstChild().getTextContent().equals("cutKeyframesIsDisplayed")) + { + Shutter.cutKeyframesIsDisplayed = true; + } + else if (eElement.getElementsByTagName("Name").item(0).getFirstChild().getTextContent().equals("rewrapKeyframesIsDisplayed")) + { + Shutter.rewrapKeyframesIsDisplayed = true; + } + else if (eElement.getElementsByTagName("Name").item(0).getFirstChild().getTextContent().equals("conformKeyframesIsDisplayed")) + { + Shutter.conformKeyframesIsDisplayed = true; + } } } } @@ -2361,8 +2375,82 @@ else if (p instanceof JPanel) cValue.appendChild(document.createTextNode(String.valueOf(videoWebCaseMetadata))); component.appendChild(cValue); - root.appendChild(component); + root.appendChild(component); + + //Saving warning values + Element warning = document.createElement("Warning"); + if (Shutter.cutKeyframesIsDisplayed) + { + component = document.createElement("Component"); + + //Type + cType = document.createElement("Type"); + cType.appendChild(document.createTextNode("String")); + component.appendChild(cType); + + //Name + cName = document.createElement("Name"); + cName.appendChild(document.createTextNode("cutKeyframesIsDisplayed")); + component.appendChild(cName); + + //Value + cValue = document.createElement("Value"); + cValue.appendChild(document.createTextNode(String.valueOf(Shutter.cutKeyframesIsDisplayed))); + component.appendChild(cValue); + + warning.appendChild(component); + } + + if (Shutter.rewrapKeyframesIsDisplayed) + { + component = document.createElement("Component"); + + //Type + cType = document.createElement("Type"); + cType.appendChild(document.createTextNode("String")); + component.appendChild(cType); + + //Name + cName = document.createElement("Name"); + cName.appendChild(document.createTextNode("rewrapKeyframesIsDisplayed")); + component.appendChild(cName); + + //Value + cValue = document.createElement("Value"); + cValue.appendChild(document.createTextNode(String.valueOf(Shutter.rewrapKeyframesIsDisplayed))); + component.appendChild(cValue); + + warning.appendChild(component); + } + + if (Shutter.conformKeyframesIsDisplayed) + { + component = document.createElement("Component"); + + //Type + cType = document.createElement("Type"); + cType.appendChild(document.createTextNode("String")); + component.appendChild(cType); + + //Name + cName = document.createElement("Name"); + cName.appendChild(document.createTextNode("conformKeyframesIsDisplayed")); + component.appendChild(cName); + + //Value + cValue = document.createElement("Value"); + cValue.appendChild(document.createTextNode(String.valueOf(Shutter.conformKeyframesIsDisplayed))); + component.appendChild(cValue); + + warning.appendChild(component); + } + + if (Shutter.cutKeyframesIsDisplayed || Shutter.rewrapKeyframesIsDisplayed || Shutter.conformKeyframesIsDisplayed) + { + root.appendChild(warning); + } + // creation du fichier XML TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); diff --git a/src/application/Shutter.java b/src/application/Shutter.java index cc9107d7..253c6b1f 100644 --- a/src/application/Shutter.java +++ b/src/application/Shutter.java @@ -186,7 +186,7 @@ public class Shutter { /* * Initialisation */ - public static String actualVersion = "18.5"; + public static String actualVersion = "18.6"; public static String getLanguage = ""; public static String arch = "x86_64"; public static long availableMemory; @@ -224,9 +224,9 @@ public class Shutter { protected static boolean subtitlesBurn = true; public static boolean autoBurn = false; public static boolean autoEmbed = false; - private static boolean cutKeyframesIsDisplayed = false; - private static boolean rewrapKeyframesIsDisplayed = false; - private static boolean conformKeyframesIsDisplayed = false; + public static boolean cutKeyframesIsDisplayed = false; + public static boolean rewrapKeyframesIsDisplayed = false; + public static boolean conformKeyframesIsDisplayed = false; public static StringBuilder errorList = new StringBuilder(); public static NumberFormat formatter = new DecimalFormat("00"); public static NumberFormat formatterToMs = new DecimalFormat("000"); @@ -4046,31 +4046,23 @@ public void actionPerformed(ActionEvent e) { public void run() { comboAccel.setEnabled(false); + comboAccel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { if (System.getProperty("os.name").contains("Windows")) { FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v av1_nvenc -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("Nvidia NVENC"); FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v av1_qsv -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("Intel Quick Sync"); FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v av1_amf -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("AMD AMF Encoder"); @@ -4078,31 +4070,31 @@ public void run() { else if (System.getProperty("os.name").contains("Mac")) { FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v av1_videotoolbox -s 640x360 -f null -"); - do { - try { - Thread.sleep(10); - } catch (InterruptedException e) {} - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("OSX VideoToolbox"); } - if (comboAccel.getModel().getSize() != graphicsAccel.size()) - { - comboAccel.setModel(new DefaultComboBoxModel(graphicsAccel.toArray())); - } + int index = comboAccel.getSelectedIndex(); + + comboAccel.setModel(new DefaultComboBoxModel(graphicsAccel.toArray())); + + if (index <= comboAccel.getModel().getSize()) + comboAccel.setSelectedIndex(index); //load hwaccel value after checking gpu capabilities if (Utils.loadEncFile != null && Utils.hwaccel != "") { comboAccel.setSelectedItem(Utils.hwaccel); + Utils.hwaccel = ""; } } catch (Exception e) {} if (comboAccel.getItemCount() > 1) comboAccel.setEnabled(true); + + comboAccel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } }); @@ -10398,7 +10390,8 @@ public void actionPerformed(ActionEvent e) { VideoPlayer.player.remove(overImage); VideoPlayer.player.add(selection); VideoPlayer.player.add(overImage); - } + } + } else { @@ -12270,6 +12263,16 @@ public void paintComponent(Graphics g) { g2d.setColor(new Color(42,42,47)); g2d.fillRoundRect(0, 0, (watermarkBottomRight.getX() + watermarkBottomRight.getWidth()) - watermarkTopLeft.getX() + 4, 18, 15, 15); + + watermarkTopLeft.repaint(); + watermarkLeft.repaint(); + watermarkBottomLeft.repaint(); + watermarkTop.repaint(); + watermarkCenter.repaint(); + watermarkBottom.repaint(); + watermarkTopRight.repaint(); + watermarkRight.repaint(); + watermarkBottomRight.repaint();; } }; @@ -17298,10 +17301,10 @@ public void mouseClicked(MouseEvent e) { lblVBR.setText("CBR"); } else if (lblVBR.getText().equals("CBR") - || lblVBR.getText().equals("VBR") && comboAccel.getSelectedItem().equals(language.getProperty("aucune").toLowerCase()) == false && (comboAccel.getSelectedItem().equals("OSX VideoToolbox") == false - || System.getProperty("os.arch").equals("aarch64")) && (comboFonctions.getSelectedItem().toString().equals("H.264") || comboFonctions.getSelectedItem().toString().equals("H.265")) - || (comboFonctions.getSelectedItem().toString().equals("H.264") == false && comboFonctions.getSelectedItem().toString().equals("H.265") == false && (comboFonctions.getSelectedItem().toString().equals("VP9") - || comboFonctions.getSelectedItem().toString().equals("AV1") || comboFonctions.getSelectedItem().toString().equals("H.266")) && lblVBR.getText().equals("VBR"))) + || lblVBR.getText().equals("VBR") && comboAccel.getSelectedItem().equals(language.getProperty("aucune").toLowerCase()) == false + && (comboAccel.getSelectedItem().equals("OSX VideoToolbox") == false || System.getProperty("os.arch").equals("aarch64")) + && comboFonctions.getSelectedItem().toString().contains("H.26") && comboAccel.getSelectedItem().equals("Vulkan Video") == false + || (lblVBR.getText().equals("VBR") && (comboFonctions.getSelectedItem().toString().equals("VP9") || comboFonctions.getSelectedItem().toString().equals("AV1") || comboFonctions.getSelectedItem().toString().equals("H.266")))) { lblVBR.setText("CQ"); bitrateSize.setText("-"); @@ -18254,6 +18257,12 @@ else if (comboGPUDecoding.getSelectedItem().equals("cuda")) comboGPUFilter.setSelectedIndex(0); comboGPUFilter.setEnabled(true); } + else if (comboGPUDecoding.getSelectedItem().equals("vulkan")) + { + comboGPUFilter.setModel(new DefaultComboBoxModel(new String[] { "vulkan", language.getProperty("aucun") })); + comboGPUFilter.setSelectedIndex(0); + comboGPUFilter.setEnabled(true); + } else { comboGPUFilter.setModel(new DefaultComboBoxModel(new String[] { language.getProperty("aucun") })); @@ -18323,6 +18332,7 @@ public void actionPerformed(ActionEvent e) { comboAccel.setMaximumRowCount(20); comboAccel.setVisible(false); comboAccel.setModel(new DefaultComboBoxModel(new String[] { language.getProperty("aucune").toLowerCase() } )); + comboAccel.setSelectedIndex(0); comboAccel.setFont(new Font(freeSansFont, Font.PLAIN, 10)); comboAccel.setEditable(false); comboAccel.setBounds(lblHWaccel.getLocation().x + lblHWaccel.getWidth() + 4, comboGPUDecoding.getY(), 115, 16); @@ -19515,7 +19525,6 @@ private static void setGPUOptions() { if ("Apple ProRes".equals(function) && System.getProperty("os.name").contains("Mac") && arch.equals("arm64") || "H.264".equals(function) || "H.265".equals(function) || "H.266".equals(function) || "AV1".equals(function) || System.getProperty("os.name").contains("Windows") && "VP9".equals(function) - || System.getProperty("os.name").contains("Windows") && "FFV1".equals(function) || System.getProperty("os.name").contains("Windows") && language.getProperty("functionPicture").equals(function) && comboFilter.getSelectedItem().toString().equals(".avif")) { lblHWaccel.setVisible(true); @@ -20124,7 +20133,8 @@ else if (language.getProperty("functionInsert").equals(function)) comboFilter.setModel(model); } - //HWaccel + //HWaccel + /* if (action && System.getProperty("os.name").contains("Windows") && "FFV1".equals(function)) { Thread hwaccel = new Thread(new Runnable() { @@ -20133,38 +20143,41 @@ else if (language.getProperty("functionInsert").equals(function)) public void run() { comboAccel.setEnabled(false); + comboAccel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v ffv1_vulkan -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); + FFMPEG.hwaccel("-init_hw_device vulkan -f lavfi -i nullsrc -t 1 -c:v ffv1_vulkan -vf format=nv12,hwupload -f null -" + '"'); if (FFMPEG.error == false) graphicsAccel.add("Vulkan Video"); - if (comboAccel.getModel().getSize() != graphicsAccel.size()) - { - comboAccel.setModel(new DefaultComboBoxModel(graphicsAccel.toArray())); - } + int index = comboAccel.getSelectedIndex(); + + comboAccel.setModel(new DefaultComboBoxModel(graphicsAccel.toArray())); + + if (index <= comboAccel.getModel().getSize()) + comboAccel.setSelectedIndex(index); //load hwaccel value after checking gpu capabilities if (Utils.loadEncFile != null && Utils.hwaccel != "") { comboAccel.setSelectedItem(Utils.hwaccel); + Utils.hwaccel = ""; } } catch (Exception e) {} if (comboAccel.getItemCount() > 1) comboAccel.setEnabled(true); + + comboAccel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } }); hwaccel.start(); - } + }*/ //grpSetAudio grpSetAudio.removeAll(); @@ -20476,33 +20489,34 @@ else if (comboFonctions.getSelectedItem().toString().equals("HAP")) public void run() { comboAccel.setEnabled(false); + comboAccel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); if ("Apple ProRes".equals(comboFonctions.getSelectedItem().toString()) && System.getProperty("os.name").contains("Mac") && arch.equals("arm64")) { FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v prores_videotoolbox -s 640x360 -f null -"); - do { - try { - Thread.sleep(10); - } catch (InterruptedException e) {} - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("OSX VideoToolbox"); - if (comboAccel.getModel().getSize() != graphicsAccel.size()) - { - comboAccel.setModel(new DefaultComboBoxModel(graphicsAccel.toArray())); - } + int index = comboAccel.getSelectedIndex(); + + comboAccel.setModel(new DefaultComboBoxModel(graphicsAccel.toArray())); + + if (index <= comboAccel.getModel().getSize()) + comboAccel.setSelectedIndex(index); //load hwaccel value after checking gpu capabilities if (Utils.loadEncFile != null && Utils.hwaccel != "") { comboAccel.setSelectedItem(Utils.hwaccel); + Utils.hwaccel = ""; } } if (comboAccel.getItemCount() > 1) comboAccel.setEnabled(true); + + comboAccel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } }); @@ -20809,6 +20823,7 @@ public void run() { public void run() { comboAccel.setEnabled(false); + comboAccel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));; String codec = "h264"; if ("H.265".equals(comboFonctions.getSelectedItem().toString())) @@ -20826,33 +20841,21 @@ else if ("H.266".equals(comboFonctions.getSelectedItem().toString())) if (System.getProperty("os.name").contains("Windows")) { FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v " + codec + "_nvenc -b_ref_mode 0 -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("Nvidia NVENC"); FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v " + codec + "_qsv -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); - + if (FFMPEG.error == false) graphicsAccel.add("Intel Quick Sync"); FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v " + codec + "_amf -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("AMD AMF Encoder"); - FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v " + codec + "_vulkan -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); + FFMPEG.hwaccel("-init_hw_device vulkan -f lavfi -i nullsrc -t 1 -c:v " + codec + "_vulkan -vf format=nv12,hwupload -f null -" + '"'); if (FFMPEG.error == false) graphicsAccel.add("Vulkan Video"); @@ -20861,9 +20864,6 @@ else if ("H.266".equals(comboFonctions.getSelectedItem().toString())) if (codec == "hevc") { FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v " + codec + "_d3d12va -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("D3D12VA"); @@ -20872,17 +20872,11 @@ else if ("H.266".equals(comboFonctions.getSelectedItem().toString())) else if (System.getProperty("os.name").contains("Linux")) { FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v " + codec + "_nvenc -s 640x360 -f null -"); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("Nvidia NVENC"); FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v " + codec + "_vaapi -s 640x360 -f null -"); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("VAAPI"); @@ -20890,17 +20884,11 @@ else if (System.getProperty("os.name").contains("Linux")) if (comboFonctions.getSelectedItem().toString().equals("H.264")) { FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v " + codec + "_v4l2m2m -s 640x360 -f null -"); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("V4L2 M2M"); FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v " + codec + "_omx -s 640x360 -f null -"); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("OpenMAX"); @@ -20909,29 +20897,31 @@ else if (System.getProperty("os.name").contains("Linux")) else //Accélération graphique Mac { FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v " + codec + "_videotoolbox -s 640x360 -f null -"); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); - + if (FFMPEG.error == false) graphicsAccel.add("OSX VideoToolbox"); } - - if (comboAccel.getModel().getSize() != graphicsAccel.size()) - { - comboAccel.setModel(new DefaultComboBoxModel(graphicsAccel.toArray())); - } + + int index = comboAccel.getSelectedIndex(); + + comboAccel.setModel(new DefaultComboBoxModel(graphicsAccel.toArray())); + + if (index <= comboAccel.getModel().getSize()) + comboAccel.setSelectedIndex(index); //load hwaccel value after checking gpu capabilities if (Utils.loadEncFile != null && Utils.hwaccel != "") { comboAccel.setSelectedItem(Utils.hwaccel); + Utils.hwaccel = ""; } } catch (Exception e) {} if (comboAccel.getItemCount() > 1) comboAccel.setEnabled(true); + + comboAccel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } }); @@ -21406,6 +21396,7 @@ else if ("H.266".equals(function)) public void run() { comboAccel.setEnabled(false); + comboAccel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { @@ -21414,9 +21405,6 @@ public void run() { if (System.getProperty("os.name").contains("Windows")) { FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v vp9_qsv -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("Intel Quick Sync"); @@ -21424,9 +21412,6 @@ public void run() { else if (System.getProperty("os.name").contains("Linux")) { FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v vp9_vaapi -s 640x360 -f null -"); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("VAAPI"); @@ -21437,25 +21422,16 @@ else if ("AV1".equals(comboFonctions.getSelectedItem().toString())) if (System.getProperty("os.name").contains("Windows")) { FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v av1_nvenc -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("Nvidia NVENC"); FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v av1_qsv -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("Intel Quick Sync"); FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v av1_amf -s 640x360 -f null -" + '"'); - do { - Thread.sleep(10); - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("AMD AMF Encoder"); @@ -21463,32 +21439,32 @@ else if ("AV1".equals(comboFonctions.getSelectedItem().toString())) else if (System.getProperty("os.name").contains("Mac")) { FFMPEG.hwaccel("-f lavfi -i nullsrc -t 1 -c:v av1_videotoolbox -s 640x360 -f null -"); - do { - try { - Thread.sleep(10); - } catch (InterruptedException e) {} - } while (FFMPEG.runProcess.isAlive()); if (FFMPEG.error == false) graphicsAccel.add("OSX VideoToolbox"); } } - if (comboAccel.getModel().getSize() != graphicsAccel.size()) - { - comboAccel.setModel(new DefaultComboBoxModel(graphicsAccel.toArray())); - } + int index = comboAccel.getSelectedIndex(); + + comboAccel.setModel(new DefaultComboBoxModel(graphicsAccel.toArray())); + + if (index <= comboAccel.getModel().getSize()) + comboAccel.setSelectedIndex(index); //load hwaccel value after checking gpu capabilities if (Utils.loadEncFile != null && Utils.hwaccel != "") { comboAccel.setSelectedItem(Utils.hwaccel); + Utils.hwaccel = ""; } } catch (Exception e) {} if (comboAccel.getItemCount() > 1) comboAccel.setEnabled(true); + + comboAccel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } }); diff --git a/src/application/Utils.java b/src/application/Utils.java index 489c7ae8..3a11e1a6 100644 --- a/src/application/Utils.java +++ b/src/application/Utils.java @@ -1436,7 +1436,7 @@ else if (p instanceof JPanel) root.appendChild(color); } - + // creation du fichier XML TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); diff --git a/src/application/VideoPlayer.java b/src/application/VideoPlayer.java index 3b881b55..a51f591e 100644 --- a/src/application/VideoPlayer.java +++ b/src/application/VideoPlayer.java @@ -1975,6 +1975,10 @@ else if (FFMPEG.videotoolboxAvailable && Shutter.comboGPUFilter.getSelectedItem( { gpuDecoding = " -hwaccel videotoolbox -hwaccel_output_format videotoolbox_vld"; } + else if (FFMPEG.vulkanAvailable && Shutter.comboGPUFilter.getSelectedItem().toString().equals(Shutter.language.getProperty("aucun")) == false && setFilter(yadif, speed, false).contains("scale_vulkan")) + { + gpuDecoding = " -hwaccel vulkan -hwaccel_output_format vulkan -init_hw_device vulkan"; + } else gpuDecoding = " -hwaccel auto"; } @@ -1996,7 +2000,7 @@ else if (System.getProperty("os.name").contains("Mac")) freezeFrame = ""; String cmd = gpuDecoding + Colorimetry.setInputCodec(extension) + " -strict -2 -v quiet -hide_banner -ss " + (long) (inputTime * inputFramerateMS) + "ms" + concat + " -i " + '"' + video + '"' + setFilter(yadif, speed, false) + " -r " + FFPROBE.currentFPS + freezeFrame + " -c:v bmp -an -f image2pipe -"; - + String codec = ""; if (Settings.btnPreviewOutput.isSelected() && VideoEncoders.setCodec() != "" && Shutter.comboFonctions.getSelectedItem().toString().equals("DNxHD") == false @@ -4779,8 +4783,8 @@ else if (line.isEmpty()) } public static boolean loadWatermark(int size) { - - if (FFPROBE.imageWidth != FFPROBE.previousImageWidth || FFPROBE.imageHeight != FFPROBE.previousImageHeight) + + if (FFPROBE.imageWidth != FFPROBE.previousImageWidth || FFPROBE.imageHeight != FFPROBE.previousImageHeight || Shutter.logoPNG == null) { try { @@ -5052,14 +5056,21 @@ public void run() { String inputPoint = " -ss " + (float) (playerCurrentFrame) * inputFramerateMS + "ms"; if (FFPROBE.totalLength <= 40 || Shutter.caseEnableSequence.isSelected()) //Image inputPoint = ""; - + + //Alpha channel + String alpha = ""; + if (FFPROBE.hasAlpha && deinterlace == "") + { + alpha = " -vf " + '"' + "split=2[bg][fg];[bg]drawbox=c=0x202025:replace=1:t=fill[bg];[bg][fg]overlay=0:0,format=rgb24" + '"'; + } + //Creating preview file - String cmd = deinterlace + " -frames:v 1 -an -s " + player.getWidth() + "x" + player.getHeight() + " -sws_flags bicubic -y "; + String cmd = deinterlace + alpha + " -frames:v 1 -an -s " + player.getWidth() + "x" + player.getHeight() + " -sws_flags bicubic -y "; if (Shutter.caseRotate.isSelected() && (Shutter.comboRotate.getSelectedIndex() == 1 || Shutter.comboRotate.getSelectedIndex() == 2)) { - cmd = deinterlace + " -frames:v 1 -an -s " + player.getHeight() + "x" + player.getWidth() + " -sws_flags bicubic -y "; + cmd = deinterlace + alpha + " -frames:v 1 -an -s " + player.getHeight() + "x" + player.getWidth() + " -sws_flags bicubic -y "; } - + if (preview == null && Shutter.caseAddSubtitles.isSelected() == false) { if (extension.toLowerCase().equals(".pdf")) @@ -5129,7 +5140,7 @@ else if (Shutter.comboResolution.getSelectedItem().toString().contains("AI")) } while (preview.exists()); } else - { + { generatePreview(Colorimetry.setInputCodec(extension) + inputPoint + " -v quiet -hide_banner -i " + '"' + file.toString() + '"' + cmd + " -frames:v 1 -c:v bmp -an -f image2pipe -"); } @@ -5374,6 +5385,10 @@ else if (FFMPEG.videotoolboxAvailable && yadif == "") { filter += "scale_vt=" + width + ":" + height + ",hwdownload,format=" + bitDepth; } + else if (FFMPEG.vulkanAvailable && yadif == "") + { + filter += "scale_vulkan=" + width + ":" + height + ",hwdownload,format=" + bitDepth; + } else { filter += "scale=" + width + ":" + height + ":sws_flags=" + algorithm + ":sws_dither=none"; @@ -5383,7 +5398,7 @@ else if (FFMPEG.videotoolboxAvailable && yadif == "") { filter += "scale=" + width + ":" + height + ":sws_flags=" + algorithm + ":sws_dither=none"; } - + //Alpha channel if (FFPROBE.hasAlpha) { diff --git a/src/functions/VideoEncoders.java b/src/functions/VideoEncoders.java index a362c9ce..2dfdb121 100644 --- a/src/functions/VideoEncoders.java +++ b/src/functions/VideoEncoders.java @@ -819,7 +819,7 @@ else if (caseDisplay.isSelected() && VideoPlayer.comboMode.getSelectedItem().toS //GPU decoding String gpuDecoding = ""; - if (FFMPEG.isGPUCompatible && (filterComplex.contains("scale_cuda") || filterComplex.contains("scale_qsv") || filterComplex.contains("scale_vt"))) + if (FFMPEG.isGPUCompatible && (filterComplex.contains("scale_cuda") || filterComplex.contains("scale_qsv") || filterComplex.contains("scale_vt") || filterComplex.contains("scale_vulkan"))) { if (Shutter.comboGPUDecoding.getSelectedItem().toString().equals("auto") && Shutter.comboGPUFilter.getSelectedItem().toString().equals("auto")) { @@ -835,6 +835,10 @@ else if (FFMPEG.videotoolboxAvailable) { gpuDecoding = " -hwaccel videotoolbox -hwaccel_output_format videotoolbox_vld"; } + else if (FFMPEG.vulkanAvailable) + { + gpuDecoding = " -hwaccel vulkan -hwaccel_output_format vulkan -init_hw_device vulkan"; + } } else gpuDecoding = " -hwaccel " + Shutter.comboGPUDecoding.getSelectedItem().toString().replace(Shutter.language.getProperty("aucun"), "none") + " -hwaccel_output_format " + Shutter.comboGPUFilter.getSelectedItem().toString().replace(Shutter.language.getProperty("aucun"), "none"); @@ -843,11 +847,16 @@ else if (FFMPEG.videotoolboxAvailable) { gpuDecoding = " -hwaccel " + Shutter.comboGPUDecoding.getSelectedItem().toString().replace(Shutter.language.getProperty("aucun"), "none"); } - + + //GPU initialization if (comboAccel.getSelectedItem().equals(language.getProperty("aucune").toLowerCase()) == false && comboAccel.getSelectedItem().equals("VAAPI")) { gpuDecoding += " -vaapi_device /dev/dri/renderD128"; } + else if (comboAccel.getSelectedItem().equals(language.getProperty("aucune").toLowerCase()) == false && comboAccel.getSelectedItem().equals("Vulkan Video") && (Shutter.comboGPUFilter.getSelectedItem().toString().equals("vulkan") || FFMPEG.vulkanAvailable == false)) + { + gpuDecoding += " -init_hw_device vulkan"; + } //GPU filtering if (filterComplex.contains("hwdownload")) //When GPU scaling is used @@ -1603,7 +1612,7 @@ else if (comboColorspace.getSelectedItem().toString().contains("12bits")) else { //Switching to GPU nv12 or p010 to avoid useless pix_fmt conversion - if (FFMPEG.isGPUCompatible && (filterComplex.contains("scale_cuda") || filterComplex.contains("scale_qsv") || filterComplex.contains("scale_vt"))) + if (FFMPEG.isGPUCompatible && (filterComplex.contains("scale_cuda") || filterComplex.contains("scale_qsv") || filterComplex.contains("scale_vt") || filterComplex.contains("scale_vulkan"))) { if (filterComplex.contains("format=p010")) { @@ -1631,7 +1640,7 @@ else if ("H.266".equals(comboFonctions.getSelectedItem().toString())) else { //Switching to GPU nv12 to avoid useless pix_fmt conversion - if (caseColorspace.isSelected() == false && FFMPEG.isGPUCompatible && (filterComplex.contains("scale_cuda") || filterComplex.contains("scale_qsv") || filterComplex.contains("scale_vt"))) + if (caseColorspace.isSelected() == false && FFMPEG.isGPUCompatible && (filterComplex.contains("scale_cuda") || filterComplex.contains("scale_qsv") || filterComplex.contains("scale_vt") || filterComplex.contains("scale_vulkan"))) { return ""; } @@ -1652,7 +1661,7 @@ else if ("H.266".equals(comboFonctions.getSelectedItem().toString())) case "Xvid": //Switching to GPU nv12 to avoid useless pix_fmt conversion - if (caseColorspace.isSelected() == false && FFMPEG.isGPUCompatible && (filterComplex.contains("scale_cuda") || filterComplex.contains("scale_qsv") || filterComplex.contains("scale_vt"))) + if (caseColorspace.isSelected() == false && FFMPEG.isGPUCompatible && (filterComplex.contains("scale_cuda") || filterComplex.contains("scale_qsv") || filterComplex.contains("scale_vt") || filterComplex.contains("scale_vulkan"))) { return ""; } diff --git a/src/library/FFMPEG.java b/src/library/FFMPEG.java index 9345b660..f98a5bed 100644 --- a/src/library/FFMPEG.java +++ b/src/library/FFMPEG.java @@ -121,6 +121,7 @@ public class FFMPEG extends Shutter { public static boolean cudaAvailable = false; public static boolean qsvAvailable = false; public static boolean videotoolboxAvailable = false; +public static boolean vulkanAvailable = false; public static int differenceMax; //Moyenne de fps @@ -373,7 +374,7 @@ public static void checkForErrors(String line) { || line.contains("Input/output error") || line.contains("Operation not permitted") || line.contains("Permission denied")) - { + { errorLog.append(line + System.lineSeparator()); error = true; } @@ -949,71 +950,63 @@ public static void hwaccel(final String cmd) { error = false; isRunning = true; - runProcess = new Thread(new Runnable() { - @Override - public void run() { - - try { - - ProcessBuilder processFFMPEG; - if (System.getProperty("os.name").contains("Windows")) - { - processFFMPEG = new ProcessBuilder('"' + PathToFFMPEG + '"' + " -strict -2 -hide_banner " + cmd.replace("PathToFFMPEG", PathToFFMPEG)); - process = processFFMPEG.start(); - } - else - { - processFFMPEG = new ProcessBuilder("/bin/bash", "-c" , PathToFFMPEG + " -strict -2 -hide_banner " + cmd.replace("PathToFFMPEG", PathToFFMPEG)); - process = processFFMPEG.start(); - } - - String line; + try { + + ProcessBuilder processFFMPEG; + if (System.getProperty("os.name").contains("Windows")) + { + processFFMPEG = new ProcessBuilder('"' + PathToFFMPEG + '"' + " -strict -2 -hide_banner " + cmd.replace("PathToFFMPEG", PathToFFMPEG)); + process = processFFMPEG.start(); + } + else + { + processFFMPEG = new ProcessBuilder("/bin/bash", "-c" , PathToFFMPEG + " -strict -2 -hide_banner " + cmd.replace("PathToFFMPEG", PathToFFMPEG)); + process = processFFMPEG.start(); + } + + String line; - if (cmd.contains("-hwaccels")) - { - InputStreamReader isr = new InputStreamReader(process.getInputStream()); - BufferedReader br = new BufferedReader(isr); - - hwaccels.append("auto" + System.lineSeparator()); - - while ((line = br.readLine()) != null) - { - - if (line.contains("Hardware acceleration methods") == false && line.equals("") == false && line != null) - { - hwaccels.append(line + System.lineSeparator()); - } - } - - hwaccels.append(language.getProperty("aucun")); - } - else - { - BufferedReader input = new BufferedReader(new InputStreamReader(process.getErrorStream())); + if (cmd.contains("-hwaccels")) + { + InputStreamReader isr = new InputStreamReader(process.getInputStream()); + BufferedReader br = new BufferedReader(isr); + + hwaccels.append("auto" + System.lineSeparator()); + + while ((line = br.readLine()) != null) + { + + if (line.contains("Hardware acceleration methods") == false && line.equals("") == false && line != null) + { + hwaccels.append(line + System.lineSeparator()); + } + } + + hwaccels.append(language.getProperty("aucun")); + } + else + { + BufferedReader input = new BufferedReader(new InputStreamReader(process.getErrorStream())); - while ((line = input.readLine()) != null) { - - Console.consoleFFMPEG.append(line + System.lineSeparator() ); - - //Errors - checkForErrors(line); - } - - Console.consoleFFMPEG.append(System.lineSeparator()); - } - process.waitFor(); - - } catch (IOException io) {//Bug Linux - } catch (InterruptedException e) { - error = true; - } - finally { - isRunning = false; - } + while ((line = input.readLine()) != null) { + + Console.consoleFFMPEG.append(line + System.lineSeparator() ); + + //Errors + checkForErrors(line); + } - } - }); - runProcess.start(); + Console.consoleFFMPEG.append(System.lineSeparator()); + } + process.waitFor(); + + } catch (IOException io) {//Bug Linux + } catch (InterruptedException e) { + error = true; + } + finally { + isRunning = false; + } } public static void checkGPUCapabilities(String file) { @@ -1024,6 +1017,7 @@ public static void checkGPUCapabilities(String file) { cudaAvailable = false; qsvAvailable = false; videotoolboxAvailable = false; + vulkanAvailable = false; //Check is GPU can decode if ((System.getProperty("os.name").contains("Windows") || System.getProperty("os.name").contains("Mac")) @@ -1096,16 +1090,30 @@ public static void checkGPUCapabilities(String file) { if (FFMPEG.error == false) qsvAvailable = true; + //Vulkan + FFMPEG.gpuFilter(" -hwaccel vulkan -hwaccel_output_format vulkan -init_hw_device vulkan -i " + '"' + file + '"' + " -vf scale_vulkan=640:360,hwdownload,format=" + bitDepth + " -an -t 1 -f null -" + '"'); + + do { + Thread.sleep(100); + } while(FFMPEG.runProcess.isAlive()); + + if (FFMPEG.error == false) + vulkanAvailable = true; + if (comboAccel.getSelectedItem().equals(language.getProperty("aucune").toLowerCase()) == false) { - if (comboAccel.getSelectedItem().equals("Intel Quick Sync")) //Cannot use CUDA decoding with QSV encoding + if (comboAccel.getSelectedItem().equals("Intel Quick Sync") || comboAccel.getSelectedItem().equals("Vulkan Video")) //Cannot use CUDA decoding with QSV encoding { cudaAvailable = false; } - else if (comboAccel.getSelectedItem().equals("Nvidia NVENC")) //Cannot use QSV decoding with NVENC encoding + else if (comboAccel.getSelectedItem().equals("Nvidia NVENC") || comboAccel.getSelectedItem().equals("Vulkan Video")) //Cannot use QSV decoding with NVENC encoding { qsvAvailable = false; } + else if (comboAccel.getSelectedItem().equals("Intel Quick Sync") || comboAccel.getSelectedItem().equals("Nvidia NVENC")) //Cannot use VULKAN decoding with QSV encoding + { + vulkanAvailable = false; + } } } else //Mac @@ -1122,17 +1130,23 @@ else if (comboAccel.getSelectedItem().equals("Nvidia NVENC")) //Cannot use QSV d } //Disable GPU if not available - if (cudaAvailable == false && qsvAvailable == false && videotoolboxAvailable == false) + if (cudaAvailable == false && qsvAvailable == false && videotoolboxAvailable == false && vulkanAvailable == false) isGPUCompatible = false; } else //Check the current selection - { - FFMPEG.gpuFilter(" -hwaccel " + Shutter.comboGPUDecoding.getSelectedItem().toString().replace(Shutter.language.getProperty("aucun"), "none") + " -hwaccel_output_format " + Shutter.comboGPUFilter.getSelectedItem().toString() + " -i " + '"' + file + '"' + " -vf scale_" + Shutter.comboGPUFilter.getSelectedItem().toString() + "=640:360,hwdownload,format=" + bitDepth + " -an -t 1 -f null -" + '"'); + { + String device = ""; + if (Shutter.comboGPUDecoding.getSelectedItem().toString().equals("vulkan")) + { + device = " -init_hw_device vulkan"; + } + + FFMPEG.gpuFilter(" -hwaccel " + Shutter.comboGPUDecoding.getSelectedItem().toString().replace(Shutter.language.getProperty("aucun"), "none") + " -hwaccel_output_format " + Shutter.comboGPUFilter.getSelectedItem().toString() + device + " -i " + '"' + file + '"' + " -vf scale_" + Shutter.comboGPUFilter.getSelectedItem().toString() + "=640:360,hwdownload,format=" + bitDepth + " -an -t 1 -f null -" + '"'); do { Thread.sleep(100); } while(FFMPEG.runProcess.isAlive()); - + if (FFMPEG.error) { isGPUCompatible = false; @@ -1149,6 +1163,10 @@ else if (Shutter.comboGPUDecoding.getSelectedItem().equals("videotoolbox")) { videotoolboxAvailable = false; } + else if (Shutter.comboGPUDecoding.getSelectedItem().equals("vulkan")) + { + vulkanAvailable = false; + } } else { @@ -1164,6 +1182,10 @@ else if (Shutter.comboGPUDecoding.getSelectedItem().equals("videotoolbox")) { videotoolboxAvailable = true; } + else if (Shutter.comboGPUDecoding.getSelectedItem().equals("vulkan")) + { + vulkanAvailable = true; + } } } } diff --git a/src/settings/AdvancedFeatures.java b/src/settings/AdvancedFeatures.java index 9e5d83ae..088ef1ce 100644 --- a/src/settings/AdvancedFeatures.java +++ b/src/settings/AdvancedFeatures.java @@ -313,12 +313,7 @@ else if ( profile.equals("main444")) profile = "main444-10"; } - if (comboAccel.getSelectedItem().equals(language.getProperty("aucune").toLowerCase()) == false && comboAccel.getSelectedItem().equals("Intel Quick Sync")) - { - return " -profile:v " + profile; - } - else - return " -profile:v " + profile + " -level " + Shutter.comboForceLevel.getSelectedItem().toString(); + return " -profile:v " + profile; } else { @@ -332,81 +327,7 @@ else if (caseColorspace.isSelected() && comboColorspace.getSelectedItem().toStri profile = "main12"; } - String s[] = FFPROBE.imageResolution.split("x"); - if (comboResolution.getSelectedItem().toString().equals(language.getProperty("source")) == false) - { - if (comboResolution.getSelectedItem().toString().contains("%")) - { - double value = (double) Integer.parseInt(comboResolution.getSelectedItem().toString().replace("%", "")) / 100; - - s[0] = String.valueOf((int) (Integer.parseInt(s[0]) * value)); - s[1] = String.valueOf((int) (Integer.parseInt(s[1]) * value)); - } - else if (comboResolution.getSelectedItem().toString().contains("x")) - { - if (comboResolution.getSelectedItem().toString().contains("AI")) - { - if (Shutter.comboResolution.getSelectedItem().toString().contains("2x")) - { - s[0] = String.valueOf(Math.round(Integer.parseInt(s[0]) * 2)); - s[1] = String.valueOf(Math.round(Integer.parseInt(s[1]) * 2)); - } - else - { - s[0] = String.valueOf(Math.round(Integer.parseInt(s[0]) * 4)); - s[1] = String.valueOf(Math.round(Integer.parseInt(s[1]) * 4)); - } - } - else - s = comboResolution.getSelectedItem().toString().split("x"); - } - else if (comboResolution.getSelectedItem().toString().contains(":")) - { - String i[] = FFPROBE.imageResolution.split("x"); - s = comboResolution.getSelectedItem().toString().replace("auto", "1").split(":"); - - int iw = Integer.parseInt(i[0]); - int ih = Integer.parseInt(i[1]); - int ow = Integer.parseInt(s[0]); - int oh = Integer.parseInt(s[1]); - float ir = (float) iw / ih; - - if (s[0].toString().equals("1")) // = auto - { - s[0] = String.valueOf((int) Math.round((float) oh * ir)); - } - else - { - s[1] = String.valueOf((int) Math.round((float) ow / ir)); - } - } - } - - int width = Integer.parseInt(s[0]); - int height = Integer.parseInt(s[1]); - - if (width > 1920 || height > 1080 || FFPROBE.currentFPS >= 59.94f) - { - if (comboAccel.getSelectedItem().equals(language.getProperty("aucune").toLowerCase()) == false && comboAccel.getSelectedItem().equals("Nvidia NVENC")) - { - return " -profile:v " + profile + " -level 6.1"; - } - else if (comboAccel.getSelectedItem().equals(language.getProperty("aucune").toLowerCase()) == false && comboAccel.getSelectedItem().equals("Intel Quick Sync")) - { - return " -profile:v " + profile; - } - else - return " -profile:v " + profile + " -level 5.2"; - } - else - { - if (comboAccel.getSelectedItem().equals(language.getProperty("aucune").toLowerCase()) == false && comboAccel.getSelectedItem().equals("Intel Quick Sync")) - { - return " -profile:v " + profile; - } - else - return " -profile:v " + profile + " -level 5.1"; - } + return " -profile:v " + profile; } } @@ -1030,6 +951,14 @@ else if (lblVBR.getText().equals("CQ") && debitVideo.getSelectedItem().toString( options += "colorprim=bt2020:transfer=" + PQorHLG + ":colormatrix=bt2020nc:master-display=G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(" + (int) FFPROBE.HDRmax * 10000 + "," + FFPROBE.HDRmin * 10000 + "):max-cll=" + FFPROBE.maxCLL + "," + FFPROBE.maxFALL; } + //Levels + if (caseForceLevel.isSelected()) + { + if (options != "") options += ":"; + + options += "level=" + Shutter.comboForceLevel.getSelectedItem().toString(); + } + if (options != "") { options = " -x265-params " + '"' + options + '"'; diff --git a/src/settings/FunctionUtils.java b/src/settings/FunctionUtils.java index 11dcc20d..8a83d340 100644 --- a/src/settings/FunctionUtils.java +++ b/src/settings/FunctionUtils.java @@ -1171,22 +1171,27 @@ public static String setFilterComplex(String filterComplex, String audio, boolea return filterComplex; } - //VAAPI Hardware encoding + //Hardware encoding switch (comboFonctions.getSelectedItem().toString()) { case "H.264": case "H.265": case "H.266": case "VP9": + case "FFV1": - if (comboAccel.getSelectedItem().equals(language.getProperty("aucune").toLowerCase()) == false && comboAccel.getSelectedItem().equals("VAAPI")) - { + if (comboAccel.getSelectedItem().equals(language.getProperty("aucune").toLowerCase()) == false + && comboAccel.getSelectedItem().equals("VAAPI") || comboAccel.getSelectedItem().equals("Vulkan Video")) + { + if (filterComplex != "") + filterComplex += ","; + if (caseColorspace.isSelected() && comboColorspace.getSelectedItem().toString().contains("10bits")) { - filterComplex += ",format=p010,hwupload"; + filterComplex += "format=p010,hwupload"; } else - filterComplex += ",format=nv12,hwupload"; + filterComplex += "format=nv12,hwupload"; } break; diff --git a/src/settings/Image.java b/src/settings/Image.java index 5d66553f..a45064ff 100644 --- a/src/settings/Image.java +++ b/src/settings/Image.java @@ -353,6 +353,7 @@ else if (comboFilter.getSelectedItem().toString().equals(".ico")) boolean autoQSV = false; boolean autoCUDA = false; boolean autoVIDEOTOOLBOX = false; + boolean autoVULKAN = false; if (Shutter.comboGPUDecoding.getSelectedItem().toString().equals("auto") && Shutter.comboGPUFilter.getSelectedItem().toString().equals("auto")) { if (FFMPEG.cudaAvailable) @@ -367,6 +368,10 @@ else if (FFMPEG.videotoolboxAvailable) { autoVIDEOTOOLBOX = true; } + else if (FFMPEG.vulkanAvailable) + { + autoVULKAN = true; + } } if ((autoQSV || Shutter.comboGPUFilter.getSelectedItem().toString().equals("qsv") && FFMPEG.isGPUCompatible) && caseForcerDesentrelacement.isSelected() == false && filterComplex.contains("yadif") == false && filterComplex.contains("force_original_aspect_ratio") == false) @@ -389,6 +394,12 @@ else if ((autoVIDEOTOOLBOX || Shutter.comboGPUFilter.getSelectedItem().toString( { filterComplex = filterComplex.replace("scale=", "scale_vt="); + filterComplex += ",hwdownload,format=" + bitDepth; + } + else if ((autoVULKAN || Shutter.comboGPUFilter.getSelectedItem().toString().equals("vulkan") && FFMPEG.isGPUCompatible) && caseForcerDesentrelacement.isSelected() == false && filterComplex.contains("yadif") == false && filterComplex.contains("force_original_aspect_ratio") == false && lblPad.getText().equals(language.getProperty("lblCrop")) == false) + { + filterComplex = filterComplex.replace("scale=", "scale_vulkan="); + filterComplex += ",hwdownload,format=" + bitDepth; } }