Skip to content

Commit

Permalink
Merge pull request #8 from zerratar/feature/add-opensubtitles-provider
Browse files Browse the repository at this point in the history
Feature/add opensubtitles provider
  • Loading branch information
zerratar authored Apr 11, 2018
2 parents 09d0afc + 1ba15e8 commit c40b875
Show file tree
Hide file tree
Showing 49 changed files with 2,261 additions and 172 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ x64/
x86/
bld/
[Bb]in/
[Bb]uild/
[Oo]bj/
[Ll]og/

Expand Down
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# SubSync
Automatically download subtitles for your movies

I hope this little tool comes to help you out as much as it did for me. My girlfriend and I both struggled with using the VLC Sub addon for downloading subtitles, a tool that we previously been using lots! And when that tool stopped working on later days, keep being unresponsive and continuesly crashing VLC, we had to manually look for those darn subtitles on the web and if you have a lot of shows to watch then its a huge pain in the ass.
I hope this little tool comes to help you out as much as it did for me. My girlfriend and I both struggled with using the VLC Sub add-on for downloading subtitles, a tool that we previously been using lots! And when that tool stopped working on later days, keep being unresponsive and continuesly crashing VLC, we had to manually look for those darn subtitles on the web and if you have a lot of shows to watch then its a huge pain in the ass.

SubSync is a tool that will keep your movies folder synchronized with subtitles. That means, if you add a video file to the folder or its subfolders being watched a subtitle will automatically be downloaded for that movie.
SubSync is a tool that will keep your movies folder synchronized with subtitles. That means, if you add a video file to the folder or its sub-folders being watched a subtitle will automatically be downloaded for that movie.

It works by using the filename of the movie to determine the name and use that to do a search on subscene.com to find the "first best" subtitle to download. It will then extract the file (if its an archive) and rename it to have the same name as the movie file so it can be quickly recognized as a subtitle using VLC.

Expand Down Expand Up @@ -60,20 +60,31 @@ Now keep it running in the background. Its not going to hog up your cpu. Its pre
## Tips and tricks
Press 'q' at any time to exit SubSync

Press 'a' to try and re-sync any previously unsyced subtitles. Yes the subtitle downloads can randomly fail some times when subscene.com decides you shouldnt be downloading their subtitles too often.
Press 'a' to try and re-sync any previously unsynced subtitles. Yes the subtitle downloads can randomly fail some times when subscene.com decides you shouldn't be downloading their subtitles too often.

Oh, and be sure to bring popcorns or your favorite snacks when watching your movies!

## Known issues / To-dos

The OpenSubtitles provider ignore language priority.

Add support for http://www.yifysubtitles.com/

## Changes
### v0.1.4
Add support for OpenSubtitles.org and is also now the default subtitle provider for SubSync. subscene.com will still be used but only if no subtitles were found on opensubtitles.org.
Improved subtitle search algorithm, but only for OpenSubtitles.org right now. The subscene provider will be updated in a future version.

The opensubtitles.org provider has not been properly tested though, so there may still be some bugs that needs to be squished. But initial tests returned great results!

### v0.1.3
Use Unicode encoding in the console to properly display all texts.

### v0.1.2
Update the usage of all FileInfo and Directoryinfo instances to use the ZetaLongPaths available from here https://github.com/UweKeim/ZetaLongPaths to fix the bug caused by too long paths.
Update the usage of all FileInfo and DirectoryInfo instances to use the ZetaLongPaths available from here https://github.com/UweKeim/ZetaLongPaths to fix the bug caused by too long paths.

### v0.1.1
This version didn't like anyone.

### v0.1.0
Initial release on github, you know the magical first version

