-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchappy2
More file actions
executable file
·122 lines (93 loc) · 2.9 KB
/
chappy2
File metadata and controls
executable file
·122 lines (93 loc) · 2.9 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
#! /usr/bin/env python3
import subprocess
from pathlib import Path
import sys
import asyncio
def cleanup():
Path('merge_files.txt').unlink(missing_ok=True)
Path('metadata.txt').unlink(missing_ok=True)
async def media_duration(filename):
result = subprocess.run(
[
'ffprobe',
'-v',
'error',
'-show_entries',
'format=duration',
'-of',
'default=noprint_wrappers=1:nokey=1',
filename,
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
return round(float(result.stdout), 3)
async def ffmpeg_cmd(cmd_str):
process = await asyncio.create_subprocess_shell(cmd_str, stdout=sys.stdout, stderr=sys.stderr)
await process.wait()
return process
async def merge_files(files):
"""Merge all files to $(pwd)_merged.m4b"""
Path('merge_files.txt').unlink(missing_ok=True)
with open('merge_files.txt', 'w') as f:
for file in files:
f.write(f"file '{file}'\n")
parent_dir = files[0].absolute().parent
output_file = parent_dir / f'{parent_dir.name}_merged.m4b'
output_file.unlink(missing_ok=True)
cmd = f'ffmpeg -f concat -safe 0 -i merge_files.txt -c:a aac -b:a 64k "{output_file}"'
await ffmpeg_cmd(cmd)
return output_file
def chapter_str(start_ms, end_ms='', title=''):
output_str = '[CHAPTER]\nTIMEBASE=1/1000\n'
output_str += f'START={start_ms}\nEND={end_ms}\n'
output_str += f'title={title}\n'
return output_str
async def get_chapters_from(files):
start_time = 0
chapters = []
for media_file in files:
dur = await media_duration(media_file)
dur = int(dur * 1000)
print(f'{start_time}:\t{dur}\t{start_time + dur}')
chapters.append(start_time)
start_time += dur
return chapters
async def write_metadata(merged_file_path, chapters):
media_file = Path(f'{merged_file_path.stem}_chapters.m4b')
metadata_file = Path('metadata.txt')
metadata_file.unlink(missing_ok=True)
with open(metadata_file, 'w') as f:
f.write(';FFMETADATA1\n')
for i, chapter in enumerate(chapters):
end = chapters[i + 1] if i + 1 < len(chapters) else ''
f.write(chapter_str(chapter, end, i))
chapters.pop()
cmd = f'ffmpeg -i "{merged_file_path}" -i metadata.txt -map_metadata 1 -codec copy "{media_file}"'
await ffmpeg_cmd(cmd)
return media_file
async def main():
files = sorted(
[
file
for ext in ['*.mp3', '*.m4b']
for file in Path().glob(ext)
if '_merged' not in file.name and '_chapters' not in file.name
]
)
if len(files) < 2:
print(f'Not enough files: {files}')
return
parent_dir = files[0].absolute().parent
merged_path = parent_dir / f'{parent_dir.name}_merged.m4b'
chapters = await get_chapters_from(files)
if merged_path.exists():
merged_file = merged_path
print('merged_file exists, skipping merge_files')
else:
merged_file = await merge_files(files)
media_file = await write_metadata(merged_file, chapters)
print(f'{round(media_file.stat().st_size / 1024 / 1024, 1)} MB\t{media_file}')
cleanup()
if __name__ == '__main__':
asyncio.run(main())