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