diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 6b7140df..8fa9f0c3 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -795,6 +795,18 @@ +
+
+ +
+
+
+
+ + + Set equal to the number of cores, or 0 for auto +
+
Audio Properties diff --git a/headphones/__init__.py b/headphones/__init__.py index 2066e5f9..286c59f4 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -218,6 +218,8 @@ ENCODEROUTPUTFORMAT = None ENCODERQUALITY = None ENCODERVBRCBR = None ENCODERLOSSLESS = False +ENCODER_MULTICORE = False +ENCODER_MULTICORE_COUNT = 0 DELETE_LOSSLESS_FILES = False PROWL_ENABLED = True PROWL_PRIORITY = 1 @@ -338,7 +340,7 @@ def initialize(): NZBSORG, NZBSORG_UID, NZBSORG_HASH, NZBSRUS, NZBSRUS_UID, NZBSRUS_APIKEY, OMGWTFNZBS, OMGWTFNZBS_UID, OMGWTFNZBS_APIKEY, \ NZB_DOWNLOADER, TORRENT_DOWNLOADER, PREFERRED_WORDS, REQUIRED_WORDS, IGNORED_WORDS, LASTFM_USERNAME, \ INTERFACE, FOLDER_PERMISSIONS, FILE_PERMISSIONS, ENCODERFOLDER, ENCODER_PATH, ENCODER, XLDPROFILE, BITRATE, SAMPLINGFREQUENCY, \ - MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, ENCODERVBRCBR, ENCODERLOSSLESS, DELETE_LOSSLESS_FILES, \ + MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, ENCODERVBRCBR, ENCODERLOSSLESS, ENCODER_MULTICORE, ENCODER_MULTICORE_COUNT, DELETE_LOSSLESS_FILES, \ PROWL_ENABLED, PROWL_PRIORITY, PROWL_KEYS, PROWL_ONSNATCH, PUSHOVER_ENABLED, PUSHOVER_PRIORITY, PUSHOVER_KEYS, PUSHOVER_ONSNATCH, PUSHOVER_APITOKEN, MIRRORLIST, \ TWITTER_ENABLED, TWITTER_ONSNATCH, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_PREFIX, \ PUSHBULLET_ENABLED, PUSHBULLET_APIKEY, PUSHBULLET_DEVICEID, PUSHBULLET_ONSNATCH, \ @@ -535,6 +537,8 @@ def initialize(): ENCODERQUALITY = check_setting_int(CFG, 'General', 'encoderquality', 2) ENCODERVBRCBR = check_setting_str(CFG, 'General', 'encodervbrcbr', 'cbr') ENCODERLOSSLESS = bool(check_setting_int(CFG, 'General', 'encoderlossless', 1)) + ENCODER_MULTICORE = bool(check_setting_int(CFG, 'General', 'encoder_multicore', 0)) + ENCODER_MULTICORE_COUNT = max(0, check_setting_int(CFG, 'General', 'encoder_multicore_count', 0)) DELETE_LOSSLESS_FILES = bool(check_setting_int(CFG, 'General', 'delete_lossless_files', 1)) PROWL_ENABLED = bool(check_setting_int(CFG, 'Prowl', 'prowl_enabled', 0)) @@ -1009,6 +1013,8 @@ def config_write(): new_config['General']['encoderquality'] = ENCODERQUALITY new_config['General']['encodervbrcbr'] = ENCODERVBRCBR new_config['General']['encoderlossless'] = int(ENCODERLOSSLESS) + new_config['General']['encoder_multicore'] = int(ENCODER_MULTICORE) + new_config['General']['encoder_multicore_count'] = int(ENCODER_MULTICORE_COUNT) new_config['General']['delete_lossless_files'] = int(DELETE_LOSSLESS_FILES) new_config['General']['mirror'] = MIRROR diff --git a/headphones/music_encoder.py b/headphones/music_encoder.py index f7a3d624..ed0f67cd 100644 --- a/headphones/music_encoder.py +++ b/headphones/music_encoder.py @@ -17,6 +17,7 @@ import os import headphones import shutil import time +import multiprocessing import subprocess from headphones import logger @@ -100,6 +101,7 @@ def encode(albumPath): i=0 encoder_failed = False + jobs = [] for music in musicFiles: infoMusic=MediaFile(music) @@ -131,15 +133,45 @@ def encode(albumPath): encode = True # encode if encode: - if not command(encoder,music,musicTempFiles[i],albumPath): - encoder_failed = True - break + job = (encoder, music, musicTempFiles[i], albumPath) + jobs.append(job) else: musicFiles[i] = None musicTempFiles[i] = None i=i+1 + # Encode music files + if len(jobs) > 0: + if headphones.ENCODER_MULTICORE: + if headphones.ENCODER_MULTICORE_COUNT == 0: + processes = multiprocessing.cpu_count() + else: + processes = headphones.ENCODER_MULTICORE_COUNT + + logger.debug("Multi-core encoding enabled, %d processes" % processes) + else: + processes = 1 + + # Use multiprocessing only if it's worth the overhead. and if it is + # enabled. If not, then use the old fashioned way. + if processes > 1: + pool = multiprocessing.Pool(processes=processes) + results = pool.map_async(command_map, jobs) + + # No new processes will be created, so close it and wait for all + # processes to finish + pool.close() + pool.join() + + # Retrieve the results + results = results.get() + else: + results = map(command_map, jobs) + + # The results are either True or False, so determine if one is False + encoder_failed = not all(results) + musicFiles = filter(None, musicFiles) musicTempFiles = filter(None, musicTempFiles) @@ -187,7 +219,10 @@ def encode(albumPath): logger.info('Encoding for folder %s is not required' % (albumPath.decode(headphones.SYS_ENCODING, 'replace'))) return musicFinalFiles - + +def command_map(args): + return command(*args) + def command(encoder,musicSource,musicDest,albumPath): cmd=[] diff --git a/headphones/webserve.py b/headphones/webserve.py index 2de324af..6532c121 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -930,6 +930,8 @@ class WebInterface(object): "encodervbrcbr": headphones.ENCODERVBRCBR, "encoderquality": headphones.ENCODERQUALITY, "encoderlossless": checked(headphones.ENCODERLOSSLESS), + "encoder_multicore": checked(headphones.ENCODER_MULTICORE), + "encoder_multicore_count": int(headphones.ENCODER_MULTICORE_COUNT), "delete_lossless_files": checked(headphones.DELETE_LOSSLESS_FILES), "prowl_enabled": checked(headphones.PROWL_ENABLED), "prowl_onsnatch": checked(headphones.PROWL_ONSNATCH), @@ -1016,7 +1018,7 @@ class WebInterface(object): pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None, preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None, file_permissions=None, folder_permissions=None, plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, - plex_password=None, plex_update=0, plex_notify=0, songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, **kwargs): + plex_password=None, plex_update=0, plex_notify=0, songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, **kwargs): headphones.HTTP_HOST = http_host headphones.HTTP_PORT = http_port @@ -1124,6 +1126,8 @@ class WebInterface(object): headphones.ENCODERVBRCBR = encodervbrcbr headphones.ENCODERQUALITY = int(encoderquality) headphones.ENCODERLOSSLESS = int(encoderlossless) + headphones.ENCODER_MULTICORE = encoder_multicore + headphones.ENCODER_MULTICORE_COUNT = max(0, int(encoder_multicore_count)) headphones.DELETE_LOSSLESS_FILES = int(delete_lossless_files) headphones.PROWL_ENABLED = prowl_enabled headphones.PROWL_ONSNATCH = prowl_onsnatch