├── .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 | --------------------------------------------------------------------------------