Skip to content

Instantly share code, notes, and snippets.

@onedr0p
Last active June 8, 2024 22:09
Show Gist options
  • Save onedr0p/c88f517a55f77ed154cdb00009463a47 to your computer and use it in GitHub Desktop.
Save onedr0p/c88f517a55f77ed154cdb00009463a47 to your computer and use it in GitHub Desktop.
A simple script to convert media files to MP4 for a better Plex experience
# Python 2.7
import os
import sys
import time
import platform
import subprocess
import urllib2
import logging
""" Are we windows or linux """
is_windows = any(platform.win32_ver())
""" Edit the following values to your liking, pay special attention to the media_path, plex_url and plex_token values """
# Paths to ffmpeg, handbrake-cli and your log file
# If you need help finding your install points in Linux, try 'which ffmpeg' and 'which handbrake'
# Make sure you install the following on your platform, ffmpeg, handbrake AND handbrake-cli
ffmpeg_cli = '/usr/bin/ffmpeg' # 'C:\ffmpeg\bin\ffmpeg.exe'
handbrake_cli = '/usr/bin/HandBrakeCLI' # 'C:\Program Files\HandBrake\HandBrakeCLI.exe'
# Put log file here
if is_windows:
log_file = os.path.expanduser('~/Desktop/simple_convert.log')
else:
log_file = os.path.expanduser('~/simple_convert.log')
# Max media files to convert
max_convert_items = 0
# File types to convert
file_types = ('.avi', '.flv', '.mkv', '.mpeg')
# Plex Server Token - See URL below inorder to obtain your Token
# https://support.plex.tv/hc/en-us/articles/204059436-Finding-your-account-token-X-Plex-Token
enable_plex_update = False
plex_url = '{YOUR PLEX IP ADDRESS}:32400'
plex_token = '{YOUR PLEX TOKEN}'
""" Don't change the following unless you know what you are doing!! """
""" Set up the logger """
logging.basicConfig(filename=log_file, level=logging.INFO)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s: %(name)-12s - %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
logger = logging.getLogger('simple_convert')
""" Update Plex Server """
def update_plex():
logger.info("plex - sending request to update Plex")
url = 'http://%s/library/sections/all/refresh?X-Plex-Token=%s' % (plex_url, plex_token)
try:
urllib2.urlopen(url).read()
except urllib2.HTTPError, e:
logger.warning("plex - unable to make request to Plex - HTTP Error %s", str(e.code))
except urllib2.URLError, e:
logger.warning("plex - unable to make request to Plex - URL Error %s", e.reason)
else:
logger.info("plex - update successful")
""" Build a array of files to convert """
def find_media_files(media_path):
unconverted = []
for root, dirs, files in os.walk(media_path):
for file in files:
if file.startswith('.'):
continue
if file.endswith(file_types):
old_file = os.path.join(root, file)
old_file_size = os.path.getsize(old_file)
new_file = os.path.splitext(old_file)[0]+'.mp4'
media_file = {
'old_file': old_file,
'old_file_size': old_file_size,
'new_file': new_file
}
unconverted.append(media_file)
sorted_unconvered = sorted(unconverted, key=lambda k: k['old_file'])
return sorted_unconvered[:max_convert_items] if max_convert_items else sorted_unconvered
""" Convert files found to mp4 using ffmeg """
def convert_ffmpeg(input_file, output_file):
logger.info("ffmpeg - converting %s to %s", input_file, output_file)
try:
dev_null = open(os.devnull, 'w')
return_code = subprocess.call([
ffmpeg_cli,
'-n',
'-fflags', '+genpts',
'-i', input_file,
'-vcodec', 'copy',
'-acodec', 'aac',
'-strict', '-2',
output_file
], stdout=dev_null, stderr=dev_null)
# If the return code is 1 that means ffmpeg failed, use handbrake instead
if return_code == 1:
logger.warning("ffmpeg - failure converting %s", os.path.basename(input_file))
remove_media_file(output_file)
convert_handbrake(input_file, output_file)
except OSError as e:
if e.errno == os.errno.ENOENT:
logger.warning("ffmpeg not found, install on your system to use this script")
sys.exit(0)
else:
logger.info("ffmpeg - converting successful: %s", os.path.basename(input_file))
""" Convert files found to mp4 using HandBrakeCLI """
def convert_handbrake(input_file, output_file):
logger.info("handbrakeCLI - converting %s to %s", input_file, output_file)
try:
dev_null = open(os.devnull, 'w')
return_code = subprocess.call([
handbrake_cli,
'-i', input_file,
'-o', output_file,
'-f', 'mp4',
'--loose-anamorphic',
'--modulus', '2',
'-e', 'x264',
'-q', '19',
'--cfr',
'-a', '1',
'-E', 'faac',
'-6', 'dp12',
'-R', 'Auto',
'-B', '320',
'-D', '0',
'--gain', '0',
'--audio-copy-mask', 'none',
'--audio-fallback', 'ffac3',
'-x', 'level=4.0:ref=16:bframes=16:b-adapt=2:direct=auto:me=tesa:merange=24:subq=11:rc-lookahead=60:analyse=all:trellis=2:no-fast-pskip=1'
], stdout=dev_null, stderr=dev_null)
# If the return code is 1 that means handbrakeCLI failed
if return_code == 1:
logger.warning("handbrakeCLI - failure converting %s", os.path.basename(input_file))
remove_media_file(output_file)
sys.exit(0)
except OSError as e:
if e.errno == os.errno.ENOENT:
logger.warning("handbrakeCLI not found, install on your system to use this script")
sys.exit(0)
else:
logger.info("handbrakeCLI - converting successful for %s", os.path.basename(input_file))
""" Remove files quietly if they don't exist """
def remove_media_file(filename):
try:
os.remove(filename)
except OSError as e:
if e.errno != os.errno.ENOENT:
raise
else:
logger.info("system - deleted file %s", os.path.basename(filename))
""" Main Application """
def main(argv):
if len(argv) == 1:
path, binary = os.path.split(argv[0])
print "Usage: {} [directory ...]".format(binary)
sys.exit(0)
media_path = argv[1]
if not os.path.exists(media_path):
logger.error("Unable to find directory: %s", media_path)
sys.exit(0)
media_files = find_media_files(media_path)
logger.info("%d total files to convert", len(media_files))
i = 1
for item in media_files:
logger.info("converting %d of %d items", i, len(media_files))
try:
convert_ffmpeg(item['old_file'], item['new_file'])
except KeyboardInterrupt:
remove_media_file(item['new_file'])
new_file_size = os.path.getsize(item['new_file'])
# Remove old file if successful
if (new_file_size >= item['old_file_size']):
remove_media_file(item['old_file'])
# Remove new file if failure, run handbrake instead
elif (new_file_size < (item['old_file_size'] * .75)):
logger.warning("ffmpeg - failure converting %s", os.path.basename(item['new_file']))
remove_media_file(item['new_file'])
try:
convert_handbrake(item['old_file'], item['new_file'])
except KeyboardInterrupt:
remove_media_file(item['new_file'])
# Remove old file if successful
elif (new_file_size < item['old_file_size']):
remove_media_file(item['old_file'])
# Update Plex
if enable_plex_update == True:
update_plex()
# Keep a counter of item processed
i = i + 1
if __name__ == '__main__':
main(sys.argv)
@onedr0p
Copy link
Author

