def replaceSubFileWith(input_file: Path, sub_file: Path, output_folder: Path): output_tmp1 = output_folder.joinpath(input_file.append_stem('_fxd_sub_tmp1').name) cmd = [ 'ffmpeg', '-y', '-i', F'{input_file}', '-map_metadata', '0', '-sn', '-c', 'copy', F'{output_tmp1}' ] common.run_process(cmd, silent=True) output_tmp = output_folder.joinpath(input_file.append_stem('_fxd_sub').name) cmd = [ 'ffmpeg', '-y', '-i', F'{output_tmp1}', '-i', F'{sub_file}', '-map_metadata', '0', '-c', 'copy', F'{output_tmp}' ] common.run_process(cmd, silent=True) output_tmp1.unlink() return output_tmp
class PyMergeMKVLinks(): def __init__(self, args): self.args = args if self.args.re_encode: self.args.via_ffmpeg = True self.sourceFolder: Path = self.args.sourceDir[0] self.outputFolder: Path = self.args.destDir[0] self.outputFolder.mkdir(exist_ok=True) self.sourceFiles = self.generateFileList() if not len(self.sourceFiles): print(F"Found no files with segments in: {self.sourceFolder}") exit(1) self.tmpDir = Path("_unlink_temp/") self.tmpDir.mkdir(exist_ok=True) self.processFiles() def generateFileList(self): segs = {} for f in self.sourceFolder.listfiles(): js = mkvstuff.mkvJson(f) if "properties" in js["container"]: segs[js["container"]["properties"]["segment_uid"]] = f return segs def processFiles(self): for i, (segmentUid, sourceFile) in enumerate(self.sourceFiles.items()): sourceFile: Path tmpOutDir = self.tmpDir.joinpath(str(i)) if segments := mkvstuff.getChapterDict(sourceFile): if not len([x for x in segments.values() if x["segment_uid"]]): self.plainCopy(sourceFile, self.outputFolder.joinpath(sourceFile.name)) continue tmpOutDir.mkdir(parents=True, exist_ok=True) if segmentList := self.buildSegmentList( sourceFile, segments, tmpOutDir): new_chapter = None if self.args.chapters: new_chapter = tmpOutDir.joinpath("new_chapter.xml") with new_chapter.open('w', encoding='utf-8') as f: f.write( mkvstuff.segmentListToChapterFile(segmentList)) self.buildMkvFromSegments(segmentList, self.outputFolder.joinpath( sourceFile.name), self.tmpDir.joinpath(str(i)), chapter=new_chapter)
def buildSegmentList(self, input_file: Path, segmentList: dict, outputDirectory: Path = None, fullOutputDirectory: Path = None): if not outputDirectory and not fullOutputDirectory: raise Exception( "mergeSegmentsIntoFile requires either outputDirectory or fullOutputDirectory to be set" ) split_times = [] for i in sorted(segmentList.keys()): seg = segmentList[i] if seg["segment_uid"] is None and ( segmentList[i + 1]["segment_uid"] if (i + 1) < len(segmentList) else True): if "time_end" not in seg: if i + 1 in segmentList: split_times.append( (i, segmentList[i + 1]["time_start"])) elif i - 1 in segmentList and 'time_end' in segmentList[i - 1]: split_times.append((i, segmentList[i - 1]["time_end"])) else: split_times.append((i, seg["time_end"])) else: if seg["segment_uid"] in self.sourceFiles: if self.args.re_encode: print( F'Re-encoding {self.sourceFiles[seg["segment_uid"]]}' ) segmentList[i]["file_path"] = mkvstuff.reEncodeFile( self.sourceFiles[seg["segment_uid"]], outputDirectory) else: segmentList[i]["file_path"] = self.sourceFiles[ seg["segment_uid"]] output_file: Path = None if fullOutputDirectory: output_file = fullOutputDirectory else: output_file = outputDirectory.joinpath( input_file.change_stem("parts").name) split_files = mkvstuff.splitFilesByTimeCodes( input_file, split_times, output_file, viaFfmpeg=self.args.via_ffmpeg, reEncode=self.args.re_encode) print(F"Files are: {','.join(str(x) for x in split_files)}") for i, seg in segmentList.items(): if i in split_files: segmentList[i]["file_path"] = split_files[i] return segmentList
def extract_chapter(input_file: Path, output_folder: Path = Path(".")) -> Path: cmd = ['mkvextract.exe'] cmd.append(str(input_file)) cmd.append('chapters') full_output_path = output_folder.joinpath(input_file.with_suffix(".xml").name) cmd.append(str(full_output_path)) common.run_process(cmd, silent=True) return full_output_path
def extract_first_subtitle(input_file: Path, output_folder: Path = None): output = input_file.change_suffix(".ass") if output_folder: output = output_folder.joinpath(input_file.change_suffix(".ass").name) cmd = [ 'ffmpeg', '-y', '-i', F'{input_file}', '-vn', '-an', F'{output}' ] common.run_process(cmd, silent=True) return output
def buildMkvFromSegments(self, segmentList, output_file: Path, tmpDir: Path, chapter=None): concat_file = Path("concat.txt") font_dir = tmpDir.joinpath("fonts") font_dir.mkdir(exist_ok=True) style_list = {} for i in sorted(segmentList.keys()): seg = segmentList[i] if 'file_path' not in seg: continue mkvstuff.ext_all_fonts_to_dir(seg['file_path'], font_dir) sub_file = mkvstuff.extract_first_subtitle(seg['file_path'], tmpDir) sub_file = mkvstuff.suffixStyleNaming( sub_file, F"partid_{i}") # silly double up for styleStr in mkvstuff.getStylesFromAssFile(sub_file): styleDict = mkvstuff.style_to_dict(styleStr) style_list[styleDict["name"]] = styleDict sub_file.unlink() for i in sorted(segmentList.keys()): seg = segmentList[i] if 'file_path' not in seg: continue _fixed_sub = mkvstuff.extract_first_subtitle( seg["file_path"], tmpDir) _fixed_sub = mkvstuff.suffixStyleNaming( _fixed_sub, F"partid_{i}" ) # silly double up, but too lazy to save in memory _fixed_sub = mkvstuff.replaceAssStylesWithList( _fixed_sub, style_list) _fixed_sub_mkv = mkvstuff.replaceSubFileWith( seg["file_path"], _fixed_sub, tmpDir) segmentList[i]["file_path"] = _fixed_sub_mkv with concat_file.open("w", encoding="utf-8") as f: for i in sorted(segmentList.keys()): seg = segmentList[i] if 'file_path' not in seg: continue f.write(F"file '{seg['file_path']}'\n") _fixed_sub.unlink() output_file_tmp = output_file.append_stem('_tmp') cmd = [ 'ffmpeg', '-y', '-f', 'concat', '-safe', '0', '-i', F'{concat_file}', ] if self.args.re_encode: cmd.append('-map') cmd.append('0') cmd.append('-g') cmd.append('1') cmd.append('-pix_fmt') cmd.append('yuv420p') cmd.append('-c:v') cmd.append('h264_nvenc') cmd.append('-c:a') cmd.append('pcm_s16le') cmd.append('-b:v') cmd.append('2M') cmd.append('-s') cmd.append('1280x720') else: cmd.append('-c') cmd.append('copy') cmd.append(F'{output_file_tmp}') fonts_list = mkvstuff.build_font_list(font_dir) print( F"Merging into{' (with re-encode)' if self.args.re_encode else ''}: {output_file_tmp}" ) common.run_process(cmd, silent=True) concat_file.unlink() output_file = output_file.parent.joinpath( common.strip_crc(output_file.stem) + output_file.suffix) print(F"Merging (with chapter & fonts) into: {output_file}") cmd = [ "mkvmerge", "--ui-language", "en", "--output", F"{output_file}", "(", F"{output_file_tmp}", ")", ] if chapter: cmd.extend([ "--chapter-language", "eng", "--chapters", F"{chapter}", ]) for font in fonts_list: cmd.extend([ "--attachment-name", F"{font.name}", "--attachment-mime-type", F"{font.mime}", "--attach-file", F"{font.resolve()}" ]) common.run_process(cmd, silent=True) output_file_tmp.unlink() print("\rCalculating and appending CRC-Sum...", end='') csum = common.crc32f(output_file) output_file.move(output_file.append_stem(F' [{csum}]')) print(F"\rCalculating and appending CRC-Sum, OK: {csum}")