├── .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[](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 | }
--------------------------------------------------------------------------------