onedr0p commented Jul 14, 2016

Fix incoming for the following error

Traceback (most recent call last):
  File "simple_convert.py", line 187, in <module>
    main()
  File "simple_convert.py", line 153, in main
    convert_ffmpeg(item['old_file'], item['new_file'])
  File "simple_convert.py", line 95, in convert_ffmpeg
    ], stdout=dev_null, stderr=dev_null)
  File "/usr/lib/python2.7/subprocess.py", line 541, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['/usr/bin/ffmpeg', '-n', '-fflags', '+genpts', '-i', '/wash/Movies/Films/Pi (1998)/Pi (1998).avi', '-vcodec', 'copy', '-acodec', 'aac', '-strict', '-2', '/wash/Movies/Films/Pi (1998)/Pi (1998).mp4']' returned non-zero exit status 1

Edit:
Fixed is the following code in convert_ffmpeg:

        # If the return code is 1 that means ffmpeg failed, use handbrake instead
        if return_code == 1:
            remove_media_file(output_file)
            convert_handbrake(input_file, output_file)

@raindropworks
Copy link

May want to make a comment near the top. Found out why I thought my handbrake was different from yours. Turned out handbrake and the handbrake-cli are two different programs, so maybe something like this:

# For Debian/Ubuntu users, verify dependencies by running as root 'apt-get install python ffmpeg handbrake handbrake-cli

