Compare commits

..

2 Commits

Author SHA1 Message Date
6adf7401a5 wip: actually set codec_opts to encoder 2024-05-26 22:13:30 +03:00
75600a85de more codecs, build ffmpeg manually 2024-05-26 20:36:36 +03:00
6 changed files with 101 additions and 89 deletions

View File

@ -1,13 +1,85 @@
FROM alpine:latest AS builder FROM alpine:3.20 AS builder
# Build FFmpeg.
RUN apk add --no-cache \
rust \
cargo \
fdk-aac \
fdk-aac-dev \
clang16 \
clang16-static \
clang16-libclang \
llvm16-dev \
libc-dev \
pkgconf \
git \
build-base \
nasm \
yasm \
aom-dev \
dav1d-dev \
lame-dev \
opus-dev \
svt-av1-dev \
libvorbis-dev \
libvpx-dev \
x264-dev \
x265-dev \
numactl-dev \
libass-dev \
libunistring-dev \
gnutls-dev && \
mkdir -p /ffmpeg/{ffmpeg_build,bin} && \
cd /ffmpeg && \
wget -O ffmpeg-7.0.1.tar.bz2 https://ffmpeg.org/releases/ffmpeg-7.0.1.tar.bz2 && \
tar xjvf ffmpeg-7.0.1.tar.bz2 && \
cd ffmpeg-7.0.1 && \
PKG_CONFIG_PATH="/ffmpeg/ffmpeg_build/lib/pkgconfig" ./configure \
--prefix="/ffmpeg/ffmpeg_build" \
--pkg-config-flags="--static" \
--extra-cflags="-I/ffmpeg/ffmpeg_build/include" \
--extra-ldflags="-L/ffmpeg/ffmpeg_build/lib" \
--extra-libs="-lpthread -lm" \
--ld="g++" \
--bindir="/ffmpeg/bin" \
--enable-gpl \
--enable-gnutls \
--enable-libaom \
--enable-libass \
--enable-libfdk-aac \
--enable-libfreetype \
--enable-libmp3lame \
--enable-libopus \
--enable-libsvtav1 \
--enable-libdav1d \
--enable-libvorbis \
--enable-libvpx \
--enable-libx264 \
--enable-libx265 \
--enable-nonfree && \
PATH="/ffmpeg/bin:$PATH" make -j$(nproc) && \
make install && \
hash -r
# Build the app.
WORKDIR /build WORKDIR /build
COPY ./ ./ COPY ./ ./
ARG PKG_CONFIG_PATH=/usr/lib/pkgconfig RUN cd /build && \
RUN apk add --no-cache ffmpeg-libs ffmpeg-dev clang16 clang16-libclang pkgconf rust cargo export PKG_CONFIG_PATH="/ffmpeg/ffmpeg_build/lib/pkgconfig" && \
RUN cd /build && cargo build --release cargo build --verbose --release
FROM alpine:latest FROM alpine:3.20
WORKDIR / WORKDIR /
RUN apk add --no-cache ffmpeg-libavutil ffmpeg-libavformat ffmpeg-libavfilter ffmpeg-libavdevice dumb-init mailcap tzdata RUN apk add --no-cache \
ffmpeg-libavutil \
ffmpeg-libavformat \
ffmpeg-libavfilter \
ffmpeg-libavdevice \
fdk-aac \
dumb-init \
mailcap \
tzdata \
gnutls
COPY --from=builder /build/target/release/atranscoder-rpc /usr/local/bin COPY --from=builder /build/target/release/atranscoder-rpc /usr/local/bin
EXPOSE 8090 EXPOSE 8090
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/atranscoder-rpc"] ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/atranscoder-rpc"]

View File

@ -1,75 +0,0 @@
# Initially I wanted to build a slim image using this Dockerfile and static linking.
# It would use something like this in Cargo.toml:
#
# [build-dependencies.clang]
# version = "2.0.0"
# default-features = false
# features = ["static"]
#
# [build-dependencies.clang-sys]
# version = "1.7.0"
# default-features = false
# features = ["clang_16_0", "static"]
#
# But there are a lot of problems with static linking and clang & clang-sys crates:
# - https://github.com/KyleMayes/clang-sys/issues/174
# - https://github.com/KyleMayes/clang-rs/issues/17
# - https://github.com/KyleMayes/clang-sys/issues/148 (this is the one I *like* the most!)
#
# So, no static linking for now, but I don't wanna erase all this gorgeous Dockerfile code, so... I'll let it linger
# in the repo for now.
# Stage 1: Build the Rust application
FROM rust:1.78-alpine AS builder
# Set the working directory
WORKDIR /usr/src/
# Install necessary packages
RUN apk add --no-cache \
ffmpeg-libs \
ffmpeg-dev \
pkgconf \
musl-dev \
clang16-static \
llvm-dev \
zlib-dev \
libffi-dev \
ncurses-dev \
gcc \
g++ \
git \
build-base
# Add target for musl
RUN rustup target add x86_64-unknown-linux-musl
# Create a new Rust project
RUN USER=root cargo new atranscoder-rpc
WORKDIR /usr/src/atranscoder-rpc
# Copy the manifest and lock files
COPY Cargo.toml Cargo.lock ./
# Set the environment variable for libclang path
ENV LIBCLANG_PATH=/usr/lib/llvm16/lib
ENV PKG_CONFIG_PATH=/usr/lib/pkgconfig
# Build dependencies to cache them
RUN cargo build --release --target x86_64-unknown-linux-musl
# Copy the source code
COPY src ./src
# Build the project and install the binary
RUN cargo install --target x86_64-unknown-linux-musl --path . && \
mkdir -p /result/{tmp,bin} && \
mv /usr/local/cargo/bin/atranscoder-rpc /result/bin
# Stage 2: Create the final minimal image
FROM scratch
COPY --from=builder /usr/bin/dumb-init /bin/
COPY --from=builder /result/bin /bin
COPY --from=builder /result/tmp /tmp
EXPOSE 8090
ENTRYPOINT ["/bin/dumb-init", "--", "/bin/atranscoder-rpc"]

View File

@ -8,7 +8,7 @@ Audio transcoder with simple HTTP API. Work in progress.
# How to Use # How to Use
Transcoding can be done like this: Transcoding can be done like this:
1. Use `cargo run` or [`neur0toxine/atranscoder-rpc`](https://hub.docker.com/r/neur0toxine/atranscoder-rpc/) Docker image. 1. Use [`neur0toxine/atranscoder-rpc`](https://hub.docker.com/r/neur0toxine/atranscoder-rpc/) Docker image.
2. Upload file for transcoding: 2. Upload file for transcoding:
```bash ```bash
curl --location 'http://localhost:8090/enqueue' \ curl --location 'http://localhost:8090/enqueue' \

View File

@ -95,6 +95,7 @@ impl Task {
&mut octx, &mut octx,
TranscoderParams { TranscoderParams {
codec: self.params.codec, codec: self.params.codec,
codec_opts: self.params.codec_opts,
bit_rate: self.params.bit_rate, bit_rate: self.params.bit_rate,
max_bit_rate: self.params.max_bit_rate, max_bit_rate: self.params.max_bit_rate,
sample_rate: self.params.sample_rate, sample_rate: self.params.sample_rate,
@ -248,10 +249,10 @@ fn upload_file<P: AsRef<Path>>(
} }
} }
fn params_to_avdictionary(input: &str) -> Dictionary { pub fn params_to_avdictionary(input: &str) -> Dictionary {
let mut dict: Dictionary = Dictionary::new(); let mut dict: Dictionary = Dictionary::new();
for pair in input.split(';') { for pair in input.split(';') {
let mut parts = pair.split(':'); let mut parts = pair.split('=');
if let (Some(key), Some(value)) = (parts.next(), parts.next()) { if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
dict.set(key, value); dict.set(key, value);

View File

@ -43,7 +43,14 @@ impl Worker {
let thread = thread::spawn(move || { let thread = thread::spawn(move || {
ffmpeg_next::init() ffmpeg_next::init()
.unwrap_or_else(|err| tracing::error!("couldn't init FFmpeg: {:?}", err)); .unwrap_or_else(|err| tracing::error!("couldn't init FFmpeg: {:?}", err));
ffmpeg_next::util::log::set_level(Level::Quiet);
ffmpeg_next::util::log::set_level(
if std::env::var("FFMPEG_VERBOSE").unwrap_or_default() == "1" {
Level::Trace
} else {
Level::Quiet
},
);
loop { loop {
let task = { let task = {

View File

@ -1,9 +1,14 @@
extern crate ffmpeg_next as ffmpeg; extern crate ffmpeg_next as ffmpeg;
use std::any::Any;
use std::error::Error; use std::error::Error;
use crate::task::params_to_avdictionary;
use ffmpeg::{codec, filter, format, frame, media}; use ffmpeg::{codec, filter, format, frame, media};
use ffmpeg_next::codec::Parameters;
use ffmpeg_next::error::EAGAIN; use ffmpeg_next::error::EAGAIN;
use ffmpeg_next::Dictionary;
use tracing::log::debug;
pub struct Transcoder { pub struct Transcoder {
pub(crate) stream: usize, pub(crate) stream: usize,
@ -16,6 +21,7 @@ pub struct Transcoder {
pub struct TranscoderParams { pub struct TranscoderParams {
pub codec: String, pub codec: String,
pub codec_opts: Option<String>,
pub bit_rate: usize, pub bit_rate: usize,
pub max_bit_rate: usize, pub max_bit_rate: usize,
pub sample_rate: i32, pub sample_rate: i32,
@ -67,9 +73,6 @@ impl Transcoder {
encoder.set_rate(sample_rate); encoder.set_rate(sample_rate);
encoder.set_channel_layout(params.channel_layout); encoder.set_channel_layout(params.channel_layout);
#[cfg(not(feature = "ffmpeg_7_0"))]
encoder.set_channels(params.channel_layout.channels());
encoder.set_format( encoder.set_format(
codec codec
.formats() .formats()
@ -97,7 +100,11 @@ impl Transcoder {
output.set_time_base((1, sample_rate)); output.set_time_base((1, sample_rate));
let in_time_base = decoder.time_base(); let in_time_base = decoder.time_base();
let encoder = encoder.open_as(codec)?; let encoder = if let Some(codec_opts) = params.codec_opts {
encoder.open_as_with(codec, params_to_avdictionary(codec_opts.as_str()))?
} else {
encoder.open_as(codec)?
};
output.set_parameters(&encoder); output.set_parameters(&encoder);
let filter = filter_graph("anull", &decoder, &encoder)?; let filter = filter_graph("anull", &decoder, &encoder)?;