15 Commits
1.1.0 ... 1.1.3

Author SHA1 Message Date
Roel van Uden
7145f72635 Bump the version 2015-03-07 13:31:30 +01:00
Roel van Uden
4f613ad45c Quick developer updates to develop with atom 2015-03-07 13:31:15 +01:00
Roel van Uden
1afcef88a0 #6: Support subtitles without multiple styles. 2015-03-07 13:30:02 +01:00
Roel van Uden
1288d0b3f8 Notes for future me 2015-03-07 13:29:36 +01:00
Roel van Uden
2bb5feb647 Generate source maps with tsconfig 2015-03-07 13:29:29 +01:00
Roel van Uden
602f772fcf Get rid of Visual Studio project files for tsconfig 2015-03-07 11:52:10 +01:00
Roel van Uden
887b3ed094 Bump the version 2015-03-06 22:22:53 +01:00
Roel van Uden
49e3290f28 Fixing the README while I'm at it 2015-03-06 22:17:45 +01:00
Roel van Uden
5d32d91d7d #5: Enable merging subtitle-less. 2015-03-06 22:12:03 +01:00
Roel van Uden
2f1858cde7 #5: Support subtitle-less videos 2015-03-06 21:58:31 +01:00
Roel van Uden
a98ed223c6 #5: Support drama videos in regex 2015-03-06 21:50:31 +01:00
Roel van Uden
575569bd91 #4: Support spaces in Windows path 2015-03-06 21:41:02 +01:00
Roel van Uden
44a66286cb Major update of README.md 2015-02-28 13:48:02 +01:00
Roel van Uden
eb7de600c1 Update typing dependencies. 2015-02-28 13:28:59 +01:00
Roel van Uden
d2e8a4c02e Update ts.js 2015-02-07 13:29:10 +01:00
25 changed files with 199 additions and 310 deletions

5
.gitignore vendored
View File

@@ -1,8 +1,3 @@
dist/ dist/
node_modules/ node_modules/
obj/
typings/ typings/
*.dat
*.dll
*.suo
*.tmp

View File

@@ -1,16 +1,8 @@
extras/ extras/
node_modules/ node_modules/
obj/
src/ src/
typings/ typings/
*.dat
*.DotSettings
*.dll
*.map
*.njsproj
*.sln
*.suo
*.tmp
ts.js ts.js
tsconfig.json
tsd.json tsd.json
tslint.json tslint.json

154
README.md
View File

@@ -1,69 +1,117 @@
# CrunchyRoll.js # CrunchyRoll.js
*CrunchyRoll.js* is capable of downloading *anime* episodes from the popular *CrunchyRoll.js* is capable of downloading *anime* episodes from the popular *CrunchyRoll* streaming service. An episode is stored in the original video format (often H.264 in a MP4 container) and the configured subtitle format (ASS or SRT).The two output files are then merged into a single MKV file.
*CrunchyRoll* streaming service. An episode is stored in the original video format
(often H.264 in a MP4 container) and the configured subtitle format (ASS or
SRT).The two output files are then merged into a single MKV file.
## Motivation ## Motivation
*CrunchyRoll* has been providing an amazing streaming service and offers the *CrunchyRoll* has been providing an amazing streaming service and offers the best way to enjoy *anime* in a *convenient* and *legal* way. As a streaming service, video files cannot be downloaded and watched offline. Understandable from a business perspective and considering possible contract implications, but annoying for users. This application enables episodes to be downloaded for offline convenience. Please do not abuse this application; download episodes for **personal use** and **delete them** if you do not have an active premium account. Continue to support *CrunchyRoll*; without our financial backing their service cannot exist!
best way to enjoy *anime* in a *convenient* and *legal* way. As a streaming
service, video files cannot be downloaded and watched offline. Understandable
from a business perspective and considering possible contract implications, but
annoying for users. This application enables episodes to be downloaded for
offline convenience. Please do not abuse this application; download episodes for
**personal use** and **delete them** if you do not have an active premium
account. Continue to support *CrunchyRoll*; without our financial backing their
service cannot exist!
## Legal Warning ## Legal Warning
This application is not endorsed or affliated with *CrunchyRoll*. The usage of This application is not endorsed or affliated with *CrunchyRoll*. The usage of this application enables episodes to be downloaded for offline convenience which may be forbidden by law in your country. Usage of this application may also cause a violation of the agreed *Terms of Service* between you and the stream provider. A tool is not responsible for your actions; please make an informed decision prior to using this application.
this application enables episodes to be downloaded for offline convenience which
may be forbidden by law in your country. Usage of this application may also
cause a violation of the agreed *Terms of Service* between you and the stream
provider. A tool is not responsible for your actions; please make an informed
decision prior to using this application.
## Status
### Implemented
* Subtitle decoding.
* Subtitle converter for SRT subtitle output.
* Video streaming.
* Episode page scraping with subtitle saving and video streaming.
* Add ASS support.
* Add muxing (MP4+ASS=MKV).
* Add series API to save an entire series rather than per-episode.
* Add support for incremental saves.
* Add batch-mode to queue a bunch of series.
* Add CLI interface with all the options.
* Support scheduled merging; if it fails now, the video is probably being watched.
* Add authentication to the entire stack to support premium content.
* Binary runner for `npm`
* Windows examples with a .bat for ease of use.
* Publish to `npm` with a fixed package.json.
* Conversion to beautiful TypeScript 1.4 code.
### Pending Implementation
* Documentation.
* Enjoy beautiful anime series from disk when internet is down.
## Configuration ## Configuration
Set defaults in https://www.crunchyroll.com/acct/?action=video. We'll use that. It is recommended to enable authentication (`-p` and `-u`) so your account permissions and settings are available for use. It is not possible to download non-free material without an account and premium subscription. Furthermore, the default account settings are used when downloading. If you want the highest quality videos, configure these preferences at https://www.crunchyroll.com/acct/?action=video.
## Prerequisites
* NodeJS >= 0.12.x (http://nodejs.org/)
* NPM >= 2.5.x (https://www.npmjs.org/)
## Installation
Use the applicable instructions to install. Is your operating system not listed? Please ask or contribute!
### Debian (Mint, Ubuntu, etc)
1. Run in *Terminal*: `sudo apt-get install nodejs npm mkvtoolnix rtmpdump`
2. Run in *Terminal*: `sudo ln -s /usr/bin/nodejs /usr/bin/node`
3. Run in *Terminal*: `sudo npm install -g crunchyroll`
### Mac OS X
1. Install *Homebrew* following the instructions at http://brew.sh/
2. Run in *Terminal*: `brew install node mkvtoolnix rtmpdump`
3. Run in *Terminal*: `npm install -g crunchyroll`
### Windows
1. Install *NodeJS* following the instructions at http://nodejs.org/
3. Run in *Command Prompt*: `npm install -g crunchyroll`
## Instructions
Use the applicable instructions for the interface of your choice (currently limited to command-line).
### Command-line Interface (`crunchyroll`)
The [command-line interface](http://en.wikipedia.org/wiki/Command-line_interface) does not have a graphical component and is ideal for automation purposes and headless machines. The interface can run using a sequence of series addresses (the site address containing the episode listing), or with a batch-mode source file. The `crunchyroll --help` command will produce the following output:
Usage: crunchyroll [options]
Options:
-h, --help output usage information
-V, --version output the version number
-p, --pass <s> The password.
-u, --user <s> The e-mail address or username.
-c, --cache Disables the cache.
-m, --merge Disables merging subtitles and videos.
-e, --episode <i> The episode filter.
-v, --volume <i> The volume filter.
-f, --format <s> The subtitle format. (Default: ass)
-o, --output <s> The output path.
-s, --series <s> The series override.
-t, --tag <s> The subgroup. (Default: CrunchyRoll)
#### Batch-mode
When no sequence of series addresses is provided, the batch-mode source file will be read (which is *CrunchyRoll.txt* in the current work directory. Each line in this file is processed as a seperate command-line statement. This makes it ideal to manage a large sequence of series addresses with variating command-line options or incremental episode updates.
#### Examples
Download in batch-mode:
crunchyroll
Download *Fairy Tail* to the current work directory:
crunchyroll http://www.crunchyroll.com/fairy-tail
Download *Fairy Tail* to `C:\Anime`:
crunchyroll --output C:\Anime http://www.crunchyroll.com/fairy-tail
#### Switches
##### Authentication
* `-p or --pass <s>` sets the password.
* `-u or --user <s>` sets the e-mail address or username.
##### Disables
* `-c or --cache` disables the cache.
* `-m or --merge` disables merging subtitles and videos.
##### Filters
* `-e or --episode <i>` filters episodes (positive is greater than, negative is smaller than).
* `-v or --volume <i>` filters volumes (positive is greater than, negative is smaller than).
##### Settings
* `-f or --format <s>` sets the subtitle format. (Default: ass)
* `-o or --output <s>` sets the output path.
* `-s or --series <s>` sets the series override.
* `-t or --tag <s>` sets The subgroup. (Default: CrunchyRoll)
## Developers ## Developers
* Visual Studio 2013 Update 4 (Core) More information will be added at a later point. For now the recommendations are:
* NodeJS Tools (Debugging)
* TypeScript 1.4 (Language)
* ReSharper 9.0+ (Hints/Formatting)
* Web Essentials (TSLint)
## Work In Progress * Atom with `atom-typescript` and `linter-tslint` (and dependencies).
Open an issue or e-mail me directly. I'd be happy to answer your questions. Since this project uses TypeScript, compile with `node ts` or `npm install`.

View File

@@ -1,102 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<EnableTypeScript>True</EnableTypeScript>
<OutputPath>.</OutputPath>
<ProjectGuid>{c5cff68a-d733-4347-83e7-6e5fe58eb0e3}</ProjectGuid>
<ProjectHome />
<ProjectTypeGuids>{3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}</ProjectTypeGuids>
<ProjectView>ShowAllFiles</ProjectView>
<SchemaVersion>2.0</SchemaVersion>
<StartupFile>cli.ts</StartupFile>
<TypeScriptModuleKind>CommonJS</TypeScriptModuleKind>
<TypeScriptNoImplicitAny>True</TypeScriptNoImplicitAny>
<TypeScriptOutDir>dist</TypeScriptOutDir>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<WorkingDirectory>.</WorkingDirectory>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<TypeScriptSourceMap>True</TypeScriptSourceMap>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<TypeScriptGeneratesDeclarations>True</TypeScriptGeneratesDeclarations>
</PropertyGroup>
<ItemGroup>
<TypeScriptCompile Include="src\cli.ts" />
<TypeScriptCompile Include="src\batch.ts" />
<TypeScriptCompile Include="src\episode.ts" />
<TypeScriptCompile Include="src\index.ts" />
<TypeScriptCompile Include="src\request.ts" />
<TypeScriptCompile Include="src\series.ts" />
<TypeScriptCompile Include="src\subtitle\decode.ts" />
<TypeScriptCompile Include="src\subtitle\formats\ass.ts" />
<TypeScriptCompile Include="src\subtitle\formats\index.ts" />
<TypeScriptCompile Include="src\subtitle\formats\srt.ts" />
<TypeScriptCompile Include="src\subtitle\index.ts" />
<TypeScriptCompile Include="src\typings.ts" />
<TypeScriptCompile Include="src\video\index.ts" />
<TypeScriptCompile Include="src\video\merge.ts" />
<TypeScriptCompile Include="src\video\stream.ts" />
<TypeScriptCompile Include="typings\big-integer\big-integer.d.ts" />
<TypeScriptCompile Include="typings\cheerio\cheerio.d.ts" />
<TypeScriptCompile Include="typings\commander\commander.d.ts" />
<TypeScriptCompile Include="typings\form-data\form-data.d.ts" />
<TypeScriptCompile Include="typings\mkdirp\mkdirp.d.ts" />
<TypeScriptCompile Include="typings\node\node.d.ts" />
<TypeScriptCompile Include="typings\request\request.d.ts" />
<TypeScriptCompile Include="typings\xml2js\xml2js.d.ts" />
</ItemGroup>
<ItemGroup>
<Folder Include="src\" />
<Folder Include="src\subtitle\" />
<Folder Include="src\subtitle\formats\" />
<Folder Include="src\video\" />
<Folder Include="typings" />
<Folder Include="typings\big-integer\" />
<Folder Include="typings\cheerio\" />
<Folder Include="typings\commander\" />
<Folder Include="typings\form-data\" />
<Folder Include="typings\mkdirp\" />
<Folder Include="typings\node" />
<Folder Include="typings\request\" />
<Folder Include="typings\xml2js\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" Condition="False" />
<Import Project="$(VSToolsPath)\Node.js Tools\Microsoft.NodejsTools.targets" />
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<AutoAssignPort>True</AutoAssignPort>
<CustomServerUrl>http://localhost:1337</CustomServerUrl>
<DevelopmentServerPort>0</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:48022/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
<UseCustomServer>True</UseCustomServer>
<UseIIS>False</UseIIS>
</WebProjectProperties>
</FlavorProperties>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}" User="">
<WebProjectProperties>
<AlwaysStartWebServerOnDebug>False</AlwaysStartWebServerOnDebug>
<AspNetDebugging>True</AspNetDebugging>
<EnableENC>False</EnableENC>
<ExternalProgram />
<NativeDebugging>False</NativeDebugging>
<SilverlightDebugging>False</SilverlightDebugging>
<SQLDebugging>False</SQLDebugging>
<StartAction>CurrentPage</StartAction>
<StartCmdLineArguments />
<StartExternalURL />
<StartPageUrl />
<StartWorkingDirectory />
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@@ -1,22 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "crunchyroll.js", "crunchyroll.js.njsproj", "{C5CFF68A-D733-4347-83E7-6E5FE58EB0E3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C5CFF68A-D733-4347-83E7-6E5FE58EB0E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5CFF68A-D733-4347-83E7-6E5FE58EB0E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5CFF68A-D733-4347-83E7-6E5FE58EB0E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5CFF68A-D733-4347-83E7-6E5FE58EB0E3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -1,65 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeEditing/Intellisense/CodeCompletion/IntellisenseGloballyEnabled/IntellisenseEnabled/@EntryValue">Disabled</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=DeclarationHides/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InconsistentNaming/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=NotAllPathsReturnValue/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantQualifier/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SpecifyVariableTypeExplicitly/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/TypeScriptInspections/Level/@EntryValue">TypeScript14</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=TypeScript/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="TypeScript"&gt;&lt;FormatAttributeQuoteDescriptor&gt;True&lt;/FormatAttributeQuoteDescriptor&gt;&lt;JsReformatCode&gt;True&lt;/JsReformatCode&gt;&lt;JsFormatDocComments&gt;True&lt;/JsFormatDocComments&gt;&lt;JsInsertSemicolon&gt;True&lt;/JsInsertSemicolon&gt;&lt;RemoveRedundantQualifiersTs&gt;True&lt;/RemoveRedundantQualifiersTs&gt;&lt;OptimizeImportsTs&gt;True&lt;/OptimizeImportsTs&gt;&lt;OptimizeReferenceCommentsTs&gt;True&lt;/OptimizeReferenceCommentsTs&gt;&lt;PublicModifierStyleTs&gt;True&lt;/PublicModifierStyleTs&gt;&lt;RelativePathStyleTs&gt;True&lt;/RelativePathStyleTs&gt;&lt;TypeAnnotationStyleTs&gt;True&lt;/TypeAnnotationStyleTs&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue">TypeScript</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/JavaScriptCodeFormatting/FORCE_CONTROL_STATEMENTS_BRACES/@EntryValue">ONLY_FOR_MULTILINE</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/JavaScriptCodeFormatting/KEEP_BLANK_LINES_BETWEEN_DECLARATIONS/@EntryValue">1</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/JavaScriptCodeFormatting/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/JavaScriptCodeFormatting/QUOTE_STYLE/@EntryValue">SingleQuoted</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/JavaScriptCodeFormatting/SPACE_WITHIN_OBJECT_LITERAL_BRACES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/JavaScriptCodeFormatting/STICK_COMMENT/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FCONSTRUCTOR/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FGLOBAL_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FLABEL/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FLOCAL_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FOBJECT_005FPROPERTY_005FOF_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FPARAMETER/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FCLASS/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FENUM/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FENUM_005FMEMBER/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FINTERFACE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FINTERFACE_005FFOR_005FJS_005FGLOBAL_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FMODULE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FMODULE_005FEXPORTED/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FMODULE_005FLOCAL/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPRIVATE_005FMEMBER_005FACCESSOR/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPRIVATE_005FSTATIC_005FTYPE_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPRIVATE_005FTYPE_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPRIVATE_005FTYPE_005FMETHOD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPROTECTED_005FMEMBER_005FACCESSOR/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPROTECTED_005FSTATIC_005FTYPE_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPROTECTED_005FTYPE_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPROTECTED_005FTYPE_005FMETHOD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPUBLIC_005FMEMBER_005FACCESSOR/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPUBLIC_005FSTATIC_005FTYPE_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPUBLIC_005FTYPE_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FPUBLIC_005FTYPE_005FMETHOD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=TS_005FTYPE_005FPARAMETER/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/WebNaming/UserRules/=ASP_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/WebNaming/UserRules/=ASP_005FHTML_005FCONTROL/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/WebNaming/UserRules/=ASP_005FTAG_005FNAME/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/WebNaming/UserRules/=ASP_005FTAG_005FPREFIX/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=NAMESPACE_005FALIAS/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FFIELD/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/CodeStyle/TypeScriptCodeStyle/ExplicitPublicModifier/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/TypeScriptCodeStyle/FileReferenceStyle/@EntryValue">RelativeDotSlash</s:String>
<s:Boolean x:Key="/Default/CodeStyle/TypeScriptCodeStyle/NoImplicitAny/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/TypeScriptCodeStyle/PreferUsingAliases/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=AAA631615CEE9646AA8766F222F9457C/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=AAA631615CEE9646AA8766F222F9457C/AbsolutePath/@EntryValue">C:\Dropbox\Github\crunchyroll.js\crunchyroll.js.sln.DotSettings</s:String>
<s:Boolean x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=FileAAA631615CEE9646AA8766F222F9457C/@KeyIndexDefined">True</s:Boolean>
<s:Double x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=FileAAA631615CEE9646AA8766F222F9457C/RelativePriority/@EntryValue">1</s:Double></wpf:ResourceDictionary>

View File

@@ -11,26 +11,26 @@
"type": "git", "type": "git",
"url": "git://github.com/Deathspike/crunchyroll.js.git" "url": "git://github.com/Deathspike/crunchyroll.js.git"
}, },
"version": "1.1.0", "version": "1.1.3",
"bin": { "bin": {
"crunchyroll": "./bin/crunchyroll" "crunchyroll": "./bin/crunchyroll"
}, },
"dependencies": { "dependencies": {
"big-integer": "^1.4.3", "big-integer": "^1.4.4",
"cheerio": "^0.18.0", "cheerio": "^0.18.0",
"commander": "^2.6.0", "commander": "^2.6.0",
"mkdirp": "^0.5.0", "mkdirp": "^0.5.0",
"request": "^2.53.0", "request": "^2.53.0",
"xml2js": "^0.4.4" "xml2js": "^0.4.5"
}, },
"devDependencies": { "devDependencies": {
"tsd": "^0.5.7", "tsd": "^0.5.7",
"tslint": "^2.1.0", "tslint": "^2.1.1",
"typescript": "^1.4.1" "typescript": "^1.4.1"
}, },
"scripts": { "scripts": {
"prepublish": "npm run tsd && node ts", "prepublish": "npm run tsd && node ts",
"test": "node ts --only-test", "test": "node ts --only-test",
"tsd": "./node_modules/.bin/tsd reinstall --overwrite" "tsd": "tsd reinstall --overwrite"
} }
} }

