c#操作CCtalk视频下载
Posted 哲虎
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c#操作CCtalk视频下载相关的知识,希望对你有一定的参考价值。
郑重声明: 本人已购买该付费课程, 必须提前购买课程才可以下载。不是广告!
写这篇文章的目的是为了方便在本地播放和保存视频.
流程
- 获取播放列表
- 通过播放列表获取视频地址并生成Aria2c批量下载URL(指定文件名)
- 使用Aria2c进行下载视频
开始分析
打开DevTools,进入已购买的课程页面,点击开始学习
按钮,在DevTools筛选器上搜索all_lesson_list
此时获取到了一个URL,这个连接是所有课程数据列表,https://www.cctalk.com/webapi/content/v1.2/series/all_lesson_list?seriesId=1648563754438470&limit=&showStudyTime=false&_timestamp=1663807475982
seriesId=1648563754438470是课程ID在下面URL中使用。
继续通过分析从该链接https://www.cctalk.com/webapi/content/v1.1/series/1648563754438470/get_content_unit_struts
获取json数据,该数据中contentIdList
就是所有课程视频ID => videoId
"data":
"seriesId": 1648563754438470,
"showType": 0,
"totalCount": 83,
"unitList": [
"contentIdList": [
16486908151117,
......
],
"unitId": 0
]
,
"message": "success",
"status": 0,
"time": "2022-09-22T09:24:25+0800"
通过上面的数据获取到了必要参数后,现在需要获取单个视频的json数据,经过分析发现通过该URLhttps://www.cctalk.com/webapi/content/v1.1/video/detail?videoId=16486908151117&seriesId=1648563754438470
可以获取到视频信息。
- videoId=16486908151117 视频ID
- seriesId=1648563754438470 课程ID
这2个参数是从上面步骤获取到的.
返回数据如下,在这里核心数据为videoUrl
这个是视频URL,通过访问该URL可以在线播放,也可以下载视频.同时也需要videoName
它是视频标题,使用它在下面下载视频保存文件时指定文件名.
"data":
"adEnable": true,
"collectInfo":
"collectCount": 2,
"collectStatus": 0,
"collectionContentId": 5096,
"contentId": 16486908151117,
"contentType": 1
,
"companyId": 0,
"coursewareInfo":
"coursewareId": "1509184337896175193",
"ocsStatus": 0,
"tenantId": 10002,
"userSign": "K0JPWgQeSLvrlK551YiXHo3Xuq/TnKdqQ3sTnwo4LVUXcIN4HGEBPvvXtvGKsjPN1LVV+290fyfn2MGBdIu3jrlJxZ9f5JZeTmFbAPRSQ62rIgTK0hqizoUr+jyx+ne77tLjdTNQQd4ISF7XpJ+lsspshn6gu9dIB0T634KPiBEPMheGNHXq6xkEv3dE3aqtXTbPvzWGk27ezVG5qHpAlu8eTdIsAhepVtc5uF4fsPYopKlbXujRYRlg1NVUmdPXHOX3/gg8yFzpxDEb2u/pC5XFkYYJ5ztdBGtxA7r1OoSmMg/b2JaOenmkrd/Cze2PxBEWKt7PM2AUphUMwm0re6EeAf2aw5TlFksl6+WkCcxkSoUtVtw6ValjGo5AxWieNc5itJpA8OQ8TenBiBhikvrQiToJBh5Aw1QvfhgAVht7LcOUDOhFd2wAEJRoZMuPkGOAbixXVhV/5i9+80lg/3WXrRuCi8lDkn1W+NPsCVvDh0P8WKX3teCgfBXYR9Gl"
,
"coverUrl": "https://cc.hjfile.cn/cc/img/20220330/2022033011024002972564.jpg",
"createUserId": xxxxxx,
"enableCache": 1,
"feedbackInfo":
"haveFeedback": 0
,
"groupId": 90268531,
"groupIsActive": 1,
"groupName": "vol框架开发实战讲解.NetCore vue uni-app",
"hideRating": false,
"hideReserveButton": false,
"intro":
"section": [
"contentStruct": ,
"contents": []
]
,
"isCharge": 1,
"isGroupMember": true,
"isOpenProtection": false,
"isReserve": true,
"isTrial": false,
"knowledgeList": [],
"likeCount": 2,
"likeStatus": 0,
"liveNum": 0,
"liveStartDate": "2022-03-31T09:40:15+0800",
"liveStatus": 11,
"logo": "vol",
"logoShowStatus": 0,
"mediaTotalTime": 411000,
"openType": 1,
"pagePermission": 1,
"playCount": 1881,
"playPermission": 1,
"ratingCount": 6,
"reserveCount": 0,
"reviewStatus": 21,
"seriesInfo":
"coverUrl": "https://cc.hjfile.cn/cc/img/20220329/2022032910204179033032.jpg",
"hasSubscribe": true,
"seriesId": 1648563754438470,
"seriesName": "vol框架视频",
"subscribeCount": 336,
"videoCount": 83
,
"showRecommend": false,
"starAvg": 5.0,
"studyPosition": 0,
"taskInfo": ,
"totalSize": 40894464,
"trialDuration": -1,
"userCard":
"avatar": "https://i2n.hjfile.cn/u/default/ext/200/5.jpg",
"followedMeCount": 366,
"nickName": "vol",
"relationStatus": 1,
"summary": "多年.net、vue前后端分离开发经验.",
"userId": xxxxxx,
"userName": "QQ用户xxxx"
,
"videoCut": 0,
"videoEndDate": "2022-03-31T09:47:06+0800",
"videoId": 16486908151117,
"videoName": "框架介绍",
"videoStartDate": "2022-03-31T09:40:15+0800",
"videoType": 0,
"videoUrl": "https://record-manual.cctalk.com/5285d79daf63dd321eb89d912a9236d5.mp4?auth_key=1663823958-58b24996728341e69d9ec573d356f669-0-0606fbe337ddd193046cff6ffc121ee9"
,
"message": "success",
"status": 0
到目前为止已经完成了基本的分析,下面开始写代码
c#代码
在下面代码中需要填写2个参数,第一个是填写自己的COOKIE,使用自己的账号登录后,从浏览器中复制cookie,第二个是课程ID,await Getcourse("1648563754438470");
这里传递的参数是课程ID
使用nuget
添加以下2个扩展,同时本地需要安装redis数据库,如果没有可以改代码,使用SortedList
代替redis
- StackExchange.Redis
- Newtonsoft.Json
Program.cs
// See https://aka.ms/new-console-template for more information
using StackExchange.Redis;
using System.Net.Http.Headers;
using System.Net;
using Newtonsoft.Json;
using System;
using Newtonsoft.Json.Converters;
using System.Globalization;
using System;
using System.Collections.Generic;
using static CCtalk.Program;
namespace CCtalk
class Program
public static string global_cookies = "填写自己的COOKIE";//使用自己的账号登录后,从浏览器中复制cookie
//连接redis服务器
public static ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost");
//获取redis数据库
public static IDatabase db = redis.GetDatabase();
static async Task Main(string[] args)
//获取课程所有视频ID
await Getcourse("1648563754438470");
//获取最终视频播放地址并把地址写入txt文件,最后使用aria2c工具进行批量下载
await Getcontent();
/// <summary>
/// 获取最终视频播放地址并把地址写入txt文件,最后使用aria2c工具进行批量下载
/// </summary>
/// <returns></returns>
public static async Task Getcontent()
//获取桌面路径
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
//在桌面生成cctalk.txt文件
using (FileStream fs = new FileStream(desktopPath + @"\\cctalk.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite))
using (StreamWriter sw = new StreamWriter(fs))
//获取redis数据
var full_redis_json_data = await db.HashGetAllAsync("cctalk_contentIdLists");
//遍历数据
foreach (var item in full_redis_json_data)
//获取单个视频JSON数据,提取VideoName和videoUrl
var result = await Browser(item.Value.ToString());
//解析json数据
var contents = Contents.FromJson(result);
//把数据保存到redis数据库,进行备份操作,方便后续使用,也可以使用自己编写代码进行下载视频.
// key为1. 框架介绍.mp4,value为最终视频URL地址
await db.HashSetAsync("cctalk_VideoLists", item.Name.ToString() + "." + contents.Data.VideoName.ToString() + ".mp4", contents.Data.VideoUrl.ToString());
//把数据写入txt文件,输入格式为Aria2c下载格式并指定文件名
Task task = sw.WriteLineAsync(contents.Data.VideoUrl.ToString() + "\\r\\n" + " out=" + item.Name.ToString() + "." + contents.Data.VideoName.ToString() + ".mp4");
/// <summary>
/// 获取课程所有视频ID
/// </summary>
/// <param name="seriesId">课程ID</param>
/// <returns></returns>
public static async Task Getcourse(string seriesId)
var kechengURL = "https://www.cctalk.com/webapi/content/v1.1/series/" + seriesId.ToString() + "/get_content_unit_struts";
string url_begin = "https://www.cctalk.com/webapi/content/v1.1/video/detail?videoId=";
string url_end = "&seriesId="+ seriesId.ToString()+ "&_timestamp=1663738877186";
//获取JSON数据
var result = await Browser(kechengURL);
//解析JSON数据
var course = Course.FromJson(result);
var i = 1;
//遍历course,获取所有视频ID
foreach (var item in course.Data.UnitList[0].ContentIdList)
//拼接完整单个视频访问URL地址
var final_url = url_begin + item.ToString() + url_end;
//将视频ID和完整URL存入redis数据库, key为i,value为完整URL地址
var redis_hastset = await db.HashSetAsync("cctalk_contentIdLists", i.ToString(), final_url.ToString());
i += 1;
public partial class Course
[JsonProperty("data")]
public Data Data get; set;
[JsonProperty("message")]
public string Message get; set;
[JsonProperty("status")]
public long Status get; set;
[JsonProperty("time")]
public string Time get; set;
public partial class Data
[JsonProperty("seriesId")]
public long SeriesId get; set;
[JsonProperty("showType")]
public long ShowType get; set;
[JsonProperty("totalCount")]
public long TotalCount get; set;
[JsonProperty("unitList")]
public UnitList[] UnitList get; set;
public partial class UnitList
[JsonProperty("contentIdList")]
public long[] ContentIdList get; set;
[JsonProperty("unitId")]
public long UnitId get; set;
public partial class Course
public static Course FromJson(string json) => JsonConvert.DeserializeObject<Course>(json, Converter.Settings);
public partial class Contents
[JsonProperty("data")]
public Data Data get; set;
[JsonProperty("message")]
public string Message get; set;
[JsonProperty("status")]
public long Status get; set;
public partial class Data
[JsonProperty("adEnable")]
public bool AdEnable get; set;
[JsonProperty("collectInfo")]
public CollectInfo CollectInfo get; set;
[JsonProperty("companyId")]
public long CompanyId get; set;
[JsonProperty("coursewareInfo")]
public CoursewareInfo CoursewareInfo get; set;
[JsonProperty("coverUrl")]
public Uri CoverUrl get; set;
[JsonProperty("createUserId")]
public long CreateUserId get; set;
[JsonProperty("enableCache")]
public long EnableCache get; set;
[JsonProperty("feedbackInfo")]
public FeedbackInfo FeedbackInfo get; set;
[JsonProperty("groupId")]
public long GroupId get; set;
[JsonProperty("groupIsActive")]
public long GroupIsActive get; set;
[JsonProperty("groupName")]
public string GroupName get; set;
[JsonProperty("hideRating")]
public bool HideRating get; set;
[JsonProperty("hideReserveButton")]
public bool HideReserveButton get; set;
[JsonProperty("intro")]
public Intro Intro get; set;
[JsonProperty("isCharge")]
public long IsCharge get; set;
[带有联合和数组的 C# pinvoke 结构
【中文标题】带有联合和数组的 C# pinvoke 结构【英文标题】:C# pinvoke structs with union and arrays
【发布时间】:2013-11-28 20:28:27
【问题描述】:
我无法定义正确的 C# 代码以使用 C++ 库,该库定义了一个具有联合和数组的复杂结构,我在执行 C++ 代码时不断收到一些内存异常,我很确定这是由于这个原因。
c++ 结构如下,忽略枚举和其他结构定义(如果需要我会发布它们,但它们非常丰富)
typedef struct
DG_CCTALK_APP_EVT_CODE eEventCode;
DG_CCTALK_APP_EVT_TYPE eEventType;
int iTime;
int iHandle;
unsigned char uchAddress;
DG_CCTALK_BILL_INFO sBillInfo;
int iBillAmount;
union
long lData;
int iData;
unsigned char ucArray[128];
char *cString;
void *pvoid;
uData;
void *_private; // for use by cctalk app layer
DG_CCTALK_APP_EVT, *PDG_CCTALK_APP_EVT;
C# 代码是这样的:
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Explicit)]
public struct Anonymous_92d21a81_2a8b_42f4_af32_f7606aa9cf40
//deberian llevar todos 0, pero el array genera problemas al ponerle 0, y cambiandolo explota en otro lado...
/// int
[System.Runtime.InteropServices.FieldOffsetAttribute(0)]
public int lData;
/// int
[System.Runtime.InteropServices.FieldOffsetAttribute(0)]
public int iData;
/// unsigned char[128]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 128, ArraySubType = System.Runtime.InteropServices.UnmanagedType.I1)]
[System.Runtime.InteropServices.FieldOffsetAttribute(4)]
public byte[] ucArray;
/// char*
[System.Runtime.InteropServices.FieldOffsetAttribute(0)]
public System.IntPtr cString;
/// void*
[System.Runtime.InteropServices.FieldOffsetAttribute(0)]
public System.IntPtr pvoid;
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct DG_CCTALK_APP_EVT
/// DG_CCTALK_APP_EVT_CODE->_DG_CCTALK_APP_EVT_CODE
public DG_CCTALK_APP_EVT_CODE eEventCode;
/// DG_CCTALK_APP_EVT_TYPE->_DG_CCTALK_APP_EVT_TYPE
public DG_CCTALK_APP_EVT_TYPE eEventType;
/// int
public int iTime;
/// int
public int iHandle;
/// unsigned char
public byte uchAddress;
/// DG_CCTALK_BILL_INFO->_DG_CCTALK_BILL_INFO
public DG_CCTALK_BILL_INFO sBillInfo;
/// int
public int iBillAmount;
/// Anonymous_92d21a81_2a8b_42f4_af32_f7606aa9cf40
public Anonymous_92d21a81_2a8b_42f4_af32_f7606aa9cf40 uData;
/// void*
public System.IntPtr _private;
委托和函数导入
public delegate void DG_CCTALK_APP_EVT_HANDLER(ref DG_CCTALK_APP_EVT pEVT);
[System.Runtime.InteropServices.DllImportAttribute("cctalk.dll", EntryPoint = "cctalk_app_enable_device", CallingConvention = CallingConvention.Cdecl)]
public static extern int cctalk_app_enable_device(ref DG_CCTALK_APP_EVT param0);
更新:asnwer 之后的新代码,仍然无法正常工作:
错误是:“尝试读取或写入受保护的内存这通常表明其他内存已损坏”
奇怪的是,我可以在第一次事件调用时使用该结构,我打印所有字段并且它们似乎是正确的(lData 和 iData 具有相同的值,cData 有一个我发送的字符串)但是在事件之后结束程序死亡。好像我的 C# 代码破坏了结构或其他东西......
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
public struct DG_CCTALK_APP_EVT
/// DG_CCTALK_APP_EVT_CODE->_DG_CCTALK_APP_EVT_CODE
public DG_CCTALK_APP_EVT_CODE eEventCode;
/// DG_CCTALK_APP_EVT_TYPE->_DG_CCTALK_APP_EVT_TYPE
public DG_CCTALK_APP_EVT_TYPE eEventType;
/// int
public int iTime;
/// int
public int iHandle;
/// unsigned char
public byte uchAddress;
/// DG_CCTALK_BILL_INFO->_DG_CCTALK_BILL_INFO
public DG_CCTALK_BILL_INFO sBillInfo;
/// int
public int iBillAmount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
public byte[] uData;
#region setters y getters para mapeo
public int lData
set
Array.Copy(BitConverter.GetBytes(value), uData, sizeof(int));
get
return BitConverter.ToInt32(uData, 0);
public int iData
set
Array.Copy(BitConverter.GetBytes(value), uData, sizeof(int));
get
return BitConverter.ToInt32(uData, 0);
public byte[] ucArray
set
Array.Copy(value, uData, 128);
get
return uData;
public IntPtr cString
set
Array.Copy(BitConverter.GetBytes(value.ToInt32()), uData, 32);
get
return (IntPtr)BitConverter.ToInt32(uData, 0);
public IntPtr pvoid
set
Array.Copy(BitConverter.GetBytes(value.ToInt32()), uData, 32);
get
return (IntPtr)BitConverter.ToInt32(uData, 0);
#endregion
/// void*
public System.IntPtr _private;
C++ 声明:
typedef void (*DG_CCTALK_APP_EVT_HANDLER)(PDG_CCTALK_APP_EVT pEVT);
DGCCTALK_PREFIX DG_ERROR cctalk_app_init(DG_CCTALK_APP_EVT_HANDLER pfnHandler);
DGCCTALK_PREFIX DG_ERROR cctalk_app_add_link(char *szPortName);
注意爸爸DGCCTALK_PREFIX被定义为__declspec(dllexport)
C# 代码:
public delegate void DG_CCTALK_APP_EVT_HANDLER(ref DG_CCTALK_APP_EVT pEVT);
[System.Runtime.InteropServices.DllImportAttribute("cctalk.dll", EntryPoint = "cctalk_app_init", CallingConvention = CallingConvention.Cdecl)]
public static extern int cctalk_app_init(DG_CCTALK_APP_EVT_HANDLER pfnHandler);
[System.Runtime.InteropServices.DllImportAttribute("cctalk.dll", EntryPoint = "cctalk_app_add_link", CallingConvention = CallingConvention.Cdecl)]
public static extern int cctalk_app_add_link(System.IntPtr szPortName);
调用它们的 C# 代码
var handler = new DG_CCTALK_APP_EVT_HANDLER(this._handler);
var resultado = cctalk_app_init(handler);
cctalk_app_add_link(Marshal.StringToHGlobalAnsi("port=" + port));
C# 处理程序头
private void _handler(ref DG_CCTALK_APP_EVT pEVT)
【问题讨论】:
为什么使用 4 的偏移量?错误是什么?怎么调用函数?
原来是0,但是会导致无效aling异常。我这里没有错误,但它是关于访问程序没有读/写权限的内存。
另外我没有显式调用这个结构的方法,它实际上是一个从 c++ 库中触发的事件,它向我发送这个结构(这里没有错误),但是在事件完成后,作为 c++代码再次运行,它中断了。
所以不要费心定义联合。并使用 Marshal 类来挑选字段。仅将 0 更改为 4 不是答案。
不接收 ref 的结构。接收一个 IntPtr。然后手动挑选你需要的东西。
【参考方案1】:
正如您所发现的,封送拆收器不喜欢您在翻译联合时尝试将数组与其他字段重叠。您通过将数组的偏移量更改为 4 而不是 0 来抑制问题。但这无济于事。
现在,由于 marshaler 不会为您处理联合,因此您必须手动完成。我会通过省略联合的非数组成员来处理这个问题。
[StructLayout(LayoutKind.Sequential)]
public struct DG_CCTALK_APP_EVT
public DG_CCTALK_APP_EVT_CODE eEventCode;
public DG_CCTALK_APP_EVT_TYPE eEventType;
public int iTime;
public int iHandle;
public byte uchAddress;
public DG_CCTALK_BILL_INFO sBillInfo;
public int iBillAmount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
public byte[] uData;
public IntPtr _private;
请注意,我已经删除了您声明的大部分冗长。我怀疑该类型是由工具自动生成的。但是所有这些冗长使得它很难阅读。
这将为结构生成正确的布局,但会让您有工作要做以访问联合中的其他字段。
那么,我们如何读取这些字段?好吧,数据包含在字节数组uData
中,所以只需从那里读取值。例如,您可以将以下属性添加到 DG_CCTALK_APP_EVT
结构:
public int lData
get return BitConverter.ToInt32(uData, 0);
set uData = BitConverter.GetBytes(value);
public int iData
get return BitConverter.ToInt32(uData, 0);
set uData = BitConverter.GetBytes(value);
// etc.
请注意,setter 将清除数组中除前 4 个字节之外的所有字节,因为它会覆盖 uData
。我对本机代码的协议知之甚少,无法确定这是您想要的。如果这不是您想要的,那么您可以这样编写 setter:
Array.Copy(BitConverter.GetBytes(value), uData, sizeof(int));
您现在已经添加了互操作边界的 C++ 端。最明显的问题是您的 C# 委托似乎有错误的调用约定。它使用stdcall
,但C++ 代码需要cdecl
。这当然足以解释回调返回后发生的灾难性崩溃。您需要确保您的委托指定调用约定。
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DG_CCTALK_APP_EVT_HANDLER(ref DG_CCTALK_APP_EVT pEVT);
您对cctalk_app_add_link
的翻译也是错误的。应该是:
[DllImport("cctalk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int cctalk_app_add_link(string szPortName);
这样做,您可以简单地传递一个字符串,从而避免您当前实现的内存泄漏。
【讨论】:
您将 SizeConst 变量设置为 128,但这只会保存 uData union 中定义的数组,那么其他字段呢?它实际上不应该更大吗? lData 4 个字节(或 8 个字节,因为它很长?)| iData 4 个字节 ucArray 128 个字节 cString 4 个字节(如果在 64 位机器上工作,则为 8 个,因为它是一个指针) pvoid 4 个字节(同上)
我已经更新了我的问题,用你的答案,但它仍然不起作用,我真的迷路了。
不,工会覆盖成员。因此,您的工具生成的所有成员的 FieldOffset 为 0。我不太确定这里的“不起作用”是什么意思。你没有照我说的去做。您的代码有 144 而不是 128。
所有的 getter 和 setter 都需要在数组的开头工作。你应该再读一遍到底什么是联合。
IntPtr 怎么样?我该如何管理它们?我的演员表是否正确(假设我只在 32 位机器上工作)【参考方案2】:
检查您的 int 和 long 字段。
我认为 C int 是 16 位,而 C# int 是 32:
如果是这样,那么 iTime、iHandle 和 iBilAmount 在 C# 和 C 定义中都被定义为 int。
如果我错了,那么 lData 在 C 中定义为 long 而在 C# 中定义为 int(如果这是问题所在,那么不要忘记修复出现在 lData 之后)。
【讨论】:
不。在 Windows 上,C++ int 是 32 位
这是一个工会!偏移量均为 0。无需固定。在 Windows 上的 C++ 中,int 和 long 是 4 个字节宽。
对不起,你是对的。错过了128长的数组,没看出他用的是windows。以上是关于c#操作CCtalk视频下载的主要内容,如果未能解决你的问题,请参考以下文章
iKcamp|基于Koa2搭建Node.js实战(含视频)? 错误处理