-
Notifications
You must be signed in to change notification settings - Fork 28
feat: support bilibili fetch for festival #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ package bilibili | |||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||||
| "errors" | ||||||||||||||||||||||||||||||
| "strconv" | ||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| "github.com/foamzou/audio-get/args" | ||||||||||||||||||||||||||||||
|
|
@@ -43,10 +44,12 @@ func (c *Core) FetchMetaAndResourceInfo() (mediaMeta *meta.MediaMeta, err error) | |||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // audio resource | ||||||||||||||||||||||||||||||
| // audio resource | ||||||||||||||||||||||||||||||
| matchStr, err := utils.RegexSingleMatch(html, `window.__playinfo__=(.+?)<\/script`) | ||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||
| logger.Warn("fetch playinfo failed, try to fetch from api", err) | ||||||||||||||||||||||||||||||
| return c.fetchBytesFromApi() | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| resource := &AudioResource{} | ||||||||||||||||||||||||||||||
| err = json.Unmarshal([]byte(matchStr), resource) | ||||||||||||||||||||||||||||||
|
|
@@ -98,6 +101,123 @@ func (c *Core) FetchMetaAndResourceInfo() (mediaMeta *meta.MediaMeta, err error) | |||||||||||||||||||||||||||||
| return mediaMeta, nil | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| func (c *Core) fetchBytesFromApi() (mediaMeta *meta.MediaMeta, err error) { | ||||||||||||||||||||||||||||||
| bvid := utils.RegexSingleMatchIgnoreError(c.Opts.Url, `(BV[a-zA-Z0-9]+)`, "") | ||||||||||||||||||||||||||||||
| if bvid == "" { | ||||||||||||||||||||||||||||||
| return nil, errors.New("bvid not found") | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // fetch meta | ||||||||||||||||||||||||||||||
| metaUrl := "https://api.bilibili.com/x/web-interface/view?bvid=" + bvid | ||||||||||||||||||||||||||||||
| metaJson, err := utils.HttpGet(consts.SourceNameBilibili, metaUrl, map[string]string{ | ||||||||||||||||||||||||||||||
| "user-agent": consts.UAMac, | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| var metaData BilibiliWebInterfaceView | ||||||||||||||||||||||||||||||
| err = json.Unmarshal([]byte(metaJson), &metaData) | ||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| if metaData.Code != 0 { | ||||||||||||||||||||||||||||||
| return nil, errors.New(metaData.Message) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // fetch resource | ||||||||||||||||||||||||||||||
| playUrl := "https://api.bilibili.com/x/player/playurl?bvid=" + bvid + "&cid=" + strconv.Itoa(metaData.Data.Cid) + "&qn=80&fnval=4048" | ||||||||||||||||||||||||||||||
| playJson, err := utils.HttpGet(consts.SourceNameBilibili, playUrl, map[string]string{ | ||||||||||||||||||||||||||||||
| "user-agent": consts.UAMac, | ||||||||||||||||||||||||||||||
| "referer": c.Opts.Url, | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| var playData BilibiliPlayUrlResponse | ||||||||||||||||||||||||||||||
| err = json.Unmarshal([]byte(playJson), &playData) | ||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| if playData.Code != 0 { | ||||||||||||||||||||||||||||||
| return nil, errors.New(playData.Message) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // construct meta | ||||||||||||||||||||||||||||||
| mediaMeta = &meta.MediaMeta{ | ||||||||||||||||||||||||||||||
| Title: metaData.Data.Title, | ||||||||||||||||||||||||||||||
| Description: metaData.Data.Desc, | ||||||||||||||||||||||||||||||
| Duration: playData.Data.Dash.Duration, | ||||||||||||||||||||||||||||||
| CoverUrl: metaData.Data.Pic, | ||||||||||||||||||||||||||||||
| ResourceType: consts.ResourceTypeVideo, | ||||||||||||||||||||||||||||||
| Album: Album, | ||||||||||||||||||||||||||||||
| Headers: map[string]string{ | ||||||||||||||||||||||||||||||
| "user-agent": consts.UAMac, | ||||||||||||||||||||||||||||||
| "referer": c.Opts.Url, | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // artist | ||||||||||||||||||||||||||||||
| if len(metaData.Data.Staff) == 0 { | ||||||||||||||||||||||||||||||
| mediaMeta.Artist = metaData.Data.Owner.Name | ||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||
| var names []string | ||||||||||||||||||||||||||||||
| for _, staff := range metaData.Data.Staff { | ||||||||||||||||||||||||||||||
| names = append(names, staff.Name) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| mediaMeta.Artist = strings.Join(names, ", ") | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+160
to
+168
|
||||||||||||||||||||||||||||||
| if len(metaData.Data.Staff) == 0 { | |
| mediaMeta.Artist = metaData.Data.Owner.Name | |
| } else { | |
| var names []string | |
| for _, staff := range metaData.Data.Staff { | |
| names = append(names, staff.Name) | |
| } | |
| mediaMeta.Artist = strings.Join(names, ", ") | |
| } | |
| mediaMeta.Artist = getSinger(metaData.Data) |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inline anonymous struct type for bestAudio (lines 171-189) duplicates the Audio struct definition from BilibiliPlayUrlResponse.Data.Dash.Audio (lines 126-144). Consider extracting this as a named type to avoid duplication and improve maintainability. For example, create a DashAudioItem type that can be reused in both places.
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no handling for the case when playData.Data.Dash.Audio is empty. If no audio streams are available, the function will return a MediaMeta with an empty Audios slice and only video streams. Consider checking if the Audio slice is empty and either returning an error similar to the original implementation (line 76: "no audio data") or documenting that this is intentional for festival videos that may only have video streams.
| if bestAudio != nil { | |
| mediaMeta.Audios = append(mediaMeta.Audios, meta.Audio{ | |
| Url: bestAudio.BaseUrl, | |
| BitRate: bestAudio.Bandwidth / 1000, | |
| }) | |
| } | |
| if bestAudio == nil { | |
| return meta.MediaMeta{}, errors.New("no audio data") | |
| } | |
| mediaMeta.Audios = append(mediaMeta.Audios, meta.Audio{ | |
| Url: bestAudio.BaseUrl, | |
| BitRate: bestAudio.Bandwidth / 1000, | |
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,3 +37,145 @@ type AudioMeta struct { | |
| } `json:"staff"` | ||
| } `json:"videoData"` | ||
| } | ||
|
|
||
| type BilibiliWebInterfaceView struct { | ||
| Code int `json:"code"` | ||
| Message string `json:"message"` | ||
| Data struct { | ||
| Bvid string `json:"bvid"` | ||
| Aid int `json:"aid"` | ||
| Cid int `json:"cid"` | ||
| Title string `json:"title"` | ||
| Desc string `json:"desc"` | ||
| Pic string `json:"pic"` | ||
| Owner struct { | ||
| Mid int `json:"mid"` | ||
| Name string `json:"name"` | ||
| Face string `json:"face"` | ||
| } `json:"owner"` | ||
| Staff []struct { | ||
| Mid int `json:"mid"` | ||
| Title string `json:"title"` | ||
| Name string `json:"name"` | ||
| } `json:"staff"` | ||
| Pages []struct { | ||
| Cid int `json:"cid"` | ||
| Page int `json:"page"` | ||
| From string `json:"from"` | ||
| Part string `json:"part"` | ||
| Duration int `json:"duration"` | ||
| Vid string `json:"vid"` | ||
| Weblink string `json:"weblink"` | ||
| Dimension struct { | ||
| Width int `json:"width"` | ||
| Height int `json:"height"` | ||
| Rotate int `json:"rotate"` | ||
| } `json:"dimension"` | ||
| } `json:"pages"` | ||
| } `json:"data"` | ||
| } | ||
|
|
||
| type BilibiliPlayUrlResponse struct { | ||
| Code int `json:"code"` | ||
| Message string `json:"message"` | ||
| Data struct { | ||
| From string `json:"from"` | ||
| Result string `json:"result"` | ||
| Message string `json:"message"` | ||
| Quality int `json:"quality"` | ||
| Format string `json:"format"` | ||
| Timelength int `json:"timelength"` | ||
| AcceptFormat string `json:"accept_format"` | ||
| AcceptDescription []string `json:"accept_description"` | ||
| AcceptQuality []int `json:"accept_quality"` | ||
| VideoCodecid int `json:"video_codecid"` | ||
| SeekParam string `json:"seek_param"` | ||
| SeekType string `json:"seek_type"` | ||
| Durl []struct { | ||
| Order int `json:"order"` | ||
| Length int `json:"length"` | ||
| Size int `json:"size"` | ||
| Ahead string `json:"ahead"` | ||
| Vhead string `json:"vhead"` | ||
| Url string `json:"url"` | ||
| BackupUrl []string `json:"backup_url"` | ||
| } `json:"durl"` | ||
| Dash struct { | ||
| Duration int `json:"duration"` | ||
| MinBufferTime float64 `json:"minBufferTime"` | ||
| MinBuffer float64 `json:"min_buffer"` | ||
| Video []struct { | ||
| Id int `json:"id"` | ||
| BaseUrl string `json:"baseUrl"` | ||
| BackupUrl []string `json:"backupUrl"` | ||
| Bandwidth int `json:"bandwidth"` | ||
| MimeType string `json:"mimeType"` | ||
| Codecid int `json:"codecid"` | ||
| Codecs string `json:"codecs"` | ||
| Width int `json:"width"` | ||
| Height int `json:"height"` | ||
| FrameRate string `json:"frameRate"` | ||
| Sar string `json:"sar"` | ||
| StartWithSap int `json:"startWithSap"` | ||
| SegmentBase struct { | ||
| Initialization string `json:"Initialization"` | ||
| IndexRange string `json:"indexRange"` | ||
| } `json:"SegmentBase"` | ||
| Codecid2 int `json:"codecid"` | ||
|
||
| } `json:"video"` | ||
| Audio []struct { | ||
| Id int `json:"id"` | ||
| BaseUrl string `json:"baseUrl"` | ||
| BackupUrl []string `json:"backupUrl"` | ||
| Bandwidth int `json:"bandwidth"` | ||
| MimeType string `json:"mimeType"` | ||
| Codecid int `json:"codecid"` | ||
| Codecs string `json:"codecs"` | ||
| Width int `json:"width"` | ||
| Height int `json:"height"` | ||
| FrameRate string `json:"frameRate"` | ||
| Sar string `json:"sar"` | ||
| StartWithSap int `json:"startWithSap"` | ||
| SegmentBase struct { | ||
| Initialization string `json:"Initialization"` | ||
| IndexRange string `json:"indexRange"` | ||
| } `json:"SegmentBase"` | ||
| Codecid2 int `json:"codecid"` | ||
|
||
| } `json:"audio"` | ||
| Dolby struct { | ||
| Type int `json:"type"` | ||
| Audio []struct { | ||
| Id int `json:"id"` | ||
| BaseUrl string `json:"baseUrl"` | ||
| BackupUrl []string `json:"backupUrl"` | ||
| Bandwidth int `json:"bandwidth"` | ||
| MimeType string `json:"mimeType"` | ||
| Codecid int `json:"codecid"` | ||
| Codecs string `json:"codecs"` | ||
| Width int `json:"width"` | ||
| Height int `json:"height"` | ||
| FrameRate string `json:"frameRate"` | ||
| Sar string `json:"sar"` | ||
| StartWithSap int `json:"startWithSap"` | ||
| SegmentBase struct { | ||
| Initialization string `json:"Initialization"` | ||
| IndexRange string `json:"indexRange"` | ||
| } `json:"SegmentBase"` | ||
| Codecid2 int `json:"codecid"` | ||
|
||
| } `json:"audio"` | ||
| } `json:"dolby"` | ||
| Flac interface{} `json:"flac"` | ||
| } `json:"dash"` | ||
| SupportFormats []struct { | ||
| Quality int `json:"quality"` | ||
| Format string `json:"format"` | ||
| NewDescription string `json:"new_description"` | ||
| DisplayDesc string `json:"display_desc"` | ||
| Superscript string `json:"superscript"` | ||
| Codecs []interface{} `json:"codecs"` | ||
| } `json:"support_formats"` | ||
| HighFormat interface{} `json:"high_format"` | ||
| LastPlayTime int `json:"last_play_time"` | ||
| LastPlayCid int `json:"last_play_cid"` | ||
| } `json:"data"` | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate comment "audio resource" appears on consecutive lines. Remove one of the duplicate comments.