Tôi thích câu trả lời của Neil nhất, vì nó không đưa ra mối tương quan giữa các tệp âm thanh: chỉ cần chọn một mức tăng và điều chỉnh mọi thứ theo nó.
Tuy nhiên tôi có một số vấn đề khi phân tích cú pháp đầu ra normalize-ogg
với một số tệp tôi có. Ngoài ra còn có một vấn đề khó chịu với bc
: nó không làm tròn thực sự, nó chỉ cắt ngắn.
Vì vậy, cuối cùng tôi đã từ bỏ kịch bản shell và chuyển sang python.
Lưu ý1: phần exiftool có thể quá mức cần thiết nhưng tôi muốn chắc chắn 100% rằng bitrate gốc sẽ được bảo toàn.
Lưu ý2: điều này sẽ ghi đè lên bản gốc, nếu bạn muốn giữ chúng, hãy sử dụng --backup trong cuộc gọi cuối cùng để chuẩn hóa-ogg. Nhưng tôi thấy thực tế hơn để giữ một bản sao trong một thư mục riêng biệt, an toàn hơn.
Lưu ý 3: giải pháp này xử lý các tệp ogg, nhưng việc điều chỉnh nó thành mp3 không quan trọng, chỉ cần thay thế các lần xuất hiện của "ogg" bằng "mp3".
Đây là vấn đề của tôi. Phiên bản mới nhất có thể được tìm thấy ở đây: regain.py
#!/usr/bin/python3
"""
Parallel normalize gains
"""
'
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'
# Absolute value, in dB for the desired gain of each file
TARGET_GAIN = -12
#
MAX_THREADS = 2
from subprocess import Popen, PIPE
from multiprocessing.dummy import Pool as ThreadPool
from os import listdir
import logging
def initlogger(logfile="log.log", mainlevel=logging.DEBUG,
filelevel=logging.DEBUG, consolelevel=logging.DEBUG):
'''initlogger'''
# create logger
logger = logging.getLogger()
logger.setLevel(mainlevel)
# create file handler which logs even debug messages
fh = logging.FileHandler(logfile)
fh.setLevel(filelevel)
# create console handler also logging at DEBUG level
ch = logging.StreamHandler()
ch.setLevel(consolelevel)
# create formatter and add it to the handlers
formatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s")
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(fh)
logger.addHandler(ch)
def logcommand(command=[]):
'''logcommand'''
if not isinstance(command, list):
return "", "", -1
logging.info("Command:\n" + " ".join(command) + "\n")
proc = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE)
output, err = proc.communicate()
output = output.decode("utf-8")
err = err.decode("utf-8")
logging.info("Output:\n" + output + "\n")
logging.info("Error:\n" + err + "\n")
logging.info("Return Code:\n" + str(proc.returncode) + "\n")
return output, err, proc.returncode
def regain(target):
'''regain'''
logging.info("============================ Start File ============================")
logging.warning(target["name"])
logging.info("Extracting gain info.\n")
commandgetlevels = ['normalize-ogg', '-n', target["name"]]
output, err, retcode = logcommand(commandgetlevels)
level = output.split()[0]
logging.debug("Level: " + level)
if "dBFS" in level:
level = level.split("dBFS")[0]
level = level.replace(',', '.')
level = int(round(float(level)))
delta = target["gain"] - level
logging.info("Required adjustment: " + str(delta) + "\n")
if delta is 0:
logging.warning(target["name"] + " is already at the correct level")
return 0
logging.info("Extracting average bitrate.\n")
commandgetinfo = ['exiftool', target["name"]]
output, err, retcode = logcommand(commandgetinfo)
bitrate = '0'
for line in output.split('\n'):
if 'Nominal Bitrate' in line:
bitrate = line.split(':')[1].split()[0]
break
logging.info("Average bitrate is: " + str(bitrate) + "\n")
if bitrate is '0':
logging.error("No valid bitrate found, aborting conversion.\n")
exit(-1)
logging.info("Re-normalizing.\n")
commandrenormalize = ['normalize-ogg', '--ogg', '--bitrate', bitrate,
'-g', str(delta) + 'db', target["name"]]
output, err, retcode = logcommand(commandrenormalize)
if retcode is not 0:
log.error("Output:\n" + output)
log.error("err:\n" + err)
exit(retcode)
return retcode
# function to be mapped over
def parallelregain(gain=TARGET_GAIN, threads=MAX_THREADS):
'''parallelregain'''
logging.info("Creating thread pool with " + str(threads) + " elements.\n")
pool = ThreadPool(threads)
targets = []
files_list = listdir(".")
files_list.sort()
counter = 0
for filename in files_list:
if filename.endswith("ogg"):
target = {
"name":filename,
"gain":gain,
}
targets.append(target)
counter = counter + 1
pool.map(regain, targets)
pool.close()
pool.join()
if __name__ == "__main__":
initlogger(logfile="normalize.log", consolelevel=logging.WARNING)
parallelregain()