1
0
mirror of https://github.com/openeggbert/youtubedl-frontend.git synced 2025-03-25 17:17:46 +01:00

Renamed to youtubedl-frontend. Added many improvements

This commit is contained in:
Robert Vokac 2024-07-05 14:17:22 +02:00
parent 7318598d8d
commit c4dc2f650f
No known key found for this signature in database
GPG Key ID: C459E1E4B4A986BB
9 changed files with 317 additions and 107 deletions

View File

@ -1,2 +1,2 @@
# archivebox-youtube-helper # youtubedl-frontend

30
pom.xml
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
archivebox-youtube-helper: Tool generating html pages for Archive Box. youtubedl-frontend: Tool generating html pages for Archive Box.
Copyright (C) 2024 the original author or authors. Copyright (C) 2024 the original author or authors.
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
@ -29,12 +29,12 @@
</parent> </parent>
<groupId>org.nanoboot.tools</groupId> <groupId>org.nanoboot.tools</groupId>
<artifactId>archivebox-youtube-helper</artifactId> <artifactId>youtubedl-frontend</artifactId>
<version>0.0.0-SNAPSHOT</version> <version>0.0.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>archivebox-youtube-helper</name> <name>youtubedl-frontend</name>
<description>archivebox-youtube-helper</description> <description>youtubedl-frontend</description>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -82,7 +82,7 @@
<configuration> <configuration>
<archive> <archive>
<manifest> <manifest>
<mainClass>org.nanoboot.archiveboxyoutubehelper.Main</mainClass> <mainClass>org.nanoboot.youtubedlfrontend.Main</mainClass>
</manifest> </manifest>
</archive> </archive>
<descriptorRefs> <descriptorRefs>
@ -187,6 +187,21 @@
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.17.1</version> <version>2.17.1</version>
</dependency> </dependency>
<!-- <dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-jpeg2000</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.sejda.imageio</groupId>
<artifactId>webp-imageio</artifactId>
<version>0.1.6</version>
</dependency>-->
</dependencies> </dependencies>
@ -201,6 +216,11 @@
<name>nanoboot-snapshots-repository</name> <name>nanoboot-snapshots-repository</name>
<url>https://maven.nanoboot.org/snapshots</url> <url>https://maven.nanoboot.org/snapshots</url>
</repository> </repository>
<repository>
<id>AsposeJavaAPI</id>
<name>Aspose Java API</name>
<url>https://repository.aspose.com/repo/</url>
</repository>
</repositories> </repositories>
<pluginRepositories> <pluginRepositories>

View File

