This commit is contained in:
Ronnie Roller 2020-02-02 22:09:40 -08:00
parent 3a47cb5dd8
commit 7e35d6a68e
3 changed files with 118 additions and 20 deletions

View file

@ -2,6 +2,77 @@ FROM golang:1.13.6-alpine3.11 as builder
RUN apk add --no-cache curl RUN apk add --no-cache curl
# ffmpeg source - https://github.com/alfg/docker-ffmpeg
ARG FFMPEG_VERSION=4.2.2
ARG PREFIX=/opt/ffmpeg
ARG LD_LIBRARY_PATH=/opt/ffmpeg/lib
ARG MAKEFLAGS="-j4"
# FFmpeg build dependencies.
RUN apk add --update \
build-base \
coreutils \
freetype-dev \
gcc \
lame-dev \
libogg-dev \
libass \
libass-dev \
libvpx-dev \
libvorbis-dev \
libwebp-dev \
libtheora-dev \
opus-dev \
pkgconf \
pkgconfig \
rtmpdump-dev \
wget \
x264-dev \
x265-dev \
yasm
# Get fdk-aac from testing.
RUN echo http://dl-cdn.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories && \
apk add --update fdk-aac-dev
# Get ffmpeg source.
RUN cd /tmp/ && \
wget http://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz && \
tar zxf ffmpeg-${FFMPEG_VERSION}.tar.gz && rm ffmpeg-${FFMPEG_VERSION}.tar.gz
# Compile ffmpeg.
RUN cd /tmp/ffmpeg-${FFMPEG_VERSION} && \
./configure \
--enable-version3 \
--enable-gpl \
--enable-nonfree \
--enable-small \
--enable-libmp3lame \
--enable-libx264 \
--enable-libx265 \
--enable-libvpx \
--enable-libtheora \
--enable-libvorbis \
--enable-libopus \
--enable-libfdk-aac \
--enable-libass \
--enable-libwebp \
--enable-librtmp \
--enable-postproc \
--enable-avresample \
--enable-libfreetype \
--disable-debug \
--disable-doc \
--disable-ffplay \
--extra-cflags="-I${PREFIX}/include" \
--extra-ldflags="-L${PREFIX}/lib" \
--extra-libs="-lpthread -lm" \
--prefix="${PREFIX}" && \
make && make install && make distclean
# Cleanup.
RUN rm -rf /var/cache/apk/* /tmp/*
WORKDIR /app WORKDIR /app
COPY src src COPY src src
@ -13,17 +84,36 @@ RUN go build -x -o media-roller ./src
# youtube-dl needs python # youtube-dl needs python
FROM python:3.8.1-alpine3.11 FROM python:3.8.1-alpine3.11
RUN apk add --no-cache ffmpeg \
curl && \ ENV PATH=/opt/ffmpeg/bin:$PATH
ffmpeg -version
RUN apk add --update --no-cache \
curl \
ca-certificates \
openssl \
pcre \
lame \
libogg \
libass \
libvpx \
libvorbis \
libwebp \
libtheora \
opus \
rtmpdump \
x264-dev \
x265-dev
COPY --from=builder /app/media-roller /app/media-roller COPY --from=builder /app/media-roller /app/media-roller
COPY --from=builder /opt/ffmpeg /opt/ffmpeg
COPY --from=builder /usr/lib/libfdk-aac.so.2 /usr/lib/libfdk-aac.so.2
COPY templates /app/templates COPY templates /app/templates
WORKDIR /app WORKDIR /app
RUN curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl && \ RUN curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl && \
chmod a+rx /usr/local/bin/youtube-dl && \ chmod a+rx /usr/local/bin/youtube-dl && \
youtube-dl --version youtube-dl --version && \
ffmpeg -version
CMD /app/media-roller CMD /app/media-roller

View file

@ -25,6 +25,7 @@ type ResponseData struct {
Id string Id string
} }
// TODO: Use something better than this. It's too tedious to map
var fetchResponseTmpl = template.Must(template.ParseFiles("templates/media/response.html")) var fetchResponseTmpl = template.Must(template.ParseFiles("templates/media/response.html"))
var fetchIndexTmpl = template.Must(template.ParseFiles("templates/media/index.html")) var fetchIndexTmpl = template.Must(template.ParseFiles("templates/media/index.html"))
@ -59,14 +60,19 @@ func FetchMedia(w http.ResponseWriter, r *http.Request) {
// returns the ID of the file // returns the ID of the file
func fetch(url string) (string, error) { func fetch(url string) (string, error) {
// This will be the output file name // The id will be used as the name of the parent directory of the output files
id := uuid.New().String() id := uuid.New().String()
// youtube-dl will add the extension as needed name := getMediaDirectory(id) + "%(title)s.%(ext)s"
name := getFilenameWithoutExtensionById(id)
log.Info().Msgf("Downloading %s to %s", url, id) log.Info().Msgf("Downloading %s to %s", url, id)
cmd := exec.Command("youtube-dl", "-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4/", "-o", name, url) cmd := exec.Command("youtube-dl",
"--format", "bestvideo+bestaudio[ext=m4a]/bestvideo+bestaudio/best",
"--merge-output-format", "mp4",
"--restrict-filenames",
"--write-info-json",
"--output", name,
url)
var stdoutBuf, stderrBuf bytes.Buffer var stdoutBuf, stderrBuf bytes.Buffer
stdoutIn, _ := cmd.StdoutPipe() stdoutIn, _ := cmd.StdoutPipe()
@ -107,13 +113,6 @@ func fetch(url string) (string, error) {
return id, nil return id, nil
} }
// Returns the relative filename without the extension. Example:
// downloads/b541cc43-9833-4146-ab19-71334484c0c1/media
// where media can be media.mp4
func getFilenameWithoutExtensionById(id string) string {
return getMediaDirectory(id) + "media"
}
// Returns the relative directory containing the media file, with a trailing slash // Returns the relative directory containing the media file, with a trailing slash
// Id is expected to be pre validated // Id is expected to be pre validated
func getMediaDirectory(id string) string { func getMediaDirectory(id string) string {

View file

@ -3,6 +3,8 @@ package media
import ( import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"net/http" "net/http"
"path/filepath"
"strings"
) )
/** /**
@ -47,12 +49,20 @@ func getFileFromId(id string) (string, error) {
files, _ := file.Readdirnames(0) // 0 to read all files and folders files, _ := file.Readdirnames(0) // 0 to read all files and folders
if len(files) == 0 { if len(files) == 0 {
return "", errors.New("ID not found") return "", errors.New("ID not found")
} else if len(files) > 1 { } else if len(files) > 2 {
// We should only have 1 media file produced // We should only have 2 media file produced, the mp4 and the json file
return "", errors.New("internal error") return "", errors.New("internal error")
} }
return root + files[0], nil // We expect two files to be produced, a json manifest and an mp4. We want to return the mp4
// Sometimes the video file might not have an mp4 extension, so filter out the json file
for _, f := range files {
if !strings.HasSuffix(f, ".json") {
return root + f, nil
}
}
return "", errors.New("unable to find file")
} }
func streamFileToClient(writer http.ResponseWriter, filename string) { func streamFileToClient(writer http.ResponseWriter, filename string) {
@ -83,8 +93,7 @@ func streamFileToClient(writer http.ResponseWriter, filename string) {
fileSize := strconv.FormatInt(fileStat.Size(), 10) fileSize := strconv.FormatInt(fileStat.Size(), 10)
// Send the headers // Send the headers
// Set the following if you want to force the client to download the file writer.Header().Set("Content-Disposition", "filename="+filepath.Base(filename))
// writer.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(filename))
writer.Header().Set("Content-Type", fileContentType) writer.Header().Set("Content-Type", fileContentType)
writer.Header().Set("Content-Length", fileSize) writer.Header().Set("Content-Length", fileSize)