Merge branch 'Fix-ERrors'

This commit is contained in:
bender
2023-02-02 18:20:25 -05:00
10 changed files with 105 additions and 86 deletions
+15 -10
View File
@@ -1,4 +1,4 @@
#Confirmed working as of 1/22/23 '''Align tracks end to end, chop based on chapters list durations, export to final folder, archive originals'''
import os import os
import shutil import shutil
@@ -18,19 +18,22 @@ def export_audio_file(start, end, name):
author_name = folder.split(" - ")[0].strip() author_name = folder.split(" - ")[0].strip()
export_folder = os.path.join(destination_path, author_name, folder_name) export_folder = os.path.join(destination_path, author_name, folder_name)
author_folder = os.path.join(destination_path, author_name) author_folder = os.path.join(destination_path, author_name)
'''If an author folder doesn't already exist it makes one'''
if not os.path.exists(author_folder): if not os.path.exists(author_folder):
os.makedirs(author_folder) os.makedirs(author_folder)
export_folder = os.path.join(author_folder, folder_name) export_folder = os.path.join(author_folder, folder_name)
'''Skips the above if an author already exists and adds the book to it'''
if not os.path.exists(export_folder): if not os.path.exists(export_folder):
os.makedirs(export_folder) os.makedirs(export_folder)
export_path = os.path.join(export_folder, f"{name}.mp3") export_path = os.path.join(export_folder, f"{name}.mp3")
export.export(export_path, format="mp3") export.export(export_path, format="mp3")
'''Defines metadata & artwork files'''
metadata_file = os.path.join(folder_path, "cleaned_metadata.json") metadata_file = os.path.join(folder_path, "cleaned_metadata.json")
chapters_file = os.path.join(folder_path, "overdrive_chapters_ms.txt")
album_art = os.path.join(folder_path, "folder.jpg") album_art = os.path.join(folder_path, "folder.jpg")
if os.path.exists(album_art): if os.path.exists(album_art):
'''Copies metadata & artwork files alongside chapterized audio'''
shutil.copy(album_art, export_folder) shutil.copy(album_art, export_folder)
shutil.copy(metadata_file, export_folder) shutil.copy(metadata_file, export_folder)
@@ -40,7 +43,7 @@ for folder in sorted(os.listdir(main_directory)):
folder_path = os.path.join(main_directory, folder) folder_path = os.path.join(main_directory, folder)
if os.path.isdir(folder_path): if os.path.isdir(folder_path):
try: try:
# Import MP3s in alphabetical order '''Import MP3s in alphabetical order'''
mp3_files = sorted(os.listdir(folder_path)) mp3_files = sorted(os.listdir(folder_path))
mp3_files = [os.path.join(folder_path, file) for file in mp3_files if file.endswith(".mp3")] mp3_files = [os.path.join(folder_path, file) for file in mp3_files if file.endswith(".mp3")]
@@ -49,10 +52,10 @@ for folder in sorted(os.listdir(main_directory)):
print(mp3_file) print(mp3_file)
combined += AudioSegment.from_file(mp3_file) combined += AudioSegment.from_file(mp3_file)
# Align end to end '''Align end to end'''
combined = combined.set_channels(1) combined = combined.set_channels(1)
# Import labels '''Import label names/durations'''
labels_file = os.path.join(folder_path, "overdrive_chapters_ms_spans.txt") labels_file = os.path.join(folder_path, "overdrive_chapters_ms_spans.txt")
labels = [] labels = []
with open(labels_file, "r") as f: with open(labels_file, "r") as f:
@@ -60,25 +63,27 @@ for folder in sorted(os.listdir(main_directory)):
start, end, name = line.strip().split("\t") start, end, name = line.strip().split("\t")
labels.append((start, end, name)) labels.append((start, end, name))
# Initialize counter for duplicate label names '''Initialize counter for duplicate label names'''
counter = {} counter = {}
# Initialize counter for file export '''Initialize counter for file export'''
file_count = 0 file_count = 0
for i, label in enumerate(labels): for i, label in enumerate(labels):
start, end, name = label start, end, name = label
'''This ensures that the final end duration matches the length of the audiobook'''
if end == "0" and i == len(labels) - 1: if end == "0" and i == len(labels) - 1:
end = combined.duration_seconds * 1000 end = combined.duration_seconds * 1000
file_count += 1 file_count += 1
name = f"{file_count:03}_{name.replace('/', '_')}" name = f"{file_count:03}_{name.replace('/', '_')}"
# format file_count as 3 digit number '''format file_count as 3 digit number'''
export_audio_file(start, end, name) export_audio_file(start, end, name)
'''Prints if there's an error'''
except Exception as e: except Exception as e:
print(f"Error processing {folder}: {e}") print(f"Error processing {folder}: {e}")
#Archive everything except chapters_list.py '''Archive everything except chapters_list.py'''
if os.path.isdir(folder_path) and folder != 'chapters_list.py': if os.path.isdir(folder_path) and folder != 'chapters_list.py':
shutil.move(folder_path, archive + '/' + folder) shutil.move(folder_path, archive + '/' + folder)
+10 -9
View File
@@ -1,4 +1,4 @@
#Confirmed working as of 1/22/23 '''Converts label durations to milliseconds for pydub to read'''
import os import os
import re import re
@@ -6,29 +6,30 @@ from datetime import datetime, timedelta
from config import annex from config import annex
def duration_to_milliseconds(duration): def duration_to_milliseconds(duration):
'''Conversion from Seconds to Milliseconds'''
hours, minutes, seconds = duration.split(':') hours, minutes, seconds = duration.split(':')
seconds = int(hours)*3600 + int(minutes)*60 + float(seconds) seconds = int(hours)*3600 + int(minutes)*60 + float(seconds)
return int(seconds * 1000) return int(seconds * 1000)
# path to directory containing the files '''path to directory containing the files'''
directory = annex directory = annex
# loop through all files in directory tree '''loop through all files in directory tree'''
for dirpath, dirnames, filenames in os.walk(directory): for dirpath, dirnames, filenames in os.walk(directory):
for filename in filenames: for filename in filenames:
if filename.startswith("overdrive_chapters") and filename.endswith(".txt"): if filename.startswith("overdrive_chapters") and filename.endswith(".txt"):
# open the file and read the contents '''open the chapters file and read the contents'''
with open(os.path.join(dirpath, filename), 'r') as f: with open(os.path.join(dirpath, filename), 'r') as f:
lines = f.readlines() lines = f.readlines()
# create a new file with the same name but with "milliseconds" added to the end '''create a new file with the same name but with "ms" added to the end'''
new_filename = filename[:-4] + "_ms.txt" new_filename = filename[:-4] + "_ms.txt"
with open(os.path.join(dirpath, new_filename), 'w') as new_file: with open(os.path.join(dirpath, new_filename), 'w') as new_file:
# loop through each line in the file '''loop through each line in the file'''
for line in lines: for line in lines:
# split the line into duration and label using regular expression '''split the line into duration and label name using regular expression'''
duration, label = re.split(r"\s+", line.strip(), 1) duration, label = re.split(r"\s+", line.strip(), 1)
# convert the duration to milliseconds '''convert the duration to milliseconds'''
milliseconds = duration_to_milliseconds(duration) milliseconds = duration_to_milliseconds(duration)
# write the converted duration and label to the new file '''write the converted duration and label to the new file'''
new_file.write(f'{milliseconds} {label}\n') new_file.write(f'{milliseconds} {label}\n')
+9 -7
View File
@@ -1,15 +1,17 @@
# Define directories '''
# I like having multiple folders so as to visualize each step happening Define directories
# but you could easily adjust the setup to fit your preferences I like having multiple folders so as to visualize each step happening
but you could easily adjust the setup to fit your preferences
'''
# Where the unpackaged odm mp3s will get moved '''Where the unpackaged odm mp3s will get moved'''
annex="/Users/jonas/Documents/SERVER/BOOKS/01_ANNEX" annex="/Users/jonas/Documents/SERVER/BOOKS/01_ANNEX"
# Where chapterized and labeled mp3s will be stored '''Where chapterized and labeled mp3s will be stored'''
circulation="/Users/jonas/Documents/SERVER/BOOKS/02_CIRCULATION" circulation="/Users/jonas/Documents/SERVER/BOOKS/02_CIRCULATION"
# Where the final finished product will land '''Where the final finished product will land'''
bookshelf="/Users/jonas/Documents/SERVER/BOOKS/03_BOOKSHELF" bookshelf="/Users/jonas/Documents/SERVER/BOOKS/03_BOOKSHELF"
# Where the scraps go '''Where the scraps go'''
archive="/Users/jonas/Documents/SERVER/BOOKS/00_ARCHIVE" archive="/Users/jonas/Documents/SERVER/BOOKS/00_ARCHIVE"
+7 -5
View File
@@ -1,4 +1,4 @@
#Confirmed working as of 1/22/23 '''Takes the contents of the Milliseconds durations/labels file and creates start/end times'''
import os import os
from config import annex from config import annex
@@ -7,6 +7,7 @@ root_dir = annex
for subdir, dirs, files in os.walk(root_dir): for subdir, dirs, files in os.walk(root_dir):
for file in files: for file in files:
'''Locates "overdrive_chapters_ms.txt"'''
file_path = os.path.join(subdir, file) file_path = os.path.join(subdir, file)
if file == 'overdrive_chapters_ms.txt': if file == 'overdrive_chapters_ms.txt':
with open(file_path, "r") as file: with open(file_path, "r") as file:
@@ -16,23 +17,24 @@ for subdir, dirs, files in os.walk(root_dir):
for i in range(len(lines)): for i in range(len(lines)):
parts = lines[i].split(" ") parts = lines[i].split(" ")
if i == len(lines) - 2: if i == len(lines) - 2:
# If it's the second-to-last line, write the last line's number and 0 '''If it's the second-to-last line, write the last line's number and 0'''
file.write(parts[0]) file.write(parts[0])
file.write("\t" + lines[i + 1].split(" ")[0]) file.write("\t" + lines[i + 1].split(" ")[0])
file.write("0") file.write("0")
elif i != len(lines) - 1: elif i != len(lines) - 1:
# Otherwise, write the number from the next line to the file '''Otherwise, write the number from the next line to the file'''
file.write(parts[0]) file.write(parts[0])
file.write("\t" + lines[i + 1].split(" ")[0]) file.write("\t" + lines[i + 1].split(" ")[0])
else: else:
# If it's the last line, just write the number and 0 '''If it's the last line, just write the number and 0'''
if parts[0] != "": if parts[0] != "":
file.write(parts[0]) file.write(parts[0])
file.write("0") file.write("0")
# Write the rest of the line to the file '''Write the rest of the line to the file'''
file.write("\t" + " ".join(parts[1:])) file.write("\t" + " ".join(parts[1:]))
if i != len(lines) - 1 or (i == len(lines) - 1 and lines[i] != ""): if i != len(lines) - 1 or (i == len(lines) - 1 and lines[i] != ""):
file.write("\n") file.write("\n")
if lines[-1] == "": if lines[-1] == "":
'''Ensures there's no empty line at the end of the txt'''
file.seek(file.tell()-1, os.SEEK_SET) file.seek(file.tell()-1, os.SEEK_SET)
file.truncate() file.truncate()
+21 -20
View File
@@ -1,19 +1,20 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Credit to ex-nerd '''
# Credit to ex-nerd
# Recursively scans current or specified directory for all subdirectories
# containing mp3 files. If these mp3 files contain overdrive chapter markers Recursively scans current or specified directory for all subdirectories
# (id3 tag), writes overdrive_chapters.txt to the same directory. containing mp3 files. If these mp3 files contain overdrive chapter markers
# (id3 tag), writes overdrive_chapters.txt to the same directory.
# Usage:
# Usage:
# extract_overdrive_chapters.py [optional directory path]
# extract_overdrive_chapters.py [optional directory path]
# Use with build_m4b from https://github.com/ex-nerd/audiotools
# Use with build_m4b from https://github.com/ex-nerd/audiotools
# Note: Due to overdrive low quality, there is no point in encoding aac files
# with better than: 64kbps stereo, HE, optimize for voice Note: Due to overdrive low quality, there is no point in encoding aac files
# with better than: 64kbps stereo, HE, optimize for voice
'''
import os, sys, re import os, sys, re
import mutagen.id3 as id3 import mutagen.id3 as id3
@@ -55,16 +56,16 @@ def load_mp3(total, dir, file):
name = re.sub(r"^\*(.+)\*$", r"\1", name) name = re.sub(r"^\*(.+)\*$", r"\1", name)
name = re.sub( name = re.sub(
r"\s*\([^)]*\)$", "", name r"\s*\([^)]*\)$", "", name
) # ignore any sub-chapter markers from Overdrive ) '''ignore any sub-chapter markers from Overdrive'''
name = re.sub( name = re.sub(
r"\s+\(?continued\)?$", "", name r"\s+\(?continued\)?$", "", name
) # ignore any sub-chapter markers from Overdrive ) '''ignore any sub-chapter markers from Overdrive'''
name = re.sub( name = re.sub(
r"\s+-\s*$", "", name r"\s+-\s*$", "", name
) # ignore any sub-chapter markers from Overdrive ) '''ignore any sub-chapter markers from Overdrive'''
name = re.sub( name = re.sub(
r"^Dis[kc]\s+\d+\W*$", "", name r"^Dis[kc]\s+\d+\W*$", "", name
) # ignore any disk markers from Overdrive ) '''ignore any disk markers from Overdrive'''
name = name.strip() name = name.strip()
t_parts = list(length.split(":")) t_parts = list(length.split(":"))
t_parts.reverse() t_parts.reverse()
@@ -81,7 +82,7 @@ def load_mp3(total, dir, file):
def visit(dirname, filenames): def visit(dirname, filenames):
print(dirname) print(dirname)
os.chdir(dirname) os.chdir(dirname)
# Parse the files '''Parse the files'''
total = 0 total = 0
all_chapters = OrderedDict() all_chapters = OrderedDict()
for file in sorted(filenames): for file in sorted(filenames):
+14 -16
View File
@@ -1,4 +1,4 @@
#Confirmed working as of 1/23/23 '''Adds metadata to the chapterized mp3s'''
import os import os
import shutil import shutil
@@ -8,7 +8,7 @@ from mutagen.id3 import ID3, APIC, TIT2, TPE1, TCOM, TCON, TSOA, TRCK, TIT3, TAL
from config import circulation,bookshelf from config import circulation,bookshelf
def traverse_directory(directory): def traverse_directory(directory):
# Iterate through all subfolders and files '''Iterate through all subfolders and files'''
for root, dirs, files in os.walk(directory): for root, dirs, files in os.walk(directory):
for dir in dirs: for dir in dirs:
subfolder_path = os.path.join(root, dir) subfolder_path = os.path.join(root, dir)
@@ -16,7 +16,7 @@ def traverse_directory(directory):
add_metadata(subfolder_path) add_metadata(subfolder_path)
def add_metadata(directory): def add_metadata(directory):
# Get the metadata from the cleaned_metadata.json file in the current subfolder '''Get the metadata from the cleaned_metadata.json file in the current subfolder'''
with open(os.path.join(directory, "cleaned_metadata.json"), "r") as f: with open(os.path.join(directory, "cleaned_metadata.json"), "r") as f:
json_data = json.load(f) json_data = json.load(f)
artist = json_data.get("Author", "") artist = json_data.get("Author", "")
@@ -24,32 +24,30 @@ def add_metadata(directory):
genre = json_data.get("Subjects", "") genre = json_data.get("Subjects", "")
album_title = json_data.get("Title", "") album_title = json_data.get("Title", "")
# Rest of the code '''Replace all occurrences of -s with "'s" in the album title'''
# Replace all occurrences of -s with 's in the album title
album_title = re.sub("-s", "'s", album_title) album_title = re.sub("-s", "'s", album_title)
# Get a list of all mp3 files in the current directory '''Get a list of all mp3 files in the current directory'''
mp3_files = [f for f in os.listdir(directory) if f.endswith(".mp3")] mp3_files = [f for f in os.listdir(directory) if f.endswith(".mp3")]
# Sort the list of mp3 files alphabetically '''Sort the list of mp3 files alphabetically'''
mp3_files.sort() mp3_files.sort()
# Iterate through the list of mp3 files '''Iterate through the list of mp3 files'''
for i, mp3_file in enumerate(mp3_files, 1): for i, mp3_file in enumerate(mp3_files, 1):
mp3_file_path = os.path.join(directory, mp3_file) mp3_file_path = os.path.join(directory, mp3_file)
# Extract the title of the mp3 file '''Extract the title of the mp3 file'''
file_name, file_ext = os.path.splitext(mp3_file) file_name, file_ext = os.path.splitext(mp3_file)
title = file_name.lstrip("0123456789_") title = file_name.lstrip("0123456789_")
# Add the track number to the file using ID3 '''Add the track number to the file using ID3'''
audio = ID3(mp3_file_path) audio = ID3(mp3_file_path)
audio.add(TRCK(encoding=3, text=str(i))) audio.add(TRCK(encoding=3, text=str(i)))
audio.add(TIT2(encoding=3, text=title)) audio.add(TIT2(encoding=3, text=title))
audio.save() audio.save()
# Add the metadata to the file using ID3 '''Add the metadata to the file using ID3'''
audio = ID3(mp3_file_path) audio = ID3(mp3_file_path)
audio.add(TPE1(encoding=3, text=artist)) audio.add(TPE1(encoding=3, text=artist))
audio.add(TCOM(encoding=3, text=composer)) audio.add(TCOM(encoding=3, text=composer))
@@ -57,21 +55,21 @@ def add_metadata(directory):
audio.add(TALB(encoding=3, text=album_title)) audio.add(TALB(encoding=3, text=album_title))
audio.save() audio.save()
# Check if folder.jpg exists in the current directory '''Check if folder.jpg exists in the current directory'''
if os.path.exists(os.path.join(directory, "folder.jpg")): if os.path.exists(os.path.join(directory, "folder.jpg")):
# Add the album art to the file using ID3 '''Add the album art to the file using ID3'''
with open(os.path.join(directory, "folder.jpg"), "rb") as albumart: with open(os.path.join(directory, "folder.jpg"), "rb") as albumart:
audio = ID3(mp3_file_path) audio = ID3(mp3_file_path)
audio.add(APIC(encoding=3, mime='image/jpeg', type=3, desc='Cover', data=albumart.read())) audio.add(APIC(encoding=3, mime='image/jpeg', type=3, desc='Cover', data=albumart.read()))
audio.save() audio.save()
# Point the script at a directory '''Point the script at a directory'''
directory = circulation directory = circulation
# Traverse the directory and apply the metadata to the mp3 files
traverse_directory(directory) traverse_directory(directory)
for root, dirs, files in os.walk(directory): for root, dirs, files in os.walk(directory):
'''Traverse the directory and apply the metadata to the mp3 files'''
for dir in dirs: for dir in dirs:
subfolder_path = os.path.join(root, dir) subfolder_path = os.path.join(root, dir)
parent_folder = os.path.dirname(subfolder_path) parent_folder = os.path.dirname(subfolder_path)
+2 -1
View File
@@ -1,4 +1,5 @@
#Confirmed working as of 1/22/23
'''Takes the .odm.metadata file and turns it into metadata.xml'''
import os import os
import shutil import shutil
+10 -12
View File
@@ -1,33 +1,31 @@
#!/bin/bash #!/bin/bash
#download odm's to chosen directory #start by downloading odms to chosen directory
# configure variables #configure variables
python3.10 config.py python3.10 config.py
# unpack odm's #unpack odms
bash overdrivedelete.sh bash overdrivedelete.sh
# extract chapters, specify folder holding the unpackaged overdrive mp3's #extract chapters, specify folder holding the unpackaged overdrive mp3s
source config.env source config.env
python3 extract_overdrive_chapters.py $annex python3 extract_overdrive_chapters.py $annex
# clean metadata #clean metadata
python3 metadatatoxml.py python3 metadatatoxml.py
python3 xmlparse.py python3 xmlparse.py
# turn chapters into ms #turn chapters into ms
python3 chapter_ms.py python3 chapter_ms.py
# format durations #format durations
python3 durations.py python3 durations.py
# export labeled audio and archive original folder #export labeled audio and archive original folder
# FIX: MAKE SURE IT CAN ADD TO EXISTING AUTHOR FOLDER # FIX: MAKE SURE IT CAN ADD TO EXISTING AUTHOR FOLDER
python3 00_exportchapters.py python3 00_exportchapters.py
# tag the exported audio with proper metadata #tag the exported audio with proper metadata
python3 final_metadata_add.py python3 final_metadata_add.py
#manually add to Plex
+12 -4
View File
@@ -1,4 +1,10 @@
#This officially works for the full ODM extraction as of 1/21/23 at 9:54pm #!/bin/bash
#This is a Frankenstein version of chbrowns overdrive script
#
#It extracts the contents of the odm into a temp directory so as to point the metadata
#file to the export path of the contents of the odm and then deletes the leftover files
source config.env source config.env
folder=$queue folder=$queue
@@ -6,7 +12,7 @@ annex=$annex
for file in $folder/*.odm for file in $folder/*.odm
do do
# Make temp dir in loop so that it clears even if there's any errors #Make temp dir in loop so that it clears even if theres any errors
temp_dir=$(mktemp -d) temp_dir=$(mktemp -d)
cd "$temp_dir" cd "$temp_dir"
@@ -20,20 +26,22 @@ do
sleep 2 # wait for 2 seconds before retrying sleep 2 # wait for 2 seconds before retrying
done done
# Get the name of the downloaded folder #Get the name of the downloaded folder
downloaded_folder=$(find "$temp_dir" -type d -mindepth 1 -maxdepth 1 | head -n 1) downloaded_folder=$(find "$temp_dir" -type d -mindepth 1 -maxdepth 1 | head -n 1)
echo "downloaded_folder: $downloaded_folder" echo "downloaded_folder: $downloaded_folder"
# Move the downloaded folder to the destination #Move the downloaded folder to the destination
mv "$downloaded_folder" "$annex" mv "$downloaded_folder" "$annex"
#Move the .odm.metadata to the downloaded folder #Move the .odm.metadata to the downloaded folder
metadata_file=$(find "$folder" -name "*.odm.metadata") metadata_file=$(find "$folder" -name "*.odm.metadata")
mv "$metadata_file" "$annex/$(basename "$downloaded_folder")" mv "$metadata_file" "$annex/$(basename "$downloaded_folder")"
#Remove temp directory
rm -rf "$temp_dir" rm -rf "$temp_dir"
done done
#Remove .odm & .odm.license files
rm $queue/*.odm rm $queue/*.odm
rm $queue/*.odm.license rm $queue/*.odm.license
+5 -2
View File
@@ -1,4 +1,4 @@
#Confirmed as of 1/22/23 '''Parses the metadata.xml file into only the required data'''
import os import os
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@@ -10,9 +10,11 @@ def extract_metadata(xml_file):
root = tree.getroot() root = tree.getroot()
data = {} data = {}
for child in root: for child in root:
'''Finds occurrences of Title, Creators, Subjects and isolates'''
if child.tag == "Title": if child.tag == "Title":
data["Title"] = child.text data["Title"] = child.text
elif child.tag == "Creators": elif child.tag == "Creators":
'''Identifies Authors and Narrators'''
for creator in child: for creator in child:
if creator.attrib["role"] == "Author": if creator.attrib["role"] == "Author":
data["Author"] = creator.text data["Author"] = creator.text
@@ -26,7 +28,8 @@ def extract_metadata(xml_file):
return data return data
def process_folder(folder_path): def process_folder(folder_path):
for dirpath, dirnames, filenames in os.walk(folder_path): '''Walks to metadata.xml & exports the parsed version as cleaned_metadata.json'''
for dirpath, filenames in os.walk(folder_path):
for filename in filenames: for filename in filenames:
if filename == "metadata.xml": if filename == "metadata.xml":
xml_file = os.path.join(dirpath, filename) xml_file = os.path.join(dirpath, filename)