From 724cef29f361b2cfd1c4fa302a81e5774f8c8b40 Mon Sep 17 00:00:00 2001 From: Pinapelz Date: Mon, 6 Mar 2023 02:39:05 -0800 Subject: Major code refactor --- .gitignore | 34 ++--- pom.xml | 6 + src/main/java/FileUtility.java | 37 +++-- src/main/java/Main.java | 269 ++++++++++++++++++++++++++----------- src/main/java/TagEditorScreen.java | 149 ++++++++++---------- 5 files changed, 299 insertions(+), 196 deletions(-) diff --git a/.gitignore b/.gitignore index abf6eda..bf4bd56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,9 @@ - -*.mp3 - - -.idea/compiler.xml -.idea/encodings.xml -.idea/jarRepositories.xml -.idea/misc.xml -.idea/uiDesigner.xml -*.jar -*.lst -*.properties -*.class -*.json +completed/ +downloaded/ +out/ +target/ +.idea/ songs.txt -.idea/modules.xml -.idea/vcs.xml -.idea/libraries/Maven__org_jaudiotagger_2_0_3.xml -.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml -.idea/libraries/Maven__junit_junit_4_11.xml -.idea/libraries/Maven__commons_io_commons_io_2_8_0.xml -.idea/libraries/Maven__com_mpatric_mp3agic_0_9_1.xml -.idea/libraries/Maven__com_jgoodies_jgoodies_forms_1_8_0.xml -.idea/libraries/Maven__com_jgoodies_jgoodies_common_1_8_0.xml -.idea/ytmp3AutoTag.iml -.idea/artifacts/ytmp3AutoTag_jar.xml -META-INF/MANIFEST.MF +build/ +.idea/libraries/ +META-INF \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7c3347c..ef973f0 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,12 @@ jaudiotagger 2.0.3 + + + org.json + json + 20230227 + com.jgoodies jgoodies-forms diff --git a/src/main/java/FileUtility.java b/src/main/java/FileUtility.java index 067e9ea..a39df78 100644 --- a/src/main/java/FileUtility.java +++ b/src/main/java/FileUtility.java @@ -1,12 +1,17 @@ +import org.json.JSONArray; +import org.json.JSONObject; + import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter; import java.io.*; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.regex.Matcher; import java.util.regex.Pattern; + public class FileUtility { public void deleteFile(String fileName) { File file = new File(fileName); @@ -37,6 +42,7 @@ public class FileUtility { } } + public String removeBlacklist(String s, String filename){ HashMap blacklist = arrayListToHashMap(readTextFile(filename),":"); for(String key : blacklist.keySet()){ @@ -63,8 +69,8 @@ public class FileUtility { } return lines; } - public HashMap arrayListToHashMap(ArrayList list, String delimiter) { + public HashMap arrayListToHashMap(ArrayList list, String delimiter) { HashMap map = new HashMap(); for (String line : list) { String[] parts = line.split(delimiter); @@ -91,9 +97,11 @@ public class FileUtility { sourceFile.renameTo(destinationFile); System.out.println("Moved file to Completed Folder"); } + public String removeNonAlphaNumeric(String str) { return str.replaceAll("[^a-zA-Z0-9]", ""); } + public static String showTextFileChooser() { javax.swing.JFileChooser chooser = new javax.swing.JFileChooser(); FileNameExtensionFilter filter = new FileNameExtensionFilter("Text File", "txt", "text"); @@ -107,11 +115,12 @@ public class FileUtility { return null; } } - public static File findMP3File(String directory){ + + public static File findFileType(String directory, String fileExt){ File dir = new File(directory); File[] files = dir.listFiles(); for(File file : files){ - if(file.getName().endsWith(".mp3")){ + if(file.getName().endsWith(fileExt)){ return file; } } @@ -130,22 +139,11 @@ public class FileUtility { } return null; } - public static String[] parseJson(String json) { - String title = ""; - String uploader = ""; - String id = ""; - Pattern titlePattern = Pattern.compile("\"fulltitle\": \"(.*?)\","); - Matcher titleMatcher = titlePattern.matcher(json); - Pattern uploaderPattern = Pattern.compile("\"uploader\": \"(.*?)\","); - Matcher uploaderMatcher = uploaderPattern.matcher(json); - Pattern idPattern = Pattern.compile("\"id\": \"(.*?)\","); - Matcher idMatcher = idPattern.matcher(json); - titleMatcher.find(); - idMatcher.find(); - uploaderMatcher.find(); - title = titleMatcher.group(1); - uploader = uploaderMatcher.group(1); - id = idMatcher.group(1); + public static String[] parseInfoJSON(String json) { + JSONObject obj = new JSONObject(json); + String title = obj.getString("fulltitle"); + String uploader = obj.getString("uploader"); + String id = obj.getString("id"); String[] info = {title,uploader,id}; return info; @@ -190,6 +188,7 @@ public class FileUtility { return null; } } + public static ArrayList txtToArrayList(String fileName) { ArrayList lines = new ArrayList(); try { diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 36ec8a9..9b7373c 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -2,8 +2,10 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Date; import org.jaudiotagger.audio.AudioFile; import org.jaudiotagger.audio.AudioFileIO; @@ -16,9 +18,15 @@ import javax.swing.text.DefaultCaret; public class Main extends JFrame { + final static String BLACKLIST = "blacklist.txt"; + final static String DOWNLOADED_DIR = "downloaded"; + final static String COMPLETED_DIR = "completed"; + + String textPath = ""; String formats[] = {"maxresdefault.jpg", "mqdefault.jpg", "hqdefault.jpg"}; + static JTextArea outputArea = new JTextArea(""); JPanel panel = new JPanel(); JScrollPane scrollPane; JButton songsGen = new JButton("Generate text file"); @@ -27,10 +35,7 @@ public class Main extends JFrame { JCheckBox defaultFileBox = new JCheckBox("Use Default songs.txt file"); JCheckBox useBlacklistBox = new JCheckBox("Use Blacklist.txt"); JProgressBar progressBar = new JProgressBar(); - static JTextArea outputArea = new JTextArea(""); JLabel title = new JLabel("YouTube to MP3 Auto Tagging [1]"); - - int progress = 0; Boolean useBlacklist = false; Boolean readyState = false; Boolean useDefault = false; @@ -46,71 +51,107 @@ public class Main extends JFrame { new Main().setVisible(true); } + /** + * Download and tag all songs in the text file + */ private void downloadAndTag() { ArrayList songs = fileUtil.txtToArrayList(textPath); - progress = 0; String timeAppend = ""; boolean partFlag = false; for (int i = 0; i < songs.size(); i++) { try { - fileUtil.deleteAllFilesDir("downloaded"); - ArrayList splitStamp = null; - try { - splitStamp = new ArrayList<>(Arrays.asList(songs.get(i).split(","))); - } catch (Exception e) { + fileUtil.deleteAllFilesDir(DOWNLOADED_DIR); + //Check if user's URL wants to download a part or full audio based on commas + ArrayList splitStamp = new ArrayList<>(Arrays.asList(songs.get(i).split(","))); + switch(splitStamp.size()){ + case 1: + downloadContentFull(songs.get(i)); + partFlag = false; + break; + case 2: + timeAppend = downloadContentPartial(splitStamp.get(0), splitStamp.get(1)); + partFlag = true; + break; + + default: + showError("Invalid Input: " + songs.get(i)+ + "\nReason: Invalid formatting. Please use the format: URL,START_TIME:END_TIME"); + return; } - if (splitStamp.size() >= 2) { - timeAppend = youtubeToMP3Part(splitStamp.get(0), splitStamp.get(1)); - partFlag = true; - } else { - youtubeToMP3Full(songs.get(i)); - } - String info[] = fileUtil.parseJson(fileUtil.jsonToString(fileUtil.findJsonFile("downloaded"))); //title,uploader + + String info[] = fileUtil.parseInfoJSON(fileUtil.jsonToString(fileUtil.findJsonFile(DOWNLOADED_DIR))); //title,uploader String uploader = info[1]; String title = info[0]; + String urlID = info[2]; + String imageUrl = "https://img.youtube.com/vi/" + urlID + "/"; + + // Remove blacklisted words if asked to if (useBlacklist) { - System.out.println("Using blacklist"); - uploader = fileUtil.removeBlacklist(uploader, "blacklist.txt"); - title = fileUtil.removeBlacklist(title, "blacklist.txt"); - } - AudioFile f = AudioFileIO.read(fileUtil.findMP3File("downloaded")); - Tag tag = f.getTag(); - System.out.println("Uploader: " + uploader); - System.out.println("Title: " + title); - tag.setField(FieldKey.ARTIST, uploader); - tag.setField(FieldKey.TITLE, title); - fileUtil.downloadImage("https://img.youtube.com/vi/" + info[2] + "/", "img.jpg", formats); - Artwork cover = Artwork.createArtworkFromFile(new File("img.jpg")); - tag.addField(cover); - f.commit(); - fileUtil.deleteFile("img.jpg"); - if (partFlag) { - fileUtil.moveFile(fileUtil.findMP3File("downloaded").getAbsolutePath(), "completed/" - + fileUtil.removeNonAlphaNumeric(info[0]) + - " [" + info[2] + "]" + timeAppend + ".mp3"); - } else { - fileUtil.moveFile(fileUtil.findMP3File("downloaded").getAbsolutePath(), "completed/" + - fileUtil.removeNonAlphaNumeric(info[0]) + " [" + info[2] + "].mp3"); + System.out.println("Using blacklist. Removing blacklisted words from title and uploader"); + uploader = fileUtil.removeBlacklist(uploader, BLACKLIST); + title = fileUtil.removeBlacklist(title, BLACKLIST); } + + // Method downloads as MP4, then converts to MP3. It's faster + File mp4File = fileUtil.findFileType(DOWNLOADED_DIR ,"mp4"); + mp4Tomp3(mp4File); + + boolean taggingSuccessful = tagMp3InDir(uploader, title, imageUrl); + if(!taggingSuccessful) + return; + + // If user wants to download a part of the video, append the time to the title. Else just move the file + File mp3Path = fileUtil.findFileType(DOWNLOADED_DIR, "mp3"); + String destinationPath = partFlag ? COMPLETED_DIR+"/" + fileUtil.removeNonAlphaNumeric(title) + "[" + urlID + "]" + timeAppend + ".mp3" : + COMPLETED_DIR+"/" + fileUtil.removeNonAlphaNumeric(info[0]) + "[" + urlID + "].mp3"; + System.out.println(destinationPath); + fileUtil.moveFile(mp3Path.getAbsolutePath(), destinationPath); + outputArea.setText(outputArea.getText() + "\n" + "Moved file to Completed Folder"); - progress = i; System.out.println("Current Progress " + calculatePercentage(i + 1, songs.size())); progressBar.setValue(calculatePercentage(i + 1, songs.size())); } catch (Exception e) { + showError("Error occured while downloading and tagging. Check the logs for more info"); e.printStackTrace(); } } } - private int calculatePercentage(int current, int total) {//Calculate the percentage when give numerator and denominator - double currentD = current; - double totalD = total; - return (int) ((currentD / totalD) * 100); + /** + * Tag mp3 with title, uploader, and image + * @param uploader Uploader of the video + * @param title Title of the video + * @param imageUrl URL of to the thumbnail image + */ + public boolean tagMp3InDir(String uploader, String title, String imageUrl) {//Tag mp3 file in downloaded directory + try { + AudioFile f = AudioFileIO.read(fileUtil.findFileType(DOWNLOADED_DIR, "mp3")); + Tag tag = f.getTag(); + System.out.println("Uploader: " + uploader); + System.out.println("Title: " + title); + tag.setField(FieldKey.ARTIST, uploader); + tag.setField(FieldKey.TITLE, title); + fileUtil.downloadImage(imageUrl, "img.jpg", formats); + Artwork cover = Artwork.createArtworkFromFile(new File("img.jpg")); + tag.addField(cover); + f.commit(); + fileUtil.deleteFile("img.jpg"); + } + catch(Exception e){ + showError("Error occured while tagging mp3. Check your program version"); + return false; + } + return true; + } - public static void youtubeToMP3Full(String url) {//Download mp3 of youtube video using yt-dlp.exe. Ran from cmd + /** + * Download part of YouTube URL in MP3 format + * @param url Youtube URL + */ + public static void downloadContentFull(String url) {//Download mp3 of youtube video using yt-dlp.exe. Ran from cmd try { ProcessBuilder builder = new ProcessBuilder( @@ -119,7 +160,7 @@ public class Main extends JFrame { "--extract-audio", "--audio-format", "mp3", "--audio-quality", "0", - "--output", "downloaded/%(title)s_%(id)s.mp3", + "--output", DOWNLOADED_DIR+"/%(title)s_%(id)s.mp3", "--ffmpeg-location", "ffmpeg.exe", "--write-info-json", url @@ -135,36 +176,30 @@ public class Main extends JFrame { } - public static String youtubeToMP3Part(String url, String stamp) { //Download mp3 of youtube video using yt-dlp.exe. Ran from cmd + /** + * Download part of YouTube URL in MP4 format + * @param url Youtube URL + * @param stamp Time stamp in format HH:MM:SS-HH:MM:SS + * @return String of time stamp to be used on filename startTimeInSeconds to endTimeInSeconds + */ + public static String downloadContentPartial(String url, String stamp) { //Download mp3 of youtube video using yt-dlp.exe. Ran from cmd System.out.println(url + " " + stamp); ArrayList times = new ArrayList<>(Arrays.asList(stamp.split("-"))); - ArrayList startTimeComponents = new ArrayList<>(Arrays.asList(times.get(0).split(":"))); - ArrayList endTimeComponents = new ArrayList<>(Arrays.asList(times.get(1).split(":"))); - int startSec = 0; - int endSec = 0; - if (startTimeComponents.size() == 3) { - startSec = Integer.parseInt(startTimeComponents.get(0)) * 60 * 60 + Integer.parseInt(startTimeComponents.get(1)) * - 60 + Integer.parseInt(startTimeComponents.get(2)); - } else if (startTimeComponents.size() == 2) { - startSec = Integer.parseInt(startTimeComponents.get(0)) * 60 + Integer.parseInt(startTimeComponents.get(1)); - } - if (endTimeComponents.size() == 3) { - endSec = Integer.parseInt(endTimeComponents.get(0)) * 60 * 60 + Integer.parseInt(endTimeComponents.get(1)) - * 60 + Integer.parseInt(endTimeComponents.get(2)); - } else if (endTimeComponents.size() == 2) { - endSec = Integer.parseInt(endTimeComponents.get(0)) * 60 + Integer.parseInt(endTimeComponents.get(1)); - } + String startTime = times.get(0); + String endTime = times.get(1); + + // Time to start in seconds and time to end in seconds + int startSec = timestampToSeconds(startTime); + int endSec = timestampToSeconds(endTime); try { ProcessBuilder builder = new ProcessBuilder( "yt-dlp.exe", "-vU", - "--extract-audio", - "--audio-format", "mp3", - "--audio-quality", "0", - "--output", "downloaded/%(title)s_%(id)s.mp3", - "--ffmpeg-location", "ffmpeg.exe", - "--write-info-json", "--download-sections", "\"*" + startSec + "-" + endSec + "\"", - "--force-keyframes-at-cuts", + "-f","\"(bestvideo+bestaudio/best)[protocol!*=dash]\"", + "--external-downloader", "ffmpeg.exe", + "--external-downloader-args", "\"ffmpeg_i:-ss " + startSec + " -to " + endSec + "\"", + "--output", "downloaded/%(title)s_%(id)s.mp4", + "--write-info-json", url ); builder.redirectErrorStream(true); @@ -178,7 +213,9 @@ public class Main extends JFrame { return startSec + "to" + endSec; } - + /** + * Initialize all GUI components + */ private void initializeComponents() {//Initiate GUI components this.setDefaultCloseOperation(EXIT_ON_CLOSE); this.setLocationRelativeTo(null); @@ -217,7 +254,10 @@ public class Main extends JFrame { } - private void initializeActionsListeners() {//Add all actionlisteners for buttons + /** + * Initialize all action listeners for buttons + */ + private void initializeActionsListeners() { //Add all actionlisteners for buttons defaultFileBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -289,13 +329,36 @@ public class Main extends JFrame { new TagEditorScreen().setVisible(true); } }); - songsGen.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - } - }); } + /** + * Convert mp4 to mp3 using ffmpeg + * @param mp4File The mp4 file to convert + */ + public static void mp4Tomp3(File mp4File){ + try { + String mp4FileName = mp4File.getName(); + String mp3FileName = mp4FileName.substring(0, mp4FileName.length() - 4) + ".mp3"; + ProcessBuilder builder = new ProcessBuilder( + "cmd.exe", "/c", "ffmpeg -i \"" + mp4File.getAbsolutePath() + "\" \""+DOWNLOADED_DIR+"/" + mp3FileName+"\"" + ); + builder.redirectErrorStream(true); + Process p = builder.start(); + relayConsole(p); + p.waitFor(); + System.out.println("Conversion of MP4 to MP3 complete"); + + } + catch (Exception e){ + e.printStackTrace(); + } + + } + + /** + * Relays the console output from the CMD to the outputArea + * @param p The process to relay to the outputArea from + */ public static void relayConsole(Process p) { BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream())); String cmd_line; @@ -314,20 +377,70 @@ public class Main extends JFrame { } } + /** + * Calculate the percentage for progress bar + * @param current The current number of songs downloaded + * @param total The total number of songs to download + * @return The percentage of songs downloaded + */ + private int calculatePercentage(int current, int total) {//Calculate the percentage when give numerator and denominator + double currentD = current; + double totalD = total; + return (int) ((currentD / totalD) * 100); + } + + /** + * Create the directories for the downloaded and completed files + */ public void createDirectories(){ - File f = new File("downloaded"); + File f = new File(DOWNLOADED_DIR); if (!f.exists()) { f.mkdir(); } - File f2 = new File("completed"); + File f2 = new File(COMPLETED_DIR); if (!f2.exists()) { f2.mkdir(); } } + /** + * Show warning message + */ public static void showWarning(String message) { JOptionPane.showMessageDialog(null, message, "JUST YOUR FRIENDLY NEIGHBORLY REMINDER", JOptionPane.WARNING_MESSAGE); } + /** + * Show error message + */ + public static void showError(String message) { + JOptionPane.showMessageDialog(null, message, "ERROR", JOptionPane.ERROR_MESSAGE); + } + + /** + * Convert timestamp to seconds hh:mm:ss or mm:ss + * @param timestamp The timestamp to convert + * Example: 01:03:20 + * @return The total number of seconds + */ + public static int timestampToSeconds(String timestamp){ + int totalSeconds = 0; + try { + SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); + Date date = sdf.parse(timestamp); + int hours = date.getHours(); + int minutes = date.getMinutes(); + int seconds = date.getSeconds(); + totalSeconds = hours * 3600 + minutes * 60 + seconds; + System.out.println(totalSeconds); + } + catch (Exception e){ + System.out.println("Error converting timestamp to seconds"); + e.printStackTrace(); + } + return totalSeconds; + + } + } diff --git a/src/main/java/TagEditorScreen.java b/src/main/java/TagEditorScreen.java index ee6d911..3979a28 100644 --- a/src/main/java/TagEditorScreen.java +++ b/src/main/java/TagEditorScreen.java @@ -2,6 +2,7 @@ import org.jaudiotagger.audio.*; import org.jaudiotagger.tag.FieldKey; import org.jaudiotagger.tag.Tag; import org.jaudiotagger.tag.datatype.Artwork; + import java.io.File; import javax.imageio.ImageIO; @@ -14,7 +15,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; -public class TagEditorScreen extends JFrame{ +public class TagEditorScreen extends JFrame { private JPanel mainPanel; private JTextField titleField; private JLabel titleLabel; @@ -35,22 +36,83 @@ public class TagEditorScreen extends JFrame{ private String currPath = ""; private Boolean imageSelected = false; - public TagEditorScreen(){ + public TagEditorScreen() { this.setContentPane(mainPanel); this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); this.setLocationRelativeTo(null); this.setSize(700, 550); initializeTable(); listenButton.setEnabled(false); + initalizeListeners(); this.setVisible(true); + + } + + private void initializeTable() { + songTable.setDefaultEditor(Object.class, null); + songTable.setModel(new DefaultTableModel(null, new String[]{"Title", "Artist", "Filepath"})); + songTable.getTableHeader().setReorderingAllowed(false); + } + + private void clearSongTable() { + DefaultTableModel dtm = (DefaultTableModel) songTable.getModel(); + dtm.setRowCount(0); + } + + private void addSongTable(File audioFile) { + try { + AudioFile f = AudioFileIO.read(audioFile); + Tag tag = f.getTag(); + DefaultTableModel model = (DefaultTableModel) songTable.getModel(); + model.addRow(new Object[]{tag.getFirst(FieldKey.TITLE), tag.getFirst(FieldKey.ARTIST), audioFile.getAbsolutePath()}); + } catch (Exception e) { + + } + } + + private void populateFields(File audioFile) { + try { + AudioFile f = AudioFileIO.read(audioFile); + Tag tag = f.getTag(); + titleField.setText(tag.getFirst(FieldKey.TITLE)); + uploaderField.setText(tag.getFirst(FieldKey.ARTIST)); + Artwork albumArt = tag.getFirstArtwork(); + ImageIcon albumArtIcon = new ImageIcon(resizeImage(albumArt.getImage(), 320, 180)); + artIconLabel.setIcon(albumArtIcon); + artIconLabel.setText(""); + listenButton.setEnabled(true); + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + private BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) throws IOException { + Image resultingImage = originalImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_DEFAULT); + BufferedImage outputImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB); + outputImage.getGraphics().drawImage(resultingImage, 0, 0, null); + return outputImage; + } + + private void playMP3(String filepath) { + try { + File file = new File(filepath); + Desktop.getDesktop().open(file); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void initalizeListeners() { chooseAudioDirectoryButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { clearSongTable(); setDirPath = fileUtil.showDirectoryChooser(); songList = fileUtil.getMp3Files(setDirPath); //get arraylist of all files in the directory - for(int i=0;i