I did the first three myself, and then after having one video fail from ffmpeg, finding out the hard way that handbrake just installed the gui version until i manually added the second.

@onedr0p
Copy link
Author

onedr0p commented Jul 14, 2016

Thanks, I'll just make a comment to install the handbrake-cli since I'm trying to be platform agnostic.

@onedr0p
Copy link
Author

onedr0p commented Jul 14, 2016

Incoming fix for:

simple_convert: INFO     Converting /wash/Movies/Films/Class Of 1984 (1982)/._Class Of 1984 (1982).avi to /wash/Movies/Films/Class Of 1984 (1982)/._Class Of 1984 (1982).mp4 using ffmpeg
Traceback (most recent call last):
  File "simple_convert.py", line 188, in <module>
    main()
  File "simple_convert.py", line 154, in main
    convert_ffmpeg(item['old_file'], item['new_file'])
  File "simple_convert.py", line 94, in convert_ffmpeg
    remove_media_file(output_file)
  File "simple_convert.py", line 140, in remove_media_file
    if e.errno != errno.ENOENT:
NameError: global name 'errno' is not defined```

Updated the function remove_media_file:

""" Remove files quietly if they don't exist """
def remove_media_file(filename):
    try:
        os.remove(filename)
    except OSError as e:
        if e.errno != os.errno.ENOENT:
            raise

@onedr0p
Copy link
Author

onedr0p commented Jul 15, 2016

Fix for

Traceback (most recent call last):
  File "simple_convert.py", line 195, in <module>
    main()
  File "simple_convert.py", line 164, in main
    new_file_size = os.path.getsize(item['new_file'])
  File "/usr/lib/python2.7/genericpath.py", line 57, in getsize
    return os.stat(filename).st_size
OSError: [Errno 2] No such file or directory: '/wash/Movies/Films/Hot Tub Time Machine 2 (2015)/._Hot Tub Time Machine 2 (2015).mp4'

Ignore dotfiles in the get_media_items function

@onedr0p
Copy link
Author

onedr0p commented Jul 15, 2016

Script now takes 1 parameter: the directory to scan for files

@DanMossa
Copy link

DanMossa commented Nov 30, 2016

Is there anyway to do a bit of compression during the conversion?

Also, might wanna write somewhere in the comments that this is for Python2.7

@onedr0p
Copy link
Author

onedr0p commented Dec 20, 2016

Do you have any suggestions for compression, specifically the command line arguments for ffmpeg or handbrake? I can update the script to take an additional parameter when we determine a preset compression command.

@arkestlerdev
Copy link

arkestlerdev commented Sep 29, 2017

Hi there, where do I place this script and how is this script used/called? Also, is there a way to send an email notification on each file processed? Thank you!

@ghirotre
Copy link

Hi, Is it possible to transcode all files that are not h264 or x264?
Now the scrypt in my server convert all file in .mp4, but for example, the .avi files in xvid are not converted in h264 but they remain xvid in .mp4
Thanks, it is a super scrypt

@ghirotre
Copy link

or at least if possible decide in the settings of the script if all *.avi are converted with handbrake
Very thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment