BiliBili 缓存视频整理


上周刚学习了 Python,我就在想能不能实现这个操作,而且还能巩固一下所学的知识,写这个程序的时候遇到了很多的问题,尤其是数组越界看得我头大,不过后来还是都解决了,写完这个程序我对列表和切片以及文件的操作掌握的更加深刻以及各种报错和异常信息,这其中还有一段代码是一维列表转换二维列表且每 4 个为一个列表,写了一晚上都没实现,最后求教大佬,大佬说这是一个矩阵算法,当我看到大佬用一行代码实现了我几十行代码的操作时,被深深的震惊住了,大佬让我多做做算法题。

前言

        上周刚学习了 Python,我就在想能不能实现这个操作,而且还能巩固一下所学的知识,写这个程序的时候遇到了很多的问题,尤其是数组越界看得我头大,不过后来还是都解决了,写完这个程序我对列表和切片以及文件的操作掌握的更加深刻以及各种报错和异常信息,这其中还有一段代码是一维列表转换二维列表且每 4 个为一个列表,写了一晚上都没实现,最后求教大佬,大佬说这是一个矩阵算法,当我看到大佬用一行代码实现了我几十行代码的操作时,被深深的震惊住了,大佬让我多做做算法题。

        这其中还有一个问题没有解决,就是这个程序只能转换 12 集视频的番,因为我想不出来 24 集的番和 13 集的番应该怎么实现,本来想着是 12 集的视频有 12 个配置文件总共存到一个列表里面就是 72 个元素

1 	 ['[.ShellClassInfo]']
2 	 ['InfoTip=初恋怪兽', '1.', '我是小学生', '你打算怎么办']
3 	 ['[ViewState]=']
4 	 ['Mode=']
5 	 ['Vid=']
6 	 ['FolderType=Generic']
7 	 ['[.ShellClassInfo]']
8 	 ['InfoTip=初恋怪兽', '2.', '啊', '华住庄']
9 	 ['[ViewState]=']
10 	 ['Mode=']
11 	 ['Vid=']
12 	 ['FolderType=Generic']
13 	 ['[.ShellClassInfo]']
14 	 ['InfoTip=初恋怪兽', '3.', '第一次的']
15 	 ['[ViewState]=']
16 	 ['Mode=']
17 	 ['Vid=']
18 	 ['FolderType=Generic']
19 	 ['[.ShellClassInfo]']
20 	 ['InfoTip=初恋怪兽', '4.', '被盯上的xx']

从第二个元素开始取到第 8 个,每隔 5 个就把这个元素的值存到新的列表里面,一开始想的是让每个下标加 5,但是后来发现也不好实现,然后就把每集的信息用已知的值卸载了代码里,详见 200 行,或许我是有办法的,这周末在康康。(本来打算用最笨的办法,就是写一个判断,如果是 12 集或者 13 集或者 24 集的视频,都写一个这样的代码)

这个程序的实现结构:

原目录结构

F:.
│  5018.info
│  cover.jpg
│  desktop.ini
│
├─5171687
│  │  5171687.dvi
│  │  cover.jpg
│  │  desktop.ini
│  │
│  └─1
│          5171687.info
│          5171687_1_0.flv
│          5171687_1_1.flv
│          5171687_1_2.flv
│          5171687_1_3.flv
│
├─5266459
│  │  5266459.dvi
│  │  cover.jpg
│  │  desktop.ini
│  │
│  └─1
│          5266459.info
│          5266459_1_0.flv
│          5266459_1_1.flv
│          5266459_1_2.flv
│          5266459_1_3.flv
│
......

目标目录结构

1 	 1_初恋怪兽_1.我是小学生你打算怎么办_517168710.flv
2 	 2_初恋怪兽_1.我是小学生你打算怎么办_517168711.flv
3 	 3_初恋怪兽_1.我是小学生你打算怎么办_517168712.flv
4 	 4_初恋怪兽_1.我是小学生你打算怎么办_517168713.flv
5 	 5_初恋怪兽_2.啊华住庄_526645910.flv
6 	 6_初恋怪兽_2.啊华住庄_526645911.flv
7 	 7_初恋怪兽_2.啊华住庄_526645912.flv
8 	 8_初恋怪兽_2.啊华住庄_526645913.flv
9 	 9_初恋怪兽_3.第一次的_535982510.flv
10 	 10_初恋怪兽_3.第一次的_535982511.flv
11 	 11_初恋怪兽_3.第一次的_535982512.flv
12 	 12_初恋怪兽_3.第一次的_535982513.flv

