stream 播放支持选择 ffmpeg 配置

组件化 manage-database
This commit is contained in:
2021-05-24 01:06:40 +08:00
parent 85513ece62
commit ccf1566cc9
4 changed files with 165 additions and 23 deletions

View File

@@ -10,6 +10,7 @@ import (
"os"
"os/exec"
"strconv"
"strings"
)
type API struct {
@@ -19,6 +20,20 @@ type API struct {
APIConfig APIConfig
}
type FfmpegConfigs struct {
FfmpegConfigs map[string]*FfmpegConfig `json:"ffmpeg_configs"`
}
type AddFfmpegConfigRequest struct {
Token string `json:"token"`
FfmpegConfig FfmpegConfig `json:"ffmpeg_config"`
}
type FfmpegConfig struct {
Name string `json:"name"`
Args string `json:"args"`
}
type Status struct {
Status string `json:"status,omitempty"`
}
@@ -310,6 +325,12 @@ func (api *API) HandleGetFileStream(w http.ResponseWriter, r *http.Request) {
api.HandleErrorString(w, r, `parameter "id" should be an integer`)
return
}
configs := q["config"]
if len(configs) == 0 {
api.HandleErrorString(w, r, `parameter "config" can't be empty`)
return
}
configName := configs[0]
file, err := api.Db.GetFile(int64(id))
if err != nil {
api.HandleError(w, r, err)
@@ -322,16 +343,19 @@ func (api *API) HandleGetFileStream(w http.ResponseWriter, r *http.Request) {
return
}
log.Println("[api] Stream file", path)
log.Println("[api] Stream file", path, configName)
cmd := exec.Command("ffmpeg",
"-i", path,
"-c:a", "libopus",
"-ab", "128k",
"-vn",
"-f", "matroska",
"-",
)
ffmpegConfig, ok := api.APIConfig.FfmpegConfigs[configName]
if !ok {
api.HandleErrorStringCode(w, r, `ffmpeg config not found`, 404)
return
}
args := strings.Split(ffmpegConfig.Args, " ")
startArgs := []string {"-i", path}
endArgs := []string {"-vn", "-f", "matroska", "-"}
ffmpegArgs := append(startArgs, args...)
ffmpegArgs = append(ffmpegArgs, endArgs...)
cmd := exec.Command("ffmpeg", ffmpegArgs...)
cmd.Stdout = w
err = cmd.Run()
if err != nil {
@@ -409,10 +433,56 @@ func (api *API) HandleGetFile(w http.ResponseWriter, r *http.Request) {
io.Copy(w, src)
}
func (api *API) HandleGetFfmpegConfigs(w http.ResponseWriter, r *http.Request) {
log.Println("[api] Get ffmpeg config list")
ffmpegConfigs:= &FfmpegConfigs{
FfmpegConfigs: api.APIConfig.FfmpegConfigs,
}
json.NewEncoder(w).Encode(&ffmpegConfigs)
}
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.FfmpegConfig.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.FfmpegConfigs[addFfmpegConfigRequest.FfmpegConfig.Name] = &addFfmpegConfigRequest.FfmpegConfig
api.HandleOK(w, r)
}
func NewAPIConfig() (APIConfig) {
apiConfig := APIConfig{
FfmpegConfigs: make(map[string]*FfmpegConfig),
}
return apiConfig
}
type APIConfig struct {
DatabaseName string
Addr string
Token string
DatabaseName string `json:"database_name"`
Addr string `json:"addr"`
Token string `json:"token"`
FfmpegConfigs map[string]*FfmpegConfig `json:"ffmpeg_configs"`
}
func NewAPI(apiConfig APIConfig) (*API, error) {
@@ -444,9 +514,11 @@ func NewAPI(apiConfig APIConfig) (*API, error) {
apiMux.HandleFunc("/get_files_in_folder", api.HandleGetFilesInFolder)
apiMux.HandleFunc("/get_random_files", api.HandleGetRandomFiles)
apiMux.HandleFunc("/get_file_stream", api.HandleGetFileStream)
apiMux.HandleFunc("/get_ffmpeg_config_list", api.HandleGetFfmpegConfigs)
// below needs token
apiMux.HandleFunc("/walk", api.HandleWalk)
apiMux.HandleFunc("/reset", api.HandleReset)
apiMux.HandleFunc("/add_ffmpeg_config", api.HandleAddFfmpegConfig)
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", apiMux))
mux.Handle("/web/", http.StripPrefix("/web", http.FileServer(http.Dir("web"))))

15
main.go
View File

@@ -18,11 +18,18 @@ func init() {
func main() {
flag.Parse()
apiConfig := api.APIConfig{
DatabaseName: DatabaseName,
Addr: Listen,
Token: Token,
apiConfig := api.NewAPIConfig()
apiConfig.FfmpegConfigs["libopus 128k"] = &api.FfmpegConfig{
Name: "libopus 128k",
Args: "-c:a libopus -ab 128k",
}
apiConfig.FfmpegConfigs["libopus 256k"] = &api.FfmpegConfig{
Name: "libopus 256k",
Args: "-c:a libopus -ab 256k",
}
apiConfig.DatabaseName = DatabaseName
apiConfig.Addr = Listen
apiConfig.Token = Token
api, err := api.NewAPI(apiConfig)
if err != nil {
log.Fatal(err)

View File

@@ -26,7 +26,7 @@
</nav>
</header>
<main>
<router-view @set_token="set_token" @play_audio="play_audio"></router-view>
<router-view :token="token" @set_token="set_token" @play_audio="play_audio"></router-view>
</main>
<footer>
<component-audio-player :file=playing_audio_file></component-audio-player>

View File

@@ -149,10 +149,6 @@ const component_manage= {
emits: ['set_token'],
data() {
return {
root: "",
pattern: [".flac", ".mp3"],
pattern_tmp: "",
s: "",
}
},
template: `
@@ -161,6 +157,22 @@ const component_manage= {
<p>本站是 MSW Project 的一个应用,希望以个人之力分享被隐藏在历史中的音乐。</p>
<p>自己是V家厨喜欢的p主包括 wonder-k, buzzG, *luna 等但却因为种种原因淹没在主流音乐APP的曲库中。本站的初衷是为了让那些知名度低的 VOCALOID / ACG / 东方曲,能够被更多有缘人听到,同时有一个跨平台的工具,能够在低网速的条件下享受硬盘中的无损音乐。</p>
<component-token :token="token" @set_token="$emit('set_token', $event)"></component-token>
<component-manage-database :token="token"></component-manage-database>
</div>
`,
}
const component_manage_database = {
props: ['token'],
data() {
return {
root: "",
pattern: [".flac", ".mp3"],
pattern_tmp: "",
s: "",
}
},
template: `
<div>
<table>
<tbody>
<tr>
@@ -352,24 +364,33 @@ const component_audio_player = {
data() {
return {
loop: true,
ffmpeg_config: {},
}
},
props: ["file"],
template: `
<div>
<div v-if="computed_show">
<span>{{ file.filename }} / {{ file.foldername }}</span><br />
<input type="checkbox" v-model="loop" />
<label>Loop</label><br />
<video class="audio-player" :src="computed_playing_audio_file_url" controls autoplay :loop="loop">
<video v-if="computed_show" class="audio-player" :src="computed_playing_audio_file_url" controls autoplay :loop="loop">
</video>
</div>
<component-stream-config @set_ffmpeg_config="set_ffmpeg_config"></component-stream-config>
</div>
`,
methods: {
set_ffmpeg_config(ffmpeg_config) {
this.ffmpeg_config = ffmpeg_config
},
},
computed: {
computed_playing_audio_file_url() {
if (this.file.play_back_type === 'raw') {
return '/api/v1/get_file_direct?id=' + this.file.id
} else if (this.file.play_back_type === 'stream') {
return '/api/v1/get_file_stream?id=' + this.file.id
return '/api/v1/get_file_stream?id=' + this.file.id + '&config=' + this.ffmpeg_config.name
}
},
computed_show() {
@@ -480,6 +501,46 @@ const component_get_random_files = {
},
}
const component_stream_config = {
emits: ['set_ffmpeg_config'],
data() {
return {
ffmpeg_config_list: [],
selected_ffmpeg_config: {},
}
},
template: `
<div>
<select v-model="selected_ffmpeg_config">
<option v-for="ffmpeg_config in ffmpeg_config_list" :value="ffmpeg_config">
{{ ffmpeg_config.name }}
</option>
</select>
</div>
`,
mounted() {
axios.get('/api/v1/get_ffmpeg_config_list',
).then(response => {
var ffmpeg_configs = response.data.ffmpeg_configs
var tmp_list = []
for (var key in ffmpeg_configs) {
tmp_list.push(ffmpeg_configs[key])
}
tmp_list.sort()
this.ffmpeg_config_list = tmp_list
this.selected_ffmpeg_config = this.ffmpeg_config_list[0]
}).catch(err => {
this.ffmpeg_config_list = [{name: 'No avaliable config'}]
this.selected_ffmpeg_config = this.ffmpeg_config_list[0]
})
},
watch: {
selected_ffmpeg_config(n, o) {
this.$emit('set_ffmpeg_config', this.selected_ffmpeg_config)
},
},
}
const routes = [
{ path: '/', component: component_get_random_files},
{ path: '/search_files', component: component_search_files},
@@ -517,6 +578,8 @@ app.component('component-search-files', component_search_files)
app.component('component-get-random-files', component_get_random_files)
app.component('component-file-dialog', component_file_dialog)
app.component('component-token', component_token)
app.component('component-stream-config', component_stream_config)
app.component('component-manage-database', component_manage_database)
app.use(router)