예제 #1
0
        /// <summary>
        /// start download.
        /// </summary>
        public async void StartDownload()
        {
            if (!_cookies.Any())
            {
                MessageBox.Show(@"未登录中国大学 MOOC.", @"提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if (string.IsNullOrEmpty(_config.CourseUrl))
            {
                MessageBox.Show(@"课程链接未输入.", @"提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            var courseUrl = _config.CourseUrl;

            if (!_config.IsDownloadDocument &&
                !_config.IsDownloadVideo &&
                !_config.IsDownloadSubtitle &&
                !_config.IsDownloadAttachment) // checked at least one of them
            {
                MessageBox.Show(@"至少勾选下载视频, 文档, 字幕, 附件其中一种类型.", @"提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }


            if (MessageBox.Show(@"开始下载?", @"提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Information)
                == DialogResult.Cancel)
            {
                Log("取消下载.");
                return;
            }

            if (!Directory.Exists(_config.CourseSavePath))
            {
                Log($@"路径: {_config.CourseSavePath} 不存在, 准备创建.");

                try
                {
                    Directory.CreateDirectory(_config.CourseSavePath);
                    Log($@"路径: {_config.CourseSavePath} 创建成功.");
                }
                catch (Exception exception)
                {
                    Log($@"路径: {_config.CourseSavePath} 创建失败, 原因: {exception.Message}.");
                    return;
                }
            }

            SetStatus("准备下载");
            Log($@"课程将会下载到文件夹: {_config.CourseSavePath}");
            SetUIStatus(false);
            ResetCurrentBar();
            ResetTotalBar();

            // 1. initializes a mooc request.
            var mooc = new MoocRequest(_cookies, courseUrl);

            // 2. get term id.
            var termId = await mooc.GetTermIdAsync();

            SetStatus("正在下载");
            Log($@"提取到课程 ID 是 {termId}");

            // 3. get Mooc term JavaScript code.
            var moocTermCode = await mooc.GetMocTermJavaScriptCodeAsync(termId);

            // 4. evaluate mooc term JavaScript code.
            moocTermCode = FixCourseBeanCode(moocTermCode);
            var moocTermJSON = EvaluateJavaScriptCode(moocTermCode, COURSE_BEAN_NAME) as string;

            // 5. deserialize moocTermJSON.
            var course = DeserializeObject <CourseModel>(moocTermJSON ?? string.Empty);

            FFmpegWorker.Instance.Start();

            for (var chapterIndex = 0; chapterIndex < course.Chapters.Count && !_isCancel; chapterIndex++)
            {
                var chapter = course.Chapters[chapterIndex];

                for (var lessonIndex = 0; lessonIndex < chapter.Lessons.Count && !_isCancel; lessonIndex++)
                {
                    var lesson = chapter.Lessons[lessonIndex];

                    for (var unitIndex = 0; unitIndex < lesson.Units.Count && !_isCancel; unitIndex++)
                    {
                        // update total progress bar.
                        var totalMax     = course.Chapters.Count + chapter.Lessons.Count + lesson.Units.Count;
                        var totalCurrent = (chapterIndex + 1) + (lessonIndex + 1) + (unitIndex + 1);

                        UpdateTotalBar(CalculatePercentage(totalCurrent, totalMax));

                        var unit = lesson.Units[unitIndex];

                        // create unit save path.
                        var chapterDir = $@"{chapterIndex + 1:00}-{FixPath(chapter.Name)}";
                        var lessonDir  = $@"{lessonIndex  + 1:00}-{FixPath(lesson.Name)}";
                        var unitPath   = Path.Combine(_config.CourseSavePath, course.CourseName, chapterDir, lessonDir);

                        var unitFileName = $@"{unitIndex + 1:00}-{FixPath(unit.Name)}";

                        if (!Directory.Exists(unitPath))
                        {
                            Directory.CreateDirectory(unitPath);
                        }

                        var unitCode = await mooc.GetUnitJavaScriptCodeAsync(
                            unit.Id, unit.ContentId, unit.TermId, unit.ContentType
                            );

                        if (unitCode.Contains("dwr.engine._remoteHandleException"))
                        {
                            Console.WriteLine(@"Error: system error.");
                            break;
                        }

                        unitCode = FixCourseBeanCode(unitCode);

                        var unitJSON   = EvaluateJavaScriptCode(unitCode, COURSE_BEAN_NAME) as string;
                        var unitResult = DeserializeObject <UnitResultModel>(unitJSON ?? string.Empty);

                        // Parse video / document / attachment link.
                        var unitType = (UnitType)(unit.ContentType ?? 0);

                        switch (unitType)
                        {
                        case UnitType.Other:     // type is null.
                            break;

                        case UnitType.Video:     // video type.
                        {
                            if (!_config.IsDownloadVideo)
                            {
                                break;
                            }

                            // get access token.
                            var tokenJSON = await mooc.GetResourceTokenJsonAsync(
                                $"{unit.Id}", $@"{unit.TermId}", $"{unit.ContentType}"
                                );

                            var tokenObject = JObject.Parse(tokenJSON);
                            var signature   = tokenObject["result"]?["videoSignDto"]?["signature"]?.ToString();
                            var videoJSON   = await mooc.GetVideoJsonAsync($@"{unit.ContentId}", signature);

                            var video       = DeserializeObject <VideoResponseModel>(videoJSON);
                            var courseVideo = new CourseVideoInfo
                            {
                                SavePath      = unitPath,
                                VideoFileName = $@"{unitFileName}.mp4",
                                MergeListFile = $@"{unitFileName}.text"
                            };

                            Log($@"下载视频: {unitFileName}");

                            // subtitles
                            if (_config.IsDownloadSubtitle)
                            {
                                foreach (var caption in video.Result.SrtCaptions)
                                {
                                    // subtitle file. E.g:
                                    //  01-第一节 Java明天 视频.zh.srt
                                    //  01-第一节 Java明天 视频.en.srt
                                    var srtName    = $@"{unitFileName}.{caption.LanguageCode}.srt";
                                    var srtContent = await mooc.DownloadSubtitleAsync(caption.Url);

                                    File.WriteAllBytes(Path.Combine(unitPath, srtName), srtContent);
                                }
                            }

                            var videoInfo = video.Result.Videos.FirstOrDefault(
                                v => v.Quality.HasValue &&
                                (VideoQuality)v.Quality == _config.VideoQuality
                                );

                            if (videoInfo != null)
                            {
                                var videoUrl = new Uri(videoInfo.VideoUrl);     // video url.

                                var baseUrl = $@"{videoUrl.Scheme}://{videoUrl.Host}" +
                                              string.Join("", videoUrl.Segments.Take(videoUrl.Segments.Length - 1));

                                Configuration.Default.BaseUri = new Uri(baseUrl, UriKind.Absolute);

                                var m3u8List = await mooc.DownloadM3U8ListAsync(videoUrl);

                                using var reader = new M3UFileReader(m3u8List);
                                var m3u8Info = reader.Read();
                                var merger   = new StringBuilder();

                                for (var i = 0; i < m3u8Info.MediaFiles.Count && !_isCancel; i++)
                                {
                                    UpdateCurrentBar(CalculatePercentage(i + 1, m3u8Info.MediaFiles.Count));

                                    var tsSavedName = $@"{unitFileName}-{i:00}.ts";

                                    for (var j = 0; j < MAX_TIMES; j++)
                                    {
                                        var tsBytes = await mooc.DownloadM3U8TSAsync(m3u8Info.MediaFiles[i].Uri);

                                        if (tsBytes is null)
                                        {
                                            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, j + 1)));
                                        }
                                        else
                                        {
                                            File.WriteAllBytes(Path.Combine(unitPath, tsSavedName), tsBytes);
                                            break;
                                        }
                                    }

                                    merger.AppendLine(
                                        $@"file '{Path.Combine(unitPath, tsSavedName)}'"
                                        ); // combine ts file path and add to list.

                                    courseVideo.TSFiles.Add(tsSavedName);
                                }

                                Log($@"课程 {unitFileName} 已下载完成.");

                                File.WriteAllText(
                                    Path.Combine(unitPath, $@"{courseVideo.MergeListFile}"), merger.ToString()
                                    );

                                FFmpegWorker.Instance.Enqueue(courseVideo);
                            }
                        }
                        break;

                        case UnitType.Document:     // document type. E.g pdf.
                        {
                            if (!_config.IsDownloadDocument)
                            {
                                break;
                            }

                            var documentUrl = unitResult.TextOrigUrl;
                            var fileName    = $@"{unitFileName}.pdf";

                            Log($@"准备下载文档: {fileName}");

                            for (var i = 0; i < MAX_TIMES; i++)
                            {
                                var document = await mooc.DownloadDocumentAsync(documentUrl);

                                if (document is null)
                                {
                                    await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i + 1)));
                                }
                                else
                                {
                                    File.WriteAllBytes(Path.Combine(unitPath, fileName), document);

                                    Log($@"文档 {fileName} 已下载完成.");
                                    break;
                                }
                            }
                        }
                        break;

                        case UnitType.Attachment:     // attachment type. E.g source code.
                        {
                            if (!_config.IsDownloadAttachment)
                            {
                                break;
                            }

                            const string attachmentBaseUrl = "https://www.icourse163.org/course/attachment.htm";

                            var content       = JObject.Parse(unit.JsonContent);
                            var nosKey        = content["nosKey"]?.ToString();
                            var fileName      = content["fileName"]?.ToString();
                            var attachmentUrl = $@"{attachmentBaseUrl}?fileName={fileName}&nosKey={nosKey}";

                            Log($@"准备下载附件: {fileName}");

                            for (var i = 0; i < MAX_TIMES; i++)
                            {
                                var attachment = await mooc.DownloadAttachmentAsync(attachmentUrl);

                                if (attachment is null)
                                {
                                    await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i + 1)));
                                }
                                else
                                {
                                    File.WriteAllBytes(
                                        Path.Combine(unitPath, $@"{unitFileName}-{FixPath(fileName)}"), attachment
                                        );

                                    Log($@"附件 {fileName} 已下载完成.");
                                    break;
                                }
                            }
                        }
                        break;

                        default:     // not recognized type
                            throw new ArgumentOutOfRangeException();
                        }
                    }
                }
            }

            SetUIStatus(true);

            if (_isCancel)
            {
                Log("已取消下载.");
            }
            else
            {
                UpdateTotalBar(100);
                UpdateCurrentBar(100);
                SetStatus("下载完成");
                Log("下载完成!");
            }
        }
