Repository: seanap/auto-m4b
Branch: main
Commit: 94760b1e1ad9
Files: 7
Total size: 18.3 KB
Directory structure:
gitextract_de1d_7so/
├── .github/
│ └── workflows/
│ └── build-and-publish.yml
├── Dockerfile
├── README.md
├── auto-m4b-tool.sh
├── clean_untagged.sh
├── docker-compose.yml
└── runscript.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/build-and-publish.yml
================================================
name: Build and Publish
on:
# run it on push to the default repository branch
push:
branches: [main]
# run it during pull request
pull_request:
jobs:
# define job to build and publish docker image
build-and-push-docker-image:
name: Build Docker image and push to repositories
# run only when code is compiling and tests are passing
runs-on: ubuntu-latest
# steps to perform in job
steps:
- name: Checkout code
uses: actions/checkout@v2
# setup Docker buld action
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_PAT }}
- name: Build image and push to Docker Hub and GitHub Container Registry
uses: docker/build-push-action@v2
with:
# relative path to the place where source code with Dockerfile is located
context: .
file: ./Dockerfile
# Note: tags has to be all lower-case
tags: |
seanap/auto-m4b:latest
# build on feature branches, push only on main branch
push: ${{ github.ref == 'refs/heads/main' }}
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
================================================
FILE: Dockerfile
================================================
FROM phusion/baseimage:jammy-1.0.1
#FROM phusion/baseimage:master
#Basic Container
RUN echo "---- INSTALL RUNTIME PACKAGES ----" && \
apt-get update && apt-get install -y --no-install-recommends \
python3-pip \
git \
#ffmpeg \
dnsutils \
iputils-ping \
wget \
crudini && rm -rf /var/lib/apt/lists/*
#Build layer
RUN echo "---- INSTALL ALL BUILD-DEPENDENCIES ----" && \
buildDeps='gcc \
g++ \
make \
autoconf \
automake \
build-essential \
cmake \
git-core \
libass-dev \
libfreetype6-dev \
libgnutls28-dev \
libmp3lame-dev \
libsdl2-dev \
libtool \
libva-dev \
libvdpau-dev \
libvorbis-dev \
libxcb1-dev \
libxcb-shm0-dev \
libxcb-xfixes0-dev \
meson \
ninja-build \
pkg-config \
texinfo \
yasm \
libfdk-aac-dev \
zlib1g-dev' && \
set -x && \
apt-get update && apt-get install -y $buildDeps --no-install-recommends && \
rm -rf /var/lib/apt/lists/* && \
echo "---- BUILD & INSTALL MP4V2 ----" && \
mkdir -p /tmp && \
cd /tmp && \
git clone https://github.com/sandreas/mp4v2 && \
cd mp4v2 && \
./configure && \
make && \
make install && \
make distclean && \
echo "---- BUILD & INSTALL ffmpeg ----" && \
mkdir -p ~/ffmpeg_sources ~/bin && \
cd ~/ffmpeg_sources && \
git -C fdk-aac pull 2> /dev/null || git clone --depth 1 https://github.com/mstorsjo/fdk-aac && \
cd fdk-aac && \
autoreconf -fiv && \
./configure --prefix="$HOME/ffmpeg_build" --disable-shared && \
make && \
make install && \
make distclean && \
cd ~/ffmpeg_sources && \
wget -O ffmpeg-snapshot.tar.bz2 https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2 && \
tar xjvf ffmpeg-snapshot.tar.bz2 && \
cd ffmpeg && \
PATH="$HOME/bin:$PATH" PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure \
--prefix="$HOME/ffmpeg_build" \
--pkg-config-flags="--static" \
--extra-cflags="-I$HOME/ffmpeg_build/include" \
--extra-ldflags="-L$HOME/ffmpeg_build/lib" \
--extra-libs="-lpthread -lm" \
--ld="g++" \
--bindir="$HOME/bin" \
--enable-libfdk-aac \
--enable-nonfree && \
PATH="$HOME/bin:$PATH" make && \
make install && \
hash -r && \
make distclean && \
mv ~/bin/* /bin/ && \
echo "---- REMOVE ALL BUILD-DEPENDENCIES ----" && \
apt-get purge -y --auto-remove $buildDeps && \
ldconfig && \
rm -r /tmp/* ~/ffmpeg_sources ~/bin
#ENV WORKDIR /mnt/
#ENV M4BTOOL_TMP_DIR /tmp/m4b-tool/
LABEL Description="Container to run m4b-tool as a deamon."
RUN echo "---- INSTALL M4B-TOOL DEPENDENCIES ----" && \
apt-get update && apt-get install -y --no-install-recommends \
fdkaac \
php-cli \
php-intl \
php-json \
php-mbstring \
php-xml \
libxcb-shm0-dev \
libxcb-xfixes0-dev \
libasound-dev \
libsdl2-dev \
libva-dev \
libvdpau-dev
#Mount volumes
VOLUME /temp
VOLUME /config
ENV PUID=""
ENV PGID=""
ENV CPU_CORES=""
ENV SLEEPTIME=""
#Merge-Script importieren
ADD runscript.sh /etc/service/bot/run
ADD auto-m4b-tool.sh /
#install actual m4b-tool
#RUN echo "---- INSTALL M4B-TOOL ----" && \
# wget https://github.com/sandreas/m4b-tool/releases/download/v.0.4.2/m4b-tool.phar -O /usr/local/bin/m4b-tool && \
# chmod +x /usr/local/bin/m4b-tool
ARG M4B_TOOL_DOWNLOAD_LINK="https://github.com/sandreas/m4b-tool/releases/latest/download/m4b-tool.tar.gz"
RUN echo "---- INSTALL M4B-TOOL ----" \
&& if [ ! -f /tmp/m4b-tool.phar ]; then \
wget "${M4B_TOOL_DOWNLOAD_LINK}" -O /tmp/m4b-tool.tar.gz && \
if [ ! -f /tmp/m4b-tool.phar ]; then \
tar xzf /tmp/m4b-tool.tar.gz -C /tmp/ && rm /tmp/m4b-tool.tar.gz ;\
fi \
fi \
&& mv /tmp/m4b-tool.phar /usr/local/bin/m4b-tool \
&& M4B_TOOL_PRE_RELEASE_LINK=$(wget -q -O - https://github.com/sandreas/m4b-tool/releases/tag/latest | grep -o 'M4B_TOOL_DOWNLOAD_LINK=[^ ]*' | head -1 | cut -d '=' -f 2) \
&& wget "${M4B_TOOL_PRE_RELEASE_LINK}" -O /tmp/m4b-tool.tar.gz \
&& tar xzf /tmp/m4b-tool.tar.gz -C /tmp/ && rm /tmp/m4b-tool.tar.gz \
&& mv /tmp/m4b-tool.phar /usr/local/bin/m4b-tool-pre \
&& chmod +x /usr/local/bin/m4b-tool /usr/local/bin/m4b-tool-pre
#use the remommended clean command
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
================================================
FILE: README.md
================================================
# Auto-M4B
[](https://gitter.im/Audiobook-Server/auto-m4b?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
This container is mostly based on the powerful [m4b-tool](https://github.com/sandreas/m4b-tool) made by sandreas
This repo is my fork of the fantastic [docker-m4b-tool](https://github.com/9Mad-Max5/docker-m4b-tool) created by 9Mad-Max5.
This is a docker container that will watch a folder for new books, auto convert mp3 books to chapterized m4b, and move all m4b books to a specific output folder, this output folder is where the [beets.io audible plugin](https://github.com/seanap/beets-audible) will look for audiobooks and use the audible api to perfectly tag and organize your books.
## Intended Use
This is meant to be an automated step between aquisition and tagging.
* Install via docker-compose
* Save new audiobooks to a /recentlyadded folder.
* All multifile m4b/mp3/m4a/ogg books will be converted to a chapterized m4b and saved to an /untagged folder
* This script will watch `/temp/recentlyadded` and automatically move mp3 books to `/temp/merge`, then automatically put all m4b's in the output folder `/temp/untagged`. It also makes a backup incase something goes wrong (can be disabled if desired).
Use the [beets.io audible plugin](https://github.com/seanap/beets-audible) to finish the tagging and sorting.
## Known Limitations
* The chapters are based on the mp3 tracks. A single mp3 file will become a single m4b with 1 chapter, also if the mp3 filenames are garbarge then your m4b chapternames will be terrible as well. See section on Chapters below for how to manually adjust.
* The conversion process actually strips some tags and covers from the files, which is why you need to use a tagger (mp3tag or beets.io) before adding to Plex.
## Need ARM Support?
Change the image to `spencermksmith/auto-m4b`
## Using torrents and need to preserve seeding?
In the settings of your client add this line to `Run external program on torrent completion`, it will copy all finished torrent files to your "recentlyadded" folder:
* `cp -r "%F" "path/to/temp/recentlyadded"`
## How to use
This docker assumes the following folder structure:
```sh
temp
│
└───recentlyadded # Input folder Add new books here
│ │ book1.m4b
│ | book2.mp3
| └─────book3
│ │ 01-book3.mp3
│ │ ...
└───merge # folder the script uses to combine mp3's
│ └─────book2
│ │ 01-book2.mp3
│ │ ...
└───untagged # Output folder where all m4b's wait to be tagged
│ └─────book4
│ │ book4.m4b
└───delete # needed by the script
|
└───fix # Manually fix books
|
└───backup # Backups incase anything goes wrong
└─────book2
│ 01-book2.mp3
│ ...
```
### Installation
1. Create a `temp` folder and keep the location in mind for Step 6
2. Install docker https://docs.docker.com/engine/install/ubuntu/
3. Manage docker as non-root https://docs.docker.com/engine/install/linux-postinstall/
4. Install docker-compose https://docs.docker.com/compose/install/
5. Create the compose file:
`nano docker-compose.yml`
6. Paste the yaml code below into the compose file, and change the volume mount locations
7. Put a test mp3 in the /temp/recentlyadded directory.
8. Start the docker (It should convert the mp3 and leave it in your /temp/untagged directory. It runs automatically every 5 min)
`docker-compose up -d`
### Example docker-compose.yml
* Replace the `/path/to/...` with your actual folder locations, but leave the `:` and everything after:
* Replace the PUID and PGID with your user ( [?](https://www.carnaghan.com/knowledge-base/how-to-find-your-uiduserid-and-gidgroupid-in-linux-via-the-command-line/) )
#### docker-compose.yml
```yaml
version: '3.7'
services:
auto-m4b:
image: seanap/auto-m4b
container_name: auto-m4b
volumes:
- /path/to/config:/config
- /path/to/temp:/temp
environment:
- PUID=1000
- PGID=1000
- CPU_CORES=2
- SLEEPTIME=1m
- MAKE_BACKUP=Y
```
## To Manually Set Chapters:
1. Put a folder with mp3's in the `/temp/recentlyadded` and let the script process the book like normal
2. In the output folder ( `/temp/untagged` ) there will be a book folder that includes the recently converted *.m4b and a *.chapters.txt file.
3. Open the chapters file and edit/add/rename, then save
4. Move the book folder (which contains the m4b and chapters.txt files) to `/temp/merge`
5. When the script runs it will re-chapterize the m4b and move it back to `/temp/untagged`
## Advanced Options
#### Edit the script that is run
You shouldn't need to change any options, but if you want to you will need to exec into the docker container. By default only vim text editor is installed, you will need to do a `apt-get update && apt-get install nano` if you want to use nano to edit the scipt.
* `docker exec -it auto-m4b sh -c 'vi auto-m4b-tool.sh'`
#### CPU Cores
The script will automatically use all CPU cores available, to change the amount of cpu cores for the converting change the `--jobs` flag in the m4b-tool command, but do not set it higher than the amount of cores available.
#### Backup Folder
For those copying files from another source into the `recentlyadded` folder, it might not make sense to waste time copying to the `backup` folder (because they were already copied from somewhere else). Backing up is enabled by default. To disable this copy operation, change this line in your compose file: `- MAKE_BACKUP=N`.
#### More Reading
More m4b-tool options https://github.com/sandreas/m4b-tool#reference
================================================
FILE: auto-m4b-tool.sh
================================================
#!/bin/bash
# set m to 1
m=1
#variable defenition
inputfolder="${INPUT_FOLDER:-"/temp/merge/"}"
outputfolder="${OUTPUT_FOLDER:-"/temp/untagged/"}"
originalfolder="${ORIGINAL_FOLDER:-"/temp/recentlyadded/"}"
fixitfolder="${FIXIT_FOLDER:-"/temp/fix"}"
backupfolder="${BACKUP_FOLDER:-"/temp/backup/"}"
binfolder="${BIN_FOLDER:-"/temp/delete/"}"
m4bend=".m4b"
logend=".log"
#ensure the expected folder-structure
mkdir -p "$inputfolder"
mkdir -p "$outputfolder"
mkdir -p "$originalfolder"
mkdir -p "$fixitfolder"
mkdir -p "$backupfolder"
mkdir -p "$binfolder"
#fix of the user for the new created folders
username="$(whoami)"
userid="$(id -u $username)"
groupid="$(id -g $username)"
chown -R $userid:$groupid /temp
#adjust the number of cores depending on the ENV CPU_CORES
if [ -z "$CPU_CORES" ]
then
echo "Using all CPU cores as not other defined."
CPUcores=$(nproc --all)
else
echo "Using $CPU_CORES CPU cores as defined."
CPUcores="$CPU_CORES"
fi
#adjust the interval of the runs depending on the ENV SLEEPTIME
if [ -z "$SLEEPTIME" ]
then
echo "Using standard 1 min sleep time."
sleeptime=1m
else
echo "Using $SLEEPTIME min sleep time."
sleeptime="$SLEEPTIME"
fi
#change to the merge folder, keeps this clear and the script could be kept inside the container
cd "$inputfolder" || return
# continue until $m 5
while [ $m -ge 0 ]; do
#copy files to backup destination
if [ "$MAKE_BACKUP" == "N" ]; then
echo "Skipping making a backup"
else
echo "Making a backup of the whole $originalfolder"
cp -Ru "$originalfolder"* $backupfolder
fi
#make sure all single file mp3's & m4b's are in their own folder
echo "Making sure all books are in their own folder"
for file in "$originalfolder"*.{m4b,mp3}; do
if [[ -f "$file" ]]; then
mkdir "${file%.*}"
mv "$file" "${file%.*}"
fi
done
# Finds folders with nested subfolders - renames and flattens files into a single folder
echo "Flattening nested subfolders 3 levels deep or more and renaming files..."
find "$originalfolder" -mindepth 3 -type f \( -name '*.mp3' -o -name '*.m4b' -o -name '*.m4a' \) -print0 |
while IFS= read -r -d '' file; do
# Get the relative path from the original folder
relative_path="${file#$originalfolder/}"
# Split the path into an array
IFS='/' read -ra path_parts <<< "$relative_path"
# Only process if the file is at least 3 levels deep
if [ ${#path_parts[@]} -ge 4 ]; then
# Get the filename (last element)
filename="${path_parts[-1]}"
# Get the grandparent directory
grandparent="${path_parts[3]}"
# Construct the new filename
new_filename=""
for ((i=4; i<${#path_parts[@]}-1; i++)); do
new_filename+="${path_parts[i]} - "
done
new_filename+="$filename"
# Create the new path (2 levels deep)
new_path="$originalfolder/$grandparent/$new_filename"
# Create the grandparent directory if it doesn't exist
mkdir -p "$(dirname "$new_path")"
# Move and rename the file
mv -v "$file" "$new_path"
fi
done
#Move folders with multiple audiofiles to inputfolder
echo "Moving folders with 2 or more audiofiles to $inputfolder "
find "$originalfolder" -maxdepth 2 -mindepth 2 -type f \( -name '*.mp3' -o -name '*.m4b' -o -name '*.m4a' \) -print0 | xargs -0 -L 1 dirname | sort | uniq -c | grep -E -v '^ *1 ' | sed 's/^ *[0-9]* //' | while read i; do mv -v "$i" $inputfolder; done
#Move single file mp3's to inputfolder
echo "Moving single file mp3's to $inputfolder "
find "$originalfolder" -maxdepth 2 -type f \( -name '*.mp3' \) -printf "%h\0" | xargs -0 mv -t "$inputfolder"
#Moving the single m4b files to the untagged folder as no Merge needed
echo "Moving all the single m4b books to $outputfolder "
find "$originalfolder" -maxdepth 2 -type f \( -iname \*.m4b -o -iname \*.mp4 -o -iname \*.m4a -o -iname \*.ogg \) -printf "%h\0" | xargs -0 mv -t "$outputfolder"
# clear the folders
rm -r "$binfolder"* 2>/dev/null
if ls -d */ 2>/dev/null; then
echo Folder Detected
for book in *; do
if [ -d "$book" ]; then
mpthree=$(find "$book" -maxdepth 2 -type f \( -name '*.mp3' -o -name '*.m4b' \) | head -n 1)
m4bfile="$outputfolder$book/$book$m4bend"
logfile="$outputfolder$book/$book$logend"
chapters=$(ls "$inputfolder$book"/*chapters.txt 2> /dev/null | wc -l)
if [ "$chapters" != "0" ]; then
echo Adjusting Chapters
mp4chaps -i "$inputfolder""$book"/*$m4bend
mv "$inputfolder$book" "$outputfolder"
else
echo Sampling $mpthree
bit=$(ffprobe -hide_banner -loglevel 0 -of flat -i "$mpthree" -select_streams a -show_entries format=bit_rate -of default=noprint_wrappers=1:nokey=1)
echo Bitrate = $bit
echo The folder "$book" will be merged to "$m4bfile"
echo Starting Conversion
m4b-tool merge "$book" -n -q --audio-bitrate="$bit" --skip-cover --use-filenames-as-chapters --no-chapter-reindexing --audio-codec=libfdk_aac --jobs="$CPUcores" --output-file="$m4bfile" --logfile="$logfile"
mv "$inputfolder$book" "$binfolder"
fi
echo Finished Converting
#make sure all single file m4b's are in their own folder
echo Putting the m4b into a folder
for file in $outputfolder*.m4b; do
if [[ -f "$file" ]]; then
mkdir "${file%.*}"
mv "$file" "${file%.*}"
fi
done
echo Deleting duplicate mp3 audiobook folder
fi
done
else
echo No folders detected, next run $sleeptime min...
sleep $sleeptime
fi
done
================================================
FILE: clean_untagged.sh
================================================
find /PATH/TO/temp/untagged/ -mindepth 1 -type d -prune -exec sh -c '[ $(du -s "$1" | awk "{print \$1}") -lt 1000 ] && rm -Rf "$1"' _ {} \;
================================================
FILE: docker-compose.yml
================================================
version: '3.7'
services:
auto-m4b:
#image: seanap/auto-m4b
build: .
container_name: auto-m4b
volumes:
- ./config:/config
- ./temp:/temp
environment:
- PUID=1000
- PGID=1000
- CPU_CORES=2
- MAKE_BACKUP=N
================================================
FILE: runscript.sh
================================================
#!/bin/bash
#cd /temp/mp3merge
#file="/temp/mp3merge/auto-m4b-tool.sh"
#cp -u /auto-m4b-tool.sh /temp/mp3merge/auto-m4b-tool.sh
user_name="autom4b"
user_id="1001"
group_id="100"
# Create user if they don't exist
if ! id -u "${PUID}" &>/dev/null; then
# If PUID is a number, create a user with that id
if [[ "${PUID}" =~ ^[0-9]+$ ]]; then
user_id="${PUID}"
# otherwise create a user with the name from PUID
else
user_name="${PUID}"
fi
# If PGID is a number, create a user with that id
if [[ "${PGID}" =~ ^[0-9]+$ ]]; then
group_id="${PGID}"
fi
addgroup --gid "${group_id}" "${user_name}"
adduser \
--uid "${user_id}" \
"${user_name}" \
--gid "${group_id}"
echo ""
echo "Created missing ${user_name} user with UID ${user_id} and GID ${group_id}"
fi
cmd_prefix=""
if [[ -n "${PUID:-}" ]]; then
cmd_prefix="/sbin/setuser ${user_name}"
fi
${cmd_prefix} /auto-m4b-tool.sh 2> /config/auto-m4b-tool.log
gitextract_de1d_7so/ ├── .github/ │ └── workflows/ │ └── build-and-publish.yml ├── Dockerfile ├── README.md ├── auto-m4b-tool.sh ├── clean_untagged.sh ├── docker-compose.yml └── runscript.sh
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (20K chars).
[
{
"path": ".github/workflows/build-and-publish.yml",
"chars": 1598,
"preview": "name: Build and Publish\n\non:\n # run it on push to the default repository branch\n push:\n branches: [main]\n # run it"
},
{
"path": "Dockerfile",
"chars": 4418,
"preview": "FROM phusion/baseimage:jammy-1.0.1\n#FROM phusion/baseimage:master\n\n#Basic Container\nRUN echo \"---- INSTALL RUNTIME PACKA"
},
{
"path": "README.md",
"chars": 5777,
"preview": "# Auto-M4B\n\n[ -lt 1000 ] && r"
},
{
"path": "docker-compose.yml",
"chars": 260,
"preview": "version: '3.7'\nservices:\n auto-m4b:\n #image: seanap/auto-m4b\n build: .\n container_name: auto-m4b\n volumes:\n"
},
{
"path": "runscript.sh",
"chars": 1005,
"preview": "#!/bin/bash\n#cd /temp/mp3merge\n#file=\"/temp/mp3merge/auto-m4b-tool.sh\"\n#cp -u /auto-m4b-tool.sh /temp/mp3merge/auto-m4b-"
}
]
About this extraction
This page contains the full source code of the seanap/auto-m4b GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (18.3 KB), approximately 5.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.