Как стать автором
Обновить

Переименование файлов, скачанных с rutracker.org

Время на прочтение6 мин
Количество просмотров21K
Хочу поделиться небольшим скриптом, который делает для меня простую, но весьма полезную вещь — производит групповое переименование скачанных файлов так, что файлы получают удобно читаемое имя со страницы на сайте торрент трекера.
В итоге вместо "God.Bless.America.2011.HDTVRiP720.mkv" я получаю файл с именем "Боже, Благослови Америку God Bless America (Боб Голдтуэйт Bobcat Goldthwait) (2011) США, триллер, чёрная комедия, криминал, HDTVRip-AVC.mkv"
В продолжение к теме Можно ли прибраться на компе раз и навсегда?

В общем вот что делает скрипт: производит поиск по каталогу Downloads, ищет скачанные файлы и соответствующие им .torrent файлы. Затем берет из .torrent файла URL странички на rutracker.org, загружает эту страничку и получает название закачки. Затем, на основании полученного текста, скрипт и переименовывает скачанный файл (вместо переименования можно выбрать копирование файлов в другой каталог, перенос или создание ссылок).

Для успешной работы скрипта понадобится Python 3 и uTorrent клиент с небольшой настройкой:
Prefferences -> Other -> Store .torrent files in:
Здесь необходимо указать каталог, куда uTorrent будет копировать .torrent файлы. Дел в том, что uTorrent, при копировании .torrent файлов в этот каталог, дает им такое-же имя как и скачиваемый файл. Чем я собственно и воспользовался.
В скрипте этот каталог необходимо указать как TORRENT_DIR.

Также необходимо прописать каталог, где скрипт будет искать скачанные файлы INPUT_DIR.
И каталог, куда скрипт будет копировать файлы (переносить или создавать жесткие ссылки), то есть OUTPUT_DIR.

При помощи переменной MOVE_ALGORITHM можно указать один из алгоритмов переноса файлов:
  • 'link' — создавать жесткие ссылки на файлы. Работает только для файловой системы NTFS в пределах одного диска. После создания жесткой ссылки, файл будет существовать до тех пор, пока не удалится последняя ссылка. По сути жесткая ссылка NTFS — это еще одно имя файла, равноценное с исходным
  • 'copy' — скопировать файлы
  • 'move' — перенести файлы из каталога INPUT_DIR в каталог OUTPUT_DIR. При выборе этого варианта будьте бдительны с активными закачками, если INPUT_DIR указывает на каталог, где производится раздача файлов
  • 'move' в сочетании, когда INPUT_DIR == OUTPUT_DIR, производит переименование файлов на месте


# -*- encoding: utf-8 -*-
import os
import re
import urllib.request
import shutil
import sys
import ctypes

INPUT_DIR       = 'D:/Downloads/uTorrent/Completed'
OUTPUT_DIR      = 'D:/Video/Movies'
TORRENT_DIR     = 'D:/Downloads/uTorrent/torrent'
MOVE_ALGORITHM  = 'link'

def GetMoveAlgorithms():
   return {'move' : MoveFile,
           'copy' : CopyFile,
           'link' : CreateHardLink}

def Print(msg):
    msg = msg.encode('cp866', 'replace')
    msg = msg.decode('cp866')
    print(msg)

def GetTorrentFilePath(fileName):
    filePath = os.path.join(TORRENT_DIR, fileName + '.torrent')
    if not os.path.exists(filePath):
        Print('Skiped, .torrent is not found: "%s' % filePath)
        return None
    return filePath

def GetTrackerUrl(torrentFilePath):
    try:
        torrentFile = open(torrentFilePath, 'r', encoding='ascii', errors='replace')
        fileData = torrentFile.read()
        trackerUrlLen, trackerUrl = re.search(r'comment([0-9]{2}):(.+)', fileData).groups()
        trackerUrl = re.search(r'(.{' + trackerUrlLen + '})', trackerUrl).groups()[0]
        return trackerUrl
    except:
        Print("Error, can't extract tracker url from .torrent file %s" % torrentFilePath)
        return None

