├── .travis.yml ├── DecryptPluralSightVideos ├── icon.ico ├── Model │ ├── ClipTranscript.cs │ ├── Course.cs │ ├── Clip.cs │ └── Module.cs ├── Encryption │ ├── IPsStream.cs │ ├── VideoEncryption.cs │ ├── PsStream.cs │ ├── VirtualFileCache.cs │ └── VirtualFileStream.cs ├── packages.config ├── Option │ ├── DecryptorOptions.cs │ └── Utils.cs ├── Properties │ └── AssemblyInfo.cs ├── App.config ├── Program.cs ├── DecryptPluralSightVideos.csproj └── Decryptor.cs ├── DecryptPluralSightVideos.sln ├── .gitattributes ├── README.md └── .gitignore /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | solution: DecryptPluralSightVideos.sln 3 | install: 4 | - nuget restore DecryptPluralSightVideos.sln 5 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibAhamed/Decrypt-PluralSight-Videos/HEAD/DecryptPluralSightVideos/icon.ico -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Model/ClipTranscript.cs: -------------------------------------------------------------------------------- 1 | namespace DecryptPluralSightVideos.Model 2 | { 3 | public class ClipTranscript 4 | { 5 | public int StartTime { get; set; } 6 | public int EndTime { get; set; } 7 | public string Text { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Model/Course.cs: -------------------------------------------------------------------------------- 1 | namespace DecryptPluralSightVideos.Model 2 | { 3 | public class Course 4 | { 5 | public string CourseName { get; set; } 6 | public string CourseTitle { get; set; } 7 | public int HasTranscript { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Model/Clip.cs: -------------------------------------------------------------------------------- 1 | namespace DecryptPluralSightVideos.Model 2 | { 3 | public class Clip 4 | { 5 | public string ClipName { get; set; } 6 | public string ClipTitle { get; set; } 7 | public int ClipId { get; set; } 8 | public int ClipIndex { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Model/Module.cs: -------------------------------------------------------------------------------- 1 | namespace DecryptPluralSightVideos.Model 2 | { 3 | public class Module 4 | { 5 | public string ModuleName { get; set; } 6 | public int ModuleId { get; set; } 7 | public string ModuleTitle { get; set; } 8 | public string AuthorHandle { get; set; } 9 | public int ModuleIndex { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Encryption/IPsStream.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace DecryptPluralSightVideos.Encryption 4 | { 5 | public interface IPsStream 6 | { 7 | long Length { get; } 8 | 9 | int BlockSize { get; } 10 | 11 | void Seek(int offset, SeekOrigin begin); 12 | 13 | int Read(byte[] pv, int i, int count); 14 | 15 | void Dispose(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Option/DecryptorOptions.cs: -------------------------------------------------------------------------------- 1 | namespace DecryptPluralSightVideos.Option 2 | { 3 | public class DecryptorOptions 4 | { 5 | public bool UseDatabase { get; set; } 6 | public bool UseOutputFolder { get; set; } 7 | public bool RemoveFolderAfterDecryption { get; set; } 8 | public bool UsageCommand { get; set; } 9 | public bool CreateTranscript { get; set; } 10 | 11 | public string InputPath { get; set; } 12 | public string DatabasePath { get; set; } 13 | public string OutputPath { get; set; } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Encryption/VideoEncryption.cs: -------------------------------------------------------------------------------- 1 | namespace DecryptPluralSightVideos.Encryption 2 | { 3 | public class VideoEncryption 4 | { 5 | public static void XorBuffer(byte[] buff, int length, long position) 6 | { 7 | string str1 = "pluralsight"; 8 | string str2 = "\x0006?zY¢\x00B2\x0085\x009FL\x00BEî0Ö.ì\x0017#©>Å£Q\x0005¤°\x00018Þ^\x008Eú\x0019Lqß'\x009D\x0003ßE\x009EM\x0080'x:\0~\x00B9\x0001ÿ 4\x00B3õ\x0003çÊ\x000EAË\x00BC\x0090è\x009Eî~\x008B\x009Aâ\x001B¸UD<\x007FKç*\x001Döæ7H\v\x0015Arý*v÷%Âþ\x00BEä;pü"; 9 | for (int index = 0; index < length; ++index) 10 | { 11 | byte num = (byte)((ulong)((int)str1[(int)((position + (long)index) % (long)str1.Length)] ^ (int)str2[(int)((position + (long)index) % (long)str2.Length)]) ^ (ulong)((position + (long)index) % 251L)); 12 | buff[index] = (byte)((uint)buff[index] ^ (uint)num); 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DecryptPluralSightVideos", "DecryptPluralSightVideos\DecryptPluralSightVideos.csproj", "{1D117714-A0FB-4186-8E2C-349B9BDB45E3}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {1D117714-A0FB-4186-8E2C-349B9BDB45E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {1D117714-A0FB-4186-8E2C-349B9BDB45E3}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {1D117714-A0FB-4186-8E2C-349B9BDB45E3}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {1D117714-A0FB-4186-8E2C-349B9BDB45E3}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Encryption/PsStream.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace DecryptPluralSightVideos.Encryption 4 | { 5 | public class PsStream : IPsStream 6 | { 7 | private readonly Stream fileStream; 8 | private long _length; 9 | 10 | public long Length { 11 | get 12 | { 13 | return this._length; 14 | } 15 | } 16 | public int BlockSize { 17 | get 18 | { 19 | return 262144; 20 | } 21 | } 22 | public PsStream(string filenamePath) 23 | { 24 | this.fileStream = (Stream)File.Open(filenamePath, FileMode.Open, FileAccess.Read, FileShare.Read); 25 | this._length = new FileInfo(filenamePath).Length; 26 | } 27 | 28 | public void Seek(int offset, SeekOrigin begin) 29 | { 30 | if (this._length <= 0L) 31 | return; 32 | this.fileStream.Seek((long)offset, begin); 33 | } 34 | 35 | public int Read(byte[] pv, int i, int count) 36 | { 37 | if (this._length <= 0L) 38 | return 0; 39 | return this.fileStream.Read(pv, i, count); 40 | } 41 | 42 | public void Dispose() 43 | { 44 | this._length = 0L; 45 | this.fileStream.Dispose(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Encryption/VirtualFileCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using System.Threading.Tasks; 5 | 6 | namespace DecryptPluralSightVideos.Encryption 7 | { 8 | class VirtualFileCache : IDisposable 9 | { 10 | private readonly IPsStream encryptedVideoFile; 11 | 12 | public long Length 13 | { 14 | get 15 | { 16 | return this.encryptedVideoFile.Length; 17 | } 18 | } 19 | 20 | public VirtualFileCache(string encryptedVideoFilePath) 21 | { 22 | this.encryptedVideoFile = (IPsStream)new PsStream(encryptedVideoFilePath); 23 | } 24 | 25 | public VirtualFileCache(IPsStream stream) 26 | { 27 | this.encryptedVideoFile = stream; 28 | } 29 | 30 | public void Read(byte[] pv, int offset, int count, IntPtr pcbRead) 31 | { 32 | if (this.Length == 0L) 33 | return; 34 | this.encryptedVideoFile.Seek(offset, SeekOrigin.Begin); 35 | int length = this.encryptedVideoFile.Read(pv, 0, count); 36 | VideoEncryption.XorBuffer(pv, length, (long)offset); 37 | if (!(IntPtr.Zero != pcbRead)) 38 | return; 39 | Marshal.WriteIntPtr(pcbRead, new IntPtr(length)); 40 | } 41 | 42 | public void Dispose() 43 | { 44 | throw new NotImplementedException(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("DecryptPluralSightVideos")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("DecryptPluralSightVideos")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1d117714-a0fb-4186-8e2c-349b9bdb45e3")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.1")] 36 | [assembly: AssemblyFileVersion("1.0.0.1")] 37 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 30 | 31 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decrypt PluralSight Videos[![Build Status](https://travis-ci.org/vinhloc1996/DecryptPluralSightVideos.svg?branch=master)](https://travis-ci.org/vinhloc1996/DecryptPluralSightVideos) 2 | When you donwload a video to watch offline through plural sight app, the video was encrypted and can only be watched by their app. This tool has been made to decrypt the videos, it will decrypt the video, rearrange the course folder name, decrypt module folder name, and for all, decrypt video of plural sight (.psv). 3 | 4 | # Getting Started 5 | This tool requires .Net Framework `4.5.2` or above. 6 | 7 | ## Installing 8 | * Download the latest binary from [here](https://github.com/vinhloc1996/DecryptPluralSightVideos/releases/download/1.0.0.0/DecryptPluralSightVideos_v1.0.zip). 9 | * Extract the zip file, open commandline and navigate to extracted folder containing DecryptPluralSightVideos.exe. 10 | * For more information about flags using on this tool, execute this command in the commandline ``DecryptPluralSightVideos /HELP``. 11 | 1. Note: All the flag in this tool is ``case-insensitive``. 12 | 2. Note: Usually the Pluralsight app will put the downloaded courses in the path: 13 | `C:\Users\\AppData\Local\Pluralsight\courses` 14 | 15 | ## Example Execute Commands 16 | - ```DecryptPluralSightVideos /F "C:\Users\MyUser\AppData\Local\Pluralsight\courses" /DB "C:\Users\MyUser\AppData\Local\Pluralsight\pluralsight.db" /TRANS /RM /OUT "E:\Course"``` 17 | ### Explainations 18 | - `/F [PATH]` will define the location of downloaded courses that you has been passed right behind it **(You don't need to point to any specified course, the tool will be decrypted all courses it found in your `courses path`)**. 19 | - `/DB [PATH]` will define the location of the database file, it will locate at Pluralsight folder. 20 | - `/TRANS` will create a subtitles file (.srt) of each videos **(Some course will not have the subtitiles)**. 21 | - `/RM` will remove the course in the database file. After course is delete in database file, open the Pluralsight app and the the courses folder will be deleted automatic. 22 | - `/OUT [PATH]` will define the path that you want to put the decrypted courses. **If you don't use this flag, the decrypted courses will be placed in the same location with encrypted courses.** 23 | 24 | ## Troubleshooting 25 | 1. In case you experience a "Path too long" exception, try to use an UNC path for export. You can share your local hard drive and connect to it using an UNC path. This way, Windows will use its Unicode API and in turn support path lengths with to 32k characters. 26 | - For example, if your export folder is ```C:\Export```, share the drive with share name ```C``` and use the following export path instead: ```\\localhost\C\Export```. 27 | 28 | # Author 29 | - Aaqib Wiki 30 | 31 | # Version 32 | - This current version is `1.1.0.1`. 33 | 34 | # Refference 35 | - This tool has been made by myself but some functions about running commandline tools or style of code that I refer from [Lynda-Decryptor](https://github.com/h4ck-rOOt/Lynda-Decryptor). 36 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using DecryptPluralSightVideos.Option; 9 | using static DecryptPluralSightVideos.Option.Utils; 10 | 11 | namespace DecryptPluralSightVideos 12 | { 13 | class Program 14 | { 15 | static void Main(string[] args) 16 | { 17 | Decryptor decryptor; 18 | DecryptorOptions decryptorOptions = new DecryptorOptions(); 19 | 20 | try 21 | { 22 | decryptorOptions = ParseCommandLineArgs(args); 23 | decryptor = new Decryptor(decryptorOptions); 24 | 25 | if (decryptorOptions.UsageCommand) 26 | { 27 | HelpCommand(); 28 | goto End; 29 | } 30 | 31 | if (!string.IsNullOrWhiteSpace(decryptorOptions.InputPath)) 32 | { 33 | // Time watch 34 | // var watch = System.Diagnostics.Stopwatch.StartNew(); 35 | // Decrypt folders with input path and output path 36 | decryptor.DecryptAllFolders(decryptorOptions.InputPath, decryptorOptions.OutputPath); 37 | // watch.Stop(); 38 | // var elapsedMs = watch.ElapsedMilliseconds; 39 | // WriteToConsole("Time: " + elapsedMs); 40 | if (decryptorOptions.RemoveFolderAfterDecryption) 41 | { 42 | WriteToConsole("Removing course in database after decryption." + Environment.NewLine, 43 | ConsoleColor.Yellow); 44 | foreach (string coursePath in Directory.GetDirectories(decryptorOptions.InputPath, "*", 45 | SearchOption.TopDirectoryOnly)) 46 | { 47 | decryptor.RemoveCourseInDb(coursePath); 48 | WriteToConsole("Course " + decryptor.GetFolderName(coursePath) + " has been deleted in database." + Environment.NewLine, 49 | ConsoleColor.Yellow); 50 | } 51 | } 52 | } 53 | else 54 | { 55 | WriteToConsole("\t/F\t flag is mandatory. Please you the /HELP flag to more information."); 56 | } 57 | } 58 | catch (Exception exception) 59 | { 60 | WriteToConsole( 61 | "Error occured: " + exception.Message + "\n" + exception.StackTrace + Environment.NewLine, 62 | ConsoleColor.Red); 63 | WriteToConsole( 64 | "Please use\t/HELP\tflag to know more about other commands or contact with the publisher."); 65 | } 66 | 67 | End: 68 | WriteToConsole(Environment.NewLine + "Press any key to exit the program..."); 69 | Console.ReadKey(); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Encryption/VirtualFileStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using System.Runtime.InteropServices.ComTypes; 5 | using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG; 6 | 7 | namespace DecryptPluralSightVideos.Encryption 8 | { 9 | internal class VirtualFileStream : IStream, IDisposable 10 | { 11 | private readonly object _Lock = new object(); 12 | private long position; 13 | private VirtualFileCache _Cache; 14 | 15 | public VirtualFileStream(string EncryptedVideoFilePath) 16 | { 17 | this._Cache = new VirtualFileCache(EncryptedVideoFilePath); 18 | } 19 | 20 | private VirtualFileStream(VirtualFileCache Cache) 21 | { 22 | this._Cache = Cache; 23 | } 24 | 25 | public void Read(byte[] pv, int cb, IntPtr pcbRead) 26 | { 27 | if (this.position < 0L || this.position > this._Cache.Length) 28 | { 29 | Marshal.WriteIntPtr(pcbRead, new IntPtr(0)); 30 | } 31 | else 32 | { 33 | lock (this._Lock) 34 | { 35 | this._Cache.Read(pv, (int)this.position, cb, pcbRead); 36 | this.position = this.position + pcbRead.ToInt64(); 37 | } 38 | } 39 | } 40 | public void Write(byte[] pv, int cb, IntPtr pcbWritten) 41 | { 42 | throw new NotImplementedException(); 43 | } 44 | 45 | public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition) 46 | { 47 | SeekOrigin seekOrigin = (SeekOrigin)dwOrigin; 48 | lock (this._Lock) 49 | { 50 | switch (seekOrigin) 51 | { 52 | case SeekOrigin.Begin: 53 | this.position = dlibMove; 54 | break; 55 | case SeekOrigin.Current: 56 | this.position = this.position + dlibMove; 57 | break; 58 | case SeekOrigin.End: 59 | this.position = this._Cache.Length + dlibMove; 60 | break; 61 | } 62 | if (!(IntPtr.Zero != plibNewPosition)) 63 | return; 64 | Marshal.WriteInt64(plibNewPosition, this.position); 65 | } 66 | } 67 | 68 | public void SetSize(long libNewSize) 69 | { 70 | throw new NotImplementedException(); 71 | } 72 | 73 | public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) 74 | { 75 | throw new NotImplementedException(); 76 | } 77 | 78 | public void Commit(int grfCommitFlags) 79 | { 80 | throw new NotImplementedException(); 81 | } 82 | 83 | public void Revert() 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | 88 | public void LockRegion(long libOffset, long cb, int dwLockType) 89 | { 90 | throw new NotImplementedException(); 91 | } 92 | 93 | public void UnlockRegion(long libOffset, long cb, int dwLockType) 94 | { 95 | throw new NotImplementedException(); 96 | } 97 | 98 | public void Stat(out STATSTG pstatstg, int grfStatFlag) 99 | { 100 | pstatstg = new System.Runtime.InteropServices.ComTypes.STATSTG(); 101 | pstatstg.cbSize = this._Cache.Length; 102 | } 103 | 104 | public void Clone(out IStream ppstm) 105 | { 106 | ppstm = (IStream)new VirtualFileStream(this._Cache); 107 | } 108 | 109 | public void Dispose() 110 | { 111 | this._Cache.Dispose(); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Option/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace DecryptPluralSightVideos.Option 5 | { 6 | public class Utils 7 | { 8 | private static ConsoleColor color_default; 9 | private static object console_lock = new object(); 10 | 11 | static Utils() 12 | { 13 | color_default = Console.ForegroundColor; 14 | } 15 | 16 | public static void WriteToConsole(string Text, ConsoleColor color = ConsoleColor.Gray) 17 | { 18 | lock (console_lock) 19 | { 20 | Console.ForegroundColor = color; 21 | Console.WriteLine(Text); 22 | Console.ForegroundColor = color_default; 23 | } 24 | } 25 | 26 | public static void HelpCommand() 27 | { 28 | WriteToConsole("This tool is published by Loc Nguyen and shared on J2Team"); 29 | WriteToConsole(@"Source code of this tool published on: https://github.com/vinhloc1996/DecryptPluralSightVideos"); 30 | 31 | WriteToConsole(Environment.NewLine + Environment.NewLine + "Flags: "); 32 | WriteToConsole("\t/F [PATH] Source path contains all downloaded courses. (Mandatory)"); 33 | WriteToConsole("\t/RM\tRemoves courses in databases after decryption is complete. (Optional)"); 34 | WriteToConsole("\t/DB [PATH] Use Database to rename folder course, module... (Mandatory)"); 35 | WriteToConsole("\t/OUT [PATH] Specifies an output directory instead of using the same source path. (Optional)"); 36 | WriteToConsole("\t/TRANS\tGenerate subtitles file (.srt) if the course are supported. (Optional)"); 37 | WriteToConsole("\t/HELP\tSee usage of other commands. (Optional)"); 38 | WriteToConsole("**Note**\nIf you want to use /RM flag, please make sure the output path that not the same with the source path.\n", ConsoleColor.Yellow); 39 | } 40 | 41 | public static DecryptorOptions ParseCommandLineArgs(string[] args) 42 | { 43 | DecryptorOptions options = new DecryptorOptions(); 44 | int index = 0; 45 | int length = args.Length; 46 | 47 | foreach (string arg in args) 48 | { 49 | if (string.IsNullOrWhiteSpace(arg)) 50 | { 51 | index++; 52 | continue; 53 | } 54 | 55 | switch (arg.ToUpper()) 56 | { 57 | case "/F": // All Folders Mode 58 | if (length - 1 > index) 59 | { 60 | options.InputPath = args[index + 1]; 61 | WriteToConsole("Start to decrypt all courses...", ConsoleColor.Yellow); 62 | } 63 | else 64 | { 65 | WriteToConsole("The directory path is missing..." + Environment.NewLine, 66 | ConsoleColor.Red); 67 | throw new FileNotFoundException( 68 | "Directory path is missing or specified directory was not found!"); 69 | } 70 | break; 71 | 72 | case "/DB": // Use Database 73 | options.UseDatabase = true; 74 | 75 | if (length - 1 > index) 76 | options.DatabasePath = args[index + 1]; 77 | break; 78 | 79 | case "/RM": // Remove encrypted folder(s) after decryption 80 | options.RemoveFolderAfterDecryption = true; 81 | break; 82 | 83 | case "/OUT": // Output Folder Path 84 | options.UseOutputFolder = true; 85 | 86 | if (args.Length - 1 > index) 87 | options.OutputPath = args[index + 1]; 88 | break; 89 | 90 | case "/TRANS": // Create Transcript If course supportted 91 | options.CreateTranscript = true; 92 | break; 93 | 94 | case "/HELP": // Open command explaination 95 | options.UsageCommand = true; 96 | break; 97 | } 98 | 99 | index++; 100 | } 101 | 102 | return options; 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /DecryptPluralSightVideos/DecryptPluralSightVideos.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {1D117714-A0FB-4186-8E2C-349B9BDB45E3} 9 | Exe 10 | DecryptPluralSightVideos 11 | DecryptPluralSightVideos 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | publish\ 18 | true 19 | Disk 20 | false 21 | Foreground 22 | 7 23 | Days 24 | false 25 | false 26 | true 27 | 0 28 | 1.0.0.%2a 29 | false 30 | false 31 | true 32 | 33 | 34 | AnyCPU 35 | true 36 | full 37 | false 38 | bin\Debug\ 39 | DEBUG;TRACE 40 | prompt 41 | 4 42 | 43 | 44 | AnyCPU 45 | pdbonly 46 | true 47 | bin\Release\ 48 | TRACE 49 | prompt 50 | 4 51 | 52 | 53 | icon.ico 54 | 55 | 56 | 57 | ..\packages\EntityFramework.6.4.0\lib\net45\EntityFramework.dll 58 | 59 | 60 | ..\packages\EntityFramework.6.4.0\lib\net45\EntityFramework.SqlServer.dll 61 | 62 | 63 | 64 | 65 | 66 | 67 | ..\packages\System.Data.SQLite.Core.1.0.105.2\lib\net451\System.Data.SQLite.dll 68 | 69 | 70 | ..\packages\System.Data.SQLite.EF6.1.0.105.2\lib\net451\System.Data.SQLite.EF6.dll 71 | 72 | 73 | ..\packages\System.Data.SQLite.Linq.1.0.105.2\lib\net451\System.Data.SQLite.Linq.dll 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | False 105 | Microsoft .NET Framework 4.5.2 %28x86 and x64%29 106 | true 107 | 108 | 109 | False 110 | .NET Framework 3.5 SP1 111 | false 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /DecryptPluralSightVideos/Decryptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Runtime.InteropServices.ComTypes; 7 | using System.Security.Cryptography; 8 | using System.Data.SQLite; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using DecryptPluralSightVideos.Encryption; 13 | using DecryptPluralSightVideos.Model; 14 | using DecryptPluralSightVideos.Option; 15 | using static System.String; 16 | using static DecryptPluralSightVideos.Option.Utils; 17 | using SearchOption = System.IO.SearchOption; 18 | using System.Text.RegularExpressions; 19 | 20 | namespace DecryptPluralSightVideos 21 | { 22 | public class Decryptor 23 | { 24 | #region Fields 25 | 26 | private VirtualFileStream playingFileStream; 27 | private IStream iStream; 28 | List InvalidFileCharacters = new List(); 29 | private SQLiteConnection DatabaseConnection; 30 | public DecryptorOptions Options = new DecryptorOptions(); 31 | private ConsoleColor color_default; 32 | List TaskList = new List(); 33 | SemaphoreSlim Semaphore = new SemaphoreSlim(5); 34 | object SemaphoreLock = new object(); 35 | 36 | #endregion 37 | 38 | 39 | /// 40 | /// Constructor Of Decryptor Class. Init invalid characters of path and console colors. 41 | /// 42 | public Decryptor() 43 | { 44 | InvalidFileCharacters.AddRange(Path.GetInvalidFileNameChars()); 45 | InvalidFileCharacters.AddRange(new char[] {':', '?', '"', '\\', '/'}); 46 | 47 | color_default = Console.ForegroundColor; 48 | } 49 | 50 | /// 51 | /// Constructor Of Decryptor Class. Init options from the console. 52 | /// 53 | /// Decryptor Options 54 | public Decryptor(DecryptorOptions options) : this() 55 | { 56 | Options = options; 57 | 58 | if (options.UseDatabase) 59 | Options.UseDatabase = InitDB(options.DatabasePath); 60 | } 61 | 62 | /// 63 | /// Clean the input string and remove all invalid chars 64 | /// 65 | /// input path 66 | /// 67 | private string CleanPath(string path) 68 | { 69 | //InvalidFile is a superset of InvalidPath so (for this purpose) OK to use InvalidFileCharacters against the path. Idea being to replace any invalid characters, 70 | //or sequence of characters with a single " - " string. Makes for easier reading. e.g. "Programming - Application" rather than "Programming- Application" 71 | path = Regex.Replace(String.Join("|", path.Split(InvalidFileCharacters.ToArray())), " *\\|+ *", "|"); // Based on Path.GetInvalidFileNameChars() 72 | return Regex.Replace(path, "\\|+", " - "); // Formatting. 73 | } 74 | 75 | /// 76 | /// Encrypt two string to become folder name. 77 | /// 78 | /// Name of Module 79 | /// Name of Author in the course 80 | /// String has been encrypted 81 | public string ModuleHash(string moduleName, string moduleAuthorName) 82 | { 83 | string s = moduleName + "|" + moduleAuthorName; 84 | using (MD5 md5 = MD5.Create()) 85 | return Convert.ToBase64String(md5.ComputeHash(Encoding.UTF8.GetBytes(s))).Replace('/', '_'); 86 | } 87 | 88 | /// 89 | /// Decryption and rename course, module path. 90 | /// 91 | /// Source path contains all courses 92 | /// Destination of output course 93 | public void DecryptAllFolders(string folderPath, string outputFolder = "") 94 | { 95 | if (!Directory.Exists(folderPath)) 96 | { 97 | throw new DirectoryNotFoundException(); 98 | } 99 | 100 | if (IsNullOrEmpty(outputFolder)) 101 | { 102 | outputFolder = folderPath; 103 | } 104 | else 105 | { 106 | if (!Directory.Exists(outputFolder)) 107 | { 108 | Directory.CreateDirectory(outputFolder); 109 | } 110 | } 111 | 112 | foreach (string coursePath in Directory.GetDirectories(folderPath, "*", 113 | SearchOption.TopDirectoryOnly)) 114 | { 115 | var course = GetCourseFromDb(coursePath); 116 | 117 | if (course != null) 118 | { 119 | 120 | // Create new course path with the output path 121 | var newCoursePath = Path.Combine(outputFolder, CleanPath(course.CourseTitle)); 122 | 123 | DirectoryInfo courseInfo = Directory.Exists(newCoursePath) 124 | ? new DirectoryInfo(newCoursePath) 125 | : Directory.CreateDirectory(newCoursePath); 126 | 127 | // Move all folders and its contents to newCoursePath 128 | 129 | #region Move file 130 | 131 | /* 132 | 133 | if (Path.GetPathRoot(newCoursePath) != Path.GetPathRoot(coursePath)) 134 | { 135 | Microsoft.VisualBasic.FileIO.FileSystem.MoveDirectory(coursePath, newCoursePath); 136 | } 137 | else 138 | { 139 | Directory.Move(coursePath, newCoursePath); 140 | } 141 | 142 | */ 143 | 144 | #endregion 145 | 146 | // Get list all modules in current course 147 | List listModules = GetModulesFromDb(course.CourseName); 148 | 149 | if (listModules.Count > 0) 150 | { 151 | // Get each module 152 | foreach (Module module in listModules) 153 | { 154 | // Generate module hash name 155 | string moduleHash = ModuleHash(module.ModuleName, module.AuthorHandle); 156 | // Generate module path 157 | string moduleHashPath = Path.Combine(coursePath, moduleHash); 158 | // Create new module path with decryption name 159 | string newModulePath = Path.Combine(courseInfo.FullName, 160 | module.ModuleIndex.ToString().PadLeft(2, '0') + ". " + CleanPath(module.ModuleTitle)); 161 | // If length too long, rename it 162 | if (newModulePath.Length > 240) 163 | { 164 | newModulePath = Path.Combine(courseInfo.FullName, 165 | module.ModuleIndex.ToString().PadLeft(2, '0') + ""); 166 | } 167 | 168 | if (Directory.Exists(moduleHashPath)) 169 | { 170 | DirectoryInfo moduleInfo = Directory.Exists(newModulePath) 171 | ? new DirectoryInfo(newModulePath) 172 | : Directory.CreateDirectory(newModulePath); 173 | // Decrypt all videos in current module folder 174 | DecryptAllVideos(moduleHashPath, module.ModuleId, moduleInfo.FullName); 175 | } 176 | else 177 | { 178 | WriteToConsole( 179 | "Folder " + moduleHash + 180 | " cannot be found in the current course path.", 181 | ConsoleColor.Red); 182 | } 183 | } 184 | } 185 | WriteToConsole("Decryption " + course.CourseTitle + " has been completed!", ConsoleColor.Magenta); 186 | } 187 | } 188 | } 189 | 190 | public bool RemoveCourseInDb(string coursePath) 191 | { 192 | string courseName = GetFolderName(coursePath); 193 | 194 | var cmd = DatabaseConnection.CreateCommand(); 195 | cmd.CommandText = @"DELETE FROM Course 196 | WHERE Name = @courseName"; 197 | cmd.Parameters.Add(new SQLiteParameter("@courseName", courseName)); 198 | 199 | var reader = cmd.ExecuteNonQuery(); 200 | 201 | return reader > 0; 202 | } 203 | 204 | /// 205 | /// Decrypt all videos in current module folder. 206 | /// 207 | /// Current module folder 208 | /// Module Id 209 | /// Destination of output video 210 | public void DecryptAllVideos(string folderPath, int moduleId, string outputPath) 211 | { 212 | // Get all clips of this module from database 213 | List listClips = GetClipsFromDb(moduleId); 214 | 215 | if (listClips.Count > 0) 216 | { 217 | foreach (Clip clip in listClips) 218 | { 219 | // Get current path of the encrypted video 220 | string currPath = Path.Combine(folderPath, clip.ClipName + ".psv"); 221 | if (File.Exists(currPath)) 222 | { 223 | // Create new path with output folder 224 | string newPath = Path.Combine(outputPath, 225 | clip.ClipIndex.ToString().PadLeft(2,'0') + ". " + CleanPath(clip.ClipTitle) + ".mp4"); 226 | // If length too long, rename it 227 | if (newPath.Length > 240) 228 | { 229 | newPath = Path.Combine(outputPath, 230 | clip.ClipIndex + ".mp4"); 231 | } 232 | 233 | // Init video and get it from istream 234 | playingFileStream = new VirtualFileStream(currPath); 235 | playingFileStream.Clone(out iStream); 236 | 237 | string fileName = Path.GetFileName(currPath); 238 | 239 | Console.ForegroundColor = ConsoleColor.Yellow; 240 | Console.WriteLine("Start to Decrypt File \"{0}\"", fileName); 241 | Console.ForegroundColor = color_default; 242 | 243 | //Semaphore.Wait(); 244 | //TaskList.Add(Task.Run(() => 245 | //{ 246 | // Write the decrypted video from istream to new file mp4 247 | DecryptVideo(iStream, newPath); 248 | if (Options.CreateTranscript) 249 | { 250 | // Generate transcript file if user ask 251 | WriteTranscriptFile(clip.ClipId, newPath); 252 | } 253 | // lock (SemaphoreLock) 254 | // { 255 | // Semaphore.Release(); 256 | // } 257 | //})); 258 | 259 | Console.ForegroundColor = ConsoleColor.Green; 260 | Console.WriteLine("Decryption File \"{0}\" successfully", Path.GetFileName(newPath)); 261 | Console.ForegroundColor = color_default; 262 | } 263 | else 264 | { 265 | Console.ForegroundColor = ConsoleColor.Gray; 266 | Console.WriteLine("File \"{0}\" cannot be found.", Path.GetFileName(currPath)); 267 | Console.ForegroundColor = color_default; 268 | } 269 | } 270 | } 271 | } 272 | 273 | /// 274 | /// Write transcript for the clip if it available. 275 | /// 276 | /// Clip Id 277 | /// Path of current clip 278 | public void WriteTranscriptFile(int clipId, string clipPath) 279 | { 280 | // Get all transcript to list 281 | List clipTranscripts = GetTrasncriptFromDb(clipId); 282 | 283 | if (clipTranscripts.Count > 0) 284 | { 285 | // Create transcript path with the same name of the clip 286 | string transcriptPath = Path.Combine(Path.GetDirectoryName(clipPath), 287 | Path.GetFileNameWithoutExtension(clipPath) + ".srt"); 288 | if (!File.Exists(transcriptPath)) 289 | { 290 | // Write it to file with stream writer 291 | StreamWriter writer = new StreamWriter(transcriptPath); 292 | int i = 1; 293 | foreach (var clipTranscript in clipTranscripts) 294 | { 295 | var start = TimeSpan.FromMilliseconds(clipTranscript.StartTime).ToString(@"hh\:mm\:ss\,fff"); 296 | var end = TimeSpan.FromMilliseconds(clipTranscript.EndTime).ToString(@"hh\:mm\:ss\,fff"); 297 | writer.WriteLine(i++); 298 | writer.WriteLine(start + " --> " + end); 299 | writer.WriteLine(clipTranscript.Text); 300 | writer.WriteLine(); 301 | } 302 | writer.Close(); 303 | WriteToConsole("Transcript of " + Path.GetFileName(clipPath) + "has been generated scucessfully.", 304 | ConsoleColor.DarkBlue); 305 | } 306 | } 307 | } 308 | 309 | public string RenameIfDuplicated(string path) 310 | { 311 | string newFullPath = Empty; 312 | int count = 1; 313 | 314 | // If path is file 315 | if (Path.HasExtension(path)) 316 | { 317 | string fileName = Path.GetFileNameWithoutExtension(path); 318 | string extension = Path.GetExtension(path); 319 | string currPath = Path.GetDirectoryName(path); 320 | newFullPath = path; 321 | 322 | while (File.Exists(newFullPath)) 323 | { 324 | string tempFileName = $"{fileName} ({count++})"; 325 | newFullPath = Path.Combine(currPath, tempFileName + extension); 326 | } 327 | } 328 | // Else path is directory 329 | else 330 | { 331 | string folderName = GetFolderName(path); 332 | string currPath = Path.GetDirectoryName(path); 333 | newFullPath = path; 334 | 335 | while (Directory.Exists(newFullPath)) 336 | { 337 | string tempFileName = $"{folderName} ({count++})"; 338 | newFullPath = Path.Combine(currPath, tempFileName); 339 | } 340 | } 341 | return newFullPath; 342 | } 343 | 344 | public string GetFileNameIfItExisted(string filePath, bool checkExisted = false) 345 | { 346 | if (checkExisted) 347 | { 348 | if (File.Exists(filePath)) 349 | { 350 | return filePath.Substring(filePath.LastIndexOf(@"\") + 1); 351 | } 352 | 353 | throw new FileNotFoundException(); 354 | } 355 | return filePath.Substring(filePath.LastIndexOf(@"\") + 1); 356 | } 357 | 358 | /// 359 | /// Get current folder name in the full path. 360 | /// 361 | /// The full folder path. 362 | /// Determine if user need to check folder is existed. 363 | /// Folder name of full path. 364 | public string GetFolderName(string folderPath, bool checkExisted = false) 365 | { 366 | if (checkExisted) 367 | { 368 | if (Directory.Exists(folderPath)) 369 | { 370 | return folderPath.Substring(folderPath.LastIndexOf(@"\") + 1); 371 | } 372 | throw new DirectoryNotFoundException(); 373 | } 374 | return folderPath.Substring(folderPath.LastIndexOf(@"\") + 1); 375 | } 376 | 377 | /// 378 | /// Compare two files 379 | /// 380 | /// Path of the first file 381 | /// Path of the second file 382 | /// Boolean value determine two files is the same. 383 | public bool CompareTwoFiles(string fileOne, string fileTwo) 384 | { 385 | return File.ReadAllBytes(fileOne).LongLength == File.ReadAllBytes(fileTwo).LongLength; 386 | } 387 | 388 | /// 389 | /// Decrypt video. 390 | /// 391 | /// IStream 392 | /// Output path of the clip 393 | public void DecryptVideo(IStream curStream, string newPath) 394 | { 395 | STATSTG stat; 396 | curStream.Stat(out stat, 0); 397 | IntPtr myPtr = (IntPtr) 0; 398 | int strmSize = (int) stat.cbSize; 399 | byte[] strmInfo = new byte[strmSize]; 400 | curStream.Read(strmInfo, strmSize, myPtr); 401 | File.WriteAllBytes(newPath, strmInfo); 402 | } 403 | 404 | /// 405 | /// Get transcript text of specified clip from database. 406 | /// 407 | /// Clip Id 408 | /// List of transcript text of the current clip. 409 | public List GetTrasncriptFromDb(int clipId) 410 | { 411 | List list = new List(); 412 | 413 | var cmd = DatabaseConnection.CreateCommand(); 414 | cmd.CommandText = @"SELECT StartTime, EndTime, Text 415 | FROM ClipTranscript 416 | WHERE ClipId = @clipId 417 | ORDER BY Id ASC"; 418 | cmd.Parameters.Add(new SQLiteParameter("@clipId", clipId)); 419 | 420 | var reader = cmd.ExecuteReader(); 421 | 422 | while (reader.Read()) 423 | { 424 | ClipTranscript clipTranscript = new ClipTranscript 425 | { 426 | StartTime = reader.GetInt32(reader.GetOrdinal("StartTime")), 427 | EndTime = reader.GetInt32(reader.GetOrdinal("EndTime")), 428 | Text = reader.GetString(reader.GetOrdinal("Text")) 429 | }; 430 | list.Add(clipTranscript); 431 | } 432 | 433 | return list; 434 | } 435 | 436 | /// 437 | /// Get all clips information of specified module from database. 438 | /// 439 | /// Module Id 440 | /// List of information about clips belong to specifed module. 441 | public List GetClipsFromDb(int moduleId) 442 | { 443 | List list = new List(); 444 | 445 | var cmd = DatabaseConnection.CreateCommand(); 446 | cmd.CommandText = @"SELECT Id, Name, Title, ClipIndex 447 | FROM Clip 448 | WHERE ModuleId = @moduleId"; 449 | cmd.Parameters.Add(new SQLiteParameter("@moduleId", moduleId)); 450 | 451 | var reader = cmd.ExecuteReader(); 452 | 453 | while (reader.Read()) 454 | { 455 | Clip clip = new Clip 456 | { 457 | ClipId = reader.GetInt32(reader.GetOrdinal("Id")), 458 | ClipName = reader.GetString(reader.GetOrdinal("Name")), 459 | ClipTitle = reader.GetString(reader.GetOrdinal("Title")), 460 | ClipIndex = reader.GetInt32(reader.GetOrdinal("ClipIndex")) 461 | }; 462 | list.Add(clip); 463 | } 464 | reader.Close(); 465 | return list; 466 | } 467 | 468 | /// 469 | /// Get all modules information of specified course from database. 470 | /// 471 | /// Name of course 472 | /// List of modules information of specified course. 473 | public List GetModulesFromDb(string courseName) 474 | { 475 | List list = new List(); 476 | 477 | var cmd = DatabaseConnection.CreateCommand(); 478 | cmd.CommandText = @"SELECT Id, Name, Title, AuthorHandle, ModuleIndex 479 | FROM Module 480 | WHERE CourseName = @courseName"; 481 | cmd.Parameters.Add(new SQLiteParameter("@courseName", courseName)); 482 | 483 | var reader = cmd.ExecuteReader(); 484 | 485 | while (reader.Read()) 486 | { 487 | Module module = new Module 488 | { 489 | ModuleId = reader.GetInt32(reader.GetOrdinal("Id")), 490 | AuthorHandle = reader.GetString(reader.GetOrdinal("AuthorHandle")), 491 | ModuleName = reader.GetString(reader.GetOrdinal("Name")), 492 | ModuleTitle = reader.GetString(reader.GetOrdinal("Title")), 493 | ModuleIndex = reader.GetInt32(reader.GetOrdinal("ModuleIndex")) 494 | }; 495 | list.Add(module); 496 | } 497 | reader.Close(); 498 | return list; 499 | } 500 | 501 | /// 502 | /// Get course information from database. 503 | /// 504 | /// Folder contains all courses 505 | /// Course information 506 | public Course GetCourseFromDb(string folderCoursePath) 507 | { 508 | Course course = null; 509 | 510 | string courseName = GetFolderName(folderCoursePath, true).Trim().ToLower(); 511 | 512 | var cmd = DatabaseConnection.CreateCommand(); 513 | cmd.CommandText = @"SELECT Name, Title, HasTranscript 514 | FROM Course 515 | WHERE Name = @courseName"; 516 | cmd.Parameters.Add(new SQLiteParameter("@courseName", courseName)); 517 | 518 | var reader = cmd.ExecuteReader(); 519 | 520 | if (reader.Read()) 521 | { 522 | course = new Course 523 | { 524 | CourseName = reader.GetString(reader.GetOrdinal("Name")), 525 | CourseTitle = reader.GetString(reader.GetOrdinal("Title")), 526 | HasTranscript = reader.GetInt32(reader.GetOrdinal("HasTranscript")) 527 | }; 528 | } 529 | 530 | reader.Close(); 531 | 532 | return course; 533 | } 534 | 535 | /// 536 | /// Init database connection. 537 | /// 538 | /// Database file path 539 | /// Boolean value determine the database is open successful or not 540 | public bool InitDB(string dbPath) 541 | { 542 | if (File.Exists(dbPath)) 543 | { 544 | if (Path.GetExtension(dbPath).Equals(".db")) 545 | { 546 | DatabaseConnection = new SQLiteConnection($"Data Source={dbPath}; Version=3;FailIfMissing=True"); 547 | DatabaseConnection.Open(); 548 | WriteToConsole("The Database Connection has been open completely." + Environment.NewLine, 549 | ConsoleColor.Green); 550 | 551 | return true; 552 | } 553 | WriteToConsole("The database file isn't corrected.", ConsoleColor.Red); 554 | return false; 555 | } 556 | WriteToConsole("Cannot find the database path.", ConsoleColor.Red); 557 | return false; 558 | } 559 | } 560 | } --------------------------------------------------------------------------------