12 Commits

Author SHA1 Message Date
Godzil
62a08e14bb Add missing mode parameter to the stream object. (and correct call to it) 2016-08-13 21:24:57 +01:00
Godzil
422d0827f9 Correct file source extension for the merge pass. 2016-08-13 16:32:56 +01:00
Godzil
546a849aa5 Add ffmpeg when using HLS instead of RTMP. 2016-08-13 16:20:33 +01:00
Roel van Uden
e06ff53210 Lock down dependencies, bump version (#9) 2015-06-25 19:42:10 +02:00
Roel van Uden
18375d3d22 Update to TS1.5 2015-05-23 19:07:14 +02:00
Roel van Uden
5fdee94b38 References for future self. 2015-03-07 18:46:42 +01:00
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
37 changed files with 321 additions and 462 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

View File

@@ -26,14 +26,14 @@ Use the applicable instructions to install. Is your operating system not listed?
### Debian (Mint, Ubuntu, etc) ### Debian (Mint, Ubuntu, etc)
1. Run in *Terminal*: `sudo apt-get install nodejs npm mkvtoolnix rtmpdump` 1. Run in *Terminal*: `sudo apt-get install nodejs npm mkvtoolnix rtmpdump ffmpeg`
2. Run in *Terminal*: `sudo ln -s /usr/bin/nodejs /usr/bin/node` 2. Run in *Terminal*: `sudo ln -s /usr/bin/nodejs /usr/bin/node`
3. Run in *Terminal*: `sudo npm install -g crunchyroll` 3. Run in *Terminal*: `sudo npm install -g crunchyroll`
### Mac OS X ### Mac OS X
1. Install *Homebrew* following the instructions at http://brew.sh/ 1. Install *Homebrew* following the instructions at http://brew.sh/
2. Run in *Terminal*: `brew install node mkvtoolnix rtmpdump` 2. Run in *Terminal*: `brew install node mkvtoolnix rtmpdump ffmpeg`
3. Run in *Terminal*: `npm install -g crunchyroll` 3. Run in *Terminal*: `npm install -g crunchyroll`
### Windows ### Windows
@@ -112,10 +112,6 @@ Download *Fairy Tail* to `C:\Anime`:
More information will be added at a later point. For now the recommendations are: More information will be added at a later point. For now the recommendations are:
* Visual Studio 2013 Update 4 (Core) * Atom with `atom-typescript` and `linter-tslint` (and dependencies).
* NodeJS Tools (Debugging)
* TypeScript 1.4 (Language)
* ReSharper 9.0+ (Hints/Formatting)
* Web Essentials (TSLint)
Since this project uses TypeScript, compile with `node ts` or `npm install`. Since this project uses TypeScript, compile with `node ts` or `npm install`.

BIN
bin/ffmpeg.exe Executable file

Binary file not shown.

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.2", "version": "1.1.5",
"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.3.0-beta",
"typescript": "^1.4.1" "typescript": "1.5.0-beta"
}, },
"scripts": { "scripts": {
"prepublish": "npm run tsd && node ts", "prepublish": "npm run tsd && tsc",
"test": "node ts --only-test", "test": "node ts --only-test",
"tsd": "./node_modules/.bin/tsd reinstall --overwrite" "tsd": "tsd reinstall -o -s"
} }
} }

View File