def LoadTrackerPage(trackerUrl):
    try:
        response = urllib.request.urlopen(trackerUrl)
        htmlPage = response.read()
    except:
        Print("Error, Can't load tracker page '%s'" % trackerUrl)
        return None
    htmlPage = htmlPage.decode('cp1251')
    return htmlPage

def PrepareFileName(fileName):
    try:
        #remove special symbols
        fileName = re.sub(r'[\\/:"\*?<>|]+', '', fileName, 0, re.UNICODE)
        #remove repeating spaces
        fileName = re.sub(r'[ ]+', ' ', fileName, 0, re.UNICODE)
        fileName = fileName.strip()
    except:
        Print("Error, can't prepare file name '%s'" % fileName)
        return None
    return fileName

class FileInfo:
    pass

def ParseTrackerPage(htmlPage):
    try:
        pageTitle = re.search(r'<title>(.+?) :: .+?</title>', htmlPage, re.UNICODE).groups()[0]
    except:
        Print("Error, Can't parse <title>")
        return None
    fileInfo = FileInfo()
    fileInfo.name = ""
    fileInfo.year = ""
    fileInfo.descr = ""
    try:
        fileInfo.name, fileInfo.year, fileInfo.descr = re.search(r'(.+?) \[([0-9]{4}).*?, (.+?)\]', pageTitle, re.UNICODE).groups()
    except:
        Print("Warning, Can't parse page title: %s" % pageTitle)
        try:
            fileInfo.name, fileInfo.year, fileInfo.descr = re.search(r'(.+?)([0-9]{4}).*?, (.+?)$', pageTitle, re.UNICODE).groups()
        except:
            Print("Warning, Can't parse page title: %s" % pageTitle)
            fileInfo.name = pageTitle
    return fileInfo

def GetDataFromTorrent(fileName):
    torrentFilePath = GetTorrentFilePath(fileName)
    if not torrentFilePath:
        return None
    trackerUrl = GetTrackerUrl(torrentFilePath)
    if not trackerUrl:
        return None
    htmlPage = LoadTrackerPage(trackerUrl)
    if not htmlPage:
        return None
    return ParseTrackerPage(htmlPage)

def PrepareNewFileName(fileName, fileInfo):
    tmp, ext = os.path.splitext(fileName)
    toPrepare = fileInfo.name + ' (' + fileInfo.year + ') ' + fileInfo.descr
    cleanName = PrepareFileName(toPrepare)
    newFileName = cleanName + ext
    return newFileName

def MoveFile(src, dst):
    shutil.move(src, dst)

def CopyFile(src, dst):
    if os.path.isdir(src):
        for fileName in os.listdir(src):
            if not os.path.exists(dst):
                os.mkdir(dst)
            subSrc = os.path.join(src, fileName)
            subDst = os.path.join(dst, fileName)
            CopyFile(src, dst)
    else:
        if not os.path.exists(dst):
            shutil.copy2(src, dst)

def CreateHardLink(src, dst):
    CreateHardLinkW = ctypes.windll.kernel32.CreateHardLinkW
    CreateHardLinkW.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_void_p)
    CreateHardLinkW.restype = ctypes.c_int
    if os.path.isdir(src):
        for fileName in os.listdir(src):
            if not os.path.exists(dst):
                os.mkdir(dst)
            subSrc = os.path.join(src, fileName)
            subDst = os.path.join(dst, fileName)
            CreateHardLink(subSrc, subDst)
    else:
        if not os.path.exists(dst):
            if CreateHardLinkW(dst, src, 0) == 0:
                raise IOError