介绍

        Android 的 BiliBili 和 Windows10 缓存的番,在备份的时候需要把缓存的目录拷贝出来,但是在某些情况下需要用电脑的播放器来直接播放视频,可在这种情况下你需要进入三层缓存的嵌套目录下然后打开某集的四段视频,这个时候当你看下一级的时候你需要返回上两层目录然后再进入下层目录在添加四段视频大播放器,这是一件很麻烦的事情,以前我总是手动整理这些视频文件到一个专有的目录下,现在 AndroidBiliBiliFilePathFormat 诞生了,AndroidBiliBiliFilePathFormat 的诞生解决了我这一切的烦恼

思路

AndroidBiliBili思维结构图

诞生的那一刻

AndroidBiliBiliFilePathFormat诞生了

代码

FormatFilePath.py 主入口

import os
import shutil
import StringLine
import drawLine
import checkFileList
import TraverseListValue

# 绘制程序头部顶部边界线
drawLine.drawStringLine()
# 程序描述
print("名称: Android BiliBili File Path Format (Android BiliBili 缓存文件路径格式化)")
print("功能: 自动化整理文件到一个目录")
print("命名规则: 番名_当前集数名称_编号")
# 绘制程序头部底部边界线
drawLine.drawStringLine()
# 获取当前番的路径
# getFilePath = input("输入需要格式化的目录: ")
# 测试使用的固定目录
# getFilePath = r"C:\Users\HiWin10\Desktop\BiliBili_Download\ss5018"
print(r"番的路径名, 例如: C:\Users\HiWin10\Desktop\BiliBili_Download\ss5018")
getFilePath = input("输入需要格式化的番绝对路径: ")
# 删除路径两侧双引号
getFilePath = getFilePath.lstrip('"')
getFilePath = getFilePath.rstrip('"')

# 初始化每集视频所在的绝对路径
list_first_Directory = []
# 初始化每集视频所在的绝对路径
list_second_vedio_info = []
# 初始化视频列表
list_second_vedio = []
# 初始化层级目录
list_Directory = []

# 遍历番的目录
file_list = os.listdir(getFilePath)
for i in file_list:
    # 获取每集视频所在绝对路径
    if os.path.isdir(getFilePath + "\\" + i + "\\" + "1"):
        list_first_Directory.append(getFilePath + "\\" + i + "\\" + "1")
# 校验每集视频所在位置的绝对路径
print("校验每集视频所在位置的绝对路径list_first_Directory: ", list_first_Directory)
# 遍历视频目录
for i in list_first_Directory:
    list_second_vedio_info.append(os.listdir(i))
TraverseListValue.ListNowDirectory(list_first_Directory)
# 每集目录下的视频以二维数组形式存储: XXX番[第X集][X集第Y段]
print("校验每集目录下的文件list_second_vedio_info: ", list_second_vedio_info)
# 获取视频二维列表长度
print("获取视频列表的一维长度, 二维需要乘以4list_second_vedio_info: ", len(list_second_vedio_info))
# 获取每集视频所在的文件路径总长度
print("每集视频所在的文件路径总长度list_first_Directory: ", len(list_first_Directory))
# 初始化计数器
count = 0
# 删除视频二维数组中所有的 .info 元素
for i in range(len(list_second_vedio_info)):
    for j in list_second_vedio_info[i]:
        # if j.endswith(".info") == False:
        if not j.endswith(".info"):
            list_second_vedio.append(j)
# 校验已删除的.info 元素
print("校验已删除的元素 .info(list_second_vedio): ", list_second_vedio)
# 获取修正后列表的长度, 应该是: 48 (实际长度44, 暂时没查出为什么)[已解决]
print("获取修正后列表的长度list_second_vedio: ", len(list_second_vedio))
# 绘制局部分割线
StringLine.StringODLine()

