Added base functionality, working prototype

This commit is contained in:
Andreas Greiner 2021-04-04 16:26:12 +02:00
parent 9d4317ee9d
commit 34ea601a95
19 changed files with 1987 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target/

19
nb-configuration.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-shared-configuration>
<!--
This file contains additional configuration written by modules in the NetBeans IDE.
The configuration is intended to be shared among all the users of project and
therefore it is assumed to be part of version control checkout.
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
-->
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
<!--
Properties that influence various parts of the IDE, especially code formatting and the like.
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
That way multiple projects can share the same settings (useful for formatting rules for example).
Any value defined here will override the pom.xml file value but is only applicable to the current project.
-->
<org-netbeans-modules-javascript2-requirejs.enabled>true</org-netbeans-modules-javascript2-requirejs.enabled>
<netbeans.hint.jdkPlatform>JDK_16</netbeans.hint.jdkPlatform>
</properties>
</project-shared-configuration>

17
nbactions.xml Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<actions>
<action>
<actionName>run</actionName>
<packagings>
<packaging>jar</packaging>
</packagings>
<goals>
<goal>process-classes</goal>
<goal>org.codehaus.mojo:exec-maven-plugin:1.5.0:exec</goal>
</goals>
<properties>
<exec.args>-classpath %classpath com.greinet.tvtotalripper.Main</exec.args>
<exec.executable>java</exec.executable>
</properties>
</action>
</actions>

27
pom.xml
View File

@ -5,6 +5,33 @@
<artifactId>TvTotalRipper</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.mp4parser</groupId>
<artifactId>isoparser</artifactId>
<version>1.9.41</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>

BIN
resources/chromedriver.exe Normal file

Binary file not shown.

View File

@ -0,0 +1,172 @@
/*
* 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<WebElement> 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<WebElement> 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<WebElement> 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<WebElement> getSeriesElements(WebDriver driver){
return driver.findElements(By.xpath("/html/body/div[5]/div/div/div/div/a"));
}
private static List<WebElement> 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<WebElement> 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);
}
}
}

View File

@ -0,0 +1,189 @@
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<ChunkOffsetBox> 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;
}
}
}

View File

@ -0,0 +1,231 @@
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<ChunkOffsetBox> 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;
}
}
}

View File

@ -0,0 +1,77 @@
/*
* 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<PropertyChangeListener> 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);
}
}
}

View File

@ -0,0 +1,179 @@
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<WebElement> 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<WebElement> 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<EpisodeWrapper> getEpisodes(String url, String seasonName){
// navigate the WebDriver to the specified URL
navigateToShow(url);
// Search the specified season
List<WebElement> elements = driverSeasonsAndEpisodes.findElements(By.xpath("/html/body/div[4]/div[1]/div[3]/div[2]/div[1]/select/option"));
Optional<WebElement> 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<WebElement> episodeListDivs = driverSeasonsAndEpisodes.findElements(By.xpath("/html/body/div[4]/div[1]/div[3]/div[2]/div"));
List<WebElement> 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<WebElement> 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<WebElement> 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<EpisodeWrapper> wrappers = new ArrayList<>();
for(int i=0;i<linksToEpisodes.size();i++){
wrappers.add(new EpisodeWrapper(linksToEpisodes.get(i).getAttribute("href"), linksToEpisodes.get(i).getText(), linksToEpisodes.get(i).getText(), seasonName, show, durationOfEpisodes.get(i).getText()));
}
return wrappers;
}else{
logger.log(Level.WARN, "No episodes found for url:[{0}], season:[{1}]",url, seasonName);
return new ArrayList<>();
}
}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<String> tabs = new ArrayList<>(driverFetchFile.getWindowHandles());
for (int i=1;i<tabs.size();i++){
driverFetchFile.switchTo().window(tabs.get(i));
driverFetchFile.close();
}
driverFetchFile.switchTo().window(driverFetchFile.getWindowHandles().toArray(new String[1])[0]);
return downloadLink;
}
}
}

View File

@ -0,0 +1,168 @@
package com.greinet.tvtotalripper.crawler;
import java.util.Objects;
import java.util.UUID;
/**
* A wrapper class to collect all episode information
* @author agreiner
*/
public class EpisodeWrapper {
/** The URL to the episode */
private String url;
/** The title */
private String title;
/** The episode */
private String episode;
/** The season */
private String season;
/** The episode duration */
private String duration;
/** The unique id */
private final long id;
private String show;
/**
* Create a episode wrapper to store episode information
* @param url the URL
* @param title the title
* @param episodeNumber the episode
* @param season the season
* @param show the show
* @param duration the duration
*/
public EpisodeWrapper(String url, String title, String episode, String season, String show, String duration) {
this.url = url;
this.title = title;
this.episode = episode;
this.season = season;
this.show = show;
this.duration = duration;
id = UUID.randomUUID().getLeastSignificantBits();
}
public String getShow() {
return show;
}
public void setShow(String show) {
this.show = show;
}
/**
* Return the unique id
* @return the id
*/
public long getId() {
return id;
}
/**
* Get the URL to the episode
* @return the URL
*/
public String getUrl() {
return url;
}
/**
* Set the URL to the episode
* @param url the URL
*/
public void setUrl(String url) {
this.url = url;
}
/**
* Get the title of the episode
* @return the title
*/
public String getTitle() {
return title;
}
/**
* Set the title of the episode
* @param title the title
*/
public void setTitle(String title) {
this.title = title;
}
/**
* Get the episode
* @return the episode
*/
public String getEpisode() {
return episode;
}
/**
* Set the episode number
* @param episode the episode
*/
public void setEpisodeNumber(String episode) {
this.episode = episode;
}
/**
* Get the season of the episode
* @return the season
*/
public String getSeason() {
return season;
}
/**
* Set the season of the episode
* @param season the season
*/
public void setSeason(String season) {
this.season = season;
}
/**
* Get the duration of the episode
* @return the duration
*/
public String getDuration() {
return duration;
}
/**
* Set the duration of the episode
* @param duration the duration
*/
public void setDuration(String duration) {
this.duration = duration;
}
@Override
public String toString() {
return title;
}
@Override
public int hashCode() {
int hash = 3;
hash = 19 * hash + Objects.hashCode(this.url);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final EpisodeWrapper other = (EpisodeWrapper) obj;
return Objects.equals(this.url, other.url);
}
}

