Full Code of h4ck-rOOt/Lynda-Decryptor for AI

master 2099f6cba734 cached
21 files
76.8 KB
16.3k tokens
51 symbols
1 requests
Download .txt
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
================================================
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
  </configSections>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
  </startup>
  <system.data>
    <DbProviderFactories>
      <remove invariant="System.Data.SQLite"/>
      <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite"/>
    </DbProviderFactories>
  </system.data>
</configuration>


================================================
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<Timestamp> timestampList = new List<Timestamp>();

            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<byte> 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<byte> returnText = new List<byte>();
                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<Subtitle> subtitleList = new List<Subtitle>();
            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<Subtitle> filteredSubtitles = new List<Subtitle>();
            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<Task> TaskList = new List<Task>();
        SemaphoreSlim Semaphore = new SemaphoreSlim(5);
        object SemaphoreLock = new object();

        // IO
        List<char> InvalidPathCharacters = new List<char>(), InvalidFileCharacters = new List<char>();
        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[] { ':', '?', '"', '\\', '/' });
        }

        /// <summary>
        /// Constructs an object with decryptor options</br>
        /// If specified this constructor inits the database
        /// </summary>
        /// <param name="options"></param>
        public Decryptor(DecryptorOptions options) : this()
        {
            Options = options;

            if (options.UseDatabase)
                Options.UseDatabase = InitDB(options.DatabasePath);
        }

        #region Methods

        /// <summary>
        /// Create the RSA Instance and EncryptedKeyBytes
        /// </summary>
        /// <param name="EncryptionKey">secret cryptographic key</param>
        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);
        }

        /// <summary>
        /// Create a SqliteConnection to the specified or default application database.
        /// </summary>
        /// <param name="databasePath">Path to database file</param>
        /// <returns>true if init was successful</returns>
        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;
            }
        }

        /// <summary>
        /// Decrypt all files in a given folder
        /// </summary>
        /// <param name="folderPath">path to folder with encrypted .lynda files</param>
        /// <param name="outputFolder">specify an output folder</param>
        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<string> 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);
        }

        /// <summary>
        /// Decrypt a single encrypted file into decrypted file path
        /// </summary>
        /// <param name="encryptedFilePath">Path to encrypted file</param>
        /// <param name="decryptedFilePath">Path to decrypted file</param>
        /// <param name="removeOldFile">Remove encrypted file after decryption?</param>
        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;
        }

        /// <summary>
        /// Retrive video infos from the database
        /// </summary>
        /// <param name="courseID">course id</param>
        /// <param name="videoID">video id</param>
        /// <returns>VideoInfo instance or null</returns>
        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;
        }

        /// <summary>
        /// Clean the input string and remove all invalid chars
        /// </summary>
        /// <param name="path">input path</param>
        /// <returns></returns>
        private string CleanPath(string path)
        {
            foreach (char invalidChar in InvalidPathCharacters)
                path = path.Replace(invalidChar, '-');

            return path;
        }


        /// <summary>
        /// get caption path and create subtitle in the same plae as the decrypted video
        /// </summary>
        /// <param name="videoPath">Initial video path (.lynda file)</param>
        /// <param name="decryptedFilePath">Full decrypted video path</param>
        /// <returns>boolean value, true for succesful conversion</returns>
        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
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{58EBF661-A637-45AE-956C-5EC1A602EDC3}</ProjectGuid>
    <OutputType>Exe</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>LyndaDecryptor</RootNamespace>
    <AssemblyName>LyndaDecryptor</AssemblyName>
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
    <TargetFrameworkProfile />
    <NuGetPackageImportStamp>
    </NuGetPackageImportStamp>
    <PublishUrl>publish\</PublishUrl>
    <Install>true</Install>
    <InstallFrom>Disk</InstallFrom>
    <UpdateEnabled>false</UpdateEnabled>
    <UpdateMode>Foreground</UpdateMode>
    <UpdateInterval>7</UpdateInterval>
    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
    <UpdatePeriodically>false</UpdatePeriodically>
    <UpdateRequired>false</UpdateRequired>
    <MapFileExtensions>true</MapFileExtensions>
    <ApplicationRevision>0</ApplicationRevision>
    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
    <IsWebBootstrapper>false</IsWebBootstrapper>
    <UseApplicationTrust>false</UseApplicationTrust>
    <BootstrapperEnabled>true</BootstrapperEnabled>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugType>none</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <UseVSHostingProcess>false</UseVSHostingProcess>
  </PropertyGroup>
  <PropertyGroup>
    <StartupObject>LyndaDecryptor.Program</StartupObject>
  </PropertyGroup>
  <PropertyGroup>
    <NoWin32Manifest>true</NoWin32Manifest>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'TestDB|AnyCPU'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\TestDB\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
    <Prefer32Bit>true</Prefer32Bit>
  </PropertyGroup>
  <PropertyGroup>
    <ApplicationIcon>lynda-logo.ico</ApplicationIcon>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System">
      <HintPath>..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.dll</HintPath>
    </Reference>
    <Reference Include="System.Core">
      <HintPath>..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.Core.dll</HintPath>
    </Reference>
    <Reference Include="System.Data.SQLite, Version=1.0.102.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
      <HintPath>..\packages\System.Data.SQLite.Core.1.0.102.0\lib\net45\System.Data.SQLite.dll</HintPath>
      <Private>True</Private>
    </Reference>
    <Reference Include="System.Data">
      <HintPath>..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.Data.dll</HintPath>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="CaptionToSrt.cs" />
    <Compile Include="Decryptor.cs" />
    <Compile Include="DecryptorOptions.cs" />
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="Utils.cs" />
    <Compile Include="VideoInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config">
      <SubType>Designer</SubType>
    </None>
    <None Include="packages.config">
      <SubType>Designer</SubType>
    </None>
  </ItemGroup>
  <ItemGroup>
    <Content Include="lynda-logo.ico" />
  </ItemGroup>
  <ItemGroup>
    <BootstrapperPackage Include=".NETFramework,Version=v4.5">
      <Visible>False</Visible>
      <ProductName>Microsoft .NET Framework 4.5 %28x86 and x64%29</ProductName>
      <Install>true</Install>
    </BootstrapperPackage>
    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
      <Visible>False</Visible>
      <ProductName>.NET Framework 3.5 SP1</ProductName>
      <Install>false</Install>
    </BootstrapperPackage>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Import Project="..\packages\System.Data.SQLite.Core.1.0.102.0\build\net45\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.102.0\build\net45\System.Data.SQLite.Core.targets')" />
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>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}".</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.102.0\build\net45\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.102.0\build\net45\System.Data.SQLite.Core.targets'))" />
  </Target>
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
</Project>

