From f8dd7be711a3683fdbc8604038ef72992af19024 Mon Sep 17 00:00:00 2001 From: agreiner Date: Mon, 5 Apr 2021 23:14:41 +0200 Subject: [PATCH] - Deleted unused classes - Refined and added javadoc - Disabled MetaDataWriter --- nbactions.xml | 63 ++- .../java/com/greinet/tvtotalripper/Main.java | 172 ------- .../greinet/tvtotalripper/MetaDataParser.java | 189 ------- .../greinet/tvtotalripper/MetaDataWriter.java | 463 +++++++++--------- .../greinet/tvtotalripper/SwingInterface.java | 77 --- .../tvtotalripper/crawler/CrawlerUtil.java | 368 +++++++------- .../tvtotalripper/crawler/EpisodeWrapper.java | 292 +++++------ .../download/ConnectionUtil.java | 155 +++--- .../tvtotalripper/download/DownloadTask.java | 225 +++++---- .../tvtotalripper/ui/BaseRipperPanel.java | 257 +++++----- .../tvtotalripper/ui/DownloadRipperPanel.java | 307 ++++++------ .../ui/DownloadTaskInformationPanel.java | 273 ++++++----- .../tvtotalripper/ui/EpisodesRipperPanel.java | 270 +++++----- .../tvtotalripper/ui/RipperWindow.java | 283 ++++++----- .../tvtotalripper/ui/SettingsRipperPanel.java | 187 +++---- 15 files changed, 1643 insertions(+), 1938 deletions(-) delete mode 100644 src/main/java/com/greinet/tvtotalripper/Main.java delete mode 100644 src/main/java/com/greinet/tvtotalripper/MetaDataParser.java delete mode 100644 src/main/java/com/greinet/tvtotalripper/SwingInterface.java diff --git a/nbactions.xml b/nbactions.xml index cc17048..bc1bcdc 100644 --- a/nbactions.xml +++ b/nbactions.xml @@ -1,17 +1,46 @@ - - - - run - - jar - - - process-classes - org.codehaus.mojo:exec-maven-plugin:1.5.0:exec - - - -classpath %classpath com.greinet.tvtotalripper.Main - java - - - + + + + run + + jar + + + process-classes + org.codehaus.mojo:exec-maven-plugin:1.5.0:exec + + + -classpath %classpath com.greinet.tvtotalripper.ui.RipperWindow + java + + + + debug + + jar + + + process-classes + org.codehaus.mojo:exec-maven-plugin:1.5.0:exec + + + -agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address} -classpath %classpath com.greinet.tvtotalripper.ui.RipperWindow + java + true + + + + profile + + jar + + + process-classes + org.codehaus.mojo:exec-maven-plugin:1.5.0:exec + + + -classpath %classpath com.greinet.tvtotalripper.ui.RipperWindow + java + + + diff --git a/src/main/java/com/greinet/tvtotalripper/Main.java b/src/main/java/com/greinet/tvtotalripper/Main.java deleted file mode 100644 index 5ddcb0e..0000000 --- a/src/main/java/com/greinet/tvtotalripper/Main.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.greinet.tvtotalripper; - -import com.greinet.tvtotalripper.crawler.CrawlerUtil; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; -import java.util.List; -import java.util.logging.Level; -import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.mp4parser.IsoFile; -import org.mp4parser.boxes.apple.AppleNameBox; -import org.mp4parser.tools.Path; -import org.openqa.selenium.By; -import org.openqa.selenium.TimeoutException; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.chrome.ChromeDriver; -import org.openqa.selenium.chrome.ChromeOptions; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; - -/** - * - * @author agreiner - */ -public class Main { - - private static final Logger logger = LogManager.getLogger(Main.class); - - - public static void main(String[] args) throws InterruptedException, IOException { - //System.setProperty("webdriver.chrome.driver", "resources/chromedriver.exe"); - File f = new File("H:/Users/Andreas/Music/Bass/videoplayback.mp4"); - - MetaDataWriter mdp = new MetaDataWriter(); - mdp.writeMetadata(f.getAbsolutePath(), "Raab im Dschungel", "Stefan Raab", "TV Total", "TV Total vom 3.2.2021"); - - - System.exit(0); - - - - System.out.println(CrawlerUtil.getFetchfileURL("https://www.myspass.de/shows/tvshows/tv-total/TV-total-Sendung-vom-08031999--/5716/")); - - - - - - downloadFile(test()); - - - - - ChromeOptions options = new ChromeOptions(); - options.addArguments("start-maximized"); - - WebDriver driver = new ChromeDriver(options); - - initialize(driver); - - List seriesElements = getSeriesElements(driver); - //seriesElements.forEach(e -> System.out.println(e.getAttribute("href").replaceAll("https://www.myspass.de/shows/tvshows/", ""))); - - System.out.println("\nTV shows\n"); - // TV shows - List tvshows = seriesElements.stream().filter(e -> e.getAttribute("href").contains("tvshows") || e.getAttribute("href").contains("UNKNOWN")).collect(Collectors.toList()); - tvshows.forEach(e -> System.out.println(e.getAttribute("href"))); - - System.out.println("\nWebshows\n"); - List webshows = seriesElements.stream().filter(e -> e.getAttribute("href").contains("webshows")).collect(Collectors.toList()); - webshows.forEach(e -> System.out.println(e.getAttribute("href"))); - - - tvshows.get(3).click(); - Thread.sleep(3000); - navigateToEpisodeOverview(driver); - Thread.sleep(3000); - getSeasonElements(driver).forEach(e -> System.out.println(e.getText())); - Thread.sleep(3000); - System.out.println("-------------------------------------------------------------------------------------------------------------------------------------"); - getEpisodeElements(driver).forEach(e -> System.out.println(e.getAttribute("href"))); - } - - private static boolean clickWhenClickable(WebDriver driver, By by, int timeout){ - WebDriverWait wait = new WebDriverWait(driver, timeout); - try{ - WebElement acceptCookiesButton = wait.until(ExpectedConditions.elementToBeClickable(by)); - acceptCookiesButton.click(); - }catch(TimeoutException ex){ - logger.warn("Element represented by ["+by+"] not clickable."); - return false; - } - return true; - } - - private static void initialize(WebDriver driver){ - //Load start page - //String urlStartPage = "https://www.myspass.de/shows/tvshows/tv-total/#bob-subnavi"; - String urlStartPage = "https://www.myspass.de/sendungen-a-bis-z/"; - logger.info("Loading start page ["+urlStartPage+"]."); - driver.get(urlStartPage); - - // Accept cookies if needed - logger.info("Accepting cookies."); - boolean cookiesSuccess = clickWhenClickable(driver, By.id("cmpbntyestxt"),5); - if(!cookiesSuccess){ - logger.info("No cookie popup present."); - } - } - - private static boolean navigateToEpisodeOverview(WebDriver driver){ - logger.info("Navigating to episode overview."); - return clickWhenClickable(driver, By.xpath("/html/body/div[4]/div[1]/div[2]/ul/li[2]/a"),5); - } - - private static List getSeriesElements(WebDriver driver){ - return driver.findElements(By.xpath("/html/body/div[5]/div/div/div/div/a")); - } - - private static List getSeasonElements(WebDriver driver){ - return driver.findElements(By.xpath("/html/body/div[4]/div[1]/div[3]/div[2]/div[1]/select/option")); - } - - private static List getEpisodeElements(WebDriver driver){ - return driver.findElements(By.xpath("/html/body/div[4]/div[1]/div[3]/div[2]/div[4]/div/div/div/a")); - } - - private static String test(){ - WebDriver driver = new ChromeDriver(); - driver.get("https://de.fetchfile.net/herunterladen-von-myspass/"); - - WebElement textbox = driver.findElement(By.id("videoPath")); - textbox.sendKeys("https://www.myspass.de/shows/tvshows/tv-total/TV-total-Sendung-vom-05012015--/20674/"); - - WebElement dlButton = driver.findElement(By.id("home-submit")); - dlButton.click(); - - WebDriverWait wait = new WebDriverWait(driver, 20); - WebElement vidButton = wait.until(ExpectedConditions.elementToBeClickable(By.className("download-link"))); - return vidButton.getAttribute("href"); - - } - - - private static void downloadFile(String urlString){ - try { - File f = new File("download.mp4"); - f.createNewFile(); - URL url = new URL(urlString); - ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream()); - FileOutputStream fileOutputStream = new FileOutputStream(f); - fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE); - } catch (MalformedURLException ex) { - - } catch (IOException ex) { - java.util.logging.Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); - } - - } -} diff --git a/src/main/java/com/greinet/tvtotalripper/MetaDataParser.java b/src/main/java/com/greinet/tvtotalripper/MetaDataParser.java deleted file mode 100644 index 3cb2ff2..0000000 --- a/src/main/java/com/greinet/tvtotalripper/MetaDataParser.java +++ /dev/null @@ -1,189 +0,0 @@ -package com.greinet.tvtotalripper; - - -import org.mp4parser.Box; -import org.mp4parser.Container; -import org.mp4parser.IsoFile; -import org.mp4parser.boxes.apple.AppleItemListBox; -import org.mp4parser.boxes.apple.AppleNameBox; -import org.mp4parser.boxes.iso14496.part12.*; -import org.mp4parser.tools.Path; - -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.util.List; - -/** - * Change metadata and make sure chunkoffsets are corrected. - */ -public class MetaDataParser { - - public FileChannel splitFileAndInsert(File f, long pos, long length) throws IOException { - FileChannel read = new RandomAccessFile(f, "r").getChannel(); - File tmp = File.createTempFile("ChangeMetaData", "splitFileAndInsert"); - FileChannel tmpWrite = new RandomAccessFile(tmp, "rw").getChannel(); - read.position(pos); - tmpWrite.transferFrom(read, 0, read.size() - pos); - read.close(); - FileChannel write = new RandomAccessFile(f, "rw").getChannel(); - write.position(pos + length); - tmpWrite.position(0); - long transferred = 0; - while ((transferred += tmpWrite.transferTo(0, tmpWrite.size() - transferred, write)) != tmpWrite.size()) { - System.out.println(transferred); - } - System.out.println(transferred); - tmpWrite.close(); - tmp.delete(); - return write; - } - - - private boolean needsOffsetCorrection(IsoFile isoFile) { - if (Path.getPath(isoFile, "moov[0]/mvex[0]") != null) { - // Fragmented files don't need a correction - return false; - } else { - // no correction needed if mdat is before moov as insert into moov want change the offsets of mdat - for (Box box : isoFile.getBoxes()) { - if ("moov".equals(box.getType())) { - return true; - } - if ("mdat".equals(box.getType())) { - return false; - } - } - throw new RuntimeException("I need moov or mdat. Otherwise all this doesn't make sense"); - } - } - - public void writeRandomMetadata(String videoFilePath, String title) throws IOException { - - File videoFile = new File(videoFilePath); - if (!videoFile.exists()) { - throw new FileNotFoundException("File " + videoFilePath + " not exists"); - } - - if (!videoFile.canWrite()) { - throw new IllegalStateException("No write permissions to file " + videoFilePath); - } - IsoFile isoFile = new IsoFile(videoFilePath); - - MovieBox moov = isoFile.getBoxes(MovieBox.class).get(0); - FreeBox freeBox = findFreeBox(moov); - - boolean correctOffset = needsOffsetCorrection(isoFile); - long sizeBefore = moov.getSize(); - long offset = 0; - for (Box box : isoFile.getBoxes()) { - if ("moov".equals(box.getType())) { - break; - } - offset += box.getSize(); - } - - // Create structure or just navigate to Apple List Box. - UserDataBox userDataBox; - if ((userDataBox = Path.getPath(moov, "udta")) == null) { - userDataBox = new UserDataBox(); - moov.addBox(userDataBox); - } - MetaBox metaBox; - if ((metaBox = Path.getPath(userDataBox, "meta")) == null) { - metaBox = new MetaBox(); - HandlerBox hdlr = new HandlerBox(); - hdlr.setHandlerType("mdir"); - metaBox.addBox(hdlr); - userDataBox.addBox(metaBox); - } - AppleItemListBox ilst; - if ((ilst = Path.getPath(metaBox, "ilst")) == null) { - ilst = new AppleItemListBox(); - metaBox.addBox(ilst); - - } - if (freeBox == null) { - freeBox = new FreeBox(128 * 1024); - metaBox.addBox(freeBox); - } - // Got Apple List Box - - AppleNameBox nam; - if ((nam = Path.getPath(ilst, "©nam")) == null) { - nam = new AppleNameBox(); - } - nam.setDataCountry(0); - nam.setDataLanguage(0); - nam.setValue(title); - ilst.addBox(nam); - - long sizeAfter = moov.getSize(); - long diff = sizeAfter - sizeBefore; - // This is the difference of before/after - - // can we compensate by resizing a Free Box we have found? - if (freeBox.getData().limit() > diff) { - // either shrink or grow! - freeBox.setData(ByteBuffer.allocate((int) (freeBox.getData().limit() - diff))); - sizeAfter = moov.getSize(); - diff = sizeAfter - sizeBefore; - } - if (correctOffset && diff != 0) { - correctChunkOffsets(moov, diff); - } - BetterByteArrayOutputStream baos = new BetterByteArrayOutputStream(); - moov.getBox(Channels.newChannel(baos)); - isoFile.close(); - FileChannel fc; - if (diff != 0) { - // this is not good: We have to insert bytes in the middle of the file - // and this costs time as it requires re-writing most of the file's data - fc = splitFileAndInsert(videoFile, offset, sizeAfter - sizeBefore); - } else { - // simple overwrite of something with the file - fc = new RandomAccessFile(videoFile, "rw").getChannel(); - } - fc.position(offset); - fc.write(ByteBuffer.wrap(baos.getBuffer(), 0, baos.size())); - fc.close(); - } - - FreeBox findFreeBox(Container c) { - for (Box box : c.getBoxes()) { - System.err.println(box.getType()); - if (box instanceof FreeBox) { - return (FreeBox) box; - } - if (box instanceof Container) { - FreeBox freeBox = findFreeBox((Container) box); - if (freeBox != null) { - return freeBox; - } - } - } - return null; - } - - private void correctChunkOffsets(MovieBox movieBox, long correction) { - List chunkOffsetBoxes = Path.getPaths((Box) movieBox, "trak/mdia[0]/minf[0]/stbl[0]/stco[0]"); - if (chunkOffsetBoxes.isEmpty()) { - chunkOffsetBoxes = Path.getPaths((Box) movieBox, "trak/mdia[0]/minf[0]/stbl[0]/st64[0]"); - } - for (ChunkOffsetBox chunkOffsetBox : chunkOffsetBoxes) { - long[] cOffsets = chunkOffsetBox.getChunkOffsets(); - for (int i = 0; i < cOffsets.length; i++) { - cOffsets[i] += correction; - } - } - } - - private static class BetterByteArrayOutputStream extends ByteArrayOutputStream { - byte[] getBuffer() { - return buf; - } - } - - -} \ No newline at end of file diff --git a/src/main/java/com/greinet/tvtotalripper/MetaDataWriter.java b/src/main/java/com/greinet/tvtotalripper/MetaDataWriter.java index f8bca9d..343d7c6 100644 --- a/src/main/java/com/greinet/tvtotalripper/MetaDataWriter.java +++ b/src/main/java/com/greinet/tvtotalripper/MetaDataWriter.java @@ -1,231 +1,234 @@ -package com.greinet.tvtotalripper; - - -import org.mp4parser.Box; -import org.mp4parser.Container; -import org.mp4parser.IsoFile; -import org.mp4parser.boxes.apple.AppleItemListBox; -import org.mp4parser.boxes.apple.AppleNameBox; -import org.mp4parser.boxes.iso14496.part12.*; -import org.mp4parser.tools.Path; - -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.util.List; -import org.mp4parser.boxes.apple.AppleArtistBox; -import org.mp4parser.boxes.apple.AppleTVEpisodeBox; -import org.mp4parser.boxes.apple.AppleTVEpisodeNumberBox; -import org.mp4parser.boxes.apple.AppleTVSeasonBox; -import org.mp4parser.boxes.apple.AppleTVShowBox; - -/** - * Change metadata and make sure chunkoffsets are corrected. - */ -public class MetaDataWriter { - - private FileChannel splitFileAndInsert(File f, long pos, long length) throws IOException { - FileChannel read = new RandomAccessFile(f, "r").getChannel(); - File tmp = File.createTempFile("ChangeMetaData", "splitFileAndInsert"); - FileChannel tmpWrite = new RandomAccessFile(tmp, "rw").getChannel(); - read.position(pos); - tmpWrite.transferFrom(read, 0, read.size() - pos); - read.close(); - FileChannel write = new RandomAccessFile(f, "rw").getChannel(); - write.position(pos + length); - tmpWrite.position(0); - long transferred = 0; - while ((transferred += tmpWrite.transferTo(0, tmpWrite.size() - transferred, write)) != tmpWrite.size()) { - } - tmpWrite.close(); - tmp.delete(); - return write; - } - - - private boolean needsOffsetCorrection(IsoFile isoFile) { - if (Path.getPath(isoFile, "moov[0]/mvex[0]") != null) { - // Fragmented files don't need a correction - return false; - } else { - // no correction needed if mdat is before moov as insert into moov want change the offsets of mdat - for (Box box : isoFile.getBoxes()) { - if ("moov".equals(box.getType())) { - return true; - } - if ("mdat".equals(box.getType())) { - return false; - } - } - throw new RuntimeException("I need moov or mdat. Otherwise all this doesn't make sense"); - } - } - - public void writeMetadata(String videoFilePath, String title, String artist, String show, String episode) throws IOException { - - File videoFile = new File(videoFilePath); - if (!videoFile.exists()) { - throw new FileNotFoundException("File " + videoFilePath + " not exists"); - } - - if (!videoFile.canWrite()) { - throw new IllegalStateException("No write permissions to file " + videoFilePath); - } - IsoFile isoFile = new IsoFile(videoFilePath); - - MovieBox moov = isoFile.getBoxes(MovieBox.class).get(0); - FreeBox freeBox = findFreeBox(moov); - - boolean correctOffset = needsOffsetCorrection(isoFile); - long sizeBefore = moov.getSize(); - long offset = 0; - for (Box box : isoFile.getBoxes()) { - if ("moov".equals(box.getType())) { - break; - } - offset += box.getSize(); - } - - // Create structure or just navigate to Apple List Box. - UserDataBox userDataBox; - if ((userDataBox = Path.getPath(moov, "udta")) == null) { - userDataBox = new UserDataBox(); - moov.addBox(userDataBox); - } - MetaBox metaBox; - if ((metaBox = Path.getPath(userDataBox, "meta")) == null) { - metaBox = new MetaBox(); - HandlerBox hdlr = new HandlerBox(); - hdlr.setHandlerType("mdir"); - metaBox.addBox(hdlr); - userDataBox.addBox(metaBox); - } - AppleItemListBox ilst; - if ((ilst = Path.getPath(metaBox, "ilst")) == null) { - ilst = new AppleItemListBox(); - metaBox.addBox(ilst); - - } - if (freeBox == null) { - freeBox = new FreeBox(128 * 1024); - metaBox.addBox(freeBox); - } - - if(title != null){ - // Write the title - AppleNameBox nam; - if ((nam = Path.getPath(ilst, "©nam")) == null) { - nam = new AppleNameBox(); - } - nam.setDataCountry(0); - nam.setDataLanguage(0); - nam.setValue(title); - ilst.addBox(nam); - } - - if(artist!=null){ - // Write the artist - AppleArtistBox art; - if ((art = Path.getPath(ilst, "©ART")) == null) { - art = new AppleArtistBox(); - } - art.setDataCountry(0); - art.setDataLanguage(0); - art.setValue(artist); - ilst.addBox(art); - } - - if(episode!=null){ - // Write the episode title - AppleTVEpisodeNumberBox ep; - if ((ep = Path.getPath(ilst, "tven")) == null) { - ep = new AppleTVEpisodeNumberBox(); - } - ep.setDataCountry(0); - ep.setDataLanguage(0); - ep.setValue(episode); - ilst.addBox(ep); - } - - if(show!=null){ - //Write the show - AppleTVShowBox sh; - if ((sh = Path.getPath(ilst, "tvsh")) == null) { - sh = new AppleTVShowBox(); - } - sh.setDataCountry(0); - sh.setDataLanguage(0); - sh.setValue(show); - ilst.addBox(sh); - } - - - - long sizeAfter = moov.getSize(); - long diff = sizeAfter - sizeBefore; - // This is the difference of before/after - - // can we compensate by resizing a Free Box we have found? - if (freeBox.getData().limit() > diff) { - // either shrink or grow! - freeBox.setData(ByteBuffer.allocate((int) (freeBox.getData().limit() - diff))); - sizeAfter = moov.getSize(); - diff = sizeAfter - sizeBefore; - } - if (correctOffset && diff != 0) { - correctChunkOffsets(moov, diff); - } - BetterByteArrayOutputStream baos = new BetterByteArrayOutputStream(); - moov.getBox(Channels.newChannel(baos)); - isoFile.close(); - FileChannel fc; - if (diff != 0) { - // this is not good: We have to insert bytes in the middle of the file - // and this costs time as it requires re-writing most of the file's data - fc = splitFileAndInsert(videoFile, offset, sizeAfter - sizeBefore); - } else { - // simple overwrite of something with the file - fc = new RandomAccessFile(videoFile, "rw").getChannel(); - } - fc.position(offset); - fc.write(ByteBuffer.wrap(baos.getBuffer(), 0, baos.size())); - fc.close(); - } - - FreeBox findFreeBox(Container c) { - for (Box box : c.getBoxes()) { - if (box instanceof FreeBox) { - return (FreeBox) box; - } - if (box instanceof Container) { - FreeBox freeBox = findFreeBox((Container) box); - if (freeBox != null) { - return freeBox; - } - } - } - return null; - } - - private void correctChunkOffsets(MovieBox movieBox, long correction) { - List chunkOffsetBoxes = Path.getPaths((Box) movieBox, "trak/mdia[0]/minf[0]/stbl[0]/stco[0]"); - if (chunkOffsetBoxes.isEmpty()) { - chunkOffsetBoxes = Path.getPaths((Box) movieBox, "trak/mdia[0]/minf[0]/stbl[0]/st64[0]"); - } - for (ChunkOffsetBox chunkOffsetBox : chunkOffsetBoxes) { - long[] cOffsets = chunkOffsetBox.getChunkOffsets(); - for (int i = 0; i < cOffsets.length; i++) { - cOffsets[i] += correction; - } - } - } - - private static class BetterByteArrayOutputStream extends ByteArrayOutputStream { - byte[] getBuffer() { - return buf; - } - } - - +package com.greinet.tvtotalripper; + +import org.mp4parser.Box; +import org.mp4parser.Container; +import org.mp4parser.IsoFile; +import org.mp4parser.boxes.apple.AppleItemListBox; +import org.mp4parser.boxes.apple.AppleNameBox; +import org.mp4parser.boxes.iso14496.part12.*; +import org.mp4parser.tools.Path; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.util.List; +import org.mp4parser.boxes.apple.AppleArtistBox; +import org.mp4parser.boxes.apple.AppleTVEpisodeNumberBox; +import org.mp4parser.boxes.apple.AppleTVShowBox; + +/** + * Utility class to change mp4 metatdata, more or less copied from https://github.com/sannies/mp4parser/blob/master/examples/src/main/java/org/mp4parser/examples/metadata/MetaDataInsert.java + */ +public class MetaDataWriter { + + private FileChannel splitFileAndInsert(File f, long pos, long length) throws IOException { + FileChannel read = new RandomAccessFile(f, "r").getChannel(); + File tmp = File.createTempFile("ChangeMetaData", "splitFileAndInsert"); + FileChannel tmpWrite = new RandomAccessFile(tmp, "rw").getChannel(); + read.position(pos); + tmpWrite.transferFrom(read, 0, read.size() - pos); + read.close(); + FileChannel write = new RandomAccessFile(f, "rw").getChannel(); + write.position(pos + length); + tmpWrite.position(0); + long transferred = 0; + while ((transferred += tmpWrite.transferTo(0, tmpWrite.size() - transferred, write)) != tmpWrite.size()) { + } + tmpWrite.close(); + tmp.delete(); + return write; + } + + private boolean needsOffsetCorrection(IsoFile isoFile) { + if (Path.getPath(isoFile, "moov[0]/mvex[0]") != null) { + // Fragmented files don't need a correction + return false; + } else { + // no correction needed if mdat is before moov as insert into moov want change the offsets of mdat + for (Box box : isoFile.getBoxes()) { + if ("moov".equals(box.getType())) { + return true; + } + if ("mdat".equals(box.getType())) { + return false; + } + } + throw new RuntimeException("I need moov or mdat. Otherwise all this doesn't make sense"); + } + } + + /** + * Write itunes metadata to a mp4 file + * @param videoFilePath the file path + * @param title the title, or null + * @param artist the artist, or null + * @param show the show, or null + * @param episode the episode, or null + * @throws IOException error heandling mp4 file + */ + public void writeMetadata(String videoFilePath, String title, String artist, String show, String episode) throws IOException { + + File videoFile = new File(videoFilePath); + if (!videoFile.exists()) { + throw new FileNotFoundException("File " + videoFilePath + " not exists"); + } + + if (!videoFile.canWrite()) { + throw new IllegalStateException("No write permissions to file " + videoFilePath); + } + IsoFile isoFile = new IsoFile(videoFilePath); + + MovieBox moov = isoFile.getBoxes(MovieBox.class).get(0); + FreeBox freeBox = findFreeBox(moov); + + boolean correctOffset = needsOffsetCorrection(isoFile); + long sizeBefore = moov.getSize(); + long offset = 0; + for (Box box : isoFile.getBoxes()) { + if ("moov".equals(box.getType())) { + break; + } + offset += box.getSize(); + } + + // Create structure or just navigate to Apple List Box. + UserDataBox userDataBox; + if ((userDataBox = Path.getPath(moov, "udta")) == null) { + userDataBox = new UserDataBox(); + moov.addBox(userDataBox); + } + MetaBox metaBox; + if ((metaBox = Path.getPath(userDataBox, "meta")) == null) { + metaBox = new MetaBox(); + HandlerBox hdlr = new HandlerBox(); + hdlr.setHandlerType("mdir"); + metaBox.addBox(hdlr); + userDataBox.addBox(metaBox); + } + AppleItemListBox ilst; + if ((ilst = Path.getPath(metaBox, "ilst")) == null) { + ilst = new AppleItemListBox(); + metaBox.addBox(ilst); + + } + if (freeBox == null) { + freeBox = new FreeBox(128 * 1024); + metaBox.addBox(freeBox); + } + + if(title != null){ + // Write the title + AppleNameBox nam; + if ((nam = Path.getPath(ilst, "©nam")) == null) { + nam = new AppleNameBox(); + } + nam.setDataCountry(0); + nam.setDataLanguage(0); + nam.setValue(title); + ilst.addBox(nam); + } + + if(artist!=null){ + // Write the artist + AppleArtistBox art; + if ((art = Path.getPath(ilst, "©ART")) == null) { + art = new AppleArtistBox(); + } + art.setDataCountry(0); + art.setDataLanguage(0); + art.setValue(artist); + ilst.addBox(art); + } + + if(episode!=null){ + // Write the episode title + AppleTVEpisodeNumberBox ep; + if ((ep = Path.getPath(ilst, "tven")) == null) { + ep = new AppleTVEpisodeNumberBox(); + } + ep.setDataCountry(0); + ep.setDataLanguage(0); + ep.setValue(episode); + ilst.addBox(ep); + } + + if(show!=null){ + //Write the show + AppleTVShowBox sh; + if ((sh = Path.getPath(ilst, "tvsh")) == null) { + sh = new AppleTVShowBox(); + } + sh.setDataCountry(0); + sh.setDataLanguage(0); + sh.setValue(show); + ilst.addBox(sh); + } + + long sizeAfter = moov.getSize(); + long diff = sizeAfter - sizeBefore; + // This is the difference of before/after + + // can we compensate by resizing a Free Box we have found? + if (freeBox.getData().limit() > diff) { + // either shrink or grow! + freeBox.setData(ByteBuffer.allocate((int) (freeBox.getData().limit() - diff))); + sizeAfter = moov.getSize(); + diff = sizeAfter - sizeBefore; + } + if (correctOffset && diff != 0) { + correctChunkOffsets(moov, diff); + } + BetterByteArrayOutputStream baos = new BetterByteArrayOutputStream(); + moov.getBox(Channels.newChannel(baos)); + isoFile.close(); + FileChannel fc; + if (diff != 0) { + // this is not good: We have to insert bytes in the middle of the file + // and this costs time as it requires re-writing most of the file's data + fc = splitFileAndInsert(videoFile, offset, sizeAfter - sizeBefore); + } else { + // simple overwrite of something with the file + fc = new RandomAccessFile(videoFile, "rw").getChannel(); + } + fc.position(offset); + fc.write(ByteBuffer.wrap(baos.getBuffer(), 0, baos.size())); + fc.close(); + } + + private FreeBox findFreeBox(Container c) { + for (Box box : c.getBoxes()) { + if (box instanceof FreeBox) { + return (FreeBox) box; + } + if (box instanceof Container) { + FreeBox freeBox = findFreeBox((Container) box); + if (freeBox != null) { + return freeBox; + } + } + } + return null; + } + + private void correctChunkOffsets(MovieBox movieBox, long correction) { + List chunkOffsetBoxes = Path.getPaths((Box) movieBox, "trak/mdia[0]/minf[0]/stbl[0]/stco[0]"); + if (chunkOffsetBoxes.isEmpty()) { + chunkOffsetBoxes = Path.getPaths((Box) movieBox, "trak/mdia[0]/minf[0]/stbl[0]/st64[0]"); + } + for (ChunkOffsetBox chunkOffsetBox : chunkOffsetBoxes) { + long[] cOffsets = chunkOffsetBox.getChunkOffsets(); + for (int i = 0; i < cOffsets.length; i++) { + cOffsets[i] += correction; + } + } + } + + private static class BetterByteArrayOutputStream extends ByteArrayOutputStream { + byte[] getBuffer() { + return buf; + } + } + + } \ No newline at end of file diff --git a/src/main/java/com/greinet/tvtotalripper/SwingInterface.java b/src/main/java/com/greinet/tvtotalripper/SwingInterface.java deleted file mode 100644 index 06884e3..0000000 --- a/src/main/java/com/greinet/tvtotalripper/SwingInterface.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.greinet.tvtotalripper; - -import java.awt.Dimension; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.ArrayList; -import java.util.List; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JProgressBar; - -/** - * - * @author agreiner - */ -public class SwingInterface implements PropertyChangeListener { - - private final JFrame frame; - private final JLabel label; - private final JProgressBar progressBar; - - private List changeListener = new ArrayList<>(); - - public SwingInterface(){ - - frame = new JFrame("TV Total Ripper"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - - label = new JLabel("Testlabel"); - label.setPreferredSize(new Dimension(200, 30)); - - progressBar = new JProgressBar(0, 100); - progressBar.setPreferredSize(new Dimension(200, 30)); - progressBar.setStringPainted(true); - - - frame.add(label); - frame.add(progressBar); - - - frame.pack(); - frame.setVisible(true); - } - - public static void main(String[] args) { - - javax.swing.SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - SwingInterface i = new SwingInterface(); - i.startDownload(); - } - }); - - } - - public void startDownload(){ - //DownloadTask task = new DownloadTask(label, "https://cldf-od.r53.cdn.tv1.eu/secdl/06d6d246daa2c7ec0ffb2f8281149072/6066001f/11021brainpool/ondemand/3583brainpool/163840/myspass2009/11/33/2171/9642/9642_61.mp4", ""); - //task.addPropertyChangeListener(this); - //task.execute(); - } - - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals("progress1")) { - int progress = (Integer) evt.getNewValue(); - progressBar.setValue(progress); - } - } - -} diff --git a/src/main/java/com/greinet/tvtotalripper/crawler/CrawlerUtil.java b/src/main/java/com/greinet/tvtotalripper/crawler/CrawlerUtil.java index 7ee1ac9..4c7cecf 100644 --- a/src/main/java/com/greinet/tvtotalripper/crawler/CrawlerUtil.java +++ b/src/main/java/com/greinet/tvtotalripper/crawler/CrawlerUtil.java @@ -1,179 +1,189 @@ -package com.greinet.tvtotalripper.crawler; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.chrome.ChromeDriver; -import org.openqa.selenium.chrome.ChromeOptions; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; - -/** - * Helper class to get show, season und episode elements from the website - * @author agreiner - */ -public class CrawlerUtil { - - static{ - System.setProperty("webdriver.chrome.driver", "resources/chromedriver.exe"); - } - - /** The logger */ - private static final Logger logger = LogManager.getLogger(CrawlerUtil.class); - /** The WebDriver for show information */ - private final static WebDriver driverShows = createShowDriver(); - /** The WebDriver for season and episode information */ - private final static WebDriver driverSeasonsAndEpisodes = createChromeDriver(); - /** The WebDriver for fetchfile.net URL conversion */ - private final static WebDriver driverFetchFile = createFetchFileDriver(); - - public static String show = ""; - /** - * Create a basic ChromeDriver - * @return - */ - private static WebDriver createChromeDriver(){ - ChromeOptions options = new ChromeOptions(); - options.addArguments("start-maximized"); - return new ChromeDriver(options); - } - - /** - * Create the WebDriver to get show information - * @return the WebDriver - */ - private static WebDriver createShowDriver(){ - WebDriver driver = createChromeDriver(); - driver.get("https://www.myspass.de/sendungen-a-bis-z/"); - return driver; - } - - private static WebDriver createFetchFileDriver(){ - WebDriver driver = createChromeDriver(); - driver.get("https://de.fetchfile.net/herunterladen-von-myspass"); - return driver; - } - - /** - * Navigate to the given URL or do nothing, if already at that URL - * @param url the URL - */ - private static void navigateToShow(String url){ - if(!driverSeasonsAndEpisodes.getCurrentUrl().equals(url)){ - driverSeasonsAndEpisodes.get(url); - } - driverSeasonsAndEpisodes.findElement(By.xpath("/html/body/div[4]/div[1]/div[2]/ul/li[2]/a")).click(); - } - - /** - * Returns the WebElements representing the shows. - * @return the show WebElements - */ - public static List getShows(){ - return driverShows.findElements(By.xpath("/html/body/div[5]/div/div/div/div/a")); - } - - /** - * Returns the Webelements represeting the seasons of a specific show - * @param url the URL to the show - * @return the season WebElements - */ - public static List getSeasons(String url){ - navigateToShow(url); - - String urlNotTrailingSlash = url.substring(0,url.lastIndexOf("/")); - - show = urlNotTrailingSlash.substring(urlNotTrailingSlash.lastIndexOf("/")+1, urlNotTrailingSlash.length()); - return driverSeasonsAndEpisodes.findElements(By.xpath("/html/body/div[4]/div[1]/div[3]/div[2]/div[1]/select/option")); - - } - - /** - * Returns the information from the episodes from a specific show and season packed into a wrapper class - * @param url the URL of the show - * @param seasonName the name of the season - * @return the list of episodes - */ - public static List getEpisodes(String url, String seasonName){ - - // navigate the WebDriver to the specified URL - navigateToShow(url); - - // Search the specified season - List elements = driverSeasonsAndEpisodes.findElements(By.xpath("/html/body/div[4]/div[1]/div[3]/div[2]/div[1]/select/option")); - Optional seasonElement = elements.stream().filter(e -> e.getText().equals(seasonName)).findFirst(); - - // If the season was found, navigate there - if(seasonElement.isPresent()){ - seasonElement.get().click(); - - // Wait for the website to load and then switch to the list view - try { - Thread.sleep(2000); - } catch (InterruptedException ex) { - logger.log(Level.ERROR, "Could not find list view button for url:[{0}], season:[{1}]",url, seasonName); - } - driverSeasonsAndEpisodes.findElement(By.className("listView--toggle_input")).click(); - - // Get the parent div of the episode information list - List episodeListDivs = driverSeasonsAndEpisodes.findElements(By.xpath("/html/body/div[4]/div[1]/div[3]/div[2]/div")); - List filteredNoDisplay = episodeListDivs.stream().filter(e -> !"none".equals(e.getCssValue("display"))).collect(Collectors.toList()); - - - if(!filteredNoDisplay.isEmpty()){ - // Get the Webelements with the link and title information - List linksToEpisodes = new ArrayList<>(); - filteredNoDisplay.forEach(e -> - linksToEpisodes.addAll(e.findElements(By.xpath(".//div/table/tbody/tr/td/a"))) - ); - // Get the WebElements with the duration information - List durationOfEpisodes = new ArrayList<>(); - filteredNoDisplay.forEach(e -> - durationOfEpisodes.addAll(e.findElements(By.xpath(".//div/table/tbody/tr/td[3]"))) - ); - // Put the information into episode wrappers and return the list - List wrappers = new ArrayList<>(); - for(int i=0;i(); - } - }else{ - logger.log(Level.ERROR, "No seaons found for url:[{0}], season:[{1}]",url, seasonName); - return new ArrayList<>(); - } - } - - public static String getFetchfileURL(String myspassURL){ - synchronized(driverFetchFile){ - WebElement videoPathInput = driverFetchFile.findElement(By.id("videoPath")); - videoPathInput.sendKeys(myspassURL); - WebElement submitButton = driverFetchFile.findElement(By.id("home-submit")); - submitButton.click(); - WebDriverWait wait = new WebDriverWait(driverFetchFile, 20); - WebElement downloadButton = wait.until(ExpectedConditions.elementToBeClickable(By.className("download-link"))); - String downloadLink = downloadButton.getAttribute("href"); - driverFetchFile.get("https://de.fetchfile.net/herunterladen-von-myspass"); - - // Close advertisement tabs - List tabs = new ArrayList<>(driverFetchFile.getWindowHandles()); - for (int i=1;i getShows(){ + return driverShows.findElements(By.xpath("/html/body/div[5]/div/div/div/div/a")); + } + + /** + * Returns the Webelements represeting the seasons of a specific show + * @param url the URL to the show + * @return the season WebElements + */ + public static List getSeasons(String url){ + navigateToShow(url); + + String urlNotTrailingSlash = url.substring(0,url.lastIndexOf("/")); + + show = urlNotTrailingSlash.substring(urlNotTrailingSlash.lastIndexOf("/")+1, urlNotTrailingSlash.length()); + return driverSeasonsAndEpisodes.findElements(By.xpath("/html/body/div[4]/div[1]/div[3]/div[2]/div[1]/select/option")); + + } + + /** + * Returns the information from the episodes from a specific show and season packed into a wrapper class + * @param url the URL of the show + * @param seasonName the name of the season + * @return the list of episodes + */ + public static List getEpisodes(String url, String seasonName){ + + // navigate the WebDriver to the specified URL + navigateToShow(url); + + // Search the specified season + List elements = driverSeasonsAndEpisodes.findElements(By.xpath("/html/body/div[4]/div[1]/div[3]/div[2]/div[1]/select/option")); + Optional seasonElement = elements.stream().filter(e -> e.getText().equals(seasonName)).findFirst(); + + // If the season was found, navigate there + if(seasonElement.isPresent()){ + seasonElement.get().click(); + + // Wait for the website to load and then switch to the list view + try { + Thread.sleep(2000); + } catch (InterruptedException ex) { + logger.log(Level.ERROR, "Could not find list view button for url:[{0}], season:[{1}]",url, seasonName); + } + driverSeasonsAndEpisodes.findElement(By.className("listView--toggle_input")).click(); + + // Get the parent div of the episode information list + List episodeListDivs = driverSeasonsAndEpisodes.findElements(By.xpath("/html/body/div[4]/div[1]/div[3]/div[2]/div")); + List filteredNoDisplay = episodeListDivs.stream().filter(e -> !"none".equals(e.getCssValue("display"))).collect(Collectors.toList()); + + + if(!filteredNoDisplay.isEmpty()){ + // Get the Webelements with the link and title information + List linksToEpisodes = new ArrayList<>(); + filteredNoDisplay.forEach(e -> + linksToEpisodes.addAll(e.findElements(By.xpath(".//div/table/tbody/tr/td/a"))) + ); + // Get the WebElements with the duration information + List durationOfEpisodes = new ArrayList<>(); + filteredNoDisplay.forEach(e -> + durationOfEpisodes.addAll(e.findElements(By.xpath(".//div/table/tbody/tr/td[3]"))) + ); + // Put the information into episode wrappers and return the list + List wrappers = new ArrayList<>(); + for(int i=0;i(); + } + }else{ + logger.log(Level.ERROR, "No seaons found for url:[{0}], season:[{1}]",url, seasonName); + return new ArrayList<>(); + } + } + + /** + * Returns the FetchFile.net URL to the myspass.com video file + * @param myspassURL the URL to the myspass.com video + * @return the FetchFile.net URL + */ + public static String getFetchfileURL(String myspassURL){ + synchronized(driverFetchFile){ + WebElement videoPathInput = driverFetchFile.findElement(By.id("videoPath")); + videoPathInput.sendKeys(myspassURL); + WebElement submitButton = driverFetchFile.findElement(By.id("home-submit")); + submitButton.click(); + WebDriverWait wait = new WebDriverWait(driverFetchFile, 20); + WebElement downloadButton = wait.until(ExpectedConditions.elementToBeClickable(By.className("download-link"))); + String downloadLink = downloadButton.getAttribute("href"); + driverFetchFile.get("https://de.fetchfile.net/herunterladen-von-myspass"); + + // Close advertisement tabs + List tabs = new ArrayList<>(driverFetchFile.getWindowHandles()); + for (int i=1;i 0) { - fileName = disposition.substring(index + 10, - disposition.length() - 1); - } - } else { - // extracts file name from URL - fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1, - fileUrl.length()); - } - - // output for debugging purpose only - System.out.println("Content-Type = " + contentType); - System.out.println("Content-Disposition = " + disposition); - System.out.println("Content-Length = " + contentLength); - System.out.println("fileName = " + fileName); - - // opens input stream from the HTTP connection - inputStream = httpConn.getInputStream(); - - } else { - throw new IOException( - "No file to download. Server replied HTTP code: " - + responseCode); - } - } - - public void disconnect() throws IOException { - inputStream.close(); - httpConn.disconnect(); - } - - public String getFileName() { - return this.fileName; - } - - public int getContentLength() { - return this.contentLength; - } - - public InputStream getInputStream() { - return this.inputStream; - } - -} +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.greinet.tvtotalripper.download; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * + * @author agreiner + */ +public class ConnectionUtil { + + /** The connection to the download URL */ + private HttpURLConnection httpConn; + /** The input stream from the connection */ + private InputStream inputStream; + /** The download size */ + private int contentLength; + + /** + * Prepare the download connection and open the input stream + * @param fileUrl the URL to the file + * @throws IOException error preparing download + */ + public void prepare(String fileUrl) throws IOException{ + URL url = new URL(fileUrl); + httpConn = (HttpURLConnection) url.openConnection(); + int responseCode = httpConn.getResponseCode(); + + // always check HTTP response code first + if (responseCode == HttpURLConnection.HTTP_OK) { + contentLength = httpConn.getContentLength(); + // opens input stream from the HTTP connection + inputStream = httpConn.getInputStream(); + + } else { + throw new IOException( + "No file to download. Server replied HTTP code: " + + responseCode); + } + } + + /** + * Close the inout stream and disconnect the download + * @throws IOException + */ + public void disconnect() throws IOException { + inputStream.close(); + httpConn.disconnect(); + } + + /** + * Get the content length of the download + * @return the content length + */ + public int getContentLength() { + return this.contentLength; + } + + /** + * Get the download related inout stream + * @return the input stream + */ + public InputStream getInputStream() { + return this.inputStream; + } + +} diff --git a/src/main/java/com/greinet/tvtotalripper/download/DownloadTask.java b/src/main/java/com/greinet/tvtotalripper/download/DownloadTask.java index edd85b8..7becf59 100644 --- a/src/main/java/com/greinet/tvtotalripper/download/DownloadTask.java +++ b/src/main/java/com/greinet/tvtotalripper/download/DownloadTask.java @@ -1,95 +1,130 @@ -package com.greinet.tvtotalripper.download; - -import com.greinet.tvtotalripper.MetaDataWriter; -import com.greinet.tvtotalripper.crawler.CrawlerUtil; -import com.greinet.tvtotalripper.crawler.EpisodeWrapper; -import com.greinet.tvtotalripper.ui.SettingsRipperPanel; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import javax.swing.SwingWorker; - -/** - * - * @author agreiner - */ -public class DownloadTask extends SwingWorker { - private static final int BUFFER_SIZE = 4096; - - private final EpisodeWrapper episodeWrapper; - - - public DownloadTask(EpisodeWrapper episodeWrapper) { - this.episodeWrapper = episodeWrapper; - } - - public EpisodeWrapper getEpisodeWrapper() { - return episodeWrapper; - } - - @Override - public String toString() { - return episodeWrapper.getTitle(); - } - - public long fileSize = 0; - - public long totalBytesRead = 0; - - public int percentCompleted = 0; - - /** - * Executed in background thread - */ - @Override - protected Void doInBackground() throws Exception { - try { - String downloadURL = CrawlerUtil.getFetchfileURL(episodeWrapper.getUrl()); - - ConnectionUtil util = new ConnectionUtil(); - util.prepare(downloadURL); - - InputStream inputStream = util.getInputStream(); - - String fixedTitle = episodeWrapper.getTitle().replace(":", " "); - - File outputFile = new File(SettingsRipperPanel.DOWNLOADFOLDER, fixedTitle+".mp4"); - FileOutputStream outputStream = new FileOutputStream(outputFile); - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead = -1; - totalBytesRead = 0; - percentCompleted = 0; - int oldPercentCompleted = 0; - fileSize = util.getContentLength(); - - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - totalBytesRead += bytesRead; - percentCompleted = (int) (totalBytesRead * 100 / fileSize); - firePropertyChange(Long.toString(episodeWrapper.getId()), oldPercentCompleted, percentCompleted); - oldPercentCompleted = percentCompleted; - //setProgress(percentCompleted); - } - - outputStream.close(); - - util.disconnect(); - - MetaDataWriter mdp = new MetaDataWriter(); - mdp.writeMetadata(outputFile.getAbsolutePath(), episodeWrapper.getTitle(), episodeWrapper.getShow(), episodeWrapper.getShow(), episodeWrapper.getEpisode()); - - } catch (IOException ex) { - cancel(true); - } - return null; - } - - /** - * Executed in Swing's event dispatching thread - */ - @Override - protected void done() { - firePropertyChange(Long.toString(episodeWrapper.getId()), 0, 101); - } -} +package com.greinet.tvtotalripper.download; + +import com.greinet.tvtotalripper.crawler.CrawlerUtil; +import com.greinet.tvtotalripper.crawler.EpisodeWrapper; +import com.greinet.tvtotalripper.ui.SettingsRipperPanel; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import javax.swing.SwingWorker; + +/** + * A task representing a single file download + */ +public class DownloadTask extends SwingWorker { + + /** The download buffer size */ + private static final int BUFFER_SIZE = 4096; + /** The episode of the downloaded file */ + private final EpisodeWrapper episodeWrapper; + + /** + * Create a download task for a episode + * @param episodeWrapper + */ + public DownloadTask(EpisodeWrapper episodeWrapper) { + this.episodeWrapper = episodeWrapper; + } + + /** + * Get the episode of the download + * @return the episode + */ + public EpisodeWrapper getEpisodeWrapper() { + return episodeWrapper; + } + + @Override + public String toString() { + return episodeWrapper.getTitle(); + } + + /** The bytes size of the file to download */ + private long fileSize = 0; + + /** the amount of downloaded bytes */ + private long totalBytesRead = 0; + + /** The completion percentage */ + private int percentCompleted = 0; + + /** + * Executed in background thread + */ + @Override + protected Void doInBackground() throws Exception { + try { + String downloadURL = CrawlerUtil.getFetchfileURL(episodeWrapper.getUrl()); + + ConnectionUtil util = new ConnectionUtil(); + util.prepare(downloadURL); + + InputStream inputStream = util.getInputStream(); + + String fixedTitle = episodeWrapper.getTitle().replace(":", " "); + + File outputFile = new File(SettingsRipperPanel.DOWNLOADFOLDER, fixedTitle+".mp4"); + try (FileOutputStream outputStream = new FileOutputStream(outputFile)) { + byte[] buffer = new byte[BUFFER_SIZE]; + int bytesRead; + totalBytesRead = 0; + percentCompleted = 0; + int oldPercentCompleted = 0; + fileSize = util.getContentLength(); + + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + totalBytesRead += bytesRead; + percentCompleted = (int) (totalBytesRead * 100 / fileSize); + firePropertyChange(Long.toString(episodeWrapper.getId()), oldPercentCompleted, percentCompleted); + oldPercentCompleted = percentCompleted; + //setProgress(percentCompleted); + } + } + + util.disconnect(); + + + // Enable MetaDataWriter + // MetaDataWriter mdp = new MetaDataWriter(); + // mdp.writeMetadata(outputFile.getAbsolutePath(), episodeWrapper.getTitle(), episodeWrapper.getShow(), episodeWrapper.getShow(), episodeWrapper.getEpisode()); + + } catch (IOException ex) { + cancel(true); + } + return null; + } + + /** + * Get the download file size in bytes + * @return the file size in bytes + */ + public long getFileSize() { + return fileSize; + } + + /** + * Get the amount of already downloaded bytes + * @return the amount of already downloaded bytes + */ + public long getTotalBytesRead() { + return totalBytesRead; + } + + /** + * Get the completiton percentage + * @return the completition percentage + */ + public int getPercentCompleted() { + return percentCompleted; + } + + /** + * Fire a property change event with the progress value of 101 meaning that the task is done + */ + @Override + protected void done() { + firePropertyChange(Long.toString(episodeWrapper.getId()), 0, 101); + } +} diff --git a/src/main/java/com/greinet/tvtotalripper/ui/BaseRipperPanel.java b/src/main/java/com/greinet/tvtotalripper/ui/BaseRipperPanel.java index cf48cc7..e11305a 100644 --- a/src/main/java/com/greinet/tvtotalripper/ui/BaseRipperPanel.java +++ b/src/main/java/com/greinet/tvtotalripper/ui/BaseRipperPanel.java @@ -1,116 +1,141 @@ -package com.greinet.tvtotalripper.ui; - -import java.awt.Color; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextField; -import javax.swing.event.ListSelectionEvent; - -/** - * - * @author agreiner - */ -public class BaseRipperPanel { - - private final JPanel panel; - private JList listPanel; - private JTextField textField; - private final JButton button; - private final JLabel label; - private Map elements; - - public BaseRipperPanel(Map elements){ - this.elements = elements; - - if(elements.isEmpty()){ - elements.put("Dummy-Key", "Dummy-URL"); - } - - panel = new JPanel(false); - panel.setLayout(new GridBagLayout()); - - GridBagConstraints c = new GridBagConstraints(); - c.gridx = 0; - c.gridy = 0; - c.gridwidth = 3; - c.fill = GridBagConstraints.BOTH; - c.weightx = 0.75; - - textField = new JTextField(); - textField.setEditable(false); - panel.add(textField,c); - - button = new JButton("Select"); - c.gridx = 3; - c.gridwidth = 2; - c.weightx = 0.25; - - panel.add(button,c); - - - List names = new ArrayList<>(elements.keySet()); - Collections.sort(names); - - listPanel = new JList<>(names.toArray(new String[elements.keySet().size()])); - JScrollPane scrollPane = new JScrollPane(); - scrollPane.setViewportView(listPanel); - listPanel.setLayoutOrientation(JList.VERTICAL); - listPanel.setSelectedIndex(0); - - listPanel.addListSelectionListener((ListSelectionEvent e) -> { - textField.setText(listPanel.getSelectedValue()); - }); - textField.setText(listPanel.getSelectedValue()); - - c.gridx = 0; - c.gridy = 1; - c.gridwidth = 5; - c.gridheight = 5; - c.weightx = 0; - - panel.add(scrollPane,c); - - label = new JLabel(Integer.toString(elements.keySet().size())); - label.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); - - - c.gridx = 4; - c.gridy = 6; - c.gridwidth = 1; - c.gridheight = 1; - c.fill = GridBagConstraints.NONE; - c.anchor = GridBagConstraints.EAST; - - panel.add(label, c); - - - - } - - public JPanel getJPanel(){ - return panel; - } - - public JButton getJButton(){ - return button; - } - - public String getCurrentSelected(){ - return elements.get(listPanel.getSelectedValue()); - } - - public JList getJList(){ - return listPanel; - } - -} +package com.greinet.tvtotalripper.ui; + +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.event.ListSelectionEvent; + +/** + * The base panel for show and episode visualization + */ +public class BaseRipperPanel { + + /** The JPanel */ + private final JPanel panel; + /** The Jlist for the elements */ + private JList listPanel; + /** The text field for the currently selected element */ + private JTextField textField; + /** The button to select the currently selected element */ + private final JButton button; + /** The label for the element count */ + private final JLabel label; + /** The map for the elements */ + private final Map elements; + + /** + * Create a base panel the display elements and select a specific one + * @param elements the elements + */ + public BaseRipperPanel(Map elements){ + this.elements = elements; + + if(elements.isEmpty()){ + elements.put("Dummy-Key", "Dummy-URL"); + } + + panel = new JPanel(false); + panel.setLayout(new GridBagLayout()); + + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 3; + c.fill = GridBagConstraints.BOTH; + c.weightx = 0.75; + + textField = new JTextField(); + textField.setEditable(false); + panel.add(textField,c); + + button = new JButton("Select"); + c.gridx = 3; + c.gridwidth = 2; + c.weightx = 0.25; + + panel.add(button,c); + + + List names = new ArrayList<>(elements.keySet()); + Collections.sort(names); + + listPanel = new JList<>(names.toArray(new String[elements.keySet().size()])); + JScrollPane scrollPane = new JScrollPane(); + scrollPane.setViewportView(listPanel); + listPanel.setLayoutOrientation(JList.VERTICAL); + listPanel.setSelectedIndex(0); + + listPanel.addListSelectionListener((ListSelectionEvent e) -> { + textField.setText(listPanel.getSelectedValue()); + }); + textField.setText(listPanel.getSelectedValue()); + + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 5; + c.gridheight = 5; + c.weightx = 0; + + panel.add(scrollPane,c); + + label = new JLabel(Integer.toString(elements.keySet().size())); + label.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); + + + c.gridx = 4; + c.gridy = 6; + c.gridwidth = 1; + c.gridheight = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.EAST; + + panel.add(label, c); + + + + } + + /** + * Get the JPanel + * @return the JPanel + */ + public JPanel getJPanel(){ + return panel; + } + + /** + * Get the selection JButton + * @return the JButton + */ + public JButton getJButton(){ + return button; + } + + /** + * Get the String representing the currently selected element + * @return the selected element + */ + public String getCurrentSelected(){ + return elements.get(listPanel.getSelectedValue()); + } + + /** + * Get the Jlist of the elements + * @return the JList + */ + public JList getJList(){ + return listPanel; + } + +} diff --git a/src/main/java/com/greinet/tvtotalripper/ui/DownloadRipperPanel.java b/src/main/java/com/greinet/tvtotalripper/ui/DownloadRipperPanel.java index a789cba..b03ba5f 100644 --- a/src/main/java/com/greinet/tvtotalripper/ui/DownloadRipperPanel.java +++ b/src/main/java/com/greinet/tvtotalripper/ui/DownloadRipperPanel.java @@ -1,143 +1,164 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.greinet.tvtotalripper.ui; - -import com.greinet.tvtotalripper.download.DownloadTask; -import java.awt.Color; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import javax.swing.BorderFactory; -import javax.swing.DefaultListModel; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextField; -import javax.swing.ListSelectionModel; -import javax.swing.event.ListSelectionEvent; - -/** - * - * @author agreiner - */ -public class DownloadRipperPanel implements PropertyChangeListener{ - - private final JPanel panel; - private JList listTasks; - private DefaultListModel listModel; - private JTextField textSelectedTask; - private final JLabel labelTaskCount; - - private final List downloadTasks; - - private DownloadTaskInformationPanel infoPanel; - - public DownloadRipperPanel(){ - - downloadTasks = new ArrayList<>(); - - panel = new JPanel(); - panel.setLayout(new GridBagLayout()); - - GridBagConstraints c = new GridBagConstraints(); - - c.gridx = 0; - c.gridy = 0; - c.gridwidth = 5; - c.fill = GridBagConstraints.BOTH; - c.weightx = 0.5; - textSelectedTask = new JTextField(); - textSelectedTask.setEditable(false); - panel.add(textSelectedTask, c); - - - listModel = new DefaultListModel<>(); - listTasks = new JList<>(listModel); - listTasks.setSelectedIndex(ListSelectionModel.SINGLE_SELECTION); - JScrollPane scrollPane = new JScrollPane(); - scrollPane.setViewportView(listTasks); - listTasks.setLayoutOrientation(JList.VERTICAL); - - c.gridx = 0; - c.gridy = 1; - c.gridwidth = 5; - c.gridheight = 5; - c.weightx = 0.5; - panel.add(scrollPane, c); - - infoPanel = new DownloadTaskInformationPanel(); - c.gridx = 5; - c.gridy = 0; - c.gridwidth = 5; - c.weightx = 0.5; - c.fill = GridBagConstraints.BOTH; - panel.add(infoPanel, c); - - listTasks.addListSelectionListener((ListSelectionEvent e) -> { - if(listTasks.getSelectedValue()!=null){ - textSelectedTask.setText(listTasks.getSelectedValue().toString()); - infoPanel.setInformation(listTasks.getSelectedValue()); - infoPanel.selectedDownloadTask = listTasks.getSelectedValue(); - } - }); - - - labelTaskCount = new JLabel(Integer.toString(listTasks.getModel().getSize())); - labelTaskCount.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); - - c.gridx = 4; - c.gridy = 6; - c.gridwidth = 1; - c.gridheight = 1; - c.fill = GridBagConstraints.NONE; - c.anchor = GridBagConstraints.EAST; - panel.add(labelTaskCount, c); - } - - public JPanel getJPanel(){ - return panel; - } - - public DownloadTask getSelectedTask(){ - return listTasks.getSelectedValue(); - } - - public void addTask(DownloadTask task){ - downloadTasks.add(task); - task.execute(); - listModel.addElement(task); - updateTaskCount(); - task.addPropertyChangeListener(this); - } - - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if(evt.getNewValue().equals(101)){ - Optional taskToRemove = downloadTasks.stream().filter(t -> Long.toString(t.getEpisodeWrapper().getId()).equals(evt.getPropertyName())).findFirst(); - if(taskToRemove.isPresent()){ - if(listTasks.getSelectedValue() != null && taskToRemove.get().equals(listTasks.getSelectedValue())){ - infoPanel.clearInformation(); - textSelectedTask.setText(""); - } - downloadTasks.remove(taskToRemove.get()); - listModel.removeElement(taskToRemove.get()); - updateTaskCount(); - - } - } - panel.repaint(); - } - - private void updateTaskCount(){ - labelTaskCount.setText(Integer.toString(listTasks.getModel().getSize())); - } -} +package com.greinet.tvtotalripper.ui; + +import com.greinet.tvtotalripper.download.DownloadTask; +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import javax.swing.BorderFactory; +import javax.swing.DefaultListModel; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; + +/** + * A download panel to show the current downloads + */ +public class DownloadRipperPanel implements PropertyChangeListener{ + + /** The JPanel for download task visualization */ + private final JPanel panel; + /** The JList for the downloads */ + private JList listTasks; + /** The ListModel for the DownloadTasks */ + private final DefaultListModel listModel; + /** The text field for the currently selected task */ + private JTextField textSelectedTask; + /** The JLabel for the DownloadTask count */ + private final JLabel labelTaskCount; + /** The list of download tasks */ + private final List downloadTasks; + /** The information panel for the currently selected task */ + private DownloadTaskInformationPanel infoPanel; + + /** + * Create a download panel + */ + public DownloadRipperPanel(){ + + downloadTasks = new ArrayList<>(); + + panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + + GridBagConstraints c = new GridBagConstraints(); + + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 5; + c.fill = GridBagConstraints.BOTH; + c.weightx = 0.5; + textSelectedTask = new JTextField(); + textSelectedTask.setEditable(false); + panel.add(textSelectedTask, c); + + + listModel = new DefaultListModel<>(); + listTasks = new JList<>(listModel); + listTasks.setSelectedIndex(ListSelectionModel.SINGLE_SELECTION); + JScrollPane scrollPane = new JScrollPane(); + scrollPane.setViewportView(listTasks); + listTasks.setLayoutOrientation(JList.VERTICAL); + + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 5; + c.gridheight = 5; + c.weightx = 0.5; + panel.add(scrollPane, c); + + infoPanel = new DownloadTaskInformationPanel(); + c.gridx = 5; + c.gridy = 0; + c.gridwidth = 5; + c.weightx = 0.5; + c.fill = GridBagConstraints.BOTH; + panel.add(infoPanel, c); + + listTasks.addListSelectionListener((ListSelectionEvent e) -> { + if(listTasks.getSelectedValue()!=null){ + textSelectedTask.setText(listTasks.getSelectedValue().toString()); + infoPanel.setInformation(listTasks.getSelectedValue()); + infoPanel.selectedDownloadTask = listTasks.getSelectedValue(); + } + }); + + + labelTaskCount = new JLabel(Integer.toString(listTasks.getModel().getSize())); + labelTaskCount.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); + + c.gridx = 4; + c.gridy = 6; + c.gridwidth = 1; + c.gridheight = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.EAST; + panel.add(labelTaskCount, c); + } + + /** + * Get the JPanel + * @return the JPanel + */ + public JPanel getJPanel(){ + return panel; + } + + /** + * Get the currently selected task + * @return the selected task + */ + public DownloadTask getSelectedTask(){ + return listTasks.getSelectedValue(); + } + + /** + * Add a download task to be executed + * @param task + */ + public void addTask(DownloadTask task){ + downloadTasks.add(task); + task.execute(); + listModel.addElement(task); + updateTaskCount(); + task.addPropertyChangeListener(this); + } + + + /** + * Listen to the property change events and remove a task if the new value is 101 + * @param evt the event parameters + */ + @Override + public void propertyChange(PropertyChangeEvent evt) { + if(evt.getNewValue().equals(101)){ + Optional taskToRemove = downloadTasks.stream().filter(t -> Long.toString(t.getEpisodeWrapper().getId()).equals(evt.getPropertyName())).findFirst(); + if(taskToRemove.isPresent()){ + if(listTasks.getSelectedValue() != null && taskToRemove.get().equals(listTasks.getSelectedValue())){ + infoPanel.clearInformation(); + textSelectedTask.setText(""); + } + downloadTasks.remove(taskToRemove.get()); + listModel.removeElement(taskToRemove.get()); + updateTaskCount(); + + } + } + panel.repaint(); + } + + /** + * Update the current task count + */ + private void updateTaskCount(){ + labelTaskCount.setText(Integer.toString(listTasks.getModel().getSize())); + } +} diff --git a/src/main/java/com/greinet/tvtotalripper/ui/DownloadTaskInformationPanel.java b/src/main/java/com/greinet/tvtotalripper/ui/DownloadTaskInformationPanel.java index c0f4ca2..2f09d3a 100644 --- a/src/main/java/com/greinet/tvtotalripper/ui/DownloadTaskInformationPanel.java +++ b/src/main/java/com/greinet/tvtotalripper/ui/DownloadTaskInformationPanel.java @@ -1,126 +1,147 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.greinet.tvtotalripper.ui; - -import com.greinet.tvtotalripper.download.DownloadTask; -import java.awt.GridLayout; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JProgressBar; -import javax.swing.JTextField; -import org.apache.commons.io.FileUtils; - -/** - * - * @author agreiner - */ -public class DownloadTaskInformationPanel extends JPanel{ - - private JLabel labelTitle; - private JLabel labelShow; - private JLabel labelSeason; - private JLabel labelEpisode; - private JLabel labelDuration; - private JLabel labelProgress; - - private JTextField textTitle; - private JTextField textShow; - private JTextField textSeason; - private JTextField textEpisode; - private JTextField textDuration; - private JProgressBar progressBar; - - public DownloadTask selectedDownloadTask; - - public DownloadTaskInformationPanel(){ - - labelTitle = new JLabel("Title"); - labelShow = new JLabel("Show"); - labelSeason = new JLabel("Season"); - labelEpisode = new JLabel("Episode"); - labelDuration = new JLabel("Duration"); - labelProgress = new JLabel("Progress"); - - textTitle = new JTextField(); - textTitle.setEditable(false); - - textShow = new JTextField(); - textShow.setEditable(false); - - textSeason = new JTextField(); - textSeason.setEditable(false); - - textEpisode = new JTextField(); - textEpisode.setEditable(false); - - textDuration = new JTextField(); - textDuration.setEditable(false); - - progressBar = new JProgressBar(0, 100); - - setLayout(new GridLayout(6, 2)); - - add(labelTitle); - add(textTitle); - add(labelShow); - add(textShow); - add(labelSeason); - add(textSeason); - add(labelEpisode); - add(textEpisode); - add(labelDuration); - add(textDuration); - add(labelProgress); - add(progressBar); - } - - public void clearInformation(){ - this.selectedDownloadTask = null; - textTitle.setText(""); - textShow.setText(""); - textSeason.setText(""); - textEpisode.setText(""); - textDuration.setText(""); - progressBar.setValue(0); - progressBar.setString(""); - } - - public void setInformation(DownloadTask downloadTask){ - textTitle.setText(downloadTask.getEpisodeWrapper().getTitle()); - textTitle.setEditable(false); - textShow.setText(downloadTask.getEpisodeWrapper().getShow()); - textShow.setEditable(false); - textSeason.setText(downloadTask.getEpisodeWrapper().getSeason()); - textSeason.setEditable(false); - textEpisode.setText(downloadTask.getEpisodeWrapper().getEpisode()); - textEpisode.setEditable(false); - textDuration.setText(downloadTask.getEpisodeWrapper().getDuration()); - - if(selectedDownloadTask != null && selectedDownloadTask.equals(downloadTask)){ - progressBar.setValue(downloadTask.percentCompleted); - progressBar.setStringPainted(true); - String done = FileUtils.byteCountToDisplaySize(downloadTask.totalBytesRead); - String todo = FileUtils.byteCountToDisplaySize(downloadTask.fileSize); - progressBar.setString(done+"/"+todo); - } - - downloadTask.addPropertyChangeListener((PropertyChangeEvent evt) -> { - - if(selectedDownloadTask != null && evt.getPropertyName().equals(Long.toString(selectedDownloadTask.getEpisodeWrapper().getId()))){ - progressBar.setValue((int)evt.getNewValue()); - progressBar.setStringPainted(true); - String done = FileUtils.byteCountToDisplaySize(downloadTask.totalBytesRead); - String todo = FileUtils.byteCountToDisplaySize(downloadTask.fileSize); - progressBar.setString(done+"/"+todo); - } - }); - } - - - -} +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.greinet.tvtotalripper.ui; + +import com.greinet.tvtotalripper.download.DownloadTask; +import java.awt.GridLayout; +import java.beans.PropertyChangeEvent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JTextField; +import org.apache.commons.io.FileUtils; + +/** + * + * @author agreiner + */ +public class DownloadTaskInformationPanel extends JPanel{ + + /** The label for the title */ + private final JLabel labelTitle; + /** The label for the show */ + private final JLabel labelShow; + /** The label for the season */ + private final JLabel labelSeason; + /** The label for the episode */ + private final JLabel labelEpisode; + /** The label for the duration */ + private final JLabel labelDuration; + /** The label for the download progress */ + private final JLabel labelProgress; + /** The text field for the title */ + private final JTextField textTitle; + /** The text field for the show */ + private final JTextField textShow; + /** The text field for the season */ + private final JTextField textSeason; + /** The text field for the episode */ + private final JTextField textEpisode; + /** The text field for the duration */ + private final JTextField textDuration; + /** The text field for the download progress */ + private final JProgressBar progressBar; + + /** The currently visualized download task */ + public DownloadTask selectedDownloadTask; + + /** + * Create a Panel for DownloadTask information visualization + */ + public DownloadTaskInformationPanel(){ + + labelTitle = new JLabel("Title"); + labelShow = new JLabel("Show"); + labelSeason = new JLabel("Season"); + labelEpisode = new JLabel("Episode"); + labelDuration = new JLabel("Duration"); + labelProgress = new JLabel("Progress"); + + textTitle = new JTextField(); + textTitle.setEditable(false); + + textShow = new JTextField(); + textShow.setEditable(false); + + textSeason = new JTextField(); + textSeason.setEditable(false); + + textEpisode = new JTextField(); + textEpisode.setEditable(false); + + textDuration = new JTextField(); + textDuration.setEditable(false); + + progressBar = new JProgressBar(0, 100); + + setLayout(new GridLayout(6, 2)); + + add(labelTitle); + add(textTitle); + add(labelShow); + add(textShow); + add(labelSeason); + add(textSeason); + add(labelEpisode); + add(textEpisode); + add(labelDuration); + add(textDuration); + add(labelProgress); + add(progressBar); + } + + /** + * Clear all information + */ + public void clearInformation(){ + this.selectedDownloadTask = null; + textTitle.setText(""); + textShow.setText(""); + textSeason.setText(""); + textEpisode.setText(""); + textDuration.setText(""); + progressBar.setValue(0); + progressBar.setString(""); + } + + /** + * Set the information of the download task + * @param downloadTask the download task + */ + public void setInformation(DownloadTask downloadTask){ + textTitle.setText(downloadTask.getEpisodeWrapper().getTitle()); + textTitle.setEditable(false); + textShow.setText(downloadTask.getEpisodeWrapper().getShow()); + textShow.setEditable(false); + textSeason.setText(downloadTask.getEpisodeWrapper().getSeason()); + textSeason.setEditable(false); + textEpisode.setText(downloadTask.getEpisodeWrapper().getEpisode()); + textEpisode.setEditable(false); + textDuration.setText(downloadTask.getEpisodeWrapper().getDuration()); + + if(selectedDownloadTask != null && selectedDownloadTask.equals(downloadTask)){ + progressBar.setValue(downloadTask.getPercentCompleted()); + progressBar.setStringPainted(true); + String done = FileUtils.byteCountToDisplaySize(downloadTask.getTotalBytesRead()); + String todo = FileUtils.byteCountToDisplaySize(downloadTask.getFileSize()); + progressBar.setString(done+"/"+todo); + } + + downloadTask.addPropertyChangeListener((PropertyChangeEvent evt) -> { + + if(selectedDownloadTask != null && evt.getPropertyName().equals(Long.toString(selectedDownloadTask.getEpisodeWrapper().getId()))){ + progressBar.setValue((int)evt.getNewValue()); + progressBar.setStringPainted(true); + String done = FileUtils.byteCountToDisplaySize(downloadTask.getTotalBytesRead()); + String todo = FileUtils.byteCountToDisplaySize(downloadTask.getFileSize()); + progressBar.setString(done+"/"+todo); + } + }); + } + + + +} diff --git a/src/main/java/com/greinet/tvtotalripper/ui/EpisodesRipperPanel.java b/src/main/java/com/greinet/tvtotalripper/ui/EpisodesRipperPanel.java index 9a3cf63..b5b1111 100644 --- a/src/main/java/com/greinet/tvtotalripper/ui/EpisodesRipperPanel.java +++ b/src/main/java/com/greinet/tvtotalripper/ui/EpisodesRipperPanel.java @@ -1,124 +1,146 @@ -package com.greinet.tvtotalripper.ui; - -import com.greinet.tvtotalripper.crawler.EpisodeWrapper; -import java.awt.Color; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.util.List; -import javax.swing.BorderFactory; -import javax.swing.DefaultListModel; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextField; -import javax.swing.event.ListSelectionEvent; - -/** - * - * @author agreiner - */ -public class EpisodesRipperPanel { - - private final JPanel panel; - - private JList listPanel; - private DefaultListModel listModel; - - private JTextField textField; - private final JButton buttonDownload; - private final JLabel label; - private final List episodes; - - private final JButton buttonDownloadAll; - - public EpisodesRipperPanel(List episodes){ - this.episodes = episodes; - - panel = new JPanel(false); - panel.setLayout(new GridBagLayout()); - - GridBagConstraints c = new GridBagConstraints(); - c.gridx = 0; - c.gridy = 0; - c.gridwidth = 3; - c.fill = GridBagConstraints.BOTH; - c.weightx = 0.4; - - textField = new JTextField(); - textField.setEditable(false); - panel.add(textField,c); - - buttonDownload = new JButton("Download"); - c.gridx = 3; - c.gridwidth = 2; - c.weightx = 0.1; - - panel.add(buttonDownload,c); - - listModel = new DefaultListModel<>(); - episodes.forEach(e -> listModel.addElement(e)); - - listPanel = new JList<>(listModel); - JScrollPane scrollPane = new JScrollPane(); - scrollPane.setViewportView(listPanel); - listPanel.setLayoutOrientation(JList.VERTICAL); - listPanel.setSelectedIndex(0); - - listPanel.addListSelectionListener((ListSelectionEvent e) -> { - textField.setText(listPanel.getSelectedValue().toString()); - }); - textField.setText(listPanel.getSelectedValue().toString()); - - c.gridx = 0; - c.gridy = 1; - c.gridwidth = 5; - c.gridheight = 5; - c.weightx = 0.5; - - panel.add(scrollPane,c); - - label = new JLabel(Integer.toString(episodes.size())); - label.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); - - buttonDownloadAll = new JButton("Download all episodes"); - c.gridx = 6; - c.gridy = 0; - c.gridwidth = 1; - c.gridheight = 6; - c.weightx = 0.5; - panel.add(buttonDownloadAll,c); - - - - c.gridx = 4; - c.gridy = 6; - c.gridwidth = 1; - c.gridheight = 1; - c.fill = GridBagConstraints.NONE; - c.anchor = GridBagConstraints.EAST; - - panel.add(label, c); - - - - } - - public JPanel getJPanel(){ - return panel; - } - - public EpisodeWrapper getCurrentSelected(){ - return listPanel.getSelectedValue(); - } - - public JButton getDownloadButton(){ - return buttonDownload; - } - - public JButton getDownloadAllButton(){ - return buttonDownloadAll; - } - -} +package com.greinet.tvtotalripper.ui; + +import com.greinet.tvtotalripper.crawler.EpisodeWrapper; +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.List; +import javax.swing.BorderFactory; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.event.ListSelectionEvent; + +/** + * A panel to display the episodes of a season + */ +public class EpisodesRipperPanel { + /** The JPanel */ + private final JPanel panel; + /** The list for the episodes */ + private JList listPanel; + /** The list model for the episode list */ + private DefaultListModel listModel; + /** The text field for the currently selected episode */ + private JTextField textField; + /** The button to start the episode download */ + private final JButton buttonDownload; + /** The label for the episode count */ + private final JLabel label; + /** The list of episodes */ + private final List episodes; + /** The button to download all episodes in the list */ + private final JButton buttonDownloadAll; + + /** + * Create a panel to display episodes in a list + * @param episodes the episodes to display + */ + public EpisodesRipperPanel(List episodes){ + this.episodes = episodes; + + panel = new JPanel(false); + panel.setLayout(new GridBagLayout()); + + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 3; + c.fill = GridBagConstraints.BOTH; + c.weightx = 0.4; + + textField = new JTextField(); + textField.setEditable(false); + panel.add(textField,c); + + buttonDownload = new JButton("Download"); + c.gridx = 3; + c.gridwidth = 2; + c.weightx = 0.1; + + panel.add(buttonDownload,c); + + listModel = new DefaultListModel<>(); + episodes.forEach(e -> listModel.addElement(e)); + + listPanel = new JList<>(listModel); + JScrollPane scrollPane = new JScrollPane(); + scrollPane.setViewportView(listPanel); + listPanel.setLayoutOrientation(JList.VERTICAL); + listPanel.setSelectedIndex(0); + + listPanel.addListSelectionListener((ListSelectionEvent e) -> { + textField.setText(listPanel.getSelectedValue().toString()); + }); + textField.setText(listPanel.getSelectedValue().toString()); + + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 5; + c.gridheight = 5; + c.weightx = 0.5; + + panel.add(scrollPane,c); + + label = new JLabel(Integer.toString(episodes.size())); + label.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); + + buttonDownloadAll = new JButton("Download all episodes"); + c.gridx = 6; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 6; + c.weightx = 0.5; + panel.add(buttonDownloadAll,c); + + + + c.gridx = 4; + c.gridy = 6; + c.gridwidth = 1; + c.gridheight = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.EAST; + + panel.add(label, c); + + + + } + /** + * Get the JPanel + * @return the JPanel + */ + public JPanel getJPanel(){ + return panel; + } + + /** + * Get the currently selecte episode + * @return + */ + public EpisodeWrapper getCurrentSelected(){ + return listPanel.getSelectedValue(); + } + + /** + * Get the download button + * @return the download button + */ + public JButton getDownloadButton(){ + return buttonDownload; + } + + /** + * Get the download all button + * @return the download all button + */ + public JButton getDownloadAllButton(){ + return buttonDownloadAll; + } + +} diff --git a/src/main/java/com/greinet/tvtotalripper/ui/RipperWindow.java b/src/main/java/com/greinet/tvtotalripper/ui/RipperWindow.java index 838cf46..479387f 100644 --- a/src/main/java/com/greinet/tvtotalripper/ui/RipperWindow.java +++ b/src/main/java/com/greinet/tvtotalripper/ui/RipperWindow.java @@ -1,132 +1,153 @@ -package com.greinet.tvtotalripper.ui; - -import com.greinet.tvtotalripper.crawler.CrawlerUtil; -import com.greinet.tvtotalripper.crawler.EpisodeWrapper; -import com.greinet.tvtotalripper.download.DownloadTask; -import java.awt.Dimension; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import javax.swing.JComponent; -import javax.swing.JFrame; -import javax.swing.JPanel; -import javax.swing.JTabbedPane; -import org.openqa.selenium.By; -import org.openqa.selenium.WebElement; - -/** - * - * @author agreiner - */ -public class RipperWindow { - - private final JFrame frame; - private final JTabbedPane tabbedPane; - - private JComponent panel1; - private JComponent panel2; - private JComponent panel3; - private JComponent panel4; - private JComponent panel5; - - private BaseRipperPanel showRipperPanel; - private SettingsRipperPanel srp; - private DownloadRipperPanel drp; - - public RipperWindow(){ - frame = new JFrame("TV Total Ripper"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - - tabbedPane = new JTabbedPane(); - - showRipperPanel = createShowsPanel(); - srp = new SettingsRipperPanel(); - drp = new DownloadRipperPanel(); - - panel1 = showRipperPanel.getJPanel(); - panel2 = new JPanel(); - panel3 = new JPanel(); - panel4 = drp.getJPanel(); - panel5 = srp.getPanel(); - - tabbedPane.addTab("Shows", panel1); - tabbedPane.addTab("Seasons", panel2); - tabbedPane.addTab("Episodes", panel3); - tabbedPane.addTab("Downloads", panel4); - tabbedPane.addTab("Settings", panel5); - - tabbedPane.setEnabledAt(1, false); - tabbedPane.setEnabledAt(2, false); - - tabbedPane.setPreferredSize(new Dimension(900, 400)); - - frame.add(tabbedPane); - - frame.pack(); - frame.setAlwaysOnTop(true); - frame.setVisible(true); - } - - public static void main(String[] args) { - RipperWindow ripperWindow = new RipperWindow(); - } - - private BaseRipperPanel createShowsPanel(){ - List shows = CrawlerUtil.getShows(); - Map namesToUrls = shows.stream().collect(Collectors.toMap(e -> e.findElement(By.xpath(".//img")).getAttribute("alt") , e -> e.getAttribute("href"))); - - BaseRipperPanel brp = new BaseRipperPanel(namesToUrls); - - brp.getJButton().addActionListener((ActionEvent e) -> { - panel3 = new JPanel(); - tabbedPane.setEnabledAt(2, false); - panel1 = createSeasonsPanel(brp.getCurrentSelected()).getJPanel(); - tabbedPane.setComponentAt(1, panel1); - tabbedPane.setEnabledAt(1, true); - tabbedPane.setSelectedIndex(1); - }); - - return brp; - } - - private BaseRipperPanel createSeasonsPanel(String url){ - List seasons = CrawlerUtil.getSeasons(url); - Map elements = seasons.stream().collect(Collectors.toMap(e -> e.getText(), e -> e.getText())); - - BaseRipperPanel brp = new BaseRipperPanel(elements); - - brp.getJButton().addActionListener((ActionEvent e) -> { - tabbedPane.setEnabledAt(2, false); - panel2 = createEpisodesPanel(url,brp.getCurrentSelected()).getJPanel(); - tabbedPane.setComponentAt(2, panel2); - tabbedPane.setEnabledAt(2, true); - tabbedPane.setSelectedIndex(2); - }); - - return brp; - } - - private EpisodesRipperPanel createEpisodesPanel(String url, String seasonName){ - List episodes = CrawlerUtil.getEpisodes(url, seasonName); - - EpisodesRipperPanel erp = new EpisodesRipperPanel(episodes); - - erp.getDownloadButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - drp.addTask(new DownloadTask(erp.getCurrentSelected())); - } - }); - - erp.getDownloadAllButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - episodes.forEach(d -> drp.addTask(new DownloadTask(d))); - } - }); - return erp; - } - +package com.greinet.tvtotalripper.ui; + +import com.greinet.tvtotalripper.crawler.CrawlerUtil; +import com.greinet.tvtotalripper.crawler.EpisodeWrapper; +import com.greinet.tvtotalripper.download.DownloadTask; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +/** + * + * @author agreiner + */ +public class RipperWindow { + /** The pane managing the tabs */ + private final JTabbedPane tabbedPane; + /** The panel for the shows */ + private JComponent panel1; + /** The panel for the seasons */ + private JComponent panel2; + /** The panel for the episodes */ + private JComponent panel3; + /** The panel for the downloads */ + private final JComponent panel4; + /** The panel for the settings */ + private final JComponent panel5; + /** The show panel */ + private final BaseRipperPanel showRipperPanel; + /** The settings panel */ + private final SettingsRipperPanel srp; + /** The downloads panel */ + private final DownloadRipperPanel drp; + + /** + * Create the main application UI + */ + public RipperWindow(){ + JFrame frame = new JFrame("TV Total Ripper"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + tabbedPane = new JTabbedPane(); + + showRipperPanel = createShowsPanel(); + srp = new SettingsRipperPanel(); + drp = new DownloadRipperPanel(); + + panel1 = showRipperPanel.getJPanel(); + panel2 = new JPanel(); + panel3 = new JPanel(); + panel4 = drp.getJPanel(); + panel5 = srp.getPanel(); + + tabbedPane.addTab("Shows", panel1); + tabbedPane.addTab("Seasons", panel2); + tabbedPane.addTab("Episodes", panel3); + tabbedPane.addTab("Downloads", panel4); + tabbedPane.addTab("Settings", panel5); + + tabbedPane.setEnabledAt(1, false); + tabbedPane.setEnabledAt(2, false); + + tabbedPane.setPreferredSize(new Dimension(900, 400)); + + frame.add(tabbedPane); + + frame.pack(); + frame.setAlwaysOnTop(true); + frame.setVisible(true); + } + + /** + * The main method to start the program + * @param args the arguments + */ + public static void main(String[] args) { + RipperWindow ripperWindow = new RipperWindow(); + } + + /** + * Create a show panel listing all available shows + * @return the shopw panel + */ + private BaseRipperPanel createShowsPanel(){ + List shows = CrawlerUtil.getShows(); + Map namesToUrls = shows.stream().collect(Collectors.toMap(e -> e.findElement(By.xpath(".//img")).getAttribute("alt") , e -> e.getAttribute("href"))); + + BaseRipperPanel brp = new BaseRipperPanel(namesToUrls); + + brp.getJButton().addActionListener((ActionEvent e) -> { + panel3 = new JPanel(); + tabbedPane.setEnabledAt(2, false); + panel1 = createSeasonsPanel(brp.getCurrentSelected()).getJPanel(); + tabbedPane.setComponentAt(1, panel1); + tabbedPane.setEnabledAt(1, true); + tabbedPane.setSelectedIndex(1); + }); + + return brp; + } + + /** + * Create a seaon panel for a selected show + * @param url the URL to the show + * @return the season panel + */ + private BaseRipperPanel createSeasonsPanel(String url){ + List seasons = CrawlerUtil.getSeasons(url); + Map elements = seasons.stream().collect(Collectors.toMap(e -> e.getText(), e -> e.getText())); + + BaseRipperPanel brp = new BaseRipperPanel(elements); + + brp.getJButton().addActionListener((ActionEvent e) -> { + tabbedPane.setEnabledAt(2, false); + panel2 = createEpisodesPanel(url,brp.getCurrentSelected()).getJPanel(); + tabbedPane.setComponentAt(2, panel2); + tabbedPane.setEnabledAt(2, true); + tabbedPane.setSelectedIndex(2); + }); + + return brp; + } + + /** + * Create a episode panel + * @param url the URL to the episode list + * @param seasonName the name of the selected season + * @return the episode panel + */ + private EpisodesRipperPanel createEpisodesPanel(String url, String seasonName){ + List episodes = CrawlerUtil.getEpisodes(url, seasonName); + + EpisodesRipperPanel erp = new EpisodesRipperPanel(episodes); + + erp.getDownloadButton().addActionListener((ActionEvent e) -> { + drp.addTask(new DownloadTask(erp.getCurrentSelected())); + }); + + erp.getDownloadAllButton().addActionListener((ActionEvent e) -> { + episodes.forEach(d -> drp.addTask(new DownloadTask(d))); + }); + return erp; + } + } \ No newline at end of file diff --git a/src/main/java/com/greinet/tvtotalripper/ui/SettingsRipperPanel.java b/src/main/java/com/greinet/tvtotalripper/ui/SettingsRipperPanel.java index 63e35da..7653799 100644 --- a/src/main/java/com/greinet/tvtotalripper/ui/SettingsRipperPanel.java +++ b/src/main/java/com/greinet/tvtotalripper/ui/SettingsRipperPanel.java @@ -1,90 +1,97 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.greinet.tvtotalripper.ui; - -import java.awt.Dimension; -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.io.File; -import javax.swing.JButton; -import javax.swing.JFileChooser; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JTextField; - -/** - * - * @author agreiner - */ -public class SettingsRipperPanel { - - private JPanel panel; - private JLabel labelDownloadFolder; - private JTextField textDownloadFolder; - private JFileChooser fileChooserDownloadFolder; - public static File DOWNLOADFOLDER = new File("."); - private JButton button; - - public SettingsRipperPanel(){ - panel = new JPanel(); - panel.setLayout(new GridBagLayout()); - - GridBagConstraints gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.NORTHWEST; - - gbc.gridx=0; - gbc.gridy=0; - gbc.gridwidth=1; - gbc.insets.bottom = 10; - labelDownloadFolder = new JLabel("Download folder"); - labelDownloadFolder.setFont(new Font(labelDownloadFolder.getFont().getFontName(), Font.BOLD, 15)); - panel.add(labelDownloadFolder, gbc); - - gbc.gridx=0; - gbc.gridy=1; - gbc.gridwidth=8; - gbc.insets.left = 20; - textDownloadFolder = new JTextField(DOWNLOADFOLDER.getAbsolutePath()); - textDownloadFolder.setEditable(false); - textDownloadFolder.setPreferredSize(new Dimension(500, 30)); - panel.add(textDownloadFolder,gbc); - - - - - - gbc.gridx=8; - gbc.gridy=1; - gbc.gridwidth=1; - gbc.insets.left = 0; - button = new JButton("..."); - panel.add(button,gbc); - - fileChooserDownloadFolder = new JFileChooser(DOWNLOADFOLDER); - fileChooserDownloadFolder.setDialogTitle("Select download folder"); - fileChooserDownloadFolder.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - fileChooserDownloadFolder.setAcceptAllFileFilterUsed(false); - - - button.addActionListener(e -> { - int value = fileChooserDownloadFolder.showOpenDialog(panel); - - if (value == JFileChooser.APPROVE_OPTION){ - DOWNLOADFOLDER = fileChooserDownloadFolder.getSelectedFile(); - textDownloadFolder.setText(DOWNLOADFOLDER.getAbsolutePath()); - } - - }); - } - - public JPanel getPanel() { - return panel; - } - - - -} +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.greinet.tvtotalripper.ui; + +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.io.File; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +/** + * + * @author agreiner + */ +public class SettingsRipperPanel { + + /** The JPanel to visualize the settings */ + private JPanel panel; + /** The JLabel for the download folder selection */ + private final JLabel labelDownloadFolder; + /** The text field for the current download folder */ + private JTextField textDownloadFolder; + /** The file chooser to select the download folder */ + private JFileChooser fileChooserDownloadFolder; + /** The download folder */ + public static File DOWNLOADFOLDER = new File("."); + /** The JButton to open the file chooser dialog */ + private final JButton button; + + /** + * Create a settings panel + */ + public SettingsRipperPanel(){ + panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.NORTHWEST; + + gbc.gridx=0; + gbc.gridy=0; + gbc.gridwidth=1; + gbc.insets.bottom = 10; + labelDownloadFolder = new JLabel("Download folder"); + labelDownloadFolder.setFont(new Font(labelDownloadFolder.getFont().getFontName(), Font.BOLD, 15)); + panel.add(labelDownloadFolder, gbc); + + gbc.gridx=0; + gbc.gridy=1; + gbc.gridwidth=8; + gbc.insets.left = 20; + textDownloadFolder = new JTextField(DOWNLOADFOLDER.getAbsolutePath()); + textDownloadFolder.setEditable(false); + textDownloadFolder.setPreferredSize(new Dimension(500, 30)); + panel.add(textDownloadFolder,gbc); + + gbc.gridx=8; + gbc.gridy=1; + gbc.gridwidth=1; + gbc.insets.left = 0; + button = new JButton("..."); + panel.add(button,gbc); + + fileChooserDownloadFolder = new JFileChooser(DOWNLOADFOLDER); + fileChooserDownloadFolder.setDialogTitle("Select download folder"); + fileChooserDownloadFolder.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fileChooserDownloadFolder.setAcceptAllFileFilterUsed(false); + + + button.addActionListener(e -> { + int value = fileChooserDownloadFolder.showOpenDialog(panel); + + if (value == JFileChooser.APPROVE_OPTION){ + DOWNLOADFOLDER = fileChooserDownloadFolder.getSelectedFile(); + textDownloadFolder.setText(DOWNLOADFOLDER.getAbsolutePath()); + } + + }); + } + + /** + * Get the JPanel + * @return the JPanel + */ + public JPanel getPanel() { + return panel; + } + +}