## Todo
Add support for OpenSubtitles as a subtitle provider
Add support for http://www.yifysubtitles.com/
Initial release on GitHub, you know the magical first version
14 changes: 14 additions & 0 deletions SubSync.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ VisualStudioVersion = 15.0.27428.2011
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SubSync", "src\SubSync\SubSync.csproj", "{A1B8C57E-2B99-4D21-A35E-D050F3BA279B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C182B254-972A-4CB2-8002-1653E0F24323}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{7D3E5130-833D-4426-9E7C-7D91CBA217B3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubSync.Tests", "tests\SubSync.Tests\SubSync.Tests.csproj", "{71C95A54-2D86-4AC9-AE79-05CF93E063EA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -15,10 +21,18 @@ Global
{A1B8C57E-2B99-4D21-A35E-D050F3BA279B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B8C57E-2B99-4D21-A35E-D050F3BA279B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B8C57E-2B99-4D21-A35E-D050F3BA279B}.Release|Any CPU.Build.0 = Release|Any CPU
{71C95A54-2D86-4AC9-AE79-05CF93E063EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71C95A54-2D86-4AC9-AE79-05CF93E063EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71C95A54-2D86-4AC9-AE79-05CF93E063EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71C95A54-2D86-4AC9-AE79-05CF93E063EA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A1B8C57E-2B99-4D21-A35E-D050F3BA279B} = {C182B254-972A-4CB2-8002-1653E0F24323}
{71C95A54-2D86-4AC9-AE79-05CF93E063EA} = {7D3E5130-833D-4426-9E7C-7D91CBA217B3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1EBFA09F-C84B-4414-AAF2-998BD08F6A97}
EndGlobalSection
Expand Down
9 changes: 9 additions & 0 deletions publish.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
rem rmdir .\build\self-contained-win10-x86 /S /Q
rem rmdir .\build\self-contained-win10-x64 /S /Q
rmdir .\build\win10-x86 /S /Q
rmdir .\build\win10-x64 /S /Q
rem dotnet publish -o ..\..\build\self-contained-win10-x86 -r win10-x86 --self-contained
rem dotnet publish -o ..\..\build\self-contained-win10-x64 -r win10-x64 --self-contained
dotnet publish -o ..\..\build\win10-x86 -r win10-x86 --self-contained false
dotnet publish -o ..\..\build\win10-x64 -r win10-x64 --self-contained false
pause
16 changes: 16 additions & 0 deletions src/SubSync/Core/AuthCredentials.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace SubSync
{
internal struct AuthCredentials
{
public readonly string Username;
public readonly string Password;

public AuthCredentials(string username, string password)
{
Username = username;
Password = password;
}

public bool IsEmpty => string.IsNullOrEmpty(Username) && string.IsNullOrEmpty(Password);
}
}
8 changes: 8 additions & 0 deletions src/SubSync/Core/Exceptions/DownloadQuotaReachedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace SubSync
{
internal class DownloadQuotaReachedException : Exception
{
}
}
12 changes: 12 additions & 0 deletions src/SubSync/Core/Exceptions/NestedArchiveNotSupportedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace SubSync
{
internal class NestedArchiveNotSupportedException : Exception
{
public NestedArchiveNotSupportedException(string filename)
: base($"Downloaded archive, '{filename}' @red@contain another archive within it and cannot properly be extracted. Archive kept for manual labor.")
{
}
}
}
8 changes: 8 additions & 0 deletions src/SubSync/Core/Exceptions/RequestQuotaReachedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace SubSync
{
internal class RequestQuotaReachedException : Exception
{
}
}
8 changes: 8 additions & 0 deletions src/SubSync/Core/Exceptions/SubtitleNotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace SubSync
{
internal class SubtitleNotFoundException : Exception
{
}
}
27 changes: 26 additions & 1 deletion src/SubSync/Core/FallbackSubtitleProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

namespace SubSync
{
internal class FallbackSubtitleProvider : ISubtitleProvider
internal class FallbackSubtitleProvider : ISubtitleProvider, IDisposable
{
private readonly ConcurrentDictionary<string, int> providerCache = new ConcurrentDictionary<string, int>();
private readonly ISubtitleProvider[] _providers;
private readonly int MaxRetryCount = 3;
private bool disposed;

public FallbackSubtitleProvider(params ISubtitleProvider[] providers)
{
Expand Down Expand Up @@ -37,5 +38,29 @@ public async Task<string> GetAsync(string name, string outputDirectory)
return await this.GetAsync(name, outputDirectory);
}
}

public void Dispose()
{
if (this.disposed)
{
return;
}

this.disposed = true;
foreach (var provider in this._providers)
{
try
{
if (provider is IDisposable disposable)
{
disposable.Dispose();
}
}
catch
{
// ignored
}
}
}
}
}
62 changes: 62 additions & 0 deletions src/SubSync/Core/FileBasedCredentialsProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;

namespace SubSync
{
internal class FileBasedCredentialsProvider : IAuthCredentialProvider
{
private readonly string filename;
private readonly ILogger logger;
private bool synchronizedWithFile;
private string username;
private string password;

public FileBasedCredentialsProvider(string filename, ILogger logger)
{
this.filename = filename;
this.logger = logger;
}

private void ReadFile()
{
if (!System.IO.File.Exists(filename))
{
return;
}
// storing the user/pass is generally a very bad idea as you could find the values by looking in the application memory.
// But since I highly doubt anyone is going to go that far to write a virus or app to read the memory of SubSync just to steal someones subtitle provider passwords. lol
try
{
var lines = System.IO.File.ReadAllLines(filename);
foreach (var line in lines)
{
var data = line.Split('=');
if (data[0].Equals("username", StringComparison.CurrentCultureIgnoreCase))
{
this.username = data[1];
}
else if (data[0].Equals("password", StringComparison.CurrentCultureIgnoreCase))
{
this.password = data[1];
}
}
synchronizedWithFile = true;
}
catch (Exception exc)
{
this.logger.WriteLine("@yel@Unable to read opensubtitles.auth! username and password will be left blank!");
}

}

public AuthCredentials Get()
{
if (!this.synchronizedWithFile)
{
// in case it previously failed or file was added after the application started.
this.ReadFile();
}

return new AuthCredentials(this.username, this.password);
}
}
}
90 changes: 90 additions & 0 deletions src/SubSync/Core/FilenameDiff.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using SubSync.Extensions;

namespace SubSync
{
public static class FilenameDiff
{
public static int IndexOfBestMatch(string needle, string[] haystack)
{
var index = 0;
var scored = new Dictionary<int, double>();
var input = Path.GetFileNameWithoutExtension(needle);

foreach (var item in haystack)
{
if (item.Equals(input, StringComparison.OrdinalIgnoreCase))
{
return index; // direct match
}
scored[index++] = FilenameDiff.GetDiffScore(input, item);
}

// score: the lower, the better.
if (scored.Count > 0)
{
return scored.OrderBy(x => x.Value).First().Key;
}

return -1;
}

public static T FindBestMatch<T>(string needle, T[] haystack, Func<T, string> stringComparison)
{
var index = IndexOfBestMatch(needle, haystack.Select(stringComparison).ToArray());
return index != -1 ? haystack[index] : default(T);
}

public static double GetDiffScore(string a, string b)
{
// first iteration is to take all distinct values from a and b
// then compare the actual content and score it.
// second iteration takes words into consideration
var c1 = a.ToLower().ToCharArray();
var c2 = b.ToLower().ToCharArray();

var diff = new HashSet<char>(c1);
diff.SymmetricExceptWith(c2);

var changes = diff.ToArray(); // c1.Intersect(c2).ToArray();
var score = 0.0;
// different chars have different scoring
// letters are 1.0
// numbers are 0.75
// brackets and paranthesis are 0.5
// spaces are 0.1
foreach (var change in changes)
{
if (change == '[' || change == ']' || change == '(' || change == ')') score += 0.5;
else if (char.IsDigit(change)) score += 0.75;
else if (change == ' ') score += 0.1;
else score += 1.0;
}

var l0 = new HashSet<string>();
a.ToLower().Split(new[] { '.', ' ' }, StringSplitOptions.RemoveEmptyEntries).ForEach(x => l0.Add(x));
if (l0.Count > 1) l0.Remove(a.ToLower());

var l1 = new HashSet<string>();
b.ToLower().Split(new[] { '.', ' ' }, StringSplitOptions.RemoveEmptyEntries).ForEach(x => l1.Add(x));
if (l1.Count > 1) l1.Remove(b.ToLower());

var l2 = new HashSet<string>();
l2.UnionWith(l0);
l2.UnionWith(l1);

for (var i = 0; i < l2.Count; i++)
{
if (!l0.Contains(l2.ElementAt(i))) score++;
if (!l1.Contains(l2.ElementAt(i))) score++;
}

return score;
}
}
}
7 changes: 7 additions & 0 deletions src/SubSync/Core/IAuthCredentialProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace SubSync
{
internal interface IAuthCredentialProvider
{
AuthCredentials Get();
}
}
2 changes: 1 addition & 1 deletion src/SubSync/Core/IFileSystemWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace SubSync
{
internal interface IFileSystemWatcher : IDisposable
internal interface IFileSystemWatcher
{
void Start();
void Stop();
Expand Down
Loading

0 comments on commit c40b875

Please sign in to comment.