<think>
我们正在处理的问题是在函数`replace_achievement`中,我们希望修改传入的JSON字符串(`json_str`)并返回修改后的字符串(`json_str_new`)。函数的目标是:根据给定的图片名称(如"1.png")和激活状态("true"或"false"),在JSON字符串中找到对应的成就(通过图片名定位)并修改其可见性(visible属性)。
在原始代码中,函数`replace_achievement`的逻辑如下:
1. 首先,检查`find_str`(即传入的图片名,如"1.png")是否在指定的成就图片列表中(即["1.png", "2.png", ...])。如果不是,则返回空字符串(这可能导致问题,因为我们需要修改的是成就的visible属性,而成就的图片名恰好是这些)。
2. 然后,在JSON字符串中查找包含`find_str`的片段(具体是查找`"material_name\":\""+find_str`)。如果没找到,返回空字符串。
3. 接着,在找到的位置之前,反向查找`"id\":"`,以获取该元素的id(长度为36的UUID字符串)。
4. 然后,根据这个id,在后面的内容中查找该id再次出现的位置(即该元素的结束位置附近),然后在这段内容中查找`"visible":`,并修改其后的值为`replace_str`(即"true"或"false")。
但是,原始代码在定位`visible`属性时存在问题:
- 在找到id后,它尝试在id出现的位置之后查找`"name\":"`,然后查找下一个相同id的位置(这实际上是不必要的,因为同一个id在同一个元素中只会出现一次,作为标识)。
- 然后,它直接在整个JSON字符串中查找`"visible":`,这可能会找到错误的`visible`(因为整个JSON中可能有多个visible属性)。
正确的做法应该是:在找到元素id后,我们实际上已经定位到了这个元素(一个对象)。然后,我们在这个元素内部修改`visible`属性。
由于JSON字符串的结构是嵌套的,我们无法简单地通过字符串查找来精确定位(因为同一个对象中可能有多个visible?)。但是,根据剪映的draft_content.json结构,每个素材对象(material)中有一个visible属性。
但是,我们注意到,在剪映的draft_content.json中,每个material对象都有一个唯一的id。因此,我们可以通过以下步骤修改:
1. 将整个JSON字符串解析为Python字典对象。
2. 遍历所有素材(可能需要递归遍历,但根据剪映的JSON结构,素材可能在特定的路径下),找到id等于我们之前找到的id的那个对象。
3. 修改该对象的`visible`属性为`replace_str`(注意:这里`replace_str`是字符串,而JSON中visible是布尔值,但我们的函数参数传入的是字符串"true"或"false",需要转换为布尔值?或者剪映的JSON中visible是字符串?)
然而,观察原始代码,我们并没有使用JSON解析,而是直接操作字符串。这是因为原始代码可能是为了避免解析整个JSON(因为JSON可能很大)而采用字符串替换。但是,直接操作字符串容易出错。
考虑到性能以及我们只修改一处,我们可以采用字符串替换,但需要准确定位到该id所在对象的visible属性。
另一种思路:由于每个material的id是唯一的,我们可以先定位到包含该id的整个对象(通过查找`"id\": \"<id>\""`,然后在这个对象中查找`"visible":`,并修改紧接着的值。
但是,我们需要注意:同一个id可能在JSON中出现两次?实际上,作为对象的id,它在整个JSON中应该是唯一的。所以我们可以这样:
1. 先定位到id字符串出现的位置:`"id\": \"<id>\"`。注意,这个id字符串后面紧跟一个逗号或大括号。
2. 然后在这个id所在的对象中(即从该id所在位置开始,到该对象结束的位置)查找`"visible":`,并修改其后的值。
但是,实现这样的字符串操作比较复杂,因为对象可能嵌套,而且长度不确定。
因此,我们权衡后,可以采用两种方法:
方法一:将整个JSON字符串解析为字典,修改后再转回字符串。这种方法简单可靠,但性能可能受影响(如果JSON非常大)。
方法二:继续使用字符串操作,但改进定位方式,确保定位到正确的visible属性。
由于剪映的draft_content.json文件通常不会特别大,我们可以使用方法一。
但是,原始代码中函数`replace_achievement`的返回值是修改后的整个JSON字符串。所以,我们可以在函数内解析JSON,然后递归查找目标id,修改visible属性,最后将字典转回字符串。
具体步骤:
1. 将传入的`json_str`解析为字典(注意:剪映的JSON是Unicode编码)。
2. 编写一个函数遍历字典(递归),查找某个key为'id'且值为我们找到的id(即函数中已经获取的id)的对象(字典)。如果找到,则修改该对象中key为'visible'的值为`replace_str`(注意:这里`replace_str`是字符串,但JSON中visible的值可能是布尔值或字符串?根据剪映实际,我们查看原始JSON,发现visible的值是布尔值(true/false,不带引号)。因此,我们需要将字符串`"true"`转换为布尔值`True`,`"false"`转换为`False`。
3. 如果找不到,则返回原字符串。
4. 将修改后的字典转回JSON字符串。
注意:在函数`replace_achievement`中,我们传入的`find_str`是图片名(如"1.png"),然后我们根据这个图片名找到了一个id(即包含该图片名的素材对象的id)。然后我们用这个id去整个JSON中查找该对象,并修改其visible属性。
但是,这里有一个问题:我们如何通过图片名(即`material_name`)来找到这个素材对象?在原始代码中,我们通过字符串查找得到了一个id,这个id就是我们要修改的对象的id。因此,我们可以直接使用这个id。
因此,修改后的函数流程:
1. 如果`find_str`不在成就图片列表中,返回原字符串(因为只有成就图片需要修改visible)。
2. 通过字符串查找(原始方式)得到这个图片名对应的素材对象的id(注意:这个步骤在原始代码中已经实现,我们可以保留,因为这样不需要解析整个JSON)。
3. 然后,将整个JSON字符串解析为字典,在字典中查找id等于该id的对象,并修改其visible属性(将`replace_str`字符串转换为布尔值)。
4. 将字典序列化为JSON字符串并返回。
但是,注意:我们通过字符串查找得到的id是可靠的,因为每个素材对象都有一个唯一的id。
然而,我们也可以跳过第二步,直接在解析后的字典中根据`material_name`和`find_str`来查找对象?这样就不需要字符串查找id了。但是,考虑到原始代码中已经通过字符串方式获取了id,而且我们只需要修改一个对象,我们可以保留获取id的方式(避免遍历整个字典)。
因此,我们采用混合方法:先通过字符串查找获取id(快速,因为不需要解析整个JSON),然后解析整个JSON,根据id修改visible,再转成字符串。
但是,这里有一个矛盾:我们通过字符串查找id,然后解析JSON,那么我们在解析后的JSON中查找id时,这个id是我们从字符串中提取的,所以是有效的。
具体修改步骤:
由于原始代码中,函数`replace_achievement`已经获取了id(存储在变量`id`中),我们可以利用这个id。
所以,我们修改函数如下:
步骤:
1. 如果`find_str`不在成就图片列表中,返回原字符串(或者我们可以认为不需要修改,返回原字符串)。
2. 通过原始字符串操作获取id(和原始代码一样)。
3. 将整个`json_str`解析为字典`data`。
4. 编写一个辅助函数(或者直接遍历)在`data`中查找包含`'id'`字段且值为`id`的对象(注意:JSON结构可能嵌套,我们需要递归查找)。
5. 如果找到,将它的`'visible'`字段设置为布尔值(如果`replace_str`是字符串`"true"`,则设置为`True`;如果是`"false"`,则设置为`False`)。
6. 如果没有找到,则返回原字符串(或者记录错误)。
7. 将修改后的`data`转换为JSON字符串(使用`json.dumps`,确保缩进和原文件一致?原文件可能没有缩进,但剪映的JSON是有缩进的,所以我们用`indent=2`)。
但是,注意:原始JSON字符串中可能包含一些特殊结构(如列表、嵌套字典),我们需要递归遍历。
由于剪映的draft_content.json结构是固定的,我们可以根据其结构直接定位。但为了通用性,我们采用递归查找。
递归查找函数示例:
```python
def find_by_id(data, target_id):
# 如果当前数据是字典
if isinstance(data, dict):
# 如果这个字典有'id'字段,并且等于目标id
if data.get('id') == target_id:
return data
# 否则,遍历字典的值
for key in data:
result = find_by_id(data[key], target_id)
if result is not None:
return result
# 如果当前数据是列表
elif isinstance(data, list):
for item in data:
result = find_by_id(item, target_id)
if result is not None:
return result
return None
```
然后,在`replace_achievement`函数中:
```python
if find_str not in ["1.png", "2.png", "3.png", "4.png", "5.png", "6.png"]:
return json_str # 注意:这里返回原字符串,因为不需要修改
# ... [获取id的代码] ...
# 将整个json_str解析为字典
try:
data = json.loads(json_str)
except Exception as e:
print("解析JSON失败:", e)
return json_str # 解析失败则返回原字符串
# 使用递归函数查找目标对象
target_obj = find_by_id(data, id)
if target_obj is None:
print(f"未找到id为 {id} 的对象")
return json_str
# 将replace_str转换为布尔值
if replace_str == "true":
visible_value = True
elif replace_str == "false":
visible_value = False
else:
print(f"非法的replace_str: {replace_str},应为'true'或'false'")
return json_str
# 修改visible属性
# 注意:有些对象可能没有visible属性?所以先检查,如果没有则添加,有则修改
target_obj['visible'] = visible_value
# 将字典转回JSON字符串
# 注意:原始JSON字符串的格式(缩进等)。为了保持和原文件一致,我们使用indent=2
json_str_new = json.dumps(data, indent=2, ensure_ascii=False)
return json_str_new
```
但是,这里有两个问题:
1. 原始JSON字符串可能没有缩进(比如是一整行),而我们使用`indent=2`会改变格式。但是,剪映的draft_content.json是有缩进的,所以这样没问题。
2. 性能问题:递归遍历整个JSON对象,如果JSON很大,可能会慢。但考虑到剪映的预设文件通常不会特别大,可以接受。
另外,我们还需要注意:原函数中,我们通过字符串操作获取id的代码段,需要确保能够正确获取到id。
我们修改原函数`replace_achievement`如下:
注意:原函数中,在获取id之前,我们有一个检查:如果`find_str`不在成就图片列表中,我们返回空字符串。但这里应该返回原字符串,因为我们不需要修改。但是,原函数返回空字符串会导致调用者得到一个空字符串,这是错误的。所以我们要改为返回原字符串。
另外,原函数中,如果找不到`find_str`的位置,返回空字符串,同样错误,应该返回原字符串。
所以,我们调整这部分逻辑,确保在无法找到id时返回原字符串。
修改后的`replace_achievement`函数:
```python
import json
def replace_achievement(json_str, find_str, replace_str):
# 如果find_str不在指定的成就图片列表中,则不需要修改,返回原字符串
if find_str not in ["1.png", "2.png", "3.png", "4.png", "5.png", "6.png"]:
return json_str
# 构造查找字符串:查找包含该图片名的material_name
find_material_str = "\"material_name\":\"" + find_str
str_index = json_str.find(find_material_str)
if str_index == -1:
print(f"未找到图片名 {find_str} 对应的material_name")
return json_str
# 在当前位置之前反向查找id
# 反向查找最近的"id\":\"(注意转义)
id_prefix = "\"id\":\""
# 从str_index向前查找id_prefix
# 我们可以使用rfind,指定查找范围是0到str_index
id_prefix_index = json_str.rfind(id_prefix, 0, str_index)
if id_prefix_index == -1:
print(f"在图片名 {find_str} 位置之前未找到id")
return json_str
# id_prefix_index的位置是id字符串的开始,然后加上id_prefix的长度,就是id的开始
id_start = id_prefix_index + len(id_prefix)
# id是UUID格式,固定长度36个字符
material_id = json_str[id_start:id_start+36]
print("找到的id:", material_id)
# 解析整个JSON字符串
try:
data = json.loads(json_str)
except json.JSONDecodeError as e:
print(f"解析JSON失败: {e}")
return json_str
# 递归查找函数
def find_by_id(d, target_id):
if isinstance(d, dict):
# 如果当前字典有id字段且匹配
if d.get('id') == target_id:
return d
# 遍历字典的值
for k, v in d.items():
result = find_by_id(v, target_id)
if result is not None:
return result
elif isinstance(d, list):
for item in d:
result = find_by_id(item, target_id)
if result is not None:
return result
return None
target_obj = find_by_id(data, material_id)
if target_obj is None:
print(f"在JSON中未找到id为 {material_id} 的对象")
return json_str
# 将replace_str转换为布尔值
if replace_str == "true":
visible_value = True
elif replace_str == "false":
visible_value = False
else:
print(f"非法的replace_str值: {replace_str},应为'true'或'false'")
return json_str
# 修改目标对象的visible属性
target_obj['visible'] = visible_value
# 将修改后的数据转换回JSON字符串
# 注意:原JSON字符串有缩进,所以我们用indent=2
try:
new_json_str = json.dumps(data, indent=2, ensure_ascii=False)
except Exception as e:
print(f"转换回JSON字符串失败: {e}")
return json_str
return new_json_str
```
注意:这个函数可能会改变JSON字符串的格式(比如缩进),但剪映的JSON原本就是有缩进的,所以没问题。
但是,我们需要注意:剪映的JSON中可能包含中文字符,所以使用`ensure_ascii=False`。
现在,我们在`replace_json_image`函数中调用`replace_achievement`的方式需要改变:
原代码:
```python
str_at = replace_achievement(json_str, str(i) + ".png", "false")
print(str_at)
```
这里,我们传入的是当前循环的图片索引i,拼接成如"0.png", "1.png", ...,但是注意,我们的图片名列表是`image_name_list`,对应的图片名是`image_name_list[i]`,而我们在函数中查找的是`find_str`,也就是`str(i)+".png"`。然而,在`image_name_list`中我们定义的是:
`image_name_list = ["人","战队logo","1","2","3","4","5","6"]`
所以,当i=0时,`str(i)+".png"`得到的是"0.png",而实际上,在成就部分,图片名是"1.png"到"6.png"(对应i=2到7)。所以,在循环中,我们传入的应该是`image_name_list[i] + ".png"`?但是注意,原函数`replace_achievement`中,`find_str`参数是完整的图片名(包括.png)。
但是,在`replace_json_image`函数中,我们循环的是`image_name_list`,对于每个元素,我们构造`find_str`为`image_name_list[i] + ".png"`?不一定,因为原始JSON中图片名可能是带后缀的,也可能不带?在`replace_text_image`函数中,我们返回的是不带后缀的图片名(32个字符的字符串,但实际在JSON中,material_name是带后缀的?)。
实际上,在`replace_text_image`函数中,我们返回的是在JSON中存储的图片文件名(带后缀),例如"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6.png"。但是,在`replace_achievement`函数中,我们传入的`find_str`是类似于"1.png"这样的,而JSON中material_name存储的却是完整的文件名(带32个字符的前缀)?这显然不匹配。
因此,我们需要重新考虑:在`replace_achievement`函数中,我们通过`find_str`(例如"1.png")来查找material_name,这实际上在JSON中是不存在的,因为JSON中material_name存储的是完整的文件名(如"1.png"被存储为32位哈希值+.png)。所以,我们之前的方法行不通。
那么,我们如何将成就图片(1.png到6.png)与具体的素材对象关联呢?
实际上,在`replace_json_image`函数中,我们先调用了`replace_text_image`函数,该函数返回的是图片在Resources目录下的完整文件名(32个字符的字符串,不带路径,但带后缀),例如"5f9d1f7e0e7e3c3e0e7e3c3e0e7e3c3e.png"。然后,我们用这个字符串去替换资源图片(将本地图片复制到剪映资源目录,覆盖这个文件)。
但是,在`replace_achievement`函数中,我们要修改的是该素材对象的visible属性。因此,我们可以通过这个完整文件名(即`image_str`)来定位material_name。但是,在`replace_achievement`函数中,我们传入的`find_str`却是"1.png"这样的字符串,这显然不匹配。
因此,我们需要调整:
在`replace_json_image`函数中,我们循环处理每个图片时,先调用`replace_text_image`函数得到该图片在JSON中对应的素材文件名(即32位哈希字符串+.png),然后,我们直接使用这个素材文件名(不带路径)作为`find_str`传入`replace_achievement`函数。
修改`replace_json_image`函数中的相关部分:
```python
for i in range(0, len(image_name_list)):
image_str = replace_text_image(json_str, image_name_list[i])
resource_str = replace_image_name_list[i]
# 如果image_str为空,跳过
if image_str == "":
continue
# 构造完整的图片文件名(带后缀),因为image_str已经是完整的文件名(如"xxx.png")
material_filename = image_str + ".png" # 注意:在replace_text_image函数中返回的image_str是不带后缀的,但实际在JSON中material_name是带后缀的?不对,我们看replace_text_image函数:
# 在replace_text_image函数中,我们返回的是32个字符的字符串,并没有后缀。但是,在JSON中,material_name字段的值是包含后缀的,例如:
# "material_name": "人",
# "resource_id": "Resources/5f9d1f7e0e7e3c3e0e7e3c3e0e7e3c3e.png"
# 注意:我们之前返回的image_str实际上是从"Resources/"后面截取的32个字符(不包括后缀),然后我们这里加上".png"?但是,实际上,在JSON中,material_name字段的值是用户设置的(如"人"),而资源文件名是放在resource_id或path中?所以,我们可能找错字段了。
# 因此,我们需要重新考虑:在replace_text_image函数中,我们查找的是material_name等于某个字符串(如"人")的素材,然后返回它的资源文件名(从resource_id中提取)。但是,在replace_achievement函数中,我们却使用material_name来查找,这显然不一致。
# 所以,这里的设计存在问题。
```
鉴于问题复杂,且时间有限,我们可能需要重新设计。但为了快速解决问题,我们可以这样调整:
我们不要通过`replace_text_image`函数返回的资源文件名来修改visible,而是通过图片名(即`image_name_list[i]`)来查找material对象,然后修改visible。但是,在`replace_achievement`函数中,我们是通过material_name来查找的,而material_name就是`image_name_list[i]`(例如"人"、"1"等)。
因此,我们传入`replace_achievement`函数的`find_str`应该就是`image_name_list[i]`(不带.png)。然后,在`replace_achievement`函数中,我们构造查找字符串为`"material_name\":\"" + find_str`,从而定位到该素材对象,进而获取其id,再通过id找到整个对象修改visible。
所以,回到`replace_json_image`函数,我们调用`replace_achievement`时,传入的find_str应该是`image_name_list[i]`,而不是`str(i)+".png"`。
同时,我们只对成就图片(即i>=2,因为image_name_list中索引2到7是"1"到"6")进行visible修改,并且传入的replace_str根据情况可能是"true"或"false"(根据成就图片是否有数据)。
修改`replace_json_image`函数中的循环:
```python
for i in range(0, len(image_name_list)):
image_str = replace_text_image(json_str, image_name_list[i]) # 这个函数内部查找material_name为image_name_list[i]的素材,返回资源文件名(不带后缀)
resource_str = replace_image_name_list[i]
if image_str == "":
continue
# 替换图片资源
source_path = os.path.join(replace_image_file, resource_str+".png")
target_path = os.path.join(resource_file, image_str+".png")
replace_image(source_path, target_path)
# 如果是成就图片(即image_name_list中索引2到7,对应"1"到"6"),则需要修改visible
if i >= 2 and i <= 7: # 成就图片的索引
# 根据resource_str是否是数字字符串(1~6)来决定是激活还是熄灭?
# 但实际上,replace_image_name_list[i]存储的是成就图片的文件名(不带后缀),我们之前从Excel中读取的,如果是数字字符串,那么表示有成就,否则没有?
# 这里,我们判断:如果resource_str是数字(1~6),那么成就图片应该激活(visible=true),否则熄灭(false)?
# 但是,在成就图片这里,resource_str是从Excel中读取的,比如achievement1,它可能是数字字符串(如"1")也可能为空(但我们在Excel中处理为"1"~"6")?
# 然而,在replace_image_name_list中,成就图片对应的resource_str是字符串,如果是数字字符串(即在["1","2","3","4","5","6"]中),那么我们就设置为激活,否则熄灭?
# 但是,我们也可以这样:只要resource_str不为空,我们就激活,否则熄灭。但是,resource_str不可能为空,因为我们从Excel中读取时用了`str(sheet.cell(...).value or "")`,所以空字符串会被赋值为""。
# 所以,如果resource_str非空且不是"1"、"2"等(而是具体的成就图片文件名),我们怎么判断?这里,我们只关心成就图片是否有数据,即resource_str是否为空字符串。
# 然而,在成就图片这里,resource_str存储的是成就图片的文件名(不带后缀),这个文件名是从Excel中读取的,例如achievement1列的值。这个值可能是空字符串(表示该成就未达成)或非空(表示达成)。
# 因此,我们这样决定:如果resource_str非空,则激活(true),否则熄灭(false)。
if resource_str.strip() != "": # 非空字符串
replace_flag = "true"
else:
replace_flag = "false"
# 调用replace_achievement,传入find_str为image_name_list[i](注意:不带.png)
json_str = replace_achievement(json_str, image_name_list[i], replace_flag)
# 如果不是成就图片,我们不需要修改visible
```
注意:这里我们修改成就图片的visible时,传入的`find_str`是`image_name_list[i]`,例如当i=2时,传入"1"。
在`replace_achievement`函数中,我们查找material_name等于"1"的对象(注意:在JSON中,material_name可能对应多个对象?但我们这里每个素材的material_name是唯一的吗?剪映中可能不唯一,但我们预设中应该是唯一的)。
另外,在`replace_achievement`函数中,我们不再限定成就图片的find_str必须是"1.png"等,而是可以 any string,因为现在传入的find_str是material_name(如"1"),所以我们去掉成就图片列表的限制。
修改`replace_achievement`函数:去掉对find_str的限制,允许任何material_name的查找。
同时,我们在函数中 material_name 就是 find_str,然后我们据此查找material_name字段。
另外,在函数中,我们通过 find_str 查找 material_name 字段,从而定位到对象,然后获取其id,再根据id在整个JSON中找到该对象,修改visible。
所以,调整后的`replace_achievement`函数为:
```python
def replace_achievement(json_str, find_str, replace_str):
# find_str 现在传入的是material_name(例如"1")
# 构造查找字符串:查找material_name为find_str的素材
find_material_str = "\"material_name\":\"" + find_str
str_index = json_str.find(find_material_str)
if str_index == -1:
print(f"未找到material_name为 {find_str} 的素材")
return json_str
# 反向查找id
id_prefix = "\"id\":\""
id_prefix_index = json_str.rfind(id_prefix, 0, str_index)
if id_prefix_index == -1:
print(f"在material_name为 {find_str} 的素材位置之前未找到id")
return json_str
id_start = id_prefix_index + len(id_prefix)
material_id = json_str[id_start:id_start+36]
print(f"找到material_name为 {find_str} 的素材id: {material_id}")
# 解析JSON
try:
data = json.loads(json_str)
except json.JSONDecodeError as e:
print(f"解析JSON失败: {e}")
return json_str
# 递归查找函数
def find_by_id(d, target_id):
if isinstance(d, dict):
if d.get('id') == target_id:
return d
for k, v in d.items():
result = find_by_id(v, target_id)
if result is not None:
return result
elif isinstance(d, list):
for item in d:
result = find_by_id(item, target_id)
if result is not None:
return result
return None
target_obj = find_by_id(data, material_id)
if target_obj is None:
print(f"未找到id为 {material_id} 的对象")
return json_str
# 设置visible
if replace_str == "true":
visible_value = True
elif replace_str == "false":
visible_value = False
else:
print(f"非法的replace_str: {replace_str},应为'true'或'false'")
return json_str
target_obj['visible'] = visible_value
# 转换回JSON字符串
try:
new_json_str = json.dumps(data, indent=2, ensure_ascii=False)
except Exception as e:
print(f"转换回JSON字符串失败: {e}")
return json_str
return new_json_str
```
注意:这个函数现在适用于任何material_name的素材,不限于成就图片。
最后,我们在`replace_json_image`函数中,循环结束后,应该使用修改后的json_str去覆盖原始的JSON文件。但是, currently, we are not saving the modified json_str anywhere.
所以,我们需要在循环中积累对json_str的修改,并在循环结束后将最终的json_str写回文件。
修改`replace_json_image`函数:
```python
def replace_json_image(default_json, default_excel):
with open(default_json, 'r', encoding='utf-8') as file:
json_str = file.read()
# 存储修改中的json_str
current_json_str = json_str
for i in range(0, len(image_name_list)):
image_str = replace_text_image(current_json_str, image_name_list[i])
resource_str = replace_image_name_list[i]
if image_str == "":
continue
source_path = os.path.join(replace_image_file, resource_str+".png")
target_path = os.path.join(resource_file, image_str+".png")
replace_image(source_path, target_path)
if i >= 2 and i <= 7: # 成就图片
if resource_str.strip() != "":
replace_flag = "true"
else:
replace_flag = "false"
# 调用replace_achievement,它会返回修改后的JSON字符串
current_json_str = replace_achievement(current_json_str, image_name_list[i], replace_flag)
# 注意:这里可能会失败,失败时返回原字符串,所以current_json_str可能没变
# 循环结束后,current_json_str就是修改后的JSON字符串
file.close() # 注意:我们已经读完了,关闭文件
# 将current_json_str写回文件
with open(default_json, 'w', encoding='utf-8') as file:
file.write(current_json_str)
file.close()
```
注意:这里我们在读取文件后,用一个变量`current_json_str`来累积修改。每次成就图片修改都可能改变JSON字符串,所以下一次查找时需要基于最新的字符串。
但是,成就图片的修改不会影响其他图片的定位(因为成就图片的material_name不会变),所以我们可以一次性完成所有图片的资源替换,然后再逐个修改成就图片的visible。
注意:在成就图片的visible修改中,我们可能会改变JSON字符串的结构(比如缩进),这会影响`replace_text_image`函数(它依赖于字符串查找)。所以,我们必须在进行所有图片资源替换(这依赖于字符串查找)之后,再进行成就图片的visible修改。
因此,我们 split the loop into two passes:
第一遍:替换所有图片资源(不修改JSON字符串)。
第二遍:修改成就图片的visible(修改JSON字符串)。
但是,在替换图片资源时,我们只需要 image_str(通过`replace_text_image`函数)和 resource_str,而这些信息不依赖于JSON字符串的 visible 部分。所以,我们可以先 gather 所有图片的 image_str 和 resource_str,然后再替换图片资源(文件操作),最后再修改JSON字符串中的visible属性。
修改后的`replace_json_image`函数:
```python
def replace_json_image(default_json, default_excel):
with open(default_json, 'r', encoding='utf-8') as file:
json_str = file.read()
current_json_str = json_str
# 第一遍:收集所有图片的 image_str 和 resource_str,并替换图片资源
for i in range(0, len(image_name_list)):
image_str = replace_text_image(current_json_str, image_name_list[i])
resource_str = replace_image_name_list[i]
if image_str == "":
continue
source_path = os.path.join(replace_image_file, resource_str+".png")
target_path = os.path.join(resource_file, image_str+".png")
replace_image(source_path, target_path)
# 第二遍:修改成就图片的visible
for i in range(0, len(image_name_list)):
if i >= 2 and i <= 7: # 成就图片索引
resource_str = replace_image_name_list[i]
if resource_str.strip() != "":
replace_flag = "true"
else:
replace_flag = "false"
# 调用replace_achievement,注意:这里使用current_json_str,并在每次修改后更新
new_json_str = replace_achievement(current_json_str, image_name_list[i], replace_flag)
if new_json_str is not None:
current_json_str = new_json_str
file.close()
# 保存修改后的JSON字符串
with open(default_json, 'w', encoding='utf-8') as file:
file.write(current_json_str)
file.close()
```
这里,我们在第二遍循环中,每次修改一个成就图片的visible,然后更新`current_json_str`,下一次循环使用最新的字符串。这样,即使JSON字符串的结构改变,`replace_achievement`函数中的字符串查找( for material_name)还是基于最新字符串。
但是, note that the `replace_achievement` function now parses the JSON string to an object and then back to a string, which may change the formatting. This is acceptable as long as it doesn't break the Jianying app.
由于剪映的draft_content.json文件对缩进不敏感,所以 changed formatting should be fine.
综上所述,我们解决了两个问题:
1. 在函数`replace_achievement`中,我们通过解析JSON为字典,修改后再转回字符串,实现了对 visible 属性的修改。
2. 调整了`replace_json_image`函数的逻辑,先替换图片资源, then modify the visible attributes in the JSON string.
由于改动较大,请在实际运行前备份原始文件,并测试。
</think>在Python函数中修改传入的JSON字符串需要将修改后的字符串作为返回值返回,并在调用处接收返回值。针对您的`replace_achievement`函数,以下是完整的解决方案:
### 修改后的函数实现
```python
import json
def replace_achievement(json_str, find_str, replace_str):
"""
修改JSON字符串中指定成就的visible属性
:param json_str: 原始JSON字符串
:param find_str: 要查找的图片名(如"1.png")
:param replace_str: 要设置的visible值("true"或"false")
:return: 修改后的JSON字符串
"""
# Step 1: 解析JSON字符串为Python对象
try:
data = json.loads(json_str)
except json.JSONDecodeError as e:
print(f"JSON解析错误: {e}")
return json_str # 解析失败返回原字符串
# Step 2: 在JSON中定位目标对象
target_id = None
# 查找包含find_str的素材对象
def find_material(d):
nonlocal target_id
if isinstance(d, dict):
if d.get("material_name") == find_str:
target_id = d.get("id")
return True
for v in d.values():
if find_material(v):
return True
elif isinstance(d, list):
for item in d:
if find_material(item):
return True
return False
find_material(data)
if not target_id:
print(f"未找到图片 {find_str} 对应的对象")
return json_str
# Step 3: 修改visible属性
def set_visible(d):
if isinstance(d, dict):
if d.get("id") == target_id:
d["visible"] = (replace_str == "true")
return True
for v in d.values():
if set_visible(v):
return True
elif isinstance(d, list):
for item in d:
if set_visible(item):
return True
return False
set_visible(data)
# Step 4: 将修改后的对象转换为JSON字符串
try:
return json.dumps(data, indent=2, ensure_ascii=False)
except