View File

@ -0,0 +1,81 @@
/*
* 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 {
private HttpURLConnection httpConn;
private InputStream inputStream;
private String fileName;
private int contentLength;
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) {
String disposition = httpConn.getHeaderField("Content-Disposition");
String contentType = httpConn.getContentType();
contentLength = httpConn.getContentLength();
if (disposition != null) {
// extracts file name from header field
int index = disposition.indexOf("filename=");
if (index > 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;
}
}

View File

@ -0,0 +1,95 @@
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<Void, Void> {
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);
}
}

View File

@ -0,0 +1,116 @@
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<String> listPanel;
private JTextField textField;
private final JButton button;
private final JLabel label;
private Map<String, String> elements;
public BaseRipperPanel(Map<String, String> 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<String> 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;
}
}

View File

@ -0,0 +1,143 @@
/*
* 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<DownloadTask> listTasks;
private DefaultListModel<DownloadTask> listModel;
private JTextField textSelectedTask;
private final JLabel labelTaskCount;
private final List<DownloadTask> 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<DownloadTask> 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()));
}
}

View File

@ -0,0 +1,126 @@
/*
* 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);
}
});
}
}

View File

@ -0,0 +1,124 @@
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<EpisodeWrapper> listPanel;
private DefaultListModel<EpisodeWrapper> listModel;
private JTextField textField;
private final JButton buttonDownload;
private final JLabel label;
private final List<EpisodeWrapper> episodes;
private final JButton buttonDownloadAll;
public EpisodesRipperPanel(List<EpisodeWrapper> 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;
}
}

View File

@ -0,0 +1,132 @@
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<WebElement> shows = CrawlerUtil.getShows();
Map<String, String> 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<WebElement> seasons = CrawlerUtil.getSeasons(url);
Map<String, String> 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<EpisodeWrapper> 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;
}
}

View File

@ -0,0 +1,90 @@
/*
* 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;
}
}