op 2.0
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
'''Converts label durations to milliseconds for pydub to read'''
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
def duration_to_milliseconds(duration):
|
||||
'''Conversion from Seconds to Milliseconds'''
|
||||
hours, minutes, seconds = duration.split(':')
|
||||
seconds = int(hours)*3600 + int(minutes)*60 + float(seconds)
|
||||
return int(seconds * 1000)
|
||||
|
||||
def read_chapters_file(dirpath, filename):
|
||||
'''Open the chapters file and read the contents.'''
|
||||
with open(os.path.join(dirpath, filename), 'r') as f:
|
||||
lines = f.readlines()
|
||||
return lines
|
||||
|
||||
def new_file(dirpath, filename):
|
||||
'''Create a new file with the same name but with "ms" added to the end.'''
|
||||
lines = read_chapters_file(dirpath, filename)
|
||||
new_filename = filename[:-4] + "_ms.txt"
|
||||
with open(os.path.join(dirpath, new_filename), 'w') as new_file:
|
||||
for line in lines:
|
||||
duration, label = re.split(r"\s+", line.strip(), 1)
|
||||
milliseconds = duration_to_milliseconds(duration)
|
||||
new_file.write(f'{milliseconds} {label}\n')
|
||||
|
||||
def add_ms_to_chapters(directory):
|
||||
'''Loop through all files in the directory tree and process the matching files.'''
|
||||
for dirpath, _, filenames in os.walk(directory):
|
||||
for filename in filenames:
|
||||
if filename.startswith("overdrive_chapters") and filename.endswith(".txt"):
|
||||
new_file(dirpath, filename)
|
||||
@@ -0,0 +1,29 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, parent_dir)
|
||||
|
||||
from Chapters_List.extract_overdrive_chapters import extract_chapters
|
||||
from Chapters_List.chapter_ms import add_ms_to_chapters
|
||||
from XML_JSON.json_scripts import load_config
|
||||
from Chapters_List.ms_durations import ms_to_durations
|
||||
|
||||
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
config_path = os.path.join(parent_dir, 'config.json')
|
||||
|
||||
config = load_config(config_path)
|
||||
|
||||
queue = config['queue'] #Where your ODMs are downloaded to
|
||||
annex = config['annex'] #Where the ODMs get unpackaged to
|
||||
circulation = config['circulation'] #Where the chapterized mp3s get saved
|
||||
bookshelf = config['bookshelf'] #Where the final audiobook gets dropped
|
||||
|
||||
def chapters_list():
|
||||
extract_chapters(annex)
|
||||
add_ms_to_chapters(annex)
|
||||
ms_to_durations(annex)
|
||||
print("Splitting audio files into chapters & adding metadata (this may take a moment)...")
|
||||
|
||||
if __name__ == '__main__':
|
||||
chapters_list()
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
'''
|
||||
Credit to ex-nerd
|
||||
|
||||
Recursively scans current or specified directory for all subdirectories
|
||||
containing mp3 files. If these mp3 files contain overdrive chapter markers
|
||||
(id3 tag), writes overdrive_chapters.txt to the same directory.
|
||||
|
||||
Usage:
|
||||
|
||||
extract_overdrive_chapters.py [optional directory path]
|
||||
|
||||
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
|
||||
'''
|
||||
|
||||
import os, sys, re
|
||||
import mutagen.id3 as id3
|
||||
from mutagen.mp3 import MP3
|
||||
from mutagen import File
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
def timestr(secs):
|
||||
(secs, ms) = str(secs).split(".")
|
||||
ms = float(ms[0:3] + "." + ms[3:])
|
||||
secs = int(secs)
|
||||
hours = int(secs // 3600)
|
||||
secs = secs % 3600
|
||||
mins = int(secs // 60)
|
||||
secs = secs % 60
|
||||
return f"{hours:02}:{mins:02}:{secs:02}.{ms:03.0f}"
|
||||
|
||||
|
||||
def load_mp3(total, dir, file):
|
||||
path = os.path.join(dir, file)
|
||||
audio = MP3(path)
|
||||
# print(audio.info.length) # , audio.info.bitrate
|
||||
m = id3.ID3(path)
|
||||
|
||||
data = m.get("TXXX:OverDrive MediaMarkers")
|
||||
if not data:
|
||||
print("Can't find TXXX data point for {0}".format(file))
|
||||
print(m.keys())
|
||||
return
|
||||
info = data.text[0]
|
||||
file_chapters = re.findall(
|
||||
r"<Name>\s*([^>]+?)\s*</Name><Time>\s*([\d:.]+)\s*</Time>", info, re.MULTILINE
|
||||
)
|
||||
chapters = []
|
||||
for chapter in file_chapters:
|
||||
(name, length) = chapter
|
||||
name = re.sub(r'^"(.+)"$', r"\1", name)
|
||||
name = re.sub(r"^\*(.+)\*$", r"\1", name)
|
||||
name = re.sub(
|
||||
r"\s*\([^)]*\)$", "", name
|
||||
)
|
||||
'''ignore any sub-chapter markers from Overdrive'''
|
||||
name = re.sub(
|
||||
r"\s+\(?continued\)?$", "", name
|
||||
)
|
||||
'''ignore any sub-chapter markers from Overdrive'''
|
||||
name = re.sub(
|
||||
r"\s+-\s*$", "", name
|
||||
)
|
||||
'''ignore any sub-chapter markers from Overdrive'''
|
||||
name = re.sub(
|
||||
r"^Dis[kc]\s+\d+\W*$", "", name
|
||||
)
|
||||
'''ignore any disk markers from Overdrive'''
|
||||
name = name.strip()
|
||||
t_parts = list(length.split(":"))
|
||||
t_parts.reverse()
|
||||
seconds = total + float(t_parts[0])
|
||||
if len(t_parts) > 1:
|
||||
seconds += int(t_parts[1]) * 60
|
||||
if len(t_parts) > 2:
|
||||
seconds += int(t_parts[2]) * 60 * 60
|
||||
chapters.append([name, seconds])
|
||||
# print(name, seconds)
|
||||
return (total + audio.info.length, chapters)
|
||||
|
||||
|
||||
def visit(dirname, filenames):
|
||||
print(dirname)
|
||||
os.chdir(dirname)
|
||||
'''Parse the files'''
|
||||
total = 0
|
||||
all_chapters = OrderedDict()
|
||||
for file in sorted(filenames):
|
||||
if file.endswith(".mp3"):
|
||||
(total, chapters) = load_mp3(total, dirname, file)
|
||||
# print(repr(chapters))
|
||||
for chapter in chapters:
|
||||
if chapter[0] in all_chapters.keys():
|
||||
continue
|
||||
all_chapters[chapter[0]] = chapter[1]
|
||||
if len(all_chapters) > 0:
|
||||
with open("overdrive_chapters.txt", "w") as file:
|
||||
for name, length in all_chapters.items():
|
||||
chapstr = f"{timestr(length)} {name}"
|
||||
file.write(chapstr + "\n")
|
||||
# print(repr(all_chapters))
|
||||
|
||||
def extract_chapters(path="."):
|
||||
abs_path = os.path.abspath(path)
|
||||
for dirname, dirs, files in os.walk(abs_path, topdown=True):
|
||||
dirs[:] = [d for d in dirs if d not in {".git", ".direnv"}]
|
||||
visit(dirname, files)
|
||||
@@ -0,0 +1,46 @@
|
||||
'''Takes the contents of the Milliseconds durations/labels file and creates start/end times'''
|
||||
import os
|
||||
|
||||
def create_durations(file, lines):
|
||||
for i in range(len(lines)):
|
||||
parts = lines[i].split(" ")
|
||||
if i == len(lines) - 2:
|
||||
'''If it's the second-to-last line, write the last line's number and 0'''
|
||||
file.write(parts[0])
|
||||
file.write("\t" + lines[i + 1].split(" ")[0])
|
||||
file.write("0")
|
||||
elif i != len(lines) - 1:
|
||||
'''Otherwise, write the number from the next line to the file'''
|
||||
file.write(parts[0])
|
||||
file.write("\t" + lines[i + 1].split(" ")[0])
|
||||
else:
|
||||
'''If it's the last line, just write the number and 0'''
|
||||
if parts[0] != "":
|
||||
file.write(parts[0])
|
||||
file.write("0")
|
||||
'''Write the rest of the line to the file'''
|
||||
file.write("\t" + " ".join(parts[1:]))
|
||||
if i != len(lines) - 1 or (i == len(lines) - 1 and lines[i] != ""):
|
||||
file.write("\n")
|
||||
|
||||
def bookmark_final_duration(file, lines):
|
||||
if lines[-1] == "":
|
||||
'''Ensures there's no empty line at the end of the txt'''
|
||||
file.seek(file.tell()-1, os.SEEK_SET)
|
||||
file.truncate()
|
||||
|
||||
def create_spans_txt(file, file_path):
|
||||
if file == 'overdrive_chapters_ms.txt':
|
||||
with open(file_path, "r") as file:
|
||||
lines = file.read().split('\n')
|
||||
new_file_path = file_path.replace('.txt', '_spans.txt')
|
||||
with open(new_file_path, "w") as file:
|
||||
create_durations(file, lines)
|
||||
bookmark_final_duration(file, lines)
|
||||
|
||||
def ms_to_durations(directory):
|
||||
for subdir, _, files in os.walk(directory):
|
||||
for file in files:
|
||||
'''Locates "overdrive_chapters_ms.txt"'''
|
||||
file_path = os.path.join(subdir, file)
|
||||
create_spans_txt(file, file_path)
|
||||
Reference in New Issue
Block a user