Skip to content

Instantly share code, notes, and snippets.

@parabola949
Last active August 29, 2015 14:19
Show Gist options
  • Save parabola949/a159eb9411a1858704c4 to your computer and use it in GitHub Desktop.
Save parabola949/a159eb9411a1858704c4 to your computer and use it in GitHub Desktop.
Auto unpack and move torrents for TV and Movies
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
public class Ini
{
Dictionary<string, Dictionary<string, string>> ini = new Dictionary<string, Dictionary<string, string>>(StringComparer.InvariantCultureIgnoreCase);
string file;
/// <summary>
/// Initialize an INI file
/// Load it if it exists
/// </summary>
/// <param name="file">Full path where the INI file has to be read from or written to</param>
public Ini(string file)
{
this.file = file;
if (!File.Exists(file))
return;
Load();
}
/// <summary>
/// Load the INI file content
/// </summary>
public void Load()
{
var txt = File.ReadAllText(file);
Dictionary<string, string> currentSection = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
ini[""] = currentSection;
foreach (var l in txt.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries)
.Select((t, i) => new
{
idx = i,
text = t.Trim()
}))
// .Where(t => !string.IsNullOrWhiteSpace(t) && !t.StartsWith(";")))
{
var line = l.text;
if (line.StartsWith(";") || string.IsNullOrWhiteSpace(line))
{
currentSection.Add(";" + l.idx.ToString(), line);
continue;
}
if (line.StartsWith("[") && line.EndsWith("]"))
{
currentSection = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
ini[line.Substring(1, line.Length - 2)] = currentSection;
continue;
}
var idx = line.IndexOf("=");
if (idx == -1)
currentSection[line] = "";
else
currentSection[line.Substring(0, idx)] = line.Substring(idx + 1);
}
}
/// <summary>
/// Get a parameter value at the root level
/// </summary>
/// <param name="key">parameter key</param>
/// <returns></returns>
public string GetValue(string key)
{
return GetValue(key, "", "");
}
/// <summary>
/// Get a parameter value in the section
/// </summary>
/// <param name="key">parameter key</param>
/// <param name="section">section</param>
/// <returns></returns>
public string GetValue(string key, string section)
{
return GetValue(key, section, "");
}
/// <summary>
/// Returns a parameter value in the section, with a default value if not found
/// </summary>
/// <param name="key">parameter key</param>
/// <param name="section">section</param>
/// <param name="default">default value</param>
/// <returns></returns>
public string GetValue(string key, string section, string @default)
{
if (!ini.ContainsKey(section))
return @default;
if (!ini[section].ContainsKey(key))
return @default;
return ini[section][key];
}
/// <summary>
/// Save the INI file
/// </summary>
public void Save()
{
var sb = new StringBuilder();
foreach (var section in ini)
{
if (section.Key != "")
{
sb.AppendFormat("[{0}]", section.Key);
sb.AppendLine();
}
foreach (var keyValue in section.Value)
{
if (keyValue.Key.StartsWith(";"))
{
sb.Append(keyValue.Value);
sb.AppendLine();
}
else
{
sb.AppendFormat("{0}={1}", keyValue.Key, keyValue.Value);
sb.AppendLine();
}
}
if (!endWithCRLF(sb))
sb.AppendLine();
}
File.WriteAllText(file, sb.ToString());
}
bool endWithCRLF(StringBuilder sb)
{
if (sb.Length < 4)
return sb[sb.Length - 2] == '\r' &&
sb[sb.Length - 1] == '\n';
else
return sb[sb.Length - 4] == '\r' &&
sb[sb.Length - 3] == '\n' &&
sb[sb.Length - 2] == '\r' &&
sb[sb.Length - 1] == '\n';
}
/// <summary>
/// Write a parameter value at the root level
/// </summary>
/// <param name="key">parameter key</param>
/// <param name="value">parameter value</param>
public void WriteValue(string key, string value)
{
WriteValue(key, "", value);
}
/// <summary>
/// Write a parameter value in a section
/// </summary>
/// <param name="key">parameter key</param>
/// <param name="section">section</param>
/// <param name="value">parameter value</param>
public void WriteValue(string key, string section, string value)
{
Dictionary<string, string> currentSection;
if (!ini.ContainsKey(section))
{
currentSection = new Dictionary<string, string>();
ini.Add(section, currentSection);
}
else
currentSection = ini[section];
currentSection[key] = value;
}
/// <summary>
/// Get all the keys names in a section
/// </summary>
/// <param name="section">section</param>
/// <returns></returns>
public string[] GetKeys(string section)
{
if (!ini.ContainsKey(section))
return new string[0];
return ini[section].Keys.ToArray();
}
/// <summary>
/// Get all the section names of the INI file
/// </summary>
/// <returns></returns>
public string[] GetSections()
{
return ini.Keys.Where(t => t != "").ToArray();
}
}
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
namespace UnpackTorrent
{
class Program
{
static string _movieDir, _tvDir, _torDir, _winRarPath, _logFilePath;
static string _vidQualRegex = @"(720p)|(1080p)|(DVDSCR)|(iNTERNAL)|(HDTV)";
static string _tvRegex = @"(S\d{2}E\d{2})";
static string _vidFileRegex = @"(mkv)|(avi)|(wmv)";
static string _sampleFileRegex = @"(sample).*((mkv)|(avi)|(wmv))";
static bool _includeYearForMovies;
//Import dlls for window hiding...
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
const int SW_HIDE = 0;
const int SW_SHOW = 5;
//Example arg[0] D:\Torrents\Game.of.Thrones.S05E02.720p.HDTV.x264-IMMERSE
//Example arg[1] Game.of.Thrones.S05E02.720p.HDTV.x264-IMMERSE
static void Main(string[] args)
{
//attempt to hide the console window
try
{
ShowWindow(GetConsoleWindow(), SW_HIDE);
}
catch(Exception e)
{
Log("Error hiding console window: {0}", e.Message);
}
//Load settings
var INI = new Ini("settings.ini");
_movieDir = INI.GetValue("Movies","Directories");
_tvDir = INI.GetValue("TV","Directories");
_torDir = INI.GetValue("Torrents", "Directories");
_includeYearForMovies = INI.GetValue("Include Year", "Other") == "True";
_winRarPath = INI.GetValue("WinRAR Path", "Other");
_logFilePath = INI.GetValue("Log File", "Directories", "unpack.log");
Log("--------------------------------------------");
Log("Beginning unpack / copy. Torrent: {0} Directory: {1}", args[1], args[0]);
var fileInRoot = false;
//Check that the item is in it's own folder
if (_torDir == (args[0]))
{
if (Directory.GetFiles(_torDir).Any(x => x.Contains(args[1])))
fileInRoot = true;
else
{
Log("Torrent in root directory, and file name does not match torrent name. Cannot find. Exiting.");
Environment.Exit(2);
}
}
Log("File in root: {0}", fileInRoot);
var parseStr = fileInRoot ? args[1] : args[0].Substring(args[0].LastIndexOf("\\") + 1);
//Now check if it is a movie or tv show (not a game or software)
if (!Regex.IsMatch(parseStr, _vidQualRegex, RegexOptions.IgnoreCase))
{
//Does not appear to be a movie, skip this file.
Log("This torrent does not appear to be a video. Skipping.\nTorrent: {0}", args[1]);
Environment.Exit(1);
}
//Is it a TV Show or a Movie?
if (Regex.IsMatch(parseStr, _tvRegex, RegexOptions.IgnoreCase))
{
//TV Show
//Ugly bits, parsing out title, season, episode (though.. we don't REALLY need the episode, but whatever)
var s00e00 = Regex.Matches(parseStr, _tvRegex)[0].Value;
var title = parseStr;
title = title.Substring(0, title.IndexOf(s00e00));
title = title.Replace('.', ' ').Trim();
var season = s00e00.Substring(1, 2);
var episode = s00e00.Substring(4, 2);
if (season[0] == '0')
season = season.Substring(1);
//Check our directories now.
//Does the show have a directory?
var showRoot = _tvDir + "\\" + title;
if (!Directory.Exists(showRoot))
Directory.CreateDirectory(showRoot);
//Does the season have a directory?
var seasonDir = showRoot + "\\Season " + season;
if (!Directory.Exists(seasonDir))
Directory.CreateDirectory(seasonDir);
//Now then, do we have a video file, or rar? Essentially, are at least half the files rars (in case there is a single rar file and a video file + nfo file)
//Forgive me for this logic....
if (!fileInRoot && IsRar(args[0]))
{
//RAR it is!
foreach (var file in Directory.GetFiles(args[0]).Where(x => x.EndsWith(".rar")))
{
Log("Unpacking RAR files from {0} and moving to {1}", file, seasonDir);
//Unpack.. winrar.exe x <sourcerar> <destpath>
Process p = Process.Start(_winRarPath, String.Format("x -ibck -o+ \"{0}\" \"{1}\"", file, seasonDir));
p.WaitForExit();
}
//And we are done..
Environment.Exit(0);
}
else
{
//single video file
foreach (var file in Directory.GetFiles(args[0]).Where(x => Regex.IsMatch(x, _vidFileRegex, RegexOptions.IgnoreCase) & !Regex.IsMatch(x, _sampleFileRegex, RegexOptions.IgnoreCase) && (fileInRoot ? x.Contains(parseStr) : true)))
{
Log("Copying file {0} to {1}", file, seasonDir);
File.Copy(file, seasonDir + file.Substring(file.LastIndexOf("\\")), true);
}
}
}
else
{
//Looks like it's a movie.
var title = parseStr;
title = title.Substring(0, title.IndexOf(Regex.Match(title, _vidQualRegex).Value));
//now we should have title.year
var year = title.Substring(title.Length - 5, 4);
title = title.Substring(0, title.IndexOf(year) - 1);
title = title.Replace("_", " ").Replace('.', ' ').Trim();
if (_includeYearForMovies)
title += " (" + year + ")";
//now to unpack
if (!fileInRoot && IsRar(args[0]))
{
foreach (var file in Directory.GetFiles(args[0]).Where(x => x.EndsWith(".rar") && (fileInRoot ? x.Contains(parseStr) : true)))
{
//Unpack.. winrar.exe x <sourcerar> <destpath>
Directory.CreateDirectory(_movieDir + "\\temp");
Process p = Process.Start(_winRarPath, String.Format("x -ibck -o+ \"{0}\" \"{1}\"", file, _movieDir + "\\temp"));
p.WaitForExit();
foreach (var video in Directory.GetFiles(_movieDir + "\\temp").Where(x => !x.EndsWith(".ini"))) //Stupid desktop.ini....
{
//ok, madness here. standard naming convention is title.year.quality.source.group
var extension = video.Substring(video.LastIndexOf('.'));
title += extension;
var moviePath = _movieDir + "\\" + title;
if (File.Exists(moviePath))
{
Log("Removing old file {0}", moviePath);
File.Delete(moviePath);
}
Log("Moving file {0} to {1}", video, moviePath);
File.Move(video, moviePath);
}
}
}
else
{
int i = 0; //just in case of multipart
//it's a movie file, we just need to copy it over (not move... SEED BITCHES!)
foreach (var file in Directory.GetFiles(args[0]).Where(x => Regex.IsMatch(x, _vidFileRegex) && !Regex.IsMatch(x, _sampleFileRegex) && (fileInRoot ? x.Contains(parseStr) : true)))
{
i++;
var extension = file.Substring(file.LastIndexOf('.'));
var path = _movieDir + "\\" + title + (i > 1 ? " Part (" + i + ")" : "") + extension;
Log("Copying {0} to {1}", file, path);
File.Copy(file, path, true);
}
}
}
}
private static bool IsRar(string path)
{
return ((double)(Directory.GetFiles(path).Count(x => Regex.IsMatch(x, @"r\d{2}") || x.EndsWith("rar")))) / ((double)Directory.GetFiles(path).Count()) >= 0.5 & !Directory.GetFiles(path).Any(x => Regex.IsMatch(x, _vidFileRegex, RegexOptions.IgnoreCase) & !Regex.IsMatch(x, _sampleFileRegex, RegexOptions.IgnoreCase));
}
private static void Log(string str, params object[] args)
{
var logStr = args == null?str:String.Format(str,args);
Console.WriteLine(logStr);
using (var sw = new StreamWriter(_logFilePath, true))
{
sw.WriteLine(logStr);
}
}
}
}
[Directories]
Movies=D:\Google Drive\Media\Movies
TV=D:\Google Drive\Media\TV Shows
Torrents=D:\Torrents
;Log file path. This can be absolute or relative, and can be whatever extension you want. it is just plain text.
Log File=D:\Torrents\unpack.log
[Other]
;path to winrar
WinRAR Path=C:\Program Files\WinRAR\WinRAR.exe
;include year in movie file name
Include Year=True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment