2121 If not, see <https://www.gnu.org/licenses/>.
2222
2323"""
24+ import hashlib
25+ import json
2426import logging
2527import os
2628from configparser import NoSectionError , NoOptionError
@@ -102,6 +104,39 @@ def custom_stream_mapping(self, stream_info: dict, stream_id: int):
102104 }
103105
104106
107+ def file_sha256 (path , chunk_size = 16 * 1024 * 1024 ):
108+ digest = hashlib .sha256 ()
109+ with open (path , 'rb' ) as f :
110+ for chunk in iter (lambda : f .read (chunk_size ), b'' ):
111+ digest .update (chunk )
112+ return digest .hexdigest ()
113+
114+
115+ def load_marker (raw ):
116+ """Parse a stored marker. Returns a dict, or None if nothing stored."""
117+ if not raw :
118+ return None
119+ try :
120+ marker = json .loads (raw )
121+ if isinstance (marker , dict ):
122+ return marker
123+ except Exception :
124+ pass
125+ # Backwards-compat: old format was a plain loudnorm string
126+ return {'version' : 1 , 'filtergraph' : raw , 'sha256' : None }
127+
128+
129+ def make_marker (settings , path ):
130+ stat = os .stat (path )
131+ return json .dumps ({
132+ 'version' : 2 ,
133+ 'filtergraph' : audio_filtergraph (settings ),
134+ 'sha256' : file_sha256 (path ),
135+ 'size' : stat .st_size ,
136+ 'mtime_ns' : stat .st_mtime_ns ,
137+ })
138+
139+
105140def audio_filtergraph (settings ):
106141 i = settings .get_setting ('I' )
107142 if not i :
@@ -122,28 +157,39 @@ def file_already_normalised(settings, path):
122157 directory_info = UnmanicDirectoryInfo (os .path .dirname (path ))
123158
124159 try :
125- previous_loudnorm_filtergraph = directory_info .get ('normalise_aac' , os .path .basename (path ))
126- except NoSectionError as e :
127- previous_loudnorm_filtergraph = ''
128- except NoOptionError as e :
129- previous_loudnorm_filtergraph = ''
160+ raw_marker = directory_info .get ('normalise_aac' , os .path .basename (path ))
161+ except (NoSectionError , NoOptionError ):
162+ raw_marker = ''
130163 except Exception as e :
131164 logger .debug ("Unknown exception {}." .format (e ))
132- previous_loudnorm_filtergraph = ''
165+ raw_marker = ''
133166
134- if previous_loudnorm_filtergraph :
135- logger .debug ("File's stream was previously normalised with {}." .format (previous_loudnorm_filtergraph ))
136- # This stream already has been normalised
137- if settings .get_setting ('ignore_previously_processed' ):
138- logger .debug ("Plugin configured to ignore previously normalised streams" )
139- return True
140- elif audio_filtergraph (settings ) in previous_loudnorm_filtergraph :
141- # The previously normalised stream matches what is already configured
142- logger .debug (
143- "Stream was previously normalised with the same settings as what the plugin is currently configured" )
144- return True
167+ marker = load_marker (raw_marker )
168+
169+ if not marker :
170+ return False
171+
172+ stored_sha256 = marker .get ('sha256' )
173+ if not stored_sha256 :
174+ # Old marker has no content hash — treat as stale so replaced files are reprocessed
175+ logger .debug ("normalise_aac marker has no content hash; reprocessing '{}'." .format (path ))
176+ return False
177+
178+ current_sha256 = file_sha256 (path )
179+ if current_sha256 != stored_sha256 :
180+ logger .debug ("File content changed since last normalisation; reprocessing '{}'." .format (path ))
181+ return False
182+
183+ logger .debug ("File '{}' content matches stored marker." .format (path ))
184+
185+ if settings .get_setting ('ignore_previously_processed' ):
186+ logger .debug ("Plugin configured to ignore previously normalised streams." )
187+ return True
188+
189+ if marker .get ('filtergraph' ) == audio_filtergraph (settings ):
190+ logger .debug ("File was previously normalised with the same settings." )
191+ return True
145192
146- # Default to...
147193 return False
148194
149195
@@ -283,7 +329,7 @@ def on_postprocessor_task_results(data):
283329 # Loop over the destination_files list and update the directory info file for each one
284330 for destination_file in data .get ('destination_files' ):
285331 directory_info = UnmanicDirectoryInfo (os .path .dirname (destination_file ))
286- directory_info .set ('normalise_aac' , os .path .basename (destination_file ), audio_filtergraph (settings ))
332+ directory_info .set ('normalise_aac' , os .path .basename (destination_file ), make_marker (settings , destination_file ))
287333 directory_info .save ()
288334 logger .debug ("Normalise AAC info written for '{}'." .format (destination_file ))
289335
0 commit comments