def main():
    Print('Hello, Find downloads in "%s" :' % INPUT_DIR)
    totalCount = 0
    processedCount = 0
    for fileName in os.listdir(INPUT_DIR):
        totalCount = totalCount + 1
        Print('Process a file: "%s"' % fileName)
        fileInfo = GetDataFromTorrent(fileName)
        if fileInfo is None:
            continue
        sNewFileName = PrepareNewFileName(fileName, fileInfo)                               
        if sNewFileName:
            oldFilePath = os.path.join(INPUT_DIR, fileName)
            newFilePath = os.path.join(OUTPUT_DIR, sNewFileName)
            try:
                GetMoveAlgorithms()[MOVE_ALGORITHM](oldFilePath, newFilePath)
                processedCount = processedCount + 1
            except:
                Print("Error, Can't move file from %s to %s" % (oldFilePath, newFilePath))
    Print("%d friles were moved from %d total found files" % (processedCount, totalCount))

if __name__ == "__main__":
    main()

Работа скрипта проверялась на Windows 7 x86 и Windows 7 x64
В теории скрипт можно портировать и под Linux.

Скрипт заточен под rutracker, но возможно будет работать и с другими сайтами.
По крайней мере после доработки напильником.

Для скачивания html странички я использовал библиотеку urllib2.
А для парсинга .torrent файлов и html я использовал регулярные выражения.
Оказалось, что на Python их использовать весьма удобно.
В принципи для парсинга html лучше было бы использовать css селекторы, но пока и так сойдет.

Так как Python это не мой основной язык, то буду рад советам опытных разработчиков.

Update1:
На случай, если пример с файлом God.Bless.America.2011.HDTVRiP720.mkv Вам кажется не убедительным, привожу список топовых файлов с рутрекера.
Как по мне, то глаза и голову можно сломать пока поймешь что за фильм скрывается за одним из этих названий.
И естественно программы-каталогизаторы, наподобие AllMyMovies или Movienizer, не смогут в автоматическом режиме найти информацию об этих файлах в базах IMDB и Кинопоиск.

  1. Na_grani_DVDRp_[rutracker.org]_by_Inсh.avi
  2. Ohotniki za golovani.chopper887.mkv
  3. Shvatka.chopper887.mkv
  4. prizrachnyi.gonschik_2.2012.hdrip.ac3.1450mb.by.riperrr.avi
  5. Dom_grez_BDRip_dub_[rutracker.org]_by_Scarabey.avi
  6. Lubov.zhivet.tri.goda.2011.BDRip.avi
  7. Nepricosaemie.chopper887
  8. Missiya-Fantom.chopper887.avi
  9. Njanki.2012.O.DVDRip.IRONCLUB.avi
  10. samoubyici.2012.dvdrip.ac3.1450mb.by.riperrr.avi
  11. belyi.tigr.2012.dvdrip.ac3.2050mb.by.riperrr.avi
  12. Zhila.byla.odna.baba.2011.BDRip.1.46.avi
  13. svidanie.2012.dvdrip.ac3.1450mb.by.riperrr.avi
  14. Moy.Paren.Angel.BDRip.avi
  15. Visotskiy_Spasibo_Chto_Jivoy_2011_DVDRip_1.46_Menen.avi
  16. Неадекватные люди_1.46.avi
  17. Den.vyborov.2007.avi
  18. shapito.shou_1.2010.hdrip.ac3.1450mb.by.riperrr.avi
  19. belyi.tigr.2012.dvdrip.ac3.1450mb.by.riperrr.avi
  20. 2 дня_1.46.avi


Update2:
По предложениям в комментариях решил обновить скрипт:
  1. Перешел на Python 3
  2. Разделил входной и выходной каталоги
  3. Добавил поддержку создания HardLink-ов для NTFS
  4. Добавил поддержку копирования файлов
Теги:
Хабы:
+12
Комментарии84

Публикации

Изменить настройки темы

Истории

Работа

Data Scientist
62 вакансии
Python разработчик
135 вакансий

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн