Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 121 additions & 1 deletion processor/bilibili/bilibili.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bilibili
import (
"encoding/json"
"errors"
"strconv"
"strings"

"github.com/foamzou/audio-get/args"
Expand Down Expand Up @@ -43,10 +44,12 @@ func (c *Core) FetchMetaAndResourceInfo() (mediaMeta *meta.MediaMeta, err error)
return
}

// audio resource
// audio resource
Copy link

Copilot AI Feb 11, 2026

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.

Suggested change
// audio resource

Copilot uses AI. Check for mistakes.
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)
Expand Down Expand Up @@ -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
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The artist extraction logic (lines 160-168) duplicates the same logic in the getSinger function (lines 238-254). Consider refactoring to reuse the getSinger function, or create a shared function that works with both AudioMeta and BilibiliWebInterfaceView structures. This would improve maintainability and ensure consistent behavior.

Suggested change
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 uses AI. Check for mistakes.

// audios
var bestAudio *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"`
}
Comment on lines +171 to +189
Copy link

Copilot AI Feb 11, 2026

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 uses AI. Check for mistakes.
maxBandwidth := 0

for i := range playData.Data.Dash.Audio {
audio := &playData.Data.Dash.Audio[i]
if audio.Bandwidth > maxBandwidth {
maxBandwidth = audio.Bandwidth
bestAudio = audio
}
}

if bestAudio != nil {
mediaMeta.Audios = append(mediaMeta.Audios, meta.Audio{
Url: bestAudio.BaseUrl,
BitRate: bestAudio.Bandwidth / 1000,
})
}
Comment on lines +200 to +205
Copy link

Copilot AI Feb 11, 2026

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.

Suggested change
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,
})

Copilot uses AI. Check for mistakes.

// videos
for _, video := range playData.Data.Dash.Video {
mediaMeta.Videos = append(mediaMeta.Videos, meta.Video{
Url: video.BaseUrl,
Width: video.Width,
Height: video.Height,
Ratio: getRatioById(video.Id),
NeedExtraAudio: true,
})
}

return mediaMeta, nil
}

func getRatioById(id int) string {
switch id {
case 16:
Expand Down
142 changes: 142 additions & 0 deletions processor/bilibili/fetch_media_entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate field "Codecid" appears twice in the same struct (lines 113 and 124). The second occurrence on line 124 is named "Codecid2" but has the same JSON tag "codecid". This will cause JSON unmarshaling to use only the first field, making the second field unreachable. Consider removing the duplicate field or using a different JSON tag if both fields are needed.

Copilot uses AI. Check for mistakes.
} `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"`
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate field "Codecid" appears twice in the same struct (lines 132 and 143). The second occurrence on line 143 is named "Codecid2" but has the same JSON tag "codecid". This will cause JSON unmarshaling to use only the first field, making the second field unreachable. Consider removing the duplicate field or using a different JSON tag if both fields are needed.

Copilot uses AI. Check for mistakes.
} `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"`
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate field "Codecid" appears twice in the same struct (lines 153 and 164). The second occurrence on line 164 is named "Codecid2" but has the same JSON tag "codecid". This will cause JSON unmarshaling to use only the first field, making the second field unreachable. Consider removing the duplicate field or using a different JSON tag if both fields are needed.

Copilot uses AI. Check for mistakes.
} `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"`
}
Loading