Skip to content

Commit f0e6b92

Browse files
authored
cue_playlist without volume/end detection
1 parent 792f035 commit f0e6b92

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

nocue_playlist.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#!/usr/bin/python3
2+
3+
import subprocess, argparse, os, os.path, random, string, tempfile, csv, re, shutil
4+
from pathlib import Path
5+
6+
FFMPEG = "/usr/local/bin/ffmpeg"
7+
MEZZANINE = "-acodec libfdk_aac -vbr 5 -ac 2 -map 0:a"
8+
MD5HashRE = re.compile(r'(?i)(?<![a-z0-9])[a-f0-9]{32}(?![a-z0-9])')
9+
10+
def analyse(filename, mezzanine=None, forceEncode=False):
11+
# Encode and store a mezzanine file, if a mezzanine directory name is given
12+
13+
print("Processing filename: %s" % filename)
14+
# If we're being asked to create a mezzanine file, we need to make a unique suffix for this file
15+
# but the suffix MUST be related to the file's contents, to be able to identify the file
16+
# so that we do not waste time encoding it twice. FFmpeg provides a hash function for this.
17+
# Incidentally, this might be a way of detecting duplicate tracks, too.
18+
if mezzanine:
19+
hashout = str(subprocess.check_output([FFMPEG, "-v", "quiet", "-hide_banner", "-i", filename, "-vn", \
20+
"-map", "0:a", "-f", "hash", "-hash", "MD5", "-"], stderr=subprocess.STDOUT))[6:-3]
21+
22+
print("MD5 hash is: %s" % hashout)
23+
#randomString = ''.join(random.choice(string.ascii_letters) for i in range(6))
24+
25+
# But! If this filename already contains a hash, we need to remove it before adding this one.
26+
# A hash exists in the between the second-to-last . and the last .
27+
# and has 32 characters from 0-9,a-f
28+
29+
origBaseName = os.path.basename(filename)
30+
searchCheck = re.search(MD5HashRE, origBaseName)
31+
print("Debug: searchcheck = %s" % searchCheck)
32+
33+
if searchCheck:
34+
# Remove the existing hash
35+
# No action required if there isn't any hash in the first place
36+
baseNameNoHash = origBaseName.replace('.' + searchCheck.group(0), '')
37+
print("Debug: name without hash: %s" % baseNameNoHash)
38+
origBaseName = baseNameNoHash
39+
40+
41+
baseName = os.path.splitext(origBaseName)[0] + "." + hashout + ".mka"
42+
print("Debug: name with replaced hash: %s" % baseName)
43+
44+
mezzanineName = os.path.join(mezzanine, baseName)
45+
print("Mezzanine name is: %s" % mezzanineName)
46+
47+
# Now we must detect if this file has already been converted
48+
# What we're interested in comparing is the string between the penultimate '.'
49+
# and the final '.'
50+
# Create a list with any filenames matching the hash.
51+
# If it contains an entry, the track has already been converted, and we
52+
# need to abandon the process.
53+
print("Looking for file containing hash.")
54+
p = Path(mezzanine+'/')
55+
findMe = mezzanine + '/' + '[FILENAME]' + hashout + '*.mka'
56+
print("Path object is:", p)
57+
print("Glob search is:", findMe)
58+
pl = list(p.glob('*'+hashout+'*.mka'))
59+
if len(pl) != 0: # There is already a file with this hash
60+
print("This hash already exists! Not encoding.")
61+
return(None)
62+
print("No file found with that hash. Encoding.")
63+
64+
else:
65+
mezzanineName = None
66+
67+
# At this point, the file of interest is EITHER the original file, OR a mezzanine name.
68+
if mezzanine:
69+
print("Creating mezzanine file.")
70+
71+
# We need a temporary filename for FFmpeg to write to. We can't write metadata in place, because
72+
# the position of other elements in the file would change.
73+
temporaryFile = tempfile.NamedTemporaryFile(delete=False).name + ".mka"
74+
test = str(subprocess.check_output([FFMPEG, "-hide_banner", "-i", filename, \
75+
"-vn", "-acodec", "copy", temporaryFile], stderr=subprocess.STDOUT)).split('\\n')
76+
shutil.move(temporaryFile, mezzanineName)
77+
else:
78+
print("We are NOT creating a new file.")
79+
80+
return({"mezzanine_name": mezzanineName})
81+
82+
83+
# What's the command?
84+
parser = argparse.ArgumentParser(description="Create start and end-of-track annotations for playlist.",
85+
epilog="For support, contact john@johnwarburton.net")
86+
parser.add_argument("playlist", help="Playlist file to be processed")
87+
parser.add_argument("-o", "--output", help="Output filename (default: '-processed' suffix)", type=str)
88+
parser.add_argument("-m", "--mezzanine", help="Directory for mezzanine-format files", type=str)
89+
args = parser.parse_args()
90+
91+
playlist = args.playlist
92+
93+
# Construct default output filename if needed
94+
if args.output:
95+
outfile = args.output
96+
else:
97+
outfile = os.path.splitext(playlist)[0] + "-processed.m3u8"
98+
99+
# Check mezzanine directory name and create if needed
100+
if args.mezzanine:
101+
# Convert given path to an absolute path
102+
mezzanine = os.path.abspath(args.mezzanine)
103+
try:
104+
os.makedirs(mezzanine, exist_ok=True)
105+
print("Created directory %s for output audio files." % mezzanine)
106+
except OSError:
107+
print("Sorry, the directory %s is weird. Might be a file?" % mezzanine)
108+
exit(1)
109+
else:
110+
mezzanine = None
111+
112+
print("Working on playlist: %s" % playlist)
113+
print("Writing to %s" % outfile)
114+
115+
with open(playlist) as i:
116+
playlistLines = i.readlines()
117+
118+
print("We have read %s items." % len(playlistLines))
119+
120+
with open(outfile, mode="w") as out:
121+
out.write("#EXTM3U\n")
122+
123+
for item in playlistLines:
124+
# Skip the M3U indicator
125+
if item == "#EXTM3U\n":
126+
continue
127+
result = analyse(filename=item.strip(), mezzanine=mezzanine, forceEncode=False)
128+
# analyse() returns None if the audio has already been converted.
129+
# At this point, we can skip writing a new line to the playlist, because the file is already
130+
# extant, and must have been referenced already within the playlist we're creating.
131+
if result==None:
132+
continue
133+
if result["mezzanine_name"]:
134+
# Remember, a file read in lines has a newline on the end of every line
135+
item = result["mezzanine_name"] + '\n'
136+
137+
assembly = item
138+
print("Writing line:")
139+
print(assembly)
140+
out.write(assembly)
141+
# Fingerprinting is now in a separate program
142+
#fing = fingerprint(item.strip())
143+
#fd = open('database.csv', 'a')
144+
#csvWriter = csv.writer(fd)
145+
#csvWriter.writerow([item.strip(), fing])
146+
#fd.close()
147+
#print("Fingerprint is:")
148+
#print(fing)
149+
print("Done.")
150+
151+
152+

0 commit comments

Comments
 (0)