Created
October 2, 2012 15:14
-
-
Save coneybeare/3819972 to your computer and use it in GitHub Desktop.
A script for Transmission that runs after a completed download. It scans for downloaded television shows then attempts to place them in the correct organizational directory on the local machine, removing the torrent as well.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'fileutils' | |
# SETUP | |
# Details about the local transmission service | |
transmission_remote_location = "/usr/sbin/transmission-remote" | |
transmission_server_port = "mini.local:9091" | |
# The place where downloaded but unsorted tv show torrents land | |
downloads_television_directory = "/Volumes/Drobo/Downloads/Television/" | |
# The final resingplace for shows - Assumes a directory structure of /Path/To/Television > [SHOW NAME] > [SEASON X] > [VIDEO FILE] | |
show_television_directory = "/Volumes/Drobo/Television/" | |
# Get the shows on the system | |
# Should correlate to a listing of the show directories on the system | |
shows = Dir.entries(show_television_directory).select{|d| File.directory?(show_television_directory + d)}.reject{|d| d[0] == "."} | |
# Words to ignore in the show titles | |
ignored_keywords = [ "the", "and", "an", "a", "in", "of" ] | |
# Files to ignore in the directory | |
ignored_files = [ ".DS_Store" ] | |
# Get a list of the files in here that are not folders | |
episodes = Dir.entries(downloads_television_directory).select { |f| File.file?(downloads_television_directory + f) } | |
# Iterate over each downloaded episode | |
episodes.each do |episode_name| | |
# skip the ignored files | |
next if ignored_files.include? episode_name | |
puts "\n----------------------------------------------------------------------------------------------------------------------------" | |
puts "Inspecting \"#{episode_name}...\"" | |
# The following two checks only affect systems where the incomplete folder is not on the same drive as the final file folder | |
# This means that when the torrent is done, it has to be moved from one drive to the other, something that is not instaneous | |
# Check if the filesize is 0. | |
# This happens when the file has started to transfer but they filesystem hasnt actually moved anything yet. | |
downloaded_file = downloads_television_directory + episode_name | |
filesize_1 = File.size(downloaded_file); | |
if filesize_1 == 0 | |
puts " -> Ignoring: File has not started transferring (filesize: #{filesize_1})" | |
next | |
end | |
# Check if the file is done transferring. | |
# This doesn't seem like the best way to do this, but in practice it works. | |
# Could produce a false positive if the system is hung or something else causes no data to transfer in 10 seconds | |
sleep(10) | |
filesize_2 = File.size(downloaded_file); | |
if filesize_2 > filesize_1 | |
puts " -> Ignoring: File is not done transferring (#{filesize_2} > #{filesize_1} )" | |
next | |
end | |
# Try to figure out which show the file belongs to by ensuring that each word of the show is in the torrent. | |
# There may be a better way to do this using torrent metadata of some sort, but this seems to work for the way torrent names have | |
# kind of been standardized | |
matches = [] | |
matched_show = nil | |
shows.each do |show| | |
matched = false | |
keywords = show.downcase.split.reject { |kw| ignored_keywords.include? kw.downcase } | |
match = keywords.all? { |kw| episode_name.downcase.include? kw } | |
if match | |
matches << show | |
end | |
end | |
if matches.length == 1 | |
# A single match is good :) | |
matched_show = matches.first | |
elsif matches.length > 1 | |
# multiple matches, need to select the right one, most likely is the one with the longer multiword title | |
# as usually false positives happen to something like "Girls" and "2 Broke Girls" | |
puts " -> Found multiple matches: #{matches}" | |
length_so_far = 0 | |
matches.each do |show| | |
keywords = show.downcase.split.reject { |kw| ignored_keywords.include? kw.downcase } | |
if keywords.length > length_so_far | |
length_so_far = keywords.length | |
matched_show = show | |
end | |
end | |
end | |
# A nil matched show here does nothing. Will have to manually sort the episode | |
if matched_show | |
show = matched_show | |
puts " -> Matched: \"#{show}\"" | |
# /Path/To/Television > [SHOW NAME] | |
show_directory = show_television_directory + show + "/" | |
# Parse season out of episode filename | |
season, episode = episode_name.scan(/[S|s](\d+)[E|e](\d+)/).flatten # handles S01E01 style | |
if !season | |
season, episode = episode_name.scan(/(\d+)[X|x](\d+)/).flatten # handles 1x01 style | |
end | |
# A nil season here does nothing. Will have to manually sort the episode | |
if season | |
# makes the season "06" become "6" | |
season = season.slice(1) if season[0] == "0" | |
# Look for a matching season to drop the episode. | |
# Iterate over the season folders: /Path/To/Television > [SHOW NAME] > [SEASON X] folders | |
# If there is no match, we will have to manually create the season and sort the file. | |
# This is to prevent a parsing error from creating tons of errant season folders | |
season_folders = Dir.entries(show_directory) | |
season_folders.each do |season_folder| | |
if season_folder == "Season #{season}" | |
# located the correct episode season | |
puts " -> Matched: \"#{season_folder}\"" | |
# /Path/To/Television > [SHOW NAME] > [SEASON X] > [VIDEO FILE] | |
target_file = show_directory + season_folder + "/" + episode_name | |
puts " -> Moving #{downloaded_file} to #{target_file}" | |
# Move the file | |
FileUtils.mv(downloaded_file, target_file) | |
# Now that we moved the file, lets remove it from transmission | |
# Assumes that transmission remote is installed on the running machine and that transmission remote is enabled in settings | |
puts " -> Removing Torrent" | |
cleaned_episode_name = episode_name.gsub('[', '\[').gsub(']', '\]') | |
#find the torrent id | |
cmd = "#{transmission_remote_location} #{transmission_server_port} -l | grep -i '#{cleaned_episode_name}' | awk " "'{print $1}'" | |
puts " -> Running: #{cmd}" | |
torrent_id = `#{cmd}`.strip.gsub('*','') | |
# if torrent id is not found, do nothing. will have to manually remove torrent from transmission | |
if !torrent_id.empty? | |
puts " -> Torrent ID: #{torrent_id}" | |
puts " -> Double checking name matches id: '#{torrent_id}'" | |
# Sanity check. Make sure that the torrent name for the id we found matches the episode name | |
cmd = "#{transmission_remote_location} #{transmission_server_port} -t '#{torrent_id}' -i | grep -i 'Name\\:' | awk " "'{print $2}'" | |
puts " -> Running: #{cmd}" | |
name = `#{cmd}`.strip | |
# if not matching, do nothing. will have to manually remove torrent from transmission | |
if name == episode_name | |
puts " -> Matched: #{episode_name} == #{name}" | |
puts " -> Removing Torrent: \"#{episode_name}\" == \"#{name}\"" | |
# Remove the torrent file from transmission | |
cmd = "#{transmission_remote_location} #{transmission_server_port} -t '#{torrent_id}' --remove-and-delete" | |
puts " -> Running: #{cmd}" | |
output = `#{cmd}`.strip | |
puts " -> #{output}" | |
end | |
else | |
puts " -> NO MATCH" | |
end | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment