diff --git a/README.md b/README.md index 20d40b0..77477b4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # VideoCut -Version 2.1.0 +Version 2.1.1 -![Download](https://github.com/kanehekili/VideoCut/releases/download/2.1.0/videocut2.1.0.tar) +![Download](https://github.com/kanehekili/VideoCut/releases/download/2.1.1/videocut2.1.1.tar) MP2/MP4 Cutter for Linux on base of mpv and ffmpeg. Cutting is lossless, the target file will not be reencoded. @@ -17,6 +17,7 @@ The current version is written in python3 and uses the qt5 widget kit. ### Prerequisites * Arch: python3, python-pillow and mpv * Debian/Mint/Ubuntu: python3 python3-pil libmpv1 python3-pyqt5.qtopengl (no-recommends) + #not working for Ubuntu 18.4: libmpv - use python3-opencv instead * Fedora: python3-pillow-qt and mpv-libs.x86_64 * ffmpeg > 3.X to 5.X * python3-pyqt5 @@ -73,7 +74,7 @@ MPV supports audio streams while playing. Unfortunately it relies on the audio s ##Install -#### Install via ppa on Linux Mint or Ubuntu (focal/Mint20 only) +#### Install via ppa on Linux Mint or Ubuntu (focal/jammy/Mint20 only) ``` sudo add-apt-repository ppa:jentiger-moratai/mediatools sudo apt update @@ -97,10 +98,11 @@ Remove with: Select video and open it with "Open with ->VideoCut", oder via terminal "VideoCut" -#### Install dependencies manually on Linux Mint or Ubuntu (tested from 16.04 to 22.04) +#### Install dependencies manually on Linux Mint or Ubuntu (tested from 20.04 to 22.04) ``` sudo apt –no-install-recommends install python3-pyqt5 ffmpeg python3-pil libmpv1 ``` +libmpv1 won't work on Ubuntu 18.04 - no bindings for the old libs - use opencv instead #### Install dependencies on Fedora ``` @@ -136,12 +138,14 @@ remux5 is a c binary based on the libavcodec library, but uses an integrated app ### Exact frame cut for one GOP only? Can't be really implemented with the ffmpeg ABI. The transcoded part will have different coding parameters than the rest of the stream. A decoder cannot handle that change. On the other hand there is no way to transcode the GOP with the exact parameters of the original stream, since only a subset of h264 paramenters are accepted by the ffmpeg ABI. -### Legacy Opencv +### Legacy Opencv (for Ubuntu 18.04 and older) Since Videocut ran with OpenCV for many years it is still available. If needed it has to be downloaded * python3-opencv * hdf5 (Arch only) -Create a .desktop file with the line "Exec= python3 .../VideoCut.py -p cv %f". Opencv will not be displaying subtitles nor frametypes. +Copy the .desktop file and change the exec line to "Exec= python3 .../VideoCut.py -p cv %f" + +Opencv will not be displaying subtitles nor frametypes. ### Changes 08.07.2016 @@ -251,4 +255,8 @@ Create a .desktop file with the line "Exec= python3 .../VideoCut.py -p cv %f". O 05.04.2022 * Support for wayland (OpenGL Widget) -* Tune MPV settings for older mpv versions \ No newline at end of file +* Tune MPV settings for older mpv versions + +04.05.2022 +* Fixed ffmpeg3 build for older distros +* revamped logging \ No newline at end of file diff --git a/build/build.properties b/build/build.properties index 100baa1..9f60a2c 100644 --- a/build/build.properties +++ b/build/build.properties @@ -1,4 +1,5 @@ -version=2.1.0 +version=2.1.1 +ubu0=bionic ubu1=focal ubu2=jammy pkgrelease=1 \ No newline at end of file diff --git a/build/build.xml b/build/build.xml index 89a7218..b807a6b 100644 --- a/build/build.xml +++ b/build/build.xml @@ -10,8 +10,10 @@ + - + + @@ -26,6 +28,7 @@ + @@ -64,6 +67,7 @@ + diff --git a/src/.gitignore b/src/.gitignore index 581d446..4b777a7 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -2,3 +2,4 @@ /VideoCut.py.old /Configuration.py /__pycache__/ +/VideoCut.log diff --git a/src/CvPlayer.py b/src/CvPlayer.py index 8a91de7..1533465 100644 --- a/src/CvPlayer.py +++ b/src/CvPlayer.py @@ -33,7 +33,7 @@ Compat layer for cv2 and 3 ''' -Log=FFMPEGTools.Log +Log = FFMPEGTools.Log class OpenCV2(): @@ -231,7 +231,7 @@ def _captureFromFile(self, rotation): return False self._capture = OPENCV.getCapture(); if not self._capture.open(self._file): - Log.logError("STREAM NOT OPENED") + Log.error("STREAM NOT OPENED") return False self.frameWidth = OPENCV.getFrameWidth() @@ -243,10 +243,10 @@ def _captureFromFile(self, rotation): duration = self._streamProbe.formatInfo.getDuration() ff_fps= self._streamProbe.getVideoStream().frameRateMultiple() ff_FrameCount = round(ff_fps*duration) - Log.logInfo("Analyze %s frameCount:%d fps:%.3f ffmpeg frameCount:%d fps:%.3f"%(self._file,self.framecount,self.fps,ff_FrameCount,ff_fps)) + Log.info("Analyze %s frameCount:%d fps:%.3f ffmpeg frameCount:%d fps:%.3f"%(self._file,self.framecount,self.fps,ff_FrameCount,ff_fps)) fps_check= (self.fps/ff_fps) if abs(fps_check -1.0)>10.0: - Log.logInfo("Irregular data, ratio: %.3f"%(fps_check)) + Log.info("Irregular data, ratio: %.3f"%(fps_check)) self.framecount=ff_FrameCount self.fps=ff_fps @@ -264,12 +264,12 @@ def _captureFromFile(self, rotation): def _sanityCheck(self): if self._streamProbe is None or not self._streamProbe.isKnownVideoFormat(): print ("STREAM NOT KNOWN") - Log.logInfo("STREAM NOT KNOWN") + Log.info("STREAM NOT KNOWN") return False if len(self._streamProbe.video)!=1: print("Zero or more than 1 video stream") - Log.logInfo("Zero or more than 1 video stream") + Log.info("Zero or more than 1 video stream") return False return True @@ -312,7 +312,7 @@ def getNextFrame(self): return frame self.framecount = self.getCurrentFrameNumber() - Log.logInfo("No more frames @" + str(self.framecount + 1)); + Log.info("No more frames @" + str(self.framecount + 1)); return self.__getLastFrame(self.framecount, 0) # A test to paint on a frame. Has artefacts.. @@ -354,7 +354,7 @@ def getFrameAt(self, frameNumber): self.setFrameAt(frameNumber) return self.getNextFrame() except: - Log.logException("Error Frame") + Log.exception("Error Frame") return None def getCurrentFrameNumber(self): diff --git a/src/FFMPEGTools.py b/src/FFMPEGTools.py index 87ade8c..5d82824 100644 --- a/src/FFMPEGTools.py +++ b/src/FFMPEGTools.py @@ -16,41 +16,50 @@ from itertools import tee import configparser from shutil import which +import gzip + + +def setupRotatingLogger(logName,logConsole): + logSize=5*1024*1024 #5MB + #if OSTools().isRoot(): won't work if you call it from commandline + # folder=OSTools().joinPath("/var","log") + # OSTools().ensureDirectory(folder) + #else: + folder = OSTools().moduleDir() + if not OSTools().canWriteToFolder(folder): + folder= OSTools().joinPathes(OSTools().getHomeDirectory(),".config",logName) + OSTools().ensureDirectory(folder) + logPath = OSTools().joinPathes(folder,logName+".log") + fh= RotatingFileHandler(logPath,maxBytes=logSize,backupCount=5) + fh.rotator=OSTools().compressor + fh.namer=OSTools().namer + logHandlers=[] + logHandlers.append(fh) + if logConsole: + logHandlers.append(logging.StreamHandler(sys.stdout)) + logging.basicConfig( + handlers=logHandlers, + #level=logging.INFO + level=logging.DEBUG, + format='%(asctime)s %(levelname)s : %(message)s' + ) + #console - only if needed + ''' + if logConsole: + cons = logging.StreamHandler(sys.stdout) + logger.addHandler(cons) + ''' +def setLogLevel(levelString): + if levelString == "Debug": + Log.setLevel(logging.DEBUG) + elif levelString == "Info": + Log.setLevel(logging.INFO) + elif levelString == "Warning": + Log.setLevel(logging.WARNING) + elif levelString == "Error": + Log.setLevel(logging.ERROR) -class Logger(): - def __init__(self): - homeDir = os.path.expanduser("~") - self.LogDir =OSTools().joinPathes(homeDir,".config","VideoCut") - OSTools().ensureDirectory(self.LogDir, None) - self.mainHandler=None - self.setupLogging() - - - def setupLogging(self): - logpath = os.path.join(self.LogDir,"VC.log") - self.mainHandler = RotatingFileHandler(logpath, mode='a', maxBytes=10000000, backupCount=2) - handler = logging.StreamHandler(sys.stdout) - handler.setLevel(logging.DEBUG) - logging.basicConfig( - handlers=[handler,self.mainHandler], - level=logging.DEBUG, - format='%(asctime)s %(message)s' - ) - self.logger = logging.getLogger(__name__) - - def logInfo(self, aString): - self.logger.log(logging.INFO, aString) - - def logError(self, aString): - self.logger.log(logging.ERROR, aString) - - def logClose(self): - logging.shutdown() - - def logException(self, text): - self.logger.exception(text) - class OSTools(): __instance=None @@ -89,6 +98,9 @@ def getWorkingDirectory(self): dname = os.path.dirname(abspath) return dname + def moduleDir(self): + return os.path.dirname(__file__) + #location of "cwd", i.e where is bash.. def getActiveDirectory(self): return os.getcwd() @@ -97,6 +109,7 @@ def getActiveDirectory(self): def isAbsolute(self,path): return os.path.isabs(path) + #The users home directory - not where the code lies def getHomeDirectory(self): return os.path.expanduser("~") @@ -113,7 +126,13 @@ def removeFile(self, path): if self.fileExists(path): os.remove(path) - def ensureDirectory(self, path, tail): + def canWriteToFolder(self,path): + return os.access(path,os.W_OK) + + def canReadFromFolder(self,path): + return os.access(path,os.R_OK) + + def ensureDirectory(self, path, tail=None): # make sure the target dir is present if tail is not None: path = os.path.join(path, tail) @@ -144,13 +163,36 @@ def __pairwise(self,iterable): next(b, None) return list(zip(a, b)) + def isRoot(self): + return os.geteuid()==0 + + def countFiles(self,aPath,searchString): + log_dir=os.path.dirname(aPath) + cnt=0 + for f in os.listdir(log_dir): + if searchString is None or searchString in f: + cnt+=1 + return cnt + + #logging rotation & compression + def compressor(self,source, dest): + with open(source,'rb') as srcFile: + data=srcFile.read() + bindata = bytearray(data) + with gzip.open(dest,'wb') as gz: + gz.write(bindata) + os.remove(source) + + def namer(self,name): + return name+".gz" class ConfigAccessor(): - __SECTION = "videocut" + __SECTION = "default" homeDir = OSTools().getHomeDirectory() - def __init__(self, filePath): - self._path = OSTools().joinPathes(self.homeDir,".config","VideoCut",filePath) + def __init__(self, folder,filePath,section="default"): + self.__SECTION=section + self._path = OSTools().joinPathes(self.homeDir,".config",folder,filePath) self.parser = configparser.ConfigParser() self.parser.add_section(self.__SECTION) @@ -191,8 +233,7 @@ def store(self): BIN = "ffmpeg" -Log = Logger() - +Log=logging.getLogger("Main") def parseCVInfos(cvtext): lines = cvtext.splitlines(False) @@ -240,13 +281,13 @@ def timedeltaToString2(deltaTime): so = str(s).rjust(2, '0') mso = str(ms).rjust(3, '0') return '%s.%s' % (so, mso) - +''' def log(*messages): #text value only... # Hook for logger... # cnt = len(messages) #print("{0} {1}".format(*messages)) Log.logInfo("{0} {1}".format(*messages)) - ''' + cnt = len(messages) r= range(cnt) tmp=[] @@ -256,8 +297,8 @@ def log(*messages): #text value only... tmp.append('}') fmt=''.join(tmp) Log.logInfo(fmt.format(*messages)) - ''' - + +''' # execs an command, yielding the lines to caller. Throws exception on error def executeAsync(cmd, commander): @@ -351,7 +392,7 @@ def _readData(self, seekTo, count): if count is not None: cmd = cmd + ["-read_intervals", seekTo + "%+#" + str(count)] cmd.extend(("-show_packets", "-select_streams", "v:0", "-show_entries", "packet=pts,pts_time,dts,dts_time,flags", "-of", "csv" , self.path, "-v", "quiet")) - log("FFPacket:", cmd) + Log.debug("FFPacket:%s", cmd) result = Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() if len(result[0]) == 0: raise IOError('No such media file ' + self.path) @@ -658,6 +699,43 @@ def _readData(self): def sanityCheck(self): if self.getVideoStream() is None: raise IOError("No video stream available") + if logging.root.level!=logging.DEBUG: + return + Log.debug("-------- Video -------------") + s = self.getVideoStream() + Log.debug("Index: %d", s.getStreamIndex()) + Log.debug("codec %s", s.getCodec()) + Log.debug("getCodecTimeBase: %s", s.getCodecTimeBase()) + Log.debug("getTimeBase: %s", s.getTimeBase()) + Log.debug("getAspect %s", s.getAspectRatio()) + Log.debug("getFrameRate_r: %.3f", s.frameRateMultiple()) + Log.debug("getAVGFrameRate: %3.f", s.frameRateAvg()) # Common denominator + Log.debug("getDuration: %.3f", s.duration()) + Log.debug("getWidth: %s", s.getWidth()) + Log.debug("getHeight: %s", s.getHeight()) + Log.debug("isAudio: %r", s.isAudio()) + Log.debug("isVideo: %r", s.isVideo()) + + Log.debug("-------- Audio -------------") + s = self.getAudioStream() + if not s: + Log.debug("No audio") + exit(0) + Log.debug("Index:%d", s.getStreamIndex()) + Log.debug("getCodec:%s", s.getCodec()) + Log.debug("bitrate(kb) %d", s.getBitRate()) + Log.debug("getCodecTimeBase: %s", s.getCodecTimeBase()) + Log.debug("getTimeBase: %s", s.getTimeBase()) + Log.debug("getDuration: %.3f", s.duration()) + Log.debug("isAudio: %r", s.isAudio()) + Log.debug("isVideo: %r", s.isVideo()) + Log.debug("-----------Formats-----------") + f=self.formatInfo + Log.debug("Fmt Names:%s",f.formatNames()) + Log.debug("Fmt bitrate;%d",f.getBitRate()) + Log.debug("Fmt dur: %.3f",f.getDuration()) + Log.debug("Fmt size %.3f",f.getSizeKB()) + Log.debug("-----------EOF---------------") def getVideoStream(self): if len(self.video) == 0: @@ -712,21 +790,19 @@ def getAspectRatio(self): This filter is required for copying an AAC stream from a raw ADTS AAC or an MPEG-TS container to MP4A-LATM. ''' - def needsAudioADTSFilter(self): if self.getAudioStream() is None: return False - return self.getAudioStream().getCodec() == "aac" and (self.isH264() or self.isMP4()) + return self.getAudioStream().getCodec() == "aac" and (self.isH264() or self.isMP4Container()) ''' check if needs the h264_mp4toannexb filter- - Used on mp4 or h264, for converting INTO transport streams + Use on: MP4 file(container) containing an H.264 stream to mpegts format ''' - def needsH264Filter(self): if self.isTransportStream(): return False; - return self.isH264() + return self.isH264Codec() #also works work mkv # VideoFormat is format info.... def getFormatNames(self): @@ -781,16 +857,16 @@ def isTransportStream(self): is MP4? Since its a formatcheck it can't be mp4-TS ''' - def isMP4(self): + def isMP4Container(self): return self.hasFormat("mp4") - def isMPEG2(self): + def isMPEG2Codec(self): return "mpeg" in self.getVideoStream().getCodec() - def isH264(self): + def isH264Codec(self): return "h264" == self.getVideoStream().getCodec() - def isVC1(self): + def isVC1Codec(self): return "vc1" == self.getVideoStream().getCodec() @@ -883,10 +959,11 @@ def __init__(self, dataArray): def _parse(self, dataArray): for entry in dataArray: + val=self.NA try: (key, val) = entry.strip().split('=') except: - log("Error in entry:", entry) + Log.error("Error in entry:%s", entry) if self.NA != val: if self.TAG in key: key = key.split(':')[1] @@ -948,7 +1025,7 @@ def _parse(self, dataArray): try: (key, val) = entry.strip().split('=') except: - log("Error in entry:", entry) + Log.error("Error in entry:%s", entry) if self.NA != val: if self.TAG in key: @@ -970,6 +1047,9 @@ def getAspectRatio(self): return 1.0 def getRotation(self): + result = self.dataDict.get('rotation',None) + if result: + return int(result) result = self.dataDict.get('TAG:rotate',None) if result: return int(result) @@ -1109,7 +1189,6 @@ def _readDataByLines(self): break if re.match('\[\/FRAME\]', line): proc += 1 - log("p ", proc) # dataBucket = self.__processLine(line,dataBucket) # if len(dataBucket)==0: @@ -1272,13 +1351,13 @@ def cutPart(self, startTimedelta, endTimedelta, index=0, nbrOfFragments=1): durString = timedeltaToFFMPEGString(timedelta(seconds=deltaSeconds, microseconds=deltaMillis)) # fast search - which is key search - log('Prefetch seek/dur: ', prefetchString, ">>", durString) + Log.info('Prefetch seek/dur:%s >> %s ', prefetchString, durString) if nbrOfFragments == 1: fragment = self.targetPath() else: ext = self.retrieveConcatExtension() fragment = self._getTempPath() + str(index) + ext - log("generate file:", fragment) + Log.debug("generate file:%s", fragment) self.say("Cutting part:" + str(index)) cmdExt = self._videoMode() @@ -1293,7 +1372,7 @@ def cutPart(self, startTimedelta, endTimedelta, index=0, nbrOfFragments=1): cmd.extend(self.langMappings) cmdExt.extend(["-avoid_negative_ts", "1", "-shortest", fragment]) cmd.extend(cmdExt) - log("cut:", cmd) + Log.debug("cut:%s", cmd) prefix = "Cut part " + str(index) + ":" try: for path in executeAsync(cmd, self): @@ -1325,7 +1404,7 @@ def _audioMode(self, mode): if self._config.streamData.getAudioStream() is None: return [] targetFmt = FORMATS.fromFilename(self.targetPath()) - log("audio:", self._config.streamData.getAudioStream().getCodec()," targets:",targetFmt) #TODO logging + Log.debug("audio: %s targets:%s", self._config.streamData.getAudioStream().getCodec(),targetFmt) #TODO logging container=targetFmt.format lib = "copy" if self._config.reencode: @@ -1335,7 +1414,7 @@ def _audioMode(self, mode): if self._config.streamData.needsAudioADTSFilter(): return ["-c:a", lib, "-bsf:a", "aac_adtstoasc"] - if self._config.streamData.isMPEG2() and container=='mpegts' and (self._fragmentCount == 1 or mode == self.MODE_JOIN): + if self._config.streamData.isMPEG2Codec() and container=='mpegts' and (self._fragmentCount == 1 or mode == self.MODE_JOIN): return ["-c:a", lib, "-f", "dvd"] # This must be configurable - at least for tests. mp2 seems to work for kodi+mp4 @@ -1354,7 +1433,7 @@ def _videoMode(self): srcContainer=srcFmt.format - log("video:", videoStream.getCodec()) + Log.debug("video %s:", videoStream.getCodec()) lib="copy" bsf=None codec=None @@ -1420,10 +1499,10 @@ def _subtitleMode(self): subSrc= self._config.streamData.subtitleCodec() subTarget= targetFmt.subtitleFormat() if not FORMATS.sameSubGroup(subSrc,subTarget): #TODO ? Only if we can't copy them. - log("subtitle:","src and target are not both text or image") + Log.warning("subtitle: src and target are not same text or image") return [] if subTarget is None: - log("subtitle:","container does not support subtitles") + Log.warning("subtitle: container does not support subtitles") return[] scopy=subTarget @@ -1511,7 +1590,7 @@ def join(self): if len(self.langMappings) > 0: cmd.extend(["-map", "0"]) cmd.append(self.targetPath()) - log("join:", cmd) + Log.info("join:%s", cmd) prefix = "Join:" try: for path in executeAsync(cmd, self): @@ -1546,7 +1625,7 @@ def parseAndDispatch(self, prefix, text): if len(text) > 5: print ("<" + text.rstrip()) if "failed" in text: - print ("ERR:", text) + Log.error("FFmpeg error: %s", text) self.say(prefix + " !Conversion failed!") self.warn(text); return False @@ -1605,7 +1684,7 @@ def stopCurrentProcess(self): # Stop button has been pressed.... self.killed = True if self.runningProcess is None: - self.log("Can't kill proc!", "-Error") + Log.warning("Can't kill proc! -Error") self.warn("Can't kill proc!", "-Error") else: print("FFMPEGCutter - stop process") @@ -1681,7 +1760,7 @@ def cut(self, cutlist): cmd = cmd + [self.config.targetPath] print(cmd) - log("cut file:", cmd) + Log.debug("cut file:%s", cmd) try: start = time.monotonic() for path in executeAsync(cmd, self): @@ -1694,7 +1773,7 @@ def cut(self, cutlist): except Exception as error: self.warn("Remux failed: %s" % (error)) - log("Remux failed", error) + Log.error("Remux failed %s", str(error)) self.runningProcess = None return False @@ -1742,7 +1821,7 @@ def parseAndDispatch(self, prefix, text, showProgress): aLine= text.rstrip() print ("<" + aLine ) if "Err:" in aLine: - log(">", aLine) + Log.debug(">%s", aLine) if not "muxing" in aLine: self.warn(aLine); return False @@ -1756,7 +1835,7 @@ def stopCurrentProcess(self): # Stop button has been pressed.... self.killed = True if self.runningProcess is None: - self.log("Can't kill proc!", "-Error") + Log.error("Can't kill proc! -Error") else: print("VCCutter - stop process") self.runningProcess.kill() @@ -1793,7 +1872,7 @@ def figureItOut(self): g1 = m.group(0) print(g1) self.version = float(g1) - log("FFmepg Version:", float(g1)) + Log.info("FFmepg Version:%.3f", float(g1)) class FFmpegPicture(): diff --git a/src/MpvPlayer.py b/src/MpvPlayer.py index eabcc27..ee57acb 100644 --- a/src/MpvPlayer.py +++ b/src/MpvPlayer.py @@ -230,7 +230,7 @@ def _hookEvents(self): if nbr>30: self.mediaPlayer.demuxer_max_back_bytes='10000MiB' self.mediaPlayer.demuxer_cache_wait='no' - Log.logInfo("applied demuxer settings for mpv > 3.x") + Log.info("applied demuxer settings for mpv > 3.x") observe=[]#"seeking","time-pos"... #ignore=["mouse-pos",""] @@ -278,7 +278,7 @@ def open(self,filePath): self.mediaPlayer.loadfile(filePath) self._getReady() except Exception as ex: - Log.logException("Open mpv file") + Log.exception("Open mpv file") print(ex) #Test, not activated @@ -312,7 +312,7 @@ def calcPosition(self,frameNumber): #performance tweak: fastseek should have a low demuxer seek offset: tune if fast seek. def seek(self,frameNumber,fast=False): if self.mediaPlayer.seeking is None: - Log.logError("No seek! Aborting") + Log.error("No seek! Aborting") return step = frameNumber - self.getCurrentFrameNumber() if abs(step) < 20: #mpv hack: mpegts small distances @@ -360,7 +360,7 @@ def seekStep(self,dialStep): self._waitSeekDone() #print("seekStep2 %f dial: %d currTime:%f"%(nxt,dialStep,self.timePos())) else: - Log.logInfo("MPV: Seek none!") + Log.info("MPV: Seek none!") def screenshotAtFrame(self,frameNumber): secs = self.calcPosition(frameNumber) @@ -382,7 +382,7 @@ def _onPropertyChange(self,name,pos): def _onDuration(self,name,val): if val is not None: self.duration=val - Log.logInfo("durance detected:%.3f"%(val)) + Log.info("durance detected:%.3f"%(val)) def _onFrameInfo(self,name,val): if val is not None: @@ -398,7 +398,7 @@ def setFPS(self,newFPS): ''' def _onFps(self,name,val): if val is not None: - Log.logInfo("fps detected: %.5f"%(val)) + Log.info("fps detected: %.5f"%(val)) self.mediaPlayer.unobserve_property("estimated-vf-fps",self._onFps) self.setFPS(val) ''' @@ -475,7 +475,7 @@ def _onReadyWait(self,name,val): def _passLog(self,loglevel, component, message): msg='{}: {}'.format(component, message) - Log.logError(">"+msg) + Log.error(">%s",msg) with self.seekLock: #TODO: file not recognized seems not be working" if message in self.ERR_IDS: @@ -494,21 +494,21 @@ def connectTo(self,func): #tweak for transport streams def tweakTansportStreamSettings(self,isInterlaced): - Log.logInfo("Transport stream. Setting seek offset to high and interlacing: %d"%(isInterlaced)) + Log.info("Transport stream. Setting seek offset to high and interlacing: %d"%(isInterlaced)) self._demuxOffset=1.5#Solution for mpegts seek if isInterlaced: self.mediaPlayer.deinterlace="yes" def tweakUHD(self): - Log.logInfo("UHD, set stream size") + Log.info("UHD, set stream size") self.mediaPlayer.stream_buffer_size='255MiB' def tweakVC1(self): - Log.logInfo("Set VC1 codec in MPV explictly") + Log.info("Set VC1 codec in MPV explictly") self.mediaPlayer.hwdec_codecs="vc1" def tweakMPG(self): - Log.logInfo("MP2: Setting frame offset in mpg (mpv bug) and seek offset to high") + Log.info("MP2: Setting frame offset in mpg (mpv bug) and seek offset to high") self._frameOffset=1 self._demuxOffset=1.5 #mpeg step seek @@ -573,9 +573,9 @@ def shutDown(self): def createWidget(self,showGL,parent): self.showGL=showGL; if showGL: - Log.logInfo("create GL Widget") + Log.info("create GL Widget") return self._createGLWidget(parent) - Log.logInfo("create X11 Widget") + Log.info("create X11 Widget") return self._createPlainwidget(parent) def _createPlainwidget(self,parent): @@ -643,26 +643,26 @@ def _sanityCheck(self,streamData): fps=1.0 #rot = streamData.getRotation() #ratio = streamData.getAspectRatio() - Log.logInfo("Analyze MPV frameCount:%d fps:%.3f /FFMPEG frameCount:%d fps:%.3f, interlaced:%d"%(frameCount,fps,ff_FrameCount,ff_fps,interlaced)) + Log.info("Analyze MPV frameCount:%d fps:%.3f /FFMPEG frameCount:%d fps:%.3f, interlaced:%d"%(frameCount,fps,ff_FrameCount,ff_fps,interlaced)) fps_check= abs(self._secureDiv(self.player.fps,ff_fps)-1) #if fps_check >0.1: - Log.logInfo("Setting FPS into MPV, ratio: %.3f setting fps %.3f"%(fps_check,ff_fps)) + Log.info("Setting FPS into MPV, ratio: %.3f setting fps %.3f"%(fps_check,ff_fps)) self.player.setFPS(ff_fps) fcCheck= self._secureDiv(self.player.framecount,ff_FrameCount) if fcCheck < 0.9 or fcCheck > 1.1: - Log.logInfo("Irregular count, ratio: %.3f, setting framecount %d"%(fcCheck,ff_FrameCount)) + Log.info.logInfo("Irregular count, ratio: %.3f, setting framecount %d"%(fcCheck,ff_FrameCount)) self.player.framecount=max(1,ff_FrameCount) #Transport stream handling: - if streamData.isTransportStream(): + if streamData.isTransportStream() or interlaced: self.player.tweakTansportStreamSettings(interlaced) if isUHD: self.player.tweakUHD() - if streamData.isVC1(): + if streamData.isVC1Codec(): self.player.tweakVC1() - if streamData.isMPEG2(): + if streamData.isMPEG2Codec(): self.player.tweakMPG() def _secureDiv(self,nominator,denominator): diff --git a/src/VideoCut.py b/src/VideoCut.py index da74959..d01068f 100644 --- a/src/VideoCut.py +++ b/src/VideoCut.py @@ -23,7 +23,6 @@ from datetime import timedelta from FFMPEGTools import FFMPEGCutter, FFStreamProbe, CuttingConfig, OSTools, ConfigAccessor, VCCutter,FFmpegVersion import FFMPEGTools -import threading from time import sleep, time import xml.etree.cElementTree as CT ##################################################### @@ -776,7 +775,7 @@ def showCodecInfo(self): text2 = ''.join(entries) except: - Log.logException("Invalid codec format") + Log.exception("Invalid codec format") text = "
No Information
" text2= "
Please select a file first" self.__getInfoDialog(text + text2).show() @@ -853,7 +852,7 @@ def closeEvent(self,event): try: super(MainFrame, self).closeEvent(event) except: - Log.logException("Error Exit") + Log.exception("Error Exit") ''' class LanguageModel(): @@ -1320,7 +1319,7 @@ def setFile(self, filePath): self.gui.updateWindowTitle(OSTools().getFileNameOnly(filePath)) self._initVideoViews() except Exception as ex: - Log.logException("Setting file") + Log.exception("Setting file") if not OSTools().fileExists(filePath): self.lastError = "File not found" else: @@ -1390,7 +1389,7 @@ def restoreVideoCuts(self): cutList = XMLAccessor(self.currentPath).readXML() except Exception as error: print(error) - Log.logException("Error restore:") + Log.exception("Error restore:") return for cut in cutList: mode = VideoCutEntry.MODE_STOP @@ -1424,7 +1423,7 @@ def gotoCutIndex(self, index): # callback from stop button def killSaveProcessing(self): if self.cutter is None: - Log.logInfo("Can't kill process") + Log.info("Can't kill process") else: self.cutter.stopCurrentProcess() @@ -1435,7 +1434,7 @@ def saveVideo(self, path): if cutEntry.isStartMode(): if block: - Log.logInfo("Start invalid: %s" % (cutEntry.getTimeString())) + Log.info("Start invalid: %s" % (cutEntry.getTimeString())) else: block = [] block.append(cutEntry) @@ -1445,7 +1444,7 @@ def saveVideo(self, path): spanns.append(block) block = None else: - Log.logInfo("Stop ignored:" + cutEntry.getTimeString()) + Log.info("Stop ignored:" + cutEntry.getTimeString()) #src = self.player._file src = self._currentFile # need that without extension! @@ -1509,7 +1508,7 @@ def __makeCuts(self, srcPath, targetPath, spanns, settings): t2 = cutmark[1].timeDelta() hasSucess = self.cutter.cutPart(t1, t2, index, slices) if not hasSucess: - Log.logError("***Cutting failed***") + Log.error("***Cutting failed***") return self.cutter.join() @@ -1671,7 +1670,7 @@ def run(self): try: self.function(*self.arguments) except Exception as ex: - Log.logException("***Error in LongRunningOperation***") + Log.exception("***Error in LongRunningOperation***") self.msg = "Error while converting: "+str(ex) finally: self.signal.emit(self) @@ -1749,15 +1748,15 @@ def handle_exception(exc_type, exc_value, exc_traceback): infoText = str(exc_value) detailText = "*".join(traceback.format_exception(exc_type, exc_value, exc_traceback)) WIN.getErrorDialog("Unexpected error", infoText, detailText).show() - Log.logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) + Log.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) def parseOptions(args): res={} res["mpv"]=True - res["log"]="debug" + res["logConsole"]=False res["file"]=None try: - opts,args=getopt.getopt(args[1:], "l:p:", ["log=","player="]) + opts,args=getopt.getopt(args[1:], "cdp:", ["console","debug","player="]) if len(args)==1: res["file"]=args[0] except getopt.GetoptError as err: @@ -1765,11 +1764,13 @@ def parseOptions(args): sys.exit(2) for o,a in opts: - if o in ("-l","--log"): - print("log:",o," val:",a) + if o in ("-d","--debug"): + FFMPEGTools.setLogLevel("Debug") elif o in ("-p","--player"): if a in "cv": res["mpv"]=False + elif o in ("-c","--console"): + res["logConsole"]=True else: print("Undef:",o) return res @@ -1782,14 +1783,14 @@ def main(): localPath = OSTools().getActiveDirectory() #won't work after setting WD OSTools().setCurrentWorkingDirectory() #Log.logInfo('*** VC located in %s***' % OSTools().getWorkingDirectory()) - vc_config = ConfigAccessor("vc.ini") + vc_config = ConfigAccessor("VideoCut","vc.ini","videocut") #folder,name§ion vc_config.read(); argv = sys.argv - #parseOptions(argv) app = QApplication(argv) app.setWindowIcon(getAppIcon()) res=parseOptions(argv) + FFMPEGTools.setupRotatingLogger("VideoCut",res["logConsole"]) VideoPlugin=setUpVideoPlugin(res["mpv"]) fn =res["file"] if fn is None: @@ -1799,9 +1800,9 @@ def main(): fn=OSTools().joinPathes(localPath,fn) WIN = MainFrame(app,fn) app.exec_() - Log.logClose() + #logging.shutdown() except: - Log.logException("Error in main:") + Log.exception("Error in main:") #traceback.print_exc(file=sys.stdout) #TODO: Respect theme diff --git a/src/ffmpeg/bin/V3/remux5 b/src/ffmpeg/bin/V3/remux5 index 51247dd..2e6daf8 100755 Binary files a/src/ffmpeg/bin/V3/remux5 and b/src/ffmpeg/bin/V3/remux5 differ diff --git a/src/ffmpeg/src/makefile3 b/src/ffmpeg/src/makefile3 new file mode 100644 index 0000000..052364d --- /dev/null +++ b/src/ffmpeg/src/makefile3 @@ -0,0 +1,10 @@ +remux5: remux5.o + gcc -o ../bin/V3/remux5 remux5.o -g -lavutil -lavformat -lavcodec + +remux5.o: remux5.c + gcc -c remux5.c + +.PHONY: clean + +clean: + rm -f *.o remux5 diff --git a/src/installOnDebian.txt b/src/installOnDebian.txt index 02322a2..1bd67d3 100644 --- a/src/installOnDebian.txt +++ b/src/installOnDebian.txt @@ -1,6 +1,8 @@ -sudo apt-get install python3-opencv (includes numpy) -sudo apt-get install python3-pyqt5 -sudo apt-get install qt5-style-plugins +sudo apt –no-install-recommends install python3-pyqt5 ffmpeg python3-pil libmpv1 + +optional: +sudo apt install python3-opencv (includes numpy) +sudo apt install qt5-style-plugins on gtk base DE add to: ~/.profile or /etc/environment export QT_QPA_PLATFORMTHEME=gtk2