@ -1,5 +1,5 @@
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
// archiveboxyoutubehelper: // youtubedlfrontend:
// Copyright (C) 2024 the original author or authors. // Copyright (C) 2024 the original author or authors.
// //
// This program is free software; you can redistribute it and/or // This program is free software; you can redistribute it and/or
@ -17,7 +17,7 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
module archiveboxyoutubehelper { module youtubedlfrontend {
requires lombok; requires lombok;
requires org.apache.logging.log4j; requires org.apache.logging.log4j;
requires org.json; requires org.json;
@ -25,4 +25,5 @@ module archiveboxyoutubehelper {
requires humble.video.noarch; requires humble.video.noarch;
requires humble.video.all; requires humble.video.all;
requires com.fasterxml.jackson.databind; requires com.fasterxml.jackson.databind;
requires java.desktop;
} }

View File

@ -1,5 +1,5 @@
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
// archivebox-youtube-helper: Tool generating html pages for Archive Box. // youtubedl-frontend: Tool generating html pages for Archive Box.
// Copyright (C) 2024 the original author or authors. // Copyright (C) 2024 the original author or authors.
// //
// This program is free software; you can redistribute it and/or // This program is free software; you can redistribute it and/or
@ -16,7 +16,7 @@
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
package org.nanoboot.archiveboxyoutubehelper; package org.nanoboot.youtubedlfrontend;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -37,48 +37,111 @@ import java.util.Map;
public class Main { public class Main {
private static int iii = 0; private static int iii = 0;
private static int videoNumberPerRow = 0; private static int internalStaticVariableVideoNumberPerRow = 0;
public static final boolean ALWAYS_COMPUTE_METADATA = false; public static boolean argAlwaysGenerateMetadata = true;
public static final int VIDEOS_PER_ROW = 4; public static boolean argAlwaysGenerateHtmlFiles = true;
private static int THUMBNAIL_WIDTH = 250; public static int argVideosPerRow = 4;
public static int THUMBNAIL_WIDTH = 250;
public static String argVideo; public static String argVideo;
public static String argChannel; public static String argChannel;
public static void main(String[] args) throws IOException, InterruptedException { public static void main(String[] args) throws IOException, InterruptedException {
System.out.println("archiveboxyoutubehelper - HTML generator\n"); System.out.println("youtubedlfrontend - HTML generator\n");
args = "/rv/blupi/archivebox --_video UDpsz1yIwiw --always-generate-metadata 1 --always-generate-html-files 1 --videos-per-row 4".split(" ");
//args = "/rv/databig/youtube --_video UDpsz1yIwiw --always-generate-metadata 1 --always-generate-html-files 1 --videos-per-row 4".split(" ");
if (args.length == 0) {
args = new String[]{"/rv/blupi/archivebox"
//, "--video", "gPU_onaTzXs"
};
}
if (args.length < 1) { if (args.length < 1) {
System.err.println("At least one argument is expected, but the count of arguments is: " + args.length + "."); System.err.println("At least one argument is expected, but the count of arguments is: " + args.length + ".");
System.exit(1); System.exit(1);
} }
argVideo = ""; argVideo = "";
argChannel = ""; argChannel = "";
if(args.length > 1) { if (args.length > 0) {
for(int i = 1;i<args.length;i++) { for (int i = 0; i < args.length; i++) {
String s = args[i]; String arg = args[i];
if(s.equals("--video")) { if (i == 0 && !arg.startsWith(TWO_DASHES)) {
continue;
}
if (arg.equals("--video")) {
i++; i++;
if(i >= args.length) { if (i >= args.length) {
throw new ArchiveBoxYoutubeHelperException("Fatal error: missing value for --video"); throw new YoutubedlFrontendException("Fatal error: missing value for --video");
} }
argVideo = args[i]; argVideo = args[i];
} }
if(s.equals("--channel")) { if (arg.equals("--channel")) {
i++; i++;
if(i >= args.length) { if (i >= args.length) {
throw new ArchiveBoxYoutubeHelperException("Fatal error: missing value for --channel"); throw new YoutubedlFrontendException("Fatal error: missing value for --channel");
} }
argChannel = args[i]; argChannel = args[i];
} }
if (arg.equals("--videos-per-row")) {
i++;
if (i >= args.length) {
throw new YoutubedlFrontendException("Fatal error: missing value for --videos-per-row");
}
argVideosPerRow = Integer.parseInt(args[i]);
if (argVideosPerRow < 2) {
argVideosPerRow = 0;
}
}
if (arg.equals("--always-generate-metadata")) {
i++;
if (i >= args.length) {
throw new YoutubedlFrontendException("Fatal error: missing value for --always-generate-metadata");
}
String s = args[i];
switch (s) {
case "1":
argAlwaysGenerateMetadata = true;
break;
case "true":
argAlwaysGenerateMetadata = true;
break;
case "0":
argAlwaysGenerateMetadata = false;
break;
case "false":
argAlwaysGenerateMetadata = false;
break;
default:
throw new YoutubedlFrontendException("Invalid value for --always-generate-metadata");
};
}
if (arg.equals("--always-generate-html-files")) {
i++;
if (i >= args.length) {
throw new YoutubedlFrontendException("Fatal error: missing value for --always-generate-html-files");
}
String s = args[i];
switch (s) {
case "1":
argAlwaysGenerateHtmlFiles = true;
break;
case "true":
argAlwaysGenerateHtmlFiles = true;
break;
case "0":
argAlwaysGenerateHtmlFiles = false;
break;
case "false":
argAlwaysGenerateHtmlFiles = false;
break;
default:
throw new YoutubedlFrontendException("Invalid value for --always-generate-html-files");
};
}
} }
} }
String workingDirectory = args.length > 0 && !args[0].startsWith(TWO_DASHES) ? args[0] : new File(".").getAbsolutePath();
File archiveBoxRootDirectory = new File(args[0]);
File archiveBoxRootDirectory = new File(workingDirectory);
File archiveBoxArchiveDirectory = new File(archiveBoxRootDirectory, "archive"); File archiveBoxArchiveDirectory = new File(archiveBoxRootDirectory, "archive");
int i = 0; int i = 0;
List<YoutubeVideo> youtubeVideos = new ArrayList<>(); List<YoutubeVideo> youtubeVideos = new ArrayList<>();
@ -90,13 +153,13 @@ public class Main {
continue; continue;
} }
YoutubeVideo youtubeVideo = new YoutubeVideo(mediaDirectory); YoutubeVideo youtubeVideo = new YoutubeVideo(mediaDirectory);
if(!Main.argVideo.isBlank() && !youtubeVideo.getId().equals(Main.argVideo)) { if (!Main.argVideo.isBlank() && !youtubeVideo.getId().equals(Main.argVideo)) {
continue;
}
if(!argVideo.isBlank() && !youtubeVideo.getId().equals(argVideo)) {
continue; continue;
} }
if(!argChannel.isBlank() && !youtubeVideo.getChannelId().equals(argChannel)) { if (!argVideo.isBlank() && !youtubeVideo.getId().equals(argVideo)) {
continue;
}
if (!argChannel.isBlank() && !youtubeVideo.getChannelId().equals(argChannel)) {
continue; continue;
} }
i++; i++;
@ -121,14 +184,18 @@ public class Main {
YoutubeVideo previousVideo = null; YoutubeVideo previousVideo = null;
YoutubeVideo nextVideo = null; YoutubeVideo nextVideo = null;
YoutubeVideo currentVideo = null; YoutubeVideo currentVideo = null;
for(int j = 0; j<youtubeVideos.size(); j++) { for (int j = 0; j < youtubeVideos.size(); j++) {
previousVideo = currentVideo; previousVideo = currentVideo;
currentVideo = youtubeVideos.get(j); currentVideo = youtubeVideos.get(j);
if(j < (youtubeVideos.size() - 1)) { if (j < (youtubeVideos.size() - 1)) {
nextVideo = youtubeVideos.get(j+1); nextVideo = youtubeVideos.get(j + 1);
}
if (previousVideo != null) {
currentVideo.setPreviousVideoId(previousVideo.getId());
}
if (nextVideo != null) {
currentVideo.setNextVideoId(nextVideo.getId());
} }
if(previousVideo != null) currentVideo.setPreviousVideoId(previousVideo.getId());
if(nextVideo != null) currentVideo.setNextVideoId(nextVideo.getId());
} }
Map<String, String> channelUrls = new HashMap<>(); Map<String, String> channelUrls = new HashMap<>();
List<String> channels = new ArrayList<>(); List<String> channels = new ArrayList<>();
@ -173,21 +240,24 @@ public class Main {
channels.forEach(c -> { channels.forEach(c -> {
sb.append("<h1>").append(c).append("</h1>\n"); sb.append("<h1>").append(c).append("</h1>\n");
sb.append("<div style=\"max-width:").append((Main.THUMBNAIL_WIDTH + 20) * Main.VIDEOS_PER_ROW).append("px\"><a href =\"").append(channelUrls.get(c)).append("\">").append(channelUrls.get(c)).append("</a><div class=\"videos\">"); sb.append("<div style=\"max-width:").append((Main.THUMBNAIL_WIDTH + 20) * Main.argVideosPerRow).append("px\"><a href =\"").append(channelUrls.get(c)).append("\">").append(channelUrls.get(c)).append("</a><div class=\"videos\">");
iii = 0; iii = 0;
videoNumberPerRow = 0; internalStaticVariableVideoNumberPerRow = 0;
sb.append("<table>\n"); sb.append("<table>\n");
youtubeVideos.stream().filter(v -> c.equals(v.getChannelName())).forEach(z -> { youtubeVideos.stream().filter(v -> c.equals(v.getChannelName())).forEach(z -> {
iii++; iii++;
if (videoNumberPerRow == 0) { if (internalStaticVariableVideoNumberPerRow == 0) {
sb.append("<tr>"); sb.append("<tr>");
} }
videoNumberPerRow++; internalStaticVariableVideoNumberPerRow++;
sb.append("<td><div class=\"box\"><table style=\"margin:5px;max-width:") sb.append("<td><div class=\"box\"><table style=\"margin:5px;max-width:")
.append(THUMBNAIL_WIDTH) .append(THUMBNAIL_WIDTH)
.append("px;\">\n<tr><td><a href=\"videos/" + z.getId() + ".html\" target=\"_blank\"><img src=\"archive/"); .append("px;\">\n<tr><td><a href=\"videos/" + z.getId() + ".html\" target=\"_blank\"><img src=\"archive/");
sb.append(z.getSnapshot()); sb.append(z.getSnapshot());
sb.append("/media/thumbnail.jpg\" width=\"") sb
.append("/media/mini-thumbnail.")
.append(z.getMiniThumbnailFormat())
.append("\" width=\"")
.append(THUMBNAIL_WIDTH) .append(THUMBNAIL_WIDTH)
.append("\"></a></td></tr>\n"); .append("\"></a></td></tr>\n");
sb.append("<tr><td><b style=\"font-size:90%;\">").append(z.getTitle()).append("</b></td></tr>\n"); sb.append("<tr><td><b style=\"font-size:90%;\">").append(z.getTitle()).append("</b></td></tr>\n");
@ -199,14 +269,14 @@ public class Main {
.append("</td></tr>\n"); .append("</td></tr>\n");
z.setNumber(iii); z.setNumber(iii);
sb.append("</table></div></td>\n"); sb.append("</table></div></td>\n");
if (videoNumberPerRow == VIDEOS_PER_ROW) { if (internalStaticVariableVideoNumberPerRow == argVideosPerRow) {
sb.append("<tr>"); sb.append("<tr>");
videoNumberPerRow = 0; internalStaticVariableVideoNumberPerRow = 0;
} }
File videoHtmlFile = new File(videosDirectory, z.getId() + ".html"); File videoHtmlFile = new File(videosDirectory, z.getId() + ".html");
// if(videoHtmlFile.exists()) { if(!videoHtmlFile.exists() || argAlwaysGenerateHtmlFiles) {
//
// }
{ {
StringBuilder sb2 = new StringBuilder(""" StringBuilder sb2 = new StringBuilder("""
<!DOCTYPE html> <!DOCTYPE html>
@ -214,8 +284,8 @@ public class Main {
<head> <head>
<link rel="icon" type="image/x-icon" href="../favicon.ico" sizes="16x16"> <link rel="icon" type="image/x-icon" href="../favicon.ico" sizes="16x16">
<title>""" <title>"""
+z.getTitle() + + z.getTitle()
""" + """
</title> </title>
<style> <style>
body {padding:20px;} body {padding:20px;}
@ -230,54 +300,78 @@ public class Main {
); );
String finalUrl = "https://www.youtube.com/watch?v=" + z.getId(); String finalUrl = "https://www.youtube.com/watch?v=" + z.getId();
sb2.append("<input type=\"text\" id=\"youtube_url\" name=\"youtube_url\" size=\"60\" width=\"60\" style=\"margint-bottom:20px;margin-right:10px;font-size:110%;padding:5px;\" value=\"" + finalUrl + "\"><br><br>"); sb2.append("<input type=\"text\" id=\"youtube_url\" name=\"youtube_url\" size=\"60\" width=\"60\" style=\"margint-bottom:20px;margin-right:10px;font-size:110%;padding:5px;\" value=\"" + finalUrl + "\"><br>\n<br>\n");
sb2.append("<a target=\"_blank\" href=\"").append(finalUrl).append("\">"); sb2.append("<a target=\"_blank\" href=\"").append(finalUrl).append("\">");
sb2.append(finalUrl).append("</a>").append("<br>"); sb2.append(finalUrl).append("</a>").append("<br>\n");
String videoLocalUrl = ""; String videoLocalUrl = "";
try { try {
videoLocalUrl = "file:///" + archiveBoxRootDirectory.getAbsolutePath() + "/archive/" + z.getSnapshot() + "/media/" + URLEncoder.encode(z.getVideoFileName(), StandardCharsets.UTF_8.toString()).replace("+", "%20"); videoLocalUrl = "file:///" + archiveBoxRootDirectory.getAbsolutePath() + "/archive/" + z.getSnapshot() + "/media/" + URLEncoder.encode(z.getVideoFileName(), StandardCharsets.UTF_8.toString()).replace("+", "%20");
} catch (UnsupportedEncodingException ex) { } catch (UnsupportedEncodingException ex) {
throw new ArchiveBoxYoutubeHelperException(ex.getMessage()); throw new YoutubedlFrontendException(ex.getMessage());
} }
if(!z.getVideoFileName().endsWith(".mkv")) {
try {
sb2.append("<video src=\"");
sb2.append("../archive/" + z.getSnapshot() + "/media/" + URLEncoder.encode(z.getVideoFileName(), StandardCharsets.UTF_8.toString()).replace("+", "%20"));
sb2.append("""
" controls width=\"800\">
Your browser does not support the video tag.
</video><br>
""");
} catch (UnsupportedEncodingException ex) {
throw new YoutubedlFrontendException(ex.getMessage());
}
} else {
sb2.append("<a target=\"_blank\" href=\"").append(videoLocalUrl).append("\">"); sb2.append("<a target=\"_blank\" href=\"").append(videoLocalUrl).append("\">");
sb2.append("<img style=\"margin:10px;width:600px;\" src=\"../archive/") sb2.append("<img style=\"margin:10px;width:600px;\" src=\"../archive/")
.append(z.getSnapshot()) .append(z.getSnapshot())
.append("/media/thumbnail.jpg\"></a><br>"); .append("/media/thumbnail.")
.append(z.getThumbnailFormat())
.append("\"></a><br>\n");
}
sb2.append("<span style=\"font-size:160%;font-weight:bold;\">").append(z.getTitle()).append("</span>"); sb2.append("<span style=\"font-size:160%;font-weight:bold;\">").append(z.getTitle()).append("</span>");
sb2.append("<br><br>"); sb2.append("<br>\n<br>\n");
sb2.append("#").append(z.getNumber()).append("&nbsp;&nbsp;&nbsp;"); sb2.append("#").append(z.getNumber()).append("&nbsp;&nbsp;&nbsp;");
if(z.getPreviousVideoId() != null) sb2.append("<a href=\"./").append(z.getPreviousVideoId()).append(".html\">"); if (z.getPreviousVideoId() != null) {
sb2.append("<a href=\"./").append(z.getPreviousVideoId()).append(".html\">");
}
sb2.append("Back"); sb2.append("Back");
if(z.getPreviousVideoId() != null) sb2.append("</a>"); if (z.getPreviousVideoId() != null) {
sb2.append("</a>");
}
sb2.append("&nbsp;&nbsp;&nbsp;"); sb2.append("&nbsp;&nbsp;&nbsp;");
if(z.getNextVideoId()!= null) sb2.append("<a href=\"./").append(z.getNextVideoId()).append(".html\">"); if (z.getNextVideoId() != null) {
sb2.append("<a href=\"./").append(z.getNextVideoId()).append(".html\">");
}
sb2.append("Next"); sb2.append("Next");
if(z.getNextVideoId() != null) sb2.append("</a>"); if (z.getNextVideoId() != null) {
sb2.append("</a>");
}
sb2.append(" "); sb2.append(" ");
sb2.append("<br>"); sb2.append("<br>\n");
sb2.append("<pre style=\"white-space: pre-wrap; border:1px solid black;max-width:600px;padding:10px;min-height:50px;\">"); sb2.append("<pre style=\"white-space: pre-wrap; border:1px solid black;max-width:600px;padding:10px;min-height:50px;\">");
sb2.append(z.getDescription().isBlank() ? "No description" : z.getDescription()); sb2.append(z.getDescription().isBlank() ? "No description" : z.getDescription());
sb2.append("</pre>"); sb2.append("</pre>");
sb2.append("<h2>Comments</h2>"); sb2.append("<h2>Comments</h2>");
z.getComments().forEach(co -> { z.getComments().forEach(co -> {
// private String id, parentId, text, author; // private String id, parentId, text, author;
// private int timestamp; // private int timestamp;
sb2.append("<div style=\"margin-left:") sb2.append("<div style=\"margin-left:")
.append(co.dotCount() * 50) .append(co.dotCount() * 50)
.append("px;\">"); .append("px;\">");
sb2.append("<h3>").append(co.getAuthor()).append("</h3>"); sb2.append("<h3>").append(co.getAuthor()).append("</h3>");
sb2.append("<span style=\"color:grey;font-size:80%;\">") sb2.append("<span style=\"color:grey;font-size:80%;\">")
.append(Utils.DATE_FORMAT.format(new Date(co.getTimestamp() * 1000))).append("</span><br>\n");
.append(Utils.DATE_FORMAT.format(new Date(co.getTimestamp() * 1000))).append("</span><br>");
sb2.append("<span style=\"color:grey;font-size:80%;\">") sb2.append("<span style=\"color:grey;font-size:80%;\">")
.append(co.getId() + " " + co.getParentId()).append("</span><br>"); .append(co.getId() + " " + co.getParentId()).append("</span><br>\n");
sb2.append("<pre style=\"white-space: pre-wrap;border:1px solid black;max-width:600px;padding:10px;min-height:50px;\">").append(co.getText()).append("</pre>"); sb2.append("<pre style=\"white-space: pre-wrap;border:1px solid black;max-width:600px;padding:10px;min-height:50px;\">").append(co.getText()).append("</pre>");
sb2.append("</div>"); sb2.append("</div>");
}); });
// private String id; // private String id;
// //
// private String title; // private String title;
@ -292,18 +386,17 @@ sb2.append("<div style=\"margin-left:")
// private String description; // private String description;
// private String thumbnail; // private String thumbnail;
// private List<YoutubeComment> comments = new ArrayList<>(); // private List<YoutubeComment> comments = new ArrayList<>();
sb2.append("</body></html>"); sb2.append("</body></html>");
Utils.writeTextToFile(sb2.toString(), videoHtmlFile); String singleVideo = sb2.toString();
//singleVideo.replace("<br>\n", "<br>\n\n");
Utils.writeTextToFile(singleVideo, videoHtmlFile);
}
} }
}); });
if (videoNumberPerRow < VIDEOS_PER_ROW) { if (internalStaticVariableVideoNumberPerRow < argVideosPerRow) {
sb.append("<tr>"); sb.append("<tr>");
} }
sb.append("<table>\n"); sb.append("</table>\n");
sb.append("</div></div>"); sb.append("</div></div>");
}); });
sb.append(""" sb.append("""
@ -317,5 +410,6 @@ sb2.append("<div style=\"margin-left:")
System.out.println("[Warning] Snapshots without videos:"); System.out.println("[Warning] Snapshots without videos:");
YoutubeVideo.missingYoutubeVideos.forEach(s -> System.out.println(s)); YoutubeVideo.missingYoutubeVideos.forEach(s -> System.out.println(s));
} }
private static final String TWO_DASHES = "--";
} }

View File

@ -1,5 +1,5 @@
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
// archivebox-youtube-helper: Tool generating html pages for Archive Box. // youtubedl-frontend: Tool generating html pages for Archive Box.
// Copyright (C) 2024 the original author or authors. // Copyright (C) 2024 the original author or authors.
// //
// This program is free software; you can redistribute it and/or // This program is free software; you can redistribute it and/or
@ -16,7 +16,7 @@
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
package org.nanoboot.archiveboxyoutubehelper; package org.nanoboot.youtubedlfrontend;
import dev.mccue.guava.hash.Hashing; import dev.mccue.guava.hash.Hashing;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -99,7 +99,7 @@ public class Utils {
return result;//.substring(0, result.length() - 1); return result;//.substring(0, result.length() - 1);
} }
public static void copyFile(File originalFile, File copiedFile) throws ArchiveBoxYoutubeHelperException { public static void copyFile(File originalFile, File copiedFile) throws YoutubedlFrontendException {
Path originalPath = originalFile.toPath(); Path originalPath = originalFile.toPath();
Path copied = new File(copiedFile, originalFile.getName()).toPath(); Path copied = new File(copiedFile, originalFile.getName()).toPath();
@ -107,7 +107,7 @@ public class Utils {
Files.copy(originalPath, copied, StandardCopyOption.REPLACE_EXISTING); Files.copy(originalPath, copied, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ex) { } catch (IOException ex) {
ex.printStackTrace(); ex.printStackTrace();
throw new ArchiveBoxYoutubeHelperException("Copying file failed: " + originalFile.getAbsolutePath()); throw new YoutubedlFrontendException("Copying file failed: " + originalFile.getAbsolutePath());
} }
} }
@ -117,7 +117,7 @@ public class Utils {
fileWriter = new FileWriter(file); fileWriter = new FileWriter(file);
} catch (IOException ex) { } catch (IOException ex) {
ex.printStackTrace(); ex.printStackTrace();
throw new ArchiveBoxYoutubeHelperException("Writing to file failed: " + file.getName(), ex); throw new YoutubedlFrontendException("Writing to file failed: " + file.getName(), ex);
} }
PrintWriter printWriter = new PrintWriter(fileWriter); PrintWriter printWriter = new PrintWriter(fileWriter);
printWriter.print(text); printWriter.print(text);
@ -131,7 +131,7 @@ public class Utils {
try { try {
return new String(Files.readAllBytes(Paths.get(file.getAbsolutePath()))); return new String(Files.readAllBytes(Paths.get(file.getAbsolutePath())));
} catch (IOException ex) { } catch (IOException ex) {
throw new ArchiveBoxYoutubeHelperException("Reading file failed: " + file.getName(), ex); throw new YoutubedlFrontendException("Reading file failed: " + file.getName(), ex);
} }
} }
@ -141,7 +141,7 @@ public class Utils {
InputStream inputStream = clazz.getResourceAsStream(fileName); InputStream inputStream = clazz.getResourceAsStream(fileName);
return readFromInputStream(inputStream); return readFromInputStream(inputStream);
} catch (IOException ex) { } catch (IOException ex) {
throw new ArchiveBoxYoutubeHelperException("Reading file failed: " + fileName, ex); throw new YoutubedlFrontendException("Reading file failed: " + fileName, ex);
} }
} }
@ -164,7 +164,7 @@ public class Utils {
return dev.mccue.guava.io.Files.hash(file, Hashing.sha512()).toString(); return dev.mccue.guava.io.Files.hash(file, Hashing.sha512()).toString();
} catch (IOException ex) { } catch (IOException ex) {
System.err.println(ex.getMessage()); System.err.println(ex.getMessage());
throw new ArchiveBoxYoutubeHelperException(ex.getMessage()); throw new YoutubedlFrontendException(ex.getMessage());
} }
} }

