672 lines
21 KiB
C#
672 lines
21 KiB
C#
using AllBase;
|
|
using AllBase.Model.Result;
|
|
using Fiddler;
|
|
using NaiBa.Util.Extension;
|
|
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace TemuKJD.BLL
|
|
{
|
|
internal static class Config
|
|
{
|
|
// ------- Cookie -------
|
|
|
|
|
|
// ------- 以下是协议里的固定常量,一般不用改 -------
|
|
public const string VideoHost = "https://westoregamma.video.qq.com";
|
|
public const string ShopHost = "https://store.weixin.qq.com";
|
|
public const int BizId = 50087; // 微信小店商品视频固定 bizId
|
|
public const int AppType = 162; // 视频场景固定 apptype
|
|
public const int FileType = 20302; // 视频场景固定 filetype
|
|
public const int ChunkSize = 2 * 1024 * 1024; // 分片大小 2MB,与抓包一致
|
|
|
|
// 轮询转码结果的间隔与最大次数
|
|
public static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(2);
|
|
public const int MaxPollTimes = 30;
|
|
}
|
|
public class WXVideoUploader
|
|
{
|
|
private string Cookie = "";
|
|
|
|
// ------- 当前登录会话的 openid -------
|
|
private string OpenId = "";
|
|
|
|
// ------- 商品 ID, groupid -------
|
|
private string ProductId = "";
|
|
|
|
private string biz_magic = "";
|
|
private readonly HttpClient _http;
|
|
public WXVideoUploader()
|
|
{
|
|
_http = new HttpClient(new HttpClientHandler
|
|
{
|
|
//Proxy = new WebProxy("http://127.0.0.1:8080"),
|
|
//UseProxy = true,
|
|
UseCookies=false,
|
|
AutomaticDecompression = System.Net.DecompressionMethods.GZip
|
|
| System.Net.DecompressionMethods.Deflate
|
|
});
|
|
}
|
|
public WXVideoUploader(HttpClient http)
|
|
{
|
|
_http = http;
|
|
}
|
|
|
|
public MultiMediaDownloadUrlResp Upload(ShopLoginInfo shop, string productId, string videoPath)
|
|
{
|
|
if (!File.Exists(videoPath))
|
|
{
|
|
throw new Exception("视频文件不存在");
|
|
}
|
|
byte[] bs = File.ReadAllBytes(videoPath);
|
|
return Upload(shop, productId, bs);
|
|
}
|
|
public MultiMediaDownloadUrlResp Upload(ShopLoginInfo shop, string productId, byte[] videoBytes)
|
|
{
|
|
var cookieLst = JsonHelper.DeserializeJsonToList<CefSharp.Cookie>(shop?.Cookies.ParseToString());
|
|
if (cookieLst == null)
|
|
cookieLst = new List<CefSharp.Cookie>();
|
|
foreach (var cook in cookieLst)
|
|
{
|
|
if (cook.Name == "biz_magic")
|
|
{
|
|
biz_magic = cook.Value;
|
|
}
|
|
Cookie += cook.Name + "=" + cook.Value + "; ";
|
|
}
|
|
|
|
OpenId = shop.openid;
|
|
ProductId = productId;
|
|
int fileSize = videoBytes.Length;
|
|
if (fileSize == 0)
|
|
{
|
|
throw new Exception("视频数据错误");
|
|
}
|
|
|
|
|
|
|
|
// taskId 贯穿整个上传流程的 X-Arguments,fileId 与之相同
|
|
string taskId = Guid.NewGuid().ToString();
|
|
Debug.WriteLine($"[0/6] GetUploadTempImgAuth");
|
|
TempImgAuthResp authResp = GetUploadTempImgAuth();
|
|
|
|
Debug.WriteLine($"[1/6] initupload fileSize={fileSize} taskId={taskId}");
|
|
InitUploadResp initResp = InitUpload(taskId, fileSize);
|
|
Debug.WriteLine($" -> fileName={initResp.FileName} uKey={initResp.UKey} videoId={initResp.VideoId}");
|
|
|
|
Debug.WriteLine("[2/6] uploadpart 分片上传...");
|
|
List<FinishPart> finishParts = UploadParts(taskId, videoBytes, initResp.FileName, initResp.UKey, fileSize);
|
|
Debug.WriteLine($" -> 共 {finishParts.Count} 片上传完成");
|
|
|
|
Debug.WriteLine("[3/6] finishupload 提交分片清单...");
|
|
FinishUpload(taskId, initResp.FileName, initResp.UKey, finishParts);
|
|
|
|
Debug.WriteLine("[4/6] transcode 触发转码...");
|
|
Transcode(taskId, initResp.FileName);
|
|
|
|
Debug.WriteLine("[5/6] getplayinfo 轮询转码结果...");
|
|
// 转码后的文件名规则: <videoId>.f100001.mp4
|
|
string transcodedFileName = $"{initResp.VideoId}.f100001.mp4";
|
|
string playUrl = PollPlayUrl(taskId, transcodedFileName);
|
|
Debug.WriteLine($" -> playUrl={playUrl}");
|
|
|
|
Debug.WriteLine("[6/6] getMultiMediaDownloadUrl 换取最终下载地址...");
|
|
MultiMediaDownloadUrlResp finalResult = GetMultiMediaDownloadUrl(playUrl);
|
|
finalResult.VideoDuration = GetMp4Duration(videoBytes).Seconds;
|
|
return finalResult;
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------
|
|
// getUploadTempImgAuth
|
|
// -------------------------------------------------------------------
|
|
private TempImgAuthResp GetUploadTempImgAuth(string uploadScene = "100")
|
|
{
|
|
string url = $"{Config.ShopHost}/shop-faas/mmecchannelspicture/cgi/getUploadTempImgAuth" +
|
|
$"?token=&lang=zh_CN&uploadScene={uploadScene}";
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, url);
|
|
req.Headers.TryAddWithoutValidation("Cookie", Cookie);
|
|
req.Headers.TryAddWithoutValidation("potter-scene", "weixinShop");
|
|
req.Headers.TryAddWithoutValidation("wecom_magic", "");
|
|
req.Headers.TryAddWithoutValidation("supplier_magic", "");
|
|
req.Headers.TryAddWithoutValidation("talent_magic", "");
|
|
req.Headers.TryAddWithoutValidation("logistics_magic", "");
|
|
req.Headers.TryAddWithoutValidation("mcn_magic", "");
|
|
req.Headers.TryAddWithoutValidation("biz_magic", biz_magic);
|
|
req.Headers.TryAddWithoutValidation("Accept", "application/json, text/plain, */*");
|
|
req.Headers.Referrer = new Uri($"{Config.ShopHost}/shop/goods/entry?productId={ProductId}");
|
|
|
|
using HttpResponseMessage resp = _http.SendAsync(req).GetAwaiter().GetResult();
|
|
string json = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
|
EnsureSuccessJson(resp, json);
|
|
|
|
var parsed = JsonConvert.DeserializeObject<TempImgAuthResp>(json, JsonOpts)!;
|
|
if (parsed.Code != 0)
|
|
throw new Exception($"getUploadTempImgAuth 失败: {json}");
|
|
|
|
return parsed;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------
|
|
// 1. initupload
|
|
// -------------------------------------------------------------------
|
|
private InitUploadResp InitUpload(string taskId, long fileSize)
|
|
{
|
|
var body = new
|
|
{
|
|
bizId = Config.BizId,
|
|
fileUpInfo = new
|
|
{
|
|
fileId = taskId,
|
|
fileType = "mp4",
|
|
fileSize
|
|
}
|
|
};
|
|
|
|
using var req = BuildVideoRequest(HttpMethod.Post, "/initupload", taskId);
|
|
req.Content = JsonContent(body);
|
|
|
|
using HttpResponseMessage resp = _http.SendAsync(req).GetAwaiter().GetResult();
|
|
string json = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
|
EnsureSuccessJson(resp, json);
|
|
|
|
var parsed = JsonConvert.DeserializeObject<InitUploadResp>(json, JsonOpts)!;
|
|
if (parsed.Code != 0)
|
|
throw new Exception($"initupload 失败: {json}");
|
|
return parsed;
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// 2. uploadpart(按 2MB 分片,partnum 从 1 开始)
|
|
// -------------------------------------------------------------------
|
|
private List<FinishPart> UploadParts(string taskId, byte[] videoBytes, string fileName, string uKey, long fileSize)
|
|
{
|
|
var finishParts = new List<FinishPart>();
|
|
|
|
using var fs = new MemoryStream(videoBytes);
|
|
byte[] buffer = new byte[Config.ChunkSize];
|
|
int partNum = 1;
|
|
long remaining = fileSize;
|
|
|
|
while (remaining > 0)
|
|
{
|
|
int toRead = (int)Math.Min(Config.ChunkSize, remaining);
|
|
int readTotal = 0;
|
|
while (readTotal < toRead)
|
|
{
|
|
int n = fs.Read(buffer, readTotal, toRead - readTotal);
|
|
if (n <= 0) throw new IOException("读取文件失败,文件可能被截断");
|
|
readTotal += n;
|
|
}
|
|
|
|
byte[] chunk = readTotal == buffer.Length ? buffer : buffer.Take(readTotal).ToArray();
|
|
|
|
string path = $"/uploadpart?filename={Uri.EscapeDataString(fileName)}&ukey={Uri.EscapeDataString(uKey)}&partnum={partNum}";
|
|
using var req = BuildVideoRequest(HttpMethod.Post, path, taskId);
|
|
req.Content = new ByteArrayContent(chunk);
|
|
req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); // 与抓包一致(实际发的是二进制)
|
|
|
|
using HttpResponseMessage resp = _http.SendAsync(req).GetAwaiter().GetResult();
|
|
string json = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
|
EnsureSuccessJson(resp, json);
|
|
|
|
var parsed = JsonConvert.DeserializeObject<UploadPartResp>(json, JsonOpts)!;
|
|
if (parsed.Code != 0)
|
|
throw new Exception($"uploadpart(part {partNum}) 失败: {json}");
|
|
|
|
Console.WriteLine($" part {partNum}: {readTotal} bytes, sha1(server)={parsed.PartSha}");
|
|
finishParts.Add(new FinishPart { PartNum = partNum, PartSha = parsed.PartSha });
|
|
|
|
remaining -= readTotal;
|
|
partNum++;
|
|
}
|
|
|
|
return finishParts;
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// 3. finishupload
|
|
// -------------------------------------------------------------------
|
|
private void FinishUpload(string taskId, string fileName, string uKey, List<FinishPart> finishParts)
|
|
{
|
|
var body = new
|
|
{
|
|
bizId = Config.BizId,
|
|
fileName,
|
|
uKey,
|
|
finishParts,
|
|
transInfo = new
|
|
{
|
|
snapshot = new[] { new { formatId = 1400 } }, // 封面截图
|
|
transcode = new[] { new { formatId = 100003 }, new { formatId = 100004 } }
|
|
}
|
|
};
|
|
|
|
using var req = BuildVideoRequest(HttpMethod.Post, "/finishupload", taskId);
|
|
req.Content = JsonContent(body);
|
|
|
|
using HttpResponseMessage resp = _http.SendAsync(req).GetAwaiter().GetResult();
|
|
string json = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
|
EnsureSuccessJson(resp, json);
|
|
|
|
var parsed = JsonConvert.DeserializeObject<CodeMsgResp>(json, JsonOpts)!;
|
|
if (parsed.Code != 0)
|
|
throw new Exception($"finishupload 失败: {json}");
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// 4. transcode(触发最终可播放版本 formatId=100001)
|
|
// -------------------------------------------------------------------
|
|
private void Transcode(string taskId, string fileName)
|
|
{
|
|
var body = new
|
|
{
|
|
fileName,
|
|
bizId = Config.BizId,
|
|
transInfo = new { transcode = new[] { new { formatId = 100001 } } },
|
|
use_new_transcode = 1
|
|
};
|
|
|
|
using var req = BuildVideoRequest(HttpMethod.Post, "/transcode", taskId);
|
|
req.Content = JsonContent(body);
|
|
|
|
using HttpResponseMessage resp = _http.SendAsync(req).GetAwaiter().GetResult();
|
|
string json = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
|
EnsureSuccessJson(resp, json);
|
|
|
|
var parsed = JsonConvert.DeserializeObject<CodeMsgResp>(json, JsonOpts)!;
|
|
if (parsed.Code != 0)
|
|
throw new Exception($"transcode 失败: {json}");
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// 5. getplayinfo 轮询(转码未完成时返回 code=404,完成后 code=0 带 playUrl)
|
|
// -------------------------------------------------------------------
|
|
private string PollPlayUrl(string taskId, string transcodedFileName)
|
|
{
|
|
for (int i = 0; i < Config.MaxPollTimes; i++)
|
|
{
|
|
string path = $"/getplayinfo?filename={Uri.EscapeDataString(transcodedFileName)}";
|
|
using var req = BuildVideoRequest(HttpMethod.Get, path, taskId);
|
|
|
|
using HttpResponseMessage resp = _http.SendAsync(req).GetAwaiter().GetResult();
|
|
string json = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
|
|
|
var parsed = JsonConvert.DeserializeObject<GetPlayInfoResp>(json, JsonOpts)!;
|
|
if (parsed.Code == 0 && parsed.FormatList != null && parsed.FormatList.Count > 0)
|
|
{
|
|
string? url = parsed.FormatList[0].PlayUrl;
|
|
if (!string.IsNullOrEmpty(url))
|
|
return url;
|
|
}
|
|
|
|
Console.WriteLine($" 转码中... ({i + 1}/{Config.MaxPollTimes}) code={parsed.Code}");
|
|
Thread.Sleep(Config.PollInterval);
|
|
}
|
|
|
|
throw new TimeoutException("等待转码超时,请增大 Config.MaxPollTimes 或检查视频云状态");
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// 6. store.weixin.qq.com getMultiMediaDownloadUrl
|
|
// -------------------------------------------------------------------
|
|
private MultiMediaDownloadUrlResp GetMultiMediaDownloadUrl(string playUrl)
|
|
{
|
|
var url = $"{Config.ShopHost}/shop-faas/mmecchannelspicture/cgi/getMultiMediaDownloadUrl?token=&lang=zh_CN";
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Post, url);
|
|
req.Headers.TryAddWithoutValidation("Cookie", Cookie);
|
|
req.Headers.TryAddWithoutValidation("potter-scene", "weixinShop");
|
|
// 抓包里这几个 magic 头是空字符串,但仍然带上
|
|
req.Headers.TryAddWithoutValidation("wecom_magic", "");
|
|
req.Headers.TryAddWithoutValidation("supplier_magic", "");
|
|
req.Headers.TryAddWithoutValidation("talent_magic", "");
|
|
req.Headers.TryAddWithoutValidation("logistics_magic", "");
|
|
req.Headers.TryAddWithoutValidation("mcn_magic", "");
|
|
req.Headers.TryAddWithoutValidation("biz_magic", biz_magic);
|
|
req.Headers.Referrer = new Uri($"{Config.ShopHost}/shop/goods/entry?productId={ProductId}");
|
|
req.Headers.TryAddWithoutValidation("Accept", "application/json, text/plain, */*");
|
|
|
|
req.Content = JsonContent(new { playUrl });
|
|
|
|
using HttpResponseMessage resp = _http.SendAsync(req).GetAwaiter().GetResult();
|
|
string json = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
|
EnsureSuccessJson(resp, json);
|
|
|
|
var parsed = JsonConvert.DeserializeObject<MultiMediaDownloadUrlResp>(json, JsonOpts)!;
|
|
if (parsed.Code != 0)
|
|
throw new Exception($"getMultiMediaDownloadUrl 失败: {json}");
|
|
return parsed;
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// 公共辅助方法
|
|
// -------------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// 构造 westoregamma.video.qq.com 上的请求,统一带上 X-Arguments 等公共头
|
|
/// </summary>
|
|
private HttpRequestMessage BuildVideoRequest(HttpMethod method, string pathAndQuery, string taskId)
|
|
{
|
|
var req = new HttpRequestMessage(method, Config.VideoHost + pathAndQuery);
|
|
|
|
string xArguments =
|
|
$"apptype={Config.AppType}&filetype={Config.FileType}&openid={OpenId}&groupid={ProductId}&taskid={taskId}";
|
|
|
|
req.Headers.TryAddWithoutValidation("X-Requested-With", "XMLHttpRequest");
|
|
req.Headers.TryAddWithoutValidation("X-Arguments", xArguments);
|
|
req.Headers.TryAddWithoutValidation("Accept", "application/json, text/plain, */*");
|
|
req.Headers.Referrer = new Uri($"{Config.ShopHost}/");
|
|
req.Headers.TryAddWithoutValidation("Origin", Config.ShopHost);
|
|
|
|
return req;
|
|
}
|
|
|
|
private static HttpContent JsonContent(object body)
|
|
{
|
|
string json = JsonConvert.SerializeObject(body);
|
|
return new StringContent(json, Encoding.UTF8, "application/json");
|
|
}
|
|
|
|
private static void EnsureSuccessJson(HttpResponseMessage resp, string json)
|
|
{
|
|
if (!resp.IsSuccessStatusCode)
|
|
throw new Exception($"HTTP {(int)resp.StatusCode} {resp.ReasonPhrase}: {json}");
|
|
}
|
|
|
|
|
|
#region 获取video时长
|
|
/// <summary>
|
|
/// 从 MP4 字节数组中读取视频时长
|
|
/// </summary>
|
|
/// <param name="videoBytes">完整的 MP4 文件字节数组</param>
|
|
/// <returns>视频时长</returns>
|
|
private static TimeSpan GetMp4Duration(byte[] videoBytes)
|
|
{
|
|
if (videoBytes == null || videoBytes.Length < 8)
|
|
throw new ArgumentException("无效的 MP4 数据", nameof(videoBytes));
|
|
|
|
int offset = 0;
|
|
|
|
while (offset + 8 <= videoBytes.Length)
|
|
{
|
|
ulong boxSize = ReadUInt32BE(videoBytes, offset);
|
|
string boxType = Encoding.ASCII.GetString(
|
|
videoBytes, offset + 4, 4);
|
|
|
|
int headerSize = 8;
|
|
|
|
// Extended Size
|
|
if (boxSize == 1)
|
|
{
|
|
if (offset + 16 > videoBytes.Length)
|
|
throw new InvalidOperationException("MP4 Box 数据不完整");
|
|
|
|
boxSize = ReadUInt64BE(videoBytes, offset + 8);
|
|
headerSize = 16;
|
|
}
|
|
// size == 0 表示一直到文件末尾
|
|
else if (boxSize == 0)
|
|
{
|
|
boxSize = (ulong)(videoBytes.Length - offset);
|
|
}
|
|
|
|
if (boxSize < (ulong)headerSize ||
|
|
boxSize > (ulong)(videoBytes.Length - offset))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"无效的 MP4 Box: " + boxType);
|
|
}
|
|
|
|
// 找到 moov
|
|
if (boxType == "moov")
|
|
{
|
|
int moovStart = offset + headerSize;
|
|
int moovLength = checked((int)boxSize - headerSize);
|
|
|
|
return ParseMoov(
|
|
videoBytes,
|
|
moovStart,
|
|
moovLength);
|
|
}
|
|
|
|
offset += checked((int)boxSize);
|
|
}
|
|
|
|
throw new InvalidOperationException(
|
|
"MP4 中没有找到 moov Box");
|
|
}
|
|
|
|
private static TimeSpan ParseMoov(
|
|
byte[] data,
|
|
int start,
|
|
int length)
|
|
{
|
|
int offset = start;
|
|
int end = start + length;
|
|
|
|
while (offset + 8 <= end)
|
|
{
|
|
ulong boxSize = ReadUInt32BE(data, offset);
|
|
|
|
string boxType = Encoding.ASCII.GetString(
|
|
data,
|
|
offset + 4,
|
|
4);
|
|
|
|
int headerSize = 8;
|
|
|
|
if (boxSize == 1)
|
|
{
|
|
if (offset + 16 > end)
|
|
throw new InvalidOperationException(
|
|
"MP4 Box 数据不完整");
|
|
|
|
boxSize = ReadUInt64BE(data, offset + 8);
|
|
headerSize = 16;
|
|
}
|
|
else if (boxSize == 0)
|
|
{
|
|
boxSize = (ulong)(end - offset);
|
|
}
|
|
|
|
if (boxSize < (ulong)headerSize ||
|
|
boxSize > (ulong)(end - offset))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"无效的 MP4 Box: " + boxType);
|
|
}
|
|
|
|
// moov -> mvhd
|
|
if (boxType == "mvhd")
|
|
{
|
|
return ParseMvhd(
|
|
data,
|
|
offset + headerSize,
|
|
checked((int)boxSize - headerSize));
|
|
}
|
|
|
|
offset += checked((int)boxSize);
|
|
}
|
|
|
|
throw new InvalidOperationException(
|
|
"MP4 中没有找到 mvhd Box");
|
|
}
|
|
|
|
private static TimeSpan ParseMvhd(
|
|
byte[] data,
|
|
int start,
|
|
int length)
|
|
{
|
|
if (length < 20)
|
|
throw new InvalidOperationException(
|
|
"mvhd 数据不完整");
|
|
|
|
// version
|
|
byte version = data[start];
|
|
|
|
// Version 0
|
|
if (version == 0)
|
|
{
|
|
// version + flags 4
|
|
// creation_time 4
|
|
// modification_time 4
|
|
// timescale 4
|
|
// duration 4
|
|
|
|
uint timescale = ReadUInt32BE(
|
|
data,
|
|
start + 12);
|
|
|
|
uint duration = ReadUInt32BE(
|
|
data,
|
|
start + 16);
|
|
|
|
if (timescale == 0)
|
|
throw new InvalidOperationException(
|
|
"MP4 timescale 为 0");
|
|
|
|
return TimeSpan.FromSeconds(
|
|
(double)duration / timescale);
|
|
}
|
|
|
|
// Version 1
|
|
if (version == 1)
|
|
{
|
|
if (length < 32)
|
|
throw new InvalidOperationException(
|
|
"mvhd version 1 数据不完整");
|
|
|
|
// version + flags 4
|
|
// creation_time 8
|
|
// modification_time 8
|
|
// timescale 4
|
|
// duration 8
|
|
|
|
uint timescale = ReadUInt32BE(
|
|
data,
|
|
start + 20);
|
|
|
|
ulong duration = ReadUInt64BE(
|
|
data,
|
|
start + 24);
|
|
|
|
if (timescale == 0)
|
|
throw new InvalidOperationException(
|
|
"MP4 timescale 为 0");
|
|
|
|
return TimeSpan.FromSeconds(
|
|
(double)duration / timescale);
|
|
}
|
|
|
|
throw new NotSupportedException(
|
|
"不支持的 mvhd version: " + version);
|
|
}
|
|
|
|
private static uint ReadUInt32BE(
|
|
byte[] data,
|
|
int offset)
|
|
{
|
|
return (uint)(
|
|
(data[offset] << 24) |
|
|
(data[offset + 1] << 16) |
|
|
(data[offset + 2] << 8) |
|
|
data[offset + 3]);
|
|
}
|
|
|
|
private static ulong ReadUInt64BE(
|
|
byte[] data,
|
|
int offset)
|
|
{
|
|
uint high = ReadUInt32BE(data, offset);
|
|
uint low = ReadUInt32BE(data, offset + 4);
|
|
|
|
return ((ulong)high << 32) | low;
|
|
}
|
|
#endregion
|
|
|
|
|
|
private static readonly JsonSerializerSettings JsonOpts = new JsonSerializerSettings
|
|
{
|
|
|
|
};
|
|
}
|
|
|
|
|
|
internal class TempImgAuthResp
|
|
{
|
|
[JsonProperty("code")] public int Code { get; set; }
|
|
[JsonProperty("disK")] public string DisK { get; set; } = "";
|
|
[JsonProperty("disT")] public long DisT { get; set; }
|
|
[JsonProperty("ext")] public string Ext { get; set; } = "";
|
|
[JsonProperty("url")] public string Url { get; set; } = "";
|
|
}
|
|
internal class InitUploadResp
|
|
{
|
|
[JsonProperty("code")] public int Code { get; set; }
|
|
[JsonProperty("fileName")] public string FileName { get; set; } = "";
|
|
[JsonProperty("uKey")] public string UKey { get; set; } = "";
|
|
[JsonProperty("videoId")] public string VideoId { get; set; } = "";
|
|
[JsonProperty("msg")] public string Msg { get; set; }
|
|
}
|
|
|
|
internal class UploadPartResp
|
|
{
|
|
[JsonProperty("code")] public int Code { get; set; }
|
|
[JsonProperty("msg")] public string Msg { get; set; }
|
|
[JsonProperty("partSha")] public string PartSha { get; set; } = "";
|
|
}
|
|
|
|
internal class FinishPart
|
|
{
|
|
[JsonProperty("partNum")] public int PartNum { get; set; }
|
|
[JsonProperty("partSha")] public string PartSha { get; set; } = "";
|
|
}
|
|
|
|
internal class CodeMsgResp
|
|
{
|
|
[JsonProperty("code")] public int Code { get; set; }
|
|
[JsonProperty("msg")] public string Msg { get; set; }
|
|
}
|
|
|
|
internal class GetPlayInfoResp
|
|
{
|
|
[JsonProperty("code")] public int Code { get; set; }
|
|
[JsonProperty("msg")] public string Msg { get; set; }
|
|
[JsonProperty("formatList")] public List<PlayFormat>? FormatList { get; set; }
|
|
}
|
|
|
|
internal class PlayFormat
|
|
{
|
|
[JsonProperty("formatId")] public int FormatId { get; set; }
|
|
[JsonProperty("playUrl")] public string PlayUrl { get; set; }
|
|
[JsonProperty("duration")] public double Duration { get; set; }
|
|
[JsonProperty("width")] public int Width { get; set; }
|
|
[JsonProperty("height")] public int Height { get; set; }
|
|
}
|
|
|
|
public class MultiMediaDownloadUrlResp
|
|
{
|
|
[JsonProperty("code")] public int Code { get; set; }
|
|
[JsonProperty("videoImgUrl")] public string VideoImgUrl { get; set; }
|
|
[JsonProperty("downloadUrl")] public string DownloadUrl { get; set; }
|
|
/// <summary>
|
|
/// 时长
|
|
/// </summary>
|
|
public int VideoDuration { get; set; }
|
|
}
|
|
}
|