Add: support customized ffmpeg container format
This commit is contained in:
40
config.json
40
config.json
@@ -6,16 +6,42 @@
|
|||||||
"token": "!! config your very strong token here !!",
|
"token": "!! config your very strong token here !!",
|
||||||
"ffmpeg_threads": 1,
|
"ffmpeg_threads": 1,
|
||||||
"ffmpeg_config_list": [
|
"ffmpeg_config_list": [
|
||||||
{ "name": "OPUS 128k", "args": "-c:a libopus -ab 128k -vn" },
|
{
|
||||||
{ "name": "OPUS 96k", "args": "-c:a libopus -ab 96k -vn" },
|
"name": "WEBM OPUS 128k",
|
||||||
{ "name": "OPUS 256k", "args": "-c:a libopus -ab 256k -vn" },
|
"args": "-c:a libopus -ab 128k -vn",
|
||||||
{ "name": "OPUS 320k", "args": "-c:a libopus -ab 320k -vn" },
|
"format": "webm"
|
||||||
{ "name": "OPUS 512k", "args": "-c:a libopus -ab 512k -vn" },
|
},
|
||||||
{ "name": "AAC 128k", "args": "-c:a aac -ab 128k -vn" },
|
{
|
||||||
{ "name": "AAC 256k", "args": "-c:a aac -ab 256k -vn" },
|
"name": "WEBM OPUS 96k",
|
||||||
|
"args": "-c:a libopus -ab 96k -vn",
|
||||||
|
"format": "webm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "WEBM OPUS 256k",
|
||||||
|
"args": "-c:a libopus -ab 256k -vn",
|
||||||
|
"format": "webm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "WEBM OPUS 512k",
|
||||||
|
"args": "-c:a libopus -ab 512k -vn",
|
||||||
|
"format": "webm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AAC 128k",
|
||||||
|
"args": "-c:a aac -ab 128k -vn",
|
||||||
|
"format": "adts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AAC 256k",
|
||||||
|
"args": "-c:a aac -ab 256k -vn",
|
||||||
|
"format": "adts"
|
||||||
|
},
|
||||||
|
{ "name": "MP3 128k", "args": "-c:a mp3 -ab 128k -vn", "format": "mp3" },
|
||||||
|
{ "name": "MP3 320k", "args": "-c:a mp3 -ab 320k -vn", "format": "mp3" },
|
||||||
{ "name": "全损音质 32k", "args": "-c:a libopus -ab 32k -vn" },
|
{ "name": "全损音质 32k", "args": "-c:a libopus -ab 32k -vn" },
|
||||||
{
|
{
|
||||||
"name": "video test",
|
"name": "video test",
|
||||||
|
"format": "webm",
|
||||||
"args": "-c:a libopus -ab 128k -filter:v scale='min(640,iw)':min'(360,ih)':force_original_aspect_ratio=decrease -c:v libvpx -r 5"
|
"args": "-c:a libopus -ab 128k -filter:v scale='min(640,iw)':min'(360,ih)':force_original_aspect_ratio=decrease -c:v libvpx -r 5"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
4
main.go
4
main.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"msw-open-music/pkg/api"
|
"msw-open-music/pkg/api"
|
||||||
|
"msw-open-music/pkg/commonconfig"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,12 +15,11 @@ func init() {
|
|||||||
flag.StringVar(&ConfigFilePath, "config", "config.json", "backend config file path")
|
flag.StringVar(&ConfigFilePath, "config", "config.json", "backend config file path")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var err error
|
var err error
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
config := api.Config{}
|
config := commonconfig.Config{}
|
||||||
configFile, err := os.Open(ConfigFilePath)
|
configFile, err := os.Open(ConfigFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"msw-open-music/pkg/database"
|
"msw-open-music/pkg/database"
|
||||||
|
"msw-open-music/pkg/commonconfig"
|
||||||
"msw-open-music/pkg/tmpfs"
|
"msw-open-music/pkg/tmpfs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -12,32 +13,13 @@ type API struct {
|
|||||||
Db *database.Database
|
Db *database.Database
|
||||||
Server http.Server
|
Server http.Server
|
||||||
token string
|
token string
|
||||||
APIConfig APIConfig
|
APIConfig commonconfig.APIConfig
|
||||||
Tmpfs *tmpfs.Tmpfs
|
Tmpfs *tmpfs.Tmpfs
|
||||||
store *sessions.CookieStore
|
store *sessions.CookieStore
|
||||||
defaultSessionName string
|
defaultSessionName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIConfig() APIConfig {
|
func NewAPI(config commonconfig.Config) (*API, error) {
|
||||||
apiConfig := APIConfig{}
|
|
||||||
return apiConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type APIConfig struct {
|
|
||||||
DatabaseName string `json:"database_name"`
|
|
||||||
SingleThread bool `json:"single_thread,default=true"`
|
|
||||||
Addr string `json:"addr"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
FfmpegThreads int64 `json:"ffmpeg_threads"`
|
|
||||||
FfmpegConfigList []FfmpegConfig `json:"ffmpeg_config_list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
APIConfig APIConfig `json:"api"`
|
|
||||||
TmpfsConfig tmpfs.TmpfsConfig `json:"tmpfs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAPI(config Config) (*API, error) {
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
apiConfig := config.APIConfig
|
apiConfig := config.APIConfig
|
||||||
@@ -117,7 +99,6 @@ func NewAPI(config Config) (*API, error) {
|
|||||||
// below needs token
|
// below needs token
|
||||||
apiMux.HandleFunc("/walk", api.HandleWalk)
|
apiMux.HandleFunc("/walk", api.HandleWalk)
|
||||||
apiMux.HandleFunc("/reset", api.HandleReset)
|
apiMux.HandleFunc("/reset", api.HandleReset)
|
||||||
apiMux.HandleFunc("/add_ffmpeg_config", api.HandleAddFfmpegConfig)
|
|
||||||
|
|
||||||
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", apiMux))
|
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", apiMux))
|
||||||
mux.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("web/build"))))
|
mux.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("web/build"))))
|
||||||
|
|||||||
@@ -3,20 +3,12 @@ package api
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
|
"msw-open-music/pkg/commonconfig"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FfmpegConfig struct {
|
func (api *API) GetFfmpegConfig(configName string) (commonconfig.FfmpegConfig, bool) {
|
||||||
Name string `json:"name"`
|
ffmpegConfig := commonconfig.FfmpegConfig{}
|
||||||
Args string `json:"args"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FfmpegConfigList struct {
|
|
||||||
FfmpegConfigList []FfmpegConfig `json:"ffmpeg_config_list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) GetFfmpegConfig(configName string) (FfmpegConfig, bool) {
|
|
||||||
ffmpegConfig := FfmpegConfig{}
|
|
||||||
for _, f := range api.APIConfig.FfmpegConfigList {
|
for _, f := range api.APIConfig.FfmpegConfigList {
|
||||||
if f.Name == configName {
|
if f.Name == configName {
|
||||||
ffmpegConfig = f
|
ffmpegConfig = f
|
||||||
@@ -30,45 +22,8 @@ func (api *API) GetFfmpegConfig(configName string) (FfmpegConfig, bool) {
|
|||||||
|
|
||||||
func (api *API) HandleGetFfmpegConfigs(w http.ResponseWriter, r *http.Request) {
|
func (api *API) HandleGetFfmpegConfigs(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Println("[api] Get ffmpeg config list")
|
log.Println("[api] Get ffmpeg config list")
|
||||||
ffmpegConfigList := &FfmpegConfigList{
|
ffmpegConfigList := &commonconfig.FfmpegConfigList{
|
||||||
FfmpegConfigList: api.APIConfig.FfmpegConfigList,
|
FfmpegConfigList: api.APIConfig.FfmpegConfigList,
|
||||||
}
|
}
|
||||||
json.NewEncoder(w).Encode(&ffmpegConfigList)
|
json.NewEncoder(w).Encode(&ffmpegConfigList)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddFfmpegConfigRequest struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
FfmpegConfig FfmpegConfig `json:"ffmpeg_config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleAddFfmpegConfig(w http.ResponseWriter, r *http.Request) {
|
|
||||||
addFfmpegConfigRequest := AddFfmpegConfigRequest{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&addFfmpegConfigRequest)
|
|
||||||
if err != nil {
|
|
||||||
api.HandleError(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check token
|
|
||||||
err = api.CheckToken(w, r, addFfmpegConfigRequest.Token)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check name and args not null
|
|
||||||
if addFfmpegConfigRequest.Name == "" {
|
|
||||||
api.HandleErrorString(w, r, `"ffmpeg_config.name" can't be empty`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if addFfmpegConfigRequest.FfmpegConfig.Args == "" {
|
|
||||||
api.HandleErrorString(w, r, `"ffmpeg_config.args" can't be empty`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("[api] Add ffmpeg config")
|
|
||||||
|
|
||||||
api.APIConfig.FfmpegConfigList = append(api.APIConfig.FfmpegConfigList, addFfmpegConfigRequest.FfmpegConfig)
|
|
||||||
|
|
||||||
api.HandleOK(w, r)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -68,11 +68,12 @@ func (api *API) HandleGetFileStream(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
args := strings.Split(ffmpegConfig.Args, " ")
|
args := strings.Split(ffmpegConfig.Args, " ")
|
||||||
startArgs := []string{"-threads", strconv.FormatInt(api.APIConfig.FfmpegThreads, 10), "-i", path}
|
startArgs := []string{"-threads", strconv.FormatInt(api.APIConfig.FfmpegThreads, 10), "-i", path}
|
||||||
endArgs := []string{"-f", "webm", "-"}
|
endArgs := []string{"-f", ffmpegConfig.Format, "-"}
|
||||||
ffmpegArgs := append(startArgs, args...)
|
ffmpegArgs := append(startArgs, args...)
|
||||||
ffmpegArgs = append(ffmpegArgs, endArgs...)
|
ffmpegArgs = append(ffmpegArgs, endArgs...)
|
||||||
cmd := exec.Command("ffmpeg", ffmpegArgs...)
|
cmd := exec.Command("ffmpeg", ffmpegArgs...)
|
||||||
cmd.Stdout = w
|
cmd.Stdout = w
|
||||||
|
// cmd.Stderr = os.Stderr
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.HandleError(w, r, err)
|
api.HandleError(w, r, err)
|
||||||
@@ -127,7 +128,7 @@ func (api *API) HandlePrepareFileStreamDirect(w http.ResponseWriter, r *http.Req
|
|||||||
api.HandleErrorStringCode(w, r, `ffmpeg config not found`, 404)
|
api.HandleErrorStringCode(w, r, `ffmpeg config not found`, 404)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
objPath := api.Tmpfs.GetObjFilePath(prepareFileStreamDirectRequst.ID, prepareFileStreamDirectRequst.ConfigName)
|
objPath := api.Tmpfs.GetObjFilePath(prepareFileStreamDirectRequst.ID, ffmpegConfig)
|
||||||
|
|
||||||
// check obj file exists
|
// check obj file exists
|
||||||
exists := api.Tmpfs.Exits(objPath)
|
exists := api.Tmpfs.Exits(objPath)
|
||||||
@@ -179,7 +180,13 @@ func (api *API) HandleGetFileStreamDirect(w http.ResponseWriter, r *http.Request
|
|||||||
configs := q["config"]
|
configs := q["config"]
|
||||||
configName := configs[0]
|
configName := configs[0]
|
||||||
|
|
||||||
path := api.Tmpfs.GetObjFilePath(int64(id), configName)
|
ffmpegConfig, ok := api.GetFfmpegConfig(configName)
|
||||||
|
if !ok {
|
||||||
|
api.HandleErrorStringCode(w, r, `ffmpeg config not found`, 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path := api.Tmpfs.GetObjFilePath(int64(id), ffmpegConfig)
|
||||||
if api.Tmpfs.Exits(path) {
|
if api.Tmpfs.Exits(path) {
|
||||||
api.Tmpfs.Record(path)
|
api.Tmpfs.Record(path)
|
||||||
}
|
}
|
||||||
|
|||||||
43
pkg/commonconfig/config.go
Normal file
43
pkg/commonconfig/config.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package commonconfig
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
APIConfig APIConfig `json:"api"`
|
||||||
|
TmpfsConfig TmpfsConfig `json:"tmpfs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIConfig struct {
|
||||||
|
DatabaseName string `json:"database_name"`
|
||||||
|
SingleThread bool `json:"single_thread,default=true"`
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
FfmpegThreads int64 `json:"ffmpeg_threads"`
|
||||||
|
FfmpegConfigList []FfmpegConfig `json:"ffmpeg_config_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FfmpegConfigList struct {
|
||||||
|
FfmpegConfigList []FfmpegConfig `json:"ffmpeg_config_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FfmpegConfig struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Args string `json:"args"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TmpfsConfig struct {
|
||||||
|
FileLifeTime int64 `json:"file_life_time"`
|
||||||
|
CleanerInternal int64 `json:"cleaner_internal"`
|
||||||
|
Root string `json:"root"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructors for Config
|
||||||
|
|
||||||
|
func NewAPIConfig() APIConfig {
|
||||||
|
apiConfig := APIConfig{}
|
||||||
|
return apiConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTmpfsConfig() *TmpfsConfig {
|
||||||
|
config := &TmpfsConfig{}
|
||||||
|
return config
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package tmpfs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"msw-open-music/pkg/commonconfig"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -10,14 +11,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Tmpfs struct {
|
type Tmpfs struct {
|
||||||
record map[string]int64
|
record map[string]int64
|
||||||
Config TmpfsConfig
|
Config commonconfig.TmpfsConfig
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
recordLocks map[string]*sync.Mutex
|
recordLocks map[string]*sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tmpfs *Tmpfs) GetObjFilePath(id int64, configName string) (string) {
|
func (tmpfs *Tmpfs) GetObjFilePath(id int64, ffmpegConfig commonconfig.FfmpegConfig) string {
|
||||||
return filepath.Join(tmpfs.Config.Root, strconv.FormatInt(id, 10) + "." + configName + ".webm")
|
return filepath.Join(tmpfs.Config.Root, strconv.FormatInt(id, 10)+"."+ffmpegConfig.Name+"."+ffmpegConfig.Format)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tmpfs *Tmpfs) GetLock(filename string) *sync.Mutex {
|
func (tmpfs *Tmpfs) GetLock(filename string) *sync.Mutex {
|
||||||
@@ -35,21 +36,10 @@ func (tmpfs *Tmpfs) Unlock(filename string) {
|
|||||||
tmpfs.GetLock(filename).Unlock()
|
tmpfs.GetLock(filename).Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
type TmpfsConfig struct {
|
func NewTmpfs(config commonconfig.TmpfsConfig) *Tmpfs {
|
||||||
FileLifeTime int64 `json:"file_life_time"`
|
|
||||||
CleanerInternal int64 `json:"cleaner_internal"`
|
|
||||||
Root string `json:"root"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTmpfsConfig() (*TmpfsConfig) {
|
|
||||||
config := &TmpfsConfig{}
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTmpfs(config TmpfsConfig) *Tmpfs {
|
|
||||||
tmpfs := &Tmpfs{
|
tmpfs := &Tmpfs{
|
||||||
record: make(map[string]int64),
|
record: make(map[string]int64),
|
||||||
Config: config,
|
Config: config,
|
||||||
recordLocks: make(map[string]*sync.Mutex),
|
recordLocks: make(map[string]*sync.Mutex),
|
||||||
}
|
}
|
||||||
tmpfs.wg.Add(1)
|
tmpfs.wg.Add(1)
|
||||||
@@ -61,7 +51,7 @@ func (tmpfs *Tmpfs) Record(filename string) {
|
|||||||
tmpfs.record[filename] = time.Now().Unix()
|
tmpfs.record[filename] = time.Now().Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tmpfs *Tmpfs) Exits(filename string) (bool) {
|
func (tmpfs *Tmpfs) Exits(filename string) bool {
|
||||||
_, ok := tmpfs.record[filename]
|
_, ok := tmpfs.record[filename]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
@@ -77,7 +67,7 @@ func (tmpfs *Tmpfs) Cleaner() {
|
|||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if now - recordTime > tmpfs.Config.FileLifeTime {
|
if now-recordTime > tmpfs.Config.FileLifeTime {
|
||||||
err = os.Remove(path)
|
err = os.Remove(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("[tmpfs] Failed to remove file", err)
|
log.Println("[tmpfs] Failed to remove file", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user