@@ -1,15 +1,13 @@
'use strict'; 'use strict';
export = main;
import commander = require('commander'); import commander = require('commander');
import fs = require('fs'); import fs = require('fs');
import path = require('path'); import path = require('path');
import series = require('./series'); import series from './series';
import typings = require('./typings');
/** /**
* Streams the batch of series to disk. * Streams the batch of series to disk.
*/ */
function main(args: string[], done: (err?: Error) => void) { export default function(args: string[], done: (err?: Error) => void) {
var config = parse(args); var config = parse(args);
var batchPath = path.join(config.output || process.cwd(), 'CrunchyRoll.txt'); var batchPath = path.join(config.output || process.cwd(), 'CrunchyRoll.txt');
tasks(config, batchPath, (err, tasks) => { tasks(config, batchPath, (err, tasks) => {
@@ -41,14 +39,15 @@ function split(value: string): string[] {
previous = i + 1; previous = i + 1;
} }
} }
pieces.push(value.substring(previous, i).match(/^"?(.+?)"?$/)[1]); var lastPiece = value.substring(previous, i).match(/^"?(.+?)"?$/);
if (lastPiece) pieces.push(lastPiece[1]);
return pieces; return pieces;
} }
/** /**
* Parses the configuration or reads the batch-mode file for tasks. * Parses the configuration or reads the batch-mode file for tasks.
*/ */
function tasks(config: typings.IConfigLine, batchPath: string, done: (err: Error, tasks?: typings.IConfigTask[]) => void) { function tasks(config: IConfigLine, batchPath: string, done: (err: Error, tasks?: IConfigTask[]) => void) {
if (config.args.length) { if (config.args.length) {
return done(null, config.args.map(address => { return done(null, config.args.map(address => {
return {address: address, config: config}; return {address: address, config: config};
@@ -58,7 +57,7 @@ function tasks(config: typings.IConfigLine, batchPath: string, done: (err: Error
if (!exists) return done(null, []); if (!exists) return done(null, []);
fs.readFile(batchPath, 'utf8', (err, data) => { fs.readFile(batchPath, 'utf8', (err, data) => {
if (err) return done(err); if (err) return done(err);
var map: typings.IConfigTask[] = []; var map: IConfigTask[] = [];
data.split(/\r?\n/).forEach(line => { data.split(/\r?\n/).forEach(line => {
if (/^(\/\/|#)/.test(line)) return; if (/^(\/\/|#)/.test(line)) return;
var lineConfig = parse(process.argv.concat(split(line))); var lineConfig = parse(process.argv.concat(split(line)));
@@ -75,7 +74,7 @@ function tasks(config: typings.IConfigLine, batchPath: string, done: (err: Error
/** /**
* Parses the arguments and returns a configuration. * Parses the arguments and returns a configuration.
*/ */
function parse(args: string[]): typings.IConfigLine { function parse(args: string[]): IConfigLine {
return new commander.Command().version(require('../package').version) return new commander.Command().version(require('../package').version)
// Authentication // Authentication
.option('-p, --pass <s>', 'The password.') .option('-p, --pass <s>', 'The password.')
@@ -92,4 +91,4 @@ function parse(args: string[]): typings.IConfigLine {
.option('-s, --series <s>', 'The series override.') .option('-s, --series <s>', 'The series override.')
.option('-t, --tag <s>', 'The subgroup. (Default: CrunchyRoll)') .option('-t, --tag <s>', 'The subgroup. (Default: CrunchyRoll)')
.parse(args); .parse(args);
} }

View File

@@ -1,6 +1,6 @@
'use strict'; 'use strict';
import batch = require('./batch'); import batch from './batch';
batch(process.argv, (err: any) => { batch(process.argv, (err: any) => {
if (err) console.error(err.stack || err); if (err) console.error(err.stack || err);
}); });

View File

@@ -1,19 +1,17 @@
'use strict'; 'use strict';
export = main;
import cheerio = require('cheerio'); import cheerio = require('cheerio');
import fs = require('fs'); import fs = require('fs');
import mkdirp = require('mkdirp'); import mkdirp = require('mkdirp');
import request = require('./request'); import request = require('./request');
import path = require('path'); import path = require('path');
import subtitle = require('./subtitle/index'); import subtitle from './subtitle/index';
import typings = require('./typings'); import video from './video/index';
import video = require('./video/index');
import xml2js = require('xml2js'); import xml2js = require('xml2js');
/** /**
* Streams the episode to disk. * Streams the episode to disk.
*/ */
function main(config: typings.IConfig, address: string, done: (err: Error) => void) { export default function(config: IConfig, address: string, done: (err: Error) => void) {
scrapePage(config, address, (err, page) => { scrapePage(config, address, (err, page) => {
if (err) return done(err); if (err) return done(err);
scrapePlayer(config, address, page.id, (err, player) => { scrapePlayer(config, address, page.id, (err, player) => {
@@ -38,7 +36,7 @@ function complete(message: string, begin: number, done: (err: Error) => void) {
/** /**
* Downloads the subtitle and video. * Downloads the subtitle and video.
*/ */
function download(config: typings.IConfig, page: typings.IEpisodePage, player: typings.IEpisodePlayer, done: (err: Error) => void) { function download(config: IConfig, page: IEpisodePage, player: IEpisodePlayer, done: (err: Error) => void) {
var series = config.series || page.series; var series = config.series || page.series;
var fileName = name(config, page, series); var fileName = name(config, page, series);
var filePath = path.join(config.output || process.cwd(), series, fileName); var filePath = path.join(config.output || process.cwd(), series, fileName);
@@ -52,7 +50,7 @@ function download(config: typings.IConfig, page: typings.IEpisodePage, player: t
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);
var isSubtited = Boolean(player.subtitle); var isSubtited = Boolean(player.subtitle);
video.merge(config, isSubtited, player.video.file, filePath, err => { video.merge(config, isSubtited, player.video.file, filePath, player.mode, err => {
if (err) return done(err); if (err) return done(err);
complete('Finished ' + fileName, now, done); complete('Finished ' + fileName, now, done);
}); });
@@ -64,7 +62,7 @@ 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: IConfig, player: IEpisodePlayer, filePath: string, done: (err?: Error) => void) {
var enc = player.subtitle; var enc = player.subtitle;
if (!enc) return done(); if (!enc) return done();
subtitle.decode(enc.id, enc.iv, enc.data, (err, data) => { subtitle.decode(enc.id, enc.iv, enc.data, (err, data) => {
@@ -81,23 +79,24 @@ function downloadSubtitle(config: typings.IConfig, player: typings.IEpisodePlaye
/** /**
* Streams the video to disk. * Streams the video to disk.
*/ */
function downloadVideo(config: typings.IConfig, function downloadVideo(config: IConfig,
page: typings.IEpisodePage, page: IEpisodePage,
player: typings.IEpisodePlayer, player: IEpisodePlayer,
filePath: string, filePath: string,
done: (err: Error) => void) { done: (err: Error) => void) {
video.stream( video.stream(
player.video.host, player.video.host,
player.video.file, player.video.file,
page.swf, page.swf,
filePath + path.extname(player.video.file), filePath, path.extname(player.video.file),
player.video.mode,
done); done);
} }
/** /**
* Names the file based on the config, page, series and tag. * Names the file based on the config, page, series and tag.
*/ */
function name(config: typings.IConfig, page: typings.IEpisodePage, series: string) { function name(config: IConfig, page: IEpisodePage, series: string) {
var episode = (page.episode < 10 ? '0' : '') + page.episode; var episode = (page.episode < 10 ? '0' : '') + page.episode;
var volume = (page.volume < 10 ? '0' : '') + page.volume; var volume = (page.volume < 10 ? '0' : '') + page.volume;
var tag = config.tag || 'CrunchyRoll'; var tag = config.tag || 'CrunchyRoll';
@@ -116,7 +115,7 @@ function prefix(value: number|string, length: number) {
/** /**
* Requests the page data and scrapes the id, episode, series and swf. * Requests the page data and scrapes the id, episode, series and swf.
*/ */
function scrapePage(config: typings.IConfig, address: string, done: (err: Error, page?: typings.IEpisodePage) => void) { function scrapePage(config: IConfig, address: string, done: (err: Error, page?: IEpisodePage) => void) {
var id = parseInt((address.match(/[0-9]+$/) || ['0'])[0], 10); var id = parseInt((address.match(/[0-9]+$/) || ['0'])[0], 10);
if (!id) return done(new Error('Invalid address.')); if (!id) return done(new Error('Invalid address.'));
request.get(config, address, (err, result) => { request.get(config, address, (err, result) => {
@@ -139,7 +138,7 @@ function scrapePage(config: typings.IConfig, address: string, done: (err: Error,
/** /**
* Requests the player data and scrapes the subtitle and video data. * Requests the player data and scrapes the subtitle and video data.
*/ */
function scrapePlayer(config: typings.IConfig, address: string, id: number, done: (err: Error, player?: typings.IEpisodePlayer) => void) { function scrapePlayer(config: IConfig, address: string, id: number, done: (err: Error, player?: IEpisodePlayer) => void) {
var url = address.match(/^(https?:\/\/[^\/]+)/); var url = address.match(/^(https?:\/\/[^\/]+)/);
if (!url) return done(new Error('Invalid address.')); if (!url) return done(new Error('Invalid address.'));
request.post(config, { request.post(config, {
@@ -150,10 +149,15 @@ function scrapePlayer(config: typings.IConfig, address: string, id: number, done
xml2js.parseString(result, { xml2js.parseString(result, {
explicitArray: false, explicitArray: false,
explicitRoot: false explicitRoot: false
}, (err: Error, player: typings.IEpisodePlayerConfig) => { }, (err: Error, player: IEpisodePlayerConfig) => {
if (err) return done(err); if (err) return done(err);
try { try {
var isSubtitled = Boolean(player['default:preload'].subtitle); var isSubtitled = Boolean(player['default:preload'].subtitle);
var streamMode="RTMP";
if (player['default:preload'].stream_info.host == "")
{
streamMode="HLS";
}
done(null, { done(null, {
subtitle: isSubtitled ? { subtitle: isSubtitled ? {
id: parseInt(player['default:preload'].subtitle.$.id, 10), id: parseInt(player['default:preload'].subtitle.$.id, 10),
@@ -161,6 +165,7 @@ function scrapePlayer(config: typings.IConfig, address: string, id: number, done
data: player['default:preload'].subtitle.data data: player['default:preload'].subtitle.data
} : null, } : null,
video: { video: {
mode: streamMode;
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
} }
@@ -170,4 +175,4 @@ function scrapePlayer(config: typings.IConfig, address: string, id: number, done
} }
}); });
}); });
} }

View File

@@ -1,4 +1,5 @@
'use strict'; 'use strict';
export import batch = require('./batch'); import batch from './batch';
export import episode = require('./episode'); import episode from './episode';
export import series = require('./series'); import series from './series';
export {batch, episode, series};

16
src/interface/IConfig.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
interface IConfig {
// Authentication
pass?: string;
user?: string;
// Disables
cache?: boolean;
merge?: boolean;
// Filters
episode?: number;
volume?: number;
// Settings
format?: string;
output?: string;
series?: string;
tag?: string;
}

3
src/interface/IConfigLine.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
interface IConfigLine extends IConfig {
args: string[];
}

4
src/interface/IConfigTask.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
interface IConfigTask {
address: string;
config: IConfigLine;
}

7
src/interface/IEpisodePage.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
interface IEpisodePage {
id: number;
episode: number;
series: string;
volume: number;
swf: string;
}

11
src/interface/IEpisodePlayer.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
interface IEpisodePlayer {
subtitle?: {
id: number;
iv: string;
data: string;
};
video: {
file: string;
host: string;
};
}

15
src/interface/IEpisodePlayerConfig.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
interface IEpisodePlayerConfig {
'default:preload': {
subtitle: {
$: {
id: string;
};
iv: string;
data: string;
};
stream_info: {
file: string;
host: string;
};
};
}

3
src/interface/IFormatterTable.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
interface IFormatterTable {
[key: string]: (input: string|Buffer, done: (err: Error, subtitle?: string) => void) => void;
}

4
src/interface/ISeries.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
interface ISeries {
episodes: ISeriesEpisode[];
series: string;
}

5
src/interface/ISeriesEpisode.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
interface ISeriesEpisode {
address: string;
episode: number;
volume: number;
}

13
src/interface/ISubtitle.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
interface ISubtitle {
$: {
title: string;
wrap_style: string;
play_res_x: string;
play_res_y: string;
id: string;
lang_string: string;
created: string;
};
events: ISubtitleEvent;
styles: ISubtitleStyle;
}

15
src/interface/ISubtitleEvent.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
interface ISubtitleEvent {
event: {
$: {
end: string;
start: string;
style: string;
name: string;
margin_l: string;
margin_r: string;
margin_v: string;
effect: string;
text: string;
};
}[];
}

29
src/interface/ISubtitleStyle.d.ts vendored Normal file
View File

@@ -0,0 +1,29 @@
interface ISubtitleStyle {
style: {
$: {
name: string;
font_name: string;
font_size: string;
primary_colour: string;
secondary_colour: string;
outline_colour: string;
back_colour: string;
bold: string;
italic: string;
underline: string;
strikeout: string;
scale_x: string;
scale_y: string;
spacing: string;
angle: string;
border_style: string;
outline: string;
shadow: string;
alignment: string;
margin_l: string;
margin_r: string;
margin_v: string;
encoding: string;
};
}[];
}

View File

@@ -1,12 +1,11 @@
'use strict'; 'use strict';
import request = require('request'); import request = require('request');
import typings = require('./typings');
var isAuthenticated = false; var isAuthenticated = false;
/** /**
* Performs a GET request for the resource. * Performs a GET request for the resource.
*/ */
export function get(config: typings.IConfig, options: request.Options, done: (err: Error, result?: string) => void) { export function get(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void) {
authenticate(config, err => { authenticate(config, err => {
if (err) return done(err); if (err) return done(err);
request.get(modify(options), (err: Error, response: any, body: any) => { request.get(modify(options), (err: Error, response: any, body: any) => {
@@ -19,7 +18,7 @@ export function get(config: typings.IConfig, options: request.Options, done: (er
/** /**
* Performs a POST request for the resource. * Performs a POST request for the resource.
*/ */
export function post(config: typings.IConfig, options: request.Options, done: (err: Error, result?: string) => void) { export function post(config: IConfig, options: request.Options, done: (err: Error, result?: string) => void) {
authenticate(config, err => { authenticate(config, err => {
if (err) return done(err); if (err) return done(err);
request.post(modify(options), (err: Error, response: any, body: any) => { request.post(modify(options), (err: Error, response: any, body: any) => {
@@ -32,7 +31,7 @@ export function post(config: typings.IConfig, options: request.Options, done: (e
/** /**
* Authenticates using the configured pass and user. * Authenticates using the configured pass and user.
*/ */
function authenticate(config: typings.IConfig, done: (err: Error) => void) { function authenticate(config: IConfig, done: (err: Error) => void) {
if (isAuthenticated || !config.pass || !config.user) return done(null); if (isAuthenticated || !config.pass || !config.user) return done(null);
var options = { var options = {
form: { form: {
@@ -60,4 +59,4 @@ function modify(options: string|request.Options): request.Options {
return options; return options;
} }
return {jar: true, url: options.toString()}; return {jar: true, url: options.toString()};
} }

View File

@@ -1,18 +1,16 @@
'use strict'; 'use strict';
export = main;
import cheerio = require('cheerio'); import cheerio = require('cheerio');
import episode = require('./episode'); import episode from './episode';
import fs = require('fs'); import fs = require('fs');
import request = require('./request'); import request = require('./request');
import path = require('path'); import path = require('path');
import typings = require('./typings');
import url = require('url'); import url = require('url');
var persistent = '.crpersistent'; var persistent = '.crpersistent';
/** /**
* Streams the series to disk. * Streams the series to disk.
*/ */
function main(config: typings.IConfig, address: string, done: (err: Error) => void) { export default function(config: IConfig, address: string, done: (err: Error) => void) {
var persistentPath = path.join(config.output || process.cwd(), persistent); var persistentPath = path.join(config.output || process.cwd(), persistent);
fs.readFile(persistentPath, 'utf8', (err, contents) => { fs.readFile(persistentPath, 'utf8', (err, contents) => {
var cache = config.cache ? {} : JSON.parse(contents || '{}'); var cache = config.cache ? {} : JSON.parse(contents || '{}');
@@ -39,9 +37,9 @@ function main(config: typings.IConfig, address: string, done: (err: Error) => vo
* Downloads the episode. * Downloads the episode.
*/ */
function download(cache: {[address: string]: number}, function download(cache: {[address: string]: number},
config: typings.IConfig, config: IConfig,
baseAddress: string, baseAddress: string,
item: typings.ISeriesEpisode, item: ISeriesEpisode,
done: (err: Error) => void) { done: (err: Error) => void) {
if (!filter(config, item)) return done(null); if (!filter(config, item)) return done(null);
var address = url.resolve(baseAddress, item.address); var address = url.resolve(baseAddress, item.address);
@@ -56,7 +54,7 @@ function download(cache: {[address: string]: number},
/** /**
* Filters the item based on the configuration. * Filters the item based on the configuration.
*/ */
function filter(config: typings.IConfig, item: typings.ISeriesEpisode) { function filter(config: IConfig, item: ISeriesEpisode) {
// Filter on chapter. // Filter on chapter.
var episodeFilter = config.episode; var episodeFilter = config.episode;
if (episodeFilter > 0 && item.episode <= episodeFilter) return false; if (episodeFilter > 0 && item.episode <= episodeFilter) return false;
@@ -72,13 +70,13 @@ function filter(config: typings.IConfig, item: typings.ISeriesEpisode) {
/** /**
* Requests the page and scrapes the episodes and series. * Requests the page and scrapes the episodes and series.
*/ */
function page(config: typings.IConfig, address: string, done: (err: Error, result?: typings.ISeries) => void) { function page(config: IConfig, address: string, done: (err: Error, result?: ISeries) => void) {
request.get(config, address, (err, result) => { request.get(config, address, (err, result) => {
if (err) return done(err); if (err) return done(err);
var $ = cheerio.load(result); var $ = cheerio.load(result);
var title = $('span[itemprop=name]').text(); var title = $('span[itemprop=name]').text();
if (!title) return done(new Error('Invalid page.')); if (!title) return done(new Error('Invalid page.'));
var episodes: typings.ISeriesEpisode[] = []; var episodes: ISeriesEpisode[] = [];
$('.episode').each((i, el) => { $('.episode').each((i, el) => {
if ($(el).children('img[src*=coming_soon]').length) return; if ($(el).children('img[src*=coming_soon]').length) return;
var volume = /([0-9]+)\s*$/.exec($(el).closest('ul').prev('a').text()); var volume = /([0-9]+)\s*$/.exec($(el).closest('ul').prev('a').text());
@@ -94,4 +92,4 @@ function page(config: typings.IConfig, address: string, done: (err: Error, resul
}); });
done(null, {episodes: episodes.reverse(), series: title}); done(null, {episodes: episodes.reverse(), series: title});
}); });
} }

View File

@@ -1,6 +1,5 @@
/* tslint:disable:no-bitwise false */ /* tslint:disable:no-bitwise false */
'use strict'; 'use strict';
export = main;
import crypto = require('crypto'); import crypto = require('crypto');
import bigInt = require('big-integer'); import bigInt = require('big-integer');
import zlib = require('zlib'); import zlib = require('zlib');
@@ -8,7 +7,7 @@ import zlib = require('zlib');
/** /**
* Decodes the data. * Decodes the data.
*/ */
function main(id: number, iv: Buffer|string, data: Buffer|string, done: (err?: Error, result?: Buffer) => void) { export default function(id: number, iv: Buffer|string, data: Buffer|string, done: (err?: Error, result?: Buffer) => void) {
try { try {
decompress(decrypt(id, iv, data), done); decompress(decrypt(id, iv, data), done);
} catch (e) { } catch (e) {
@@ -73,4 +72,4 @@ function secret(size: number, modulo: number, firstSeed: number, secondSeed: num
previousValue = oldValue; previousValue = oldValue;
} }
return result; return result;
} }

View File

@@ -1,16 +1,14 @@
'use strict'; 'use strict';
export = main;
import xml2js = require('xml2js'); import xml2js = require('xml2js');
import typings = require('../../typings');
/** /**
* Converts an input buffer to a SubStation Alpha subtitle. * Converts an input buffer to a SubStation Alpha subtitle.
*/ */
function main(input: string|Buffer, done: (err: Error, subtitle?: string) => void) { export default function(input: string|Buffer, done: (err: Error, subtitle?: string) => void) {
xml2js.parseString(input.toString(), { xml2js.parseString(input.toString(), {
explicitArray: false, explicitArray: false,
explicitRoot: false explicitRoot: false
}, (err: Error, xml: typings.ISubtitle) => { }, (err: Error, xml: ISubtitle) => {
if (err) return done(err); if (err) return done(err);
try { try {
done(null, script(xml) + '\n' + done(null, script(xml) + '\n' +
@@ -25,11 +23,11 @@ function main(input: string|Buffer, done: (err: Error, subtitle?: string) => voi
/** /**
* Converts the event block. * Converts the event block.
*/ */
function event(block: typings.ISubtitleEvent): string { function event(block: 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 + ',' +
@@ -44,7 +42,7 @@ function event(block: typings.ISubtitleEvent): string {
/** /**
* Converts the script block. * Converts the script block.
*/ */
function script(block: typings.ISubtitle): string { function script(block: ISubtitle): string {
return '[Script Info]\n' + return '[Script Info]\n' +
'Title: ' + block.$.title + '\n' + 'Title: ' + block.$.title + '\n' +
'ScriptType: v4.00+\n' + 'ScriptType: v4.00+\n' +
@@ -59,14 +57,14 @@ function script(block: typings.ISubtitle): string {
/** /**
* Converts the style block. * Converts the style block.
*/ */
function style(block: typings.ISubtitleStyle): string { function style(block: ISubtitleStyle): string {
var format = 'Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,' + var format = 'Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,' +
'OutlineColour,BackColour,Bold,Italic,Underline,StrikeOut,ScaleX,' + 'OutlineColour,BackColour,Bold,Italic,Underline,StrikeOut,ScaleX,' +
'ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,' + 'ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,' +
'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 + ',' +
@@ -90,4 +88,4 @@ function style(block: typings.ISubtitleStyle): string {
style.$.margin_r + ',' + style.$.margin_r + ',' +
style.$.margin_v + ',' + style.$.margin_v + ',' +
style.$.encoding).join('\n') + '\n'; style.$.encoding).join('\n') + '\n';
} }

View File

@@ -1,10 +1,8 @@
'use strict'; 'use strict';
export = main; import ass from './ass';
import ass = require('./ass'); import srt from './srt';
import srt = require('./srt');
import typings = require('../../typings');
var main: typings.IFormatterTable = { export default <IFormatterTable> {
ass: ass, ass: ass,
srt: srt srt: srt
}; };

View File

@@ -1,14 +1,12 @@
'use strict'; 'use strict';
export = srt;
import xml2js = require('xml2js'); import xml2js = require('xml2js');
import typings = require('../../typings');
/** /**
* Converts an input buffer to a SubRip subtitle. * Converts an input buffer to a SubRip subtitle.
*/ */
function srt(input: Buffer|string, done: (err: Error, subtitle?: string) => void) { export default function(input: Buffer|string, done: (err: Error, subtitle?: string) => void) {
var options = {explicitArray: false, explicitRoot: false}; var options = {explicitArray: false, explicitRoot: false};
xml2js.parseString(input.toString(), options, (err: Error, xml: typings.ISubtitle) => { xml2js.parseString(input.toString(), options, (err: Error, xml: ISubtitle) => {
try { try {
if (err) return done(err); if (err) return done(err);
done(null, xml.events.event.map((event, index) => { done(null, xml.events.event.map((event, index) => {
@@ -63,4 +61,4 @@ function time(value: string): string {
var seconds = prefix(all[3], 2); var seconds = prefix(all[3], 2);
var milliseconds = suffix(all[4], 3); var milliseconds = suffix(all[4], 3);
return hours + ':' + minutes + ':' + seconds + ',' + milliseconds; return hours + ':' + minutes + ':' + seconds + ',' + milliseconds;
} }

View File

@@ -1,3 +1,4 @@
'use strict'; 'use strict';
export import decode = require('./decode'); import decode from './decode';
export import formats = require('./formats/index'); import formats from './formats/index';
export default {decode, formats};

View File

@@ -1,136 +0,0 @@
export interface IConfig {
// Authentication
pass?: string;
user?: string;
// Disables
cache?: boolean;
merge?: boolean;
// Filters
episode?: number;
volume?: number;
// Settings
format?: string;
output?: string;
series?: string;
tag?: string;
}
export interface IConfigLine extends IConfig {
args: string[];
}
export interface IConfigTask {
address: string;
config: IConfigLine;
}
export interface IEpisodePage {
id: number;
episode: number;
series: string;
volume: number;
swf: string;
}
export interface IEpisodePlayer {
subtitle?: {
id: number;
iv: string;
data: string;
};
video: {
file: string;
host: string;
};
}
export interface IEpisodePlayerConfig {
'default:preload': {
subtitle: {
$: {
id: string;
};
iv: string;
data: string;
};
stream_info: {
file: string;
host: string;
};
};
}
export interface IFormatterTable {
[key: string]: (input: string|Buffer, done: (err: Error, subtitle?: string) => void) => void;
}
export interface ISeries {
episodes: ISeriesEpisode[];
series: string;
}
export interface ISeriesEpisode {
address: string;
episode: number;
volume: number;
}
export interface ISubtitle {
$: {
title: string;
wrap_style: string;
play_res_x: string;
play_res_y: string;
id: string;
lang_string: string;
created: string;
};
events: ISubtitleEvent;
styles: ISubtitleStyle;
}
export interface ISubtitleEvent {
event: {
$: {
end: string;
start: string;
style: string;
name: string;
margin_l: string;
margin_r: string;
margin_v: string;
effect: string;
text: string;
};
}[];
}
export interface ISubtitleStyle {
style: {
$: {
name: string;
font_name: string;
font_size: string;
primary_colour: string;
secondary_colour: string;
outline_colour: string;
back_colour: string;
bold: string;
italic: string;
underline: string;
strikeout: string;
scale_x: string;
scale_y: string;
spacing: string;
angle: string;
border_style: string;
outline: string;
shadow: string;
alignment: string;
margin_l: string;
margin_r: string;
margin_v: string;
encoding: string;
};
}[];
}

View File

@@ -1,3 +1,4 @@
'use strict'; 'use strict';
export import merge = require('./merge'); import merge from './merge';
export import stream = require('./stream'); import stream from './stream';
export default {merge, stream};

View File

@@ -1,18 +1,24 @@
'use strict'; 'use strict';
export = main;
import childProcess = require('child_process'); import childProcess = require('child_process');
import fs = require('fs'); import fs = require('fs');
import path = require('path'); import path = require('path');
import os = require('os'); import os = require('os');
import subtitle = require('../subtitle/index'); import subtitle from '../subtitle/index';
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, isSubtitled: boolean, rtmpInputPath: string, filePath: string, done: (err: Error) => void) { export default function(config: IConfig, isSubtitled: boolean, rtmpInputPath: string, filePath: string, streamMode: 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;
if (streamMode == "RTMP")
{
videoPath += path.extname(rtmpInputPath);
}
else
{
videoPath += ".mp4";
}
childProcess.exec(command() + ' ' + childProcess.exec(command() + ' ' +
'-o "' + filePath + '.mkv" ' + '-o "' + filePath + '.mkv" ' +
'"' + videoPath + '" ' + '"' + videoPath + '" ' +
@@ -55,4 +61,4 @@ function unlinkTimeout(videoPath: string, subtitlePath: string, timeout: number)
if (err) unlinkTimeout(videoPath, subtitlePath, timeout); if (err) unlinkTimeout(videoPath, subtitlePath, timeout);
}); });
}, timeout); }, timeout);
} }

View File

@@ -1,5 +1,4 @@
'use strict'; 'use strict';
export = main;
import childProcess = require('child_process'); import childProcess = require('child_process');
import path = require('path'); import path = require('path');
import os = require('os'); import os = require('os');
@@ -7,20 +6,38 @@ import os = require('os');
/** /**
* Streams the video to disk. * Streams the video to disk.
*/ */
function main(rtmpUrl: string, rtmpInputPath: string, swfUrl: string, filePath: string, done: (err: Error) => void) { export default function(rtmpUrl: string, rtmpInputPath: string, swfUrl: string, filePath: string, fileExt: string, mode: string, done: (err: Error) => void) {
childProcess.exec(command() + ' ' + if (mode == "RTMP")
'-r "' + rtmpUrl + '" ' + {
'-y "' + rtmpInputPath + '" ' + childProcess.exec(command("rtmpdump") + ' ' +
'-W "' + swfUrl + '" ' + '-r "' + rtmpUrl + '" ' +
'-o "' + filePath + '"', { '-y "' + rtmpInputPath + '" ' +
maxBuffer: Infinity '-W "' + swfUrl + '" ' +
}, done); '-o "' + filePath + fileExt + '"', {
maxBuffer: Infinity
}, done);
}
else if (mode == "HLS")
{
console.info("Experimental FFMPEG, MAY FAIL!!!");
var cmd=command("ffmpeg") + ' ' +
'-i "' + rtmpInputPath + '" ' +
'-c copy -bsf:a aac_adtstoasc ' +
'"' + filePath + '.mp4"';
childProcess.exec(cmd, {
maxBuffer: Infinity
}, done);
}
else
{
console.error("No such mode: " + mode);
}
} }
/** /**
* Determines the command for the operating system. * Determines the command for the operating system.
*/ */
function command(): string { function command(exe: string): string {
if (os.platform() !== 'win32') return 'rtmpdump'; if (os.platform() !== 'win32') return exe;
return '"' + path.join(__dirname, '../../bin/rtmpdump.exe') + '"'; return '"' + path.join(__dirname, '../../bin/' + exe + '.exe') + '"';
} }

24
ts.js
View File

@@ -4,6 +4,15 @@ 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 build task should be removed upon release of TypeScript 1.5 with
// the support for `tsconfig.json`. Invoking `tsc` from `package.json` will then
// read the configuration and compile accordingly. It seems that `TSLint` will,
// eventually, support this mechanism too. That prevents the need for any kind
// of build task and will run entirely based on instructions from `npm`.
//
// Reference #1: https://github.com/Microsoft/TypeScript/issues/1667
// Reference #2: https://github.com/palantir/tslint/issues/281
read(function(err, fileNames) { read(function(err, fileNames) {
clean(fileNames, function() { clean(fileNames, function() {
var hasLintError = false; var hasLintError = false;
@@ -50,11 +59,11 @@ function clean(filePaths, 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 +92,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);
} }

52
tsconfig.json Normal file
View File

@@ -0,0 +1,52 @@
{
"version": "1.5.1-beta",
"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/interface/IConfig.d.ts",
"src/interface/IConfigLine.d.ts",
"src/interface/IConfigTask.d.ts",
"src/interface/IEpisodePage.d.ts",
"src/interface/IEpisodePlayer.d.ts",
"src/interface/IEpisodePlayerConfig.d.ts",
"src/interface/IFormatterTable.d.ts",
"src/interface/ISeries.d.ts",
"src/interface/ISeriesEpisode.d.ts",
"src/interface/ISubtitle.d.ts",
"src/interface/ISubtitleEvent.d.ts",
"src/interface/ISubtitleStyle.d.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/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"
]
}