├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── Tools ├── curl.exe ├── curl_64.exe ├── ffmerge.exe ├── ffmpeg.exe ├── logger.exe ├── m3u8_dl-js-master │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── dist.zip │ ├── makefile │ ├── o │ │ ├── play_server │ │ │ ├── make_m3u8.coffee │ │ │ └── play_server.coffee │ │ └── play_static │ │ │ └── play_m3u8.html │ ├── package.json │ └── src │ │ ├── async.coffee │ │ ├── auto_retry.coffee │ │ ├── config.coffee │ │ ├── decrypt.coffee │ │ ├── dl_clip.coffee │ │ ├── dl_speed.coffee │ │ ├── dl_with_curl.coffee │ │ ├── dl_with_proxy.coffee │ │ ├── do_dl.coffee │ │ ├── is_task_done.coffee │ │ ├── key_host.coffee │ │ ├── log.coffee │ │ ├── m3u8_dl.coffee │ │ ├── parse_m3u8.coffee │ │ ├── show_dl_speed.coffee │ │ ├── thread_pool.coffee │ │ └── util.coffee ├── node.exe └── node_64.exe ├── m3u8_dl-js_GUI.sln ├── m3u8_dl-js_GUI ├── App.config ├── Mainform.Designer.cs ├── Mainform.cs ├── Mainform.resx ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── RealAction.cs ├── Run.cs ├── bitbug_favicon.ico ├── download_64px_1170980_easyicon.net.ico ├── m3u8_dl-js_GUI.csproj └── m3u8_dl.cs └── pre_processor ├── doc └── pre_processor.md ├── pp_271-m-ts ├── pp_271_m_ts.py └── pre_processor.meta.json ├── pp_le-decode ├── pp_le_decode.py └── pre_processor.meta.json ├── pp_tv-line-me ├── pp_tv-line-me.py └── pre_processor.meta.json ├── pp_wwq-any_page-url_list ├── pp_wwq_any_page.py └── pre_processor.meta.json └── ppp_unpacker └── ppp_unpacker.py /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # m3u8_dl-js_GUI 2 | A simple GUI for m3u8_dl-js... 3 | 4 | ## 公告(2018年12月25日) 5 | * 由于本人看着这上古渣代码难受,估计程序不会继续更新了。 6 | 7 | ## 如何进行批量下载 8 | #### 方法1 9 | * 将m3u8链接保存为txt文件,每行一个链接。然后将txt文件拖入程序的地址栏,点击下载。程序会自动逐个识别文件名并下载。
10 |
11 | 12 |

13 | 14 | #### 方法2(推荐) 15 | * 本地多个m3u8文件,保存在新建文件夹中,将整个文件夹拖入程序的地址栏,点击下载。程序会自动逐个识别文件名并下载。
16 |
17 | 18 |

