| Tool | Role | Required |
|---|---|---|
JDK 21+ |
Gradle and plugin runtime |
β |
FFmpeg or Docker |
Audio to MP3 conversion |
β (one or the other) |
|
Note
|
No Python dependency is required. |
newpipe-gradle/ <-- this repo (consumer project)
βββ build.gradle.kts <-- applies the plugin, nothing else
βββ settings.gradle.kts <-- declares JitPack in pluginManagement
βββ musics.yml <-- your music selection
βββ gradle/
β βββ libs.versions.toml <-- version catalog
βββ newpipe-plugin/ <-- plugin source code (sub-project)JitPack must be declared in pluginManagement because the plugin depends on
NewPipe Extractor, which is hosted on JitPack.
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
dependencyResolutionManagement {
repositories {
mavenLocal()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
rootProject.name = "newpipe-plugin"
include("newpipe")Minimal configuration β only configPath is required:
plugins {
id("com.cheroliv.newpipe") version ("0.0.4")
}
newpipe {
configPath = file("musics.yml").absolutePath
}Full configuration with all available options:
plugins {
id("com.cheroliv.newpipe") version ("0.0.4")
}
newpipe {
// Required β path to the YAML selection file
configPath = file("musics.yml").absolutePath
// Optional β Docker image used for FFmpeg conversion.
// Default: "jrottenberg/ffmpeg:4.4-alpine"
// Only used when FFmpeg is not installed locally, OR when forceDocker = true.
ffmpegDockerImage = "jrottenberg/ffmpeg:8-scratch"
// Optional β force Docker even if FFmpeg is installed locally.
// Useful for reproducible builds or CI environments. Default: false
forceDocker = true
}| Property | Type | Default | Description |
|---|---|---|---|
|
|
β |
Required. Absolute path to the |
|
|
|
Docker image used for conversion when FFmpeg is not installed locally, |
|
|
|
When |
The configuration file declares artists, their individual tracks and their
YouTube playlists.
artistes:
- name: "Daft Punk"
tunes:
- "https://www.youtube.com/watch?v=5NV6Rdv1a3I"
- "https://www.youtube.com/watch?v=gAjR4_CbPpQ"
playlists:
- "https://www.youtube.com/playlist?list=PLx0sYbCqOb8TBPRdmBHs5Iftvv9TPboYG"
- name: "Massive Attack"
tunes:
- "https://www.youtube.com/watch?v=u7K72X4eo_s"
playlists:
- "https://www.youtube.com/watch?v=5dWeeUIZFgA&list=RDEMtbC_BXEtvKp9p_L81bzVwA&start_radio=1"| Field | Description |
|---|---|
|
List of artists to download |
|
Artist name β used as the folder name under |
|
Individual YouTube URLs. The artist name comes from the |
|
YouTube playlist URLs (standard or Mix Radio |
|
Note
|
YouTube Mix Radio ( |
The plugin resolves the conversion strategy once at startup, in this order:
| Priority | Strategy | Condition |
|---|---|---|
1 |
|
|
2 |
|
|
3 |
|
Neither available β explicit error at conversion time |
Setting forceDocker = true skips priority 1 entirely, regardless of whether
ffmpeg is installed.
By default the plugin prefers local FFmpeg when available, as it is faster
(no container startup overhead). You can force a specific behaviour depending
on your environment:
| Scenario | Configuration | Effect |
|---|---|---|
FFmpeg installed locally, use it directly |
|
|
FFmpeg installed, but Docker preferred anyway |
|
|
No local FFmpeg, Docker available |
|
|
CI/CD β guaranteed reproducible build |
|
Same FFmpeg image regardless of the execution environment |
When the DOCKER strategy is selected, the plugin runs:
docker run --rm \
--user <current-uid>:<current-gid> \
-v <artist-dir>:/data \
<ffmpegDockerImage> \
-i /data/<input.m4a> -vn -ar 44100 -ac 2 -b:a <bitrate> -y \
/data/<output.mp3>The --user flag ensures the output MP3 is owned by the user running Gradle,
not by the containerβs root user. This avoids permission errors when jaudiotagger
writes the ID3 tags after the conversion.
Both the temp file (_temp.m4a) and the output MP3 are placed in the same
artist directory, so a single volume mount covers both.
The plugin automatically selects the best available audio stream for each
video, following a descending priority ladder:
| Priority | Target | Behaviour |
|---|---|---|
1 |
320 kbps |
Closest stream >= 320 kbps selected first |
2 |
256 kbps |
If no stream >= 320 kbps is available |
3 |
192 kbps |
If no stream >= 256 kbps is available |
4 |
160 kbps |
Last acceptable fallback |
Fallback |
Best available |
If no stream reaches 160 kbps β warning logged |
The stream format (m4a, webm, opusβ¦) is deliberately ignored during selection:
FFmpeg handles the conversion to MP3 regardless of the source format.
The selected bitrate is passed directly to FFmpeg (-b:a <bitrate>k),
ensuring the YouTube stream quality is faithfully preserved in the final MP3.
If an MP3 was downloaded in a previous session at a lower quality than what
is currently available on YouTube, the plugin detects this automatically and
re-downloads it at the best possible quality.
Detection is based on comparing the encoded bitrate of the existing MP3
(read from its audio header) with the bitrate of the best available YouTube
stream. A gap greater than 32 kbps triggers a re-download β this threshold
absorbs normal FFmpeg encoding variance (a 320 kbps stream typically produces
315β318 kbps encoded).
The quality upgrade proceeds in three phases to ensure a file is never lost:
1. The old MP3 is renamed to <name>.old (backup)
2. The new stream is downloaded and converted to MP3
3. On success β the .old file is deleted
On failure β the .old file is restored as .mp3At no point are you left without an audio file for a given track, even if
the conversion or download fails midway.
./gradlew -i downloadMP3s are organised as follows:
downloads/
βββ Daft Punk/
β βββ Daft Punk - Get Lucky.mp3
β βββ Daft Punk - Around the World.mp3
βββ Massive Attack/
βββ Massive Attack - Teardrop.mp3./gradlew download --url=https://www.youtube.com/watch?v=5NV6Rdv1a3IWith a custom output folder:
./gradlew download \
--url=https://www.youtube.com/watch?v=5NV6Rdv1a3I \
--output=/home/user/MusicThe plugin downloads up to 3 videos simultaneously to maximise throughput
while avoiding YouTube throttling. As soon as a download completes, the MP3
conversion starts immediately β temporary files do not accumulate.
Before each download, the plugin checks whether an identical MP3 already
exists by comparing ID3 tags (title, artist), audio duration (Β±2s tolerance)
and encoded bitrate. If the file matches in both content and quality, it is
skipped silently.
If the task is interrupted (Ctrl+C, network failure), temporary files
(_temp.m4a) and incomplete MP3s are automatically deleted on the next run.
The download restarts cleanly from the beginning.
If a quality upgrade was in progress, the .old backup file is automatically
restored as .mp3.
If a playlist or video no longer exists (deleted, private, invalid URL),
it is skipped with a warning in the logs. The rest of the selection continues
to be downloaded.
| Tag | Value |
|---|---|
|
YouTube video title |
|
Artist name (YAML for tunes, YouTube uploader for playlists) |
|
|
|
Current year |
|
YouTube video thumbnail |
|
Note
|
Two-pass tag writing |
Check that musics.yml exists at the project root and that the path in
build.gradle.kts is correct:
newpipe {
configPath = file("musics.yml").absolutePath // absolute path required
}YouTube throttles unofficial clients. This behaviour is tied to the version
of NewPipe Extractor in use. To point to the latest dev branch commit:
# gradle/libs.versions.toml
[versions]
newpipe-extractor = "4701a17" # hash of the latest dev commit
# https://github.com/TeamNewPipe/NewPipeExtractor/commits/devInstall FFmpeg locally:
# Debian / Ubuntu
sudo apt install ffmpeg
# macOS
brew install ffmpeg
# Arch Linux
sudo pacman -S ffmpegOr configure the plugin to use Docker instead (no local FFmpeg required):
newpipe {
configPath = file("musics.yml").absolutePath
ffmpegDockerImage = "jrottenberg/ffmpeg:4.4-alpine"
forceDocker = true
}If Docker is installed on your machine but you prefer to use local FFmpeg
(faster, no container startup):
newpipe {
configPath = file("musics.yml").absolutePath
forceDocker = false // default β local FFmpeg takes priority
}The plugin automatically detects local FFmpeg first whenever forceDocker = false.
If you see an error like:
Cannot modify /path/to/downloads/Artist/Artist - Title.mp3
because do not have permissions to modify fileThis means the Docker container wrote the MP3 as root. The plugin passes
--user $(id -u):$(id -g) to docker run automatically to prevent this.
If the error persists, verify that your Docker daemon version supports the
--user flag and that your user has write access to the downloads/ directory:
chmod -R u+w downloads/This indicates that the fileβs ID3 tags do not match the YouTube metadata
(different title or artist), or that the detected bitrate is significantly
below the best available stream. Check the fileβs tags with a tag editor
(Ex Falso, MusicBrainz Picard) and compare with the exact YouTube video title.