Skip to content

Commit

Permalink
feat: get dash video url
Browse files Browse the repository at this point in the history
  • Loading branch information
Misaka-L committed Jul 28, 2024
1 parent d9e12e2 commit 550d858
Show file tree
Hide file tree
Showing 8 changed files with 346 additions and 128 deletions.
127 changes: 119 additions & 8 deletions MisakaBiliApi/Controllers/BiliVideoController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using MisakaBiliApi.Models;
using MisakaBiliApi.Models.ApiResponse;
using MisakaBiliCore.Models.BiliApi;
using MisakaBiliCore.Services.BiliApi;
Expand Down Expand Up @@ -48,9 +49,10 @@ public class BiliVideoController(IBiliApiServices biliApiServices) : ControllerB
/// <response code="302">重定向到视频流地址</response>
[HttpGet("url/mp4")]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType<MisakaVideoStreamUrlResponse>(StatusCodes.Status200OK)]
[ProducesResponseType<MisakaVideoStreamMp4UrlResponse>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status302Found)]
[Produces("application/json")]
public async ValueTask<ActionResult<MisakaVideoStreamUrlResponse>> GetVideoUrl(string bvid = "",
public async ValueTask<ActionResult<MisakaVideoStreamMp4UrlResponse>> GetVideoMp4Url(string bvid = "",
string avid = "", int page = 0, bool redirect = false)
{
if (string.IsNullOrWhiteSpace(bvid) && string.IsNullOrWhiteSpace(avid))
Expand All @@ -74,10 +76,9 @@ public async ValueTask<ActionResult<MisakaVideoStreamUrlResponse>> GetVideoUrl(s
var pageDetail = await GetVideoPageInternal(bvid, avid, page);

var urlResponse = useBvid
? await biliApiServices.GetVideoUrlByBvid(bvid, pageDetail.Cid, (int)BiliVideoQuality.R1080PHighRate,
(int)BiliVideoStreamType.Mp4)
: await biliApiServices.GetVideoUrlByAvid(avid, pageDetail.Cid, (int)BiliVideoQuality.R1080PHighRate,
(int)BiliVideoStreamType.Mp4);
? await biliApiServices.GetVideoMp4UrlByBvid(bvid, pageDetail.Cid, (int)BiliVideoQuality.R1080PHighRate)
: await biliApiServices.GetVideoMp4UrlByAvid(avid, pageDetail.Cid,
(int)BiliVideoQuality.R1080PHighRate);

var urls = urlResponse.Data.Durl.SelectMany(durl => (string[]) [durl.Url, ..durl.BackupUrl ?? []])
.ToArray();
Expand All @@ -88,7 +89,7 @@ public async ValueTask<ActionResult<MisakaVideoStreamUrlResponse>> GetVideoUrl(s
return Redirect(url);
}

return new MisakaVideoStreamUrlResponse(
return new MisakaVideoStreamMp4UrlResponse(
Url: url,
Format: urlResponse.Data.Format,
TimeLength: urlResponse.Data.Timelength,
Expand All @@ -102,6 +103,115 @@ public async ValueTask<ActionResult<MisakaVideoStreamUrlResponse>> GetVideoUrl(s
}
}

/// <summary>
/// 请求哔哩哔哩视频流地址 (DASH)
/// </summary>
/// <param name="bvid">BV 号</param>
/// <param name="avid">AV 号(纯数字)</param>
/// <param name="page">分 P(从 0 开始)</param>
/// <param name="redirect">重定向到视频流或音频流 URL或不重定向</param>
/// <returns>返回或重定向到 MP4 视频流地址</returns>
/// <remarks>
/// 示例请求(BV 号):
///
/// GET /video/url/mp4?bvid=BV1LP411v7Bv
/// GET /video/url/mp4?bvid=BV1LP411v7Bv&amp;redirect=true (获取视频流 URL 并重定向)
/// GET /video/url/mp4?bvid=BV1mx411M793&amp;page=2 (获取 P3 的视频流 URL)
///
/// 示例请求(AV 号):
///
/// GET /video/url/mp4?avid=315594987
/// GET /video/url/mp4?avid=315594987&amp;redirect=true (获取视频流 URL 并重定向)
/// GET /video/url/mp4?bvid=15627712&amp;page=2 (获取 P3 的视频流 URL)
///
/// 示例响应:
///
/// {
/// "url": "https://*.bilivideo.com/*",
/// "format": "mp4720",
/// "timeLength": 222000,
/// "quality": 64
/// }
/// </remarks>
/// <response code="400">请求参数错误</response>
/// <response code="200">返回视频流地址</response>
[HttpGet("url/dash")]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType<MisakaVideoStreamDashUrlResponse>(StatusCodes.Status200OK)]
[Produces("application/json")]
public async ValueTask<ActionResult<MisakaVideoStreamDashUrlResponse>> GetVideoDashUrl(string bvid = "",
string avid = "", int page = 0, DashRequestRedirectType redirect = DashRequestRedirectType.None)
{
if (string.IsNullOrWhiteSpace(bvid) && string.IsNullOrWhiteSpace(avid))
{
ModelState.AddModelError(nameof(bvid), "You need at last a bvid or avid");
ModelState.AddModelError(nameof(avid), "You need at last a bvid or avid");
}

if (!string.IsNullOrWhiteSpace(bvid) && !string.IsNullOrWhiteSpace(avid))
{
ModelState.AddModelError(nameof(bvid), "You input avid and bvid at the sametime");
ModelState.AddModelError(nameof(avid), "You input avid and bvid at the sametime");
}

if (!ModelState.IsValid) return BadRequest();

var useBvid = !string.IsNullOrWhiteSpace(bvid);

try
{
var pageDetail = await GetVideoPageInternal(bvid, avid, page);

var urlResponse = useBvid
? await biliApiServices.GetVideoDashUrlByBvid(bvid, pageDetail.Cid,
(int)BiliVideoQuality.R1080PHighRate)
: await biliApiServices.GetVideoDashUrlByAvid(avid, pageDetail.Cid,
(int)BiliVideoQuality.R1080PHighRate);

var videoDashs = GetDashs(urlResponse.Data.Dash.Video);
if (redirect == DashRequestRedirectType.Video)
{
return Redirect(videoDashs[0].Urls[0]);
}

var audioDashs = GetDashs(urlResponse.Data.Dash.Audio);
if (redirect == DashRequestRedirectType.Audio)
{
return Redirect(audioDashs[0].Urls[0]);
}

return new MisakaVideoStreamDashUrlResponse(
VideoDashs: videoDashs,
AudioDashs: audioDashs,
Duration: urlResponse.Data.Dash.Duration
);
}
catch (ArgumentException argumentException)
{
ModelState.AddModelError(argumentException.ParamName ?? "", argumentException.Message);
return BadRequest();
}
}

private static MisakaVideoDashItem[] GetDashs(BiliVideoDashUrlItem[] dashs)
{
return dashs.Select(dash =>
{
return new MisakaVideoDashItem(
Urls: NoP2PUtils.GetNoP2PUrls(
[dash.BaseUrl.ToString(), ..dash.BackupUrls?.Select(url => url.ToString()) ?? []]),
FrameRate: dash.FrameRate,
Width: dash.Width,
Height: dash.Height,
Codecs: dash.Codecs,
Bandwidth: dash.Bandwidth,
Id: dash.Id
);
})
.OrderByDescending(dash => dash.Bandwidth)
.ToArray();
}

private async ValueTask<BiliVideoPage> GetVideoPageInternal(string? bvid = null, string? avid = null, int page = 0)
{
if (string.IsNullOrWhiteSpace(bvid) && string.IsNullOrWhiteSpace(avid))
Expand All @@ -115,7 +225,8 @@ private async ValueTask<BiliVideoPage> GetVideoPageInternal(string? bvid = null,
? (await biliApiServices.GetVideoDetailByBvid(bvid)).Data
: (await biliApiServices.GetVideoDetailByAid(avid)).Data;

if (page > videoDetail.Pages.Length - 1) throw new ArgumentOutOfRangeException(nameof(page), "Page out of index");
if (page > videoDetail.Pages.Length - 1)
throw new ArgumentOutOfRangeException(nameof(page), "Page out of index");

return videoDetail.Pages[page];
}
Expand Down
25 changes: 15 additions & 10 deletions MisakaBiliApi/Models/ApiResponse/MisakaStreamUrlResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,36 @@

namespace MisakaBiliApi.Models.ApiResponse;

/// <summary>
/// 视频流地址响应数据
/// </summary>
/// <param name="Url">流地址</param>
public record MisakaStreamUrl(string Url);

/// <summary>
/// 视频流地址响应数据
/// </summary>
/// <param name="Url">视频流地址</param>
/// <param name="Format">视频格式,如 mp4720</param>
/// <param name="TimeLength">时长,单位为毫秒</param>
/// <param name="Quality">视频流画质<see href="https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/videostream_url.md#qn%E8%A7%86%E9%A2%91%E6%B8%85%E6%99%B0%E5%BA%A6%E6%A0%87%E8%AF%86"/></param>
public record MisakaVideoStreamUrlResponse(
public record MisakaVideoStreamMp4UrlResponse(
string Url,
string Format,
long TimeLength,
[property: JsonPropertyName("quality")]
BiliVideoQuality Quality) : MisakaStreamUrl(Url);
BiliVideoQuality Quality);

public record MisakaVideoStreamDashUrlResponse(MisakaVideoDashItem[] VideoDashs, MisakaVideoDashItem[] AudioDashs, long Duration);

public record MisakaVideoDashItem(
string[] Urls,
string FrameRate,
long Width,
long Height,
string Codecs,
long Bandwidth,
long Id
);

/// <summary>
/// 直播流地址响应数据
/// </summary>
/// <param name="Url">直播流地址</param>
/// <param name="Quality">直播流画质</param>
/// <param name="StreamType">直播流类型</param>
public record MisakaLiveStreamUrlResponse(string Url, BiliLiveQuality Quality, LiveStreamType StreamType)
: MisakaStreamUrl(Url);
public record MisakaLiveStreamUrlResponse(string Url, BiliLiveQuality Quality, LiveStreamType StreamType);
8 changes: 8 additions & 0 deletions MisakaBiliApi/Models/DashRequestRedirectType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace MisakaBiliApi.Models;

public enum DashRequestRedirectType
{
Video,
Audio,
None
}
26 changes: 25 additions & 1 deletion MisakaBiliCore/Models/BiliApi/BiliVideoQuality.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,31 @@ public enum BiliVideoStreamType
/// </summary>
Mp4 = 1,
/// <summary>
/// DASH
/// </summary>
Dash = 16,
/// <summary>
/// HDR
/// </summary>
HDR = 64,
/// <summary>
/// 4K
/// </summary>
R4K = 128
R4K = 128,
/// <summary>
/// Dobly
/// </summary>
Dobly = 256,
/// <summary>
/// DoblyVision
/// </summary>
DoblyVision = 256,
/// <summary>
/// 8K
/// </summary>
R8K = 1024,
/// <summary>
/// AV1 Encoder
/// </summary>
AV1 = 2048,
}
102 changes: 0 additions & 102 deletions MisakaBiliCore/Models/BiliApi/BiliVideoUrlResponse.cs

This file was deleted.

Loading

0 comments on commit 550d858

Please sign in to comment.