forked from ThioJoe/Auto-Synced-Translated-Dubs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTrackAdder.py
252 lines (209 loc) · 11.9 KB
/
TrackAdder.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#========================================= USER SETTINGS ===============================================
# REMEMBER: Unlike the .ini config files, the variable values here must be surrounded by "quotation" marks
# The folder (or path relative to this script file) containing the audio track files to add to the video
# Note: ALL audio tracks in the folder will be added, so ensure only the tracks you want are in there
# A resulting copy of the original video, now with all tracks added, will also be placed in this folder
tracksFolder = r"output"
# The video can be anywhere as long as you use the full absolute filepath. Or you can use a relative path.
# The original will remain the same, and a copy with "MultiTrack" added to the name will be created in the output folder
# This script assumes the video is an mp4 file. I'm not sure if it will work with other formats/containers.
videoToProcess = r"whatever\path\here"
# Whether to merge a sound effect track into each audio track before adding to the video
# The original audio track files will remain unchanged
useSoundEffectsTrack = False
# If applicable, the filename of the sound effects or music track to add to each audio track before adding to the video
# If "useSoundEffectsTrack" is set to False, this will be ignored
# Must be in the same folder as the audio tracks!
effectsTrackFileName = r"your_sound_effects_file.mp3"
# Whether to save a copy of each audio track with the sound effects track merged into it
# They will go into a folder called "Merged Effects Tracks"
# Note: The original audio track files will always remain unchanged no matter this setting
saveMergedTracks = True
# The three letter language code for the default track. English = eng, Spanish = spa, etc
defaultLanguage = "eng"
#========================================================================================================
import subprocess as sp
import os
import pathlib
import sys
import shutil
# Note: Require ffmpepg to be installed and in the PATH environment variable
from pydub import AudioSegment
import langcodes
from utils import parseBool
# Auto fetch tracks from tracksFolder
tracksToAddDict = {}
for file in os.listdir(tracksFolder):
if (file.endswith(".mp3") or file.endswith(".aac") or file.endswith(".wav")) and file != effectsTrackFileName:
nameNoExt = os.path.splitext(file)[0]
# Get the language code from the end of the filename. Assumes the code will be separated by ' - '
if ' - ' in nameNoExt:
parsedLanguageCode = nameNoExt.split(' - ')[-1].strip()
else:
# Print error and ask whether to continue
print(f"\nWARNING: Could not find language code in filename: {file}")
print("\nTo read the language code, separate the language code from the rest of the filename with: ")
print(" ' - ' (a dash surrounded by spaces)")
print("For example: 'Whatever Video - en-us.wav'")
print("Enter 'y' to skip that track and conitnue, or enter anything else to exit.")
userInput = input("Continue Anyway? (y/n): ")
if userInput.lower() != 'y':
sys.exit()
# Check if the language code is valid
try:
langObject = langcodes.get(parsedLanguageCode)
threeLetterCode = langObject.to_alpha3()
languageDisplayName = langcodes.get(threeLetterCode).display_name()
if threeLetterCode in tracksToAddDict.keys():
print(f"\ERROR while checking {file}: Language '{languageDisplayName}' is already in use by file: {tracksToAddDict[threeLetterCode]}")
userInput = input("\nPress Enter to exit... ")
sys.exit()
tracksToAddDict[threeLetterCode] = file
except:
print(f"\nWARNING: Language code '{parsedLanguageCode}' is not valid for file: {file}")
print("Enter 'y' to skip that track and conitnue, or enter anything else to exit.")
userInput = input("\nContinue Anyway and Skip File? (y/n): ")
if userInput.lower() != 'y':
sys.exit()
print("")
#--------------------------------------------------------------------------------------------------------------
outputFile = f"{pathlib.Path(videoToProcess).stem} - MultiTrack.mp4"
# Convert each entry in tracksToAddDict to an absolute path and combine with tracksFolder
tracksFolder = os.path.normpath(tracksFolder)
soundEffectsDict = {'effects':effectsTrackFileName}
videoToProcess = os.path.join(tracksFolder, videoToProcess)
outputFile = os.path.join(tracksFolder, outputFile)
tempdir = os.path.join(tracksFolder, "temp")
mergedTracksDir = os.path.join(tracksFolder, "Merged Effects Tracks")
# Get number of tracks to add
numTracks = len(tracksToAddDict)
tempFilesToDelete = []
# Check if tracks are stereo, if not it will convert them to stereo before adding
def convert_to_stereo(tracksDict):
# Key is the language code, value is the relative file path to audio track
for langcode, fileName in tracksDict.items():
filePath = os.path.join(tracksFolder, fileName)
audio = AudioSegment.from_file(filePath)
# Check the number of channels in the audio file
num_channels = audio.channels
if num_channels == 1:
# Check if temp directory exists, if not create it
if not os.path.exists(tempdir):
os.makedirs(tempdir)
# Get the file extension of the file without the period
fileExtension = os.path.splitext(filePath)[1][1:]
# convert to stereo
stereo_file = audio.set_channels(2)
# save the stereo file
tempFilePath = f"{os.path.join(tempdir, fileName)}_stereo_temp.{fileExtension}" # Change this before publishing, needs to adapt to filetype
# Determine the format needed for pydub to export
if fileExtension == "aac":
formatString = "adts"
else:
formatString = fileExtension
# Export the file with appropriate format
stereo_file.export(tempFilePath, format=formatString, bitrate="128k") # Change this before publishing, needs to adapt to filetype
tracksDict[langcode] = tempFilePath
# Add to list of files to delete later when done, unless need to save merged tracks
if parseBool(useSoundEffectsTrack) and parseBool(saveMergedTracks) and langcode != "effects":
pass
else:
tempFilesToDelete.append(tempFilePath)
else:
# File is already stereo, so just use the original file
tracksDict[langcode] = filePath
return tracksDict
print("\nChecking if tracks are stereo...")
tracksToAddDict = convert_to_stereo(tracksToAddDict)
# Use pydub to combine the sound effects track with each audio track
if parseBool(useSoundEffectsTrack):
# Ensure the sound effects track is stereo, if not make it stereo
soundEffectsDict = convert_to_stereo(soundEffectsDict)
# Check if temp directory exists, if not create it
if not os.path.exists(tempdir):
os.makedirs(tempdir)
# Check if temporary files for tracks already exist and use those, if not create them
for langcode, filePath in tracksToAddDict.items():
if "_stereo_temp" not in filePath:
# Create the new filename and path for the temporary file
fileExtension = os.path.splitext(filePath)[1][1:]
fileName = os.path.basename(filePath)
appendString = "_temp."+fileExtension
tempFilePath = os.path.join(tempdir, fileName+appendString)
shutil.copy(filePath, tempFilePath)
tracksToAddDict[langcode] = tempFilePath
# Add to list of files to delete later when done, unless set to save merged tracks
if not parseBool(saveMergedTracks):
tempFilesToDelete.append(tempFilePath)
# Merge the sound effects into temporary track files
print("\nMerging sound effects...")
for langcode, trackFilePath in tracksToAddDict.items():
soundEffects = AudioSegment.from_file(soundEffectsDict['effects'])
audio = AudioSegment.from_file(trackFilePath)
combined = audio.overlay(soundEffects)
# Double check it is a temporary file
if "_temp" in trackFilePath:
# Get file extension
fileExtension = os.path.splitext(trackFilePath)[1][1:]
# Determine the format needed for pydub to export
if fileExtension == "aac":
formatString = "adts"
else:
formatString = fileExtension
combined.export(trackFilePath, format=formatString, bitrate="128k")
else:
print("\n\nERROR: The script did not create a temporary file - cannot overwrite original file.")
print("This should not happen and is a bug. Please report it here: https://github.com/ThioJoe/Auto-Synced-Translated-Dubs/issues")
userInput = input("\nPress Enter to exit... ")
sys.exit()
# If set to save merged tracks, move the temporary file to the tracks folder
if parseBool(saveMergedTracks):
# If the merged tracks directory does not exist, create it
if not os.path.exists(mergedTracksDir):
os.makedirs(mergedTracksDir)
# Get the filename from the temporary file path
tempFileName = os.path.basename(trackFilePath)
ext = os.path.splitext(trackFilePath)[1][1:]
# Remove the _temp from the filename, also remove double extensions
fileName = fileName.replace("_stereo_temp", "")
fileName = tempFileName.replace("_temp", "")
fileName = fileName.replace(f".{ext}.{ext}", f".{ext}")
# Insert effects to the filename before the last " - "
nameNoExt = os.path.splitext(fileName)[0]
parsedLanguageCode = nameNoExt.split(' - ')[-1].strip()
fileName = fileName.replace(parsedLanguageCode, f"With Effects - {parsedLanguageCode}")
# Create the new file path
newFilePath = os.path.join(mergedTracksDir, fileName)
# Move the file
shutil.move(trackFilePath, newFilePath)
# Add the new file path to the tracksToAddDict
tracksToAddDict[langcode] = newFilePath
# Create string for ffmpeg command for each string
#Example: sp.run(f'ffmpeg -i "video.mp4" -i "audioTrack.mp3" -map 0 -map 1 -metadata:s:a:0 language=eng -metadata:s:a:1 language=spa -codec copy output.mp4')
# In metadata, a=audio, s=stream, 0=first stream, 1=second stream, etc - Also: g=global container, c=chapter, p=program
trackStringsCombined = ""
mapList = "-map 0"
metadataCombined = f'-metadata:s:a:0 language={defaultLanguage} -metadata:s:a:0 title="{defaultLanguage}" -metadata:s:a:0 handler_name="{defaultLanguage}"'
count = 1
for langcode, filePath in tracksToAddDict.items():
languageDisplayName = langcodes.get(langcode).display_name()
trackStringsCombined += f' -i "{filePath}"'
metadataCombined += f' -metadata:s:a:{count} language={langcode}'
metadataCombined += f' -metadata:s:a:{count} handler_name={languageDisplayName}' # Handler shows as the track title in MPC-HC
metadataCombined += f' -metadata:s:a:{count} title="{languageDisplayName}"' # This is the title that will show up in the audio track selection menu
mapList += f' -map {count}'
count+=1
finalCommand = f'ffmpeg -i "{videoToProcess}" {trackStringsCombined} {mapList} {metadataCombined} -codec copy "{outputFile}"'
print("\n Adding audio tracks to video...")
sp.run(finalCommand)
# Delete temp files
print("\nDeleting temporary files...")
for file in tempFilesToDelete:
os.remove(file)
# Delete temp directory
try:
if os.path.exists(tempdir):
os.rmdir(tempdir)
except OSError as e:
print("Could not delete temp directory. It may not be empty.")
print("\nDone!")