├── .gitignore
├── ElectronApp
├── app
│ ├── base.js
│ ├── css
│ │ └── index.css
│ ├── icon.ico
│ ├── index.html
│ └── main.js
├── package.json
└── package_for_windows.bat
├── README.MD
├── WPF_App
├── .vs
│ └── DC_sub_downloader
│ │ └── v15
│ │ └── .suo
├── DC_sub_downloader.sln
├── DC_sub_downloader
│ ├── App.xaml
│ ├── App.xaml.cs
│ ├── DC_sub_downloader.csproj
│ ├── MainWindow.xaml
│ ├── MainWindow.xaml.cs
│ ├── Properties
│ │ ├── AssemblyInfo.cs
│ │ ├── Resources.Designer.cs
│ │ ├── Resources.resx
│ │ ├── Settings.Designer.cs
│ │ └── Settings.settings
│ ├── README.MD
│ ├── SubDownloader.cs
│ ├── app.config
│ ├── icon.ico
│ └── packages.config
└── packages
│ └── Newtonsoft.Json.9.0.1
│ ├── Newtonsoft.Json.9.0.1.nupkg
│ ├── lib
│ ├── net20
│ │ ├── Newtonsoft.Json.dll
│ │ └── Newtonsoft.Json.xml
│ ├── net35
│ │ ├── Newtonsoft.Json.dll
│ │ └── Newtonsoft.Json.xml
│ ├── net40
│ │ ├── Newtonsoft.Json.dll
│ │ └── Newtonsoft.Json.xml
│ ├── net45
│ │ ├── Newtonsoft.Json.dll
│ │ └── Newtonsoft.Json.xml
│ ├── netstandard1.0
│ │ ├── Newtonsoft.Json.dll
│ │ └── Newtonsoft.Json.xml
│ ├── portable-net40+sl5+wp80+win8+wpa81
│ │ ├── Newtonsoft.Json.dll
│ │ └── Newtonsoft.Json.xml
│ └── portable-net45+wp80+win8+wpa81
│ │ ├── Newtonsoft.Json.dll
│ │ └── Newtonsoft.Json.xml
│ └── tools
│ └── install.ps1
└── python
├── example.py
└── thunder_subs.py
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | release/
--------------------------------------------------------------------------------
/ElectronApp/app/base.js:
--------------------------------------------------------------------------------
1 | var crypto = require("crypto");
2 | var fs = require("fs");
3 | var buffer = require("buffer");
4 | var request = require('request');
5 | var path = require("path");
6 | exports.cid_hash_file = cid_hash_file;
7 | exports.get_sub_info_list = get_sub_info_list;
8 | exports.processOneMovie = processOneMovie;
9 |
10 | function cid_hash_file(file_path) {
11 | var file_size = fs.statSync(file_path).size;
12 | var the_file = fs.openSync(file_path, "r");
13 | var sha1_hasher = crypto.createHash("sha1");
14 | if (file_size < 0xf000) {
15 | var buffer = new Buffer(file_size);
16 | fs.readSync(the_file, buffer, 0, file_size, 0);
17 | sha1_hasher.update(buffer);
18 | } else {
19 | var buffer = new Buffer(0x5000);
20 | fs.readSync(the_file, buffer, 0, 0x5000, 0);
21 | sha1_hasher.update(buffer);
22 |
23 | fs.readSync(the_file, buffer, 0, 0x5000, parseInt(file_size / 3));
24 | sha1_hasher.update(buffer);
25 |
26 | fs.readSync(the_file, buffer, 0, 0x5000, file_size - 0x5000);
27 | sha1_hasher.update(buffer);
28 | }
29 | fs.close(the_file);
30 | return sha1_hasher.digest("HEX").toUpperCase();
31 | }
32 |
33 | function get_sub_info_list(cid, callback) {
34 | var url = "http://sub.xmp.sandai.net:8000/subxl/" + cid + ".json";
35 | result = null;
36 | request.get(url, function (err, respone, body) {
37 | if (respone.statusCode == 200) {
38 | var sublist = JSON.parse(body).sublist;
39 | sublist = sublist.filter((value, index, array) => {
40 | for (var name in value) {
41 | return true;
42 | }
43 | return false;
44 | })
45 | callback(sublist);
46 | } else {
47 | get_sub_info_list(cid, callback);
48 | }
49 | })
50 | }
51 |
52 | function processOneMovie(file) {
53 | var cid = cid_hash_file(file.path);
54 | get_sub_info_list(cid, sublist => {
55 | var sub_type_map = build_sub_type_map(sublist);
56 | sub_type_map.forEach((item, index, theMap) => {
57 | item.forEach((item, index, array) => {
58 | downloadOneSub(item, index, file);
59 | });
60 | });
61 | })
62 | }
63 |
64 | function downloadOneSub(sub_info, index, file) {
65 | if (sub_info.rate === "0") {
66 | return;
67 | }
68 |
69 | var target_dir = path.dirname(file.path);
70 | var movie_extname = path.extname(file.path);
71 | var movie_name = path.basename(file.path, movie_extname);
72 | var sub_extname = sub_info.surl.split(".").pop();
73 |
74 | if (index === 0) {
75 | var sub_file_name = movie_name + "." + sub_extname;
76 | } else {
77 | var sub_file_name = movie_name + index + "." + sub_extname;
78 | }
79 | var sub_file_path = path.join(target_dir, sub_file_name);
80 | var my_request = request.get(sub_info.surl);
81 | my_request
82 | .on('error', (err) => {
83 | downloadOneSub(sub_info, index, file);
84 | })
85 | .on('response', (respone) => {
86 | if (respone.statusCode == 200) {
87 | my_request.pipe(fs.createWriteStream(sub_file_path));
88 | } else {
89 | downloadOneSub(sub_info, index, file);
90 | }
91 | })
92 | }
93 |
94 | function build_sub_type_map(sublist) {
95 | var result = new Map();
96 | for (var sub_info of sublist) {
97 | var sub_type = sub_info.surl.split(".").pop();
98 | if (result.has(sub_type)) {
99 | result.get(sub_type).push(sub_info);
100 | } else {
101 | result.set(sub_type, new Array(sub_info));
102 | }
103 | }
104 | return result;
105 | }
--------------------------------------------------------------------------------
/ElectronApp/app/css/index.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | #drop_area {
4 | height: 100%;
5 | padding: 0px;
6 | margin: 0px;
7 | }
8 |
9 | #drop_area {
10 | line-height: 100%;
11 | font-size: 40px;
12 | color: #d3d3d3;
13 | display: flex;
14 | justify-content: center;
15 | align-items: center;
16 | }
--------------------------------------------------------------------------------
/ElectronApp/app/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCjanus/ThunderSubs/77a7284f6a88cfa63df1462e2085e3c627ab3d5c/ElectronApp/app/icon.ico
--------------------------------------------------------------------------------
/ElectronApp/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 迅雷字幕下载器
7 |
8 |
9 |
10 |
11 |
12 | 将视频文件拖入窗口内
13 |
14 |
15 |
16 |
17 |
37 |
38 |
--------------------------------------------------------------------------------
/ElectronApp/app/main.js:
--------------------------------------------------------------------------------
1 | const {
2 | app,
3 | BrowserWindow
4 | } = require('electron')
5 | const path = require('path')
6 | const url = require('url')
7 |
8 | let win
9 |
10 | function createWindow() {
11 | win = new BrowserWindow({
12 | width: 800,
13 | height: 600,
14 | })
15 | win.setMenuBarVisibility(false);
16 | win.loadURL(url.format({
17 | pathname: path.join(__dirname, 'index.html'),
18 | protocol: 'file:',
19 | slashes: true
20 | }))
21 | win.on('closed', () => {
22 | win = null
23 | })
24 | }
25 | app.on('ready', createWindow)
26 | app.on('window-all-closed', () => {
27 | if (process.platform !== 'darwin') {
28 | app.quit()
29 | }
30 | })
31 | app.on('activate', () => {
32 | if (win === null) {
33 | createWindow()
34 | }
35 | })
--------------------------------------------------------------------------------
/ElectronApp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dc",
3 | "version": "1.0.0",
4 | "description": "逆向迅雷影音字幕API做的垃圾字幕下载器",
5 | "main": "app/main.js",
6 | "author": "DCjanus",
7 | "license": "ISC",
8 | "dependencies": {
9 | "electron": "^1.4.13",
10 | "request": "^2.80.0"
11 | },
12 | "devDependencies": {
13 | "electron-packager": "^8.5.2",
14 | "electron": "^1.4.13"
15 | },
16 | "scripts": {
17 | "start": "electron ."
18 | }
19 | }
--------------------------------------------------------------------------------
/ElectronApp/package_for_windows.bat:
--------------------------------------------------------------------------------
1 | electron-packager . dc --paltform=win32 --arch=all --icon=./app/icon.ico --overwrite --out ./release
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | 逆向迅雷影音字幕API,封装为单独字幕下载器。
2 |
3 | 本REPO下有C#版与Electron版,但二者均为当初练手所写,代码质量很糟糕,实现也较为简单,有兴趣的朋友可以自行实现。
4 |
5 | 已经很久没有使用Windows,故将不会继续维护这两个版本,目前主要维护的将会是[Rust命令行版](https://github.com/DCjanus/lone-ranger)。
6 |
7 | [下载页面](https://github.com/DCjanus/ThunderSubs/releases)
8 |
9 | [流程解析](https://blog.dcjanus.com/posts/api_for_xunlei_subscene/)
10 |
11 | # 其他版本
12 |
13 | 感谢[weaming](https://github.com/weaming)提供的Python命令行版:
14 |
15 | # 播放器安利
16 |
17 | Windows用户:[PotPlayer播放器](https://potplayer.daum.net/)+[Zune改版皮肤](http://tieba.baidu.com/p/3063231978?pid=51087618953&cid=#51087618953)(需要在D3D模式下使用)
18 |
--------------------------------------------------------------------------------
/WPF_App/.vs/DC_sub_downloader/v15/.suo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCjanus/ThunderSubs/77a7284f6a88cfa63df1462e2085e3c627ab3d5c/WPF_App/.vs/DC_sub_downloader/v15/.suo
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26228.4
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DC_sub_downloader", "DC_sub_downloader\DC_sub_downloader.csproj", "{BB5D2164-2F87-416E-8F50-1C3D47F7C852}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {BB5D2164-2F87-416E-8F50-1C3D47F7C852}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {BB5D2164-2F87-416E-8F50-1C3D47F7C852}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {BB5D2164-2F87-416E-8F50-1C3D47F7C852}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {BB5D2164-2F87-416E-8F50-1C3D47F7C852}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Windows;
7 |
8 | namespace DC_sub_downloader
9 | {
10 | ///
11 | /// App.xaml 的交互逻辑
12 | ///
13 | public partial class App : Application
14 | {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/DC_sub_downloader.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {BB5D2164-2F87-416E-8F50-1C3D47F7C852}
8 | WinExe
9 | DC_sub_downloader
10 | DC_sub_downloader
11 | v4.5
12 | 512
13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 4
15 |
16 |
17 |
18 | AnyCPU
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 | false
27 |
28 |
29 | AnyCPU
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 | false
37 |
38 |
39 | icon.ico
40 |
41 |
42 |
43 | ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | 4.0
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | MSBuild:Compile
62 | Designer
63 |
64 |
65 |
66 | MSBuild:Compile
67 | Designer
68 |
69 |
70 | App.xaml
71 | Code
72 |
73 |
74 | MainWindow.xaml
75 | Code
76 |
77 |
78 |
79 |
80 | Code
81 |
82 |
83 | True
84 | True
85 | Resources.resx
86 |
87 |
88 | True
89 | Settings.settings
90 | True
91 |
92 |
93 | ResXFileCodeGenerator
94 | Resources.Designer.cs
95 |
96 |
97 |
98 |
99 | SettingsSingleFileGenerator
100 | Settings.Designer.cs
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
15 |
19 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | using System.Windows;
5 | using System.IO;
6 |
7 | namespace DC_sub_downloader
8 | {
9 | public partial class MainWindow : Window
10 | {
11 | public MainWindow()
12 | {
13 | InitializeComponent();
14 | this.AllowDrop = true;
15 | }
16 |
17 | private async void Window_Drop(object sender, DragEventArgs e)
18 | {
19 | var allFilePaths = (String[])e.Data.GetData(DataFormats.FileDrop);
20 | var downloadInfoList = allFilePaths.Select(x => new DownloadInfo() { fileName = System.IO.Path.GetFileName(x), status = DownloadStatus.Pre, subNumber = 0 }).ToArray();
21 |
22 | this.messageBlock.Text = String.Join("\n", downloadInfoList.Select(x => x.message));
23 |
24 | for (var i = 0; i < allFilePaths.Length; i++)
25 | {
26 | var filePath = allFilePaths[i];
27 | if (File.Exists(filePath))
28 | {
29 | var theDownloader = new SubDownloader(filePath);
30 |
31 | downloadInfoList[i].status = DownloadStatus.Ing;
32 | this.messageBlock.Text = String.Join("\n", downloadInfoList.Select(x => x.message));
33 |
34 | var subNumber = await theDownloader.downLoadAllAsync();
35 |
36 | downloadInfoList[i].subNumber = subNumber;
37 | downloadInfoList[i].status = DownloadStatus.Done;
38 | }
39 | else
40 | {
41 | downloadInfoList[i].status = DownloadStatus.NotFile;
42 | }
43 | this.messageBlock.Text = String.Join("\n", downloadInfoList.Select(x => x.message));
44 | }
45 | }
46 |
47 | private void Window_DragEnter(object sender, DragEventArgs e)
48 | {
49 | if (e.Data.GetDataPresent(DataFormats.FileDrop))
50 | {
51 | e.Effects = DragDropEffects.Link;
52 | }
53 | else
54 | {
55 | e.Effects = DragDropEffects.None;
56 | this.messageBlock.Text = "拖入的不是文件";
57 | }
58 | }
59 |
60 | private void Window_DragOver(object sender, DragEventArgs e)
61 | {
62 | if (e.Data.GetDataPresent(DataFormats.FileDrop))
63 | {
64 | e.Effects = DragDropEffects.Link;
65 | }
66 | else
67 | {
68 | e.Effects = DragDropEffects.None;
69 | }
70 | }
71 | }
72 |
73 | enum DownloadStatus
74 | {
75 | Pre, Ing, Done, NotFile
76 | }
77 |
78 | class DownloadInfo
79 | {
80 | public String fileName;
81 | public DownloadStatus status;
82 | public int subNumber;
83 | public String message
84 | {
85 | get
86 | {
87 | if (status == DownloadStatus.Pre)
88 | {
89 | return String.Format("即将开始下载:{0}", fileName);
90 | }
91 | else if (status == DownloadStatus.Ing)
92 | {
93 | return String.Format("正在下载字幕:{0}", fileName);
94 | }
95 | else if (status == DownloadStatus.NotFile)
96 | {
97 | return String.Format("不是文件或者文件无法访问:{0}", fileName);
98 | }
99 | else
100 | {
101 | return String.Format("{1,2}个字幕完成:{0}", fileName, subNumber);
102 | }
103 | }
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // 有关程序集的一般信息由以下
8 | // 控制。更改这些特性值可修改
9 | // 与程序集关联的信息。
10 | [assembly: AssemblyTitle("DC_sub_downloader")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("DC_sub_downloader")]
15 | [assembly: AssemblyCopyright("Copyright © 2017")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // 将 ComVisible 设置为 false 会使此程序集中的类型
20 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
21 | //请将此类型的 ComVisible 特性设置为 true。
22 | [assembly: ComVisible(false)]
23 |
24 | //若要开始生成可本地化的应用程序,请设置
25 | //.csproj 文件中的 CultureYouAreCodingWith
26 | //例如,如果您在源文件中使用的是美国英语,
27 | //使用的是美国英语,请将 设置为 en-US。 然后取消
28 | //对以下 NeutralResourceLanguage 特性的注释。 更新
29 | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //主题特定资源词典所处位置
36 | //(未在页面中找到资源时使用,
37 | //或应用程序资源字典中找到时使用)
38 | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
39 | //(未在页面中找到资源时使用,
40 | //、应用程序或任何主题专用资源字典中找到时使用)
41 | )]
42 |
43 |
44 | // 程序集的版本信息由下列四个值组成:
45 | //
46 | // 主版本
47 | // 次版本
48 | // 生成号
49 | // 修订号
50 | //
51 | // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
52 | // 方法是按如下所示使用“*”: :
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace DC_sub_downloader.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// 一个强类型的资源类,用于查找本地化的字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// 返回此类使用的缓存的 ResourceManager 实例。
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DC_sub_downloader.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// 使用此强类型资源类,为所有资源查找
51 | /// 重写当前线程的 CurrentUICulture 属性。
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace DC_sub_downloader.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.0.1.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/README.MD:
--------------------------------------------------------------------------------
1 | 用C#写的WPF程序,最终的打包成了单文件发布
2 |
3 | icon.ico是软件图标,不过我想你们也不会在意的233333
4 |
5 | [项目依赖](packages.config)
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/SubDownloader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using System.Net;
5 | using Newtonsoft.Json;
6 | using System.Security.Cryptography;
7 | using System.Linq;
8 | using System.Text;
9 |
10 | namespace DC_sub_downloader
11 | {
12 | public class SubDownloader
13 | {
14 | private String filePath;
15 | public SubDownloader(String filePath)
16 | {
17 | this.filePath = filePath;
18 | }
19 |
20 | public async Task downLoadAllAsync()
21 | {
22 | var dirName = Path.GetDirectoryName(this.filePath);
23 | var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(this.filePath);
24 | var client = new WebClient();
25 |
26 | var subInfoList = await this.getSubInfoListAsync();
27 | subInfoList = subInfoList.Where(s => s.rate > 0).ToArray();
28 |
29 | for (var i = 0; i < subInfoList.Length; i++)
30 | {
31 | var theSubInfo = subInfoList[i];
32 | var subFileName = String.Format("{0}_{1}{2}", fileNameWithoutExtension, i, Path.GetExtension(theSubInfo.surl));
33 | var subFilePath = Path.Combine(dirName, subFileName);
34 | while (true)
35 | {
36 | try
37 | {
38 | await client.DownloadFileTaskAsync(theSubInfo.surl, subFilePath);
39 | break;
40 | }
41 | catch (WebException)
42 | {
43 | continue;
44 | }
45 | }
46 | }
47 | return subInfoList.Length;
48 | }
49 |
50 | public String Cid
51 | {
52 | get
53 | {
54 | var stream = new FileStream(this.filePath, FileMode.Open, FileAccess.Read);
55 | var reader = new BinaryReader(stream);
56 | var fileSize = (new FileInfo(filePath).Length);
57 | var SHA1 = new SHA1CryptoServiceProvider();
58 | var buffer = new byte[0xf000];
59 | if (fileSize < 0xf000)
60 | {
61 | reader.Read(buffer, 0, (int)fileSize);
62 | buffer = SHA1.ComputeHash(buffer, 0, (int)fileSize);
63 | }
64 | else
65 | {
66 | reader.Read(buffer, 0, 0x5000);
67 | stream.Seek(fileSize / 3, SeekOrigin.Begin);
68 | reader.Read(buffer, 0x5000, 0x5000);
69 | stream.Seek(fileSize - 0x5000, SeekOrigin.Begin);
70 | reader.Read(buffer, 0xa000, 0x5000);
71 |
72 | buffer = SHA1.ComputeHash(buffer, 0, 0xf000);
73 | }
74 | var result = "";
75 | foreach (var i in buffer)
76 | {
77 | result += String.Format("{0:X2}", i);
78 | }
79 | return result;
80 | }
81 | }
82 |
83 | public async Task getRawSubInfosAsync()
84 | {
85 | var client = new WebClient();
86 | var url = String.Format("http://sub.xmp.sandai.net:8000/subxl/{0}.json", this.Cid);
87 | while (true)
88 | {
89 | try
90 | {
91 | var data = await client.DownloadDataTaskAsync(url);
92 | return Encoding.UTF8.GetString(data);
93 | }
94 | catch (WebException)
95 | {
96 | continue;
97 | }
98 | }
99 | }
100 |
101 | public async Task getSubInfoListAsync()
102 | {
103 | var result = JsonConvert.DeserializeObject(await this.getRawSubInfosAsync()).sublist;
104 | result = result.Where(s => !string.IsNullOrEmpty(s.surl)).ToArray();
105 | return result;
106 | }
107 |
108 |
109 | }
110 | public class SubInfo
111 | {
112 | public String scid { get; set; }
113 | public String sname { get; set; }
114 | public String language { get; set; }
115 | public long rate { get; set; }
116 | public String surl { get; set; }
117 | public long svote { get; set; }
118 | public long roffset { get; set; }
119 | }
120 | public class SubList
121 | {
122 | public SubInfo[] sublist;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCjanus/ThunderSubs/77a7284f6a88cfa63df1462e2085e3c627ab3d5c/WPF_App/DC_sub_downloader/icon.ico
--------------------------------------------------------------------------------
/WPF_App/DC_sub_downloader/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/WPF_App/packages/Newtonsoft.Json.9.0.1/Newtonsoft.Json.9.0.1.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCjanus/ThunderSubs/77a7284f6a88cfa63df1462e2085e3c627ab3d5c/WPF_App/packages/Newtonsoft.Json.9.0.1/Newtonsoft.Json.9.0.1.nupkg
--------------------------------------------------------------------------------
/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/net20/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCjanus/ThunderSubs/77a7284f6a88cfa63df1462e2085e3c627ab3d5c/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/net20/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/net35/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCjanus/ThunderSubs/77a7284f6a88cfa63df1462e2085e3c627ab3d5c/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/net35/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/net40/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCjanus/ThunderSubs/77a7284f6a88cfa63df1462e2085e3c627ab3d5c/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/net40/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/net45/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCjanus/ThunderSubs/77a7284f6a88cfa63df1462e2085e3c627ab3d5c/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/net45/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/netstandard1.0/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCjanus/ThunderSubs/77a7284f6a88cfa63df1462e2085e3c627ab3d5c/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/netstandard1.0/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/portable-net40+sl5+wp80+win8+wpa81/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCjanus/ThunderSubs/77a7284f6a88cfa63df1462e2085e3c627ab3d5c/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/portable-net40+sl5+wp80+win8+wpa81/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCjanus/ThunderSubs/77a7284f6a88cfa63df1462e2085e3c627ab3d5c/WPF_App/packages/Newtonsoft.Json.9.0.1/lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/WPF_App/packages/Newtonsoft.Json.9.0.1/tools/install.ps1:
--------------------------------------------------------------------------------
1 | param($installPath, $toolsPath, $package, $project)
2 |
3 | # open json.net splash page on package install
4 | # don't open if json.net is installed as a dependency
5 |
6 | try
7 | {
8 | $url = "http://www.newtonsoft.com/json/install?version=" + $package.Version
9 | $dte2 = Get-Interface $dte ([EnvDTE80.DTE2])
10 |
11 | if ($dte2.ActiveWindow.Caption -eq "Package Manager Console")
12 | {
13 | # user is installing from VS NuGet console
14 | # get reference to the window, the console host and the input history
15 | # show webpage if "install-package newtonsoft.json" was last input
16 |
17 | $consoleWindow = $(Get-VSComponentModel).GetService([NuGetConsole.IPowerConsoleWindow])
18 |
19 | $props = $consoleWindow.GetType().GetProperties([System.Reflection.BindingFlags]::Instance -bor `
20 | [System.Reflection.BindingFlags]::NonPublic)
21 |
22 | $prop = $props | ? { $_.Name -eq "ActiveHostInfo" } | select -first 1
23 | if ($prop -eq $null) { return }
24 |
25 | $hostInfo = $prop.GetValue($consoleWindow)
26 | if ($hostInfo -eq $null) { return }
27 |
28 | $history = $hostInfo.WpfConsole.InputHistory.History
29 |
30 | $lastCommand = $history | select -last 1
31 |
32 | if ($lastCommand)
33 | {
34 | $lastCommand = $lastCommand.Trim().ToLower()
35 | if ($lastCommand.StartsWith("install-package") -and $lastCommand.Contains("newtonsoft.json"))
36 | {
37 | $dte2.ItemOperations.Navigate($url) | Out-Null
38 | }
39 | }
40 | }
41 | else
42 | {
43 | # user is installing from VS NuGet dialog
44 | # get reference to the window, then smart output console provider
45 | # show webpage if messages in buffered console contains "installing...newtonsoft.json" in last operation
46 |
47 | $instanceField = [NuGet.Dialog.PackageManagerWindow].GetField("CurrentInstance", [System.Reflection.BindingFlags]::Static -bor `
48 | [System.Reflection.BindingFlags]::NonPublic)
49 |
50 | $consoleField = [NuGet.Dialog.PackageManagerWindow].GetField("_smartOutputConsoleProvider", [System.Reflection.BindingFlags]::Instance -bor `
51 | [System.Reflection.BindingFlags]::NonPublic)
52 |
53 | if ($instanceField -eq $null -or $consoleField -eq $null) { return }
54 |
55 | $instance = $instanceField.GetValue($null)
56 |
57 | if ($instance -eq $null) { return }
58 |
59 | $consoleProvider = $consoleField.GetValue($instance)
60 | if ($consoleProvider -eq $null) { return }
61 |
62 | $console = $consoleProvider.CreateOutputConsole($false)
63 |
64 | $messagesField = $console.GetType().GetField("_messages", [System.Reflection.BindingFlags]::Instance -bor `
65 | [System.Reflection.BindingFlags]::NonPublic)
66 | if ($messagesField -eq $null) { return }
67 |
68 | $messages = $messagesField.GetValue($console)
69 | if ($messages -eq $null) { return }
70 |
71 | $operations = $messages -split "=============================="
72 |
73 | $lastOperation = $operations | select -last 1
74 |
75 | if ($lastOperation)
76 | {
77 | $lastOperation = $lastOperation.ToLower()
78 |
79 | $lines = $lastOperation -split "`r`n"
80 |
81 | $installMatch = $lines | ? { $_.StartsWith("------- installing...newtonsoft.json ") } | select -first 1
82 |
83 | if ($installMatch)
84 | {
85 | $dte2.ItemOperations.Navigate($url) | Out-Null
86 | }
87 | }
88 | }
89 | }
90 | catch
91 | {
92 | try
93 | {
94 | $pmPane = $dte2.ToolWindows.OutputWindow.OutputWindowPanes.Item("Package Manager")
95 |
96 | $selection = $pmPane.TextDocument.Selection
97 | $selection.StartOfDocument($false)
98 | $selection.EndOfDocument($true)
99 |
100 | if ($selection.Text.StartsWith("Attempting to gather dependencies information for package 'Newtonsoft.Json." + $package.Version + "'"))
101 | {
102 | # don't show on upgrade
103 | if (!$selection.Text.Contains("Removed package"))
104 | {
105 | $dte2.ItemOperations.Navigate($url) | Out-Null
106 | }
107 | }
108 | }
109 | catch
110 | {
111 | # stop potential errors from bubbling up
112 | # worst case the splash page won't open
113 | }
114 | }
115 |
116 | # still yolo
--------------------------------------------------------------------------------
/python/example.py:
--------------------------------------------------------------------------------
1 | import thunder_subs
2 |
3 | # 获取一个本地电影文件名为cid的hash值
4 | cid = thunder_subs.cid_hash_file(
5 | r"E:\电影\神秘博士 第6季\Doctor.Who.2005.Christmas.Special.2011.The.Doctor.The.Widow.And.The.Wardrobe.720p.HDTV.x264-FoV.mkv")
6 |
7 | info_list = thunder_subs.get_sub_info_list(cid, 1000)
8 | if info_list is None:
9 | print("超过最大重试次数后仍然未能获得正确结果")
10 | else:
11 | for i in info_list:
12 | print(i)
13 | '''
14 | 本次输出结果:
15 | {'scid': '86AE53FC9D5A2E41E5E9CAB7C1A3794A1B7206B9', 'sname': '神秘博士2011圣诞篇The.Doctor.The.Widow.And.The.Wardrobe.ass', 'language': '简体', 'rate': '4', 'surl': 'http://subtitle.v.geilijiasu.com/86/AE/86AE53FC9D5A2E41E5E9CAB7C1A3794A1B7206B9.ass', 'svote': 545, 'roffset': 4114797192}
16 | {'scid': '6D314FF209BCDF94390429D5826314B7DC4CFF4C', 'sname': 'doctor_who_2005.christmas_special_2011.the_doctor_the_widow_and_the_wardrobe.720p_hdtv_x264-fov.srt', 'language': '简体&英语', 'rate': '3', 'surl': 'http://subtitle.v.geilijiasu.com/6D/31/6D314FF209BCDF94390429D5826314B7DC4CFF4C.srt', 'svote': 120, 'roffset': 4114797192}
17 | {'scid': '833D79CD8D9C97190DFC6DED38603D0F1AB13989', 'sname': '第六季doctor_who_2005.christmas_special_2011.the_doctor_the_widow_and_the_wardrobe.720p_hdtv_x264-fov.eng1.srt', 'language': '英语', 'rate': '3', 'surl': 'http://subtitle.v.geilijiasu.com/83/3D/833D79CD8D9C97190DFC6DED38603D0F1AB13989.srt', 'svote': 97, 'roffset': 4114797192}
18 | {'scid': '49D8C936BE2920D1F5BA9FCD499689FBF0AC6706', 'sname': '', 'language': '简体', 'rate': '3', 'surl': 'http://subtitle.v.geilijiasu.com/49/D8/49D8C936BE2920D1F5BA9FCD499689FBF0AC6706.srt', 'svote': 56, 'roffset': 4114797192}
19 | {'scid': 'C8BE7928FCB62A0E49F1702D7DADDB90D98F02B4', 'sname': 'Doctor.Who.2005.Christmas.Special.2011.The.Doctor.The.Widow.And.The.Wardrobe.720p.HDTV.x264-FoV.srt', 'language': '英语', 'rate': '1', 'surl': 'http://subtitle.v.geilijiasu.com/C8/BE/C8BE7928FCB62A0E49F1702D7DADDB90D98F02B4.srt', 'svote': 7, 'roffset': 4114797192}
20 | {'scid': '8F6572265A069E77EDA4EB1AED88EA616F232809', 'sname': 'DW博士之时 上半部 - 副本.CHS&EN.ass', 'language': '简体&英语', 'rate': '0', 'surl': 'http://subtitle.v.geilijiasu.com/8F/65/8F6572265A069E77EDA4EB1AED88EA616F232809.ass', 'svote': 1, 'roffset': 4114797192}
21 | {'scid': '8F94F0C457A2E87AB344C00239F5FB229E650481', 'sname': 'Downton Abbey / 唐顿庄园@@Downton Abbey - 02x10 - Christmas Specia1.srt', 'language': '简体&英语', 'rate': '0', 'surl': 'http://subtitle.v.geilijiasu.com/8F/94/8F94F0C457A2E87AB344C00239F5FB229E650481.srt', 'svote': 1, 'roffset': 4114797192}
22 | {'scid': 'E0B4E327DC1AEE5408A6ACDABE3B7998D94A15D0', 'sname': 'Doctor.Who.2005.S07E07.The.Rings.Of.Akhaten.720p.HDTV.x264-FoV.srt', 'language': '简体&英语', 'rate': '0', 'surl': 'http://subtitle.v.geilijiasu.com/E0/B4/E0B4E327DC1AEE5408A6ACDABE3B7998D94A15D0.srt', 'svote': 1, 'roffset': 4114797192}
23 | {'scid': 'FAFDF91FF46183E342EE8FF7D43FB99FC5511A54', 'sname': '', 'language': '简体', 'rate': '0', 'surl': 'http://subtitle.v.geilijiasu.com/FA/FD/FAFDF91FF46183E342EE8FF7D43FB99FC5511A54.ass', 'svote': 2, 'roffset': 4114797192}
24 | {'scid': '8DEFC8CE8396B455810B694F3204D5C95EC930B3', 'sname': 'Doctor.Who.2005.S05.Special.A.Christmas.Carol.2010.Special.BDRip.XviD-HAGGiS.srt', 'language': '英语', 'rate': '0', 'surl': 'http://subtitle.v.geilijiasu.com/8D/EF/8DEFC8CE8396B455810B694F3204D5C95EC930B3.srt', 'svote': 4, 'roffset': 4114797192}
25 |
26 | 格式化其中一个结果如下:
27 | {
28 | 'scid': '86AE53FC9D5A2E41E5E9CAB7C1A3794A1B7206B9',
29 | 'sname': '神秘博士2011圣诞篇The.Doctor.The.Widow.And.The.Wardrobe.ass',
30 | 'language': '简体',
31 | 'rate': '4',
32 | 'surl': 'http://subtitle.v.geilijiasu.com/86/AE/86AE53FC9D5A2E41E5E9CAB7C1A3794A1B7206B9.ass',
33 | 'svote': 545,
34 | 'roffset': 4114797192
35 | }
36 |
37 | 每项中需要注意的数据有:
38 | scid: 猜测为字幕文件的scid
39 | sname: 字幕文件的原始文件名
40 | language: 字幕语言
41 | surl: 字幕下载地址
42 | '''
43 |
--------------------------------------------------------------------------------
/python/thunder_subs.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import hashlib
4 | import requests
5 |
6 |
7 | def cid_hash_file(path: str):
8 | '''
9 | 计算文件名为cid的hash值,算法来源:https://github.com/iambus/xunlei-lixian
10 | :param path: 需要计算的本地文件路径
11 | :return: 所给路径对应文件的cid值
12 | '''
13 | h = hashlib.sha1()
14 | size = os.path.getsize(path)
15 | with open(path, 'rb') as stream:
16 | if size < 0xF000:
17 | h.update(stream.read())
18 | else:
19 | h.update(stream.read(0x5000))
20 | stream.seek(size // 3)
21 | h.update(stream.read(0x5000))
22 | stream.seek(size - 0x5000)
23 | h.update(stream.read(0x5000))
24 | return h.hexdigest().upper()
25 |
26 |
27 | def get_sub_info_list(cid: str, max_retry_times: int = 0):
28 | '''
29 | 获取迅雷字幕库中字幕信息列表
30 | :param cid: 本地电影文件的cid值
31 | :param max_retry_times: 最大重试次数,非正数时会无限次重试直到获得正确结果
32 | :return: 字幕信息列表,超过最大重试次数还未获得正确结果时会返回None。
33 | '''
34 | url = "http://sub.xmp.sandai.net:8000/subxl/{cid}.json".format(cid=cid)
35 | result = None
36 | if max_retry_times <= 0:
37 | while True:
38 | response = requests.get(url)
39 | if response.status_code == 200:
40 | result = json.loads(response.text)["sublist"]
41 | break
42 | else:
43 | for i in range(max_retry_times):
44 | response = requests.get(url)
45 | if response.status_code == 200:
46 | result_dict = json.loads(response.text)
47 | result = result_dict["sublist"]
48 | break
49 | return [i for i in result if i]
50 |
--------------------------------------------------------------------------------