View File

@ -2,7 +2,7 @@
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/ */
package org.nanoboot.archiveboxyoutubehelper; package org.nanoboot.youtubedlfrontend;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;

View File

@ -2,7 +2,7 @@
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/ */
package org.nanoboot.archiveboxyoutubehelper; package org.nanoboot.youtubedlfrontend;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Properties; import java.util.Properties;
import javax.imageio.ImageIO;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -50,6 +51,7 @@ public class YoutubeVideo implements Comparable<YoutubeVideo> {
private long timestamp; private long timestamp;
private String description; private String description;
private String thumbnail; private String thumbnail;
private String miniThumbnail;
private List<YoutubeComment> comments = new ArrayList<>(); private List<YoutubeComment> comments = new ArrayList<>();
private String previousVideoId = null; private String previousVideoId = null;
private String nextVideoId = null; private String nextVideoId = null;
@ -60,7 +62,7 @@ public class YoutubeVideo implements Comparable<YoutubeVideo> {
public YoutubeVideo(File mediaDirectory) throws InterruptedException, IOException { public YoutubeVideo(File mediaDirectory) throws InterruptedException, IOException {
File metadataFile = new File(mediaDirectory, "metadata"); File metadataFile = new File(mediaDirectory, "metadata");
if (!Main.ALWAYS_COMPUTE_METADATA && metadataFile.exists()) { if (!Main.argAlwaysGenerateMetadata && metadataFile.exists()) {
YoutubeVideo yv = new YoutubeVideo(); YoutubeVideo yv = new YoutubeVideo();
//new ObjectMapper().readValue(Utils.readTextFromFile(metadataFile), YoutubeVideo.class); //new ObjectMapper().readValue(Utils.readTextFromFile(metadataFile), YoutubeVideo.class);
@ -86,6 +88,7 @@ public class YoutubeVideo implements Comparable<YoutubeVideo> {
timestamp = Long.parseLong(properties.getProperty("timestamp")); timestamp = Long.parseLong(properties.getProperty("timestamp"));
description = properties.getProperty("description"); description = properties.getProperty("description");
thumbnail = properties.getProperty("thumbnail"); thumbnail = properties.getProperty("thumbnail");
miniThumbnail = properties.getProperty("miniThumbnail");
comments = new ArrayList<>(); comments = new ArrayList<>();
JSONArray ja = new JSONArray(properties.getProperty("comments")); JSONArray ja = new JSONArray(properties.getProperty("comments"));
ja.forEach(o -> { ja.forEach(o -> {
@ -96,7 +99,7 @@ public class YoutubeVideo implements Comparable<YoutubeVideo> {
comments.add(new ObjectMapper().readValue(toString, YoutubeComment.class)); comments.add(new ObjectMapper().readValue(toString, YoutubeComment.class));
} catch (JsonProcessingException ex) { } catch (JsonProcessingException ex) {
throw new ArchiveBoxYoutubeHelperException(ex.getMessage()); throw new YoutubedlFrontendException(ex.getMessage());
} }
} }
); );
@ -119,17 +122,82 @@ public class YoutubeVideo implements Comparable<YoutubeVideo> {
if (thumbnail == null) { if (thumbnail == null) {
thumbnail = ""; thumbnail = "";
} }
File thumbnailFile = new File(mediaDirectory, "thumbnail.jpg"); JSONArray thumbnails = jsonObject.getJSONArray("thumbnails");
if (!thumbnailFile.exists() && thumbnail != null) { for (int i = 0; i < thumbnails.length(); i++) {
try (BufferedInputStream in = new BufferedInputStream(new URL(thumbnail).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(thumbnailFile.getAbsolutePath())) { JSONObject o = (JSONObject) thumbnails.get(i);
byte dataBuffer[] = new byte[1024]; if (!o.has("width")) {
int bytesRead; continue;
while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { } else {
fileOutputStream.write(dataBuffer, 0, bytesRead); int width = o.getInt("width");
if (width < (((double)Main.THUMBNAIL_WIDTH) * 0.8d)) {
continue;
} }
} catch (IOException e) { miniThumbnail = o.getString("url");
System.out.println(e.getMessage()); break;
} }
}
File thumbnailFile = new File(mediaDirectory, "thumbnail." + getThumbnailFormat());
File miniThumbnailFile = new File(mediaDirectory, "mini-thumbnail." + getMiniThumbnailFormat());
// new File(mediaDirectory, "thumbnail.jpg").delete();
// new File(mediaDirectory, "mini-thumbnail.jpg").delete();
// new File(mediaDirectory, "thumbnail.webp").delete();
// new File(mediaDirectory, "mini-thumbnail.webp").delete();
if (thumbnail != null) {
if (!thumbnailFile.exists()) {
try (BufferedInputStream in = new BufferedInputStream(new URL(thumbnail).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(thumbnailFile.getAbsolutePath())) {
byte dataBuffer[] = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
fileOutputStream.write(dataBuffer, 0, bytesRead);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
if (!miniThumbnailFile.exists()) {
try (BufferedInputStream in = new BufferedInputStream(new URL(miniThumbnail).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(miniThumbnailFile.getAbsolutePath())) {
byte dataBuffer[] = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
fileOutputStream.write(dataBuffer, 0, bytesRead);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
// for (String s : ImageIO.getReaderFormatNames()) {
// System.out.println(s);
// }
//if(!miniThumbnailFile.exists()) {
//String miniThumbnailFileAbsolutePath = miniThumbnailFile.getAbsolutePath();
// String formatName = miniThumbnailFileAbsolutePath.substring(miniThumbnailFileAbsolutePath
// .lastIndexOf(".") + 1);
// BufferedImage inputImage = ImageIO.read(thumbnailFile);
// if(inputImage == null) {
//
// }
// int thumbnailWidth = inputImage.getWidth();
// int thumbnailHeight = inputImage.getHeight();
// double heightWidthRatio = ((double) thumbnailHeight) / ((double) thumbnailWidth);
// int miniThumbnailWidth = Main.THUMBNAIL_WIDTH;
// int miniThumbnailHeight = (int) (heightWidthRatio * ((double) Main.THUMBNAIL_WIDTH));
//
// BufferedImage outputImage = new BufferedImage(miniThumbnailWidth,
// miniThumbnailHeight, inputImage.getType());
//
// Graphics2D g2d = outputImage.createGraphics();
// g2d.drawImage(inputImage, 0, 0, miniThumbnailWidth, miniThumbnailHeight, null);
// g2d.dispose();
//
//
//
// ImageIO.write(outputImage, formatName, new File(miniThumbnailFileAbsolutePath));
//}
} }
// //
Optional<File> descriptionFile = files.stream().filter(f -> f.getName().endsWith(".description")).findFirst(); Optional<File> descriptionFile = files.stream().filter(f -> f.getName().endsWith(".description")).findFirst();
@ -198,6 +266,7 @@ public class YoutubeVideo implements Comparable<YoutubeVideo> {
properties.put("timestamp", String.valueOf(timestamp)); properties.put("timestamp", String.valueOf(timestamp));
properties.put("description", description); properties.put("description", description);
properties.put("thumbnail", thumbnail); properties.put("thumbnail", thumbnail);
properties.put("minithumbnail", miniThumbnail);
properties.put("comments", new JSONArray(comments).toString()); properties.put("comments", new JSONArray(comments).toString());
if (previousVideoId != null) { if (previousVideoId != null) {
properties.put("previousVideoId", previousVideoId); properties.put("previousVideoId", previousVideoId);
@ -225,6 +294,32 @@ public class YoutubeVideo implements Comparable<YoutubeVideo> {
} }
public String getThumbnailFormat() {
return getExtensionFromUrl(thumbnail);
}
public String getMiniThumbnailFormat() {
return getExtensionFromUrl(miniThumbnail);
}
private String getExtensionFromUrl(String url) {
String result = url.substring(url
.lastIndexOf(".") + 1);
int questionMarkIndex = 0;
for(int i = 0;i<result.length();i++) {
char ch = result.charAt(i);
if(ch != '?') {
continue;
} else {
questionMarkIndex = i;
}
}
if(questionMarkIndex > 0) {
result = result.substring(0, questionMarkIndex);
}
return result;
}
/** /**
* Pretty prints a timestamp (in {@link Global.NO_PTS} units) into a string. * Pretty prints a timestamp (in {@link Global.NO_PTS} units) into a string.
* *

View File

@ -1,5 +1,5 @@
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
// archivebox-youtube-helper: Tool generating html pages for Archive Box. // youtubedl-frontend: Tool generating html pages for Archive Box.
// Copyright (C) 2024 the original author or authors. // Copyright (C) 2024 the original author or authors.
// //
// This program is free software; you can redistribute it and/or // This program is free software; you can redistribute it and/or
@ -17,19 +17,19 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
package org.nanoboot.archiveboxyoutubehelper; package org.nanoboot.youtubedlfrontend;
/** /**
* @author <a href="mailto:robertvokac@nanoboot.org">Robert Vokac</a> * @author <a href="mailto:robertvokac@nanoboot.org">Robert Vokac</a>
* @since 0.0.0 * @since 0.0.0
*/ */
public class ArchiveBoxYoutubeHelperException extends RuntimeException { public class YoutubedlFrontendException extends RuntimeException {
public ArchiveBoxYoutubeHelperException(String msg) { public YoutubedlFrontendException(String msg) {
super(msg); super(msg);
} }
public ArchiveBoxYoutubeHelperException(String msg, Exception e) { public YoutubedlFrontendException(String msg, Exception e) {
super(msg, e); super(msg, e);
} }