微信上货 增加视频上传
This commit is contained in:
1 parent
4d38059e72
commit
3611d9a8e9
3 files changed
+8186
-7468
No files matched your search
@@ -0,0 +1,671 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -63,7 +63,7 @@
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
@@ -73,7 +73,7 @@
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
@@ -155,6 +155,7 @@
|
||||
<Compile Include="BLL\VDian\VDianUpProdMappingBLL.cs" />
|
||||
<Compile Include="BLL\VDian\VDianProductBLL.cs" />
|
||||
<Compile Include="BLL\WxApiBLL.cs" />
|
||||
<Compile Include="BLL\WXVideoUploader.cs" />
|
||||
<Compile Include="BLL\Wx\WxPlatformRecommendProdManage.cs" />
|
||||
<Compile Include="BLL\YkdHttpResponseBLL.cs" />
|
||||
<Compile Include="BLL\WXHttpResponseBLL.cs" />
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user