國家中小學智慧教育平臺視頻課程下載

工具下載

鏈接:https://pan.baidu.com/s/1zCW4wyuzkjnAfQz3zeisBQ
提取碼:1234

前言

最近家裏有一臺10年前的老電視機,因爲不能連接無線網絡。
家裏也沒有用有線電視機頂盒了。只能播放一些本地視頻了。
再加上小孩子現在正在放暑假,所以想搞些學習視頻給他看一下。
正好有國家中小學智慧教育平臺,裏面的同步課程視頻都是可以免費看的。
所以我打算把他全部下載到U盤裏在電視機上播放。

開始

一個學期一門課程的同步課程視頻大約有50~80個視頻,如果一個個下載的話。沒個半天是搞不完的。
但是一個學期不只有一門課,就算只下語文數學的話,那一天時間就全完在這上面了。
這到還不是重點,重點是一天都在做重複的勞動
打開一個課程,然後找到視頻下載地址,然後用下載工具把視頻下載下來。
重複這個動作大約百來次,每次要等個幾分鐘下載。
做爲一個程序員的我,這個是接受不了的。
所以我就打算寫個程序,可以批量把所有視頻批量下載。

分析視頻下載地址

打開第一個課程,我們發現有個請求地址爲
https://s-file-1.ykt.cbern.com.cn/zxx/s_course/v1/x_class_hour_activity/89306b9d-db26-4db5-9f39-54865d43314b/resources.json
返回的內容爲視頻的下載地址

一看就知道89306b9d-db26-4db5-9f39-54865d43314b這個Id爲此課程的Id
如果我要批量下載的話。那麼我就要找到當前學科的所有課程Id
這樣的話,我就能找到所有的視頻地址
於是,我又在網絡請求裏找到了一個這樣的地址
https://s-file-1.ykt.cbern.com.cn/zxx/s_course/v2/activity_sets/75f96245-8136-457e-b8dd-626fe960d131/fulls.json
這個地址返回的是當前課程的所有信息,所以通過這個就可以找到一門課程的所有視頻Id了

當然找到這裏,就能完成我所有的需求了。
我只要找到這個課程的目錄把對應的課程信息fulls.json拿到
就可以找到所有視頻的Id,然後通過每個視頻Id去請求對應視頻的resources.json
就可以拿到所有的視頻地址了。

下載視頻

我們通過分析下載的視頻地址爲
https://r3-ndr.ykt.cbern.com.cn/edu_product/65/video/17b72ffb547a11eb96b8fa20200c3759/5bf35e06793611b4383bdf3bf9b550ea.1280.720.false/5bf35e06793611b4383bdf3bf9b550ea.1280.720.m3u8
像這樣的m3u8格式地址。這樣的地址並不能下載爲完整的mp4格式視頻
因爲m3u8是視頻的分片格式,把一個視頻文件分片了很多小片段,
片段內容記錄到了m3u8文件裏,所以我們要把m3u8裏的每一個分片都下載下來,
然後再合成一個mp4文件
所以我們就請出了在GitHub上最火的m3u8視頻下載器
https://github.com/nilaoda/N_m3u8DL-CLI
通過這個工具只要傳一個m3u8地址,他就會自動幫你下載所有分片
最後合成一個mp4視頻。

One More Time

都已經做到了可以拿到一門課程就可以把課程的所有視頻批量下載
那進一步是不是可以把所有課程信息都拿下來,是不是就可以下載全部視頻了。
所以通過分析,我們又發現了一個牛B接口
https://s-file-1.ykt.cbern.com.cn/zxx/api_static/tag_views/trees/sync-course.json
通過這個接口,我們可以知道所有學段、年級、學科、版本、冊次信息
這樣我們就可以知道所有課程了。

結束

最後根據以上信息,花一天天時間,就完成了國家中小學智慧教育平臺視頻所有課程批量下載工具
第一步選擇要下載的課程

選擇好之後,工具會自動幫你分析出所有視頻的下載的址
並按對應的學科、單元、課程創建對應的視頻目錄與文件

這樣所有的視頻都下載完成後
就可以保存到U盤,放到我的老電視機上播放了。