# 检查文件名的规律和顺序
print("检查文件名的规律和顺序list_second_vedio: ")
checkFileList.checkVedioFileList(list_second_vedio)
# 初始化备用视频文件名
backup_list_second_vedio = list_second_vedio.copy()
# 绘制局部分割线
StringLine.StringODLine()
"""
理论视频长度为48段, 实际列表输出长度为44
原因: 第 47 行 list_second_vedio_info 遍历列表是长度 - 1 (减一是为了防止列表越界)
    这直接导致了这个问题的产生: 最后一个目录并没有被获取到
    2020/07/22  19:48    <DIR>          6321373
结果: 第 47 行代码已修正, 之前在调试时加上去的.
"""
# 遍历层级目录
# getFilePath 的上级目录
list_Directory.append(os.listdir(getFilePath))
# 初始化层级目录绝对路径
list_Directory_Path = []
# 拼接层级绝对路径
for i in range(len(list_Directory)):
    for j in list_Directory[i]:
        list_Directory_Path.append(getFilePath + "\\" + j)
# 检查层级目录输出信息
print("检查层级目录输出信息list_Directory_Path: ", list_Directory_Path)
# 绘制局部分割线
StringLine.StringODLine()
# 初始化层级目录的子列表
list_Directory_Path_List = []
# 判断是目录
for i in list_Directory_Path:
    if os.path.isdir(i):
        list_Directory_Path_List.append(i)
# 检查新的层级列表
print("检查新的层级列表list_Directory_Path_List: ")
TraverseListValue.ListNowDirectory(list_Directory_Path_List)
# 绘制局部分割线
StringLine.StringODLine()

# 初始化配置文件列表
list_Directory_Info = []
# 获取层级目录的下一级文件列表
print("获取层级目录的下一级文件列表list_Directory_Path_List: ")
TraverseListValue.ListNowDownDirectory(list_Directory_Path_List)
# 插入局部分割线
StringLine.StringODLine()
# 初始化每个层级目录下的配置文件列表
list_Directory_Path_list_Info_File = []
for i in list_Directory_Path_List:
    list_Directory_Path_list_Info_File.append(os.listdir(i))
print("初始化每个层级目录下的配置文件列表: ", list_Directory_Path_list_Info_File)
# 绘制局部分割线
StringLine.StringODLine()

# 初始化计数器
count = 0
# 添加配置文件到列表
for i in list_Directory_Path_list_Info_File:
    for j in list_Directory_Path_list_Info_File[count]:
        if j == "desktop.ini":
            list_Directory_Info.append(j)
    count += 1
# 检查列表数据
print("检查列表数据list_Directory_Info: ")
TraverseListValue.ListNowDirectory(list_Directory_Info)
"""
二级层级目录绝对路径拼接配置文件示例:
print(list_Directory_Path_List[0] + "\\" + list_Directory_Info[0])
"""
# 绘制局部分割线
StringLine.StringODLine()

# 初始化层级目录绝对路径和配置文件路径拼接
list_Directory_Path_List_And_list_Directory_Info = []
# 拼接层级目录和配置文件绝对路径
for i in list_Directory_Path_List:
    list_Directory_Path_List_And_list_Directory_Info.append(i + "\\" + "desktop.ini")
# 校验层级目录绝对路径和配置文件路径拼接数据
print("校验层级目录绝对路径和配置文件路径拼接数据list_Directory_Path_List_And_list_Directory_Info: ")
TraverseListValue.ListNowDirectory(list_Directory_Path_List_And_list_Directory_Info)
# 绘制局部分割线
StringLine.StringODLine()

# 初始化配置文件信息列表
desktopInformationList = []
# 获取配置文件信息
for i in list_Directory_Path_List_And_list_Directory_Info:
    with open(i, mode="r") as fileRead:
        desktopInformationList.append(fileRead.readlines())
# 校验配置文件信息列表
print("校验配置文件信息列表desktopInformationList: ")
TraverseListValue.ListNowDirectory(desktopInformationList)
# 绘制局部分割线
StringLine.StringODLine()

