Repository: h4ck-rOOt/Lynda-Decryptor
Branch: master
Commit: 2099f6cba734
Files: 21
Total size: 76.8 KB
Directory structure:
gitextract_xog5ihvi/
├── .gitignore
├── LICENSE
├── LyndaDecryptor/
│ ├── App.config
│ ├── CaptionToSrt.cs
│ ├── Decryptor.cs
│ ├── DecryptorOptions.cs
│ ├── LyndaDecryptor.csproj
│ ├── Program.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── Utils.cs
│ ├── VideoInfo.cs
│ └── packages.config
├── LyndaDecryptorTests/
│ ├── CommandLineParserTest.cs
│ ├── DecryptionTest.cs
│ ├── LyndaDecryptorTests.csproj
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ └── TestFiles/
│ ├── 88067_2195c10678b4f73e34795af641ad1ecc.lynda
│ ├── 88071_4650ab745df849fd96f1fdbdb016a5e6.lynda
│ ├── 88087_44c891cdef18ee48d968a018afe3befd.lynda
│ └── 88089_191d15e08d44c0e84f2237f433c67a67.lynda
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Microsoft Azure ApplicationInsights config file
ApplicationInsights.config
# Windows Store app package directory
AppPackages/
BundleArtifacts/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
# FAKE - F# Make
.fake/
*.sln
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 Tobias Becht
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: LyndaDecryptor/App.config
================================================
================================================
FILE: LyndaDecryptor/CaptionToSrt.cs
================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace LyndaDecryptor
{
class CaptionToSrt
{
private string filePath;
private string outFile;
public CaptionToSrt(string afilePath)
{
this.filePath = afilePath;
}
public string FilePath
{
get
{
return filePath;
}
set
{
filePath = value;
}
}
public string OutFile
{
get
{
return outFile;
}
set
{
outFile = value;
}
}
public byte[] stringToBytes(string text)
{
return System.Text.Encoding.UTF8.GetBytes(text);
}
public enum timestampFormat
{
shortTimestamp, //Format [##:##:##.##], trailing binary data 14 bytes.
longTimestamp //Format [##:##:##.###], trailing binary data 15 bytes.
}
public class Timestamp
{
public readonly int position;
public readonly timestampFormat type;
public Timestamp(int _position, timestampFormat _type)
{
position = _position;
type = _type;
}
}
public Timestamp[] findTimestamps(byte[] inputByteArray)
{
List timestampList = new List();
byte openingBracket = stringToBytes("[")[0];
byte closingBracket = stringToBytes("]")[0];
byte colon = stringToBytes(":")[0];
byte dot = stringToBytes(".")[0];
// Offsets for the following characters are relative to the opening bracket.
int firstColonOffset = 3;
int secondColonOffset = 6;
int dotOffset = 9;
int rangeStartOffset = -16; // Start of binary data before timestamp occurs this far from the opening bracket.
// Character positions for short timestamp.
int closingBracketOffset_shortTimestamp = 12;
int rangeEndOffset_shortTimestamp = 26; // End of binary data after timestamp occurs this far from the opening bracket.
// Character positions for long timestamp.
int closingBracketOffset_longTimestamp = 13;
// int rangeEndOffset_longTimestamp = 27; // End of binary data after timestamp occurs this far from the opening bracket. // Not used in this part of the code, but still good to be aware of.
// The distance you can skip forward when searching for the next timestamp. This can vary according to the timestamp format. More logic could be put into this, but taking the length belonging to the shortest format is simplest and safest.
int minimumRangeEndOffset = rangeEndOffset_shortTimestamp;
timestampFormat timestampFormat = timestampFormat.shortTimestamp; //Default value, real format will be determined later.
int inputIndex = rangeStartOffset * -1; // Valid opening bracket can't occur before this position so start here.
bool foundTimestamp = false; //Default value, will be set to true later if a timestamp is found.
byte inputCharacter;
int inputLength = inputByteArray.Length;
// Look for timestamps in the caption file data.
while (inputIndex < inputLength)
{
inputCharacter = inputByteArray[inputIndex];
// Search for opening bracket.
if (inputCharacter == openingBracket)
{
// Check if opening bracket belongs to timestamp.
if (inputCharacter + minimumRangeEndOffset <= inputLength)
{
// Timestamps can take 2 forms: [##:##:##.##] and [##:##:##.###].
// Check the elements they have in common (::.).
if (inputByteArray[inputIndex + firstColonOffset] == colon &&
inputByteArray[inputIndex + secondColonOffset] == colon &&
inputByteArray[inputIndex + dotOffset] == dot)
{
// Fixed timestamp identifiers match, now check location of the closing bracket.
if (inputByteArray[inputIndex + closingBracketOffset_shortTimestamp] == closingBracket)
{
// Timestamp was format [##:##:##.##].
timestampFormat = timestampFormat.shortTimestamp;
foundTimestamp = true;
}
else if (inputByteArray[inputIndex + closingBracketOffset_longTimestamp] == closingBracket)
{
// Timestamp was format [##:##:##.###].
timestampFormat = timestampFormat.longTimestamp;
foundTimestamp = true;
}
else
{
// No match for closing bracket. Not a timestamp after all, or in an unknown format.
foundTimestamp = false;
}
}
else
{
// Other timestamp identifiers don't match, opening bracket was not part of timestamp.
foundTimestamp = false;
}
}
else
{
// Not enough space after opening bracket for this to be the beginning of a subtitle.
foundTimestamp = false;
}
}
if (foundTimestamp)
{
// The timestamp is preceded by some binary data (rangeStartOffset), so skip over that and add the actual beginning of the timestamp.
// Also add the timestamp format ([##:##:##.##] or [##:##:##.###]), this is important later for parsing.
timestampList.Add(new Timestamp(inputIndex + rangeStartOffset, timestampFormat));
// Set checkpoint for next timestamp beyond the (minimum) range of this one and the leading binary data of the next.
inputIndex += minimumRangeEndOffset + 1 - rangeStartOffset;
// Reset found flag.
foundTimestamp = false;
}
else
{
// Advance to the next character in the caption data.
inputIndex++;
}
}
// Return timestamp locations in the caption file data.
return timestampList.ToArray();
}
public class Subtitle
{
// Text portion of the subtitle starts this far from the beginning of the timestamp. Can vary according to timestamp format.
private int textStartPosition;
// Check if all necessary parameters for a valid subtitle have been set.
public bool isValidSubtitle
{
get
{
if (start_timestamp != null &&
end_timestamp != null &&
subtitleText != null)
{
return true;
}
else
{
return false;
}
}
}
// Time when the subtitle should appear in the video.
private string start_timestamp;
public string Start_timestamp
{
get { return start_timestamp; }
}
// Time when the subtitle should disappear.
private string end_timestamp;
public string End_timestamp
{
get { return end_timestamp; }
}
// The actual text of the subtitle.
private string subtitleText;
public string SubtitleText
{
get { return subtitleText; }
}
// Initialize subtitle object.
public Subtitle(byte[] subtitleData, timestampFormat ts_format)
{
extractStartTimestamp(subtitleData, ts_format);
extractText(subtitleData);
}
// Unknown at initialization. Will be set later with the start time of the next subtitle object.
public void setEndTimestamp(string endtime)
{
end_timestamp = endtime;
}
// Extract timestamp from subtitle data.
private void extractStartTimestamp(byte[] subtitleData, timestampFormat ts_format)
{
int timestampLength;
if (ts_format == timestampFormat.shortTimestamp)
{
// Short timestamp (without brackets) is 11 bytes long.
timestampLength = 11;
textStartPosition = 43;
}
else
{
// Long timestamp (without brackets) is 12 bytes long.
timestampLength = 12;
textStartPosition = 44;
}
byte[] timestampByteArray = new byte[timestampLength];
// Timestamp (without brackets) starts at position 17.
Array.Copy(subtitleData, 17, timestampByteArray, 0, timestampLength);
string timestampString = bytesToString(timestampByteArray, timestampByteArray.Length);
string properFormatTimestamp = convertToShortFormat(timestampString);
// Change "." in timestamp to "," which is used in the SRT format.
start_timestamp = Regex.Replace(properFormatTimestamp, "\\.", ",");
}
// Extract text from subtitle data.
private void extractText(byte[] subtitleData)
{
bool hasValidText;
byte[] trimmedText = new byte[0];
if (subtitleData.Length > textStartPosition)
{
// Subtitle is long enough to contain text following the timestamp.
int textLength = subtitleData.Length - textStartPosition;
byte[] textPortion = new byte[textLength];
Array.Copy(subtitleData, textStartPosition, textPortion, 0, textLength);
// Strip nonprintable characters from beginning and end of text.
trimmedText = trimNonprintable(textPortion);
if (trimmedText.Length > 0)
{
hasValidText = true;
}
else
{
// Text consisted of only nonprintable characters.
hasValidText = false;
}
}
else
{
// Subtitle is too short to have text.
hasValidText = false;
}
if (hasValidText)
{
trimmedText = enforce_CRLF_linebreaks(trimmedText);
subtitleText = bytesToString(trimmedText, trimmedText.Length);
}
}
private byte[] trimNonprintable(byte[] textData)
{
List textList = textData.ToList();
int currentCharacter;
int removeIndex;
bool searchForward = true;
// Trim beginning of text.
while (textList.Count > 0)
{
if (searchForward)
{
// Scan forwards from beginning of text.
currentCharacter = Convert.ToInt32(textList.First());
removeIndex = 0;
}
else
{
// Scan backwards from end of text.
currentCharacter = Convert.ToInt32(textList.Last());
removeIndex = textList.Count - 1;
}
if (currentCharacter < 33 || currentCharacter > 126)
{
// Character values lower than 33 or higher than 126 are all nonprintable, remove them.
textList.RemoveAt(removeIndex);
}
else
{
// Hit a printable character.
if (searchForward)
{
// Switch to trimming end of text.
searchForward = false;
}
else
{
// Trimming complete.
break;
}
}
}
return textList.ToArray();
}
private byte[] enforce_CRLF_linebreaks(byte[] text)
{
// Make all linebreaks CRLF (if they aren't already), just in case mixed linebreaks would trip up some SRT interpreter.
byte currentCharacter;
byte previousCharacter = 0x0; // Set to something not \r
int textLength = text.Length;
bool found_LF_linebreak = false;
List returnText = new List();
byte LF_Character = stringToBytes("\n")[0];
byte CR_Character = stringToBytes("\r")[0];
for (int characterIndex = 0; characterIndex < textLength; characterIndex++)
{
currentCharacter = text[characterIndex];
if (currentCharacter == LF_Character)
{
if (previousCharacter != CR_Character)
{
found_LF_linebreak = true;
}
}
if (found_LF_linebreak)
{
returnText.Add(CR_Character);
returnText.Add(LF_Character);
// Reset found flag.
found_LF_linebreak = false;
}
else
{
// Character was not part of linebreak, or character was part of CRLF linebreak.
returnText.Add(currentCharacter);
}
previousCharacter = currentCharacter;
}
return returnText.ToArray();
}
// Change long timestamp formats to short format for .SRT file. SRT can handle long format too, but maybe not mixed formats. Making everything the same just in case.
private string convertToShortFormat(string timestamp)
{
if (timestamp.Length == 12)
{
// Timestamp is long format.
// Return everything but last character.
return timestamp.Substring(0,11);
}
else
{
// Timestamp is already short format.
return timestamp;
}
}
private string bytesToString(byte[] bytes, int length)
{
return System.Text.Encoding.UTF8.GetString(bytes, 0, length);
}
private byte[] stringToBytes(string text)
{
return System.Text.Encoding.UTF8.GetBytes(text);
}
}
public Subtitle[] parseCaptionData(byte[] captionData, Timestamp[] timestampDataArray)
{
// Extract subtitles from caption file data.
int timestampCount = timestampDataArray.Length;
int startPosition;
int subtitleLength;
byte[] newSubtitleData;
Subtitle newSubtitle;
List subtitleList = new List();
for (int timestampIndex = 0; timestampIndex < timestampCount; timestampIndex++)
{
startPosition = timestampDataArray[timestampIndex].position;
if (timestampIndex == timestampCount - 1)
{
// Special case for the last subtitle. It extends to the end of the caption data.
subtitleLength = captionData.Length - startPosition;
}
else
{
// The subtitle extends up to the beginning of the next one.
subtitleLength = timestampDataArray[timestampIndex + 1].position - startPosition;
}
newSubtitleData = new byte[subtitleLength];
Array.Copy(captionData, startPosition, newSubtitleData, 0, subtitleLength);
// Create subtitle object, which also handles parsing of the data.
timestampFormat ts_format = timestampDataArray[timestampIndex].type;
newSubtitle = new Subtitle(newSubtitleData, ts_format);
subtitleList.Add(newSubtitle);
}
// Set end time of each subtitle to the beginning of the next one.
int subtitleCount = subtitleList.Count;
for (int subtitleIndex = 0; subtitleIndex < subtitleCount - 1; subtitleIndex++)
{
// End timestamp is equal to the start timestamp of the next one. Last subtitle is skipped because there is no next one. It wouldn't contain text anyway.
Subtitle currentSub = subtitleList[subtitleIndex];
Subtitle nextSub = subtitleList[subtitleIndex + 1];
currentSub.setEndTimestamp(nextSub.Start_timestamp);
}
// Discard subtitles that have no text
List filteredSubtitles = new List();
for (int subtitleIndex = 0; subtitleIndex < subtitleCount; subtitleIndex++)
{
Subtitle currentSub = subtitleList[subtitleIndex];
if (currentSub.isValidSubtitle)
{
filteredSubtitles.Add(currentSub);
}
}
// Return array of valid subtitles.
return filteredSubtitles.ToArray();
}
public bool convertToSrt()
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NOTES ON THE STRUCTURE OF .CAPTION FILES //
// //
// The .caption files start with a header. It is ignored by the conversion function. //
// Following the header are blocks of subtitle data. These blocks consist of 16 bytes of binary data, followed by a timestamp in the format [##:##:##.##], or //
// in some cases [##:##:##.###]. This is followed by another 14 or 15 bytes of binary data (depending on timestamp format), after which the text of the //
// subtitle starts. The text continues until the next block. Some blocks contain no valid text, and serve as markers for the end time of the previous block. //
// They usually occur once (or several times) at the end of the .caption file, but can sometimes be found following each valid subtitle block. //
// Linebreaks used by .caption files are usually CRLF, but can also be LF. This may be different for each video. //
// Blocks are most often separated by double, but sometimes single linebreaks (in combination with end-time blocks). //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NOTES ON THE CONVERSION CODE //
// //
// During testing, splitting subtitle blocks by linebreaks was the first attempted method of extracting data. It came with some hard-to-squish bugs, as the //
// binary data mentioned above would sometimes randomly produce linebreak characters. The method of extraction was then switched to locating the timestamps //
// and going from there. Their fixed location in the subtitle block makes it easy to locate (and ignore) other elements in the block. //
// //
// The data is mostly handled in byte-form. While performing operations on strings would be more simple and concise, it can interfere with the evenly-spaced //
// layout of the data, sometimes producing extra or missing characters. Handling the data in byte-form prevents that. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Read content of source subtitle file as bytes.
byte[] subtitleFile = File.ReadAllBytes(filePath);
// Locate start positions of subtitles.
Timestamp[] timestampData = findTimestamps(subtitleFile);
// Extract subtitles from file data.
Subtitle[] subtitles = parseCaptionData(subtitleFile, timestampData);
// Output data in .srt file.
this.buildSrt(subtitles, this.outFile);
return true;
}
private bool buildSrt(Subtitle[] subtitleArray, string path)
{
try
{
//SRT is perhaps the most basic of all subtitle formats.
//It consists of four parts, all in text..
//1.A number indicating which subtitle it is in the sequence.
//2.The time that the subtitle should appear on the screen, and then disappear.
//3.The subtitle itself.
//4.A blank line indicating the start of a new subtitle.
//1
//00:02:17,440-- > 00:02:20,375
//and here goes the text
//blank line
StreamWriter writer = new StreamWriter(path);
Subtitle currentSubtitle;
for (int subtitleIndex = 0; subtitleIndex < subtitleArray.Length; subtitleIndex++)
{
currentSubtitle = subtitleArray[subtitleIndex];
writer.WriteLine(subtitleIndex + 1);
writer.WriteLine(currentSubtitle.Start_timestamp + " --> " + currentSubtitle.End_timestamp);
writer.WriteLine(currentSubtitle.SubtitleText);
writer.WriteLine();
}
writer.Close();
return true;
}
catch (Exception ex)
{
Console.WriteLine("Error: Cannot write file " + path + ex.ToString());
return false;
}
}
}
}
================================================
FILE: LyndaDecryptor/Decryptor.cs
================================================
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static LyndaDecryptor.Utils;
namespace LyndaDecryptor
{
public class Decryptor
{
#region Fields & Properties
// Cryptographics
RijndaelManaged RijndaelInstace;
byte[] KeyBytes;
// Database Connection
SQLiteConnection DatabaseConnection;
// Threading
List TaskList = new List();
SemaphoreSlim Semaphore = new SemaphoreSlim(5);
object SemaphoreLock = new object();
// IO
List InvalidPathCharacters = new List(), InvalidFileCharacters = new List();
DirectoryInfo OutputDirectory = null;
// Decryptor Options
public DecryptorOptions Options = new DecryptorOptions();
#endregion
public Decryptor()
{
InvalidPathCharacters.AddRange(Path.GetInvalidPathChars());
InvalidPathCharacters.AddRange(new char[] { ':', '?', '"', '\\', '/' });
InvalidFileCharacters.AddRange(Path.GetInvalidFileNameChars());
InvalidFileCharacters.AddRange(new char[] { ':', '?', '"', '\\', '/' });
}
///
/// Constructs an object with decryptor options
/// If specified this constructor inits the database
///
///
public Decryptor(DecryptorOptions options) : this()
{
Options = options;
if (options.UseDatabase)
Options.UseDatabase = InitDB(options.DatabasePath);
}
#region Methods
///
/// Create the RSA Instance and EncryptedKeyBytes
///
/// secret cryptographic key
public void InitDecryptor(string EncryptionKey)
{
WriteToConsole("[START] Init Decryptor...");
RijndaelInstace = new RijndaelManaged
{
KeySize = 0x80,
Padding = PaddingMode.Zeros
};
KeyBytes = new ASCIIEncoding().GetBytes(EncryptionKey);
WriteToConsole("[START] Decryptor successful initalized!" + Environment.NewLine, ConsoleColor.Green);
}
///
/// Create a SqliteConnection to the specified or default application database.
///
/// Path to database file
/// true if init was successful
public bool InitDB(string databasePath)
{
WriteToConsole("[DB] Creating db connection...");
// Check for databasePath
if (string.IsNullOrEmpty(databasePath))
{
// Try to figure out default app db path
string AppPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "lynda.com", "video2brain Desktop App");
if (!Directory.Exists(AppPath))
AppPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "lynda.com", "lynda.com Desktop App");
// Find db file or databasePath = default(string)
databasePath = Directory.EnumerateFiles(AppPath, "*.sqlite", SearchOption.AllDirectories).FirstOrDefault();
}
// Check if databasePath is present (specific or default)
if (!string.IsNullOrEmpty(databasePath))
{
DatabaseConnection = new SQLiteConnection($"Data Source={databasePath}; Version=3;FailIfMissing=True");
DatabaseConnection.Open();
WriteToConsole("[DB] DB successfully connected and opened!" + Environment.NewLine, ConsoleColor.Green);
return true;
}
else
{
WriteToConsole("[DB] Couldn't find db file!" + Environment.NewLine, ConsoleColor.Red);
return false;
}
}
///
/// Decrypt all files in a given folder
///
/// path to folder with encrypted .lynda files
/// specify an output folder
public void DecryptAll(string folderPath, string outputFolder = "")
{
if (!Directory.Exists(folderPath))
throw new DirectoryNotFoundException();
if (string.IsNullOrWhiteSpace(outputFolder))
outputFolder = Path.Combine(Path.GetDirectoryName(folderPath), "decrypted");
OutputDirectory = Directory.Exists(outputFolder) ? new DirectoryInfo(outputFolder) : Directory.CreateDirectory(outputFolder);
IEnumerable files = Directory.EnumerateFiles(folderPath, "*.lynda", SearchOption.AllDirectories)
.Concat(Directory.EnumerateFiles(folderPath, "*.ldcw", SearchOption.AllDirectories));
foreach (string entry in files)
{
string newPath = string.Empty;
string item = entry;
if (Options.UseDatabase)
{
try
{
// get metadata with courseID and videoID
VideoInfo videoInfo = GetVideoInfoFromDB(new DirectoryInfo(Path.GetDirectoryName(item)).Name, Path.GetFileName(item).Split('_')[0]);
if (videoInfo != null)
{
// create new path and folder
string complexTitle = $"E{videoInfo.VideoIndex} - {videoInfo.VideoTitle}.mp4";
string simpleTitle = $"E{videoInfo.VideoIndex}.mp4";
newPath = Path.Combine(OutputDirectory.FullName, CleanPath(videoInfo.CourseTitle),
CleanPath(videoInfo.ChapterTitle), CleanPath(complexTitle));
if (newPath.Length > 240)
{
newPath = Path.Combine(OutputDirectory.FullName, CleanPath(videoInfo.CourseTitle),
CleanPath(videoInfo.ChapterTitle), CleanPath(simpleTitle));
}
if (!Directory.Exists(Path.GetDirectoryName(newPath)))
Directory.CreateDirectory(Path.GetDirectoryName(newPath));
}
}
catch (Exception e)
{
WriteToConsole($"[ERR] Could not retrive media information from database! Exception: {e.Message} Falling back to default behaviour!", ConsoleColor.Yellow);
}
}
if (String.IsNullOrWhiteSpace(newPath))
{
newPath = Path.ChangeExtension(item, ".mp4");
}
Semaphore.Wait();
TaskList.Add(Task.Run(() =>
{
Decrypt(item, newPath);
ConvertSub(item, newPath, Options.RemoveFilesAfterDecryption);
lock (SemaphoreLock)
{
Semaphore.Release();
}
}));
}
Task.WhenAll(TaskList).Wait();
WriteToConsole("Decryption completed!", ConsoleColor.DarkGreen);
}
///
/// Decrypt a single encrypted file into decrypted file path
///
/// Path to encrypted file
/// Path to decrypted file
/// Remove encrypted file after decryption?
public void Decrypt(string encryptedFilePath, string decryptedFilePath)
{
if (!File.Exists(encryptedFilePath))
{
WriteToConsole("[ERR] Couldn't find encrypted file...", ConsoleColor.Red);
return;
}
var encryptedFileInfo = new FileInfo(encryptedFilePath);
if (File.Exists(decryptedFilePath))
{
var decryptedFileInfo = new FileInfo(decryptedFilePath);
if (decryptedFileInfo.Length == encryptedFileInfo.Length)
{
WriteToConsole("[DEC] File " + decryptedFilePath + " exists already and will be skipped!", ConsoleColor.Yellow);
return;
}
else
WriteToConsole("[DEC] File " + decryptedFilePath + " exists already but seems to differ in size...", ConsoleColor.Blue);
decryptedFileInfo = null;
}
byte[] buffer = new byte[0x50000];
if (encryptedFileInfo.Extension != ".lynda" &&
encryptedFileInfo.Extension != ".ldcw")
{
WriteToConsole("[ERR] Couldn't load file: " + encryptedFilePath, ConsoleColor.Red);
return;
}
if (encryptedFileInfo.Extension == ".lynda")
{
using (var inStream = new FileStream(encryptedFilePath, FileMode.Open))
{
using (var decryptionStream = new CryptoStream(inStream, RijndaelInstace.CreateDecryptor(KeyBytes, KeyBytes), CryptoStreamMode.Read))
{
using (var outStream = new FileStream(decryptedFilePath, FileMode.Create))
{
WriteToConsole("[DEC] Decrypting file " + encryptedFileInfo.Name + "...");
while ((inStream.Length - inStream.Position) >= buffer.Length)
{
decryptionStream.Read(buffer, 0, buffer.Length);
outStream.Write(buffer, 0, buffer.Length);
}
buffer = new byte[inStream.Length - inStream.Position];
decryptionStream.Read(buffer, 0, buffer.Length);
outStream.Write(buffer, 0, buffer.Length);
outStream.Flush();
outStream.Close();
WriteToConsole("[DEC] File decryption completed: " + decryptedFilePath, ConsoleColor.DarkGreen);
}
}
inStream.Close();
buffer = null;
}
}
else if (encryptedFileInfo.Extension == ".ldcw")
{
using (var inStream = new FileStream(encryptedFilePath, FileMode.Open))
{
using (var outStream = new FileStream(decryptedFilePath, FileMode.Create))
{
WriteToConsole("[DEC] Decrypting file " + encryptedFileInfo.Name + "...");
byte[] array = new byte[63];
int num = inStream.Read(array, 0, 63);
byte[] array2 = new byte[63];
for (int i = 0; i < num; i++)
{
array2[i] = (byte)~array[i];
}
outStream.Write(array2, 0, array2.Length);
while ((inStream.Length - inStream.Position) >= buffer.Length)
{
inStream.Read(buffer, 0, buffer.Length);
outStream.Write(buffer, 0, buffer.Length);
}
buffer = new byte[inStream.Length - inStream.Position];
inStream.Read(buffer, 0, buffer.Length);
outStream.Write(buffer, 0, buffer.Length);
outStream.Flush();
outStream.Close();
WriteToConsole("[DEC] File decryption completed: " + decryptedFilePath, ConsoleColor.DarkGreen);
}
inStream.Close();
buffer = null;
}
}
ConvertSub(encryptedFilePath, decryptedFilePath, Options.RemoveFilesAfterDecryption);
if (Options.RemoveFilesAfterDecryption)
encryptedFileInfo.Delete();
encryptedFileInfo = null;
}
///
/// Retrive video infos from the database
///
/// course id
/// video id
/// VideoInfo instance or null
private VideoInfo GetVideoInfoFromDB(string courseID, string videoID)
{
VideoInfo videoInfo = null;
try
{
SQLiteCommand cmd = DatabaseConnection.CreateCommand();
// Query all required tables and fields from the database
cmd.CommandText = @"SELECT Video.ID, Video.ChapterId, Video.CourseId,
Video.Title, Filename, Course.Title as CourseTitle,
Video.SortIndex, Chapter.Title as ChapterTitle,
Chapter.SortIndex as ChapterIndex
FROM Video, Course, Chapter
WHERE Video.ChapterId = Chapter.ID
AND Course.ID = Video.CourseId
AND Video.CourseId = @courseId
AND Video.ID = @videoId";
cmd.Parameters.Add(new SQLiteParameter("@courseId", courseID));
cmd.Parameters.Add(new SQLiteParameter("@videoId", videoID));
SQLiteDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
videoInfo = new VideoInfo
{
CourseTitle = reader.GetString(reader.GetOrdinal("CourseTitle")),
ChapterTitle = reader.GetString(reader.GetOrdinal("ChapterTitle")),
ChapterIndex = reader.GetInt32(reader.GetOrdinal("ChapterIndex")),
VideoIndex = reader.GetInt32(reader.GetOrdinal("SortIndex")),
VideoTitle = reader.GetString(reader.GetOrdinal("Title"))
};
videoInfo.ChapterTitle = $"{videoInfo.ChapterIndex} - {videoInfo.ChapterTitle}";
videoInfo.VideoID = videoID;
videoInfo.CourseID = courseID;
}
}
catch (Exception e)
{
WriteToConsole($"[ERR] Exception occured during db query ({courseID}/{videoID}): {e.Message}", ConsoleColor.Yellow);
videoInfo = null;
}
return videoInfo;
}
///
/// Clean the input string and remove all invalid chars
///
/// input path
///
private string CleanPath(string path)
{
foreach (char invalidChar in InvalidPathCharacters)
path = path.Replace(invalidChar, '-');
return path;
}
///
/// get caption path and create subtitle in the same plae as the decrypted video
///
/// Initial video path (.lynda file)
/// Full decrypted video path
/// boolean value, true for succesful conversion
private Boolean ConvertSub(string videoPath, string decryptedFilePath, bool deleteSourceOnSuccess = false)
{
using (var md5 = MD5.Create())
{
string videoId = Path.GetFileName(videoPath).Split('_')[0];
byte[] inputBytes = Encoding.ASCII.GetBytes(videoId);
byte[] hashBytes = md5.ComputeHash(inputBytes);
// Convert the byte array to hexadecimal string
var sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
sb.Append(hashBytes[i].ToString("X2"));
}
string subFName = sb.ToString() + ".caption";
string captionFilePath = Path.Combine(Path.GetDirectoryName(videoPath), subFName);
bool conversionSucceeded = false;
if (File.Exists(captionFilePath))
{
var csConv = new CaptionToSrt(captionFilePath);
string srtFile = Path.Combine(Path.GetDirectoryName(decryptedFilePath), Path.GetFileNameWithoutExtension(decryptedFilePath) + ".srt");
csConv.OutFile = srtFile;
conversionSucceeded = csConv.convertToSrt();
}
if (conversionSucceeded && deleteSourceOnSuccess)
{
File.Delete(captionFilePath);
}
return conversionSucceeded;
}
}
#endregion
}
}
================================================
FILE: LyndaDecryptor/DecryptorOptions.cs
================================================
namespace LyndaDecryptor
{
public class DecryptorOptions
{
public Mode UsageMode { get; set; }
public bool UseDatabase { get; set; }
public bool UseOutputFolder { get; set; }
public bool RemoveFilesAfterDecryption { get; set; }
public string InputPath { get; set; }
public string OutputPath { get; set; }
public string OutputFolder { get; set; }
public string DatabasePath { get; set; }
}
}
================================================
FILE: LyndaDecryptor/LyndaDecryptor.csproj
================================================
Debug
AnyCPU
{58EBF661-A637-45AE-956C-5EC1A602EDC3}
Exe
Properties
LyndaDecryptor
LyndaDecryptor
v4.5.2
512
true
publish\
true
Disk
false
Foreground
7
Days
false
false
true
0
1.0.0.%2a
false
false
true
AnyCPU
true
full
false
bin\Debug\
DEBUG;TRACE
prompt
4
AnyCPU
none
true
bin\Release\
TRACE
prompt
4
false
LyndaDecryptor.Program
true
true
bin\TestDB\
DEBUG;TRACE
full
AnyCPU
prompt
MinimumRecommendedRules.ruleset
true
lynda-logo.ico
..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.dll
..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.Core.dll
..\packages\System.Data.SQLite.Core.1.0.102.0\lib\net45\System.Data.SQLite.dll
True
..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.Data.dll
Designer
Designer
False
Microsoft .NET Framework 4.5 %28x86 and x64%29
true
False
.NET Framework 3.5 SP1
false
Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}".
================================================
FILE: LyndaDecryptor/Program.cs
================================================
using System;
using static LyndaDecryptor.Utils;
namespace LyndaDecryptor
{
public enum Mode
{
None = 0,
File,
Folder
};
public class Program
{
static void Main(string[] args)
{
Decryptor decryptor;
var decryptorOptions = new DecryptorOptions();
try
{
decryptorOptions = ParseCommandLineArgs(args);
decryptor = new Decryptor(decryptorOptions);
if (decryptorOptions.UsageMode == Mode.None)
{
Usage();
goto End;
}
else if (decryptorOptions.RemoveFilesAfterDecryption)
{
WriteToConsole("[ARGS] Removing files after decryption." + Environment.NewLine, ConsoleColor.Yellow);
WriteToConsole("[ARGS] Press any key to continue or CTRL + C to break..." + Environment.NewLine, ConsoleColor.Yellow);
Console.ReadKey();
}
decryptor.InitDecryptor(ENCRYPTION_KEY);
if (decryptorOptions.UsageMode == Mode.Folder)
decryptor.DecryptAll(decryptorOptions.InputPath, decryptorOptions.OutputFolder);
else if (decryptorOptions.UsageMode == Mode.File)
decryptor.Decrypt(decryptorOptions.InputPath, decryptorOptions.OutputPath);
}
catch (Exception e)
{
WriteToConsole("[START] Error occured: " + e.Message + Environment.NewLine, ConsoleColor.Red);
Usage();
}
End:
WriteToConsole(Environment.NewLine + "Press any key to exit the program...");
Console.ReadKey();
}
///
/// Print usage instructions
///
static void Usage()
{
Console.WriteLine("Usage (Directory): LyndaDecryptor /D PATH_TO_FOLDER [OPTIONS]");
Console.WriteLine("Usage (File): LyndaDecryptor /F ENCRYPTED_FILE DECRYPTED_FILE [OPTIONS]");
Console.WriteLine(Environment.NewLine + Environment.NewLine + "Flags: ");
Console.WriteLine("\t/D\tSource files are located in a folder.");
Console.WriteLine("\t/F\tSource and Destination file are specified.");
Console.WriteLine("\t/DB [PATH]\tSearch for Database or specify the location on your system.");
Console.WriteLine("\t/RM\tRemoves all files after decryption is complete.");
Console.WriteLine("\t/OUT [PATH]\tSpecifies an output directory instead of using default directory.");
}
}
}
================================================
FILE: LyndaDecryptor/Properties/AssemblyInfo.cs
================================================
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über die folgenden
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die einer Assembly zugeordnet sind.
[assembly: AssemblyTitle("LyndaDecryptor")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LyndaDecryptor")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar
// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von
// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
[assembly: ComVisible(false)]
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
[assembly: Guid("58ebf661-a637-45ae-956c-5ec1a602edc3")]
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
//
// Hauptversion
// Nebenversion
// Buildnummer
// Revision
//
// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern
// übernehmen, indem Sie "*" eingeben:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.3.0.0")]
[assembly: AssemblyFileVersion("1.3.0.0")]
================================================
FILE: LyndaDecryptor/Utils.cs
================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LyndaDecryptor
{
public static class Utils
{
private static ConsoleColor color_default;
private static object console_lock = new object();
public static string ENCRYPTION_KEY = "~h#\x00b0" + new string(new char[] { '\'', '*', '\x00b2', '"', 'C', '\x00b4', '|', '\x00a7', '\\' }) + "3~.";
static Utils()
{
color_default = Console.ForegroundColor;
}
public static void WriteToConsole(string Text, ConsoleColor color = ConsoleColor.Gray)
{
lock (console_lock)
{
Console.ForegroundColor = color;
Console.WriteLine(Text);
Console.ForegroundColor = color_default;
}
}
public static DecryptorOptions ParseCommandLineArgs(string[] args)
{
var options = new DecryptorOptions();
int index = 0;
int length = args.Length;
foreach (string arg in args)
{
if (string.IsNullOrWhiteSpace(arg))
{
index++;
continue;
}
switch (arg.ToUpper())
{
case "/D": // Directory Mode
if (length - 1 > index && Directory.Exists(args[index + 1]))
{
options.InputPath = args[index + 1];
options.UsageMode = Mode.Folder;
WriteToConsole("[ARGS] Changing mode to Folder decryption!", ConsoleColor.Yellow);
}
else
{
WriteToConsole("[ARGS] The directory path is missing..." + Environment.NewLine, ConsoleColor.Red);
throw new FileNotFoundException("Directory path is missing or specified directory was not found!");
}
break;
case "/F": // File Mode
if (length - 1 > index && File.Exists(args[index + 1]))
{
options.InputPath = args[index + 1];
if (length - 1 > index + 1 && !string.IsNullOrWhiteSpace(args[index + 2]))
{
if (File.Exists(args[index + 2]))
throw new IOException("File already exists: " + args[index + 2]);
options.OutputPath = args[index + 2];
}
else
throw new FormatException("Output file path is missing...");
options.UsageMode = Mode.File;
WriteToConsole("[ARGS] Changing mode to Single decryption!", ConsoleColor.Yellow);
}
else
{
throw new FileNotFoundException("Input file is missing or not specified!");
}
break;
case "/DB": // Use Database
options.UseDatabase = true;
if (length - 1 > index && File.Exists(args[index + 1]))
options.DatabasePath = args[index + 1];
break;
case "/RM": // Remove encrypted files after decryption
options.RemoveFilesAfterDecryption = true;
break;
case "/OUT":
options.UseOutputFolder = true;
if (args.Length - 1 > index)
options.OutputFolder = args[index + 1];
break;
}
index++;
}
return options;
}
}
}
================================================
FILE: LyndaDecryptor/VideoInfo.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LyndaDecryptor
{
public class VideoInfo
{
public string CourseTitle { get; set; }
public string ChapterTitle { get; set; }
public string VideoTitle { get; set; }
public string VideoID { get; set; }
public string CourseID { get; set; }
public int ChapterIndex { get; set; }
public int VideoIndex { get; set; }
}
}
================================================
FILE: LyndaDecryptor/packages.config
================================================
================================================
FILE: LyndaDecryptorTests/CommandLineParserTest.cs
================================================
#define TEST
using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using LyndaDecryptor;
using System.Collections.Generic;
using System.IO;
namespace LyndaDecryptorTests
{
[TestClass]
public class CommandLineParserTest
{
[TestMethod]
public void TestFileMode()
{
List args = new List();
args.Add("/F");
args.Add("TestFiles\\88067_2195c10678b4f73e34795af641ad1ecc.lynda");
args.Add("output.mp4");
DecryptorOptions options = new DecryptorOptions();
options = Utils.ParseCommandLineArgs(args.ToArray());
Assert.IsTrue(options.UsageMode == Mode.File);
Assert.AreEqual("TestFiles\\88067_2195c10678b4f73e34795af641ad1ecc.lynda", options.InputPath);
Assert.AreEqual("output.mp4", options.OutputPath);
args.Add("/DB");
options = Utils.ParseCommandLineArgs(args.ToArray());
Assert.IsTrue(options.UseDatabase);
Assert.AreEqual(null, options.DatabasePath);
args.Add("TestDB\\db_de.sqlite");
options = Utils.ParseCommandLineArgs(args.ToArray());
Assert.AreEqual(args.Last(), options.DatabasePath);
args.Add("/RM");
options = Utils.ParseCommandLineArgs(args.ToArray());
Assert.IsTrue(options.RemoveFilesAfterDecryption);
args.Add("/OUT");
args.Add("testfolder");
options = Utils.ParseCommandLineArgs(args.ToArray());
Assert.IsTrue(options.UseOutputFolder);
Assert.AreEqual(args.Last(), options.OutputFolder);
}
[TestMethod]
public void TestFolderMode()
{
List args = new List();
args.Add("/D");
args.Add("TestFiles");
DecryptorOptions options = new DecryptorOptions();
options = Utils.ParseCommandLineArgs(args.ToArray());
Assert.IsTrue(options.UsageMode == Mode.Folder);
Assert.AreEqual(options.InputPath, "TestFiles");
args.Add("/RM");
options = Utils.ParseCommandLineArgs(args.ToArray());
Assert.IsTrue(options.RemoveFilesAfterDecryption);
args.Add("/DB");
options = Utils.ParseCommandLineArgs(args.ToArray());
Assert.IsTrue(options.UseDatabase);
Assert.AreEqual(null, options.DatabasePath);
args.Add("TestDB\\db_de.sqlite");
options = Utils.ParseCommandLineArgs(args.ToArray());
Assert.AreEqual(args.Last(), options.DatabasePath);
args.Add("/OUT");
args.Add("testfolder");
options = Utils.ParseCommandLineArgs(args.ToArray());
Assert.IsTrue(options.UseOutputFolder);
Assert.AreEqual(options.OutputFolder, args.Last());
}
[TestMethod, ExpectedException(typeof(FormatException))]
public void MissingOutputArgShouldFailWithException()
{
List args = new List();
args.Add("/F");
args.Add("TestFiles\\88067_2195c10678b4f73e34795af641ad1ecc.lynda");
Utils.ParseCommandLineArgs(args.ToArray());
}
[TestMethod, ExpectedException(typeof(IOException))]
public void OutputFileAlreadyExistShouldFailWithException()
{
List args = new List();
args.Add("/F");
args.Add("TestFiles\\88067_2195c10678b4f73e34795af641ad1ecc.lynda");
args.Add("TestFiles\\88071_4650ab745df849fd96f1fdbdb016a5e6.lynda");
Utils.ParseCommandLineArgs(args.ToArray());
}
[TestMethod, ExpectedException(typeof(FileNotFoundException))]
public void TestMissingFolder()
{
List args = new List();
args.Add("/D");
args.Add("TestFiles2");
Utils.ParseCommandLineArgs(args.ToArray());
}
}
}
================================================
FILE: LyndaDecryptorTests/DecryptionTest.cs
================================================
using System;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using LyndaDecryptor;
namespace LyndaDecryptorTests
{
[TestClass]
public class DecryptionTest
{
[TestMethod]
public void TestSingleDecryption()
{
DecryptorOptions options = new DecryptorOptions
{
UsageMode = Mode.File,
InputPath = "TestFiles\\88067_2195c10678b4f73e34795af641ad1ecc.lynda",
OutputPath = "TestFiles\\88067_2195c10678b4f73e34795af641ad1ecc.mp4",
RemoveFilesAfterDecryption = false
};
Decryptor decryptor = new Decryptor(options);
decryptor.InitDecryptor(Utils.ENCRYPTION_KEY);
decryptor.Decrypt(options.InputPath, options.OutputPath);
FileInfo encryptedFile = new FileInfo(options.InputPath);
FileInfo decryptedFile = new FileInfo(options.OutputPath);
Assert.AreEqual(encryptedFile.Length, decryptedFile.Length);
}
[TestMethod]
public void TestSingleDecryptionWithDB()
{
}
[TestMethod]
public void TestFolderDecryption()
{
}
[TestMethod]
public void TestFolderDecryptionWithDB()
{
}
}
}
================================================
FILE: LyndaDecryptorTests/LyndaDecryptorTests.csproj
================================================
Debug
AnyCPU
{F8726170-D017-4517-AFB9-B1870A282E81}
Library
Properties
LyndaDecryptorTests
LyndaDecryptorTests
v4.5.2
512
{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
10.0
$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
False
UnitTest
true
full
false
bin\Debug\
DEBUG;TRACE
prompt
4
pdbonly
true
bin\Release\
TRACE
prompt
4
False
{58ebf661-a637-45ae-956c-5ec1a602edc3}
LyndaDecryptor
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
PreserveNewest
False
False
False
False
================================================
FILE: LyndaDecryptorTests/Properties/AssemblyInfo.cs
================================================
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über folgende
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die einer Assembly zugeordnet sind.
[assembly: AssemblyTitle("LyndaDecryptorTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LyndaDecryptorTests")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Wenn ComVisible auf "false" festgelegt wird, sind die Typen innerhalb dieser Assembly
// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
[assembly: ComVisible(false)]
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
[assembly: Guid("f8726170-d017-4517-afb9-b1870a282e81")]
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
//
// Hauptversion
// Nebenversion
// Buildnummer
// Revision
//
// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern
// übernehmen, indem Sie "*" eingeben:
// [Assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
================================================
FILE: README.md
================================================
# Video2Brain (Lynda-Decryptor)
You may have noticed that if you want to download Videos from Video2Brain, the only way is to use the Video2Brain desktop app.
Now you can use this application to decrypt all videos and create a folder structure with normal titles instead of hash values.
## Usage
- Please download the latest binary from release section ([download](https://github.com/h4ck-rOOt/Lynda-Decryptor/releases/download/v1.3/LyndaDecryptor.zip))
- Extract the files with your favorit compression tool or buildin os functions.
- Open commandline and navigate to the extracted folder containing LyndaDecryptor.exe
- Execute LyndaDecryptor.exe with LyndaDecryptor /F [PathToEncryptedFile] [PathToDecryptedFile] or LyndaDecrytor /D [FolderWithEncryptedFiles] where you have to replace the "PathToEncryptedFile", "PathToDecryptedFile" or "FolderWithEncryptedFiles" with the relative or full path to your files.
There are some flags you can use to control the behavior:
- /RM will remove encrypted files after decryption
- /OUT is available to specify an output folder for decrypted files
- /DB [PATH] let you specify the full path to the database containing titles (to get rid of these odd names)
Converting subtitles is possible (thanks to @mdomnita) with this little tool [Repo](https://github.com/mdomnita/LyndaCaptionToSrtConvertor)
LDCW Support provided by @DmitriySokhach (thanks for your work)
---
## Limitations
This program is **windows only** at the time of this writing. You can compile it under Linux and Mac using [Mono](http://www.mono-project.com/) or [DotNetCore](https://www.microsoft.com/net/core), with some changes due to the sqlite platform binding.
At the moment **decrypting files from android or ios apps are not supported**
---