static async Task Main(string[] args)
    {
        //var courseUrl = "https://s-file-1.ykt.cbern.com.cn/zxx/api_static/tag_views/trees/sync-course.json";
        var httpClient = new HttpClient();

        var tagJson = await File.ReadAllTextAsync("tag.json");
        var tags = JsonSerializer.Deserialize<tag_view>(tagJson);
        Console.WriteLine(tags.tag_view_name);
        var group = tags.tag_tree.FirstOrDefault();
        var selectedTags = new List<string>();
        do
        {
            Console.WriteLine($"請選擇{group.tag_group_name}");
            for (int i = 0; i < group.tags.Count; i++)
            {
                Console.WriteLine($"\t{i + 1}:{group.tags[i].tag_name}");
            }
            var readKey = Console.ReadKey();
            var index = int.Parse(readKey.KeyChar.ToString());
            var tag = group.tags[index - 1];
            Console.WriteLine($"您選擇的是{tag.tag_name}");
            selectedTags.Add(tag.tag_code);
            group = group.tags[index - 1].children.FirstOrDefault();
        }
        while (group != null);

        Console.WriteLine(String.Join(',', selectedTags));

        var libJson = await File.ReadAllTextAsync("libraries.json");
        var libraries = JsonSerializer.Deserialize<List<librariesModel>>(libJson);

        var selectedLib = libraries.FirstOrDefault(w => w.catalog_ids.Except(selectedTags).Count() == 0);

        if (selectedLib == null)
        {
            Console.WriteLine("未找到課程");
            return;
        }
        Console.WriteLine($"{selectedLib.title}");

        var courseUrl = $"https://s-file-1.ykt.cbern.com.cn/zxx/s_course/v2/business_courses/{selectedLib.resource_id}/course_relative_infos/zh-CN.json";
        var courseJson = await httpClient.GetStringAsync(courseUrl);
        var course = JsonSerializer.Deserialize<course>(courseJson);

        var activityUrl = $"https://s-file-1.ykt.cbern.com.cn/zxx/s_course/v2/activity_sets/{course.course_detail.activity_set_id}/fulls.json";
        var activityJson = await httpClient.GetStringAsync(activityUrl);
        var activity = JsonSerializer.Deserialize<activityModel>(activityJson);

        var rootPath = AppDomain.CurrentDomain.BaseDirectory;
        var activeSetPath = Path.Combine(rootPath, activity.activity_set_name);
        if (!Directory.Exists(activeSetPath)) Directory.CreateDirectory(activeSetPath);
        Console.WriteLine($"{activity.activity_set_name}");
        foreach (var catalog in activity.nodes)
        {
            var catalogPath = Path.Combine(activeSetPath, $"{catalog.order_no}.{catalog.node_name}");
            if (!Directory.Exists(catalogPath)) Directory.CreateDirectory(catalogPath);
            Console.WriteLine($"{catalog.order_no}.{catalog.node_name}");
            foreach (var active in catalog.child_nodes)
            {
                //var activePath = Path.Combine(catalogPath, $"{active.order_no}.{active.node_name}");
                //if (!Directory.Exists(activePath)) Directory.CreateDirectory(activePath);
                var mp4Path= Path.Combine(catalogPath, $"{active.order_no}.{active.node_name}.mp4");
                if (File.Exists(mp4Path)) continue;

                var resourceUrl = $"https://s-file-1.ykt.cbern.com.cn/zxx/s_course/v1/x_class_hour_activity/{active.node_id}/resources.json";
                var resourceJson = await httpClient.GetStringAsync(resourceUrl);
                var listResource = JsonSerializer.Deserialize<List<resourcesModel>>(resourceJson);
                var resource = listResource.FirstOrDefault(w => w.resource_type == "video");
                var videoUrl = resource.video_extend.urls.Last().urls.FirstOrDefault();
                Console.WriteLine($"{Environment.NewLine}{active.node_name}{Environment.NewLine}{videoUrl}");

                var fileName = $"{active.order_no}.{active.node_name}";
                var dlargs = $"\"{videoUrl}\" --workDir \"{catalogPath}\" --saveName \"{fileName}\" --enableDelAfterDone ";
                ProcessStartInfo dlProcessInfo = new ProcessStartInfo(Path.Combine(rootPath, "N_m3u8DL-CLI_v3.0.1.exe"), dlargs);
                //dlProcessInfo.CreateNoWindow = true;
                dlProcessInfo.WorkingDirectory = rootPath;
                var p = new Process();
                p.StartInfo = dlProcessInfo;
                p.Start();
                await p.WaitForExitAsync();
            }
        }
        Console.WriteLine("下載完成,按任意鍵結束");
        Console.ReadKey();
    }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章