commit
d38623c4fe
@ -0,0 +1,46 @@
|
|||||||
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: test
|
||||||
|
image: golang
|
||||||
|
environment:
|
||||||
|
GO111MODULE: on
|
||||||
|
commands:
|
||||||
|
- echo $GO111MODULE
|
||||||
|
- go test ./... -v -cover
|
||||||
|
|
||||||
|
- name: build
|
||||||
|
image: golang
|
||||||
|
environment:
|
||||||
|
GO111MODULE: on
|
||||||
|
commands:
|
||||||
|
- go build
|
||||||
|
|
||||||
|
- name: docker
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
auto_tag: true
|
||||||
|
build_args_from_env:
|
||||||
|
- DRONE_COMMIT
|
||||||
|
- DRONE_TAG
|
||||||
|
username:
|
||||||
|
from_secret: username
|
||||||
|
password:
|
||||||
|
from_secret: password
|
||||||
|
repo: registry.ramonr.ch/mediaconverter
|
||||||
|
registry: registry.ramonr.ch
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
---
|
||||||
|
kind: secret
|
||||||
|
name: username
|
||||||
|
get:
|
||||||
|
path: registry-basicauth
|
||||||
|
name: user
|
||||||
|
---
|
||||||
|
kind: secret
|
||||||
|
name: password
|
||||||
|
get:
|
||||||
|
path: registry-basicauth
|
||||||
|
name: password
|
@ -0,0 +1,20 @@
|
|||||||
|
FROM golang:alpine as builder
|
||||||
|
|
||||||
|
ARG DRONE_COMMIT="none"
|
||||||
|
ARG DRONE_TAG="none"
|
||||||
|
|
||||||
|
RUN apk --no-cache add git
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
# ldflags to remove debugging tables
|
||||||
|
ENV DRONE_COMMIT=$DRONE_COMMIT
|
||||||
|
ENV DRONE_TAG=$DRONE_TAG
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s -X main.version=$DRONE_TAG -X main.commit=$DRONE_COMMIT" -o "mediaconverter" .
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s -X main.version=$DRONE_TAG -X main.commit=$DRONE_COMMIT" -o "faker" ./faker/
|
||||||
|
|
||||||
|
FROM alfg/ffmpeg
|
||||||
|
USER 1000:1000
|
||||||
|
COPY --from=builder /app/mediaconverter /mediaconverter
|
||||||
|
COPY --from=builder /app/faker /mediaconverter
|
||||||
|
|
||||||
|
CMD ["/mediaconverter"]
|
@ -0,0 +1,195 @@
|
|||||||
|
package converter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/xfrr/goffmpeg/transcoder"
|
||||||
|
"k8s.io/klog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Converter struct {
|
||||||
|
*message
|
||||||
|
// albumFolder is populated at runtime and contains the album name
|
||||||
|
albumFolder string
|
||||||
|
// the "automatically add to iTunes.localized" directory
|
||||||
|
iTunesDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMessage is basically the reverse process to the conversion.
|
||||||
|
// instead of getting the message and using it, this function can
|
||||||
|
// read a directory and create the message from it.
|
||||||
|
// Only populates what is necessary to do the conversion.
|
||||||
|
// artist must match the directory name.
|
||||||
|
func CreateMessage(artistName, albumFolder string) ([]byte, error) {
|
||||||
|
albumName := path.Base(albumFolder)
|
||||||
|
artistPath := path.Clean(strings.ReplaceAll(albumFolder, albumName, ""))
|
||||||
|
var trackFiles []trackFile
|
||||||
|
err := filepath.Walk(albumFolder, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error visiting file")
|
||||||
|
}
|
||||||
|
if filepath.Ext(path) == ".flac" {
|
||||||
|
trackFiles = append(trackFiles, trackFile{Path: path})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error walking path")
|
||||||
|
}
|
||||||
|
sort.Slice(trackFiles, func(i, j int) bool {
|
||||||
|
return trackFiles[i].Path < trackFiles[j].Path
|
||||||
|
})
|
||||||
|
|
||||||
|
tracks := func() []track {
|
||||||
|
var tracks []track
|
||||||
|
for i, file := range trackFiles {
|
||||||
|
tracks = append(
|
||||||
|
tracks,
|
||||||
|
track{
|
||||||
|
Title: strings.ReplaceAll(path.Base(file.Path), ".flac", ""),
|
||||||
|
TrackNumber: strconv.Itoa(i + 1),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return tracks
|
||||||
|
}()
|
||||||
|
|
||||||
|
m := message{
|
||||||
|
Tracks: tracks,
|
||||||
|
TrackFiles: trackFiles,
|
||||||
|
EventType: "Download",
|
||||||
|
Artist: artist{
|
||||||
|
Name: artistName,
|
||||||
|
Path: artistPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return json.Marshal(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
type message struct {
|
||||||
|
Tracks []track `json:"tracks"`
|
||||||
|
TrackFiles []trackFile `json:"trackFiles"`
|
||||||
|
IsUpgrade bool `json:"isUpgrade"`
|
||||||
|
EventType string `json:"eventType"`
|
||||||
|
Artist artist `json:"artist"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type artist struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
MBID string `json:"mbID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type track struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
TrackNumber string `json:"trackNumber"`
|
||||||
|
Quality string `json:"quality"`
|
||||||
|
QualityVersion int `json:"qualityVersion"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *track) mp3Name() string {
|
||||||
|
return fmt.Sprintf("%s - %s.mp3", t.TrackNumber, t.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
type trackFile struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Quality string `json:"quality"`
|
||||||
|
QualityVersion int `json:"qualityVersion"`
|
||||||
|
SceneName string `json:"sceneName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new converter
|
||||||
|
func New(iTunesDir string) *Converter {
|
||||||
|
return &Converter{
|
||||||
|
iTunesDir: iTunesDir,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tf *trackFile) Convert(newFile string) error {
|
||||||
|
trans := new(transcoder.Transcoder)
|
||||||
|
if err := trans.Initialize(tf.Path, newFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
trans.MediaFile().SetAudioVariableBitrate()
|
||||||
|
trans.MediaFile().SetAudioBitRate("0")
|
||||||
|
return <-trans.Run(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertFiles converts all files from the message into the
|
||||||
|
// iTunesDir and changes the permissions.
|
||||||
|
func (c *Converter) convertFiles() error {
|
||||||
|
for i, track := range c.Tracks {
|
||||||
|
destination := path.Join(c.iTunesDir, c.albumFolder, track.mp3Name())
|
||||||
|
if err := c.TrackFiles[i].Convert(destination); err != nil {
|
||||||
|
return errors.Wrapf(err, "could not convert %v", c.TrackFiles[i].Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := changePermission(path.Join(destination)); err != nil {
|
||||||
|
return errors.Wrapf(err, "could not change permissions on %v", destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *message) extractAlbumFolder() string {
|
||||||
|
artistDir := filepath.Clean(m.Artist.Path) + "/"
|
||||||
|
fileDir := filepath.Dir(m.TrackFiles[0].Path)
|
||||||
|
return strings.ReplaceAll(fileDir, artistDir, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the message, converting the files, changing permissions and copying artwork
|
||||||
|
func (c *Converter) Process(msgSource io.Reader) error {
|
||||||
|
// read and parse the message
|
||||||
|
encMessage, err := ioutil.ReadAll(msgSource)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not read given source")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.message = new(message)
|
||||||
|
if err := json.Unmarshal(encMessage, c.message); err != nil {
|
||||||
|
return errors.Wrapf(err, "could not unmarshal given source")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.message.EventType != "Download" {
|
||||||
|
klog.Infof("eventType is %v, nothing to do", c.message.EventType)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.albumFolder = c.TrackFiles[0].SceneName
|
||||||
|
if c.albumFolder == "" {
|
||||||
|
c.albumFolder = c.extractAlbumFolder()
|
||||||
|
}
|
||||||
|
klog.Infof("got request to transcode %v", c.albumFolder)
|
||||||
|
|
||||||
|
// actual conversion work
|
||||||
|
if err := os.MkdirAll(path.Join(c.iTunesDir, c.albumFolder), os.ModePerm); err != nil {
|
||||||
|
return errors.Wrapf(err, "could not create iTunes Directory")
|
||||||
|
}
|
||||||
|
if err := os.Chmod(path.Join(c.iTunesDir, c.albumFolder), 0775); err != nil {
|
||||||
|
return errors.Wrapf(err, "could not change permissions on iTunes dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.convertFiles(); err != nil {
|
||||||
|
return errors.Wrapf(err, "could not convert files")
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.Infof("Successfully converted %v", path.Join(c.Artist.Path, c.albumFolder))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func changePermission(newFile string) error {
|
||||||
|
err := os.Chmod(newFile, 0777)
|
||||||
|
return errors.Wrapf(err, "chmod failed")
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package converter
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestExtractAlbumFolder(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
m *message
|
||||||
|
albumFolder string
|
||||||
|
}{
|
||||||
|
{createMessage("/test/artistX", "/test/artistX/albumY/songX.flac"), "albumY"},
|
||||||
|
{createMessage("/test/artistX/", "/test/artistX/album Y/songX.flac"), "album Y"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
actual := tt.m.extractAlbumFolder()
|
||||||
|
expected := tt.albumFolder
|
||||||
|
if actual != expected {
|
||||||
|
t.Errorf("expected %v, got %v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMessage(artistPath, filePath string) *message {
|
||||||
|
return &message{
|
||||||
|
Artist: artist{
|
||||||
|
Path: artistPath,
|
||||||
|
},
|
||||||
|
TrackFiles: []trackFile{
|
||||||
|
{
|
||||||
|
Path: filePath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.ramonr.ch/ramon/mediaconverter/converter"
|
||||||
|
"k8s.io/klog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// set at compile time
|
||||||
|
var (
|
||||||
|
version = ""
|
||||||
|
commit = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
klog.InitFlags(nil)
|
||||||
|
postAddr := flag.String("post-addr", "mediaconverter", "the address where to post the request")
|
||||||
|
directory := flag.String("dir", "", "the directory that contains the flac files")
|
||||||
|
artistName := flag.String("artist", "", "name of the artist")
|
||||||
|
flag.Parse()
|
||||||
|
fmt.Printf("Mediaconverter version %v, commit %v\n", version, commit)
|
||||||
|
|
||||||
|
msg, err := converter.CreateMessage(*artistName, *directory)
|
||||||
|
if err != nil {
|
||||||
|
klog.Fatalf("could not create message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.Infof("sending request")
|
||||||
|
req, err := http.NewRequest("POST", *postAddr, bytes.NewBuffer(msg))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
klog.Infof("received answer")
|
||||||
|
|
||||||
|
fmt.Println("response Status:", resp.Status)
|
||||||
|
fmt.Println("response Headers:", resp.Header)
|
||||||
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
fmt.Println("response Body:", string(body))
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
module git.ramonr.ch/ramon/mediaconverter
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/pkg/errors v0.8.1
|
||||||
|
github.com/xfrr/goffmpeg v0.0.0-20191108092855-c49093f2d9f7
|
||||||
|
k8s.io/klog v1.0.0
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/xfrr/goffmpeg => github.com/tommyknows/goffmpeg v0.0.0-20191109121430-95bbe3aa58b5
|
@ -0,0 +1,15 @@
|
|||||||
|
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/tommyknows/goffmpeg v0.0.0-20191108092855-c49093f2d9f7 h1:19vePvbw3O7GvNDnipom3Fu8uLCN2XAZlpABr/Z+yFo=
|
||||||
|
github.com/tommyknows/goffmpeg v0.0.0-20191108092855-c49093f2d9f7/go.mod h1:W1AEwoY9XJ1POFmGvJ5y/sifvS6MnkGfwaCbV6nWKfo=
|
||||||
|
github.com/tommyknows/goffmpeg v0.0.0-20191109114406-40d09fee25ad h1:2ziJ/9WFv0zyZZvn2W7V/2SohYKw1M7K+9pHsGzW2bw=
|
||||||
|
github.com/tommyknows/goffmpeg v0.0.0-20191109114406-40d09fee25ad/go.mod h1:W1AEwoY9XJ1POFmGvJ5y/sifvS6MnkGfwaCbV6nWKfo=
|
||||||
|
github.com/tommyknows/goffmpeg v0.0.0-20191109120730-72121122651e h1:fuzOvyuRh3eGf+tgf7npiGQkg5Xn74DtlIMKizJpJNE=
|
||||||
|
github.com/tommyknows/goffmpeg v0.0.0-20191109120730-72121122651e/go.mod h1:W1AEwoY9XJ1POFmGvJ5y/sifvS6MnkGfwaCbV6nWKfo=
|
||||||
|
github.com/tommyknows/goffmpeg v0.0.0-20191109121430-95bbe3aa58b5 h1:UqjC/7jF5Sc9DhepBZyobGwNPiMNNtB2h+U6pHSLoEg=
|
||||||
|
github.com/tommyknows/goffmpeg v0.0.0-20191109121430-95bbe3aa58b5/go.mod h1:W1AEwoY9XJ1POFmGvJ5y/sifvS6MnkGfwaCbV6nWKfo=
|
||||||
|
github.com/xfrr/goffmpeg v0.0.0-20191108092855-c49093f2d9f7 h1:EMf+JgASgRmdgyXxsd5YqnDkmO6PI9cyp/yN7Z/wtk0=
|
||||||
|
github.com/xfrr/goffmpeg v0.0.0-20191108092855-c49093f2d9f7/go.mod h1:mL+qPvJWwu9An5a66+HyEJ7X7iEUSI09zX/Xl67XRXs=
|
||||||
|
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||||
|
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.ramonr.ch/ramon/mediaconverter/converter"
|
||||||
|
"k8s.io/klog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const iTunesDir = "/mnt/data/media/iTunes/iTunes Media/Automatically Add to Music.localized"
|
||||||
|
|
||||||
|
// set at compile time
|
||||||
|
var (
|
||||||
|
version = ""
|
||||||
|
commit = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
klog.InitFlags(nil)
|
||||||
|
listenAddr := flag.String("listen-addr", "127.0.0.1:8088", "the address where to listen on")
|
||||||
|
flag.Parse()
|
||||||
|
fmt.Printf("Mediaconverter version %v, commit %v\n", version, commit)
|
||||||
|
|
||||||
|
http.HandleFunc("/", conversionHandler())
|
||||||
|
fmt.Println("Starting to listen on", *listenAddr)
|
||||||
|
err := http.ListenAndServe(*listenAddr, nil)
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func conversionHandler() http.HandlerFunc {
|
||||||
|
conv := converter.New(iTunesDir)
|
||||||
|
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := conv.Process(r.Body); err != nil {
|
||||||
|
klog.Errorf("could not convert: %v", err)
|
||||||
|
fmt.Fprintf(w, "could not convert: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue