stream 播放支持选择 ffmpeg 配置
组件化 manage-database
This commit is contained in:
@@ -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
15
main.go
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
75
web/index.js
75
web/index.js
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user