8000 Mkvmerge cutter by nullSoup · Pull Request #216 · ozmartian/vidcutter · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Mkvmerge cutter #216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ _packaging/flatpak/.flatpak-builder
_packaging/flatpak/build
_packaging/flatpak/*repo*
_packaging/flatpak/ozmartian
venv
6 changes: 4 additions & 2 deletions vidcutter/libs/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,14 @@ def binaries(self) -> dict:
'nt': { # Windows
'ffmpeg': ['ffmpeg.exe'],
'ffprobe': ['ffprobe.exe'],
'mediainfo': ['MediaInfo.exe']
'mediainfo': ['MediaInfo.exe'],
'mkvmerge': ['mkvmerge.exe']
},
'posix': { # Linux + macOS
'ffmpeg': ['ffmpeg', 'avconv'],
'ffprobe': ['ffprobe', 'avprobe'],
'mediainfo': ['mediainfo']
'mediainfo': ['mediainfo'],
'mkvmerge': ['mkvmerge']
}
}

Expand Down
67 changes: 58 additions &am 10000 p; 9 deletions vidcutter/libs/videoservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
from functools import partial
from typing import List, Optional, Union

from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QDir, QFileInfo, QObject, QProcess, QProcessEnvironment, QSettings,
QSize, QStandardPaths, QStorageInfo, QTemporaryFile, QTime)
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QDir, QFile, QFileInfo, QObject, QProcess, QProcessEnvironment,
QSettings, QSize, QStandardPaths, QStorageInfo, QTemporaryFile, QTime)
from PyQt5.QtGui import QPainter, QPixmap
from PyQt5.QtWidgets import QMessageBox, QWidget

Expand Down Expand Up @@ -107,11 +107,12 @@ def setMedia(self, source: str) -> None:

@staticmethod
def findBackends(settings: QSettings) -> Munch:
tools = Munch(ffmpeg=None, ffprobe=None, mediainfo=None)
tools = Munch(ffmpeg=None, ffprobe=None, mediainfo=None, mkvmerge=None)
settings.beginGroup('tools')
tools.ffmpeg = settings.value('ffmpeg', None, type=str)
tools.ffprobe = settings.value('ffprobe', None, type=str)
tools.mediainfo = settings.value('mediainfo', None, type=str)
tools.mkvmerge = settings.value('mkvmerge', None, type=str)
for tool in list(tools.keys()):
path = tools[tool]
if path is None or not len(path) or not os.path.isfile(path):
Expand All @@ -134,6 +135,8 @@ def findBackends(settings: QSettings) -> Munch:
raise ToolNotFoundException('FFprobe missing')
if tools.mediainfo is None:
raise ToolNotFoundException('MediaInfo missing')
if tools.mkvmerge is None:
raise ToolNotFoundException('Mkvmerge missing')
return tools

@staticmethod
Expand Down Expand Up @@ -264,8 +267,16 @@ def codecs(self, source: str = None) -> tuple:
else:
args = '-i "{}"'.format(source)
result = self.cmdExec(self.backends.ffmpeg, args, True)
vcodec = re.search(r'Stream.*Video:\s(\w+)', result).group(1)
acodec = re.search(r'Stream.*Audio:\s(\w+)', result).group(1)
match = re.search(r'Stream.*Video:\s(\w+)', result)
if match:
vcodec = match.group(1)
else:
vcodec = None
match = re.search(r'Stream.*Audio:\s(\w+)', result)
if match:
acodec = match.group(1)
else:
acodec = None
return vcodec, acodec

def parseMappings(self, allstreams: bool = True) -> str:
Expand Down Expand Up @@ -403,7 +414,11 @@ def smartcheck(self, code: int, status: QProcess.ExitStatus) -> None:
args.remove('-map')
del args[pos]
self.smartcut_jobs[index].procs[name].setArguments(args)
self.smartcut_jobs[index].procs[name].started.disconnect()
try:
self.smartcut_jobs[index].procs[name].started.disconnect()
except TypeError as e:
self.logger.exception('Failed to disconnect SmartCut job {0}.'.format(name), exc_info=True)

self.smartcut_jobs[index].procs[name].start()
return
else:
Expand Down Expand Up @@ -447,10 +462,37 @@ def smartjoin(self, index: int) -> None:
VideoService.cleanup(joinlist)
self.finished.emit(final_join, self.smartcut_jobs[index].output)

def mkvcut(self, source: str, dest: str, nclips: int,
split_str: str, workdir: str, chapters: List[str]=None) -> None:
clips_file_base, ext = os.path.splitext(os.path.basename(dest))
args = ' '.join(['-o', '"' + os.path.join(workdir, clips_file_base + ext) + '"',
'"' + source + '"', '--split', 'parts:' + split_str])
self.cmdExec(os.path.abspath(self.backends.mkvmerge), args, output=True)
for index in range(0, nclips):
self.progress.emit(index)

if nclips > 1:
merge_string = ['-o', '"' + dest + '"',
'"' + os.path.join(workdir, clips_file_base + '-{0:03d}'.format(1)) + ext + '"']
for i in range(2, nclips + 1):
merge_string.append('--no-global-tags')
merge_string.append('+')
merge_string.append('"' + os.path.join(workdir, clips_file_base + '-{0:03d}'.format(i)) + ext + '"')

if chapters is not None:
merge_string.append('--generate-chapters')
merge_string.append('when-appending')
self.cmdExec(os.path.abspath(self.backends.mkvmerge), ' '.join(merge_string), output=True)
else:
split_file = os.path.join(workdir, clips_file_base) + ext
if QFile.exists(dest):
QFile.remove(dest)
QFile.copy(split_file, dest)

@staticmethod
def cleanup(files: List[str]) -> None:
try:
[os.remove(file) for file in files]
[os.remove(file) for file in files if file]
except FileNotFoundError:
pass

Expand Down Expand Up @@ -577,7 +619,11 @@ def getKeyframes(self, source: str, formatted_time: bool = False) -> list:
keyframe_times.append(timecode[:-3])
else:
keyframe_times.append(float(timecode))
last_keyframe = self.duration().toString('h:mm:ss.zzz')
if formatted_time:
last_keyframe = self.duration().toString('h:mm:ss.zzz')
else:
last_keyframe = self.parent.qtime2delta(self.duration())
self.logger.info('getKeyframes last keyframe: {0}, repr: {0!r}'.format(last_keyframe))
if keyframe_times[-1] != last_keyframe:
keyframe_times.append(last_keyframe)
if source == self.source and not formatted_time:
Expand Down Expand Up @@ -619,6 +665,8 @@ def mpegtsJoin(self, inputs: list, output: str, chapters: Optional[List[str]]=No
video_bsf, audio_bsf = self.getBSF(inputs[0])
# 1. transcode to mpeg transport streams
for file in inputs:
if not file:
continue
name, _ = os.path.splitext(file)
outfile = '{}.ts'.format(name)
outfiles.append(outfile)
Expand Down Expand Up @@ -700,5 +748,6 @@ def getAppPath(path: str = None) -> str:
if VideoService.frozen and getattr(sys, '_MEIPASS', False):
app_path = sys._MEIPASS
else:
app_path = os.path.dirname(os.path.realpath(sys.argv[0]))
app_path = os.path.realpath(os.path.join(os.path.dirname(__file__),'..','..'))

return app_path if path is None else os.path.join(app_path, path)
16 changes: 16 additions & 0 deletions vidcutter/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,19 @@ def __init__(self, parent=None):
mediainfolayout.addWidget(mediainfolabel)
mediainfolayout.addWidget(self.mediainfopath)
mediainfolayout.addWidget(mediainfobutton)
self.mkvmergepath = QLineEdit(QDir.toNativeSeparators(self.parent.service.backends.mkvmerge), self)
self.mkvmergepath.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
mkvmergebutton = QPushButton(self)
mkvmergebutton.setIcon(QIcon(':/images/folder.png'))
mkvmergebutton.setToolTip('Set Mkvmerge path')
mkvmergebutton.setCursor(Qt.PointingHandCursor)
mkvmergebutton.clicked.connect(lambda c, backend='Mkvmerge': self.setPath(backend, self.mkvmergepath))
mkvmergelabel = QLabel('Mkvmerge:', self)
mkvmergelabel.setFixedWidth(65)
mkvmergelayout = QHBoxLayout()
mkvmergelayout.addWidget(mkvmergelabel)
mkvmergelayout.addWidget(self.mkvmergepath)
mkvmergelayout.addWidget(mkvmergebutton)
resetbutton = QPushButton('Reset to defaults', self)
resetbutton.setObjectName('resetpathsbutton')
resetbutton.setToolTip('Reset paths to their defaults')
Expand All @@ -427,6 +440,7 @@ def __init__(self, parent=None):
pathsLayout.addLayout(ffmpeglayout)
pathsLayout.addLayout(ffprobelayout)
pathsLayout.addLayout(mediainfolayout)
pathsLayout.addLayout(mkvmergelayout)
pathsLayout.addLayout(resetlayout)
pathsGroup = QGroupBox('Paths')
pathsGroup.setLayout(pathsLayout)
Expand All @@ -442,11 +456,13 @@ def resetPaths(self) -> None:
self.parent.settings.setValue('ffmpeg', None)
self.parent.settings.setValue('ffprobe', None)
self.parent.settings.setValue('mediainfo', None)
self.parent.settings.setValue('mkvmerge', None)
self.parent.settings.endGroup()
self.parent.service.backends = VideoService.findBackends(self.parent.settings)
self.ffmpegpath.setText(self.parent.service.backends.ffmpeg)
self.ffprobepath.setText(self.parent.service.backends.ffprobe)
self.mediainfopath.setText(self.parent.service.backends.mediainfo)
self.mkvmergepath.setText(self.parent.service.backends.mkvmerge)

def setPath(self, backend: str, field: QLineEdit) -> None:
path = field.text()
Expand Down
36 changes: 31 additions & 5 deletions vidcutter/videocutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1217,15 +1217,15 @@ def addExternalClips(self) -> None:
file4Test = lastItem[3] if len(lastItem[3]) else self.currentMedia
if self.videoService.testJoin(file4Test, file):
self.clipTimes.append([QTime(0, 0), self.videoService.duration(file),
self.captureImage(file, QTime(0, 0, second=2), True), file])
self.captureImage(file, QTime(0, 0, second=2), True), file, None])
filesadded = True
else:
cliperrors.append((file,
(self.videoService.lastError if len(self.videoService.lastError) else '')))
self.videoService.lastError = ''
else:
self.clipTimes.append([QTime(0, 0), self.videoService.duration(file),
self.captureImage(file, QTime(0, 0, second=2), True), file])
self.captureImage(file, QTime(0, 0, second=2), True), file, None])
filesadded = True
if len(cliperrors):
detailedmsg = '''<p>The file(s) listed were found to be incompatible for inclusion to the clip index as
Expand Down Expand Up @@ -1354,6 +1354,31 @@ def saveMedia(self) -> None:
self.toolbar_save.setDisabled(True)
if not os.path.isdir(self.workFolder):
os.mkdir(self.workFolder)
if source_ext.lower() == '.mkv':
self.seekSlider.showProgress(2)
self.parent.lock_gui(True)
split_str = ''
for clip in self.clipTimes:
start_time = timedelta(hours=clip[0].hour(), minutes=clip[0].minute(), seconds=clip[0].second(),
milliseconds=clip[0].msec())
stop_time = timedelta(hours=clip[1].hour(), minutes=clip[1].minute(), seconds=clip[1].second(),
milliseconds=clip[1].msec())
split_str += self.delta2String(start_time) + 's-' + self.delta2String(stop_time) + 's,'
split_str = split_str.rstrip(',')
chapters = None
if self.createChapters:
chapters = []
[
chapters.append(clip[4] if clip[4] is not None else 'Chapter {}'.format(index + 1))
for index, clip in enumerate(self.clipTimes)
]
workdir = os.path.dirname(os.path.abspath(self.finalFilename)) if self.keepClips else os.path.abspath(self.workFolder)
self.videoService.mkvcut(source=os.path.abspath(source_file + source_ext),
dest=os.path.abspath(self.finalFilename), nclips=len(self.clipTimes),
split_str=split_str, workdir=workdir,
chapters=chapters)
self.complete(rename=False, finalize=False)
return
if self.smartcut:
self.seekSlider.showProgress(6 if clips > 1 else 5)
self.parent.lock_gui(True)
Expand All @@ -1372,7 +1397,7 @@ def saveMedia(self) -> None:
duration = self.delta2QTime(clip[0].msecsTo(clip[1])).toString(self.timeformat)
filename = '{0}_{1}{2}'.format(file, '{0:0>2}'.format(index), source_ext)
if not self.keepClips:
filename = os.path.join(self.workFolder, os.path.basename(filename))
filename = os.path.join(os.path.abspath(self.workFolder), os.path.basename(filename))
filename = QDir.toNativeSeparators(filename)
filelist.append(filename)
if not self.videoService.cut(source='{0}{1}'.format(source_file, source_ext),
Expand Down Expand Up @@ -1449,13 +1474,14 @@ def joinMedia(self, filelist: list) -> None:
else:
self.complete(True, filelist[-1])

def complete(self, rename: bool=True, filename: str=None) -> None:
def complete(self, rename: bool=True, filename: str=None, finalize: bool=True) -> None:
if rename and filename is not None:
# noinspection PyCallByClass
QFile.remove(self.finalFilename)
# noinspection PyCallByClass
QFile.rename(filename, self.finalFilename)
self.videoService.finalize(self.finalFilename)
if finalize:
self.videoService.finalize(self.finalFilename)
self.seekSlider.updateProgress()
self.toolbar_save.setEnabled(True)
self.parent.lock_gui(False)
Expand Down
0