19 | 20 | #### 方法3 21 | * 将m3u8链接下载为本地m3u8文件后再使用方法2下载。 ([辅助程序](https://github.com/nilaoda/m3u8_dl-js_GUI/releases/download/v0.4.1/m3u8.exe)) 22 | 23 | ## IDM下载m3u8的几个小贴士 24 | #### 为什么点击后没反应 25 | * 将鼠标停留在按钮上数秒,按照提示操作 26 | `(若依旧无法下载,请往下看)` 27 | #### 为什么要用IDM下载 28 | * 稳定,基本不会出现花屏等现象 29 | * 强大的队列功能 30 | #### 建议使用IDM的情况 31 | 32 | `(除非你特别了解你的操作,否则我都建议先尝试IDM)` 33 | * 腾讯视频的m3u8 34 | * 爱奇艺的m3u8 35 | * 优酷4K的m3u8 `(1080P最好下载分段,m3u8存在问题 )` 36 | * 搜狐的m3u8 37 | * 芒果的m3u8 38 | #### 优酷H264的补充说明 39 | 好久没下优酷,今天(2018年10月8日)发现优酷H264的m3u8也变成真正的TS分片了,可以直接idm下载。(不是全部视频都改了,有的还是分段) 40 | 41 | 由于某些原因,优酷H264只建议下载**分段**而不是m3u8: 42 | 可以把优酷网页端appinfo接口中的各个分段url改写为一个“m3u8”然后使用下载器下载并在设置中启用**更慢的合并方式**,或者直接处理m3u8将之变成**分段**形式的“m3u8”以减少不确定性([辅助程序](https://github.com/nilaoda/m3u8_dl-js_GUI/releases/download/v0.3.0/YK-m3u82clip.exe)) 43 | #### 哪些不能下载 44 | * 不能嗅探https开头的m3u8 45 | * 无法直接下载某些禁止二次请求的m3u8 46 | * 无法下载AES-128或任何形式的加密HLS流 47 | #### **奇技淫巧** 48 | 先将本地m3u8变成http形式的url,再次尝试调用IDM来下载那些本身无法被嗅探或服务器禁止二次访问的文件。 49 | 50 | 具体操作如下: 51 | * [下载HFS。](http://www.rejetto.com/hfs/?f=dl) 52 | * 下载url为m3u8文件并进行适当修改 53 | `(修改操作例如为m3u8文件手动添加Baseurl、加载外部key等. PS:事实上如果你点击过软件中的按钮,程序已经将m3u8文件下载到%temp%目录)` 54 | * 在HFS打开时,默认的IP地址即为你的本地IP地址,你可以随意指定一个端口。`(菜单-限制-防止反复连接 取消勾选)` 55 | * 此时将m3u8文件拖入左边的文件列表,然后右键刚拖入的m3u8,点击复制URL(Copy URL Address)。 56 | * 返回m3u8_dl-js_GUI,粘贴url,再次尝试IDM下载。优酷4K、搜狐的m3u8已测试通过。 57 | `(如果你有公网地址,在获取外部IP后,可以把URL分享给其他人,让他们使用下载器或IDM下载或使用移动设备直接观看。必要时在路由器端做一下端口映射哦)` 58 | 59 | 手动批量下载: 60 | * 先将url批量下载为m3u8文件 ([辅助程序](https://github.com/nilaoda/m3u8_dl-js_GUI/releases/download/v0.4.0/m3u8.exe)) 61 | * 将m3u8文件放在一个文件夹中并将此文件夹拖入HFS,选择虚拟目录模式,并复制该**文件夹**URL。 62 | * 在Chrome等浏览器中打开复制好的URL,进入HFS的WEB管理界面。 63 | * **顺次**点击(下载)所列出的m3u8文件,如无意外,IDM将自动嗅探并顺序编号,此时点击悬浮框,再点击下载全部即可以队列方式将TS文件添加至IDM。 64 | 65 | 自动化: 66 | * 暂未公开的程序,敬请期待 67 | #### 下载的TS文件如何批量转换为MP4封装 68 |  使用 [MP4-Tags-Editor](https://github.com/nilaoda/MP4-Tags-Editor/releases) 69 | #### 特别提醒 70 |  在程序设置页的Headers将同样被用于请求URL,以应对某些特殊情况的IDM捕获。 71 | 72 | 73 | 74 | ## 关于“更慢的合并方式”的适用情况(#EXT-X-DISCONTINUITY引发) 75 | * 优酷的**由大分段切片的**m3u8 76 | * 搜狐、优酷、哔哩哔哩等的以**H264分段**形式组成的“m3u8” 77 | -------------------------------------------------------------------------------- /Tools/curl.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/m3u8_dl-js_GUI/3e94310ece920ca82735d15ac82f7001279da02d/Tools/curl.exe -------------------------------------------------------------------------------- /Tools/curl_64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/m3u8_dl-js_GUI/3e94310ece920ca82735d15ac82f7001279da02d/Tools/curl_64.exe -------------------------------------------------------------------------------- /Tools/ffmerge.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/m3u8_dl-js_GUI/3e94310ece920ca82735d15ac82f7001279da02d/Tools/ffmerge.exe -------------------------------------------------------------------------------- /Tools/ffmpeg.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/m3u8_dl-js_GUI/3e94310ece920ca82735d15ac82f7001279da02d/Tools/ffmpeg.exe -------------------------------------------------------------------------------- /Tools/logger.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/m3u8_dl-js_GUI/3e94310ece920ca82735d15ac82f7001279da02d/Tools/logger.exe -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /dist 3 | /tmp 4 | 5 | # node.js (npm) 6 | node_modules/ 7 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/README.md: -------------------------------------------------------------------------------- 1 | # m3u8_dl-js 2 | A simple `m3u8` downloader in `node.js`. 3 | 4 | 5 | ## Build from source 6 | 7 | + **1**. Install `node.js` () 8 | 9 | + **2**. 10 | 11 | ``` 12 | $ npm install 13 | $ node ./node_modules/.bin/coffee -o dist/ src/ 14 | ``` 15 | 16 | **Run** 17 | 18 | ``` 19 | $ node dist/m3u8_dl.js --version 20 | m3u8_dl-js version 0.2.0 test20170606 2238 21 | ``` 22 | 23 | 24 | ## Usage 25 | 26 | + **m3u8_dl** 27 | 28 | ``` 29 | $ node dist/m3u8_dl.js --help 30 | m3u8_dl-js [OPTIONS] M3U8 31 | Usage: 32 | -o, --output DIR Download files to this Directory 33 | 34 | -T, --thread NUM Set number of download thread (default: 1) 35 | --auto-remove Remove raw file after decrypt success 36 | --exit-on-flag Exit when FLAG file exist 37 | -H, --header NAME:VALUE Set http header (can use more than once) 38 | --proxy-http IP:PORT Set http proxy 39 | --proxy-socks5 IP:PORT Set socks5 proxy 40 | --m3u8-base-url URL Set base URL of the m3u8 file 41 | 42 | Set KEY (and IV) for AES-128 decrypt. Use HEX format, base64 format, 43 | or local binary file. Use ID to set multi-keys. 44 | 45 | --m3u8-key [ID:]HEX 46 | --m3u8-iv [ID:]HEX 47 | --m3u8-key-base64 [ID:]BASE64 48 | --m3u8-iv-base64 [ID:]BASE64 49 | --m3u8-key-file [ID::]FILE 50 | --m3u8-iv-file [ID::]FILE 51 | 52 | --version Show version of this program 53 | --help Show this help text 54 | More information online 55 | $ 56 | ``` 57 | 58 | + **auto_retry** 59 | 60 | ``` 61 | $ node dist/auto_retry.js --help 62 | auto_retry [OPTIONS] M3U8 -- OPTIONS_FOR_M3U8_DL 63 | Usage: 64 | -o, --output DIR Download files to this Directory 65 | 66 | --retry NUM Retry times (default: 3) 67 | --use-raw-m3u8 Use `raw.m3u8` file for retry 68 | --sleep SEC Sleep seconds before next retry (default: 1) 69 | 70 | --remove-part-files Remove all `.part` files before run m3u8_dl-js 71 | 72 | --version Show version of this program 73 | --help Show this help text 74 | More information online 75 | $ 76 | ``` 77 | 78 | + **show_dl_speed** 79 | 80 | ``` 81 | $ node dist/show_dl_speed.js --help 82 | show_dl_speed [OPTIONS] [DIR] 83 | Usage: 84 | 85 | --put-exit-flag Enable retry function (put flag file) 86 | 87 | --retry-after SEC Retry after this seconds (default: 10) 88 | --retry-hide SEC Hide retry debug info in this seconds (default: 5) 89 | --init-wait SEC Wait seconds for init_wait mode (default: 20) 90 | 91 | --version Show version of this program 92 | --help Show this help text 93 | More information online 94 | $ 95 | ``` 96 | 97 | 98 | ## LICENSE 99 | 100 | `GNU GPL v3+` 101 | 102 | 103 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/dist.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/m3u8_dl-js_GUI/3e94310ece920ca82735d15ac82f7001279da02d/Tools/m3u8_dl-js-master/dist.zip -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/makefile: -------------------------------------------------------------------------------- 1 | 2 | target: build play-server 3 | .PHONY: target 4 | 5 | build: 6 | node ./node_modules/.bin/coffee -o dist/ src/ 7 | .PHONY: build 8 | 9 | play-server: 10 | node ./node_modules/.bin/coffee -o dist/play_server o/play_server/ 11 | .PHONY: play-server 12 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/o/play_server/make_m3u8.coffee: -------------------------------------------------------------------------------- 1 | # make_m3u8.coffee, m3u8_dl-js/o/play_server/ 2 | 3 | # build a m3u8 file (text) from m3u8_dl-js 's .meta.json file 4 | make_m3u8 = (meta) -> 5 | m = meta.m3u8_info 6 | 7 | o = [] 8 | # m3u8 headers 9 | o.push '#EXTM3U' 10 | o.push '#EXT-X-VERSION:3' 11 | o.push "#EXT-X-TARGETDURATION:#{m.target_duration}" 12 | # add clips 13 | for c in m.clip 14 | o.push "#EXTINF:#{c.time_s}," 15 | o.push c.name.ts 16 | # m3u8 end 17 | o.push '#EXT-X-ENDLIST' 18 | 19 | # done 20 | o.join('\n') + '\n' 21 | 22 | module.exports = make_m3u8 23 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/o/play_server/play_server.coffee: -------------------------------------------------------------------------------- 1 | # play_server.coffee, m3u8_dl-js/o/play_server/ 2 | path = require 'path' 3 | http = require 'http' 4 | 5 | express = require 'express' 6 | 7 | async_ = require '../async' 8 | util = require '../util' 9 | config = require '../config' 10 | 11 | make_m3u8 = require './make_m3u8' 12 | 13 | 14 | MIME_TYPE = { 15 | m3u8: 'application/x-mpegURL' 16 | ts: 'video/MP2T' 17 | } 18 | 19 | _etc = { 20 | meta: null # loaded meta data 21 | 22 | port: 8800 # port to listen 23 | } 24 | 25 | 26 | _serve_m3u8 = (req, res) -> 27 | u = req.path 28 | if u is '/raw.m3u8' 29 | # check and load meta data 30 | if await async_.file_exist config.META_FILE 31 | text = await async_.read_file config.META_FILE 32 | _etc.meta = JSON.parse text 33 | # TODO check meta.p_version ? 34 | m3u8 = make_m3u8 _etc.meta 35 | data = Buffer.from m3u8 36 | res.status 200 37 | res.set 'Content-Type', MIME_TYPE.m3u8 38 | res.set 'Content-Length', data.length 39 | res.send data 40 | res.end() 41 | else 42 | # DEBUG 43 | console.log "DEBUG: meta file `#{config.META_FILE}` not exist !" 44 | _res_code res, 404 45 | else # `XXXX.ts` file 46 | if ! _etc.meta? 47 | _res_code res, 404 48 | else # check is one clip name 49 | for c in _etc.meta.m3u8_info.clip 50 | if u is ('/' + c.name.ts) 51 | # check file exist 52 | if await async_.file_exist c.name.ts 53 | # send this clip 54 | filepath = path.resolve c.name.ts 55 | 56 | res.sendFile filepath, { 57 | headers: { 58 | 'Content-Type': MIME_TYPE.ts 59 | } 60 | } 61 | else 62 | _res_code res, 404 63 | return 64 | _res_code res, 404 65 | 66 | _res_code = (res, code) -> 67 | text = Buffer.from "HTTP #{code} #{http.STATUS_CODES[code]}\n" 68 | res.status code 69 | res.set 'Content-Type', 'text/plain' 70 | res.set 'Content-Length', text.length 71 | res.send text 72 | res.end() 73 | 74 | _init_server = -> 75 | app = express() 76 | serve_m3u8 = express() 77 | # /m3u8_dl-js/play_m3u8/ -> _serve_m3u8 78 | serve_m3u8.all '*', (req, res) -> 79 | # check method 80 | if req.method != 'GET' 81 | _res_code res, 405 82 | return 83 | _serve_m3u8(req, res).catch( (err) -> 84 | # FIXME 85 | console.log err.stack 86 | 87 | _res_code res, 500 88 | ).then () -> 89 | # TODO 90 | app.use '/m3u8_dl-js/play_m3u8/', serve_m3u8 91 | 92 | # TODO static page ? 93 | root = express() 94 | root.all '*', (req, res) -> 95 | # check method 96 | if req.method != 'GET' 97 | _res_code res, 405 98 | return 99 | u = req.path # request url 100 | switch u 101 | #when '/' 102 | # / -> res 404 103 | # TODO for / 104 | # /m3u8 -> 302: /m3u8_dl-js/play_m3u8/raw.m3u8 105 | when '/m3u8' 106 | to = '/m3u8_dl-js/play_m3u8/raw.m3u8' 107 | res.redirect 302, to 108 | res.end() 109 | else # res 404 110 | _res_code res, 404 111 | app.use '/', root 112 | # init done 113 | app 114 | 115 | _normal = (a) -> 116 | if a.port? 117 | _etc.port = a.port 118 | # change working directory 119 | if a.root_dir? 120 | process.chdir a.root_dir 121 | # DEBUG 122 | console.log "DEBUG: working directory: #{process.cwd()}" 123 | 124 | app = _init_server() 125 | app.listen _etc.port, '127.0.0.1', () -> 126 | play_url = "http://127.0.0.1:#{_etc.port}/m3u8" 127 | if a.name? 128 | play_url += "?name=#{a.name}" 129 | # DEBUG 130 | console.log "Play at #{play_url}" 131 | 132 | _p_help = -> 133 | console.log ''' 134 | play_server [OPTIONS] [DIR] 135 | Usage: 136 | --port PORT Port to listen (http server) 137 | --name NAME Add a comment for the m3u8 to play 138 | 139 | --version Show version of this program 140 | --help Show this help text 141 | More information online 142 | ''' 143 | 144 | _p_arg = (args) -> 145 | rest = args 146 | _next = -> 147 | o = rest[0] 148 | rest = rest[1..] 149 | o 150 | 151 | o = {} 152 | while rest.length > 0 153 | one = _next() 154 | switch one 155 | when '--help', '--version' 156 | o.type = one 157 | when '--port' 158 | o.port = Number.parseInt _next() 159 | when '--name' 160 | o.name = _next() 161 | else # default: DIR 162 | o.root_dir = one 163 | o 164 | 165 | _start = -> 166 | a = _p_arg process.argv[2..] 167 | switch a.type 168 | when '--help' 169 | _p_help() 170 | when '--version' 171 | util.p_version() 172 | else 173 | _normal a 174 | 175 | _start() 176 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/o/play_static/play_m3u8.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/m3u8_dl-js_GUI/3e94310ece920ca82735d15ac82f7001279da02d/Tools/m3u8_dl-js-master/o/play_static/play_m3u8.html -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "coffeescript": "^2.0.0-beta2" 4 | }, 5 | "dependencies": { 6 | "express": "^4.15.3", 7 | "socks": "^1.1.10" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/async.coffee: -------------------------------------------------------------------------------- 1 | # async.coffee, m3u8_dl-js/src/ 2 | # TODO use node v8.0 util.promisify ? 3 | 4 | fs = require 'fs' 5 | child_process = require 'child_process' 6 | 7 | 8 | read_file = (file_path) -> 9 | new Promise (resolve, reject) -> 10 | fs.readFile file_path, 'utf8', (err, data) -> 11 | if err 12 | reject err 13 | else 14 | resolve data 15 | 16 | read_file_byte = (filename) -> 17 | new Promise (resolve, reject) -> 18 | fs.readFile filename, (err, data) -> 19 | if err 20 | reject err 21 | else 22 | resolve data 23 | 24 | write_file = (file_path, text) -> 25 | new Promise (resolve, reject) -> 26 | fs.writeFile file_path, text, 'utf8', (err) -> 27 | if err 28 | reject err 29 | else 30 | resolve() 31 | 32 | # move file 33 | mv = (from, to) -> 34 | new Promise (resolve, reject) -> 35 | fs.rename from, to, (err) -> 36 | if err 37 | reject err 38 | else 39 | resolve() 40 | 41 | # check if file exist 42 | file_exist = (file_path) -> 43 | new Promise (resolve, reject) -> 44 | fs.access file_path, fs.constants.R_OK, (err) -> 45 | if err 46 | resolve false 47 | else 48 | resolve true 49 | 50 | # if file not exist, return null 51 | get_file_size = (file_path) -> 52 | new Promise (resolve, reject) -> 53 | fs.stat file_path, (err, stats) -> 54 | if err # never reject 55 | resolve null 56 | else 57 | resolve stats.size 58 | 59 | list_dir = (file_path) -> 60 | new Promise (resolve, reject) -> 61 | fs.readdir file_path, (err, file_list) -> 62 | if err 63 | reject err 64 | else 65 | resolve file_list 66 | 67 | mkdir = (file_path) -> 68 | new Promise (resolve, reject) -> 69 | fs.mkdir file_path, (err) -> 70 | if err 71 | reject err 72 | else 73 | resolve() 74 | 75 | # for file-lock 76 | fs_open = (file_path, flags) -> 77 | new Promise (resolve, reject) -> 78 | fs.open file_path, flags, (err, fd) -> 79 | if err 80 | reject err 81 | else 82 | resolve fd 83 | 84 | fs_close = (fd) -> 85 | new Promise (resolve, reject) -> 86 | fs.close fd, (err) -> 87 | if err 88 | reject err 89 | else 90 | resolve() 91 | 92 | 93 | # remove file 94 | rm = (file_path) -> 95 | new Promise (resolve, reject) -> 96 | fs.unlink file_path, (err) -> 97 | if err 98 | reject err 99 | else 100 | resolve() 101 | 102 | # sleep: setTimeout 103 | sleep = (time_ms) -> 104 | new Promise (resolve, reject) -> 105 | _callback = -> 106 | resolve() # never reject 107 | setTimeout _callback, time_ms 108 | 109 | # run shell command, pipe stdin -> stdin, stdout -> stdout, stderr -> stderr, return exit_code 110 | run_cmd = (args) -> 111 | new Promise (resolve, reject) -> 112 | cmd = args[0] 113 | rest = args[1..] 114 | # DEBUG 115 | console.log " run -> #{args.join(' ')}" 116 | p = child_process.spawn cmd, rest, { 117 | stdio: 'inherit' 118 | } 119 | p.on 'error', (err) -> 120 | reject err 121 | p.on 'exit', (exit_code) -> 122 | resolve exit_code 123 | 124 | 125 | module.exports = { 126 | read_file # async 127 | read_file_byte # async 128 | write_file # async 129 | 130 | mv # async 131 | rm # async 132 | file_exist # async 133 | get_file_size # async 134 | list_dir # async 135 | 136 | mkdir # async 137 | fs_open # async 138 | fs_close # async 139 | 140 | sleep # async 141 | run_cmd # async 142 | } 143 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/auto_retry.coffee: -------------------------------------------------------------------------------- 1 | # auto_retry.coffee, m3u8_dl-js/src/ 2 | path = require 'path' 3 | 4 | async_ = require './async' 5 | util = require './util' 6 | log = require './log' 7 | config = require './config' 8 | dl_speed = require './dl_speed' 9 | 10 | 11 | _p_help = -> 12 | console.log ''' 13 | auto_retry [OPTIONS] M3U8 -- OPTIONS_FOR_M3U8_DL 14 | Usage: 15 | -o, --output DIR Download files to this Directory 16 | 17 | --retry NUM Retry times (default: 3) 18 | --use-raw-m3u8 Use `raw.m3u8` file for retry 19 | --sleep SEC Sleep seconds before next retry (default: 1) 20 | 21 | --remove-part-files Remove all `.part` files before run m3u8_dl-js 22 | 23 | --version Show version of this program 24 | --help Show this help text 25 | More information online 26 | ''' 27 | 28 | _p_arg = (args) -> 29 | rest = args 30 | _next = -> 31 | o = rest[0] 32 | rest = rest[1..] 33 | o 34 | 35 | o = { 36 | pass: [] # arguments passed to m3u8_dl 37 | } 38 | start_pass = false 39 | while rest.length > 0 40 | one = _next() 41 | if start_pass 42 | o.pass.push one 43 | continue 44 | switch one 45 | when '--help', '--version' 46 | o.type = one 47 | when '-o', '--output' 48 | config.output_dir _next() 49 | 50 | when '--retry' 51 | o.retry = Number.parseInt _next() 52 | # NaN for retry Infinity 53 | if o.retry < 1 54 | throw new Error "bad retry times #{o.retry}" 55 | when '--sleep' 56 | o.retry_sleep = JSON.parse _next() 57 | if (typeof o.retry_sleep != 'number') || Number.isNaN(o.retry_sleep) || (o.retry_sleep < 0) 58 | throw new Error "bad retry sleep #{o.retry_sleep}" 59 | when '--use-raw-m3u8' 60 | o.use_raw_m3u8 = true 61 | when '--remove-part-files' 62 | o.remove_part_files = true 63 | when '--' 64 | start_pass = true 65 | 66 | else # default: M3U8 67 | o.m3u8 = one 68 | o 69 | 70 | # for _normal tasks 71 | 72 | _etc = { 73 | pass: [] 74 | retry: config.DEFAULT_RETRY_TIMES 75 | retry_sleep: config.DEFAULT_RETRY_SLEEP 76 | use_raw_m3u8: false 77 | remove_part_files: false 78 | m3u8: null 79 | 80 | retry_count: 0 81 | last_clip_done: 0 82 | 83 | all_retry_count: 0 # not reset this count 84 | } 85 | 86 | M3U8_DL_BIN = './m3u8_dl.js' 87 | _run_m3u8_dl = (args) -> 88 | # check lock file before run m3u8_dl 89 | if await async_.file_exist config.LOCK_FILE 90 | throw new Error "lock file `#{path.resolve config.LOCK_FILE}` already exist " 91 | # check remove_part_files 92 | if _etc.remove_part_files && (await async_.file_exist(config.META_FILE)) 93 | log.d "AUTO_RETRY: remove all `.part` files .. . " 94 | await dl_speed.load_meta_file() 95 | clip = dl_speed.get_meta().m3u8_info.clip 96 | for c in clip 97 | if await async_.file_exist c.name.part 98 | await async_.rm c.name.part 99 | 100 | node = process.argv[0] 101 | m3u8_dl = path.join __dirname, M3U8_DL_BIN 102 | 103 | arg = [node, m3u8_dl].concat args 104 | await async_.run_cmd arg 105 | 106 | # return `true` to exit 107 | _retry_loop_one = -> 108 | # check meta file exist 109 | if ! await async_.file_exist config.META_FILE 110 | log.w "AUTO_RETRY: meta file `#{path.resolve config.META_FILE}` not exist ! " 111 | meta_exist = false 112 | task_done = false 113 | cost_retry = true 114 | else # load meta file and check task finished 115 | await dl_speed.load_meta_file() 116 | task_done = await dl_speed.update() 117 | 118 | meta_exist = true 119 | # check cost retry 120 | clip_count = dl_speed.get_clip_count() 121 | # DEBUG 122 | log.d "AUTO_RETRY: clip count: #{clip_count.done}/#{clip_count.all} (last #{_etc.last_clip_done} done)" 123 | clip_done = clip_count.done 124 | if clip_done > _etc.last_clip_done 125 | cost_retry = false 126 | else 127 | cost_retry = true 128 | _etc.last_clip_done = clip_done # update clip done 129 | # check task done 130 | if task_done 131 | log.p "AUTO_RETRY: [ OK ] task done. (retry #{_etc.all_retry_count} times)" 132 | return true 133 | # check cost retry 134 | if ! cost_retry 135 | log.d "AUTO_RETRY: reset retry count " 136 | _etc.retry_count = 0 137 | else 138 | if _etc.retry_count >= _etc.retry 139 | log.e "AUTO_RETRY: give up after retry #{_etc.retry_count} times " 140 | # TODO process.exit(1) ? 141 | return true 142 | else 143 | log.d "AUTO_RETRY: retry #{_etc.retry_count}/#{_etc.retry}" 144 | # inc retry count 145 | if cost_retry 146 | _etc.retry_count += 1 147 | _etc.all_retry_count += 1 148 | # retry sleep 149 | if _etc.retry_sleep > 0 150 | log.d "AUTO_RETRY: sleep #{_etc.retry_sleep} seconds before next retry " 151 | await async_.sleep(_etc.retry_sleep * 1e3) 152 | # retry: check use_raw_m3u8 153 | if (! meta_exist) || (! _etc.use_raw_m3u8) 154 | arg = _etc.pass.concat [_etc.m3u8] 155 | else 156 | log.d "AUTO_RETRY: enable use_raw_m3u8 " 157 | 158 | base_url = dl_speed.get_meta().m3u8_base_url 159 | if base_url? 160 | arg = ['--m3u8-base-url', base_url, config.RAW_M3U8] 161 | else 162 | arg = [config.RAW_M3U8] 163 | await _run_m3u8_dl _etc.pass.concat(arg) 164 | # not exit 165 | false 166 | 167 | _normal = (a) -> 168 | # change cwd 169 | await util.check_change_cwd true # create dir if not exist 170 | # save arguments 171 | _etc.pass = a.pass 172 | if a.retry? 173 | _etc.retry = a.retry 174 | if a.retry_sleep? 175 | _etc.retry_sleep = a.retry_sleep 176 | _etc.use_raw_m3u8 = a.use_raw_m3u8 177 | _etc.remove_part_files = a.remove_part_files 178 | _etc.m3u8 = a.m3u8 179 | 180 | # run m3u8_dl for the first time 181 | arg = _etc.pass.concat [_etc.m3u8] 182 | log.d "AUTO_RETRY: first run m3u8_dl " 183 | await _run_m3u8_dl arg 184 | # enter retry loop 185 | while true 186 | if await _retry_loop_one() 187 | break 188 | 189 | main = (argv) -> 190 | try 191 | a = _p_arg argv 192 | catch e 193 | util.p_bad_command_line() 194 | process.exit 1 # bad command line 195 | switch a.type 196 | when '--help' 197 | _p_help() 198 | when '--version' 199 | util.p_version() 200 | else 201 | await _normal a 202 | # FIX process will not exit 203 | process.exit 0 204 | 205 | _start = -> 206 | try 207 | await main(process.argv[2..]) 208 | catch e 209 | # DEBUG 210 | console.log "ERROR: #{e.stack}" 211 | #throw e 212 | process.exit 1 213 | _start() 214 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/config.coffee: -------------------------------------------------------------------------------- 1 | # config.coffee, m3u8_dl-js/src/ 2 | 3 | # m3u8_dl program version 4 | P_VERSION = 'm3u8_dl-js version 0.3.0-5 test20170614 2148' 5 | 6 | 7 | # local file struct 8 | LOCK_FILE = 'm3u8_dl.lock' 9 | META_FILE = 'm3u8_dl.meta.json' 10 | EXIT_FLAG_FILE = 'm3u8_dl.exit.flag' # exit download process if this file exist 11 | RAW_M3U8 = 'raw.m3u8' 12 | RAW_KEY = ['raw.', '.key'] # the key for m3u8 (support multi-keys) 13 | LIST_FILE = 'ffmpeg_merge.list' 14 | # clip file 15 | CLIP_SUFFIX_DL_PART = '.ts.part' 16 | CLIP_SUFFIX_ENCRYPTED = '.ts.encrypted' 17 | CLIP_SUFFIX_TS = '.ts' 18 | # TODO support not '.ts' in m3u8 ? 19 | 20 | # auto retry 21 | DEFAULT_RETRY_TIMES = 3 22 | DEFAULT_RETRY_SLEEP = 1 23 | 24 | 25 | _etc = { 26 | # support multi-keys 27 | m3u8_key: {} 28 | m3u8_iv: {} 29 | } 30 | 31 | # get / set proxy 32 | proxy = (p) -> 33 | if p? 34 | _etc.proxy = p 35 | _etc.proxy 36 | 37 | m3u8_base_url = (base) -> 38 | if base? 39 | _etc.m3u8_base_url = base 40 | _etc.m3u8_base_url 41 | 42 | m3u8_key = (i, k) -> 43 | if k? 44 | _etc.m3u8_key[i] = k 45 | _etc.m3u8_key[i] 46 | 47 | m3u8_iv = (i, iv) -> 48 | if iv? 49 | _etc.m3u8_iv[i] = iv 50 | _etc.m3u8_iv[i] 51 | 52 | get_all_m3u8_key = -> 53 | _etc.m3u8_key 54 | 55 | get_all_m3u8_iv = -> 56 | _etc.m3u8_iv 57 | 58 | dl_thread = (t) -> 59 | if t? 60 | _etc.dl_thread = t 61 | _etc.dl_thread 62 | 63 | # download files to (default current dir) 64 | output_dir = (d) -> 65 | if d? 66 | _etc.output_dir = d 67 | _etc.output_dir 68 | 69 | auto_remove = (a) -> 70 | if a? 71 | _etc.auto_remove = a 72 | _etc.auto_remove 73 | 74 | # TODO support set different headers for different request ? 75 | headers = (h) -> 76 | if h? 77 | _etc.headers = h 78 | _etc.headers 79 | 80 | exit_on_flag = (f) -> 81 | if f? 82 | _etc.exit_on_flag = f 83 | _etc.exit_on_flag 84 | 85 | lock_file_fd = (fd) -> 86 | if fd? 87 | _etc.lock_file_fd = fd 88 | _etc.lock_file_fd 89 | 90 | curl_bin = (bin) -> 91 | if bin? 92 | _etc.curl_bin = bin 93 | _etc.curl_bin 94 | 95 | 96 | module.exports = { 97 | P_VERSION 98 | 99 | LOCK_FILE 100 | META_FILE 101 | EXIT_FLAG_FILE 102 | RAW_M3U8 103 | RAW_KEY 104 | LIST_FILE 105 | 106 | CLIP_SUFFIX_DL_PART 107 | CLIP_SUFFIX_ENCRYPTED 108 | CLIP_SUFFIX_TS 109 | 110 | DEFAULT_RETRY_TIMES 111 | DEFAULT_RETRY_SLEEP 112 | 113 | 114 | proxy # get / set 115 | m3u8_base_url # get /set 116 | m3u8_key # get / set 117 | m3u8_iv # get / set 118 | get_all_m3u8_key 119 | get_all_m3u8_iv 120 | 121 | dl_thread # get / set 122 | output_dir # get / set 123 | auto_remove # get / set 124 | headers # get / set 125 | 126 | exit_on_flag # get / set 127 | lock_file_fd # get / set 128 | 129 | curl_bin # get / set 130 | } 131 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/decrypt.coffee: -------------------------------------------------------------------------------- 1 | # decrypt.coffee, m3u8_dl-js/src/ 2 | crypto = require 'crypto' 3 | 4 | log = require './log' 5 | 6 | 7 | # decrypt m3u8 with `aes-128-cbc` (for '#EXT-X-KEY:METHOD=AES-128,') 8 | create_decrypt_stream = (key, iv) -> 9 | # if key is str, process as hex 10 | if typeof key is 'string' 11 | key = Buffer.from key, 'hex' 12 | # else: key should be a buffer 13 | if ! key instanceof Buffer 14 | throw new Error('key is not Buffer') 15 | # check key length: 128bit (8 Byte) 16 | if key.length != (128 / 8) 17 | log.w "decrypt: key length (#{key.length * 8}) is not 128bit !" 18 | # if iv in number, make it as buffer 19 | if typeof iv is 'number' 20 | i = iv 21 | # iv is 128bit, as BE 22 | iv_size = 128 / 8 23 | iv = Buffer.alloc(iv_size) 24 | iv.writeUInt32BE i, iv_size - 4 25 | # TODO more check for iv 26 | 27 | crypto.createDecipheriv 'aes-128-cbc', key, iv 28 | 29 | module.exports = { 30 | create_decrypt_stream 31 | } 32 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/dl_clip.coffee: -------------------------------------------------------------------------------- 1 | # dl_clip.coffee, m3u8_dl-js/src/ 2 | 3 | fs = require 'fs' 4 | 5 | async_ = require './async' 6 | log = require './log' 7 | config = require './config' 8 | decrypt = require './decrypt' 9 | key_host = require './key_host' 10 | 11 | dl_with_proxy = require './dl_with_proxy' 12 | dl_with_curl = require './dl_with_curl' 13 | 14 | 15 | _decrypt_clip = (clip) -> 16 | # support multi-keys 17 | _do_decrypt = (c, name) -> 18 | new Promise (resolve, reject) -> 19 | r = fs.createReadStream name.encrypted 20 | w = fs.createWriteStream name.ts_tmp 21 | r.pipe(c).pipe(w) 22 | r.on 'error', (err) -> 23 | reject err 24 | c.on 'error', (err) -> 25 | reject err 26 | w.on 'error', (err) -> 27 | reject err 28 | w.on 'finish', () -> 29 | resolve() 30 | before_size = await async_.get_file_size clip.name.encrypted 31 | 32 | key_id = clip.key_id 33 | iv = config.m3u8_iv key_id 34 | if ! iv? 35 | iv = clip.media_sequence 36 | key = await key_host.get_key key_id 37 | 38 | c = decrypt.create_decrypt_stream(key, iv) 39 | await _do_decrypt c, clip.name 40 | 41 | after_size = await async_.get_file_size clip.name.ts_tmp 42 | # check decrypt success (by file_size) 43 | if ! (after_size < before_size) # after remove padding 44 | log.w "#{clip.name.ts}: decrypt MAY fail ! (before_size = #{before_size}, after_size = #{after_size}) " 45 | else # check auto remove 46 | if config.auto_remove() 47 | log.d "auto remove #{clip.name.encrypted}" 48 | await async_.rm clip.name.encrypted 49 | 50 | dl_one = (file_url, filename) -> 51 | # check use which downloader 52 | if config.curl_bin()? 53 | await dl_with_curl file_url, filename 54 | else 55 | await dl_with_proxy file_url, filename 56 | 57 | # support multi-keys 58 | dl_clip = (m3u8_info, index) -> 59 | clip = m3u8_info.clip[index] 60 | # check already exist and skip it 61 | if await async_.file_exist(clip.name.ts) 62 | log.d "dl_clip: skip exist file #{clip.name.ts}" 63 | return 64 | # load key before download clip 65 | if clip.key_id? 66 | await key_host.get_key clip.key_id 67 | # download file (support proxy) 68 | try 69 | clip_url = clip.clip_url 70 | # DEBUG 71 | log.d "dl_clip: #{clip.name.ts}: #{clip_url}" 72 | await dl_one clip_url, clip.name.part 73 | catch e 74 | log.e "dl_clip: #{clip.name.ts}: download error ! " 75 | # print stack in multi-thread mode 76 | if config.dl_thread()? && (config.dl_thread() > 1) 77 | console.log e.stack 78 | throw e 79 | # check need decrypt clip 80 | if clip.key_id? 81 | # download one file done, rename it 82 | await async_.mv clip.name.part, clip.name.encrypted 83 | await _decrypt_clip clip 84 | await async_.mv clip.name.ts_tmp, clip.name.ts 85 | else # no need to decrypt 86 | await async_.mv clip.name.part, clip.name.ts 87 | # download one clip done 88 | 89 | dl_clip.dl_one = dl_one # async 90 | module.exports = dl_clip # async 91 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/dl_speed.coffee: -------------------------------------------------------------------------------- 1 | # dl_speed.coffee, m3u8_dl-js/src/ 2 | 3 | async_ = require './async' 4 | util = require './util' 5 | log = require './log' 6 | config = require './config' 7 | 8 | 9 | # global config 10 | UPDATE_TIME = 1e3 # refresh every 1s (1000 ms) 11 | 12 | _TIME_LEFT_SPEED = 5 # use 5s 's speed to calc time_left 13 | 14 | 15 | _etc = { 16 | # cache `.ts` file size 17 | ts_file_size: {} 18 | 19 | # meta file data 20 | meta: null 21 | 22 | # speed info for print 23 | speed_info: { 24 | percent: 0 # 0: 0.0 % ; 1: 100% 25 | dl_speed: 0 # unit: Byte 26 | clip_count: { 27 | done: 0 28 | doing: 0 29 | all: 0 30 | } 31 | dl_size: 0 # downloaded size (unit: Byte ) 32 | all_size: null # unit: Byte ( `null` for unknow) 33 | time_left: null # unit: second ( `null` for unknow) 34 | } 35 | 36 | # history data (to make speed_info) 37 | last_dl_size: [ 0 ] 38 | } 39 | 40 | 41 | load_meta_file = -> 42 | text = await async_.read_file config.META_FILE 43 | _etc.meta = JSON.parse text 44 | # check meta.p_version 45 | if _etc.meta.p_version != config.P_VERSION 46 | log.w "META file version mismatch: #{_etc.meta.p_version}" 47 | 48 | 49 | _count_file_size = -> 50 | # get file list (current dir) 51 | l = await async_.list_dir '.' 52 | fl = {} 53 | for i in l 54 | fl[i] = true 55 | # scan clip file (in meta) and get file size 56 | m = _etc.meta.m3u8_info 57 | o = { 58 | ts: { # count of `.ts` files 59 | count: 0 # number of `.ts` files 60 | size: 0 # files size in Byte 61 | time_s: 0 # sum of clip time in second 62 | } 63 | encrypted: { # count of `.encrypted` files (not include `.ts` file) 64 | count: 0 65 | size: 0 66 | } 67 | part: { # `.part` files 68 | count: 0 69 | size: 0 70 | } 71 | time_s: 0 # count of all clips 72 | count: m.clip.length 73 | } 74 | _count_one = (clip) -> 75 | # count clip time_s 76 | clip_time_s = clip.time_s 77 | if ! (clip_time_s >= 0) 78 | clip_time_s = 0 79 | o.time_s += clip_time_s 80 | # check `.ts` file 81 | ts = clip.name.ts 82 | if fl[ts] # `.ts` file exist 83 | # cache `.ts` file size 84 | if ! _etc.ts_file_size[ts]? 85 | _etc.ts_file_size[ts] = await async_.get_file_size ts 86 | size = _etc.ts_file_size[ts] 87 | 88 | o.ts.count += 1 89 | o.ts.size += size 90 | o.ts.time_s += clip_time_s 91 | return 92 | # check `.encrypted` file 93 | en = clip.name.encrypted 94 | if fl[en] 95 | size = await async_.get_file_size en 96 | 97 | o.encrypted.count += 1 98 | o.encrypted.size += size 99 | return 100 | # check `.part` file 101 | pa = clip.name.part 102 | if fl[pa] 103 | size = await async_.get_file_size pa 104 | 105 | o.part.count += 1 106 | o.part.size += size 107 | return 108 | # count each clip 109 | for c in m.clip 110 | await _count_one c 111 | o 112 | 113 | _calc_speed_info = (fl) -> 114 | exit_flag = false 115 | 116 | si = _etc.speed_info 117 | # percent 118 | if fl.ts.count is fl.count 119 | si.percent = 1 # download done 120 | exit_flag = true 121 | else 122 | si.percent = fl.ts.count / fl.count 123 | # clip count 124 | si.clip_count.done = fl.ts.count 125 | si.clip_count.doing = fl.part.count + fl.encrypted.count 126 | si.clip_count.all = fl.count 127 | # dl_size 128 | si.dl_size = fl.ts.size + fl.encrypted.size + fl.part.size 129 | # dl_speed 130 | si.dl_speed = si.dl_size - _etc.last_dl_size[0] 131 | # process history 132 | _etc.last_dl_size = [si.dl_size].concat _etc.last_dl_size 133 | if _etc.last_dl_size.length > (_TIME_LEFT_SPEED + 1) 134 | _etc.last_dl_size.pop() 135 | # FIX last_dl_size (last 0 item) (fix calc download speed, time_left) 136 | if (_etc.last_dl_size.length > 1) && (_etc.last_dl_size[_etc.last_dl_size.length - 1] is 0) 137 | _etc.last_dl_size.pop() 138 | # all_size 139 | if fl.ts.count < 1 140 | si.all_size = null 141 | else if fl.ts.count is fl.count 142 | si.all_size = fl.ts.size 143 | else 144 | # calc average bitrate 145 | br = fl.ts.size / fl.ts.time_s 146 | si.all_size = br * fl.time_s 147 | # time_left 148 | dl_delta = si.dl_size - _etc.last_dl_size[_etc.last_dl_size.length - 1] 149 | if dl_delta < 1 150 | avg_speed = null 151 | else 152 | avg_speed = dl_delta / (_etc.last_dl_size.length - 1) 153 | if (! si.all_size?) || (! avg_speed?) 154 | si.time_left = null 155 | else 156 | rest_byte = si.all_size - si.dl_size 157 | if rest_byte <= 0 158 | si.time_left = 0 159 | else 160 | si.time_left = rest_byte / avg_speed 161 | # done 162 | exit_flag 163 | 164 | update = -> 165 | fl = await _count_file_size() 166 | _calc_speed_info fl # return exit_flag ( `true` to exit) 167 | 168 | 169 | # for print_speed 170 | 171 | # right indent 172 | _r = (o, len) -> 173 | while o.length < len 174 | o = ' ' + o 175 | o 176 | 177 | _print_percent = (percent) -> 178 | if percent is 1 179 | o = '100' 180 | else 181 | o = (percent * 1e2).toFixed 1 182 | o += ' %' 183 | # right indent self 184 | _r o, 6 # '99.9 %', '100 %' 185 | 186 | _print_dl_speed = (speed) -> 187 | U = 1024 188 | if speed <= U # < 1 KB/s 189 | o = speed + ' Byte/s' 190 | else if speed <= (U * U) # < 1 MB/s 191 | o = Math.round(speed / U) + ' KB/s' 192 | else if speed <= (U * U * U) # < 1 GB/s 193 | o = (speed / (U * U)).toFixed(1) + ' MB/s' 194 | else 195 | o = (speed / (U * U * U)).toFixed(1) + ' GB/s' 196 | # right indent self 197 | _r o, 11 # '1024 Byte/s', '1024 KB/s', '1023.9 MB/s' 198 | 199 | _print_clip_count = (c) -> 200 | o = "(#{c.done}/#{c.doing}/#{c.all})" 201 | # right indent self 202 | _r o, c.all.toString().length * 3 + ('(//)').length 203 | 204 | _print_dl_size_all = (dl_size, all_size) -> 205 | U = 1024 206 | # dl_size 207 | if dl_size < 1 208 | dl = '0' 209 | else if dl_size <= U # < 1 KB 210 | dl = dl_size + ' Byte' 211 | else if dl_size <= (U * U) # < 1 MB 212 | dl = Math.round(dl_size / U) + ' KB' 213 | else if dl_size <= (U * U * U) # < 1 GB 214 | dl = (dl_size / (U * U)).toFixed(1) + ' MB' 215 | else 216 | dl = (dl_size / (U * U * U)).toFixed(1) + ' GB' 217 | # all_size 218 | if (! all_size?) || (all_size < (U * U)) # < 1 MB 219 | all = 'unknow' 220 | else if all_size is dl_size 221 | all = dl 222 | else if all_size < (U * U * U) # < 1 GB 223 | all = Math.round(all_size / (U * U)).toFixed(1) + ' MB' 224 | else 225 | all = (all_size / (U * U * U)).toFixed(1) + ' GB' 226 | o = dl + '/ ' + all 227 | # indent self (9 + 2 + 9) 228 | _r o, 20 # '0', '1024 Byte', '1024 KB', '1023.9 MB'; 'unknow', '1023.9 MB' 229 | 230 | print_time = (time_s) -> 231 | _add_zero = (raw) -> 232 | o = raw.toString() 233 | if o.length < 2 234 | o = '0' + o 235 | o 236 | 237 | if ! time_s? 238 | o = 'UNKNOW' 239 | else 240 | if time_s != 0 241 | time_s = Number.parseInt(time_s) + 1 242 | m = Number.parseInt(time_s / 60) 243 | s = time_s - m * 60 # second 244 | h = Number.parseInt(m / 60) # hour 245 | m -= h * 60 # minute 246 | 247 | o = _add_zero(m) + ':' + _add_zero(s) 248 | if h > 0 249 | o = _add_zero(h) + ':' + o 250 | o 251 | 252 | _print_rest_time = (time_s) -> 253 | # indent self 254 | _r print_time(time_s), 8 # '00:00:00', 'UNKNOW' 255 | 256 | print_speed = -> 257 | si = _etc.speed_info 258 | 259 | prefix = '->' 260 | percent = _print_percent si.percent 261 | speed = _print_dl_speed si.dl_speed 262 | clip = _print_clip_count si.clip_count 263 | dl_all = _print_dl_size_all si.dl_size, si.all_size 264 | time = _print_rest_time si.time_left 265 | 266 | # TODO print clip_time/all_time ? 267 | 268 | # output line 269 | "#{prefix} #{percent} #{speed} #{clip} #{dl_all} #{time}" 270 | 271 | # exports function 272 | 273 | get_dl_speed = -> 274 | _etc.speed_info.dl_speed 275 | 276 | get_clip_count = -> 277 | _etc.speed_info.clip_count 278 | 279 | get_time_left = -> 280 | _etc.speed_info.time_left 281 | 282 | get_meta = -> 283 | _etc.meta 284 | 285 | module.exports = { 286 | UPDATE_TIME 287 | 288 | load_meta_file # async 289 | update # async 290 | 291 | print_speed 292 | print_time 293 | 294 | get_dl_speed 295 | get_clip_count 296 | get_time_left 297 | get_meta 298 | } 299 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/dl_with_curl.coffee: -------------------------------------------------------------------------------- 1 | # dl_with_curl.coffee, m3u8_dl-js/src/ 2 | 3 | util = require './util' 4 | log = require './log' 5 | config = require './config' 6 | 7 | dl_with_curl = (file_url, filename) -> 8 | # make curl args 9 | arg = ['-Ss', '-L'] 10 | # support proxy 11 | proxy = config.proxy() 12 | if proxy? 13 | switch proxy.type 14 | when 'http' 15 | arg.push '--proxy' 16 | arg.push "http://#{proxy.hostname}:#{proxy.port}" 17 | when 'socks5' 18 | arg.push '--proxy' 19 | arg.push "socks5://#{proxy.hostname}:#{proxy.port}" 20 | else 21 | throw new Error "unknow proxy.type #{proxy.type}" 22 | # add custom headers 23 | ch = config.headers() 24 | if ch? 25 | for i of ch 26 | arg.push '-H' 27 | arg.push "#{i}: #{ch[i]}" 28 | # normal options 29 | CURL_BIN = config.curl_bin() 30 | args = [CURL_BIN].concat(arg).concat ['-o', filename, file_url] 31 | # TODO remove `*.part` file before call curl ? 32 | # call curl 33 | await util.run_check args 34 | 35 | module.exports = dl_with_curl # async 36 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/dl_with_proxy.coffee: -------------------------------------------------------------------------------- 1 | # dl_with_proxy.coffee, m3u8_dl-js/src/ 2 | 3 | fs = require 'fs' 4 | url = require 'url' 5 | http = require 'http' 6 | https = require 'https' 7 | 8 | socks = require 'socks' 9 | 10 | log = require './log' 11 | config = require './config' 12 | 13 | 14 | # HTTP 200 OK 15 | # HTTP 301 Moved Permanently 16 | # HTTP 302 Found 17 | # HTTP 307 Temporary Redirect 18 | # HTTP 308 Permanent Redirect 19 | _do_dl = (opt, filename, protocol) -> 20 | _req_res = (opt, protocol) -> 21 | new Promise (resolve, reject) -> 22 | _on_res = (res) -> 23 | resolve [req, res] 24 | # make http/https request 25 | switch protocol 26 | when 'http:' 27 | req = http.request opt, _on_res 28 | when 'https:' 29 | opt.rejectUnauthorized = false 30 | req = https.request opt, _on_res 31 | else 32 | reject new Error "unknow protocol `#{protocol}`" 33 | # FIXME support https-proxy ? 34 | req.on 'error', (err) -> 35 | reject err 36 | req.on 'aborted', (err) -> 37 | if err? 38 | reject err 39 | else 40 | reject new Error "aborted" 41 | # DO start request 42 | req.end() 43 | _save_file = (res, filename) -> 44 | new Promise (resolve, reject) -> 45 | # TODO process gzip compress ? 46 | # create write stream 47 | w = fs.createWriteStream filename 48 | res.pipe(w) 49 | res.on 'error', (err) -> 50 | reject err 51 | w.on 'error', (err) -> 52 | reject err 53 | w.on 'finish', () -> 54 | resolve() 55 | 56 | [req, res] = await _req_res opt, protocol 57 | # check res code 58 | code = res.statusCode 59 | switch code 60 | when 302 61 | location = res.headers['location'] 62 | log.d "dl_with_proxy: #{filename}: 302 location #{location}" 63 | # download again 64 | await dl_with_proxy location, filename 65 | when 200 # http 200 OK 66 | await _save_file res, filename 67 | else # unknow code 68 | throw new Error "unknow res code `#{code}`" 69 | 70 | # do a simple http GET download a file throw the proxy config 71 | dl_with_proxy = (file_url, filename) -> 72 | info = url.parse file_url 73 | # check proxy 74 | # TODO FIXME support https with proxy ? 75 | proxy = config.proxy() 76 | if proxy? 77 | switch proxy.type 78 | when 'http' 79 | opt = { 80 | hostname: proxy.hostname 81 | port: proxy.port 82 | path: file_url 83 | headers: { 84 | 'Host': info.hostname 85 | } 86 | } 87 | when 'socks5' 88 | # TODO move socks agent creation to ./config 89 | # TODO improve: not create so much socks agent 90 | p = { 91 | ipaddress: proxy.hostname 92 | port: proxy.port 93 | type: 5 # socks5 94 | } 95 | if info.protocol is 'https:' 96 | agent = new socks.Agent { 97 | proxy: p 98 | }, true, # true for https server 99 | false 100 | else 101 | agent = new socks.Agent { 102 | proxy: p 103 | }, false, # false for http server 104 | false 105 | opt = { 106 | hostname: info.hostname 107 | port: info.port 108 | path: info.path 109 | 110 | agent: agent # use socks5 proxy 111 | } 112 | else 113 | throw new Error "unknow proxy.type #{proxy.type}" 114 | else # no proxy 115 | opt = { 116 | hostname: info.hostname 117 | port: info.port 118 | path: info.path 119 | } 120 | # add custom headers 121 | ch = config.headers() 122 | if ch? 123 | if opt.headers? 124 | Object.assign opt.headers, ch 125 | else 126 | opt.headers = ch 127 | await _do_dl opt, filename, info.protocol 128 | # FIXME close socks proxy agent when error 129 | # try close socks proxy socket 130 | if agent? 131 | agent.encryptedSocket.end() 132 | 133 | 134 | module.exports = dl_with_proxy # async 135 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/do_dl.coffee: -------------------------------------------------------------------------------- 1 | # do_dl.coffee, m3u8_dl-js/src/ 2 | path = require 'path' 3 | fs = require 'fs' 4 | 5 | async_ = require './async' 6 | util = require './util' 7 | log = require './log' 8 | config = require './config' 9 | key_host = require './key_host' 10 | dl_speed = require './dl_speed' 11 | 12 | parse_m3u8 = require './parse_m3u8' 13 | dl_clip = require './dl_clip' 14 | thread_pool = require './thread_pool' 15 | 16 | 17 | _create_meta_file = (m3u8, m3u8_info) -> 18 | # support multi-keys 19 | _save_key = -> 20 | o = { 21 | m3u8_key: {} 22 | m3u8_iv: {} 23 | } 24 | keys = config.get_all_m3u8_key() 25 | ivs = config.get_all_m3u8_iv() 26 | 27 | for i of keys 28 | o.m3u8_key[i] = keys[i].toString 'base64' 29 | for i of ivs 30 | o.m3u8_iv[i] = ivs[i].toString 'base64' 31 | o 32 | 33 | o = { 34 | p_version: config.P_VERSION 35 | m3u8: m3u8 36 | cwd: process.cwd() 37 | # save base url 38 | m3u8_base_url: config.m3u8_base_url() 39 | # save KEY and IV in meta file 40 | decrypt: _save_key() 41 | 42 | m3u8_info: m3u8_info 43 | last_update: util.last_update() 44 | } 45 | text = util.print_json(o) + '\n' 46 | await util.write_file config.META_FILE, text 47 | 48 | _make_filename = (m3u8_info) -> 49 | _add_zero = (raw, len) -> 50 | while raw.length < len 51 | raw = '0' + raw 52 | raw 53 | len = m3u8_info.clip.length.toString().length 54 | 55 | _one_filename = (index) -> 56 | base = _add_zero('' + index, len) 57 | # output 58 | { 59 | part: base + config.CLIP_SUFFIX_DL_PART 60 | encrypted: base + config.CLIP_SUFFIX_ENCRYPTED 61 | ts_tmp: base + config.CLIP_SUFFIX_TS + util.WRITE_REPLACE_FILE_SUFFIX 62 | ts: base + config.CLIP_SUFFIX_TS 63 | } 64 | for i in [0... m3u8_info.clip.length] 65 | c = m3u8_info.clip[i] 66 | c.name = _one_filename i 67 | # make key filename (multi-key support) 68 | if m3u8_info.key? 69 | key = m3u8_info.key 70 | key_count = Object.keys(key).length 71 | 72 | len = key_count.toString().length 73 | N = config.RAW_KEY 74 | for i of key 75 | key[i].filename = N[0] + _add_zero(i, len) + N[1] 76 | # add key_count 77 | m3u8_info.key_count = key_count 78 | # add clip_count 79 | m3u8_info.clip_count = m3u8_info.clip.length 80 | m3u8_info 81 | 82 | # check and process clip base url 83 | _check_clip_base_url = (m3u8_info) -> 84 | # DEBUG 85 | if config.m3u8_base_url()? 86 | log.d "base URL #{config.m3u8_base_url()}" 87 | for c in m3u8_info.clip 88 | c.clip_url = util.check_merge_base_url c.url 89 | m3u8_info 90 | 91 | # create ffmpeg merge list 92 | _create_list_file = (m3u8_info) -> 93 | o = [] 94 | for c in m3u8_info.clip 95 | o.push "file \'#{c.name.ts}\'" 96 | text = o.join('\n') + '\n' 97 | # write list file 98 | await util.write_file config.LIST_FILE, text 99 | 100 | _create_lock = -> 101 | # check lock file 102 | fd = await util.create_lock_file config.LOCK_FILE 103 | config.lock_file_fd fd 104 | 105 | _remove_lock = -> 106 | # close file before remove it 107 | try 108 | fs.closeSync fd 109 | catch e 110 | # ignore error 111 | try 112 | fs.unlinkSync config.LOCK_FILE 113 | catch e 114 | # ignore if file not exist 115 | # check lock file exist 116 | if fs.existsSync config.LOCK_FILE 117 | log.e "can not remove LOCK file: #{path.resolve config.LOCK_FILE}" 118 | # remove LOCK on process exit 119 | process.on 'exit', _remove_lock 120 | # FIXME other ways to exit this process 121 | process.on 'SIGINT', () -> 122 | log.d "recv SIGINT, exiting .. . " 123 | process.exit 0 124 | 125 | _check_exit_on_flag = -> 126 | _enable_exit_on_flag = -> 127 | while true 128 | # sleep 1s 129 | await async_.sleep 1e3 130 | # check flag file exist 131 | if await async_.file_exist config.EXIT_FLAG_FILE 132 | # try to remove it first 133 | await async_.rm config.EXIT_FLAG_FILE 134 | # re-check 135 | if await async_.file_exist config.EXIT_FLAG_FILE 136 | log.w "can not remove FLAG file `#{path.resolve config.EXIT_FLAG_FILE}`, not exit" 137 | else 138 | log.d "flag file `#{path.resolve config.EXIT_FLAG_FILE}` exist, exiting .. . " 139 | process.exit 1 # do exit 140 | if config.exit_on_flag() 141 | log.d "enable exit_on_flag" 142 | _enable_exit_on_flag() 143 | 144 | 145 | _count_clip_time = (m3u8_info) -> 146 | o = 0 147 | for i in m3u8_info.clip 148 | t = i.time_s 149 | if t >= 0 150 | o += t 151 | o 152 | 153 | _download_clips = (m3u8_info) -> 154 | # single-thread mode: just use loop 155 | _single_thread = -> 156 | for i in [0... m3u8_info.clip.length] 157 | await dl_clip m3u8_info, i 158 | # use thread_pool 159 | _multi_thread = (n) -> 160 | _worker = (i) -> 161 | await dl_clip m3u8_info, i 162 | # create task list 163 | t = [] 164 | for i in [0... m3u8_info.clip.length] 165 | t.push i 166 | pool = thread_pool n 167 | await pool.run t, _worker 168 | 169 | video_time = dl_speed.print_time _count_clip_time(m3u8_info) 170 | log.d "download #{m3u8_info.clip.length} clips, video time #{video_time} " 171 | # check number of download thread 172 | thread_n = config.dl_thread() 173 | if (! thread_n?) || (thread_n <= 1) 174 | await _single_thread() 175 | else 176 | log.d " with #{thread_n} threads " 177 | await _multi_thread thread_n 178 | 179 | 180 | do_dl = (m3u8) -> 181 | # DEBUG 182 | if config.proxy()? 183 | log.d "use proxy #{JSON.stringify config.proxy()}" 184 | # check is remote file (http) or local file 185 | if m3u8.startsWith('http://') || m3u8.startsWith('https://') 186 | # remote file 187 | if ! config.m3u8_base_url()? # not override command line 188 | config.m3u8_base_url util.get_base_url(m3u8) # set base_url 189 | # change working directory now 190 | await util.check_change_cwd true 191 | await _create_lock() 192 | # download that m3u8 file 193 | log.d "download m3u8 file #{m3u8}" 194 | dl_tmp_file = config.RAW_M3U8 + util.WRITE_REPLACE_FILE_SUFFIX 195 | await dl_clip.dl_one m3u8, dl_tmp_file 196 | await async_.mv dl_tmp_file, config.RAW_M3U8 197 | # read that text 198 | m3u8_text = await async_.read_file config.RAW_M3U8 199 | else # local file 200 | log.d "local m3u8 file #{path.resolve m3u8}" 201 | m3u8_text = await async_.read_file m3u8 202 | # change working directory here 203 | await util.check_change_cwd true 204 | await _create_lock() 205 | # create raw m3u8 file 206 | await util.write_file config.RAW_M3U8, m3u8_text 207 | # parse m3u8 text, and create meta file 208 | m3u8_info = parse_m3u8 m3u8_text 209 | # create clip filename 210 | m3u8_info = _make_filename m3u8_info 211 | m3u8_info = _check_clip_base_url m3u8_info 212 | 213 | await _create_meta_file m3u8, m3u8_info 214 | await _create_list_file m3u8_info 215 | # DEBUG output: key_count 216 | key_count = m3u8_info.key_count 217 | if key_count? && (key_count > 0) 218 | log.d " #{key_count} keys in m3u8 file " 219 | # set key_info to key_host before start download 220 | key_host.set_key_info m3u8_info.key 221 | 222 | # support exit_on_flag 223 | _check_exit_on_flag() 224 | 225 | await _download_clips m3u8_info 226 | # not DEBUG 227 | log.p "[ OK ] all download done. " 228 | 229 | 230 | module.exports = do_dl # async 231 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/is_task_done.coffee: -------------------------------------------------------------------------------- 1 | # is_task_done.coffee, m3u8_dl-js/src/ 2 | # 3 | # Usage: 4 | # is_task_done DIR 5 | # 6 | # exit 0 if task done 7 | # 8 | path = require 'path' 9 | 10 | util = require './util' 11 | log = require './log' 12 | config = require './config' 13 | dl_speed = require './dl_speed' 14 | 15 | 16 | main = (argv) -> 17 | config.output_dir argv[0] 18 | await util.check_change_cwd() 19 | log.d "load meta file #{path.resolve(config.META_FILE)}" 20 | 21 | await dl_speed.load_meta_file() 22 | exit_flag = await dl_speed.update() 23 | clip = dl_speed.get_clip_count() 24 | if exit_flag 25 | log.p "[ OK ] task done (#{clip.done}/#{clip.all})" 26 | process.exit 0 # task done 27 | else 28 | log.e "task NOT done (#{clip.done}/#{clip.all})" 29 | process.exit 1 # task NOT done 30 | 31 | _start = -> 32 | try 33 | await main(process.argv[2..]) 34 | catch e 35 | # DEBUG 36 | console.log "ERROR: #{e.stack}" 37 | #throw e 38 | process.exit 1 # task NOT done 39 | _start() 40 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/key_host.coffee: -------------------------------------------------------------------------------- 1 | # key_host.coffee, m3u8_dl-js/src/ 2 | 3 | async_ = require './async' 4 | util = require './util' 5 | log = require './log' 6 | config = require './config' 7 | 8 | dl_with_proxy = require './dl_with_proxy' 9 | 10 | 11 | _etc = { 12 | key_info: null 13 | 14 | # loaded KEY (value) list { key_id: key_value } 15 | key: {} 16 | } 17 | 18 | 19 | # download one key file (MUST run in single thread) 20 | _dl_one_key = (key_id) -> 21 | key = _etc.key_info[key_id] 22 | # check key method 23 | if key.method != 'AES-128' 24 | throw new Error "not support encrypt (KEY) method `#{key.method}`" 25 | # check base_url 26 | key_url = util.check_merge_base_url key.uri 27 | key_file = key.filename # local file name 28 | log.d "download key file #{key_file}: #{key_url}" 29 | await dl_with_proxy key_url, key_file 30 | # read key 31 | key_value = await async_.read_file_byte key_file 32 | # save key (cache key ?) 33 | _etc.key[key_id] = key_value 34 | 35 | _init_key_info = -> 36 | # reset load key list 37 | _etc.to_load_list = [] 38 | _etc.flag_load_thread_run = false 39 | 40 | # (fake) multi-thread to single-thread (maybe use lock ?) 41 | _load_key = (key_id) -> 42 | new Promise (resolve, reject) -> 43 | _put_in_list = -> 44 | _callback = (err) -> 45 | if err 46 | reject err 47 | else 48 | resolve() 49 | one = { 50 | key_id 51 | callback: _callback 52 | } 53 | _etc.to_load_list.push one 54 | 55 | _check_load_next = -> 56 | if _etc.to_load_list.length < 1 57 | # no more keys to load 58 | _etc.flag_load_thread_run = false # reset flag 59 | return # end this (fake) thread 60 | [one, _etc.to_load_list] = [ _etc.to_load_list[0], _etc.to_load_list[1..] ] 61 | # check this key already loaded 62 | if _etc.key[one.key_id]? 63 | one.callback() 64 | 65 | # load next key 66 | setTimeout _check_load_next, 0 67 | return # not load one key twice 68 | # start load this key 69 | _dl_one_key(one.key_id).catch( (err) -> 70 | # load one key error 71 | log.e "load key #{one.key_id} failed ! " 72 | one.callback err 73 | 74 | # load next key 75 | _check_load_next() 76 | ).then () -> 77 | one.callback() # load success 78 | # load next key 79 | _check_load_next() 80 | # put task in list 81 | _put_in_list() 82 | # check load thread running 83 | if ! _etc.flag_load_thread_run 84 | _etc.flag_load_thread_run = true # set flag 85 | _check_load_next() # start load thread 86 | 87 | 88 | get_key = (key_id) -> 89 | # check config (command line) first 90 | key = config.m3u8_key key_id 91 | if key? 92 | return key 93 | # key in m3u8 file 94 | key = _etc.key[key_id] 95 | if key? 96 | return key 97 | # check key_id 98 | if ! _etc.key_info[key_id] 99 | throw new Error "no key info for key #{key_id}" 100 | # load key 101 | await _load_key key_id 102 | # NOW key should be loaded 103 | get_key key_id # call self 104 | 105 | get_iv = (key_id) -> 106 | # check config first 107 | iv = config.m3u8_iv key_id 108 | if iv? 109 | return iv 110 | # iv in m3u8 file 111 | key = _etc.key_info[key_id] 112 | if ! key? 113 | throw new Error "no key info for key #{key_id}" 114 | key.iv # may return null (if no IV) 115 | 116 | # can be use only once before start download 117 | set_key_info = (key_info) -> 118 | _etc.key_info = key_info 119 | _init_key_info() 120 | 121 | module.exports = { 122 | get_key # async 123 | 124 | get_iv 125 | set_key_info 126 | } 127 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/log.coffee: -------------------------------------------------------------------------------- 1 | # log.coffee, m3u8_dl-js/src/ 2 | 3 | _PREFIX = 'm3u8_dl' 4 | 5 | 6 | # TODO print to stderr ? 7 | _p = (text) -> 8 | o = _PREFIX + text 9 | console.log o 10 | 11 | 12 | # exports 13 | d = (text) -> 14 | _p '.D: ' + text 15 | e = (text) -> 16 | _p '.ERROR: ' + text 17 | w = (text) -> 18 | _p '.WARNING: ' + text 19 | p = (text) -> 20 | _p ': ' + text 21 | 22 | 23 | module.exports = { 24 | d # DEBUG 25 | e # ERROR 26 | w # WARNING 27 | 28 | p # normal print 29 | } 30 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/m3u8_dl.coffee: -------------------------------------------------------------------------------- 1 | # m3u8_dl.coffee, m3u8_dl-js/src/ 2 | path = require 'path' 3 | 4 | async_ = require './async' 5 | util = require './util' 6 | log = require './log' 7 | config = require './config' 8 | 9 | do_dl = require './do_dl' 10 | 11 | 12 | _p_help = -> 13 | console.log ''' 14 | m3u8_dl-js [OPTIONS] M3U8 15 | Usage: 16 | -o, --output DIR Download files to this Directory 17 | 18 | -T, --thread NUM Set number of download thread (default: 1) 19 | --auto-remove Remove raw file after decrypt success 20 | --exit-on-flag Exit when FLAG file exist 21 | -H, --header NAME:VALUE Set http header (can use more than once) 22 | --proxy-http IP:PORT Set http proxy 23 | --proxy-socks5 IP:PORT Set socks5 proxy 24 | --dl-with-curl CURL Download clips with cURL 25 | --m3u8-base-url URL Set base URL of the m3u8 file 26 | 27 | Set KEY (and IV) for AES-128 decrypt. Use HEX format, base64 format, 28 | or local binary file. Use ID to set multi-keys. 29 | 30 | --m3u8-key [ID:]HEX 31 | --m3u8-iv [ID:]HEX 32 | --m3u8-key-base64 [ID:]BASE64 33 | --m3u8-iv-base64 [ID:]BASE64 34 | --m3u8-key-file [ID::]FILE 35 | --m3u8-iv-file [ID::]FILE 36 | 37 | --version Show version of this program 38 | --help Show this help text 39 | More information online 40 | ''' 41 | 42 | _p_arg = (args) -> 43 | rest = args 44 | _next = -> 45 | o = rest[0] 46 | rest = rest[1..] 47 | o 48 | 49 | _split_ip_port = (raw) -> 50 | p = raw.split ':' 51 | o = { 52 | hostname: p[0] 53 | port: Number.parseInt p[1] 54 | } 55 | 56 | headers = {} 57 | _set_header = (raw) -> 58 | name = raw.split(':', 1)[0] 59 | value = raw[(name.length + 1) ..] 60 | headers[name] = value 61 | 62 | # support multi-keys 63 | key_file_list = [] 64 | iv_file_list = [] 65 | _set_key_iv = (key_iv, format, value) -> 66 | if format is 'file' 67 | key_id = 0 # default key_id 68 | i = value.indexOf '::' 69 | if i != -1 70 | key_id = Number.parseInt value[0... i] 71 | filename = value[(i + 2) ..] 72 | else 73 | filename = value 74 | one = { 75 | key_id 76 | filename 77 | } 78 | switch key_iv 79 | when 'key' 80 | key_file_list.push one 81 | when 'iv' 82 | iv_file_list.push one 83 | else 84 | key_id = 0 # default key_id 85 | i = value.indexOf ':' 86 | if i != -1 87 | key_id = Number.parseInt value[0... i] 88 | value = value[(i + 1) ..] 89 | value = Buffer.from value, format 90 | switch key_iv 91 | when 'key' 92 | config.m3u8_key value, key_id 93 | when 'iv' 94 | config.m3u8_iv value, key_id 95 | 96 | o = {} 97 | while rest.length > 0 98 | one = _next() 99 | switch one 100 | when '--help', '--version' 101 | o.type = one 102 | when '-o', '--output' 103 | config.output_dir _next() 104 | 105 | when '-T', '--thread' 106 | t = Number.parseInt _next() 107 | config.dl_thread t 108 | if t < 1 109 | throw new Error "bad thread num #{t}" 110 | when '--auto-remove' 111 | config.auto_remove true 112 | when '--exit-on-flag' 113 | config.exit_on_flag true 114 | when '-H', '--header' 115 | _set_header _next() 116 | 117 | when '--proxy-http' 118 | p = _split_ip_port _next() 119 | p.type = 'http' 120 | config.proxy p 121 | when '--proxy-socks5' 122 | p = _split_ip_port _next() 123 | p.type = 'socks5' 124 | config.proxy p 125 | when '--dl-with-curl' 126 | config.curl_bin _next() 127 | 128 | when '--m3u8-base-url' 129 | config.m3u8_base_url _next() 130 | 131 | when '--m3u8-key' 132 | _set_key_iv 'key', 'hex', _next() 133 | when '--m3u8-iv' 134 | _set_key_iv 'iv', 'hex', _next() 135 | when '--m3u8-key-base64' 136 | _set_key_iv 'key', 'base64', _next() 137 | when '--m3u8-iv-base64' 138 | _set_key_iv 'iv', 'base64', _next() 139 | when '--m3u8-key-file' 140 | _set_key_iv 'key', 'file', _next() 141 | when '--m3u8-iv-file' 142 | _set_key_iv 'iv', 'file', _next() 143 | 144 | else # default: m3u8 145 | # warning before set 146 | if o.m3u8? 147 | log.w "set M3U8 to #{one}" 148 | o.m3u8 = one 149 | if (! o.type?) && (! o.m3u8?) 150 | throw new Error "empty command line" 151 | # check set headers 152 | if Object.keys(headers).length > 0 153 | log.d "use headers #{util.print_json headers}" 154 | config.headers headers 155 | # key/iv files to load 156 | o.m3u8_key_file = key_file_list 157 | o.m3u8_iv_file = iv_file_list 158 | o 159 | 160 | _normal = (a) -> 161 | # load key/iv files (support multi-keys) 162 | key_list = a.m3u8_key_file 163 | iv_list = a.m3u8_iv_file 164 | for i in key_list 165 | log.d "load KEY (#{i.key_id}) file #{i.filename}" 166 | config.m3u8_key i.key_id, await async_.read_file_byte(i.filename) 167 | for i in iv_list 168 | log.d "load IV (#{i.key_id}) file #{i.filename}" 169 | config.m3u8_iv i.key_id, await async_.read_file_byte(i.filename) 170 | 171 | # DEBUG output: key/iv set from command line 172 | key = config.get_all_m3u8_key() 173 | iv = config.get_all_m3u8_iv() 174 | flag_debug = false 175 | o = {} 176 | if Object.keys(key).length > 0 177 | o.key = key 178 | flag_debug = true 179 | if Object.keys(iv).length > 0 180 | o.iv = iv 181 | flag_debug = true 182 | if flag_debug 183 | log.d "use KEY #{util.print_json o}" 184 | 185 | await do_dl a.m3u8 186 | # try to remove lock file 187 | fd = config.lock_file_fd() 188 | try # close file before remove it 189 | await async_.fs_close fd 190 | catch e 191 | # ignore error 192 | try 193 | await async_.rm config.LOCK_FILE 194 | catch e 195 | # ignore 196 | # check lock file exist 197 | if await async_.file_exist config.LOCK_FILE 198 | log.e "can not remove LOCK file `#{path.resolve config.LOCK_FILE}`" 199 | # FIX process will not exit 200 | process.exit 0 201 | 202 | main = (argv) -> 203 | try 204 | a = _p_arg argv 205 | catch e 206 | util.p_bad_command_line() 207 | process.exit 1 # bad command line 208 | switch a.type 209 | when '--help' 210 | _p_help() 211 | when '--version' 212 | util.p_version() 213 | else 214 | await _normal a 215 | 216 | _start = -> 217 | try 218 | await main(process.argv[2..]) 219 | catch e 220 | # DEBUG 221 | console.log "ERROR: #{e.stack}" 222 | #throw e 223 | process.exit 1 224 | _start() 225 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/parse_m3u8.coffee: -------------------------------------------------------------------------------- 1 | # parse_m3u8.coffee, m3u8_dl-js/src/ 2 | log = require './log' 3 | 4 | 5 | _split_lines = (raw_text) -> 6 | lines = raw_text.split '\n' 7 | o = [] 8 | for l in lines 9 | l = l.trim() 10 | if l != '' 11 | o.push l 12 | o 13 | 14 | _parse_one_key = (raw_line) -> 15 | # support key line: `#EXT-X-KEY:METHOD=AES-128,URI="http://XXXX.key"` 16 | 17 | # TODO support parse IV 18 | # TODO support more format parse 19 | o = {} 20 | rest = raw_line 21 | M = 'METHOD=' 22 | if rest.startsWith M 23 | rest = rest[M.length ..] 24 | i = rest.indexOf(',') 25 | if i is -1 26 | o.method = rest 27 | else 28 | o.method = rest[0...i] 29 | rest = rest[(i + 1)..] 30 | 31 | U = 'URI=' 32 | if rest.startsWith U 33 | rest = rest[U.length ..] 34 | if rest.startsWith '"' 35 | o.uri = rest[1..] 36 | if o.uri.endsWith '"' 37 | o.uri = o.uri[0... o.uri.length - 1] 38 | else 39 | o.uri = rest 40 | # TODO maybe more process 41 | # else: TODO unknow format 42 | o 43 | 44 | # support multi-keys 45 | _parse_key = (key_info, raw_line) -> 46 | one = _parse_one_key raw_line 47 | # check key_id 48 | if key_info.key_id? 49 | key_info.key_id += 1 50 | else # init key_id 51 | key_info.key_id = 0 52 | key_info.key = {} 53 | # save key info 54 | key_info.key[key_info.key_id] = one 55 | 56 | 57 | parse_m3u8 = (raw_m3u8_text) -> 58 | M3U = '#EXTM3U' 59 | V = '#EXT-X-VERSION:' 60 | MS = '#EXT-X-MEDIA-SEQUENCE:' 61 | TD = '#EXT-X-TARGETDURATION:' 62 | K = '#EXT-X-KEY:' 63 | INFO = '#EXTINF:' 64 | END = '#EXT-X-ENDLIST' 65 | 66 | line = _split_lines raw_m3u8_text 67 | # check format 68 | if line[0] != M3U 69 | log.w "parse_m3u8: file format is not `#EXTM3U` " 70 | 71 | media_sequence = 0 72 | clip_s = -1 # clip time_s 73 | # support multi-keys 74 | key_info = { 75 | key_id: null 76 | key: null 77 | } 78 | 79 | o = { 80 | key: null 81 | clip: [] 82 | } 83 | for l in line 84 | if l.startsWith '#' 85 | if l.startsWith INFO 86 | clip_s = JSON.parse l[INFO.length ..].split(',', 1)[0] 87 | if l.startsWith V 88 | o.version = l[V.length ..] 89 | else if l.startsWith MS 90 | media_sequence = Number.parseInt l[MS.length ..] 91 | else if l.startsWith TD 92 | o.target_duration = l[TD.length ..] 93 | else if l.startsWith K 94 | _parse_key key_info, l[K.length ..] 95 | else if l is END 96 | o.key = key_info.key # add key info 97 | return o # got file end 98 | # else: ignore this line 99 | else 100 | # is a clip file line 101 | one = { 102 | media_sequence 103 | url: l 104 | time_s: clip_s 105 | } 106 | # add key_id 107 | if key_info.key_id? 108 | one.key_id = key_info.key_id 109 | o.clip.push one 110 | 111 | clip_s = -1 # reset clip time_s 112 | media_sequence += 1 113 | # no `#EXT-X-ENDLIST` 114 | log.w "parse_m3u8: not found m3u8 end `#EXT-X-ENDLIST` " 115 | o.key = key_info.key 116 | o 117 | 118 | module.exports = parse_m3u8 119 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/show_dl_speed.coffee: -------------------------------------------------------------------------------- 1 | # show_dl_speed.coffee, m3u8_dl-js/src/ 2 | path = require 'path' 3 | 4 | async_ = require './async' 5 | util = require './util' 6 | log = require './log' 7 | config = require './config' 8 | dl_speed = require './dl_speed' 9 | 10 | 11 | _p_help = -> 12 | console.log ''' 13 | show_dl_speed [OPTIONS] [DIR] 14 | Usage: 15 | 16 | --put-exit-flag Enable retry function (put flag file) 17 | 18 | --retry-after SEC Retry after this seconds (default: 10) 19 | --retry-hide SEC Hide retry debug info in this seconds (default: 5) 20 | --init-wait SEC Wait seconds for init_wait mode (default: 20) 21 | 22 | --version Show version of this program 23 | --help Show this help text 24 | More information online 25 | ''' 26 | 27 | _p_arg = (args) -> 28 | rest = args 29 | _next = -> 30 | o = rest[0] 31 | rest = rest[1..] 32 | o 33 | 34 | o = {} 35 | while rest.length > 0 36 | one = _next() 37 | switch one 38 | when '--help', '--version' 39 | o.type = one 40 | when '--put-exit-flag' 41 | o.put_exit_flag = true 42 | 43 | when '--retry-after' 44 | o.retry_after = Number.parseInt _next() 45 | when '--retry-hide' 46 | o.retry_hide = Number.parseInt _next() 47 | when '--init-wait' 48 | o.init_wait = Number.parseInt _next() 49 | 50 | else # default: DIR 51 | config.output_dir one 52 | o 53 | 54 | _etc = { 55 | # command line arguments 56 | put_exit_flag: false 57 | retry_after: 10 58 | retry_hide: 5 59 | init_wait: 20 60 | 61 | # show_dl_speed mode 62 | # + null Init / exit 63 | # + 'wait_meta' Wait meta file to exist 64 | # + 'wait_lock' Wait lock file to exist 65 | # + 'init_wait' Wait after first lock file exist 66 | # + 'show_speed' Normal show download speed mode 67 | # + 'speed_0' Download speed is 0 68 | mode: null 69 | 70 | # print flags 71 | not_show_speed: false 72 | # print warnings (only show once) 73 | show_lock_not_exist: false 74 | 75 | show_will_retry: false 76 | 77 | init_wait_count: 0 78 | speed_0_count: 0 79 | } 80 | 81 | # create `m3u8_dl.exit.flag` file to retry 82 | _put_flag_file = (speed_keep_s) -> 83 | log.d "now retry (speed 0 for #{speed_keep_s} s ): put exit flag file `#{path.resolve config.EXIT_FLAG_FILE}`" 84 | await util.write_file config.EXIT_FLAG_FILE, '' # create null flag file 85 | 86 | 87 | # TODO re-write show_dl_speed state process with a state machine class 88 | # some reset (mode) functions 89 | 90 | _reset_flags = -> 91 | _etc.mode = null 92 | _etc.show_lock_not_exist = false 93 | 94 | _reset_to_wait_meta = -> 95 | if _etc.mode is 'wait_meta' 96 | return # not reset if already in this mode 97 | _reset_flags() 98 | _etc.mode = 'wait_meta' 99 | 100 | _reset_to_wait_lock = -> 101 | if _etc.mode is 'wait_lock' 102 | return 103 | _reset_flags() 104 | _etc.mode = 'wait_lock' 105 | # set show_lock_not_exist 106 | _etc.show_lock_not_exist = true 107 | 108 | _reset_to_init_wait = -> 109 | if _etc.mode is 'init_wait' 110 | return 111 | _reset_flags() 112 | _etc.mode = 'init_wait' 113 | # reset init_wait_count 114 | _etc.init_wait_count = 0 115 | 116 | _reset_to_show_speed = -> 117 | if _etc.mode is 'show_speed' 118 | return 119 | _reset_flags() 120 | _etc.mode = 'show_speed' 121 | 122 | _reset_to_speed_0 = -> 123 | if _etc.mode is 'speed_0' 124 | return 125 | _reset_flags() 126 | _etc.mode = 'speed_0' 127 | # reset speed 0 count 128 | _etc.speed_0_count = 0 129 | 130 | # FIXME improve output with speed 131 | _print_enter_init_mode = -> 132 | log.d "init_wait mode for #{_etc.init_wait} s .. . " 133 | 134 | _check_mode = (lock_exist, speed) -> 135 | # reset flags 136 | _etc.show_will_retry = false 137 | # check enable put_exit_flag 138 | if ! _etc.put_exit_flag 139 | if lock_exist # just enter 'show_speed' mode 140 | _reset_to_show_speed() 141 | return 142 | 143 | # process mode: 'wait_lock' (and mode null) 144 | if (_etc.mode is 'wait_lock') || (! _etc.mode?) 145 | if ! lock_exist 146 | return # still wait 147 | else # enter 'init_wait' mode 148 | _reset_to_init_wait() 149 | # check print debug 150 | if speed is 0 151 | _print_enter_init_mode() 152 | # process mode: 'init_wait' 153 | if _etc.mode is 'init_wait' 154 | # check speed 155 | if speed is 0 156 | _etc.init_wait_count += 1 157 | if _etc.init_wait_count < _etc.init_wait 158 | return # still wait 159 | else 160 | log.d "exit init_wait mode (timeout after #{_etc.init_wait_count} s)" 161 | else 162 | log.d "exit init_wait mode (speed > 0)" 163 | # enter 'show_speed' mode 164 | _reset_to_show_speed() 165 | # NOTE there is no 'wait_exit' mode 166 | 167 | # check speed 168 | if speed is 0 169 | _reset_to_speed_0() 170 | 171 | _etc.speed_0_count += 1 172 | if _etc.speed_0_count > _etc.retry_after 173 | await _put_flag_file _etc.speed_0_count 174 | # just enter init_wait mode 175 | _reset_to_init_wait() 176 | # FIXME improve output with speed check 177 | _print_enter_init_mode() 178 | else if _etc.speed_0_count > _etc.retry_hide 179 | _etc.show_will_retry = true 180 | else 181 | _reset_to_show_speed() 182 | 183 | _show_speed = (speed) -> 184 | # check and print dl speed 185 | time_left = dl_speed.get_time_left() 186 | if (speed > 0) || time_left? # time_left is not `unknow` 187 | _etc.not_show_speed = false # reset flag 188 | else if _etc.not_show_speed 189 | return 190 | # not show 0 speed in these mode 191 | else if ['wait_meta', 'init_wait'].indexOf(_etc.mode) != -1 192 | return 193 | else 194 | _etc.not_show_speed = true 195 | console.log dl_speed.print_speed() 196 | 197 | _update = -> 198 | # check meta file exist 199 | if ! await async_.file_exist(config.META_FILE) 200 | _reset_to_wait_meta() 201 | log.d "waiting for #{path.resolve config.META_FILE}" 202 | return 203 | # check lock file exist 204 | if ! await async_.file_exist(config.LOCK_FILE) 205 | _reset_to_wait_lock() 206 | else 207 | lock_exist = true 208 | # update scan 209 | await dl_speed.load_meta_file() 210 | exit_flag = await dl_speed.update() 211 | speed = dl_speed.get_dl_speed() 212 | if (! speed?) || (speed < 1) 213 | speed = 0 214 | else # speed is not 0 215 | _etc.speed_0_count = 0 # reset speed_0_count 216 | # process mode 217 | await _check_mode lock_exist, speed 218 | 219 | _show_speed() 220 | # check and print warnings 221 | if _etc.not_show_speed # print warnings only after not show 0 speed 222 | if _etc.show_lock_not_exist 223 | _etc.show_lock_not_exist = false # only show once 224 | log.w "lock file `#{path.resolve config.LOCK_FILE}` not exist. m3u8_dl-js NOT running ?" 225 | # check print will retry 226 | if _etc.show_will_retry 227 | log.d "will retry after #{_etc.retry_after - _etc.speed_0_count} s (speed 0) .. . " 228 | # done 229 | exit_flag 230 | 231 | _normal = (a) -> 232 | # save options 233 | if a.put_exit_flag 234 | _etc.put_exit_flag = true 235 | if a.retry_after? 236 | _etc.retry_after = a.retry_after 237 | if a.retry_hide? 238 | _etc.retry_hide = a.retry_hide 239 | if a.init_wait? 240 | _etc.init_wait = a.init_wait 241 | # change cwd 242 | await util.check_change_cwd() 243 | log.d "working directory #{process.cwd()}" 244 | # init scan 245 | if await async_.file_exist config.META_FILE 246 | await dl_speed.load_meta_file() 247 | exit_flag = await dl_speed.update() 248 | if exit_flag 249 | console.log dl_speed.print_speed() 250 | return # task done, not enter main loop 251 | # main loop 252 | while true 253 | # sleep before scan again 254 | await async_.sleep dl_speed.UPDATE_TIME # sleep 1s 255 | if await _update() 256 | break # exit 257 | 258 | main = (argv) -> 259 | a = _p_arg argv 260 | # check start type 261 | switch a.type 262 | when '--help' 263 | _p_help() 264 | when '--version' 265 | util.p_version() 266 | else 267 | await _normal a 268 | 269 | _start = -> 270 | try 271 | await main(process.argv[2..]) 272 | catch e 273 | # DEBUG 274 | console.log "ERROR: #{e.stack}" 275 | #throw e 276 | process.exit 1 277 | _start() 278 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/thread_pool.coffee: -------------------------------------------------------------------------------- 1 | # thread_pool.coffee, m3u8_dl-js/src/ 2 | 3 | # create a new thread pool 4 | thread_pool = (pool_size) -> 5 | run = (todo_list, worker) -> 6 | o = [] # output (return) result 7 | current_run_thread_count = 0 # current running tasks count 8 | current_todo_task_id = 0 # this task is waiting todo 9 | 10 | new Promise (resolve, reject) -> 11 | # check and run a new thread 12 | check_run = -> 13 | if current_todo_task_id < todo_list.length 14 | # check start a new thread 15 | if current_run_thread_count < pool_size 16 | current_task_id = current_todo_task_id 17 | current_todo_task_id += 1 18 | current_todo = todo_list[current_task_id] 19 | 20 | # start worker 21 | worker(current_todo).catch( (err) -> 22 | # save error in result 23 | o[current_task_id] = err 24 | # end this thread 25 | current_run_thread_count -= 1 26 | # check after thread end 27 | check_run() 28 | ).then (result) -> 29 | # save result 30 | o[current_task_id] = result 31 | # end this thread 32 | current_run_thread_count -= 1 33 | check_run() 34 | # add current thread count 35 | current_run_thread_count += 1 36 | # check after start a thread 37 | check_run() 38 | # else: waiting more threads to end 39 | # else: waiting tasks to finish 40 | else if current_run_thread_count < 1 41 | resolve o # done 42 | # else: still waiting 43 | check_run() 44 | o = { 45 | run # async 46 | } 47 | 48 | module.exports = thread_pool 49 | -------------------------------------------------------------------------------- /Tools/m3u8_dl-js-master/src/util.coffee: -------------------------------------------------------------------------------- 1 | # util.coffee, m3u8_dl-js/src/ 2 | path = require 'path' 3 | url = require 'url' 4 | 5 | async_ = require './async' 6 | log = require './log' 7 | config = require './config' 8 | 9 | 10 | last_update = -> 11 | new Date().toISOString() 12 | 13 | # pretty-print JSON text 14 | print_json = (data) -> 15 | JSON.stringify data, '', ' ' 16 | 17 | 18 | WRITE_REPLACE_FILE_SUFFIX = '.tmp' 19 | # atomic write-replace for a file 20 | write_file = (file_path, text) -> 21 | tmp_file = file_path + WRITE_REPLACE_FILE_SUFFIX 22 | await async_.write_file tmp_file, text 23 | await async_.mv tmp_file, file_path 24 | 25 | create_lock_file = (file_path) -> 26 | try 27 | return await async_.fs_open file_path, 'wx' 28 | catch e 29 | log.e "can not create LOCK file #{file_path} " 30 | throw e 31 | 32 | get_base_url = (full_url) -> 33 | o = url.parse full_url 34 | # clear values 35 | o.hash = null 36 | o.search = null 37 | o.query = null 38 | o.path = null 39 | o.href = null 40 | 41 | o.pathname = path.posix.dirname o.pathname 42 | url.format(o) 43 | 44 | p_bad_command_line = -> 45 | log.e 'bad command line, please try `--help` ' 46 | 47 | p_version = -> 48 | # print version 49 | console.log config.P_VERSION 50 | 51 | check_change_cwd = (create_dir)-> 52 | to = config.output_dir() 53 | if ! to? 54 | return 55 | to_path = path.resolve to 56 | # if output dir not exist, try to create it 57 | if (! await async_.file_exist(to)) && create_dir 58 | log.d "create dir #{to_path}" 59 | await async_.mkdir to 60 | process.chdir to 61 | cwd = process.cwd() 62 | if path.resolve(cwd) != to_path 63 | log.w "can not change current directory to `#{to_path}`, current directory is `#{cwd}`" 64 | 65 | check_merge_base_url = (raw_url) -> 66 | o = url.parse raw_url 67 | if ! o.protocol? 68 | base = config.m3u8_base_url() 69 | if ! base? 70 | log.e "no base URL for #{raw_url}" 71 | throw new Error "no base URL" 72 | # merge base url 73 | if ! base.endsWith('/') 74 | base += '/' 75 | o = new url.URL raw_url, base 76 | url.format o 77 | else 78 | raw_url 79 | 80 | # run command and check exit_code is 0 (else will throw Error) 81 | run_check = (cmd) -> 82 | exit_code = await async_.run_cmd cmd 83 | if exit_code != 0 84 | throw new Error "run command FAILED (exit_code = #{exit_code})" 85 | 86 | 87 | module.exports = { 88 | last_update 89 | print_json 90 | 91 | WRITE_REPLACE_FILE_SUFFIX 92 | write_file # async 93 | 94 | create_lock_file # async 95 | check_change_cwd # async 96 | run_check # async 97 | 98 | get_base_url 99 | p_bad_command_line 100 | p_version 101 | 102 | check_merge_base_url 103 | } 104 | -------------------------------------------------------------------------------- /Tools/node.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/m3u8_dl-js_GUI/3e94310ece920ca82735d15ac82f7001279da02d/Tools/node.exe -------------------------------------------------------------------------------- /Tools/node_64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/m3u8_dl-js_GUI/3e94310ece920ca82735d15ac82f7001279da02d/Tools/node_64.exe -------------------------------------------------------------------------------- /m3u8_dl-js_GUI.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}") = "m3u8_dl-js_GUI", "m3u8_dl-js_GUI\m3u8_dl-js_GUI.csproj", "{FD9FCD0D-DF69-4328-9089-39A73017FE9B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B}.Debug|x64.ActiveCfg = Debug|x64 21 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B}.Debug|x64.Build.0 = Debug|x64 22 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B}.Debug|x86.ActiveCfg = Debug|x86 23 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B}.Debug|x86.Build.0 = Debug|x86 24 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B}.Release|x64.ActiveCfg = Release|x64 27 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B}.Release|x64.Build.0 = Release|x64 28 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B}.Release|x86.ActiveCfg = Release|x86 29 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B}.Release|x86.Build.0 = Release|x86 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/Mainform.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 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | 124 | 197, 17 125 | 126 | 127 | 128 | 129 | AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAEAAABMLAAATCwAAAAAAAAAA 130 | AACbvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 131 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 132 | Mv+bvDL/nL0y/5q7Mv+LqDX/hqI2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 133 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 134 | Nv+Hozb/h6M2/4ejNv+Hozb/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 135 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 136 | Mv+bvDL/m7wy/5u8Mv+bvDL/nL0y/5q7Mv+LqTX/hqI2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 137 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 138 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 139 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 140 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/nL0y/5q7Mv+KpzX/hqE2/4ejNv+Hozb/h6M2/4ej 141 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 142 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+bvDL/m7wy/5u8 143 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 144 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/nL0y/5m6Mv+KpzX/hqI2/4ek 145 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 146 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 147 | Nv+Hozb/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 148 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/nL0y/5m6 149 | Mv+Jpjb/hqI2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 150 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 151 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 152 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 153 | Mv+bvDL/nL0y/5m5Mv+JpjX/hqI2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 154 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 155 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 156 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 157 | Mv+bvDL/m7wy/5u8Mv+bvDL/nL0y/5m5Mv+JpTX/hqI2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 158 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 159 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/m7wy/5u8 160 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 161 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/nL0y/5i4Mv+JpTX/hqI2/4ejNv+Hozb/h6M2/4ej 162 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 163 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 164 | Nv+Hozb/h6M2/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 165 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/nL4y/5e4M/+IpDb/hqI2/4ej 166 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 167 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 168 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 169 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/nL0y/5e3 170 | M/+IpDb/hqI2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 171 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 172 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/m7wy/5u8Mv+bvDL/m7wy/5u8 173 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 174 | Mv+bvDL/nL4y/5e3M/+HpDb/hqI2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 175 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 176 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/5u8 177 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 178 | Mv+bvDL/m7wy/5u8Mv+bvDL/nL4y/5a3M/+HpDb/hqI2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 179 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 180 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 181 | Nv+Hozb/h6M2/4ejNv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 182 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+auzL/nL0y/5W1M/+Hozb/hqI2/4ejNv+Hozb/h6M2/4ej 183 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 184 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 185 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 186 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5q6Mv+auzH/nr8y/5a2M/+IpDb/h6Q2/4ik 187 | Nv+IpDb/iKQ2/4ikNv+IpDb/iKQ2/4ikNv+IpDb/iKQ2/4ikNv+IpDb/iKQ2/4ikNv+IpDb/iKQ2/4ik 188 | Nv+IpDb/iKQ2/4ikNv+IpDb/iKQ2/4ikNv+IpDb/iKQ2/4aiNv+Gojb/h6M2/4ejNv+Hozb/h6M2/4ej 189 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/5u8Mv+bvDL/m7wy/5u8 190 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5q7Mv+gwjP/oMI1/5Kw 191 | NP+FoDf/haA3/4WhN/+FoDf/haE3/4WhN/+FoTf/haE3/4WhN/+FoTf/haE3/4WhN/+FoTf/haE3/4Wh 192 | N/+FoTf/haE3/4WhN/+FoTf/haE3/4WhN/+FoTf/haE3/4WhN/+FoTf/haE3/4SgNv+LqDn/i6g3/4ai 193 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 194 | Nv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5q6 195 | Mv+gwjP/gZwt/yUqE/8cHxL/HSAS/x0gEv8dIBL/HSAS/x0gEv8dIBL/HSAS/x0gEv8dIBL/HSAS/x0g 196 | Ev8dIBL/HSAS/x0gEv8dIBL/HSAS/x0gEv8dIBL/HSAS/x0gEv8dIBL/HSAS/x0gEv8dIBL/HSAS/x0h 197 | Ev8cHxL/ICUS/3GHL/+LqDf/hqI2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 198 | Nv+Hozb/h6M2/4ejNv+Hozb/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 199 | Mv+bvDL/m7wy/5u8Mv+auzH/ocM1/yYrEv89SB3/dY0y/2yCLv9uhC7/boQu/26ELv9uhC7/boQu/26E 200 | Lv9uhC7/boQu/26ELv9uhC7/boQu/26ELv9uhC7/boQu/26ELv9uhC7/boQu/26ELv9uhC7/boQu/26E 201 | Lv9uhC7/boQu/26ELv9sgi7/dY0y/z9LHf8hJhL/jKk5/4aiNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 202 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 203 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/nL4y/5i4M/8fIhP/dYwy/5KxOf+Nqjn/j606/4+t 204 | Ov+PrTr/j606/4+tOv+PrTr/j606/4+tOv+PrTr/j606/4+tOv+PrTr/j606/4+tOv+PrTr/j606/4+t 205 | Ov+PrTr/j606/4+tOv+PrTr/j606/4+tOv+PrTr/jao5/5KxOf91jDH/HB8S/4SgNv+IpDb/h6M2/4ej 206 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+bvDL/m7wy/5u8 207 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5y9Mv+ZuTP/ICQT/2yB 208 | Lv+Nqjn/ND0b/xodEP8fIxP/HiIT/x4iE/8eIhP/HiIT/x4iE/8eIhP/HiIT/x4iE/8fIxX/ICQV/yAk 209 | FP8gIxT/HyIS/x8jEv8fIxL/HyMS/x8jEv8fIxL/HyMS/x8jEv8gJBP/Gx4Q/zQ9G/+Nqjn/bIIu/x0h 210 | Ev+FoTf/iKQ2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 211 | Nv+Hozb/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 212 | Mv+cvTL/mbkz/yAjE/9sgi7/j6w6/xodEP9ZaiD/fZcr/3aOKf93jyn/d48p/3ePKf93jyn/d48p/3eP 213 | Kf94kCv/cowe/2qDHf9fdR//YXch/2l9Lv9nfCv/Z3wr/2d8LP9nfCz/Z3ws/2d8LP9meiv/bYMu/01b 214 | Iv8aHRD/jqw6/22DLv8dIBL/haE3/4ikNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 215 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 216 | Mv+bvDL/m7wy/5u8Mv+bvDL/nL0y/5i4M/8hJBP/b4Uv/5CuOv8iJhT/fZYq/6vQNv+hxDT/o8Y0/6PG 217 | NP+jxjT/o8Y0/6PGNP+lxzf/m8El/8Hadf/t9df/6/PX/7LHeP+FpSr/kK08/46sOP+Oqzj/jqw4/46s 218 | OP+OrDj/jao4/5W0O/9sgi3/IiYU/4+tOv9whi//HiET/4SgNv+IpDb/h6M2/4ejNv+Hozb/h6M2/4ej 219 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 220 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+cvTX/Gh0Q/2R4Kv+OrDr/EhMO/3qT 221 | K/+hwzP/mLgx/5q6Mv+aujL/mroy/5q6Mv+cvDX/kLQf/8LVh//5+fz/s8SG/7TFhv/6+vv/tMWH/3uZ 222 | JP+Iozn/hqE2/4ahNv+GoTb/hqE2/4WgNf+MqTj/an8t/xMVDf+Oqzr/ZXkr/xgaD/+IpDj/h6M2/4ej 223 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Gojb/m7wy/5u8 224 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+ZujH/o8Y1/1tt 225 | I/8REgz/FhgN/zQ9F/+cvTT/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+dvTb/krUg/8PXh//7+/3/m7Ja/3ua 226 | Iv97miL/m7Ja//z8/P+1xof/fJsk/4mlOv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ikN/8uNRf/FxkM/xES 227 | C/9PXyP/jqw5/4ahNf+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 228 | Nv+GoTb/jKk1/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 229 | Mv+bvDL/m7wy/5u8Mv+hwzX/fpgr/3GIJv+bvDT/nb8y/5q7Mv+bvDL/m7wy/5u8Mv+dvTb/krUg/8PX 230 | h//7+/3/mrJZ/3+dKf+KpTv/iqU7/3+dKf+asln//Pz8/7XGh/98myT/iaU6/4ejNv+Hozb/h6M2/4ei 231 | Nv+JpTb/h6M3/2J2KP9uhC7/jKk4/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 232 | Nv+Hozb/h6M2/4ejNv+FoTb/jas1/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 233 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/mrsx/6HENP+kxzX/m7wy/5q7Mv+bvDL/m7wy/5u8 234 | Mv+dvTb/krUg/8PXh//7+/3/mrJZ/3+dKf+JpTr/h6M2/4ejNv+JpTr/f50p/5qyWf/8/Pz/tcaH/3yb 235 | JP+JpTr/h6M2/4ejNv+Hozb/h6I2/4ejNv+PrTn/jao4/4aiNf+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 236 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+FoTb/jas1/5u9Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 237 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+aujL/mboy/5u8 238 | Mv+bvDL/m7wy/5u8Mv+dvTb/krUg/8PXh//7+/3/mrJZ/3+dKf+JpTr/h6M2/4ejNv+Hozb/h6M2/4ml 239 | Ov9/nSn/mrJZ//z8/P+1xof/fJsk/4mlOv+Hozb/h6M2/4ejNv+Hozb/hqE1/4aiNv+Hozb/h6M2/4ej 240 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+GoTb/jqw0/5u8Mv+bvDL/m7wy/5u8 241 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 242 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+dvTb/krUg/8PXh//7+/3/mrJZ/3+dKf+JpTr/h6M2/4ej 243 | Nv+Hozb/h6M2/4ejNv+Hozb/iaU6/3+dKf+asln//Pz8/7XGh/98myT/iaU6/4ejNv+Hozb/h6M2/4ej 244 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+FoTb/jqw0/5y9 245 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 246 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+dvTb/krUg/8PXh//7+/3/mrJZ/3+d 247 | Kf+JpTr/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+JpTr/f50p/5qyWf/8/Pz/tcaH/3yb 248 | JP+JpTr/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 249 | Nv+GoTb/jqw0/5y9Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 250 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+dvTb/krYg/8PX 251 | h//7+/3/mrJZ/3+dKf+JpTr/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ml 252 | Ov9/nSn/mrJZ//z8/P+1xof/fJsk/4mlOv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 253 | Nv+Hozb/h6M2/4ejNv+GoTb/j600/5y9Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 254 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 255 | Mv+dvTX/krYg/8PXh//7/P3/mrJZ/3+dKf+JpTr/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 256 | Nv+Hozb/h6M2/4ejNv+Hozb/iaU6/3+dKf+asln//Pz8/7XGh/98myT/iaU5/4ejNv+Hozb/h6M2/4ej 257 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+FoTb/j600/5y9Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 258 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 259 | Mv+bvDL/m7wy/5u8Mv+cvTT/k7cj/8PXh//7/P3/mrJZ/3+dKf+JpTr/h6M2/4ejNv+Hozb/h6M2/4ej 260 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+JpTr/f50p/5qyWf/8/Pz/tseH/36c 261 | J/+JpDj/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+GoTb/kK80/5y9Mv+bvDL/m7wy/5u8 262 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 263 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+cvTT/lbgm/7rRdf/7/P3/m7Ja/3+dKf+JpTr/h6M2/4ej 264 | Nv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ml 265 | Ov9/nSn/m7Ja//v8/P+rv3X/gJ4r/4ikOP+Hozb/h6M2/4ejNv+Hozb/h6M2/4ejNv+GoTb/ka80/5y+ 266 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 267 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/nL0z/5W4Kf/r8tn/scOA/32b 268 | JP+Mpz7/iaQ5/4mkOf+JpTn/iKQ3/4ejNv+GoTn/h6M3/4ejNv+Hozb/h6M2/4ejNv+Hozb/h6M2/4ej 269 | Nv+IpDf/iaU5/4mkOf+JpDn/jKc+/32bJP+yxIH/5uzY/4CeLP+IpDf/h6M2/4ejNv+Hozb/h6M2/4ej 270 | Nv+GoTb/ka80/5y+Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 271 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5y9 272 | M/+WuCn/7PPb/6/Bff92lhj/f50n/32cJf99nCX/fpwm/4ShMf+Iozf/jasq/4mlM/+Hozf/h6M2/4ej 273 | Nv+Hozb/h6M2/4ejNv+Hozf/hKEx/36cJv99myX/fZsl/36cJ/91lRj/sMJ9/+ft2v+Bni3/iKQ3/4ej 274 | Nv+Hozb/h6M2/4ejNv+GoTb/kbA0/5y+Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 275 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 276 | Mv+bvDL/m7wy/5u8Mv+cvTX/lbgm/73TfP/4+fr/xdKj/8DOmv/Bz5z/wc+c/8DOmf+asVb/g6Ez/15w 277 | h/99mEr/iaYy/4ejN/+Hozb/h6M2/4ejNv+IpDf/hKEw/5mxV//Az5n/wtGc/8PRnP/B0Jr/x9Sj//n6 278 | +P+vwnz/gJ4q/4ikOf+Hozb/h6M2/4ejNv+Gojb/krA0/5y+Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 279 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 280 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5y8M/+WuSr/ts5s/9vouP/a57f/2+e4/9bk 281 | r//9//v/ucmM/3ydLP8vNeT/cotg/4yoLf+Gojj/h6M2/4ejNv+Hozb/iaQ5/36cJ/+5yY7/+fr5/8zY 282 | rv/S3Lf/0dy2/9Ldt/+mu23/gp8t/4ikN/+Hozb/h6M2/4ejNv+Gojb/k7Iz/52+Mv+bvDL/m7wy/5u8 283 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 284 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/nL00/5a4 285 | J/+StiD/krYi/5S3JP+KsBL/2ue5/7rJjv98nSr/Nz7V/3ONXf+LqC7/hqI4/4ejNv+Hozb/h6M2/4mk 286 | Of9+nCb/usqP/9LcuP90lBb/f50o/32bJv98myT/gZ4r/4ikOP+Hozb/h6M2/4ejNv+Gojb/k7Iz/5y+ 287 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 288 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 289 | Mv+bvDL/m7wy/5u8Mv+cvTT/nb01/529Nf+evjf/lbgn/9/qw/+6yY7/fJ0q/zY92P9zjV3/i6gt/4ai 290 | OP+Hozb/h6M2/4ejNv+JpDn/fpwm/7rKkP/Y4cH/gJ4q/4qmPP+JpDn/iaU5/4ikOP+Hozb/h6M2/4ej 291 | Nv+Gojb/k7Mz/5y+Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 292 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 293 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/nL01/5O3I//f6sH/usmO/3yd 294 | Kv82Pdj/c41d/4uoLf+Gojj/h6M2/4ejNv+Hozb/iaQ5/36cJv+7ypD/1+DA/36cJ/+JpDn/h6M2/4ej 295 | Nv+Hozb/h6M2/4ejNv+Gojb/k7Mz/52+Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 296 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 297 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5y9 298 | Nf+TtyP/3+rB/7rJjv98nSr/Nj3Y/3ONXf+LqC3/hqI4/4ejNv+Hozb/h6M2/4mkOf9+nCb/u8qQ/9fg 299 | wP9+nCf/iaQ5/4ejNv+Hozb/h6M2/4ejNv+Hozb/lLQz/5y+Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 300 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 301 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 302 | Mv+bvDL/m7wy/5u8Mv+cvTX/k7cj/9/qwf+6yY7/fJ0q/zY92P9zjV3/i6gt/4aiOP+Hozb/h6M2/4ej 303 | Nv+JpDn/fpwm/7vKkP/X4MD/fpwn/4mkOf+Hozb/h6M2/4ejNv+Hojb/lbQz/52+Mv+bvDL/m7wy/5u8 304 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 305 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 306 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/nL01/5O3I//f6sH/usmO/3ydKv82Pdj/c41d/4uo 307 | Lf+Gojj/h6M2/4ejNv+Hozb/iaQ5/36cJv+7ypD/1+DA/36cJ/+JpDn/h6M2/4ejNv+Hozb/lbUz/5y+ 308 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 309 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 310 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5y9Nf+TtyP/3+rB/7rJ 311 | jv98nSr/Nj3Y/3ONXf+LqC3/hqI4/4ejNv+Hozb/h6M2/4mkOf9+nCb/u8qQ/9fgwP9+nCf/iaU5/4ej 312 | Nv+Hozb/lrUz/5y+Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 313 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 314 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 315 | Mv+cvTX/k7cj/9/qwf+6yY7/fJ0q/zc+1f9zjV3/i6gt/4aiOP+Hozb/h6M2/4ejNv+JpDn/fpwm/7vK 316 | kP/X4MD/fpwn/4ikOf+Hozb/lrUz/5y+Mv+buzL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 317 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 318 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 319 | Mv+bvDL/m7wy/5u8Mv+bvDL/nL01/5O3I//f6sH/usmO/3ydK/8xNuH/cotg/4yoLf+Gojj/h6M2/4ej 320 | Nv+Hozb/iaQ5/36cJv+7ypD/1+DA/36bJ/+JpTj/l7cz/5y+Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 321 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 322 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 323 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5y9Nf+TtyT/3urA/7rJjv9+nSv/UGOp/3qW 324 | U/+LpzL/h6M5/4ikOP+IpDj/iKQ4/4qlOv9/nSj/u8uP/9bfv/9/nSf/mLg1/5y+Mv+buzL/m7wy/5u8 325 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 326 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 327 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+cvTX/k7Yi/+Hs 328 | xf+5yI7/d5ca/4mnKf+DoSv/gp8u/4KfLf+Cny3/gp8t/4KfLf+EoTH/d5ca/7rKjv/a48T/jbAj/56/ 329 | Nf+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 330 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 331 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 332 | Mv+bvDL/nL01/5S3JP/D14f/8vTw/6G3Zv+bsV3/nbNf/52zXv+ds17/nbNe/52zXv+ds17/nLJc/6G3 333 | Zf/y9PD/w9eG/5W4JP+cvTX/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 334 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 335 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 336 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/mLot/8zdmv/w9eb/6/He/+zy3//s8t//7PLf/+zy 337 | 3//s8t//7PLf/+vx3v/w9eb/zN2a/5i6Lf+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 338 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 339 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 340 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+UtyX/mrsy/529 341 | N/+cvTb/nL02/5y9Nv+cvTb/nL02/5y9Nv+dvTf/mrsy/5S3Jf+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 342 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 343 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 344 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 345 | Mv+bvDL/nL01/5u8Mf+avDD/mrww/5q8MP+avDD/mrww/5q8MP+avDD/mrww/5u8Mf+cvTX/m7wy/5u8 346 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 347 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 348 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 349 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wz/5u8M/+bvDP/m7wz/5u8M/+bvDP/m7wz/5u8 350 | M/+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 351 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 352 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 353 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 354 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 355 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 356 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 357 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 358 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 359 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 360 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 361 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 362 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 363 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 364 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 365 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 366 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 367 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 368 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 369 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 370 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 371 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 372 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 373 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 374 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 375 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 376 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 377 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 378 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 379 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 380 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 381 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 382 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 383 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 384 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 385 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 386 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 387 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 388 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 389 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 390 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 391 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 392 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 393 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 394 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 395 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 396 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 397 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 398 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 399 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 400 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 401 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 402 | Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8Mv+bvDL/m7wy/5u8 403 | Mv+bvDL/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 404 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 405 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 406 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 407 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 408 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 409 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 410 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 411 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 412 | 413 | 414 | -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Windows.Forms; 6 | 7 | namespace M3U8_DL_GUI 8 | { 9 | static class Program 10 | { 11 | /// 12 | /// 应用程序的主入口点。 13 | /// 14 | [STAThread] 15 | static void Main(string[] args) 16 | { 17 | Application.EnableVisualStyles(); 18 | Application.SetCompatibleTextRenderingDefault(false); 19 | if (args.Length == 0) 20 | Application.Run(new Form1()); 21 | else 22 | Application.Run(new Form1(args)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("m3u8_dl_GUI")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("nilaoda")] 12 | [assembly: AssemblyProduct("m3u8_dl_GUI")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | //将 ComVisible 设置为 false 将使此程序集中的类型 18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("fd9fcd0d-df69-4328-9089-39a73017fe9b")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace m3u8_dl_GUI.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("m3u8_dl_GUI.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 | -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/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 | -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace m3u8_dl_GUI.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 | -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/RealAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | 10 | namespace M3U8_DL_GUI 11 | { 12 | // 1.定义委托 13 | public delegate void DelReadStdOutput(string result); 14 | public delegate void DelReadErrOutput(string result); 15 | 16 | public partial class Form1 : Form 17 | { 18 | // 2.定义委托事件 19 | public event DelReadStdOutput ReadStdOutput; 20 | public event DelReadErrOutput ReadErrOutput; 21 | 22 | private void Init() 23 | { 24 | //3.将相应函数注册到委托事件中 25 | ReadStdOutput += new DelReadStdOutput(ReadStdOutputAction); 26 | ReadErrOutput += new DelReadErrOutput(ReadErrOutputAction); 27 | } 28 | 29 | private void RealAction(string StartFileName) 30 | { 31 | Process CmdProcess = new Process(); 32 | CmdProcess.StartInfo.FileName = StartFileName; // 命令 33 | //CmdProcess.StartInfo.Arguments = StartFileArg; // 参数 34 | 35 | CmdProcess.StartInfo.CreateNoWindow = true; // 不创建新窗口 36 | CmdProcess.StartInfo.UseShellExecute = false; 37 | CmdProcess.StartInfo.RedirectStandardInput = true; // 重定向输入 38 | CmdProcess.StartInfo.RedirectStandardOutput = true; // 重定向标准输出 39 | CmdProcess.StartInfo.RedirectStandardError = true; // 重定向错误输出 40 | //CmdProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; 41 | 42 | CmdProcess.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived); 43 | CmdProcess.ErrorDataReceived += new DataReceivedEventHandler(p_ErrorDataReceived); 44 | 45 | CmdProcess.EnableRaisingEvents = true; // 启用Exited事件 46 | CmdProcess.Exited += new EventHandler(CmdProcess_Exited); // 注册进程结束事件 47 | 48 | CmdProcess.Start(); 49 | PID_1 = CmdProcess.Id; //PID 50 | CmdProcess.BeginOutputReadLine(); 51 | CmdProcess.BeginErrorReadLine(); 52 | 53 | // 如果打开注释,则以同步方式执行命令,此例子中用Exited事件异步执行。 54 | //CmdProcess.WaitForExit(); 55 | } 56 | 57 | private void p_OutputDataReceived(object sender, DataReceivedEventArgs e) 58 | { 59 | if (e.Data != null) 60 | { 61 | // 4. 异步调用,需要invoke 62 | this.Invoke(ReadStdOutput, new object[] { e.Data }); 63 | } 64 | } 65 | 66 | private void p_ErrorDataReceived(object sender, DataReceivedEventArgs e) 67 | { 68 | if (e.Data != null) 69 | { 70 | this.Invoke(ReadErrOutput, new object[] { e.Data }); 71 | } 72 | } 73 | 74 | private void ReadStdOutputAction(string result) 75 | { 76 | textBox_Info.AppendText(result + "\r\n"); 77 | if (result.Contains("m3u8_dl.D: download") && result.Contains("clips, video time")) 78 | { 79 | label_dur_clip_out.Text = "m3u8信息:" + result 80 | .Replace("m3u8_dl.D: download", "") 81 | .Replace("clips, video time", "个分片; 预计时长:"); 82 | } 83 | } 84 | 85 | private void ReadErrOutputAction(string result) 86 | { 87 | textBox_Info.AppendText(result + "\r\n"); 88 | } 89 | 90 | private void CmdProcess_Exited(object sender, EventArgs e) 91 | { 92 | button_Download.Enabled = true; //启用下载按钮 93 | 94 | //杀死读取下载速度的进程 95 | 96 | EndProcessTree(PID_2); 97 | PID_1 = 0; 98 | PID_2 = 0; 99 | 100 | bool isNormalExited = File.Exists(downpath + "\\isNormalExited"); //是否是正常停止进程 101 | 102 | if (isNormalExited && checkBox_auto_merge.Checked == true && checkBox_del_afterMerge.Checked == true) 103 | { 104 | try 105 | { 106 | DirectoryInfo directoryInfo = new DirectoryInfo(downpath); 107 | directoryInfo.Delete(true); 108 | } 109 | catch (Exception) { } 110 | } 111 | 112 | MessageBox.Show("命令执行结束!", "m3u8_dl_GUI", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly); // 执行结束后触发 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/Run.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | 9 | namespace M3U8_DL_GUI 10 | { 11 | public partial class Form1 : Form 12 | { 13 | private void RealAction2(string StartFileName) 14 | { 15 | Process p = new Process(); 16 | p.StartInfo.FileName = StartFileName; 17 | p.StartInfo.UseShellExecute = false; //必须为false才能重定向输出 18 | p.StartInfo.RedirectStandardInput = true; 19 | p.StartInfo.RedirectStandardOutput = true; 20 | p.StartInfo.RedirectStandardError = true; 21 | p.StartInfo.CreateNoWindow = true; 22 | p.OutputDataReceived += new DataReceivedEventHandler(P_OutputDataReceived); 23 | p.Start(); 24 | PID_2 = p.Id; //PID 25 | p.BeginOutputReadLine(); 26 | } 27 | 28 | private delegate void AddMessageHandler(string msg); 29 | 30 | private void P_OutputDataReceived(object sender, DataReceivedEventArgs e) 31 | { 32 | AddMessageHandler handler = delegate (string msg) 33 | { 34 | this.label_speed_out.Text = msg; 35 | }; 36 | if (this.label_speed_out.InvokeRequired) 37 | this.label_speed_out.Invoke(handler, e.Data); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/bitbug_favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/m3u8_dl-js_GUI/3e94310ece920ca82735d15ac82f7001279da02d/m3u8_dl-js_GUI/bitbug_favicon.ico -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/download_64px_1170980_easyicon.net.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/m3u8_dl-js_GUI/3e94310ece920ca82735d15ac82f7001279da02d/m3u8_dl-js_GUI/download_64px_1170980_easyicon.net.ico -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/m3u8_dl-js_GUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FD9FCD0D-DF69-4328-9089-39A73017FE9B} 8 | WinExe 9 | Properties 10 | m3u8_dl_GUI 11 | m3u8_dl_GUI 12 | v4.6 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | download_64px_1170980_easyicon.net.ico 37 | 38 | 39 | true 40 | bin\x64\Debug\ 41 | DEBUG;TRACE 42 | full 43 | x64 44 | prompt 45 | MinimumRecommendedRules.ruleset 46 | true 47 | 48 | 49 | bin\x64\Release\ 50 | TRACE 51 | true 52 | pdbonly 53 | x64 54 | prompt 55 | MinimumRecommendedRules.ruleset 56 | true 57 | 58 | 59 | true 60 | bin\x86\Debug\ 61 | DEBUG;TRACE 62 | full 63 | x86 64 | prompt 65 | MinimumRecommendedRules.ruleset 66 | true 67 | 68 | 69 | bin\x86\Release\ 70 | TRACE 71 | true 72 | pdbonly 73 | x86 74 | prompt 75 | MinimumRecommendedRules.ruleset 76 | true 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | Form 95 | 96 | 97 | Form 98 | 99 | 100 | Mainform.cs 101 | 102 | 103 | 104 | 105 | Form 106 | 107 | 108 | Form 109 | 110 | 111 | Mainform.cs 112 | 113 | 114 | ResXFileCodeGenerator 115 | Resources.Designer.cs 116 | Designer 117 | 118 | 119 | True 120 | Resources.resx 121 | True 122 | 123 | 124 | SettingsSingleFileGenerator 125 | Settings.Designer.cs 126 | 127 | 128 | True 129 | Settings.settings 130 | True 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 148 | -------------------------------------------------------------------------------- /m3u8_dl-js_GUI/m3u8_dl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace M3U8_DL_GUI 12 | { 13 | public partial class Form1 : Form 14 | { 15 | //建立动态数组存放文件名数据 16 | ArrayList fileName = new ArrayList(); 17 | 18 | //x64 19 | string curl_path = System.Windows.Forms.Application.StartupPath + @"\Tools\curl_64.exe"; 20 | string node_path = System.Windows.Forms.Application.StartupPath + @"\Tools\node_64.exe"; 21 | 22 | //x86 23 | //string curl_path = System.Windows.Forms.Application.StartupPath + @"\Tools\curl.exe"; 24 | //string node_path = System.Windows.Forms.Application.StartupPath + @"\Tools\node.exe"; 25 | 26 | public string GetCommand() 27 | { 28 | string ffmerge_path = System.Windows.Forms.Application.StartupPath + @"\Tools\ffmerge.exe"; //ffmerge路径 29 | string ffmpeg_path = System.Windows.Forms.Application.StartupPath + @"\Tools\ffmpeg.exe"; //ffmpeg路径 30 | //string logger_path = System.Windows.Forms.Application.StartupPath + "\\Tools\\logger.exe"; //logger路径 31 | string m3u8_dl_path = System.Windows.Forms.Application.StartupPath + @"\Tools\m3u8_dl-js-master\dist\m3u8_dl.js"; //主程序路径 32 | string is_task_done_path = System.Windows.Forms.Application.StartupPath + @"\Tools\m3u8_dl-js-master\dist\is_task_done.js"; 33 | string auto_retry_path = System.Windows.Forms.Application.StartupPath + @"\Tools\m3u8_dl-js-master\dist\auto_retry.js"; 34 | string m3u8_path = textBox_Adress.Text.Trim().Replace("%","%%"); 35 | //string log_name = System.Windows.Forms.Application.StartupPath + "\\Logs\\Log-" + System.DateTime.Now.ToString("yyyy.MM.dd-HH.mm.ss") + ".txt"; //日志文件位置 36 | string command = ""; 37 | string file_path = label_outPutPath.Text + "\\" + textBox_filename.Text; 38 | //string command = "\"" + logger_path + "\" -format raw -log \"" + log_name + "\" : "; //最终命令 39 | command += "(echo =======================-m3u8_dl-js 版本信息-=======================)"; 40 | command += " && (\"" + node_path + "\"" + " \"" + m3u8_dl_path + "\" --version)"; 41 | command += " && (echo =======================-m3u8_dl-js 命令开始-=======================)"; 42 | command += " && (" + "\"" + node_path + "\"" + " \"" + auto_retry_path + "\" -o \"" + label_outPutPath.Text + "\\" + textBox_filename.Text.Replace("%", "%%") + "\"" 43 | + " --retry " + numericUpDown_retry.Text + " --sleep " + numericUpDown_sleep.Text + " --remove-part-files --use-raw-m3u8 \"" + m3u8_path + "\"" 44 | + " -- " //下面是传递给m3u8_dl的参数 45 | + "--thread " + numericUpDown_threads.Text + " "; 46 | if (checkBox_auto_remove.Checked == true) { command += "--auto-remove "; } 47 | if (checkBox_add_base_url.Checked == true && textBox_Base_url.Text != "" && !textBox_Base_url.Text.Contains(" ")) 48 | { 49 | command += "--m3u8-base-url " + textBox_Base_url.Text.Trim().Replace("%", "%%") + " "; 50 | } 51 | if (checkBox_proxy.Checked == true && textBox_proxy.Text != "") 52 | { 53 | if (radioButton_http.Checked == true) { command += "--proxy-http \"" + textBox_proxy.Text.Trim() + "\" "; } 54 | if (radioButton_socks5.Checked == true) { command += "--proxy-socks5 \"" + textBox_proxy.Text.Trim() + "\" "; } 55 | } 56 | if (checkBox_Headers.Checked == true && textBox_Headers.Text != "") 57 | { 58 | string[] headers = GetHeader(textBox_Headers.Text.Replace("%", "%%")); 59 | for(int i = 0; i < headers.Length; i++) 60 | { 61 | command += "--header \"" + headers[i] + "\" "; 62 | } 63 | } 64 | if (checkBox_down_with_curl.Checked == true) 65 | { 66 | command += "--dl-with-curl \"" + curl_path + "\" "; 67 | } 68 | 69 | command += "--exit-on-flag)"; 70 | command += " && (" + "echo =======================-检测是否完成下载-=======================)"; 71 | command += " && (" + "\"" + node_path + "\"" + " \"" + is_task_done_path + "\" \"" + label_outPutPath.Text + "\\" + textBox_filename.Text + "\")"; //检测是否完成下载 72 | 73 | //合并选项 74 | if (checkBox_auto_merge.Checked == true) 75 | { 76 | command += " && (cd /d \"" + label_outPutPath.Text + "\")"; 77 | command += " && (" + "echo =======================-合并命令开始-=======================)"; 78 | 79 | /* 80 | if (radioButton_mergeBinary.Checked == true) 81 | { 82 | command += " && " //+ "\"" + logger_path + "\" -format raw -append -log \"" + log_name + "\" : " 83 | + "copy /b \"" + label_outPutPath.Text + "\\" + textBox_filename.Text + "\\*.ts\" \"" 84 | + label_outPutPath.Text + "\\" + textBox_filename.Text + ".ts\""; 85 | } 86 | if (radioButton_mergeFFmpeg.Checked == true) 87 | { 88 | string file_path = label_outPutPath.Text + "\\" + textBox_filename.Text; 89 | command += " && cd /d \"" + file_path + "\" " 90 | + " && \"" + ffmpeg_path + "\" -f concat -safe 0 -i \"" 91 | + label_outPutPath.Text + "\\" + textBox_filename.Text 92 | + "\\ffmpeg_merge.list\" -threads 0 -c copy -f mpegts \"" 93 | + label_outPutPath.Text + "\\" + textBox_filename.Text + ".ts\""; 94 | command += " && cd /d .."; //调回上级目录 95 | } 96 | */ 97 | 98 | //通过 ffmpeg_merge.list 读取全部文件,写入数组,生成转换文件 99 | //修改了 do_dl.js 的 114 行 o.push(`${c.name.ts}`); 100 | 101 | if (checkBox_IsNewMerge.Checked == true) 102 | { 103 | command += " && (echo [转换分片中...])"; 104 | command += " && (for /f \"usebackq tokens=*\" %%i in (\"" + file_path + "\\ffmpeg_merge.list\") do ("; //usebackq参数,防止引号中的内容被当成字符串 105 | command += " \"" + ffmpeg_path + "\" -y -i \"" + file_path + "\\%%i\" -loglevel quiet -map 0 -c copy -f mpegts -bsf:v h264_mp4toannexb \"" + file_path + "\\[TS]%%i\""; 106 | command += " && del \"" + file_path + "\\%%i\""; 107 | command += " && move \"" + file_path + "\\[TS]%%i\" \"" + file_path + "\\%%i\" >nul))"; 108 | } 109 | 110 | /* 111 | command += " && (echo [合并分片中...])" 112 | + " && (copy /b \"" + file_path + "\\*.ts\" \"" 113 | + file_path + ".ts\")"; 114 | */ 115 | 116 | command += " && pushd \"" + file_path + "\""; //进入此目录,为合并做准备 117 | 118 | //转换格式部分 119 | if (comboBox_convertFormat.SelectedIndex != 0) 120 | { 121 | if (comboBox_convertFormat.SelectedIndex == 1) //MP4 122 | { 123 | /* 124 | command += " && (" + "\"" + ffmpeg_path + "\"" + " -threads 0 -i \"" + file_path + ".ts\"" 125 | + " -c copy -y -bsf:a aac_adtstoasc \"" + file_path + ".mp4\")"; 126 | */ 127 | command += " && (" + "\"" + ffmerge_path + "\" \"" 128 | + ffmpeg_path + "\" \"" + file_path 129 | + "\\ffmpeg_merge.list\" \"" + file_path + "\" MP4)"; 130 | } 131 | if (comboBox_convertFormat.SelectedIndex == 2) //MKV 132 | { 133 | command += " && (" + "\"" + ffmerge_path + "\" \"" 134 | + ffmpeg_path + "\" \"" + file_path 135 | + "\\ffmpeg_merge.list\" \"" + file_path + "\" MKV)"; 136 | } 137 | if (comboBox_convertFormat.SelectedIndex == 3) //FLV 138 | { 139 | command += " && (" + "\"" + ffmerge_path + "\" \"" 140 | + ffmpeg_path + "\" \"" + file_path 141 | + "\\ffmpeg_merge.list\" \"" + file_path + "\" FLV)"; 142 | } 143 | if (comboBox_convertFormat.SelectedIndex == 4) //TS 144 | { 145 | command += " && (" + "\"" + ffmerge_path + "\" \"" 146 | + ffmpeg_path + "\" \"" + file_path 147 | + "\\ffmpeg_merge.list\" \"" + file_path + "\" TS)"; 148 | } 149 | } 150 | else //不转换格式,采用旧版二进制合并方案 151 | { 152 | command += " && (" + "echo =======================-合并命令开始-=======================)"; 153 | command += " && (copy /b \"" + file_path + "\\*.ts\" \"" 154 | + file_path + ".ts\")"; 155 | } 156 | 157 | /* 158 | if (checkBox_del_afterMerge.Checked == true) 159 | { 160 | command += " && (" + "echo =======================-删除命令开始-=======================)"; 161 | command += " && (ping 127.0.0.1 -n 3 >nul)"; //延迟3秒执行 162 | command += " && (" //+ "\"" + logger_path + "\" -format raw -append -log \"" + log_name + "\" : " 163 | + "rd /s /Q \"" + file_path + "\")"; 164 | } 165 | */ 166 | } 167 | 168 | command += " && (type nul >\"" + file_path + "\\isNormalExited\")"; //所有命令执行完毕后生成空白文件用于判断是否为正常退出进程 169 | 170 | return command.Replace(":\\\\", ":\\"); //简单粗暴解决根目录问题 171 | } 172 | 173 | public string GetSpeedCommand(string batPath) 174 | { 175 | string show_dl_speed_path = System.Windows.Forms.Application.StartupPath + "\\Tools\\m3u8_dl-js-master\\dist\\show_dl_speed.js"; 176 | string command = ""; 177 | command = "\"" + node_path + "\" \"" + show_dl_speed_path + "\" --retry-after 30 --retry-hide 5 --put-exit-flag \"" 178 | + label_outPutPath.Text + "\\" + textBox_filename.Text + "\""; 179 | string file_path = batPath.Replace("m3u8_dl_GUI_Download", "m3u8_dl_GUI_Show_Speed"); 180 | StreamWriter writer = new StreamWriter(file_path, false, Encoding.Default); //false代表替换而不是追加 181 | writer.WriteLine("@echo off"); 182 | writer.Write(command); 183 | writer.Close(); 184 | 185 | return file_path; 186 | } 187 | 188 | private static string[] GetHeader(string ArrayStr) 189 | { 190 | string StrJson = ArrayStr; 191 | return StrJson.Split('|'); 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /pre_processor/doc/pre_processor.md: -------------------------------------------------------------------------------- 1 | 4 | (`zh_CN`) 5 | 6 | # `m3u8_dl-js_GUI` 预处理程序 接口规范 7 | `version 0.1.0` 8 | 9 | 10 | ## 1. 概述 11 | 12 | `m3u8_dl-js_GUI` (以下 简称 `GUI`) 使用 **预处理程序** (`pre_processor`) 来 13 | 增强 其功能. 14 | 15 | 预处理程序 可以使用 **任意** 编程语言 实现, 目前 至少支持: 16 | 17 | + `python 3` (python 3.6 运行环境) 18 | + `nodejs` (node v8.0 运行环境) 19 | 20 | 21 | ## 2. 命令行参数 (格式) 22 | 23 | `GUI` 使用 如下格式 调用 预处理程序: 24 | 25 | ``` 26 | PRE_PROCESSOR OUT_FILE RAW_FILE [URL] [-- OPTIONS] 27 | ``` 28 | 29 | + **`PRE_PROCESSOR`**
30 | 预处理程序 (可执行程序 或者 主入口文件) 31 | 32 | + **`OUT_FILE`**
33 | 输出文件 (文件名 / 绝对路径) 34 | 35 | `GUI` 期望 预处理程序 生成的文件. 36 | 37 | + **`RAW_FILE`**
38 | 原始文件 (文件名 / 绝对路径) 39 | 40 | 预处理程序 的 输入文件. 41 | 42 | + **`URL`** (可选)
43 | 原始文件 对应 的 原始 URL 44 | 45 | **注**: 如果 没有 原始 URL, 那么 没有 此项 参数. 46 | 47 | + **`--`** (可选)
48 | 自定义参数 的 分隔符 49 | 50 | + **`OPTIONS`** (可选)
51 | 传递给 预处理程序 的 自定义参数 (可能 有 多个) 52 | 53 | 例如, `GUI` 使用 如下方式 (命令行 参数) 调用一个 预处理程序: 54 | 55 | ``` 56 | python pp1.py out1 raw.m3u8 "http://test.org/1.m3u8" -- XXX YYY 57 | ``` 58 | 59 | 60 | ## 3. 调用 预处理程序 61 | 62 | `GUI` 使用如下 步骤 调用 预处理程序: 63 | 64 | + (1) `GUI` 下载 原始 URL 对应的 文件, 作为 原始文件 (`RAW_FILE`) 65 | 66 | + (2) `GUI` 使用 原始文件 路径, 原始 URL 等 作为 参数, 调用 预处理程序 67 | 68 | + (3) `GUI` 分析/处理 预处理程序 生成的 输出文件 (`OUT_FILE`) 69 | 70 | 71 | ## 4. 输出文件 72 | 73 | 预处理程序 可以根据 `GUI` 指定的 路径 (`OUT_FILE`), 输出 以下类型的 文件: 74 | 75 | + (1) **不输出** 文件
76 | 预处理程序 没有 生成 输出文件. 77 | 78 | 当 预处理程序 发生错误 时, 可以 不生成 输出文件. 79 | 此时 `GUI` 将从 预处理程序 (进程) 的 80 | 标准输出 (`stdout`) / 标准错误输出 (`stderr`) 获取 错误信息. 81 | 82 | + (2) 输出 **`OUT_FILE.error`** 文件 83 | 84 | 发生错误 时, 预处理程序 也可以生成一个 文本文件, 里面含有 错误信息. 85 | 86 | + (3) 输出 **`OUT_FILE.m3u8`** 文件
87 | 生成 标准格式 的 m3u8 文件. 88 | 89 | + (4) 输出 **`OUT_FILE.list`** 文件
90 | 生成 列表 文件. 91 | 92 | 此 文本文件 格式 如下: 93 | 94 | ``` 95 | # 说明 (可选) 96 | URL 97 | ``` 98 | 99 | 每连续的 2 行 为 一组, 表示 列表中的 一项. 100 | 以 `#` 字符 起始的 说明 行, 可以 省略. 101 | 102 | **注**: 空白行 会被 忽略. 103 | 104 | + (5) 输出 **`OUT_FILE.format`** 文件
105 | 生成 多格式选择 文件. 106 | 107 | 此 文本文件 格式 如下: 108 | 109 | ``` 110 | # 格式说明 111 | OPTIONS 112 | ``` 113 | 114 | 类似 *列表* 文件, 每连续 2 行 为 一组. 115 | 116 | `OPTIONS` 为 需要 传递给 预处理程序 的 自定义参数. 117 | 118 | 119 | ## 5. 预处理程序 打包格式 120 | 121 | 为了方便 预处理程序 的 分发 以及 用户 使用, 可以 对 预处理程序 打包. 122 | 123 | 打包格式 为 标准的 **`zip` 压缩文件**, 后缀为 **`.zip.ppp`** . 124 | (其中 `ppp` 表示 `Pre Processor Package` ) 125 | 126 | 压缩包 根目录 下 有一个 **`pre_processor.meta.json`** 文件, 描述 预处理程序 有关信息. 127 | 此文件 格式如下: 128 | 129 | ``` 130 | // 下面 使用 javascript 语法描述 文件格式, 实际使用时 请使用 JSON 语法 131 | { 132 | pp_version: '0.1.0', // 预处理程序 接口规范 版本号 (当前 是 '0.1.0') 133 | 134 | id: '', // 预处理程序 标识 135 | name: '', // 此 预处理程序 用户友好 的 描述性 名称 136 | version: '', // 预处理程序 版本号 137 | 138 | type: '', // 预处理程序 类型, 可用 值 有: 'python3', 'nodejs', 'exe' 139 | main: '', // 主文件 (可执行程序 或者 入口文件) 路径 140 | // (相对于此 `pre_processor.meta.json` 文件) 141 | 142 | // 以上 项目 是 **必须** 的, 以下 项目 是 *可选* 的 143 | license: '', // LICENSE 144 | author: '', // 作者 145 | copyright: '', 146 | home: '', // 项目 (此 预处理程序) 主页 URL 147 | 148 | // 此外, 还允许使用 自定义项目, 自定义项目 请以 `_` (下划线) 起始 149 | } 150 | ``` 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /pre_processor/pp_271-m-ts/pp_271_m_ts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.6 2 | # pp_271_m_ts.py, m3u8_dl-js_GUI/pre_processor/pp_271-m-ts/ 3 | # 4 | # upstream: 5 | # LICENSE: GNU GPL v3+ 6 | # 7 | import sys 8 | 9 | import re 10 | import time, hashlib 11 | import urllib.request 12 | import json 13 | 14 | 15 | def r1(r, text): 16 | m = re.search(r, text) 17 | if m: 18 | return m.group(1) 19 | 20 | def dl_json(url): 21 | print('D: GET ' + url) 22 | r = urllib.request.urlopen(url) 23 | return json.load(r) 24 | 25 | 26 | _BID_TO_FORMAT = { 27 | 19 : { 28 | 'index': 7, 29 | 'name': '4K (h265)', 30 | }, 31 | 18 : { 32 | 'index': 6, 33 | 'name': '1080p (h265)', 34 | }, 35 | 10 : { 36 | 'index': 5, 37 | 'name': '4K', # h264 38 | }, 39 | 5 : { 40 | 'index': 4, 41 | 'name': '1080p', # h264 42 | }, 43 | 44 | 17 : { 45 | 'index': 3, 46 | 'name': '720p (h265)', 47 | }, 48 | 4 : { 49 | 'index': 2, 50 | 'name': '720p', # h264 51 | }, 52 | 53 | 21 : { 54 | 'index': 1, 55 | 'name': '540p (h265)', 56 | }, 57 | 2 : { 58 | 'index': 0, 59 | 'name': '540p', 60 | }, 61 | 1 : { 62 | 'index': -1, 63 | 'name': '360p', 64 | }, 65 | 96 : { 66 | 'index': -2, 67 | 'name': '210p', 68 | }, 69 | } 70 | 71 | 72 | class Mts(object): 73 | 74 | @staticmethod 75 | def vms_url(tvid, vid): 76 | src = '76f90cbd92f94a2e925d83e8ccd22cb7' 77 | key = 'd5fb4bd9d50c4be6948c97edd7254b0e' 78 | 79 | t = int(time.time() * 1e3) 80 | sc = hashlib.new('md5', bytes(str(t) + key + vid, 'utf-8')).hexdigest() 81 | return 'http://cache.m.iqiyi.com/tmts/{0}/{1}/?t={2}&sc={3}&src={4}'.format(tvid, vid, t, sc, src) 82 | 83 | @staticmethod 84 | def get_info(html, url): 85 | o = {} 86 | o['tvid'] = \ 87 | r1(r'#curid=(.+)_', url) or \ 88 | r1(r'tvid=([^&]+)', url) or \ 89 | r1(r'data-player-tvid="([^"]+)"', html) 90 | o['vid'] = \ 91 | r1(r'#curid=.+_(.*)$', url) or \ 92 | r1(r'vid=([^&]+)', url) or \ 93 | r1(r'data-player-videoid="([^"]+)"', html) 94 | o['title'] = r1('([^<]+)', html).split('-')[0] 95 | return o 96 | 97 | @staticmethod 98 | def parse_vms(vms, tvid): 99 | OK_CODE = 'A00000' 100 | 101 | if vms['code'] != OK_CODE: 102 | raise Exception('vms.code = `' + vms['code'] + '` != ' + OK_CODE) 103 | o = [] 104 | ctl = vms['data']['ctl'] 105 | for bid in ctl['vip']['bids']: 106 | vid = ctl['configs'][str(bid)]['vid'] 107 | vms_url = Mts.vms_url(tvid, vid) 108 | print('D: bid = ' + str(bid) + ', vid = ' + str(vid)) 109 | v = dl_json(vms_url) 110 | if v['code'] != OK_CODE: 111 | raise Exception('vms.code = `' + v['code'] + '` != ' + OK_CODE) 112 | m3u8_url = v['data']['m3u'] 113 | o.append({ 114 | 'bid': bid, 115 | 'm3u8_url': m3u8_url, 116 | }) 117 | for s in vms['data']['vidl']: 118 | o.append({ 119 | 'bid': s['vd'], 120 | 'm3u8_url': s['m3u'], 121 | }) 122 | return o 123 | 124 | @staticmethod 125 | def output(v): 126 | o = [] 127 | for i in v: 128 | one = _BID_TO_FORMAT[i['bid']].copy() 129 | one['url'] = i['m3u8_url'] 130 | o.append(one) 131 | o.sort(key = lambda x: x['index'], reverse=True) 132 | return o 133 | 134 | @staticmethod 135 | def parse(html, url): 136 | info = Mts.get_info(html, url) 137 | print('D: tvid = ' + info['tvid'] + ', vid = ' + info['vid'] + ', title = ' + info['title']) 138 | vms_url = Mts.vms_url(info['tvid'], info['vid']) 139 | vms = dl_json(vms_url) 140 | 141 | v = Mts.parse_vms(vms, info['tvid']) 142 | o = { 143 | 'title': info['title'], 144 | 'v': Mts.output(v), 145 | } 146 | return o 147 | 148 | def _make_out_file(title, v): 149 | o = [] 150 | o += ['# ' + title, ''] 151 | for i in v: 152 | o += ['# ' + i['name'], i['url'], ''] 153 | return ('\n').join(o) 154 | 155 | def main(args): 156 | OUT_FILE, RAW_FILE, URL = sys.argv[1], sys.argv[2], sys.argv[3] 157 | 158 | with open(RAW_FILE, 'rt') as f: 159 | html = f.read() 160 | o = Mts.parse(html, URL) 161 | text = _make_out_file(o['title'], o['v']) 162 | 163 | with open(OUT_FILE + '.format', 'wt') as f: 164 | f.write(text) 165 | 166 | if __name__ == '__main__': 167 | exit(main(sys.argv[1:])) 168 | # end pp_271_m_ts.py 169 | -------------------------------------------------------------------------------- /pre_processor/pp_271-m-ts/pre_processor.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "pp_version": "0.1.0", 3 | 4 | "id": "pp_271-m-ts", 5 | "name": "WWQ 271 移动端 m3u8", 6 | "version": "0.1.0 test20170610 0210", 7 | 8 | "type": "python3", 9 | "main": "pp_271_m_ts.py", 10 | 11 | "license": "GNU GPL v3+", 12 | "author": "sceext <sceext@foxmail.com>, wwqgtxx <wwqgtxx@gmail.com>", 13 | "home": "https://github.com/nilaoda/m3u8_dl-js_GUI", 14 | 15 | "_support_site": "271", 16 | "_upstream": "https://github.com/wwqgtxx/wwqLyParse" 17 | } 18 | -------------------------------------------------------------------------------- /pre_processor/pp_le-decode/pp_le_decode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.6 2 | # pp_le_decode.py, m3u8_dl-js_GUI/pre_processor/pp_le-decode/ 3 | # 4 | # upstream: <https://github.com/zhangn1985/ykdl> 5 | # 6 | import sys 7 | 8 | def decode(data): 9 | version = data[0:5] 10 | if version.lower() == b'vc_01': 11 | loc2 = data[5:] 12 | length = len(loc2) 13 | loc4 = [0] * (2 * length) 14 | for i in range(length): 15 | loc4[2 * i] = int(loc2[i]) >> 4 16 | loc4[2 * i + 1] = int(loc2[i]) & 15 17 | loc6 = loc4[len(loc4) - 11 :] + loc4[:len(loc4) - 11] 18 | loc7 = [0] * length 19 | for i in range(length): 20 | loc7[i] = (loc6[2 * i] << 4) + loc6[2 * i + 1] 21 | return ('').join([chr(i) for i in loc7]) 22 | else: 23 | return data 24 | 25 | def main(arg): 26 | OUT_FILE, RAW_FILE = arg[0], arg[1] 27 | 28 | with open(RAW_FILE, 'rb') as f: 29 | data = f.read() 30 | text = decode(data) 31 | 32 | with open(OUT_FILE + '.m3u8', 'wt') as f: 33 | f.write(text) 34 | 35 | if __name__ == '__main__': 36 | exit(main(sys.argv[1:])) 37 | # end pp_le_decode.py 38 | -------------------------------------------------------------------------------- /pre_processor/pp_le-decode/pre_processor.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "pp_version": "0.1.0", 3 | 4 | "id": "pp_le-decode", 5 | "name": "乐视 m3u8 解码", 6 | "version": "0.1.0 test20170611 1629", 7 | 8 | "type": "python3", 9 | "main": "pp_le_decode.py", 10 | 11 | "license": "GNU GPL v3+", 12 | "author": "sceext <sceext@foxmail.com>", 13 | "home": "https://github.com/nilaoda/m3u8_dl-js_GUI", 14 | 15 | "_support_site": "www.le.com", 16 | "_upstream": "https://github.com/zhangn1985/ykdl" 17 | } 18 | -------------------------------------------------------------------------------- /pre_processor/pp_tv-line-me/pp_tv-line-me.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.6 2 | # pp_tv-line-me.py, m3u8_dl-js_GUI/pre_processor/pp_tv-line-me/ 3 | # m3u8 pre-processor for tv.line.me 4 | # 5 | # <https://github.com/nilaoda/m3u8_dl-js_GUI> 6 | # 7 | import sys 8 | 9 | OUT_FILE, RAW_FILE, URL = sys.argv[1], sys.argv[2], sys.argv[3] 10 | 11 | 12 | query = URL.split('?', 1)[1] 13 | base_url = URL.split('?', 1)[0].rsplit('/', 1)[0] 14 | 15 | with open(RAW_FILE, 'rt') as f: 16 | line = f.readlines() 17 | 18 | out = [] 19 | for l in line: 20 | l = l.strip() 21 | if l.startswith('#') or (l == ''): 22 | out.append(l) 23 | else: 24 | out.append(base_url + '/' + l + '?' + query) 25 | out_text = ('\n').join(out) + '\n' 26 | 27 | with open(OUT_FILE + '.m3u8', 'wt') as f: 28 | f.write(out_text) 29 | 30 | # end pp_tv-line-me.py 31 | -------------------------------------------------------------------------------- /pre_processor/pp_tv-line-me/pre_processor.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "pp_version": "0.1.0", 3 | 4 | "id": "pp_tv-line-me", 5 | "name": "tv.line.me 的 m3u8 预处理程序", 6 | "version": "0.1.0 test20170608 1858", 7 | 8 | "type": "python3", 9 | "main": "pp_tv-line-me.py", 10 | 11 | "license": "GNU GPL v3+", 12 | "author": "sceext <sceext@foxmail.com>", 13 | "home": "https://github.com/nilaoda/m3u8_dl-js_GUI", 14 | 15 | "_support_site": "tv.line.me" 16 | } 17 | -------------------------------------------------------------------------------- /pre_processor/pp_wwq-any_page-url_list/pp_wwq_any_page.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.6 2 | # pp_wwq_any_page.py, m3u8_dl-js_GUI/pre_processor/pp_wwq-any_page-url_list/ 3 | # 4 | # upstream: <https://github.com/wwqgtxx/wwqLyParse> 5 | # LICENSE: GNU GPL v3+ 6 | # 7 | import sys 8 | 9 | import re 10 | from pyquery import PyQuery as pq 11 | 12 | 13 | def load_page(url): 14 | d = pq(url=url) 15 | return d 16 | 17 | 18 | class AnyPage(object): 19 | 20 | @staticmethod 21 | def check_a(raw): 22 | a = pq(raw) 23 | title = a.attr('title') 24 | if title == None: 25 | title = a.text() 26 | url = a.attr('href') 27 | # remove bad urls 28 | if url == None: 29 | return None 30 | if (title == None) or (title.strip() == ''): 31 | return None 32 | 33 | if re.match('^(http|https|ftp)://.+\.(mp4|mkv|ts|avi)', url): 34 | url = '$-> ' + url 35 | if not re.match('(^(http|https)://.+\.(shtml|html|mp4|mkv|ts|avi))|(^(http|https)://.+/video/)', url): 36 | return None 37 | if re.search('[^\?](list|mall|about|help|shop|map|vip|faq|support|download|copyright|contract|product|tencent|upload|common|index.html|v.qq.com/u/|open.baidu.com|www.iqiyi.com/lib/s_|www.iqiyi.com/dv/|top.iqiyi.com)', url): 38 | return None 39 | if re.search('(下载|播 放|播放|投诉|评论|(\d{1,2}:\d{1,2}))', title): 40 | return None 41 | 42 | return { 'title': title, 'url': url } 43 | 44 | @staticmethod 45 | def parse(html, url): 46 | d = pq(html) 47 | page_title = d('title').text() 48 | 49 | used_urls = {} 50 | o = [] 51 | for i in d('a'): 52 | one = AnyPage.check_a(i) 53 | if one == None: 54 | continue 55 | # ignore dup urls 56 | if one['url'] in used_urls: 57 | continue 58 | used_urls[one['url']] = True 59 | o.append(one) 60 | return { 'title': page_title, 'list': o } 61 | 62 | def _make_out_file(raw): 63 | o = [] 64 | o += ['# ' + raw['title'], ''] 65 | for i in raw['list']: 66 | o += ['# ' + i['title'], i['url'], ''] 67 | return ('\n').join(o) 68 | 69 | def main(arg): 70 | OUT_FILE, RAW_FILE, URL = arg[0], arg[1], arg[2] 71 | 72 | with open(RAW_FILE, 'rt') as f: 73 | html = f.read() 74 | o = AnyPage.parse(html, URL) 75 | text = _make_out_file(o) 76 | 77 | with open(OUT_FILE + '.list', 'wt') as f: 78 | f.write(text) 79 | 80 | if __name__ == '__main__': 81 | exit(main(sys.argv[1:])) 82 | # end pp_wwq_any_page.py 83 | -------------------------------------------------------------------------------- /pre_processor/pp_wwq-any_page-url_list/pre_processor.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "pp_version": "0.1.0", 3 | 4 | "id": "pp_wwq-any_page-url_list", 5 | "name": "WWQ 任意页面 URL 列表", 6 | "version": "0.1.0 test20170610 1616", 7 | 8 | "type": "python3", 9 | "main": "pp_wwq_any_page.py", 10 | 11 | "license": "GNU GPL v3+", 12 | "author": "sceext <sceext@foxmail.com>, wwqgtxx <wwqgtxx@gmail.com>", 13 | "home": "https://github.com/nilaoda/m3u8_dl-js_GUI", 14 | 15 | "_support_site": "*", 16 | "_upstream": "https://github.com/wwqgtxx/wwqLyParse", 17 | "_deps": { 18 | "pyquery": "https://pypi.python.org/pypi/pyquery", 19 | "cssselect": "https://pypi.python.org/pypi/cssselect", 20 | "lxml": "https://pypi.python.org/pypi/lxml" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pre_processor/ppp_unpacker/ppp_unpacker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.6 2 | # ppp_unpacker.py, m3u8_dl-js_GUI/pre_processor/ppp_unpacker/ 3 | # unpack pre-processor's `.zip.ppp` file, and check `pre_processor.meta.json` 4 | # 5 | # Usage: 6 | # --to TMP_DIR ZIP_FILE 7 | # or 8 | # --check PP_DIR 9 | # 10 | import os, sys 11 | import zipfile 12 | import shutil 13 | import json 14 | 15 | 16 | PP_VERSION = '0.1.0' # pre_processor specification version 17 | META_FILE = 'pre_processor.meta.json' 18 | 19 | # check `pre_processor.meta.json`, return True if error 20 | def _check_meta(raw_text): 21 | try: 22 | meta = json.loads(raw_text) 23 | except Exception: 24 | print('ERROR: meta: bad json format !') 25 | raise 26 | if not isinstance(meta, dict): 27 | print('ERROR: meta: bad json format !') 28 | return True 29 | must_keys = [ 30 | 'pp_version', 31 | 'id', 'name', 'version', 32 | 'type', 'main', 33 | ] 34 | optional_keys = [ 35 | 'license', 'author', 'copyright', 'home', 36 | ] 37 | # check pp_version 38 | if meta.get('pp_version', None) != PP_VERSION: 39 | print('ERROR: meta: pp_version != ' + PP_VERSION) 40 | return 1 41 | # check must keys 42 | bad_key = False 43 | for i in must_keys: 44 | k = meta.get(i, None) 45 | if not isinstance(k, str): 46 | print('ERROR: meta: bad key `' + i + '`') 47 | bad_key = True 48 | elif k.strip() == '': 49 | print('ERROR: meta: empty key `' + i + '`') 50 | bad_key = True 51 | # check optional keys 52 | for i in optional_keys: 53 | if (i in meta) and (not isinstance(meta[i], str)): 54 | print('ERROR: meta: bad key `' + i + '`') 55 | bad_key = True 56 | if bad_key: 57 | return 1 58 | # check unknow keys 59 | allow_keys = must_keys + optional_keys 60 | for i in meta.keys(): 61 | if (not i in allow_keys) and (not i.startswith('_')): 62 | print('WARNING: unknow key `' + i + '`') 63 | # check done 64 | return False 65 | 66 | 67 | # to process `.zip.ppp` file 68 | class Ppp(object): 69 | def __init__(self, zip_file): 70 | # open the zip file 71 | self._z = zipfile.ZipFile(zip_file) 72 | 73 | def get_file_list(self): 74 | return self._z.namelist() 75 | 76 | def get_zip_root(self): 77 | for i in self.get_file_list(): 78 | p = os.path.split(i) 79 | if p[0] == '': 80 | return p[1] 81 | elif p[1] == '': 82 | return p[0] 83 | return None 84 | 85 | def find_meta(self): 86 | l = self.get_file_list() 87 | if META_FILE in l: 88 | return META_FILE 89 | # find meta in root 90 | root = self.get_zip_root() 91 | if root == None: 92 | return None 93 | for i in l: 94 | p = os.path.split(i) 95 | if (p[0] == root) and (p[1] == META_FILE): 96 | return i 97 | return None 98 | 99 | def extract_meta(self): 100 | meta_file = self.find_meta() 101 | if meta_file == None: 102 | return None 103 | else: 104 | with self._z.open(meta_file, 'r') as f: 105 | return f.read() 106 | 107 | def extract(self, pp_dir): 108 | tmp_dir = pp_dir + '.tmp' 109 | self._z.extractall(tmp_dir) 110 | # move files to pp_dir 111 | root = self.get_zip_root() 112 | if root == None: 113 | os.rename(tmp_dir, pp_dir) 114 | else: 115 | os.rename(os.path.join(tmp_dir, root), pp_dir) 116 | # remove tmp_dir 117 | shutil.rmtree(tmp_dir) 118 | 119 | def _to(pp_dir, zip_file): 120 | # DEBUG 121 | print('D: open ' + zip_file) 122 | try: 123 | p = Ppp(zip_file) 124 | except Exception: 125 | print('ERROR: can not open zip file ' + zip_file) 126 | raise 127 | # find meta file 128 | meta_file = p.find_meta() 129 | if meta_file == None: 130 | print('ERROR: can not find meta file `' + META_FILE + '` !') 131 | return 1 132 | # check meta file 133 | meta_text = p.extract_meta().decode('utf-8') 134 | print('D: check meta file ' + meta_file) 135 | if _check_meta(meta_text): 136 | print('ERROR: bad meta file !') 137 | return 1 138 | # do extract 139 | print('D: extract files to ' + pp_dir) 140 | p.extract(pp_dir) 141 | 142 | return 0 # done 143 | 144 | def _check(pp_dir): 145 | meta_file = os.path.join(pp_dir, META_FILE) 146 | try: 147 | with open(meta_file, 'rb') as f: 148 | meta_text = f.read().decode('utf-8') 149 | except Exception: 150 | print('ERROR: can not open meta file ' + meta_file) 151 | raise 152 | # check meta file 153 | if _check_meta(meta_text): 154 | print('ERROR: bad meta file !') 155 | return 1 156 | return 0 # done 157 | 158 | 159 | def _p_arg(args): 160 | _a = { 161 | 'rest': args 162 | } 163 | o = {} 164 | def _next(): 165 | o = _a['rest'][0] 166 | _a['rest'] = _a['rest'][1:] 167 | return o 168 | while len(_a['rest']) > 0: 169 | one = _next() 170 | if one == '--to': 171 | o['type'] = 'to' 172 | o['pp_dir'] = _next() 173 | elif one == '--check': 174 | o['type'] = 'check' 175 | o['pp_dir'] = _next() 176 | else: # default: ZIP_FILE 177 | o['zip_file'] = one 178 | return o 179 | 180 | def main(args): 181 | a = _p_arg(args) 182 | if not 'type' in a: 183 | print('ERROR: bad command line. ') 184 | return 1 185 | if a['type'] == 'to': 186 | return _to(a['pp_dir'], a['zip_file']) 187 | else: 188 | return _check(a['pp_dir']) 189 | 190 | if __name__ == '__main__': 191 | exit(main(sys.argv[1:])) 192 | 193 | # end ppp_unpacker.py 194 | --------------------------------------------------------------------------------