================================================
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();
        }

        /// <summary>
        /// Print usage instructions
        /// </summary>
        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
================================================
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="System.Data.SQLite" version="1.0.102.0" targetFramework="net45" />
  <package id="System.Data.SQLite.Core" version="1.0.102.0" targetFramework="net45" />
</packages>

================================================
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<string> args = new List<string>();
            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<string> args = new List<string>();
            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<string> args = new List<string>();
            args.Add("/F");
            args.Add("TestFiles\\88067_2195c10678b4f73e34795af641ad1ecc.lynda");

            Utils.ParseCommandLineArgs(args.ToArray());
        }

        [TestMethod, ExpectedException(typeof(IOException))]
        public void OutputFileAlreadyExistShouldFailWithException()
        {
            List<string> args = new List<string>();
            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<string> args = new List<string>();
            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
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{F8726170-D017-4517-AFB9-B1870A282E81}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>LyndaDecryptorTests</RootNamespace>
    <AssemblyName>LyndaDecryptorTests</AssemblyName>
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
    <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
    <IsCodedUITest>False</IsCodedUITest>
    <TestProjectType>UnitTest</TestProjectType>
    <TargetFrameworkProfile />
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
  </ItemGroup>
  <Choose>
    <When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
      <ItemGroup>
        <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
      </ItemGroup>
    </When>
    <Otherwise>
      <ItemGroup>
        <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework">
          <Private>False</Private>
        </Reference>
      </ItemGroup>
    </Otherwise>
  </Choose>
  <ItemGroup>
    <Compile Include="CommandLineParserTest.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="DecryptionTest.cs" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\LyndaDecryptor\LyndaDecryptor.csproj">
      <Project>{58ebf661-a637-45ae-956c-5ec1a602edc3}</Project>
      <Name>LyndaDecryptor</Name>
    </ProjectReference>
  </ItemGroup>
  <ItemGroup>
    <None Include="TestDB\db_de.sqlite">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Include="TestFiles\88067_2195c10678b4f73e34795af641ad1ecc.lynda">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Include="TestFiles\88071_4650ab745df849fd96f1fdbdb016a5e6.lynda">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Include="TestFiles\88087_44c891cdef18ee48d968a018afe3befd.lynda">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Include="TestFiles\88089_191d15e08d44c0e84f2237f433c67a67.lynda">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
  <Choose>
    <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
      <ItemGroup>
        <Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
          <Private>False</Private>
        </Reference>
        <Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
          <Private>False</Private>
        </Reference>
        <Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
          <Private>False</Private>
        </Reference>
        <Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
          <Private>False</Private>
        </Reference>
      </ItemGroup>
    </When>
  </Choose>
  <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
</Project>

================================================
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**

---

![Lynda.com](https://upload.wikimedia.org/wikipedia/commons/thumb/5/5f/Video2brain.jpg/800px-Video2brain.jpg)
Download .txt
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
Download .txt
SYMBOL INDEX (51 symbols across 8 files)

FILE: LyndaDecryptor/CaptionToSrt.cs
  class CaptionToSrt (line 12) | class CaptionToSrt
    method CaptionToSrt (line 18) | public CaptionToSrt(string afilePath)
    method stringToBytes (line 52) | public byte[] stringToBytes(string text)
    type timestampFormat (line 58) | public enum timestampFormat
    class Timestamp (line 65) | public class Timestamp
      method Timestamp (line 70) | public Timestamp(int _position, timestampFormat _type)
    method findTimestamps (line 78) | public Timestamp[] findTimestamps(byte[] inputByteArray)
    class Subtitle (line 179) | public class Subtitle
      method Subtitle (line 224) | public Subtitle(byte[] subtitleData, timestampFormat ts_format)
      method setEndTimestamp (line 231) | public void setEndTimestamp(string endtime)
      method extractStartTimestamp (line 237) | private void extractStartTimestamp(byte[] subtitleData, timestampFor...
      method extractText (line 262) | private void extractText(byte[] subtitleData)
      method trimNonprintable (line 297) | private byte[] trimNonprintable(byte[] textData)
      method enforce_CRLF_linebreaks (line 342) | private byte[] enforce_CRLF_linebreaks(byte[] text)
      method convertToShortFormat (line 380) | private string convertToShortFormat(string timestamp)
      method bytesToString (line 395) | private string bytesToString(byte[] bytes, int length)
      method stringToBytes (line 400) | private byte[] stringToBytes(string text)
    method parseCaptionData (line 407) | public Subtitle[] parseCaptionData(byte[] captionData, Timestamp[] tim...
    method convertToSrt (line 465) | public bool convertToSrt()
    method buildSrt (line 507) | private bool buildSrt(Subtitle[] subtitleArray, string path)

FILE: LyndaDecryptor/Decryptor.cs
  class Decryptor (line 15) | public class Decryptor
    method Decryptor (line 40) | public Decryptor()
    method Decryptor (line 54) | public Decryptor(DecryptorOptions options) : this()
    method InitDecryptor (line 68) | public void InitDecryptor(string EncryptionKey)
    method InitDB (line 86) | public bool InitDB(string databasePath)
    method DecryptAll (line 124) | public void DecryptAll(string folderPath, string outputFolder = "")
    method Decrypt (line 201) | public void Decrypt(string encryptedFilePath, string decryptedFilePath)
    method GetVideoInfoFromDB (line 319) | private VideoInfo GetVideoInfoFromDB(string courseID, string videoID)
    method CleanPath (line 374) | private string CleanPath(string path)
    method ConvertSub (line 389) | private Boolean ConvertSub(string videoPath, string decryptedFilePath,...

FILE: LyndaDecryptor/DecryptorOptions.cs
  class DecryptorOptions (line 3) | public class DecryptorOptions

FILE: LyndaDecryptor/Program.cs
  type Mode (line 7) | public enum Mode
  class Program (line 14) | public class Program
    method Main (line 16) | static void Main(string[] args)
    method Usage (line 59) | static void Usage()

FILE: LyndaDecryptor/Utils.cs
  class Utils (line 10) | public static class Utils
    method Utils (line 17) | static Utils()
    method WriteToConsole (line 22) | public static void WriteToConsole(string Text, ConsoleColor color = Co...
    method ParseCommandLineArgs (line 32) | public static DecryptorOptions ParseCommandLineArgs(string[] args)

FILE: LyndaDecryptor/VideoInfo.cs
  class VideoInfo (line 9) | public class VideoInfo

FILE: LyndaDecryptorTests/CommandLineParserTest.cs
  class CommandLineParserTest (line 12) | [TestClass]
    method TestFileMode (line 15) | [TestMethod]
    method TestFolderMode (line 54) | [TestMethod]
    method MissingOutputArgShouldFailWithException (line 91) | [TestMethod, ExpectedException(typeof(FormatException))]
    method OutputFileAlreadyExistShouldFailWithException (line 101) | [TestMethod, ExpectedException(typeof(IOException))]
    method TestMissingFolder (line 112) | [TestMethod, ExpectedException(typeof(FileNotFoundException))]

FILE: LyndaDecryptorTests/DecryptionTest.cs
  class DecryptionTest (line 8) | [TestClass]
    method TestSingleDecryption (line 11) | [TestMethod]
    method TestSingleDecryptionWithDB (line 33) | [TestMethod]
    method TestFolderDecryption (line 39) | [TestMethod]
    method TestFolderDecryptionWithDB (line 45) | [TestMethod]
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (83K chars).
[
  {
    "path": ".gitignore",
    "chars": 3748,
    "preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User"
  },
  {
    "path": "LICENSE",
    "chars": 1079,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Tobias Becht\n\nPermission is hereby granted, free of charge, to any person obta"
  },
  {
    "path": "LyndaDecryptor/App.config",
    "chars": 529,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n  <configSections>\n  </configSections>\n  <startup>\n    <supported"
  },
  {
    "path": "LyndaDecryptor/CaptionToSrt.cs",
    "chars": 25330,
    "preview": "using System;\r\nusing System.Collections;\r\nusing System.Collections.Generic;\r\nusing System.IO;\r\nusing System.Linq;\r\nusin"
  },
  {
    "path": "LyndaDecryptor/Decryptor.cs",
    "chars": 17623,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Data.SQLite;\nusing System.IO;\nusing System.Linq;\nusing Sys"
  },
  {
    "path": "LyndaDecryptor/DecryptorOptions.cs",
    "chars": 469,
    "preview": "namespace LyndaDecryptor\n{\n    public class DecryptorOptions\n    {\n        public Mode UsageMode { get; set; }\n        "
  },
  {
    "path": "LyndaDecryptor/LyndaDecryptor.csproj",
    "chars": 6524,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"14.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.micros"
  },
  {
    "path": "LyndaDecryptor/Program.cs",
    "chars": 2773,
    "preview": "using System;\r\n\r\nusing static LyndaDecryptor.Utils;\r\n\r\nnamespace LyndaDecryptor\r\n{\r\n    public enum Mode\r\n    {\r\n      "
  },
  {
    "path": "LyndaDecryptor/Properties/AssemblyInfo.cs",
    "chars": 1500,
    "preview": "using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// Allgemeine In"
  },
  {
    "path": "LyndaDecryptor/Utils.cs",
    "chars": 4099,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Thr"
  },
  {
    "path": "LyndaDecryptor/VideoInfo.cs",
    "chars": 510,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
  },
  {
    "path": "LyndaDecryptor/packages.config",
    "chars": 231,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"System.Data.SQLite\" version=\"1.0.102.0\" targetFramewor"
  },
  {
    "path": "LyndaDecryptorTests/CommandLineParserTest.cs",
    "chars": 4055,
    "preview": "#define TEST\n\nusing System;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing LyndaDecryptor"
  },
  {
    "path": "LyndaDecryptorTests/DecryptionTest.cs",
    "chars": 1312,
    "preview": "using System;\nusing System.IO;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing LyndaDecryptor;\n\nnamespace Lyn"
  },
  {
    "path": "LyndaDecryptorTests/LyndaDecryptorTests.csproj",
    "chars": 5423,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"14.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.micros"
  },
  {
    "path": "LyndaDecryptorTests/Properties/AssemblyInfo.cs",
    "chars": 1512,
    "preview": "using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// Allgemeine In"
  },
  {
    "path": "README.md",
    "chars": 1876,
    "preview": "# Video2Brain (Lynda-Decryptor)\n\nYou may have noticed that if you want to download Videos from Video2Brain, the only way"
  }
]

// ... and 4 more files (download for full content)

About this extraction

This page contains the full source code of the h4ck-rOOt/Lynda-Decryptor GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (76.8 KB), approximately 16.3k tokens, and a symbol index with 51 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!