@@ -3605,16 +3605,9 @@ def _save_to_file(self, path: str) -> bool:
36053605 def make_relative (p : str ) -> str :
36063606 if not p :
36073607 return p
3608- # If the path is already relative, leave it unchanged to avoid
3609- # reinterpreting it against the current working directory.
3610- if not os .path .isabs (p ):
3611- return p
36123608 try :
3613- base_dir = os .path .abspath (os .path .dirname (path ))
3614- abs_p = os .path .abspath (p )
3615- return os .path .relpath (abs_p , base_dir )
3609+ return os .path .relpath (p , os .path .dirname (path ))
36163610 except ValueError :
3617- # Fall back to the original path if relpath cannot be computed
36183611 return p
36193612
36203613 # Serialize clips
@@ -3636,7 +3629,8 @@ def make_relative(p: str) -> str:
36363629
36373630 # Add type-specific data
36383631 if clip .clip_type == "image" :
3639- clip_dict ['image_path' ] = make_relative (clip .image_path )
3632+ clip_dict ['image_path' ] = clip .image_path
3633+ clip_dict ['image_path_relative' ] = make_relative (clip .image_path )
36403634
36413635 # Serialize words if available
36423636 if clip .words :
@@ -3661,10 +3655,13 @@ def make_relative(p: str) -> str:
36613655 project_data = {
36623656 'version' : '1.1' , # Bumped for settings support
36633657 'saved_at' : datetime .now ().isoformat (),
3664- 'script_path' : make_relative (self .script_path ),
3658+ 'script_path' : self .script_path ,
3659+ 'script_path_relative' : make_relative (self .script_path ),
36653660 'script_content' : script_content , # Save script content
3666- 'image_folder' : make_relative (self .image_folder ),
3667- 'speaker_audio_map' : relative_audio_map ,
3661+ 'image_folder' : self .image_folder ,
3662+ 'image_folder_relative' : make_relative (self .image_folder ),
3663+ 'speaker_audio_map' : self .speaker_audio_map ,
3664+ 'speaker_audio_map_relative' : relative_audio_map ,
36683665 'clips' : clips_data ,
36693666 'settings' : self .runtime_config .to_dict (), # Save settings
36703667 }
@@ -3687,29 +3684,41 @@ def _load_project_data(self, data: dict, project_path: str = None):
36873684 from ui .timeline_widget import TimelineClip
36883685 from PyQt6 .QtGui import QColor
36893686 import os
3690-
3691- # Helper to resolve paths relative to the project directory
3692- def resolve_path (path : str ) -> str :
3693- if not path or not project_path :
3694- return path
3695- if os .path .isabs (path ):
3696- return path
3697- return os .path .normpath (os .path .join (os .path .dirname (project_path ), path ))
36983687
3688+ # Helper to resolve paths relative to the project directory
3689+ def resolve_path (abs_path : str , rel_path : str ) -> str :
3690+ # 1. Try absolute path first
3691+ if abs_path and os .path .exists (abs_path ):
3692+ return abs_path
3693+
3694+ # 2. Try relative path if project_path is known
3695+ if rel_path and project_path :
3696+ resolved_rel = os .path .normpath (os .path .join (os .path .dirname (project_path ), rel_path ))
3697+ if os .path .exists (resolved_rel ):
3698+ return resolved_rel
3699+
3700+ # 3. Fallback to original abs_path (might be missing, but keeps original string)
3701+ return abs_path
3702+
36993703 # Clear image cache before loading new project
37003704 from .image_cache import get_image_cache
37013705 get_image_cache ().clear ()
37023706
37033707 # Load basic info
3704- self .script_path = resolve_path (data .get ('script_path' ))
3705- self .image_folder = resolve_path (data .get ('image_folder' ))
3708+ self .script_path = resolve_path (data .get ('script_path' ), data . get ( 'script_path_relative' ) )
3709+ self .image_folder = resolve_path (data .get ('image_folder' ), data . get ( 'image_folder_relative' ) )
37063710
37073711 # Resolve audio map paths
37083712 raw_audio_map = data .get ('speaker_audio_map' , {})
3709- self .speaker_audio_map = {
3710- speaker : resolve_path (path ) if path else path
3711- for speaker , path in raw_audio_map .items ()
3712- }
3713+ raw_audio_map_relative = data .get ('speaker_audio_map_relative' , {})
3714+
3715+ self .speaker_audio_map = {}
3716+ # Merge keys from both dicts in case some are only in one
3717+ all_speakers = set (raw_audio_map .keys ()) | set (raw_audio_map_relative .keys ())
3718+ for speaker in all_speakers :
3719+ abs_p = raw_audio_map .get (speaker )
3720+ rel_p = raw_audio_map_relative .get (speaker )
3721+ self .speaker_audio_map [speaker ] = resolve_path (abs_p , rel_p ) if abs_p or rel_p else abs_p
37133722
37143723 # Update canvas map
37153724 self .timeline_widget .canvas .speaker_audio_map = self .speaker_audio_map
@@ -3753,9 +3762,11 @@ def resolve_path(path: str) -> str:
37533762
37543763 # Validate image path exists for image clips
37553764 image_path = clip_data .get ('image_path' )
3756- if image_path :
3757- image_path = resolve_path (image_path )
3758- if not Path (image_path ).exists ():
3765+ image_path_rel = clip_data .get ('image_path_relative' )
3766+
3767+ if image_path or image_path_rel :
3768+ image_path = resolve_path (image_path , image_path_rel )
3769+ if image_path and not Path (image_path ).exists ():
37593770 missing_images .append (f" - { Path (image_path ).name } " )
37603771
37613772 clip = TimelineClip (
0 commit comments