-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLVLR.py
More file actions
1807 lines (1437 loc) · 73.3 KB
/
LVLR.py
File metadata and controls
1807 lines (1437 loc) · 73.3 KB
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env pythonw
#!/usr/bin/env ffmpeg
#################################
# NPR Leveler
# NPR Labs, Copyright 2015
# Provides a platform-agnostic user interface for analyzing and
# adjusting audio files to enforce compliance with EBU Loudness
# standards.
#
# This version is for DEBUGGING PURPOSES ONLY and has limited
# functionality.
#
# Project Leaders: Chris Nelson and Alice Goldfarb
# Principle Developers: Olivia Waring and Alice Goldfarb
# Installation assistance by Ty Von Plinsky
# Logo Artwork by Alice Goldfarb
#################################
# Dependencies:
import wx
import time
import os
import sys
import re
import subprocess
import shutil
from random import randint
from os.path import expanduser
from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin
from wx.lib.agw import ultimatelistctrl as ULC
import wx.lib.agw
import wx.html
import collections
import logging
from wx.lib.delayedresult import startWorker
app = wx.App(False)
work_dir = "/Applications"
log_name = work_dir + "/LVLR.app/Contents/my_log_file.txt"
log = open(log_name, 'w')
sys.stdout = log
sys.stderr = log
print "work_dir :=\n\t" + work_dir
command = work_dir + "/LVLR.app/Contents/MacOS/bin/ffmpeg"
print command
com = "echo $PATH"
print com
output_name = work_dir + "/LVLR.app/Contents/output_file_resultAnalyzeProducer.txt" ##
output_file = open(output_name, 'w') ##
print output_file
#a = subprocess.Popen(['PATH=$PATH'], bufsize=1, stdout=output_file, stderr=output_file)
## proc = subprocess.Popen(['echo', '$PATH'], bufsize=1, stdout=output_file, stderr=output_file)
#a = subprocess.call(['$PATH'], bufsize=1, stdout=output_file, stderr=output_file)
for d in sys.path:
print d
print "\n"
sys.stdout.flush()
# Default settings:
TARGET_IL_DEFAULT = -24
TARGET_PEAK_DEFAULT = -2
GRACE_DEFAULT = 2 # How far from the targetIL counts as 'within spec.'
ADJ_FILE_LOC_DEFAULT = os.getcwd()
LOG_FILE_LOC_DEFAULT = expanduser("~")
OVERWRITE_DEFAULT = True
SAME_FOLDER_DEFAULT = True
ADVANCED_ACCESS_DEFAULT = False
# OS-specific settings:
ffmpegEXE = command # Windows: os.getcwd() + "\\FFMPEG\\bin\\ffmpeg.exe"
SEPARATOR = '/' # Windows: '\\'
# Dictionary entry indices:
FILE_NAME = 0
MEETS_SPECS = 1
INT_LOUD = 2
T_PEAK = 3
S_PEAK = 4
LOUDNESS_RANGE = 5
BITRATE = 6
IS_RENDERED = 7
IS_ANALYZED = 8
IS_ADJUSTED = 9
IS_MP = 10
MEETS_SPECS_ADJ = 11
INT_LOUD_ADJ = 12
T_PEAK_ADJ = 13
S_PEAK_ADJ = 14
LOUDNESS_RANGE_ADJ = 15
IS_BAD = 16
IS_MONO = 17
# Miscellaneous Files:
#CONFIG_FILE = 'LevelerConfig.txt'
LOGO_IMG = work_dir + '/LVLR.app/Contents/level.png'
NPR_IMG = work_dir + '/LVLR.app/Contents/npr.png'
#subprocess.call([ffmpegEXE])
print command
class MainWindow(wx.Frame):
"""Build the primary Leveler window."""
# Initialize session-specific data structures.
fileList = collections.OrderedDict() # Primary file-storage data structure.
buffer_list = [] # Stores recently deleted files.
indexMap = [] # Maps the indices of the GUI list to the entries in fileList.
# Specify allowed filetypes.
wildcard = "MP3 files (*.mp3)|*.mp3|WAV files (*.wav)|*.wav|M4A files (*.m4a)|*.m4a|AIFF files (*.aiff)|*.aiff|MP3 files (*.mp2)|*.mp2"
allowedExtensions = ["MP3","mp3","WAV","wav","M4A","m4a","AIFF","aiff","MP2","mp2"]
# ALICE: I use both of these data structures to keep track of allowed file extensions, but it would be lovely if we could link them somehow.
def __init__(self, parent, id, title):
"""Initialize and display the main GUI window."""
wx.Frame.__init__(self, parent, id, title, size=(1000, 500))
# Initialize settings from the configuration file and log file.
self.config = {}
#self.ReadConfig()
self.currentIndex = 1
if self.currentIndex == 1:
self.targetPeak = TARGET_PEAK_DEFAULT
self.targetIL = TARGET_IL_DEFAULT
self.grace = GRACE_DEFAULT
self.logFileLoc = LOG_FILE_LOC_DEFAULT
self.adjFileLoc = ADJ_FILE_LOC_DEFAULT
self.overwrite = OVERWRITE_DEFAULT
self.advancedAccess = ADVANCED_ACCESS_DEFAULT
self.sameFold = SAME_FOLDER_DEFAULT
# else:
# Construct horizontal menu bar.
menubar = wx.MenuBar()
about = wx.Menu()
preferences = wx.Menu()
help = wx.Menu()
levelerInfoItem = about.Append(wx.NewId(), '&Leveler', 'Information about the Leveler')
self.Bind(wx.EVT_MENU, self.OnLevelerInfo, levelerInfoItem)
loudnessInfoItem = about.Append(wx.NewId(), '&Loudness Standards', 'Overview of loudness standards')
self.Bind(wx.EVT_MENU, self.OnLoudnessInfo, loudnessInfoItem)
labsInfoItem = about.Append(wx.NewId(), '&NPR Labs', 'Information about NPR Labs')
self.Bind(wx.EVT_MENU, self.OnLabsInfo, labsInfoItem)
preferenceItem = preferences.Append(wx.NewId(), "Preferences", "Adjust Leveler settings")
self.Bind(wx.EVT_MENU, self.OnPreferences, preferenceItem)
helpDocItem = help.Append(wx.NewId(), "&Documentation", "Guide to Leveler usage")
self.Bind(wx.EVT_MENU, self.OnHelpDoc, helpDocItem)
#helpVidItem = help.Append(wx.NewId(), "&Tutorial Video", "Video walktrhough of Leveler usage")
#self.Bind(wx.EVT_MENU, self.OnHelpVid, helpVidItem)
help.AppendSeparator()
menubar.Append(about, '&About')
menubar.Append(preferences, '&Settings')
menubar.Append(help, '&LevelerHelp')
self.SetMenuBar(menubar)
# Add internal structure to the main frame.
panel = wx.Panel(self, -1)
vbox1 = wx.BoxSizer(wx.VERTICAL)
vbox2 = wx.BoxSizer(wx.VERTICAL)
hbox = wx.BoxSizer(wx.HORIZONTAL)
buttonPanel = wx.Panel(panel, -1)
filePanel = wx.Panel(panel, -1)
# Construct the GUI's underlying file list.
# ALICE: I used a module called "UltimateListCtrl" to build the GUI, becuase the normal list control
# does not support embedding progress bars into rows. If you need to make any further modifications,
# refer to http://wxpython.org/Phoenix/docs/html/lib.agw.ultimatelistctrl.UltimateListCtrl.html
# (there's also some semi-helpful information in the wxPython demo files).
self.list = ULC.UltimateListCtrl(filePanel, agwStyle = wx.LC_REPORT | wx.LC_VRULES )
self.list.InsertColumn(0, ' ', width=20)
self.list.InsertColumn(1, 'Filename', wx.LIST_FORMAT_LEFT, width=300)
self.list.InsertColumn(2, 'Progress', wx.LIST_FORMAT_CENTER, width=100)
self.list.InsertColumn(3, 'Meets Specs?', wx.LIST_FORMAT_CENTER, width=100)
self.list.InsertColumn(4, 'Integrated Loudness', wx.LIST_FORMAT_CENTER, width=130)
self.list.InsertColumn(5, 'True Peak', wx.LIST_FORMAT_CENTER, width=100)
self.list.InsertColumn(6, 'Peak Value', wx.LIST_FORMAT_CENTER, width=100)
#self.list.InsertColumn(7, 'Loudness Range', wx.LIST_FORMAT_CENTER, width=100)
# Establish file list as a drag-and-drop target.
dropTarget = FileDrop(self.list, self)
self.list.SetDropTarget(dropTarget)
# Populate the button panel.
self.loadfile = wx.Button(buttonPanel, -1, 'Upload File', size=(100, -1))
self.loadfolder = wx.Button(buttonPanel, -1, 'Upload Folder', size=(100, -1))
self.sel = wx.Button(buttonPanel, -1, 'Select All', size=(100, -1))
self.sel.Disable()
self.des = wx.Button(buttonPanel, -1, 'Deselect All', size=(100, -1))
self.des.Disable()
self.ana = wx.Button(buttonPanel, -1, 'Analyze', size=(100, -1))
self.ana.Disable()
self.adj = wx.Button(buttonPanel, -1, 'Adjust', size=(100, -1))
self.adj.Disable()
#self.rem = wx.Button(buttonPanel, -1, 'Remove', size=(100,-1))
#self.rem.Disable()
#self.und = wx.Button(buttonPanel, -1, 'Undo Remove', size=(100, -1))
#self.und.Disable()
# Add Leveler logo.
img = wx.Image(LOGO_IMG, wx.BITMAP_TYPE_ANY)
w = img.GetWidth()
h = img.GetHeight()
img_scaled = img.Scale(w/1, h/1) ##
self.logo = wx.StaticBitmap(buttonPanel, -1, wx.BitmapFromImage(img_scaled))
# Bind buttons to their respective functions.
self.Bind(wx.EVT_BUTTON, self.OnLoadFile, id=self.loadfile.GetId())
print "ID:: " + str(id)
self.Bind(wx.EVT_BUTTON, self.OnLoadFolder, id=self.loadfolder.GetId())
#self.Bind(wx.EVT_BUTTON, self.OnRemove, id=self.rem.GetId())
#self.Bind(wx.EVT_BUTTON, self.OnUndo, id=self.und.GetId())
self.Bind(wx.EVT_BUTTON, self.OnSelectAll, id=self.sel.GetId())
self.Bind(wx.EVT_BUTTON, self.OnDeselectAll, id=self.des.GetId())
self.Bind(wx.EVT_BUTTON, self.OnAnalyze, id=self.ana.GetId())
self.Bind(wx.EVT_BUTTON, self.OnAdjust, id=self.adj.GetId())
# Add NPR logo and copyright message.
wmark = wx.Image(NPR_IMG, wx.BITMAP_TYPE_ANY)
w = wmark.GetWidth()
h = wmark.GetHeight()
wmark_scaled = wmark.Scale(w/1, h/1) ##
self.watermark = wx.StaticBitmap(filePanel, -1, wx.BitmapFromImage(wmark_scaled))
self.copyright = wx.StaticText(filePanel, -1, "Copyright 2015 NPR Labs")
# Set sizers.
vbox1.Add(self.list, 1, wx.EXPAND | wx.TOP, 3)
vbox1.Add((-1, 10))
vbox1.Add(self.watermark,0, wx.ALIGN_BOTTOM | wx.ALIGN_CENTRE | wx.SHAPED)
vbox1.Add(self.copyright, 0, wx.ALIGN_BOTTOM | wx.ALIGN_CENTRE)
filePanel.SetSizer(vbox1)
vbox2.Add(self.logo, 0, wx.ALL, 10)
vbox2.Add(self.loadfile, 0, wx.ALL, 10)
vbox2.Add(self.loadfolder, 0, wx.ALL, 10)
vbox2.Add(self.sel, 0, wx.ALL, 10)
vbox2.Add(self.des, 0, wx.ALL, 10)
#vbox2.Add(self.rem, 0, wx.ALL, 10)
#vbox2.Add(self.und, 0, wx.ALL, 10) # Disabled until applicable
vbox2.Add(self.ana, 0, wx.ALL, 10)
vbox2.Add(self.adj, 0, wx.ALL, 10)
buttonPanel.SetSizer(vbox2)
hbox.Add(buttonPanel, 0, wx.EXPAND | wx.RIGHT, 5)
hbox.Add(filePanel, 1, wx.EXPAND)
hbox.Add((3, -1))
panel.SetSizer(hbox)
# Initialize log file.
logfilepath = self.logFileLoc + SEPARATOR + "NPRLevelerLogfile.txt"
if os.path.exists(logfilepath):
self.logfile = open(logfilepath, 'a+')
else:
try:
self.logfile = open(logfilepath, 'w+')
except:
pass
# Center and display main frame.
self.Centre()
self.Show(True)
def OnQuit(self, event):
self.Close()
def OnPreferences(self, event):
"""Launch a settings dialog box."""
dlg = SettingsDialog(self)
dlg.ShowModal()
dlg.Destroy()
def OnLevelerInfo(self, event):
"""Launch a window that provides Leveler information."""
description = """The NPR Leveler is a lightweight software application - currently available for PC and OSX - that automatically analyzes and adjusts audio files to ensure compliance with EBU loudness standards."""
licence = """The NPR Leveler is free software, made available to all NPR member stations in the US to ensure consistent loudness levels across the American public radio landscape."""
# ALICE: Do we need to reference the GNU General Public License? Or the Free Software Foundation?
# I suppose legal can help clarify the requirements here...
info = wx.AboutDialogInfo()
info.SetName('Leveler')
info.SetVersion('1.0')
info.SetDescription(description)
info.SetCopyright('(C) 2015 NPR Labs')
info.SetLicence(licence)
info.AddDeveloper('Olivia Waring, Alice Goldfarb, Ty Von Plinsky, Chris Nelson')
wx.AboutBox(info)
def OnLoudnessInfo(self, event):
"""Launch a window that provides general information about loudness standards."""
dlg = LoudnessInfoDialog(self)
dlg.ShowModal()
dlg.Destroy()
def OnLabsInfo(self, event):
"""Launch a window that provides general information about NPR Labs."""
dlg = LabsInfoDialog(self)
dlg.ShowModal()
dlg.Destroy()
def OnHelpDoc(self, event):
"""To be implemented (hi ALICE!)"""
pass
#def OnHelpVid(self, event):
#pass
def OnBadInput(self, file):
"""Notify the user of any invalid inputs."""
self.fileList[file][IS_ANALYZED] = True
self.fileList[file][IS_ADJUSTED] = True
self.fileList[file][IS_BAD] = True
message = wx.StaticText(self.panel, id=-1,style=wx.ALIGN_CENTRE, label="ffmpeg detected bad input for %s; check the extension on your file and ensure that your data has not been corrupted." % file)
dlg = wx.MessageDialog(self, "Bad Input", message, style=wx.OK|wx.ICON_EXCLAMATION)
dlg.ShowModal()
def OnAnalyze(self, event):
"""The event handler for the 'Analyze' button."""
# Calls the AnalyzeHelper method with the argument 'False' to indicate that
# the 'Adjust' button has not also been triggered.
print "OnAnalyze"
self.AnalyzeHelper(False)
def AnalyzeHelper(self, toAdjustLater):
"""Call an analysis thread for every selected item."""
print "AnalyzeHelper\n\t toAdjustLater:" + str(self) + str(toAdjustLater)
# Populate a list of selected files to be analyzed (ignoring any that have already been analyzed).
toAnalyze = []
for i in range(self.list.GetItemCount()):
print "i (" + str(i) + ") of " + str(self.list.GetItemCount())
if i%3 == 0: # ALICE: Change this value to i%2 if you decide to get rid of the spacer
# line (and do likewise where appropriate throughout the file).
check = self.list.GetItem(i,0)
box = check.GetWindow()
file = self.indexMap[i/3]
if box.GetValue() and not self.fileList[file][IS_ANALYZED]:
toAnalyze.append((file,i))
# Launch an analysis thread and progress bar for each selected file.
for (file,index) in toAnalyze:
item = self.list.GetItem(index,2)
gauge = item.GetWindow()
gauge.Pulse()
##self._resultAnalyzeProducer(file) # Inserted for debugging purposes!
self._resultAnalyzeProducer(file, index)
# analysisThread = startWorker(self._resultAnalyzeConsumer, self._resultAnalyzeProducer, cargs=(file,index,), wargs=(file,))
# analysisThread.join() # Stall processing until all analysis threads finish.
# If the call to 'AnalyzeHelper' was initiated by pressing the 'Adjust' button,
# proceed to the AdjustHelper function.
if toAdjustLater:
self.AdjustHelper()
def OnAdjust(self, event):
"""The event handler for the 'Adjust' button."""
print "OnAdjust\n"
# Initiate analysis, while flagging files for post-analysis adjustment.
self.AnalyzeHelper(True)
def AdjustHelper(self):
"""Call an adjust thread for every selected item."""
# Compile list of selected files (ignoring any that have already been adjusted).
toAdjust = []
for i in range(self.list.GetItemCount()):
if i%3 == 0:
check = self.list.GetItem(i,0)
box = check.GetWindow()
file = self.indexMap[i/3]
if box.GetValue() and not self.fileList[file][IS_ADJUSTED]:
toAdjust.append((file,i+1))
# Perform adjustment for each selected item.
for (file,index) in toAdjust:
# Add progress bars.
item = self.list.GetItem(index,2)
item._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_FORMAT | ULC.ULC_MASK_FONTCOLOUR
width = self.list.GetColumnWidth(1)
gauge = wx.Gauge(self.list,-1,range=50,size=(width,15),style=wx.GA_HORIZONTAL | wx.GA_SMOOTH)
gauge.Pulse() #shouldn't start this right away...
item.SetWindow(gauge, wx.ALIGN_CENTRE)
self.list.SetItem(item)
# Display the names of the to-be-adjusted files.
(dir,just_file) = os.path.split(os.path.abspath(file))
print type(just_file)
file_name_as_string = str(just_file)
print "string version"
print type(file_name_as_string)
print file_name_as_string
dot = re.search("(.*\.)*(.*)", file_name_as_string)
extension = dot.group(2)
print "EXTENSION::: "
print extension
### this is the old one ###
#dot_o = re.search("\.", just_file)
#extension_o = just_file[dot_o.end():]
#print "older version: "
#print extension_o
#stem = just_file[:dot.end()-1]
n = re.search(extension, file_name_as_string)
stem = file_name_as_string[:n.start()-1]
print "---stem"
print stem
print type(stem)
print str(stem)
#stem = file_name_as_string[:dot.end()-1]
timestamp = time.strftime("%d-%m-%Y;%H%M") + '.'
if not self.overwrite:
adjusted_file = stem + "_adjusted_" + timestamp + extension
else:
adjusted_file = stem + "_adjusted." + extension
self.list.SetStringItem(index, 1, adjusted_file)
# Launch an adjust thread for each selected file.
print "file name"
print file
print "TARGET:"
print self.targetIL
self._resultAdjustProducer(file, timestamp, index)
# startWorker(self._resultAdjustConsumer, self._resultAdjustProducer, cargs=(file,index,timestamp,), wargs=(file,timestamp,))
# Once adjustment has been performed, uncheck the box and set the 'adjusted' field to True.
self.fileList[file][IS_ADJUSTED] = True
check = self.list.GetItem(index-1,0)
box = check.GetWindow()
box.SetValue(False)
"""Invoke the ffmpeg analysis routine and pipe the output to self._resultAnalyzeConsumer."""
def _resultAnalyzeProducer(self, file, index):
print "_resultAnalyzeProducer\n"
print file
print self
print "target::::"
print self.targetIL
output_name = work_dir + "/LVLR.app/Contents/output_file_resultAnalyzeProducer.txt" ##
output_file = open(output_name, 'w') ##
proc = subprocess.call([ffmpegEXE, '-nostats', '-i', file, '-filter_complex', 'ebur128=peak=true+sample:framelog=verbose', '-f', 'null', '-'], bufsize=1, stdout=output_file, stderr=output_file)
sys.stdout.flush()
buffered = open(output_name, 'rU').read() ##made it = rather than +=
sys.stdout.flush()
#
self.prologue = buffered.split('Press [q]')[0] # Capture the beginning of the output information (which includes bitrate).
##test mono v. stereo
channels = re.search(r'Hz, (.+?),', self.prologue)
chVal = channels.group(1)
print chVal
print type(chVal)
print "S v. M"
if ((chVal == "mono") or (chVal == "1 channels")):
print "mono!"
self.fileList[file][IS_MONO] = True
self.targetIL = -27
print self.targetIL
print self.fileList[file][FILE_NAME]
## put mono stuff back in here ##
if ((chVal == "stereo") or (chVal == "2 channels")):
print "stereo!"
self.fileList[file][IS_MONO] = False
self.targetIL = -24
print self.targetIL
print self.fileList[file][FILE_NAME]
if not ((chVal == "mono") or (chVal == "1 channels") or (chVal == "stereo") or (chVal == "2 channels")):
print "some other options!"
self.fileList[file][IS_MONO] = False
self.targetIL = -24
print self.targetIL
print "461"
print self.fileList[file][IS_MONO]
# self.summary = stdout.split('\n')[-16:] # Capture the end of the output information.
# self.summary = buffered.split('\n')[-16:] # Capture the end of the output information.
##changed to deal with longer headers
split_part = buffered.partition('Summary:\n')
self.summary = str(split_part[2])
result = self.prologue + ''.join(self.summary) # Return both for processing.
item = self.list.GetItem(index,2)
gauge = item.GetWindow()
gauge.SetValue(50)
#
# if not self.overwrite:
# stereo_file = stem + "_stereo_" + timestamp + extension
# mono_file = stem + "_mono_" + timestamp + extension
# else:
# stereo_file = stem + "_stereo." + extension
# mono_file = stem + "_mono." + extension
# mono_file_full = dir + "/"+ mono_file
# stereo_file_full = dir + "/"+ stereo_file
# shutil.copy(file, mono_file_full)
# if os.path.isfile(mono_file_full):
# os.remove(mono_file_full)
# if os.path.isfile(stereo_file_full):
# os.remove(stereo_file_full)
# Catch bad inputs and inform the user.
badInput = re.search(r'Invalid data found when processing input', result) #will it get here without error?
if badInput is not None:
wx.CallAfter(self.OnBadInput,file)
else:
self.ProcessSummary(file, result, index)
##
def ProcessSummary(self, file, summary, i):
"""Process ffmpeg data and display it in the GUI."""
errorFlag = False
# Extract bitrate.
if not self.fileList[file][IS_ANALYZED]:
bitrate = re.search(r'bitrate:\s(.+?) kb', summary) #changed from summary
print "bitrate: "##
print bitrate.group(1)
if bitrate is not None:
BR = bitrate.group(1)
print "BR"
else:
BR = "n/a"
errorFlag = True
self.fileList[file][BITRATE] = BR
# Extract loudness range.
#loudnessRange = re.search(r'LRA:\s(.+?) LU', summary)
#if loudnessRange is not None:
# LRA = loudnessRange.group(1)
#else:
# LRA = "n/a"
# errorFlag = True
#self.list.SetStringItem(i,7,LRA)
#if i%3 == 0:
# self.fileList[file][LOUDNESS_RANGE] = LRA
#else:
# self.fileList[file][LOUDNESS_RANGE_ADJ] = LRA
# Extract integrated loudness.
##print "blerg / problems second time through\n\n------\n"
##print summary
print "\n\n-----"
##print self.summary
print "\n\n-----"
print type(summary)
print type(self.summary)
intloud = re.search(r'I:\s*(.+?) LUFS', summary)
full_thing = intloud.group(0)
print full_thing + "LUFS:: "
print "|" + intloud.group(1)+"|"
if intloud is not None:
IL = intloud.group(1)
else:
IL = "n/a"
errorFlag = True
#IL = "30"
#i = 0 ##testing
##test##
#IL = "33"
print "look::"
print type(IL)
##
if self.fileList[file][IS_MONO] == True:
ILint = float(IL)
ILtoShow = str(ILint + 3)
else:
ILtoShow = IL
self.list.SetStringItem(i,4,ILtoShow)
print "IL shown " + str(ILtoShow) + " & actual IL " + IL
if i%3 == 0:
self.fileList[file][INT_LOUD] = IL
else:
self.fileList[file][INT_LOUD_ADJ] = IL
print "SUMMARY to USE"
print type(summary)
##print summary
# Extract true peak.
truePeak = re.search(r'True peak:\s+Peak:\s+(.+?) dBFS', summary)
#print "TP 0:" + truePeak.group(0)
print "TP 1:"
print truePeak.group(1)
if truePeak is not None:
TP = truePeak.group(1)
else:
TP = "n/a"
errorFlag = True
self.list.SetStringItem(i,5,TP)
print "TP: \n"
print TP ##
if i%3 == 0:
self.fileList[file][T_PEAK] = TP
else:
self.fileList[file][T_PEAK_ADJ] = IL
# Extract sample peak.
samplePeak = re.search(r'Sample peak:\s*Peak:\s*(.+?) dBFS', summary)
print "SP 0:" + samplePeak.group(0)
print "SP 1:" + samplePeak.group(1)
if samplePeak is not None:
SP = samplePeak.group(1)
print "SP: "
print SP
else:
SP = "n/a"
errorFlag = True
self.list.SetStringItem(i,6,SP)
if i%3 == 0:
self.fileList[file][S_PEAK] = SP
else:
self.fileList[file][S_PEAK_ADJ] = IL
# Append time-stamped data to log file.
self.logfile.write(time.strftime("%d/%m/%Y: %H:%M:%S: "))
self.logfile.write(file)
if i%3 == 1:
self.logfile.write(" (adjusted)")
if errorFlag:
self.logfile.write("\nThis process was aborted prematurely.")
self.logfile.write("\nIntegrated Loudness: " + str(IL) + "\n")
self.logfile.write("True Peak: " + str(TP) + "\n")
#self.logfile.write("LRA: " + str(LRA) + "\n\n\n")
self.logfile.write("********************************************************\n\n")
##
##
# Notify users of any data-collection errors; if no errors are detected, flag the file as analyzed.
if errorFlag:
self.list.SetStringItem(i,3,"Error")
print "Error!"
else:
self.fileList[file][IS_ANALYZED] = True
print "Is _ Analyzed"
# Determine whether the file conforms to specs.
upperbound = self.targetIL + self.grace
lowerbound = self.targetIL - self.grace
if float(IL) > lowerbound and float(IL) < upperbound:
valid = True
else:
valid = False
if float(truePeak.group(1)) > self.targetPeak:
valid = False
# Notify the GUI of whether the file meets specs.
if valid:
self.list.SetStringItem(i,3,"Yes")
item = self.list.GetItem(i,3)
item.SetTextColour(wx.GREEN) # ALICE: I can't make this color change work! Seems like it shouldn't be hard...
self.list.SetItem(item)
if i%3 == 0:
self.fileList[file][MEETS_SPECS] = "Yes"
check = self.list.GetItem(i,0)
box = check.GetWindow()
# box.SetValue(False)
# self.list.SetItem(check)
else:
self.fileList[file][MEETS_SPECS_ADJ] = "Yes"
else:
self.list.SetStringItem(i,3,"No")
if i%3 == 0:
self.fileList[file][MEETS_SPECS] = "No"
else:
self.fileList[file][MEETS_SPECS_ADJ] = "No"
def _resultAdjustProducer(self, file, timestamp, index):
"""Invoke the ffmpeg adjust routine and pipe the output to __resultAdjustConsumer."""
###
print self.fileList[file][FILE_NAME]
print "target : \n\t"
print self.targetIL
print "is mp3 or mp2?"
print self.fileList[file][IS_MP]
print "is mono? \n\t"
print self.fileList[file][IS_MONO]
if self.fileList[file][IS_MONO] == True:
self.targetIL = -27
else:
self.targetIL = -24
print "target : \n\t"
print self.targetIL
output_name = work_dir + "/LVLR.app/Contents/output_file_resultAdjustProducer.txt" ##
#output_file = open(output_name, 'w') ##
output_name_second = work_dir + "/LVLR.app/Contents/output_file_resultAdjustProducer_second.txt" ##
#output_file_second = open(output_name, 'w') ##
####
file_name_as_string = str(file)
print "string version"
print type(file_name_as_string)
print file_name_as_string
dot = re.search("(.*\.)*(.*)", file_name_as_string)
extension = dot.group(2)
print "EXTENSION::: "
print extension
#####
##olde##
# Extract file extension.
#dot_o = re.search("\.", file)
#extension_o = file[dot_o.end():]
#print "older: "
#print extension_o
# Specify path of adjusted file based on config settings.
(dir,just_file) = os.path.split(os.path.abspath(file))
if not self.sameFold:
dir = self.adjFileLoc
fileNewFold = dir + SEPARATOR + just_file #this could be part of the problem... maybe not a valid path?
else:
fileNewFold = file
print "fileNewFold"
type(fileNewFold)
fileNewFold_str = str(fileNewFold)
dot = re.search("(.*\.)*(.*)", fileNewFold_str)
#dot = re.search("\.", fileNewFold)
print "dot for full fileNewFold"
print dot
print type(dot)
print str(dot)
# Convert mp3 files to wav before processing.
# ALICE: This would be where you can add mp2 conversion as well.
print "EXT:: "
print extension
n = re.search(extension, fileNewFold_str)
stem = fileNewFold_str[:n.start() - 1]
print "stem--"
print stem
if ((extension == "mp3") or (extension == "mp2")):
print "IS MP"
old_ext = extension
print "old extensions: " + old_ext
self.fileList[file][IS_MP] = True # Designate as mp3.
print "yes, MP"
extension = "wav"
# wav_file = fileNewFold[:dot.start()] + '.' + extension # Name equivalent wav file.#
wav_file = stem + '.' + extension # Name equivalent wav file.#
#print wave_file
if os.path.isfile(wav_file): # Remove any existing files of that name.
os.remove(wav_file)
output_file = open(output_name, 'w') ##proc = subprocess.call([ffmpegEXE, '-i', file, wav_file], bufsize = 1, stdout=output_file, stderr=output_file)
sys.stdout.flush()
output_file.close()
# proc = subprocess.call([ffmpegEXE, '-i', file, wav_file], bufsize = 1, stdout=output_file, stderr=output_file)
#stdout,stderr = proc.communicate()
adjusted_MP = stem + "_adjusted." + old_ext # Name final adjusted MP3 file.
#adjusted_MP = fileNewFold[:dot.start()] + "_adjusted." + old_ext # Name final adjusted MP3 file.
print "adjusted_MP"
print adjusted_MP
if os.path.isfile(adjusted_MP): # Remove any existing files of that name.
os.remove(adjusted_MP)
# Define other helper files and remove any duplicates (i.e. permit overwrites).
## overwriting ##
#
if self.overwrite:
start_file = stem + "_start." + extension
intermed_file = stem + "_intermed." + extension
adjusted_file = stem + "_adjusted." + extension
print "start, int, adjusted"
print start_file
print intermed_file
print adjusted_file
'''
start_file = fileNewFold[:dot.start()] + "_start." + extension
intermed_file = fileNewFold[:dot.start()] + "_intermed." + extension
adjusted_file = fileNewFold[:dot.start()] + "_adjusted." + extension '''
#if self.overwrite:
# start_file = fileNewFold[:dot.start()] + "_start_" + timestamp + extension
# intermed_file = fileNewFold[:dot.start()] + "_intermed_" + timestamp + extension
# adjusted_file = fileNewFold[:dot.start()] + "_adjusted_" + timestamp + extension
else:
start_file = stem + "_start_" + timestamp + extension
intermed_file = stem + "_intermed_" + timestamp + extension
adjusted_file = stem + "_adjusted_" + timestamp + extension
print "start, int, adjusted"
print start_file
print intermed_file
print adjusted_file
'''
start_file = fileNewFold[:dot.start()] + "_start_" + timestamp + extension
intermed_file = fileNewFold[:dot.start()] + "_intermed_" + timestamp + extension
adjusted_file = fileNewFold[:dot.start()] + "_adjusted_" + timestamp + extension '''
if os.path.isfile(start_file):
os.remove(start_file)
if os.path.isfile(adjusted_file):
os.remove(adjusted_file)
if os.path.isfile(intermed_file):
os.remove(intermed_file)
# Copy the original file to `start_file' to prevent modification of the original.
shutil.copy(file, start_file)
print "int loud :: :: ::"
print type(INT_LOUD)
print INT_LOUD
print type(self.fileList[file][INT_LOUD])
print str(self.fileList[file][INT_LOUD])
# Calculate gain shift and target peak.
IL = float(self.fileList[file][INT_LOUD])
TP = float(self.fileList[file][T_PEAK])
gain = self.targetIL - IL
newTruePeak = TP + gain
print "newTP: " + str(newTruePeak) + "\t TP: " + str(TP) + "\t gain :" + str(gain)
# Branch 1: integrated loudness is too low, upward gain shift required.
if gain > 0:
print "gain > 0"
# Perform up to five extra rounds of gain shifting, to "nudge" finnicky files to the target value.
count = 0
direction = [False,False] #added by alice
while (count < 5):
print count
# Branch 1A: no compression required, simply gain shift.
if newTruePeak < self.targetPeak:
#output_name_temp = work_dir + "/LVLR.app/Contents/output_file_resultAdjustProducer_indent.txt" ##
#output_file = open(output_name_temp, 'w') ##
print adjusted_file
print "\n --if -- \n\nadjusted file\n"
output_file = open(output_name_second, 'w') ##
proc = subprocess.call([ffmpegEXE, '-i', start_file, '-strict', '-2', '-af', 'volume=volume=' + str(gain) + 'dB:precision=fixed', adjusted_file], bufsize=1, stdout=output_file, stderr=output_file)
#stdout,stderr = proc.communicate()
sys.stdout.flush()
output_file.close()
# Branch 1B: begin iterative compression and gain shift.
else:
j = 0
print "else"
print j
while (newTruePeak > self.targetPeak) and j < 4: # Forestall infinite loops.
print "(newTruePeak > self.targetPeak)"
print newTruePeak
print self.targetPeak
# Perform round of compression.
offset = self.targetPeak - gain
#output_name_temp = work_dir + "/LVLR.app/Contents/output_file_resultAdjustProducer_indent2.txt" ##
#output_file = open(output_name_temp, 'w') ##
output_file = open(output_name, 'w') ##
proc = subprocess.call([ffmpegEXE, '-i', start_file, '-strict', '-2', '-af', 'compand=.00001:.5:-90/-90|-40/-40|-30/-30|-20/-20|-10/-10|-8/-8' + str(offset) + '/' + str(offset - 2) + '|-3/' + str(offset - 1) + '|-1/' + str(offset - 0.1) + '|0/' + str(offset) + ':0.01:0:0:0.01', intermed_file], bufsize=1, stdout=output_file, stderr=output_file)
sys.stdout.flush()
output_file.close()
# #stdout,stderr = proc.communicate()
# proc = subprocess.call([ffmpegEXE, '-i', start_file, '-strict', '-2', '-af', 'compand=.00001:.5:-90/-90|-40/-40|-30/-30|-20/-20|-10/-10|-8/-8' + str(offset) + '/' + str(offset - 2) + '|-3/' + str(offset - 1) + '|-1/' + str(offset - 0.1) + '|0/' + str(offset) + ':0.01:0:0:0.01', intermed_file], bufsize=1, stdout=output_file, stderr=output_file)
# #stdout,stderr = proc.communicate()
# Analyze the resulting file.
# proc = subprocess.Popen([ffmpegEXE, '-nostats', '-i', intermed_file, '-filter_complex', 'ebur128=peak=true+sample', '-f', 'null', '-'], bufsize=1, stdout=output_file, stderr=output_file)
# #stdout,stderr = proc.communicate()
#output_name_temp = work_dir + "/LVLR.app/Contents/output_file_resultAdjustProducer_indent3.txt" ##
#output_file = open(output_name_temp, 'w') ##
print output_name
print "895"
output_file = open(output_name_second, 'w') ##
proc = subprocess.call([ffmpegEXE, '-nostats', '-i', intermed_file, '-filter_complex', 'ebur128=peak=true+sample:framelog=verbose', '-f', 'null', '-'], bufsize=1, stdout=output_file, stderr=output_file)
sys.stdout.flush()
output_file.close()
########
########
#stdout,stderr = proc.communicate()
#
##
#bufferTry = open filePath,
## trying to make this work. 7.20
##
# buf = open(output_name, 'rU').read() ##
buffered = open(output_name_second, 'rU').read() ##
print "\n\n\n"
print "------898a"
print output_name
print output_file
print type(buffered)
print buffered
print "\n\n\n"
##
##
print "------902"
split_part = buffered.partition('Summary:\n')
self.summary = str(split_part[2])
print type(self.summary)