As a heavy user of Nautilus and Filebot, i decided to integrate the two.
Following on from my previous article on column providers for nautilus, here as the additional functionality for nautilus filebot integration.
Its a basic threaded extension; for the most basic and most used operation, “Strict file renaming”.
It solves 95% of my usage scenarios. To find out how to use the code, see the article https://fio.ie/python-column-provider-nautilus/
Github: https://github.com/dmzoneill/filebot-nautilus
The code
#!/usr/bin/python
# dave@fio.ie
import os
import urllib
import logging
import re
import threading
import gi
gi.require_version('Nautilus', '3.0')
gi.require_version('Gtk', '3.0')
from gi.repository import Nautilus, GObject, Gtk, Gdk, GLib, GdkPixbuf
from hachoir_core.error import HachoirError
from hachoir_core.stream import InputIOStream
from hachoir_parser import guessParser
from hachoir_metadata import extractMetadata
from subprocess import Popen, PIPE
GObject.threads_init()
class VideoMetadataExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.MenuProvider, Nautilus.InfoProvider):
def __init__(self):
logging.basicConfig(filename='/tmp/VideoMetadataExtension.log',level=logging.DEBUG)
self.videomimes = [
'video/x-msvideo',
'video/mpeg',
'video/x-ms-wmv',
'video/mp4',
'video/x-flv',
'video/x-matroska'
]
self.win = None
def get_columns(self):
return (
Nautilus.Column(name="NautilusPython::video_width_columnn",attribute="video_width",label="Width",description="Video width"),
Nautilus.Column(name="NautilusPython::video_height_columnn",attribute="video_height",label="Height",description="Video height"),
)
def update_file_info_full(self, provider, handle, closure, file_info):
filename = urllib.unquote(file_info.get_uri()[7:])
video_width = ''
video_height = ''
name_suggestion = ''
file_info.add_string_attribute('video_width', video_width)
file_info.add_string_attribute('video_height', video_height)
file_info.add_string_attribute('name_suggestion', name_suggestion)
if file_info.get_uri_scheme() != 'file':
logging.debug("Skipped: " + filename)
return Nautilus.OperationResult.COMPLETE
for mime in self.videomimes:
if file_info.is_mime_type(mime):
GObject.idle_add(self.get_video_metadata, provider, handle, closure, file_info)
logging.debug("in Progress: " + filename)
return Nautilus.OperationResult.IN_PROGRESS
logging.debug("Skipped: " + filename)
return Nautilus.OperationResult.COMPLETE
def get_file_items_full(self, provider, window, files):
for mime in self.videomimes:
for file in files:
if file.get_uri_scheme() == 'file' and file.is_mime_type(mime):
top_menuitem = Nautilus.MenuItem(name='NautilusPython::Filebot', label='Filebot', tip='Filebot renamer')
submenu = Nautilus.Menu()
top_menuitem.set_submenu(submenu)
filebot_tvdb_menuitem = Nautilus.MenuItem(name='NautilusPython::FilebotRenameTVDB', label='Filebot TVDB', tip='Fetch names from TVDB')
filebot_tvdb_menuitem.connect('activate', self.filebot_activate_cb, files, 'tvdb')
submenu.append_item(filebot_tvdb_menuitem)
filebot_moviedb_menuitem = Nautilus.MenuItem(name='NautilusPython::FilebotRenameMoviewDB', label='Filebot MovieDB', tip='Fetch names from MovieDB')
filebot_moviedb_menuitem.connect('activate', self.filebot_activate_cb, files, 'moviedb')
submenu.append_item(filebot_moviedb_menuitem)
return top_menuitem,
def filebot_activate_cb(self, menu, files, source):
self.win = FileBotWindow(self, source, files)
self.win.connect("delete-event", Gtk.main_quit)
self.win.show_all()
Gtk.main()
def get_video_metadata(self, provider, handle, closure, file_info):
video_width = ''
video_height = ''
name_suggestion = ''
filename = urllib.unquote(file_info.get_uri()[7:])
filelike = open(filename, "rw+")
try:
filelike.seek(0)
except (AttributeError, IOError):
logging.debug("Unabled to read: " + filename)
Nautilus.info_provider_update_complete_invoke(closure, provider, handle, Nautilus.OperationResult.FAILED)
return False
stream = InputIOStream(filelike, None, tags=[])
parser = guessParser(stream)
if not parser:
logging.debug("Unabled to determine parser: " + filename)
Nautilus.info_provider_update_complete_invoke(closure, provider, handle, Nautilus.OperationResult.FAILED)
return False
try:
metadata = extractMetadata(parser)
except HachoirError:
logging.debug("Unabled to extract metadata: " + filename)
Nautilus.info_provider_update_complete_invoke(closure, provider, handle, Nautilus.OperationResult.FAILED)
return False
if metadata is None:
logging.debug("Metadata None: " + filename)
Nautilus.info_provider_update_complete_invoke(closure, provider, handle, Nautilus.OperationResult.FAILED)
return False
matchObj = re.search( r'Image width: (.*?) pixels', str(metadata), re.M|re.I)
if matchObj:
video_width = matchObj.group(1)
matchObj = re.search( r'Image height: (.*?) pixels', str(metadata), re.M|re.I)
if matchObj:
video_height = matchObj.group(1)
file_info.add_string_attribute('video_width', video_width)
file_info.add_string_attribute('video_height', video_height)
file_info.add_string_attribute('name_suggestion', name_suggestion)
logging.debug("Completed: " + filename)
file_info.invalidate_extension_info()
Nautilus.info_provider_update_complete_invoke(closure, provider, handle, Nautilus.OperationResult.COMPLETE)
return False
class FileBotWindow(Gtk.Window):
def __init__(self, videoMetadataExtension, source, files):
self.todo = len(files)
self.processing = 0
self.files = files
self.source = source
self.quit = False
self.videoMetadataExtension = videoMetadataExtension
Gtk.Window.__init__(self, title="Filebot operation")
self.set_size_request(200, 100)
self.set_border_width(10)
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.add(vbox)
self.working_label = Gtk.Label(label="Working")
vbox.pack_start(self.working_label, True, True, 0)
self.button = Gtk.Button(label="Cancel")
self.button.connect("clicked", self.on_button_clicked)
vbox.pack_start(self.button, True, True, 0)
#GObject.timeout_add_seconds(1, self.process, files, source)
self.update = threading.Thread(target=self.process)
self.update.setDaemon(True)
self.update.start()
def process(self):
for file in self.files:
if file.get_uri_scheme() != 'file':
continue
filename = urllib.unquote(file.get_uri()[7:])
self.filebot_process(filename)
def on_button_clicked(self, widget):
self.quit = True
self.close()
def filebot_process(self, filename):
if self.quit == True:
return
self.processing = self.processing + 1
text = "Processing (" + str(self.processing) + "/" + str(self.todo) + ") " + os.path.basename(filename)
GObject.idle_add( self.working_label.set_text, text, priority=GObject.PRIORITY_DEFAULT )
p = Popen(['filebot', '-rename', filename,'--db', self.source], stdin=PIPE, stdout=PIPE, stderr=PIPE)
output, err = p.communicate(b"input data that is passed to subprocess' stdin")
rc = p.returncode
if self.processing == self.todo:
GObject.idle_add( self.close, priority=GObject.PRIORITY_DEFAULT )