diff --git a/Executable/JavaYTD.exe b/Executable/JavaYTD.exe index 2f0fc98..ffa124a 100644 Binary files a/Executable/JavaYTD.exe and b/Executable/JavaYTD.exe differ diff --git a/Executable/copyToMiniPrograms.bat b/Executable/copyToMiniPrograms.bat index f042f5f..ed60ac6 100644 --- a/Executable/copyToMiniPrograms.bat +++ b/Executable/copyToMiniPrograms.bat @@ -1,3 +1,3 @@ -copy "C:\Users\David\GitHub Repos\JavaYTD\Executable\JavaYTD.exe" "C:\Users\David\Google Drive\Computery\Mini Programs\JavaYTD.exe" +copy "C:\Users\David\GitHub Repos\JavaYTD\Executable\JavaYTD.exe" "C:\Users\David\Google Drive\Computery\Mini Programs\My Own\JavaYTD.exe" @pause \ No newline at end of file diff --git a/Preview.png b/Preview.png index e6dbc7d..b359a28 100644 Binary files a/Preview.png and b/Preview.png differ diff --git a/Project/build/built-jar.properties b/Project/build/built-jar.properties index 85c61be..c6f6b01 100644 --- a/Project/build/built-jar.properties +++ b/Project/build/built-jar.properties @@ -1,4 +1,4 @@ -#Sat, 06 Feb 2021 14:43:36 +0800 +#Sun, 07 Feb 2021 21:58:20 +0800 C\:\\Users\\David\\GitHub\ Repos\\JavaYTD\\Project= diff --git a/Project/build/classes/.netbeans_automatic_build b/Project/build/classes/.netbeans_automatic_build deleted file mode 100644 index e69de29..0000000 diff --git a/Project/build/classes/.netbeans_update_resources b/Project/build/classes/.netbeans_update_resources deleted file mode 100644 index e69de29..0000000 diff --git a/Project/build/classes/main/Code$1.class b/Project/build/classes/main/Code$1.class index ad72a41..0615cff 100644 Binary files a/Project/build/classes/main/Code$1.class and b/Project/build/classes/main/Code$1.class differ diff --git a/Project/build/classes/main/Code.class b/Project/build/classes/main/Code.class index 9af4ae9..22dbed7 100644 Binary files a/Project/build/classes/main/Code.class and b/Project/build/classes/main/Code.class differ diff --git a/Project/build/classes/main/Command.class b/Project/build/classes/main/Command.class index 0c09fe5..16c154e 100644 Binary files a/Project/build/classes/main/Command.class and b/Project/build/classes/main/Command.class differ diff --git a/Project/build/classes/main/FileIO.class b/Project/build/classes/main/FileIO.class index a570ca4..4d319a3 100644 Binary files a/Project/build/classes/main/FileIO.class and b/Project/build/classes/main/FileIO.class differ diff --git a/Project/build/classes/main/GUI$1.class b/Project/build/classes/main/GUI$1.class index 5431680..8d19523 100644 Binary files a/Project/build/classes/main/GUI$1.class and b/Project/build/classes/main/GUI$1.class differ diff --git a/Project/build/classes/main/GUI$10.class b/Project/build/classes/main/GUI$10.class index 6943a65..28add60 100644 Binary files a/Project/build/classes/main/GUI$10.class and b/Project/build/classes/main/GUI$10.class differ diff --git a/Project/build/classes/main/GUI$11.class b/Project/build/classes/main/GUI$11.class index 213ace3..1155125 100644 Binary files a/Project/build/classes/main/GUI$11.class and b/Project/build/classes/main/GUI$11.class differ diff --git a/Project/build/classes/main/GUI$12.class b/Project/build/classes/main/GUI$12.class index 5d91e4d..8e74175 100644 Binary files a/Project/build/classes/main/GUI$12.class and b/Project/build/classes/main/GUI$12.class differ diff --git a/Project/build/classes/main/GUI$13.class b/Project/build/classes/main/GUI$13.class index 96cd664..9f84ef7 100644 Binary files a/Project/build/classes/main/GUI$13.class and b/Project/build/classes/main/GUI$13.class differ diff --git a/Project/build/classes/main/GUI$14.class b/Project/build/classes/main/GUI$14.class new file mode 100644 index 0000000..9ff5845 Binary files /dev/null and b/Project/build/classes/main/GUI$14.class differ diff --git a/Project/build/classes/main/GUI$2.class b/Project/build/classes/main/GUI$2.class index ef41748..e05c8d7 100644 Binary files a/Project/build/classes/main/GUI$2.class and b/Project/build/classes/main/GUI$2.class differ diff --git a/Project/build/classes/main/GUI$3.class b/Project/build/classes/main/GUI$3.class index 70bec3d..424afb4 100644 Binary files a/Project/build/classes/main/GUI$3.class and b/Project/build/classes/main/GUI$3.class differ diff --git a/Project/build/classes/main/GUI$4.class b/Project/build/classes/main/GUI$4.class index 81ec9c8..361a62e 100644 Binary files a/Project/build/classes/main/GUI$4.class and b/Project/build/classes/main/GUI$4.class differ diff --git a/Project/build/classes/main/GUI$5.class b/Project/build/classes/main/GUI$5.class index ab8b933..f783158 100644 Binary files a/Project/build/classes/main/GUI$5.class and b/Project/build/classes/main/GUI$5.class differ diff --git a/Project/build/classes/main/GUI$6.class b/Project/build/classes/main/GUI$6.class index 4b556e5..3319a1b 100644 Binary files a/Project/build/classes/main/GUI$6.class and b/Project/build/classes/main/GUI$6.class differ diff --git a/Project/build/classes/main/GUI$7.class b/Project/build/classes/main/GUI$7.class index cbc376b..ae02995 100644 Binary files a/Project/build/classes/main/GUI$7.class and b/Project/build/classes/main/GUI$7.class differ diff --git a/Project/build/classes/main/GUI$8.class b/Project/build/classes/main/GUI$8.class index 35163f1..dcaac39 100644 Binary files a/Project/build/classes/main/GUI$8.class and b/Project/build/classes/main/GUI$8.class differ diff --git a/Project/build/classes/main/GUI$9.class b/Project/build/classes/main/GUI$9.class index cf97b29..a35731c 100644 Binary files a/Project/build/classes/main/GUI$9.class and b/Project/build/classes/main/GUI$9.class differ diff --git a/Project/build/classes/main/GUI.class b/Project/build/classes/main/GUI.class index da641ac..56c2669 100644 Binary files a/Project/build/classes/main/GUI.class and b/Project/build/classes/main/GUI.class differ diff --git a/Project/build/classes/main/GUI.form b/Project/build/classes/main/GUI.form deleted file mode 100644 index ded7324..0000000 --- a/Project/build/classes/main/GUI.form +++ /dev/null @@ -1,633 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Project/dist/JavaYTD.jar b/Project/dist/JavaYTD.jar index e35cdf2..fa22879 100644 Binary files a/Project/dist/JavaYTD.jar and b/Project/dist/JavaYTD.jar differ diff --git a/Project/nbproject/private/private.xml b/Project/nbproject/private/private.xml index 624611e..284eeec 100644 --- a/Project/nbproject/private/private.xml +++ b/Project/nbproject/private/private.xml @@ -2,10 +2,6 @@ - - file:/C:/Users/David/GitHub%20Repos/JavaYTD/Project/src/main/Code.java - file:/C:/Users/David/GitHub%20Repos/JavaYTD/Project/src/main/GUI.java - file:/C:/Users/David/GitHub%20Repos/JavaYTD/Project/src/main/FileIO.java - + diff --git a/Project/src/main/Code.java b/Project/src/main/Code.java index 14740cb..e54fd15 100644 --- a/Project/src/main/Code.java +++ b/Project/src/main/Code.java @@ -1,15 +1,11 @@ package main; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Timer; import java.util.TimerTask; import javax.swing.JComboBox; +import static main.GUI.*; /** * Contains 'back-end' methods @@ -18,8 +14,14 @@ */ public class Code { + // Path to youtube-dl.exe + private final String ytdlPath = "youtube-dl.exe"; + + // Term used to describe youtube-dl.exe + private final String ytdlTerm = "Program Requirement"; + // Default option string - private final String defOpt = "Default/Best"; + private final String defOpt = "Default (Best Audio and Video)"; // Timer private final Timer timer; @@ -27,10 +29,7 @@ public class Code { // FileIO helper object public static FileIO fio; - // Path to youtube-dl.exe - private final String ytdlPath = "youtube-dl.exe"; - - // The user's downloads folder path + // The user's Downloads folder path public static String downloadsPath; /** @@ -51,7 +50,7 @@ public Code() { // Check youtube-dl.exe checkYTDL(); - // Determine downloads folder path and notify + // Determine Downloads folder path and notify downloadsPath = getDwlFolderPath(); outputln("Downloaded media will be placed here."); outputln(""); @@ -66,12 +65,7 @@ public Code() { + " determine the video's available formats."); // Enable various GUI elements - GUI.gui.getURLField().setEnabled(true); - GUI.gui.getCustArgField().setEnabled(true); - GUI.gui.getExportBut().setEnabled(true); - GUI.gui.getResetBut().setEnabled(true); - GUI.gui.getCheckbox("exit").setEnabled(true); - GUI.gui.getCheckbox("cookie").setEnabled(true); + enableInterfaceElements(); } /** @@ -79,45 +73,61 @@ public Code() { */ public final void checkYTDL() { + // Combine strings + String ytdlDesc = ytdlTerm + ": '" + ytdlPath + "'"; + // Notify and print - outputln("\nChecking 'youtube-dl.exe'..."); + outputln("\nChecking " + ytdlDesc + "..."); // If youtube-dl program does not exist if (!fio.isValidPath(ytdlPath)) { // Notify - outputln("Not found. Will be downloaded next to exe and hidden"); + outputln("Not found. Will be downloaded next to exe and hidden."); - // Get youtube-dl program + // Run slow download command String prog = "curl"; - String[] args = new String[4]; - args[0] = "-L"; - args[1] = "https://yt-dl.org/latest/youtube-dl.exe"; - args[2] = "--output"; - args[3] = ytdlPath; - Command comm = new Command(prog, args); - comm.run(); + ArrayList argList = new ArrayList<>(); + argList.add("-L"); + argList.add("https://yt-dl.org/latest/youtube-dl.exe"); + argList.add("--output"); + argList.add(ytdlPath); + String desc = ytdlTerm + ": Downloading"; + runSlowComm(prog, argList, desc); + + // If download didn't work + if (!fio.isValidPath(ytdlPath)) { + + // Notify + handleCritErr(ytdlDesc + " could not be downloaded. " + + "\nCheck your internet connection."); + } // Make executable hidden - Path ppo = Paths.get(ytdlPath); - try { - Files.setAttribute(ppo, "dos:hidden", true, - LinkOption.NOFOLLOW_LINKS); - } catch (IOException e) { - Code.outputerr(e); - } + FileIO.hideFile(ytdlPath); + } else { // Notify outputln("Found pre-existing!"); // Determine version - Command versionC = new Command(ytdlPath, new String[]{"--version"}); + ArrayList vArgList = new ArrayList<>(); + vArgList.add("--version"); + Command versionC = new Command(ytdlPath, vArgList); versionC.run(); - String version = refineCommOutLine(versionC.getOutput()); + String version = Command.refineCommOutLine(versionC.getOutput()); outputln("Version: " + version); - outputln(""); + + // Notify + outputln("If this version date looks too old, " + + "delete the hidden '" + ytdlPath + "', " + + "\nso it may be re-downloaded when the " + + "program starts next."); } + + // Space + outputln(""); } /** @@ -173,7 +183,7 @@ public final String getDwlFolderPath() { // Extract downloads path and refine dwlPathTry = comm.getErrOutput().split(" ")[1]; - dwlPathTry = refineCommOutLine(dwlPathTry); + dwlPathTry = Command.refineCommOutLine(dwlPathTry); dwlPathTry = dwlPathTry.replace("'", ""); // If path is valid @@ -189,11 +199,10 @@ public final String getDwlFolderPath() { } // If no methods worked, notify and exit - outputln("\nDownloads folder could not be found"); - System.exit(1); + handleCritErr("Downloads folder could not be found"); // Never reached - return dwlPathTry; + return null; } /** @@ -213,14 +222,6 @@ public void parseURL(String refinedURL) { // If parsing was successful if (getYTCommStatus(parseC, "Available formats for")) { - // Notify - outputln("\nSuccessfully parsed URL! Choose your desired format " - + "in the dropdown format menu, " - + "then press the Download button."); - - // Get format list - String[] formats = parseC.getOutput().split("NL:"); - // Get combo box JComboBox formatCB = GUI.gui.getFormatCB(); @@ -230,6 +231,12 @@ public void parseURL(String refinedURL) { // Add default option formatCB.addItem(defOpt); + // Get format list + String[] formats = parseC.getOutput().split("NL:"); + + // Format count + int formatCount = 0; + // Add certain output lines as formats for (String curFmtS : formats) { @@ -243,14 +250,28 @@ public void parseURL(String refinedURL) { // Add to combo box formatCB.addItem(curFmtS); + + // Increase format count + formatCount++; } } + // Notify + outputln("\nSuccessfully parsed URL! " + + formatCount + " formats discovered." + + "\nChoose your desired format " + + "in the dropdown format menu, " + + "then press the Download button."); + + //// Update interface // Enable combo box formatCB.setEnabled(true); // Disable parse button GUI.gui.getParseBut().setEnabled(false); + + // Enable download button + GUI.gui.getDownloadBut().setEnabled(true); } else { // Else if URL could not be parsed @@ -290,20 +311,19 @@ public void processDwlReq(String refinedURL) { // Notify outputln("\nDownloaded video successfully!"); - // Move video to downloads folder + // Move video to Downloads folder String finalPath = fio.moveToDownloads(dirBefore); // Notify and print about path outputln("Path: " + finalPath); // Notify and print about size - double sizeInBytes = new File(finalPath).length(); - double sizeInMB = sizeInBytes / (1024.0 * 1024.0); - sizeInMB = Math.round(sizeInMB * 100.0) / 100.0; - outputln("Size: " + sizeInMB + " MB"); + outputln("Size: " + FileIO.getReadableFileSize(finalPath)); - // Exit if desired + // If exit wanted if (GUI.gui.getCheckboxStatus("exit")) { + + // Exit normally System.exit(0); } } else { @@ -352,11 +372,11 @@ private ArrayList getExtraDownloadArgs() { * * @param refinedURL Refined URL * @param extra Extra arguments - * @param desc Present-tense verb description of process + * @param desc Description of process * @return */ - private Command runSlowYTD(String refinedURL, - ArrayList extra, String desc) { + private Command runSlowYTD(String refinedURL, ArrayList extra, + String desc) { // Arguments ArrayList argList = new ArrayList<>(); @@ -368,8 +388,8 @@ private Command runSlowYTD(String refinedURL, argList.add("--verbose"); // If cookies enabled - if (GUI.gui.getCheckboxStatus("cookie")) { - + if (GUI.gui.isCookieFileEnabled()) { + // Add cookie argument argList.add("--cookies " + FileIO.cookieFP); } @@ -380,15 +400,29 @@ private Command runSlowYTD(String refinedURL, // Add custom arguments argList.addAll(GUI.gui.getCustomArguments()); - // Create comm - Command comm = new Command(ytdlPath, argList); + // Run slow command and return + return runSlowComm(ytdlPath, argList, desc); + } + + /** + * Run a slow command, giving user progress indication during execution + * + * @param prog Program name + * @param argList Argument list + * @param desc Description of process (e.g. Downloading) + * @return + */ + private Command runSlowComm(String prog, ArrayList argList, + String desc) { + + // Create command + Command comm = new Command(prog, argList); // Output command to be run output(comm.toString() + "\n"); // Start process notifications - output( - "\n" + desc + "..."); + output("\n" + desc + "..."); // Create printing dots task TimerTask tt = new TimerTask() { @@ -400,18 +434,16 @@ public void run() { // Start printing dots intermittently, // to let user know that program is working - timer.scheduleAtFixedRate(tt, - 10, 777); + timer.scheduleAtFixedRate(tt, 10, 639); // Run command comm.run(); - // Cancel process notifications after parsing finished + // Cancel process notifications (when command finished) tt.cancel(); // Space - outputln( - ""); + outputln(""); // Return command return comm; @@ -443,6 +475,25 @@ private void notifyDwlPath(boolean successful, int method, String dwlPathTry) { outputln(msg); } + /** + * Enable interface elements after initialization is finished + */ + private void enableInterfaceElements() { + + // Enable text fields + gui.getURLField().setEnabled(true); + gui.getCustArgField().setEnabled(true); + + // Enable buttons + gui.getExportBut().setEnabled(true); + gui.getRegenBut().setEnabled(true); + gui.getResetBut().setEnabled(true); + + // Enable checkboxes + gui.getCheckbox("exit").setEnabled(true); + gui.getCheckbox("cookie").setEnabled(true); + } + /** * Open the given URL * @@ -451,11 +502,34 @@ private void notifyDwlPath(boolean successful, int method, String dwlPathTry) { public void openURL(String url) { // Run command to open URL via explorer.exe - String[] args = {url}; + ArrayList args = new ArrayList<>(); + args.add(url); Command comm = new Command("explorer.exe", args); comm.run(); } + /** + * When critical error occurs, stop execution but keep window open until it + * is closed by user + * + * @param desc + */ + public void handleCritErr(String desc) { + + // Notify + outputln("CRITICAL ERROR: " + desc); + outputln("Program stopped. " + + "Take note of the error and close the program."); + + // Sleep until user closes window + while (true) { + try { + Thread.sleep(10); + } catch (InterruptedException ex) { + } + } + } + /** * Output an exception to both the console and output TA * @@ -466,11 +540,11 @@ public static void outputerr(Exception e) { // Convert exception to string String s = "Err: " + e.toString(); - // Add to output TA without a new line - GUI.gui.updateOutput(s, false); - // Print out System.out.print(s); + + // Add to output TA without a new line + GUI.gui.updateOutput(s, false); } /** @@ -480,11 +554,11 @@ public static void outputerr(Exception e) { */ public static void outputln(String s) { - // Add to output TA with a new line - GUI.gui.updateOutput(s, true); - // Print out as new line System.out.println(s); + + // Add to output TA with a new line + GUI.gui.updateOutput(s, true); } /** @@ -494,11 +568,11 @@ public static void outputln(String s) { */ private static void output(String s) { - // Add to output TA without a new line - GUI.gui.updateOutput(s, false); - // Print out System.out.print(s); + + // Add to output TA without a new line + GUI.gui.updateOutput(s, false); } /** @@ -515,12 +589,12 @@ private boolean getYTCommStatus(Command comm, String successPart) { } /** - * Extract a line of command output + * Return string in quotes * - * @param outputLine + * @param s * @return */ - private String refineCommOutLine(String outputLine) { - return outputLine.replace("\nNL:", ""); + public static String quoteS(String s) { + return "\"" + s + "\""; } } diff --git a/Project/src/main/Command.java b/Project/src/main/Command.java index c14db7b..2a26323 100644 --- a/Project/src/main/Command.java +++ b/Project/src/main/Command.java @@ -13,6 +13,9 @@ */ public class Command { + // Start of output lines + private static final String lineStart = "\nNL:"; + // Command list private final ArrayList cmdList; @@ -21,18 +24,18 @@ public class Command { private String errOutput; /** - * Initialize command with program name and arg array + * Initialize command with program name and argument list * * @param progName Program - * @param args Arguments + * @param argList */ - public Command(String progName, String[] args) { + public Command(String progName, ArrayList argList) { // Add program name and space to command String commS = progName + " "; // Add arguments to command - for (String curArg : args) { + for (String curArg : argList) { commS += curArg + " "; } @@ -43,17 +46,6 @@ public Command(String progName, String[] args) { cmdList.add(commS); } - /** - * Initialize command with program name and arg list (wrapper constructor) - * - * @param progName Program - * @param argList - */ - public Command(String progName, ArrayList argList) { - - this(progName, argList.toArray(new String[0])); - } - /** * Run command */ @@ -68,8 +60,9 @@ public void run() { // Extract output normOutput = getStringFromStream(p.getInputStream()); errOutput = getStringFromStream(p.getErrorStream()); - - // Always print full output (console only) + + // Always print command and full output to console + System.out.println(toString() + " (for debugging)"); printOutput(); } catch (IOException e) { @@ -100,7 +93,7 @@ private String getStringFromStream(InputStream is) throws IOException { // Extract sting String curLine; while ((curLine = reader.readLine()) != null) { - output += " \nNL:" + curLine; + output += " " + lineStart + curLine; } // If line is very short @@ -124,6 +117,16 @@ public void printOutput() { System.out.println(); } + /** + * Extract a line of command output + * + * @param outputLine + * @return + */ + public static String refineCommOutLine(String outputLine) { + return outputLine.replace(lineStart, ""); + } + /** * Get string representation of command (as it would be manually typed) */ diff --git a/Project/src/main/FileIO.java b/Project/src/main/FileIO.java index 3bf3999..7d130e9 100644 --- a/Project/src/main/FileIO.java +++ b/Project/src/main/FileIO.java @@ -2,12 +2,13 @@ import java.io.File; import java.io.IOException; -import java.net.CookieStore; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -25,10 +26,22 @@ public class FileIO { // Cookies file path public static final String cookieFP = "cookies.txt"; + // Substring used to determine whether a text file is an exported cookie file + private final String validCookieSS = "# Netscape HTTP Cookie File"; + /** - * Generate cookie file + * Generate cookie file if cookies are enabled + * + * @param regen Delete and regenerate option */ - public void genCookieFile() { + public void genCookieFile(boolean regen) { + + // If cookies are not enabled + if (!GUI.gui.isCookieFileEnabled()) { + + // Do not process further + return; + } // Space outputln(""); @@ -37,14 +50,27 @@ public void genCookieFile() { if (isValidPath(cookieFP)) { // Notify - outputln("Previous '" + cookieFP - + "' file was found next to exe! " - + "This file will be used."); - outputln("If you wish to regenerate the cookies file, " - + "delete this file and try enabling cookies again."); + outputln("Previous '" + cookieFP + "' file was found."); + + // If regeneration is wanted + if (regen) { + + // Delete cookie file + deleteFile(cookieFP); + + // Notify + outputln("Deleted previous cookies file. Regenerating..."); + + } else { + + // Else if regeneration is not wanted + outputln("This cookies file will be used. To make " + + "a new cookies file, use the regenerate button."); + + // Do not process further + return; + } - // Do not process further - return; } else { // Else if no cookies file exists, @@ -57,10 +83,10 @@ public void genCookieFile() { // Notify about number of downloads files int numF = paths.length; - outputln("Detected " + numF + " files in downloads folder."); + outputln("Detected " + numF + " files in Downloads folder."); int fLimit = 30; if (numF > fLimit) { - outputln("Since this program scans all downloads folder files, " + outputln("Since this program scans all Downloads folder files, " + "it would be a good idea to " + "reduce the number of files present " + "(to below " + fLimit + ")"); @@ -69,9 +95,6 @@ public void genCookieFile() { // Cookies file string String cookiesFS = ""; - // Valid cookie file substring - String cookieSubstr = "# Netscape HTTP Cookie File"; - // For each filepath in paths for (String fp : paths) { @@ -87,26 +110,14 @@ public void genCookieFile() { // If file is definitely a text file if (file.endsWith(".txt")) { - // Read in contents - String curContents = getFileAsString(fp); + // Process text file + String contents = processTxtFileinDwl(fp, file); - // If seems like cookie file - if (curContents.contains(cookieSubstr)) { - - // Notify - outputln("Retrieved data from " - + "valid exported cookie file: " + file); + // If contents is not null + if (contents != null) { // Add to cookie file string - cookiesFS += curContents; - } else { - - // If non-valid cookie text file was found, notify - outputln("Found text file (" + file - + "), but was not " - + "valid exported cookie file."); - outputln("(i.e. didn't contain: '" - + cookieSubstr + "')"); + cookiesFS += contents; } } } @@ -115,7 +126,7 @@ public void genCookieFile() { // If cookie file string is empty if (cookiesFS.isEmpty()) { outputln("No exported cookie text files were " - + "found in the downloads folder."); + + "found in the Downloads folder."); outputln("Cookies file could not be generated."); return; } @@ -128,7 +139,7 @@ public void genCookieFile() { } /** - * Move the downloaded video to the downloads folder + * Move the downloaded video to the Downloads folder * * @param dirBefore * @return @@ -147,29 +158,40 @@ public String moveToDownloads(String[] dirBefore) { dirAL.remove(curFile); } - // Arguments - String[] args = new String[2]; + // Move command arguments + ArrayList movArgs = new ArrayList<>(); // If one file remains if (dirAL.size() == 1) { // Get the string remaining - the new file + // This must be the downloaded media file String newFile = dirAL.get(0); newFile = newFile.replace(".\\", ""); - newFile = "\"" + newFile + "\""; // Move file to downloads String prog = "move"; - args[0] = newFile; - args[1] = downloadsPath; - Command comm = new Command(prog, args); + movArgs.add(Code.quoteS(newFile)); + movArgs.add(Code.quoteS(downloadsPath)); + Command comm = new Command(prog, movArgs); comm.run(); - } - // Deduce final path and return - args[0] = args[0].replace("\"", ""); - String finalPath = args[1] + "\\" + args[0]; - return finalPath; + // Deduce final path and return + String finalPath = movArgs.get(1) + "\\"; + finalPath += movArgs.get(0); + finalPath = finalPath.replace("\"", ""); + return finalPath; + } else { + + // Else, there is no new file detected + outputln("Error: Could not move file to downloads"); + outputln("Debug Info:"); + outputln("DirBefore: " + Arrays.toString(dirBefore)); + outputln("DirAfter: " + Arrays.toString(dirAfter)); + + // Return where file is + return "next to exe"; + } } /** @@ -218,6 +240,76 @@ public String[] getFileList(String dirPath) { return fileStrings; } + /** + * Process a text file in the Downloads folder + * + * @param fp The file path + * @param file The file's name and extension + * @return Contents if valid cookie file, otherwise null + */ + private String processTxtFileinDwl(String fp, String file) { + + // Read in contents + String curContents = getFileAsString(fp); + + // If seems like cookie file + if (curContents.contains(validCookieSS)) { + + // Notify + outputln("Retrieved data from valid exported cookie file: " + + file); + + // Return contents for adding to cookies file + return curContents; + } else { + + // If non-valid cookie text file was found, notify + outputln("Found text file (" + file + "), but was not valid " + + "exported cookie file."); + outputln("(i.e. didn't contain: '" + + validCookieSS + "')"); + + // Return null + return null; + } + } + + /** + * Get readable size of a given file + * + * @param fp File path + * @return + */ + public static String getReadableFileSize(String fp) { + + // Get raw file size + long size = 0; + try { + size = Files.size(Paths.get(fp)); + } catch (IOException ex) { + outputerr(ex); + } + + // If size is 0 + if (size <= 0) { + + // Return 0 bytes + return "0 B"; + } + + // Add size + int dg = (int) (Math.log10(size) / Math.log10(1024)); + String rs; + rs = new DecimalFormat("#,##0.#").format(size / Math.pow(1024, dg)); + + // Add unit + String[] units = {"B", "kB", "MB", "GB", "TB"}; + rs += " " + units[dg]; + + // Return readable size + return rs; + } + /** * Get contents of a file as a string * @@ -264,6 +356,25 @@ public void saveStringToFile(String fpS, String outputS) { } } + /** + * Hide a given file + * + * @param fp File path + */ + public static void hideFile(String fp) { + try { + + // Get path + Path path = Paths.get(fp); + + // Set hidden attribute to true + LinkOption lo = LinkOption.NOFOLLOW_LINKS; + Files.setAttribute(path, "dos:hidden", true, lo); + } catch (IOException e) { + Code.outputerr(e); + } + } + /** * Delete file at given file path * @@ -271,6 +382,15 @@ public void saveStringToFile(String fpS, String outputS) { * @return True if successful */ public boolean deleteFile(String fp) { + + // If file does not exist + if (!isValidPath(fp)) { + + // Return false as cannot delete + return false; + } + + // Return true if file was deleted return new File(fp).delete(); } @@ -278,10 +398,9 @@ public boolean deleteFile(String fp) { * Return true if path is valid file or folder * * @param path - * @return + * @return True if valid */ public boolean isValidPath(String path) { return (new File(path)).exists(); } - } diff --git a/Project/src/main/GUI.form b/Project/src/main/GUI.form index ded7324..2d186f4 100644 --- a/Project/src/main/GUI.form +++ b/Project/src/main/GUI.form @@ -135,7 +135,7 @@ - + @@ -166,27 +166,27 @@ - - - - + - - - - - - - - + + + + + + + + + + + @@ -247,10 +247,11 @@ - + + @@ -611,7 +612,7 @@ - + @@ -627,6 +628,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Project/src/main/GUI.java b/Project/src/main/GUI.java index b46aaf6..db312b4 100644 --- a/Project/src/main/GUI.java +++ b/Project/src/main/GUI.java @@ -7,6 +7,7 @@ import java.awt.Toolkit; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -110,6 +111,7 @@ private void initComponents() { javax.swing.JLabel formatLabel = new javax.swing.JLabel(); javax.swing.JTextField custArgField = new javax.swing.JTextField(); javax.swing.JCheckBox cookieCheckbox = new javax.swing.JCheckBox(); + javax.swing.JButton regenBut = new javax.swing.JButton(); javax.swing.JMenuBar menuBar = new javax.swing.JMenuBar(); javax.swing.JMenu readmeMenu = new javax.swing.JMenu(); javax.swing.JMenu sitesMenu = new javax.swing.JMenu(); @@ -301,7 +303,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { cookieCheckbox.setBackground(new java.awt.Color(51, 0, 51)); cookieCheckbox.setFont(new java.awt.Font("Segoe UI", 1, 18)); // NOI18N cookieCheckbox.setForeground(new java.awt.Color(255, 255, 255)); - cookieCheckbox.setText("Generate and Use Cookies File"); + cookieCheckbox.setText("Use Cookies File"); cookieCheckbox.setActionCommand(""); cookieCheckbox.setBorder(javax.swing.BorderFactory.createCompoundBorder()); cookieCheckbox.setEnabled(false); @@ -314,6 +316,25 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { } }); + regenBut.setBackground(new java.awt.Color(204, 153, 255)); + regenBut.setFont(new java.awt.Font("Segoe UI", 1, 18)); // NOI18N + regenBut.setForeground(new java.awt.Color(0, 0, 0)); + regenBut.setText("Delete and Regenerate Cookies File"); + regenBut.setToolTipText(""); + regenBut.setAlignmentY(0.0F); + regenBut.setBorder(null); + regenBut.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + regenBut.setEnabled(false); + regenBut.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + regenBut.setIconTextGap(0); + regenBut.setMargin(new java.awt.Insets(0, 0, 0, 0)); + regenBut.setName("regenBut"); // NOI18N + regenBut.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + regenButActionPerformed(evt); + } + }); + javax.swing.GroupLayout panelLayout = new javax.swing.GroupLayout(panel); panel.setLayout(panelLayout); panelLayout.setHorizontalGroup( @@ -322,22 +343,24 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addGap(33, 33, 33) .addGroup(panelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(panelLayout.createSequentialGroup() - .addGroup(panelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(exitCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 179, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(custArgLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 282, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(exitCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 179, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) - .addGroup(panelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(panelLayout.createSequentialGroup() - .addComponent(cookieCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 318, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(resetBut, javax.swing.GroupLayout.PREFERRED_SIZE, 186, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(custArgField))) + .addComponent(cookieCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 208, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(regenBut, javax.swing.GroupLayout.PREFERRED_SIZE, 335, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(resetBut, javax.swing.GroupLayout.PREFERRED_SIZE, 186, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelLayout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) .addComponent(formatLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 81, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(formatCB, javax.swing.GroupLayout.PREFERRED_SIZE, 880, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(parseBut, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(downloadBut, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(downloadBut, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(panelLayout.createSequentialGroup() + .addComponent(custArgLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 282, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(custArgField))) .addGap(868, 868, 868)) .addGroup(panelLayout.createSequentialGroup() .addGap(34, 34, 34) @@ -384,10 +407,11 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(custArgField, javax.swing.GroupLayout.PREFERRED_SIZE, 41, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(18, 18, 18) .addGroup(panelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(cookieCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(regenBut, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(panelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(resetBut, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(exitCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addComponent(exitCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(cookieCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(downloadBut, javax.swing.GroupLayout.PREFERRED_SIZE, 56, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18)) @@ -451,7 +475,7 @@ public void mouseClicked(java.awt.event.MouseEvent evt) { getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(panel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 1041, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(panel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 1041, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -493,12 +517,9 @@ private void readmeMenuMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST: * @param evt */ private void resetButActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_resetButActionPerformed - - // Notify - outputln("\nResetted downloader!"); - - // Reset URL field + // Reset fields getURLField().setText(""); + getCustArgField().setText(""); // Remove format options and disable format selector JComboBox formatCB = getFormatCB(); @@ -507,6 +528,9 @@ private void resetButActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRS // Disable download button getDownloadBut().setEnabled(false); + + // Notify + outputln("\nResetted downloader!"); }//GEN-LAST:event_resetButActionPerformed /** @@ -515,7 +539,6 @@ private void resetButActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRS * @param evt */ private void formatCBFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_formatCBFocusGained - // Enable download button getDownloadBut().setEnabled(true); }//GEN-LAST:event_formatCBFocusGained @@ -526,12 +549,14 @@ private void formatCBFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:eve * @param evt */ private void parseButActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_parseButActionPerformed + // Generate cookie file if enabled + fio.genCookieFile(false); // Parse URL on separate thread timer.schedule(new TimerTask() { @Override public void run() { - code.parseURL(GUI.gui.getRefinedURL()); + code.parseURL(GUI.gui.getURLArg()); } }, 5); }//GEN-LAST:event_parseButActionPerformed @@ -569,12 +594,14 @@ private void urlFieldFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:eve * @param evt */ private void downloadButAction(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_downloadButAction + // Generate cookie file if enabled + fio.genCookieFile(false); // Process download request on separate thread timer.schedule(new TimerTask() { @Override public void run() { - code.processDwlReq(GUI.gui.getRefinedURL()); + code.processDwlReq(GUI.gui.getURLArg()); } }, 5); }//GEN-LAST:event_downloadButAction @@ -585,9 +612,20 @@ public void run() { * @param evt */ private void exportButActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportButActionPerformed + // Log folder path + String logPath = "logs"; + + // If log folder does not exist + if (!fio.isValidPath(logPath)) { + + // Create log folder + new File(logPath).mkdir(); + } // Get file path to save to - String fpS = "JYTD-output-"; + String fpS = logPath + "\\JYTD-output-"; + + // Add current date and time DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); Date date = new Date(); fpS += dateFormat.format(date) + ".log"; @@ -609,11 +647,11 @@ private void exportButActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIR */ private void cookieCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cookieCheckboxActionPerformed // If cookie checkbox is selected - if (getCheckboxStatus("cookie")) { + if (isCookieFileEnabled()) { // Output usage String usage = ""; - usage += "\nCookies enabled!"; + usage += "\nCookies file usage enabled!"; usage += "\nCookies can be used to provide credentials " + "(e.g. for downloading members-only videos)."; usage += "\nCookie files can be acquired by getting a " @@ -621,20 +659,18 @@ private void cookieCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GE usage += "\nAn example is the 'Get cookies.txt' extension " + "by R. Shaw on the Chrome Web Store."; usage += "\nFor YouTube, cookies must be exported from " - + "“www.youtube.com” and “myaccount.google.com”."; + + "'www.youtube.com' and 'myaccount.google.com'."; usage += "\nEnsure all exported Netscape format cookie text files " - + "are in your downloads folder."; + + "are in your Downloads folder."; + usage += "\nThe exported cookie files in your Downloads folder " + + "will be used to generate the cookies file."; // Output usage info outputln(usage); - - // Try generating cookies file - fio.genCookieFile(); - + } else { - outputln("\nCookies disabled."); + outputln("\nCookies file usage disabled."); } - }//GEN-LAST:event_cookieCheckboxActionPerformed /** @@ -643,14 +679,17 @@ private void cookieCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GE * @param evt */ private void memberMenuMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_memberMenuMouseClicked - -// // Open supported sites URL -// String sites; -// sites = "https://github.com/ytdl-org/youtube-dl/"; -// sites += "blob/master/docs/supportedsites.md"; -// code.openURL(sites); + // Open GitHub wiki page + String wiki = "https://github.com/DavoDC/JavaYTD/"; + wiki += "wiki/How-to-Download-Members-Only-Videos"; + code.openURL(wiki); }//GEN-LAST:event_memberMenuMouseClicked + /** + * Exit checkbox + * + * @param evt + */ private void exitCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exitCheckboxActionPerformed // If exit checkbox is selected if (getCheckboxStatus("exit")) { @@ -660,6 +699,24 @@ private void exitCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN- } }//GEN-LAST:event_exitCheckboxActionPerformed + /** + * Regenerate cookies file + * + * @param evt + */ + private void regenButActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_regenButActionPerformed + // If cookies are not enabled + if (!isCookieFileEnabled()) { + + // Notify and stop + outputln("\nCookie file usage is not enabled."); + return; + } + + // Regenerate/generate cookie file + fio.genCookieFile(true); + }//GEN-LAST:event_regenButActionPerformed + /** * Retrieve a component by its name * @@ -685,11 +742,8 @@ public Component getComponentByName(String nameQuery) { comp = curComp; break; } - } - System.out.println(); - // If no component could be found if (comp == null) { @@ -717,7 +771,7 @@ public void updateOutput(String outputS, boolean newLine) { // If new line wanted if (newLine) { - // Add new line + // Add new line character outputS += "\n"; } @@ -729,30 +783,39 @@ public void updateOutput(String outputS, boolean newLine) { } /** - * Get only important part of URL + * Get URL argument * * @return */ - public String getRefinedURL() { + public String getURLArg() { // Get raw URL string String rawURL = GUI.gui.getURLField().getText(); + // Holder + String refURL; + // If URL contains ampersand if (rawURL.contains("&")) { // Refine, notify and return - return refineURLtype(rawURL, "&"); + refURL = refineURLtype(rawURL, "&"); } else if (rawURL.contains("?list=")) { // Refine, notify and return - return refineURLtype(rawURL, "?list="); + refURL = refineURLtype(rawURL, "?list="); } else { - // Otherwise, return raw URL - return rawURL; + // Otherwise, use raw URL + refURL = rawURL; } + + // Quote URL + refURL = Code.quoteS(refURL); + + // Return final URL + return refURL; } /** @@ -766,7 +829,7 @@ private String refineURLtype(String rawURL, String splitS) { // Get split pattern String pattern = Pattern.quote(splitS); - // Split up by ampersand + // Split up by pattern String[] URLparts = rawURL.split(pattern); // Extract first part @@ -845,6 +908,15 @@ public boolean getCheckboxStatus(String nameSubstr) { return getCheckbox(nameSubstr).isSelected(); } + /** + * Return true if cookie file usage is enabled + * + * @return + */ + public boolean isCookieFileEnabled() { + return getCheckboxStatus("cookie"); + } + /** * Get custom arguments text field * @@ -881,6 +953,15 @@ public JButton getDownloadBut() { return (JButton) getComponentByName("downloadBut"); } + /** + * Get export button + * + * @return + */ + public JButton getExportBut() { + return (JButton) getComponentByName("exportBut"); + } + /** * Get parse button * @@ -900,12 +981,12 @@ public JButton getResetBut() { } /** - * Get export button + * Get regenerate button * * @return */ - public JButton getExportBut() { - return (JButton) getComponentByName("exportBut"); + public JButton getRegenBut() { + return (JButton) getComponentByName("regenBut"); } /** diff --git a/Screenshots/CookiesExt.png b/Screenshots/CookiesExt.png new file mode 100644 index 0000000..872827d Binary files /dev/null and b/Screenshots/CookiesExt.png differ diff --git a/Screenshots/ExportGoogle.png b/Screenshots/ExportGoogle.png new file mode 100644 index 0000000..8f9e908 Binary files /dev/null and b/Screenshots/ExportGoogle.png differ diff --git a/Screenshots/PinExt.png b/Screenshots/PinExt.png new file mode 100644 index 0000000..74d3926 Binary files /dev/null and b/Screenshots/PinExt.png differ diff --git a/Screenshots/Preview1.2.png b/Screenshots/Preview1.2.png new file mode 100644 index 0000000..f0f8c70 Binary files /dev/null and b/Screenshots/Preview1.2.png differ diff --git a/Screenshots/exportYT.png b/Screenshots/exportYT.png new file mode 100644 index 0000000..4fd2ca4 Binary files /dev/null and b/Screenshots/exportYT.png differ