View File

@@ -51,7 +51,8 @@ function download(config: typings.IConfig, page: typings.IEpisodePage, player: t
downloadVideo(config, page, player, filePath, err => { downloadVideo(config, page, player, filePath, err => {
if (err) return done(err); if (err) return done(err);
if (config.merge) return complete('Finished ' + fileName, now, done); if (config.merge) return complete('Finished ' + fileName, now, done);
video.merge(config, player.video.file, filePath, err => { var isSubtited = Boolean(player.subtitle);
video.merge(config, isSubtited, player.video.file, filePath, err => {
if (err) return done(err); if (err) return done(err);
complete('Finished ' + fileName, now, done); complete('Finished ' + fileName, now, done);
}); });
@@ -63,8 +64,9 @@ function download(config: typings.IConfig, page: typings.IEpisodePage, player: t
/** /**
* Saves the subtitles to disk. * Saves the subtitles to disk.
*/ */
function downloadSubtitle(config: typings.IConfig, player: typings.IEpisodePlayer, filePath: string, done: (err: Error) => void) { function downloadSubtitle(config: typings.IConfig, player: typings.IEpisodePlayer, filePath: string, done: (err?: Error) => void) {
var enc = player.subtitle; var enc = player.subtitle;
if (!enc) return done();
subtitle.decode(enc.id, enc.iv, enc.data, (err, data) => { subtitle.decode(enc.id, enc.iv, enc.data, (err, data) => {
if (err) return done(err); if (err) return done(err);
var formats = subtitle.formats; var formats = subtitle.formats;
@@ -121,7 +123,7 @@ function scrapePage(config: typings.IConfig, address: string, done: (err: Error,
if (err) return done(err); if (err) return done(err);
var $ = cheerio.load(result); var $ = cheerio.load(result);
var swf = /^([^?]+)/.exec($('link[rel=video_src]').attr('href')); var swf = /^([^?]+)/.exec($('link[rel=video_src]').attr('href'));
var regexp = /Watch\s+(.+?)(?:\s+Season\s+([0-9]+))?\s+Episode\s+([0-9]+)/; var regexp = /-\s+(?:Watch\s+)?(.+?)(?:\s+Season\s+([0-9]+))?(?:\s+-)?\s+Episode\s+([0-9]+)/;
var data = regexp.exec($('title').text()); var data = regexp.exec($('title').text());
if (!swf || !data) return done(new Error('Invalid page.')); if (!swf || !data) return done(new Error('Invalid page.'));
done(null, { done(null, {
@@ -151,12 +153,13 @@ function scrapePlayer(config: typings.IConfig, address: string, id: number, done
}, (err: Error, player: typings.IEpisodePlayerConfig) => { }, (err: Error, player: typings.IEpisodePlayerConfig) => {
if (err) return done(err); if (err) return done(err);
try { try {
var isSubtitled = Boolean(player['default:preload'].subtitle);
done(null, { done(null, {
subtitle: { subtitle: isSubtitled ? {
id: parseInt(player['default:preload'].subtitle.$.id, 10), id: parseInt(player['default:preload'].subtitle.$.id, 10),
iv: player['default:preload'].subtitle.iv, iv: player['default:preload'].subtitle.iv,
data: player['default:preload'].subtitle.data data: player['default:preload'].subtitle.data
}, } : null,
video: { video: {
file: player['default:preload'].stream_info.file, file: player['default:preload'].stream_info.file,
host: player['default:preload'].stream_info.host host: player['default:preload'].stream_info.host

View File

@@ -29,7 +29,7 @@ function event(block: typings.ISubtitleEvent): string {
var format = 'Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text'; var format = 'Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text';
return '[Events]\n' + return '[Events]\n' +
'Format: ' + format + '\n' + 'Format: ' + format + '\n' +
block.event.map(style => ('Dialogue: 0,' + [].concat(block.event).map(style => ('Dialogue: 0,' +
style.$.start + ',' + style.$.start + ',' +
style.$.end + ',' + style.$.end + ',' +
style.$.style + ',' + style.$.style + ',' +
@@ -66,7 +66,7 @@ function style(block: typings.ISubtitleStyle): string {
'MarginL,MarginR,MarginV,Encoding'; 'MarginL,MarginR,MarginV,Encoding';
return '[V4+ Styles]\n' + return '[V4+ Styles]\n' +
'Format: ' + format + '\n' + 'Format: ' + format + '\n' +
block.style.map(style => 'Style: ' + [].concat(block.style).map(style => 'Style: ' +
style.$.name + ',' + style.$.name + ',' +
style.$.font_name + ',' + style.$.font_name + ',' +
style.$.font_size + ',' + style.$.font_size + ',' +

View File

@@ -33,7 +33,7 @@ export interface IEpisodePage {
} }
export interface IEpisodePlayer { export interface IEpisodePlayer {
subtitle: { subtitle?: {
id: number; id: number;
iv: string; iv: string;
data: string; data: string;

View File

@@ -10,13 +10,13 @@ import typings = require('../typings');
/** /**
* Merges the subtitle and video files into a Matroska Multimedia Container. * Merges the subtitle and video files into a Matroska Multimedia Container.
*/ */
function main(config: typings.IConfig, rtmpInputPath: string, filePath: string, done: (err: Error) => void) { function main(config: typings.IConfig, isSubtitled: boolean, rtmpInputPath: string, filePath: string, done: (err: Error) => void) {
var subtitlePath = filePath + '.' + (subtitle.formats[config.format] ? config.format : 'ass'); var subtitlePath = filePath + '.' + (subtitle.formats[config.format] ? config.format : 'ass');
var videoPath = filePath + path.extname(rtmpInputPath); var videoPath = filePath + path.extname(rtmpInputPath);
childProcess.exec(command() + ' ' + childProcess.exec(command() + ' ' +
'-o "' + filePath + '.mkv" ' + '-o "' + filePath + '.mkv" ' +
'"' + videoPath + '" ' + '"' + videoPath + '" ' +
'"' + subtitlePath + '"', { (isSubtitled ? '"' + subtitlePath + '"' : ''), {
maxBuffer: Infinity maxBuffer: Infinity
}, err => { }, err => {
if (err) return done(err); if (err) return done(err);
@@ -32,7 +32,7 @@ function main(config: typings.IConfig, rtmpInputPath: string, filePath: string,
*/ */
function command(): string { function command(): string {
if (os.platform() !== 'win32') return 'mkvmerge'; if (os.platform() !== 'win32') return 'mkvmerge';
return path.join(__dirname, '../../bin/mkvmerge.exe'); return '"' + path.join(__dirname, '../../bin/mkvmerge.exe') + '"';
} }
/** /**

View File

@@ -22,5 +22,5 @@ function main(rtmpUrl: string, rtmpInputPath: string, swfUrl: string, filePath:
*/ */
function command(): string { function command(): string {
if (os.platform() !== 'win32') return 'rtmpdump'; if (os.platform() !== 'win32') return 'rtmpdump';
return path.join(__dirname, '../../bin/rtmpdump.exe'); return '"' + path.join(__dirname, '../../bin/rtmpdump.exe') + '"';
} }

21
ts.js
View File

@@ -4,6 +4,10 @@ var fs = require('fs');
var path = require('path'); var path = require('path');
var isTest = process.argv[2] === '--only-test'; var isTest = process.argv[2] === '--only-test';
// TODO: This file can use some cleaning up. We want to use the tsconfig.json
// and go from there, but then without source maps. That should give us a final
// build output. For now, this legacy build file will remain to do its job.
read(function(err, fileNames) { read(function(err, fileNames) {
clean(fileNames, function() { clean(fileNames, function() {
var hasLintError = false; var hasLintError = false;
@@ -48,13 +52,13 @@ function clean(filePaths, done) {
* @param {function(Error)} done * @param {function(Error)} done
*/ */
function compile(filePaths, done) { function compile(filePaths, done) {
if (isTest) return done(null); if (isTest) return done(null);
var execPath = path.join(__dirname, 'node_modules/.bin/tsc'); var execPath = path.join(__dirname, 'node_modules/.bin/tsc');
var options = '--declaration --module CommonJS --noImplicitAny --outDir dist'; var options = '--declaration --module CommonJS --noImplicitAny --outDir dist --target ES5';
childProcess.exec([execPath, options].concat(filePaths).join(' '), function(err, stdout) { childProcess.exec([execPath, options].concat(filePaths).join(' '), function(err, stdout) {
if (stdout) return done(new Error(stdout)); if (stdout) return done(new Error(stdout));
done(null); done(null);
}); });
} }
/** /**
@@ -83,10 +87,5 @@ function lint(filePaths, handler, done) {
* @param {function(Error, Array.<string>)} done * @param {function(Error, Array.<string>)} done
*/ */
function read(done) { function read(done) {
var contents = fs.readFileSync('crunchyroll.js.njsproj', 'utf8'); done(null, JSON.parse(fs.readFileSync('tsconfig.json', 'utf8')).files);
var expression = /<TypeScriptCompile\s+Include="([\w\W]+?\.ts)" \/>/g;
var matches;
var filePaths = [];
while ((matches = expression.exec(contents))) filePaths.push(matches[1]);
done(null, filePaths);
} }

41
tsconfig.json Normal file
View File

@@ -0,0 +1,41 @@
{
"version": "1.4.1",
"compilerOptions": {
"declaration": true,
"noImplicitAny": true,
"removeComments": false,
"module": "commonjs",
"outDir": "dist",
"sourceMap": true,
"target": "es5"
},
"filesGlob": [
"src/**/*.ts",
"typings/**/*.ts"
],
"files": [
"src/batch.ts",
"src/cli.ts",
"src/episode.ts",
"src/index.ts",
"src/request.ts",
"src/series.ts",
"src/subtitle/decode.ts",
"src/subtitle/formats/ass.ts",
"src/subtitle/formats/index.ts",
"src/subtitle/formats/srt.ts",
"src/subtitle/index.ts",
"src/typings.ts",
"src/video/index.ts",
"src/video/merge.ts",
"src/video/stream.ts",
"typings/big-integer/big-integer.d.ts",
"typings/cheerio/cheerio.d.ts",
"typings/commander/commander.d.ts",
"typings/form-data/form-data.d.ts",
"typings/mkdirp/mkdirp.d.ts",
"typings/node/node.d.ts",
"typings/request/request.d.ts",
"typings/xml2js/xml2js.d.ts"
]
}

View File

@@ -6,28 +6,28 @@
"bundle": "typings/tsd.d.ts", "bundle": "typings/tsd.d.ts",
"installed": { "installed": {
"node/node.d.ts": { "node/node.d.ts": {
"commit": "42c8a3b74c05f6887ce21dd63c6234e424f9f8fe" "commit": "3882d337bb0808cde9fe4c08012508a48c135482"
}, },
"commander/commander.d.ts": { "commander/commander.d.ts": {
"commit": "42c8a3b74c05f6887ce21dd63c6234e424f9f8fe" "commit": "3882d337bb0808cde9fe4c08012508a48c135482"
}, },
"xml2js/xml2js.d.ts": { "xml2js/xml2js.d.ts": {
"commit": "42c8a3b74c05f6887ce21dd63c6234e424f9f8fe" "commit": "3882d337bb0808cde9fe4c08012508a48c135482"
}, },
"cheerio/cheerio.d.ts": { "cheerio/cheerio.d.ts": {
"commit": "42c8a3b74c05f6887ce21dd63c6234e424f9f8fe" "commit": "3882d337bb0808cde9fe4c08012508a48c135482"
}, },
"mkdirp/mkdirp.d.ts": { "mkdirp/mkdirp.d.ts": {
"commit": "42c8a3b74c05f6887ce21dd63c6234e424f9f8fe" "commit": "3882d337bb0808cde9fe4c08012508a48c135482"
}, },
"request/request.d.ts": { "request/request.d.ts": {
"commit": "42c8a3b74c05f6887ce21dd63c6234e424f9f8fe" "commit": "3882d337bb0808cde9fe4c08012508a48c135482"
}, },
"big-integer/big-integer.d.ts": { "big-integer/big-integer.d.ts": {
"commit": "42c8a3b74c05f6887ce21dd63c6234e424f9f8fe" "commit": "3882d337bb0808cde9fe4c08012508a48c135482"
}, },
"form-data/form-data.d.ts": { "form-data/form-data.d.ts": {
"commit": "42c8a3b74c05f6887ce21dd63c6234e424f9f8fe" "commit": "3882d337bb0808cde9fe4c08012508a48c135482"
} }
} }
} }