예제 #2
0
        private async Task DownloadAttachmentAsync(UnitModel unit, MoocRequest mooc, string unitPath,
                                                   string unitFileName)
        {
            if (!_config.IsDownloadAttachment)
            {
                return;
            }

            const string attachmentBaseUrl = "https://www.icourse163.org/course/attachment.htm";

            if (string.IsNullOrEmpty(unit.JsonContent))
            {
                WriteLog($"附件 {unit.Name} 下载链接为空, 跳过下载.");
                return;
            }

            var content            = JObject.Parse(unit.JsonContent);
            var nosKey             = content["nosKey"]?.ToString();
            var fileName           = content["fileName"]?.ToString();
            var attachmentUrl      = $@"{attachmentBaseUrl}?fileName={fileName}&nosKey={nosKey}";
            var downloadAttSuccess = false;
            var attachSavePath     = Path.Combine(unitPath, $@"{unitFileName}-{FixPath(fileName)}");

            if (File.Exists(attachSavePath)) // exist attachment, skip.
            {
                WriteLog($@"附件 {fileName} 已下载, 跳过.");
                return;
            }

            WriteLog($@"准备下载附件: {fileName}");

            for (var i = 0; i < MAX_TIMES; i++)
            {
                try
                {
                    var attachment = await mooc.DownloadAttachmentAsync(attachmentUrl);

                    if (attachment is null)
                    {
                        WriteLog($"下载附件 {fileName} 失败, 准备重试, 当前重试第 {i + 1} 次.");
                        await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i)));
                    }
                    else
                    {
                        File.WriteAllBytes(attachSavePath, attachment);
                        downloadAttSuccess = true;

                        WriteLog($@"附件 {fileName} 已下载完成.");
                        break;
                    }
                }
                catch (Exception exception)
                {
                    await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i)));

                    WriteLog($@"下载附件 {fileName} 发生错误, 原因: {exception.Message}, 准备重试, 当前重试第 {i + 1} 次.");
                }
            }

            if (!downloadAttSuccess)
            {
                WriteLog($"下载附件 {fileName} 失败, 已跳过.");
            }
        }