# 初始化配置文件配置信息子串列表
desktopInformationSubStringList = []
# 初始化计数器
count = 0
# 初始化配置文件信息临时存储子串列表
desktopInformationTempSubStringList = []
# 截取配置文件信息中的: 番名称 + 当前集编号 + 当前集名称 + 当前视频分段编号
for i in desktopInformationList:
    for j in desktopInformationList[count]:
        # 截取配置文件第二行
        # print(j[8:-1])
        desktopInformationTempSubStringList.append(j.split())
    count += 1
# 校验配置文件信息临时存储子串列表
print("校验配置文件信息临时存储子串列表desktopInformationTempSubStringList: ")
TraverseListValue.ListNowDirectory(desktopInformationTempSubStringList)
# 初始化计数器
count, count_j = 0, 0
# 绘制局部分割线
StringLine.StringODLine()

# 添加配置文件信息到列表
for i in desktopInformationTempSubStringList:
    for j in desktopInformationTempSubStringList[count]:
        # 大佬的实现方式
        if (count - 1) % 6 == 0:
            desktopInformationSubStringList.append(j)
    count += 1
# 校验列表
print("校验列表desktopInformationTempSubStringList: ")
TraverseListValue.ListNowDirectory(desktopInformationTempSubStringList)
# 绘制局部分割线
StringLine.StringODLine()

# 校验子串列表截取的配置信息
print("校验子串列表截取的配置信息desktopInformationSubStringList: ")
TraverseListValue.ListNowDirectory(desktopInformationSubStringList)
# 绘制局部分割线
StringLine.StringODLine()

# 初始化配置文件信息字符串
desktopInformationSubStringToString = []
# 删除列表中所有的 InfoTip= 子串
# print('InfoTip=初恋怪兽'[8:])
for i in desktopInformationSubStringList:
    if "Info" in i:
        desktopInformationSubStringToString.append(i[8:])
    else:
        desktopInformationSubStringToString.append(i)
# 校验配置文件
print("校验配置文件desktopInformationSubStringToString: ")
TraverseListValue.ListNowDirectory(desktopInformationSubStringToString)
# 绘制局部分割线
StringLine.StringODLine()

# 初始化番名
BiliBiliHeadName = desktopInformationSubStringToString[0]
print("获取番名BiliBiliHeadName: ", BiliBiliHeadName)
# 初始化计数器
count = 0
# 删除列表中所有番名
for i in desktopInformationSubStringToString:
    if i == BiliBiliHeadName:
        del desktopInformationSubStringToString[count]
    count += 1
# 校验已修正后的列表
print("校验已修正后的列表desktopInformationSubStringToString: ")
TraverseListValue.ListNowDirectory(desktopInformationSubStringToString)
# 绘制局部分割线
StringLine.StringODLine()

# 初始化名称列表
BiliBiliName = []
# 初始化名称字符串
BiliBiliNameString = ""
# 初始化数字位置记录列表
isNumberList = []
# 初始化计时器
count = 0
for i in desktopInformationSubStringToString:
    # 判断当前元素是否是数字
    if i[0:1].isnumeric():
        isNumberList.append(count)
    count += 1
# 校验获取到每个数字在列表中的位置
print("获取到每个数字在列表中的位置isNumberList: ", isNumberList)
# 初始化计数器
count = 0
counts = 1
# 截取每个数字之前的元素
for i in range(len(desktopInformationSubStringToString)):
    # 如果下个字符串的头不是数字
    if desktopInformationSubStringToString[i][0:1].isnumeric():
        BiliBiliNameString += "_"
    # 添加每个元素到名称字符串 (虽然和本意不一样,但是功能实现了就行, 本来打算是根据isNumberList的区间进行分割和合并元素值的, 无意中这样给实现了)
    if i < len(desktopInformationSubStringToString):
        BiliBiliNameString += desktopInformationSubStringToString[i]
    counts += 1
# 校验字符串
print("向每个数字前添加下划线字符串BiliBiliNameString: ", BiliBiliNameString)
BiliBiliName = BiliBiliNameString.split("_")
# 初始化计数器
count = 0
# 判断列表中的元素是否为空
for i in BiliBiliName:
    # 为空
    if i == "":
        # 删除元素
        BiliBiliName.pop(count)
    count += 1
# 添加番名 (如果加进去可能会破坏后序对应的命名, 两个列表对接)
# BiliBiliName.insert(0, BiliBiliHeadName)
print("查看整理后的名称列表BiliBiliName: ")
TraverseListValue.ListNowDirectory(BiliBiliName)
# 绘制局部分割线
StringLine.StringODLine()

# 合并每集的文件名称
# 初始化临时列表
temp_list = []
# 初始化临时二维列表
list_second_vedio_list = []
# 初始化计数器
count = 0
# 查看已存在视频列表
print(list_second_vedio)
# 删除列表中所有的下划线
for i in list_second_vedio:
    i = i.replace("_", "")
    list_second_vedio[count] = i
    count += 1
print("删除列表元素中的下划线list_second_vedio: ", list_second_vedio)
print("查看列表类型list_second_vedio: ", type(list_second_vedio))
# 一维列表转换二维列表 (某大佬的帮我写的代码)
list_second_vedio_list = []
temp_list = []
for idx, item in enumerate(list_second_vedio):
    temp_list.append(item)
    if idx % 4 == 3:
        list_second_vedio_list.append(temp_list.copy())
        temp_list.clear()
# 大佬的一行代码实现:
# rets = [[list_second_vedio[i + j] for j in range(4)] for i in range(0, len(list_second_vedio), 4)]
# 校验二维列表
print("校验二维列表list_second_vedio_list: ", list_second_vedio_list)
TraverseListValue.ListNowDirectory(list_second_vedio_list)
# 绘制局部分割线
StringLine.StringODLine()

"""
拼接名称格式:
番名_第x集序号_第x集名称_第x1段
番名_第x集序号_第x集名称_第x2段
番名_第x集序号_第x集名称_第x3段
番名_第x集序号_第x集名称_第x4段
番名_第y集序号_第y集名称_第y1段
"""
# 测试拼接名称
print("测试拼接名称: ", BiliBiliHeadName + "_" + BiliBiliName[0] + "_" + list_second_vedio_list[0][1])
# 初始化名称拼接列表 番名 与 集序号名称 与 视频段
BiliBiliHeadNameAndBiliBiliNameAndlist_second_vedio_list = []
# 初始化计数器
count = 0
count_j = 0
counts = 1
for i in list_second_vedio_list:
    for j in list_second_vedio_list[count]:
        BiliBiliHeadNameAndBiliBiliNameAndlist_second_vedio_list.append(str(counts) + "_" + BiliBiliHeadName + "_" + BiliBiliName[count] + "_" + list_second_vedio_list[count][count_j])
        count_j += 1
        counts += 1
    count += 1
    # 重置 count_j
    if count_j == 4:
        count_j = 0
# 校验完整名称列表
print("校验完整名称列表BiliBiliHeadNameAndBiliBiliNameAndlist_second_vedio_list: ")
TraverseListValue.ListNowDirectory(BiliBiliHeadNameAndBiliBiliNameAndlist_second_vedio_list)
# 绘制局部分割线
StringLine.StringODLine()

"""
重命名每集视频的名称: 
[每集绝对路径 + x段视频路径] -> BiliBiliHeadNameAndBiliBiliNameAndlist_second_vedio_list
每集视频所在路径变量: list_first_Directory
总视频名称列表: list_second_vedio
总视频完整名称列表: BiliBiliHeadNameAndBiliBiliNameAndlist_second_vedio_list
备用视频文件名称: backup_list_second_vedio
获取的根目录: getFilePath
"""
# 文件绝路路径名
fileAbsPathName = []
# 检查list_first_Directory数据类型
print("检查list_first_Directory数据类型: ", type(list_first_Directory))
# 初始化计数器
count = 0
count_j = 0
# 转换视频文件列表为二维列表
backup_list_second_vedio = [[backup_list_second_vedio[i + j] for j in range(4)] for i in range(0, len(backup_list_second_vedio), 4)]
print("转换视频文件列表为二维列表backup_list_second_vedio: ")
TraverseListValue.ListNowDirectory(backup_list_second_vedio)
# 检查二维列表嵌套长度
print("检查二维列表嵌套长度backup_list_second_vedio: ", len(backup_list_second_vedio[0]))
# 拼接每集视频完整名称的绝对路径名
for i in list_first_Directory:
    for j in backup_list_second_vedio[count]:
        fileAbsPathName.append(list_first_Directory[count] + "\\" + backup_list_second_vedio[count][count_j])
        count_j += 1
    count += 1
    # 重置内嵌计数器
    if count_j == 4:
        count_j = 0
# 检查拼接视频文件的绝对路径
print("检查拼接视频文件的绝对路径fileAbsPathName: ")
TraverseListValue.ListNowDirectory(fileAbsPathName)
# 绘制局部分割线
StringLine.StringODLine()

# 获取 根目录的上一层目录
UpperLevelDirectory = os.path.dirname(getFilePath)
# 创建目标目录
os.system("mkdir " + UpperLevelDirectory + "\\" + BiliBiliHeadName)
# 获取目标目录位置
TargetDirectory = UpperLevelDirectory + "\\" + BiliBiliHeadName
# 校验目标目录位置
print("校验目标目录位置TargetDirectory: ", TargetDirectory)
# 移动每个视频文件到整理后的目录 move tem1\*.flv tem2
# 初始化计数器
count = 0
# 移动每集分段视频到目标目录
for i in fileAbsPathName:
    # 测试时需要使用copy, 项目写完之后替换为move (这段代码已被废弃)
    # os.system("copy " + fileAbsPathName[count] + " " + TargetDirectory)
    shutil.copy(fileAbsPathName[count], TargetDirectory)
    print(TargetDirectory + "\t" + "总文件数: " + str(len(fileAbsPathName)) + "\t已复制 " + str(count) + " 个文件")
    count += 1
# 检查文件是否移动成功
print("检查文件是否移动成功: ", os.system("dir " + str(TargetDirectory) + " /F"))
print("文件复制完成")
# 绘制局部分割线
StringLine.StringODLine()

"""
这里抛数组越界异常, 是因为在之前的一次运行导致原始目录没有文件
静默删除目录 rmdir /S temp
为了测试 删除目标目录
"""
# 读取目标目录文件列表
TargetDirectoryVedioFileList = os.listdir(TargetDirectory)
# 遍历目标目录文件列表
TraverseListValue.ListNowDirectory(TargetDirectoryVedioFileList)
# 初始化计数器
count = 0
# 重命名目标目录下的所有文件
for i in TargetDirectoryVedioFileList:
    # 这玩意不仅会乱码还不好使
    # os.system("rename " + TargetDirectoryVedioFileList[count] + " " + BiliBiliHeadNameAndBiliBiliNameAndlist_second_vedio_list[count])
    os.rename(TargetDirectory + "\\" + TargetDirectoryVedioFileList[count], TargetDirectory + "\\" + BiliBiliHeadNameAndBiliBiliNameAndlist_second_vedio_list[count])
    count += 1
# 读取已修正后的目标目录文件列表
print("读取已修正后的目标目录文件列表: ")
TargetDirectoryVedioFileList = os.listdir(TargetDirectory)
TraverseListValue.ListNowDirectory(TargetDirectoryVedioFileList)
print("操作完成!")
# 绘制局部分割线
StringLine.StringODLine()

# 是否删除原本的目录
boolDelDirectory = input("是否要删除 原目录 和 cover.jpg ? (回答格式: 是 或 不是): ")
if boolDelDirectory == "是":
    os.system("rmdir /S " + getFilePath + "&&" + "del " + "cover.jpg")
print("删除完成!")
input("按下回车退出程序...")

文件列表

FormatFilePath 为程序入口,这个程序带有注释和print输出语句,参考学习请打开这个文件

miniCodeAndroidBiliBiliFilePathFormat 这是一个精简版,删除了所有的注释和空行以及多余的print输出语句,仅仅使用可以执行这个文件

操作步骤

详情参考当前目录下的 AndroidBiliBiliFilePathFormat.mp4
https://gitee.com/StringOD/AndroidBiliBiliFilePathFormat/blob/master/AndroidBiliBiliFilePathFormat.mp4

Gitee


文章作者: StringOD
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 StringOD !
  目录