Browse Source

remove gitmodules to make the project smaller and less complex

master
Constantin Fürst 2 years ago
parent
commit
3486ef1f8e
  1. 6
      .gitmodules
  2. 1
      FFcuesplitter
  3. 1
      ffcuesplitter
  4. 353
      ffcuesplitter/cuesplitter.py
  5. 41
      ffcuesplitter/exceptions.py
  6. 230
      ffcuesplitter/ffmpeg.py
  7. 91
      ffcuesplitter/ffprobe.py
  8. 64
      ffcuesplitter/str_utils.py
  9. 86
      ffcuesplitter/utils.py

6
.gitmodules

@ -1,6 +0,0 @@
[submodule "FFcuesplitter"]
path = FFcuesplitter
url = https://github.com/jeanslack/FFcuesplitter.git
[submodule "deflacue"]
path = deflacue
url = https://github.com/idlesign/deflacue.git

1
FFcuesplitter

@ -1 +0,0 @@
Subproject commit 6a92928778b523e5e35be057232ef6f385155802

1
ffcuesplitter

@ -1 +0,0 @@
FFcuesplitter/ffcuesplitter

353
ffcuesplitter/cuesplitter.py

@ -0,0 +1,353 @@
"""
First release: January 16 2022
Name: cuesplitter.py
Porpose: FFmpeg based audio splitter for Cue sheet files
Platform: MacOs, Gnu/Linux, FreeBSD
Writer: jeanslack <jeanlucperni@gmail.com>
license: GPL3
Rev: February 06 2022
Code checker: flake8 and pylint
####################################################################
This file is part of FFcuesplitter.
FFcuesplitter is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FFcuesplitter is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FFcuesplitter. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import shutil
import tempfile
import chardet
from deflacue.deflacue import CueParser
from ffcuesplitter.str_utils import msgdebug, msg
from ffcuesplitter.exceptions import (InvalidFileError,
FFCueSplitterError,
)
from ffcuesplitter.ffprobe import ffprobe
from ffcuesplitter.ffmpeg import FFMpeg
class FFCueSplitter(FFMpeg):
"""
This class implements an interface for retrieve the required
data to accurately split audio CD images using FFmpeg.
Usage:
>>> from ffcuesplitter.cuesplitter import FFCueSplitter
Splittings:
>>> split = FFCueSplitter('/home/user/my_file.cue')
>>> split.open_cuefile()
>>> split.do_operations()
Get data tracks:
>>> data = FFCueSplitter('/home/user/other.cue', dry=True)
>>> data.open_cuefile()
>>> data.audiotracks # trackdata
>>> data.cue.meta.data # cd_info
>>> data.ffmpeg_arguments()
Only processing the track three:
>>> myfile = FFCueSplitter('/home/user/my_file.cue',
progress_meter='tqdm')
>>> f.open_cuefile()
>>> f.kwargs['tempdir'] = '/tmp/mytempdir'
>>> f.ffmpeg_arguments()
>>> f.processing(myfile.arguments[2], myfile.seconds[2])
>>> f.move_files_to_outputdir()
For a full meaning of the arguments to pass to the instance, read
the __init__ docstring of this class.
"""
def __init__(self,
filename,
outputdir=str('.'),
suffix=str('flac'),
overwrite=str('ask'),
ffmpeg_cmd=str('ffmpeg'),
ffmpeg_loglevel=str('info'),
ffprobe_cmd=str('ffprobe'),
ffmpeg_add_params=str(''),
progress_meter=str('standard'),
dry=bool(False)
):
"""
------------------
Arguments meaning:
------------------
filename:
absolute or relative CUE sheet file
outputdir:
absolute or relative pathname to output files
suffix:
output format, one of ("wav", "flac", "mp3", "ogg") .
overwrite:
overwriting options, one of "ask", "never", "always".
ffmpeg_cmd:
an absolute path command of ffmpeg
ffmpeg_loglevel:
one of "error", "warning", "info", "verbose", "debug" .
ffprobe_cmd:
an absolute path command of ffprobe.
ffmpeg_add_params:
additionals parameters of FFmpeg.
progress_meter:
one of 'tqdm', 'standard', default is 'standard'.
dry:
with `True`, perform the dry run with no changes
done to filesystem.
"""
super().__init__()
self.kwargs = {'filename': os.path.abspath(filename)}
self.kwargs['dirname'] = os.path.dirname(self.kwargs['filename'])
if outputdir == '.':
self.kwargs['outputdir'] = self.kwargs['dirname']
else:
self.kwargs['outputdir'] = os.path.abspath(outputdir)
self.kwargs['format'] = suffix
self.kwargs['overwrite'] = overwrite
self.kwargs['ffmpeg_cmd'] = ffmpeg_cmd
self.kwargs['ffmpeg_loglevel'] = ffmpeg_loglevel
self.kwargs['ffprobe_cmd'] = ffprobe_cmd
self.kwargs['ffmpeg_add_params'] = ffmpeg_add_params
self.kwargs['progress_meter'] = progress_meter
self.kwargs['dry'] = dry
self.kwargs['logtofile'] = os.path.join(self.kwargs['dirname'],
'ffcuesplitter.log')
self.kwargs['tempdir'] = '.'
self.audiotracks = None
self.probedata = []
self.cue_encoding = None # data chardet
self.cue = None
self.testpatch = None # set for test cases only
# ----------------------------------------------------------------#
def move_files_to_outputdir(self):
"""
All files are processed in a /temp folder. After the split
operation is complete, all tracks are moved from /temp folder
to output folder. Here evaluates what to do if files already
exists on output folder.
Raises:
FFCueSplitterError
Returns:
None
"""
outputdir = self.kwargs['outputdir']
overwr = self.kwargs['overwrite']
for track in os.listdir(self.kwargs['tempdir']):
if os.path.exists(os.path.join(outputdir, track)):
if overwr in ('n', 'N', 'y', 'Y', 'ask'):
while True:
msgdebug(warn=f"File already exists: "
f"'{os.path.join(outputdir, track)}'")
overwr = input("Overwrite [Y/n/always/never]? > ")
if overwr in ('Y', 'y', 'n', 'N', 'always', 'never'):
break
msgdebug(err=f"Invalid option '{overwr}'")
continue
if overwr == 'never':
msgdebug(info=("Do not overwrite any files because "
"you specified 'never' option"))
return None
if overwr in ('y', 'Y', 'always', 'never', 'ask'):
if overwr == 'always':
msgdebug(info=("Overwrite existing file because "
"you specified the 'always' option"))
try:
shutil.move(os.path.join(self.kwargs['tempdir'], track),
os.path.join(outputdir, track))
except Exception as error:
raise FFCueSplitterError(error) from error
return None
# ----------------------------------------------------------------#
def do_operations(self):
"""
Automates the work in a temporary context using tempfile.
Raises:
FFCueSplitterError
Returns:
None
"""
with tempfile.TemporaryDirectory(suffix=None,
prefix='ffcuesplitter_',
dir=None) as tmpdir:
self.kwargs['tempdir'] = tmpdir
self.ffmpeg_arguments()
msgdebug(info=(f"Temporary Target: '{self.kwargs['tempdir']}'"))
count = 0
msgdebug(info="Extracting audio tracks (type Ctrl+c to stop):")
for args, secs, title in zip(self.arguments,
self.seconds,
self.audiotracks):
count += 1
msg(f'\nTRACK {count}/{len(self.audiotracks)} '
f'>> "{title["TITLE"]}.{self.outsuffix}" ...')
self.processing(args, secs)
if self.kwargs['dry'] is True:
return
msg('\n')
msgdebug(info="...done exctracting")
msgdebug(info="Move files to: ",
tail=(f"\033[34m"
f"'{os.path.abspath(self.kwargs['outputdir'])}'"
f"\033[0m"))
try:
os.makedirs(self.kwargs['outputdir'],
mode=0o777, exist_ok=True)
except Exception as error:
raise FFCueSplitterError(error) from error
self.move_files_to_outputdir()
# ----------------------------------------------------------------#
def get_track_duration(self, tracks):
"""
Gets total duration of the source audio tracks for chunks
calculation on the progress meter during ffmpeg executions.
Given a total duration calculates the remains duration
for the last track as well.
This method is called by `cuefile_parser` method, Do not
call this method directly.
Raises:
FFCueSplitterError
Returns:
tracks (list), all track data taken from the cue file.
"""
if self.testpatch:
probe = {'format': {'duration': 6.000000}}
else:
filename = tracks[0].get('FILE')
cmd = self.kwargs['ffprobe_cmd']
kwargs = {'loglevel': 'error', 'hide_banner': None}
probe = ffprobe(filename, cmd=cmd, **kwargs)
self.probedata.append(probe)
time = []
for idx in enumerate(tracks):
if idx[0] != len(tracks) - 1: # minus last
trk = (tracks[idx[0] + 1]['START'] -
tracks[idx[0]]['START']) / (44100)
time.append(trk)
if not time:
last = (float(probe['format']['duration']) -
tracks[0]['START'] / 44100)
else:
last = float(probe['format']['duration']) - sum(time)
time.append(last)
for keydur, remain in zip(tracks, time):
keydur['DURATION'] = remain
return tracks
# ----------------------------------------------------------------#
def deflacue_object_handler(self):
"""
Handles `deflacue.CueParser` data.
Raises:
FFCueSplitterError: if no source audio file found
Returns:
'audiotracks' list object
"""
self.audiotracks = []
cd_info = self.cue.meta.data
def sanitize(val: str) -> str:
return val.replace('/', '').replace('\\','').replace('"', '')
tracks = self.cue.tracks
sourcenames = {k: [] for k in [str(x.file.path) for x in tracks]}
for track in enumerate(tracks):
track_file = track[1].file.path
if not track_file.exists():
msgdebug(warn=(f'Source file `{track_file}` is not '
f'found. Track is skipped.'))
if str(track_file) in sourcenames:
sourcenames.pop(str(track_file))
if not sourcenames:
raise FFCueSplitterError('No audio source files '
'found!')
continue
filename = (f"{sanitize(track[1].title)}")
data = {'FILE': str(track_file), **cd_info, **track[1].data}
data['TITLE'] = filename
data['START'] = track[1].start
if track[1].end != 0:
data['END'] = track[1].end
if f"{data['FILE']}" in sourcenames.keys():
sourcenames[f'{data["FILE"]}'].append(data)
for val in sourcenames.values():
self.audiotracks += self.get_track_duration(val)
return self.audiotracks
# ----------------------------------------------------------------#
def check_cuefile(self):
"""
Cue file check
"""
filesuffix = os.path.splitext(self.kwargs['filename'])[1]
isfile = os.path.isfile(self.kwargs['filename'])
if not isfile or filesuffix not in ('.cue', '.CUE'):
raise InvalidFileError(f"Invalid CUE sheet file: "
f"'{self.kwargs['filename']}'")
# ----------------------------------------------------------------#
def open_cuefile(self, testpatch=None):
"""
Read cue file and start file parsing via deflacue package
"""
if testpatch:
self.testpatch = True
self.check_cuefile()
curdir = os.getcwd()
os.chdir(self.kwargs['dirname'])
with open(self.kwargs['filename'], 'rb') as file:
cuebyte = file.read()
self.cue_encoding = chardet.detect(cuebyte)
parser = CueParser.from_file(self.kwargs['filename'],
encoding=self.cue_encoding['encoding'])
self.cue = parser.run()
self.deflacue_object_handler()
os.chdir(curdir)

41
ffcuesplitter/exceptions.py

@ -0,0 +1,41 @@
"""
Name: exceptions.py
Porpose: defines class Exceptions for ffcuesplitter
Platform: MacOs, Gnu/Linux, FreeBSD
Writer: jeanslack <jeanlucperni@gmail.com>
license: GPL3
Rev: February 03 2022
Code checker: flake8 and pylint
####################################################################
This file is part of FFcuesplitter.
FFcuesplitter is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FFcuesplitter is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FFcuesplitter. If not, see <http://www.gnu.org/licenses/>.
"""
class FFMpegError(Exception):
"""Excepion raised by FFMpeg class"""
class FFProbeError(Exception):
"""Excepion raised by FFProbe class"""
class InvalidFileError(Exception):
"""Exception type raised when CUE file is invalid."""
class FFCueSplitterError(Exception):
"""Exception raised in all other cases."""

230
ffcuesplitter/ffmpeg.py

@ -0,0 +1,230 @@
# -*- coding: UTF-8 -*-
"""
Name: ffmpeg.py
Porpose: builds arguments for FFmpeg processing.
Compatibility: Python3
Platform: all platforms
Author: Gianluca Pernigotto <jeanlucperni@gmail.com>
Copyright: (c) 2022/2023 Gianluca Pernigotto <jeanlucperni@gmail.com>
license: GPL3
Rev: February 06 2022
Code checker: flake8, pylint
########################################################
This file is part of FFcuesplitter.
FFcuesplitter is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FFcuesplitter is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FFcuesplitter. If not, see <http://www.gnu.org/licenses/>.
"""
import subprocess
import os
import sys
import platform
from tqdm import tqdm
from ffcuesplitter.exceptions import FFMpegError
from ffcuesplitter.str_utils import msg
from ffcuesplitter.utils import Popen
if not platform.system() == 'Windows':
import shlex
class FFMpeg:
"""
FFMpeg is a parent base class interface for FFCueSplitter.
It represents FFmpeg command and arguments with their
sub-processing.
"""
DATACODECS = {'wav': 'pcm_s16le -ar 44100',
'flac': 'flac -ar 44100',
'ogg': 'libvorbis -ar 44100',
'mp3': 'libmp3lame -ar 44100',
}
def __init__(self, **kwargs):
"""
Constructor
"""
self.kwargs = kwargs
self.audiotracks = kwargs
self.seconds = None
self.arguments = None
self.osplat = platform.system()
self.outsuffix = None
# -------------------------------------------------------------#
def codec_setup(self, sourcef):
"""
Returns codec arg based on given format
"""
if self.kwargs['format'] == 'copy':
self.outsuffix = os.path.splitext(sourcef)[1].replace('.', '')
codec = '-c copy'
else:
self.outsuffix = self.kwargs['format']
codec = f'-c:a {FFMpeg.DATACODECS[self.kwargs["format"]]}'
return codec, self.outsuffix
# -------------------------------------------------------------#
def ffmpeg_arguments(self):
"""
Builds `FFmpeg` arguments and calculates time seconds
for each audio track.
Returns:
dict(arguments:[...], seconds:[...])
"""
self.arguments = []
self.seconds = []
meters = {'tqdm': '-progress pipe:1 -nostats -nostdin', 'standard': ''}
for track in self.audiotracks:
codec, suffix = self.codec_setup(track["FILE"])
metadata = {'ARTIST': track.get('PERFORMER', ''),
'ALBUM': track.get('ALBUM', ''),
'TITLE': track.get('TITLE', ''),
'TRACK': (str(track['TRACK_NUM']) + '/' +
str(len(self.audiotracks))),
'DISCNUMBER': track.get('DISCNUMBER', ''),
'GENRE': track.get('GENRE', ''),
'DATE': track.get('DATE', ''),
'COMMENT': track.get('COMMENT', ''),
'DISCID': track.get('DISCID', ''),
}
cmd = f'"{self.kwargs["ffmpeg_cmd"]}" '
cmd += f' -loglevel {self.kwargs["ffmpeg_loglevel"]}'
cmd += f" {meters[self.kwargs['progress_meter']]}"
fpath = os.path.join(self.kwargs["dirname"], track["FILE"])
cmd += f' -i "{fpath}"'
cmd += f" -ss {round(track['START'] / 44100, 6)}" # ff to secs
if 'END' in track:
cmd += f" -to {round(track['END'] / 44100, 6)}" # ff to secs
for key, val in metadata.items():
cmd += f' -metadata {key}="{val}"'
cmd += f' {codec}'
cmd += f" {self.kwargs['ffmpeg_add_params']}"
cmd += ' -y'
num = str(track['TRACK_NUM']).rjust(2, '0')
name = f'{num} - {track["TITLE"]}.{suffix}'
cmd += f' "{os.path.join(self.kwargs["tempdir"], name)}"'
self.arguments.append(cmd)
self.seconds.append(track['DURATION'])
return {'arguments': self.arguments, 'seconds': self.seconds}
# --------------------------------------------------------------#
def processing(self, arg, secs):
"""
Redirect to required processing
"""
if self.kwargs['progress_meter'] == 'tqdm':
cmd = arg if self.osplat == 'Windows' else shlex.split(arg)
if self.kwargs['dry'] is True:
msg(cmd) # stdout cmd in dry mode
return
self.processing_with_tqdm_progress(cmd, secs)
elif self.kwargs['progress_meter'] == 'standard':
cmd = arg if self.osplat == 'Windows' else shlex.split(arg)
if self.kwargs['dry'] is True:
msg(cmd) # stdout cmd in dry mode
return
self.processing_with_standard_progress(cmd)
# --------------------------------------------------------------#
def processing_with_tqdm_progress(self, cmd, seconds):
"""
FFmpeg sub-processing showing a tqdm progress meter
for each loop. Also writes a log file to the same
destination folder as the .cue file .
Usage for get seconds elapsed:
progbar = tqdm(total=round(seconds), unit="s", dynamic_ncols=True)
progbar.clear()
previous_s = 0
s_processed = round(int(output.split('=')[1]) / 1_000_000)
s_increase = s_processed - previous_s
progbar.update(s_increase)
previous_s = s_processed
Raises:
FFMpegError
Returns:
None
"""
progbar = tqdm(total=100,
unit="s",
dynamic_ncols=True
)
progbar.clear()
try:
with open(self.kwargs['logtofile'], "w", encoding='utf-8') as log:
log.write(f'\nCOMMAND: {cmd}')
with Popen(cmd,
stdout=subprocess.PIPE,
stderr=log,
bufsize=1,
universal_newlines=True) as proc:
for output in proc.stdout:
if "out_time_ms" in output.strip():
s_processed = int(output.split('=')[1]) / 1_000_000
percent = s_processed / seconds * 100
progbar.update(round(percent) - progbar.n)
if proc.wait(): # error
progbar.close()
raise FFMpegError(f"ffmpeg FAILED: See log details: "
f"'{self.kwargs['logtofile']}'"
f"\nExit status: {proc.wait()}")
except (OSError, FileNotFoundError) as excepterr:
progbar.close()
raise FFMpegError(excepterr) from excepterr
except KeyboardInterrupt:
# proc.kill()
progbar.close()
proc.terminate()
sys.exit("\n[KeyboardInterrupt] FFmpeg process terminated.")
progbar.close()
# --------------------------------------------------------------#
def processing_with_standard_progress(self, cmd):
"""
FFmpeg sub-processing with stderr output to console.
The output depending on the ffmpeg loglevel option.
Raises:
FFMpegError
Returns:
None
"""
with open(self.kwargs['logtofile'], "w", encoding='utf-8') as log:
log.write(f'COMMAND: {cmd}')
try:
subprocess.run(cmd, check=True, shell=False)
except FileNotFoundError as err:
raise FFMpegError(f"{err}") from err
except subprocess.CalledProcessError as err:
raise FFMpegError(f"ffmpeg FAILED: {err}") from err
except KeyboardInterrupt:
sys.exit("\n[KeyboardInterrupt]")

91
ffcuesplitter/ffprobe.py

@ -0,0 +1,91 @@
# -*- coding: UTF-8 -*-
"""
Name: ffprobe.py
Porpose: simple cross-platform wrap for ffprobe
Compatibility: Python3
Platform: all platforms
Author: Gianluca Pernigotto <jeanlucperni@gmail.com>
Copyright: (c) 2022/2023 Gianluca Pernigotto <jeanlucperni@gmail.com>
license: GPL3
Rev: Feb.17.2022
Code checker: flake8, pylint
########################################################
This file is part of FFcuesplitter.
FFcuesplitter is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FFcuesplitter is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FFcuesplitter. If not, see <http://www.gnu.org/licenses/>.
"""
import subprocess
import shlex
import platform
import json
from ffcuesplitter.exceptions import FFProbeError
from ffcuesplitter.utils import Popen
def from_kwargs_to_args(kwargs):
"""
Helper function to build command line
arguments out of dict.
"""
args = []
for key in sorted(kwargs.keys()):
val = kwargs[key]
args.append(f'-{key}')
if val is not None:
args.append(f'{val}')
return args
def ffprobe(filename, cmd='ffprobe', **kwargs):
"""
Run ffprobe on the specified file and return a
JSON representation of the output.
Raises:
:class:`ffcuesplitter.FFProbeError`: if ffprobe
returns a non-zero exit code;
`ffcuesplitter.FFProbeError` from `OSError`,
`FileNotFoundError` if a generic error.
Usage:
ffprobe(filename,
cmd='/usr/bin oi/ffprobe',
loglevel='error',
hide_banner=None,
etc,
)
"""
args = (f'"{cmd}" -show_format -show_streams -of json '
f'{" ".join(from_kwargs_to_args(kwargs))} '
f'"{filename}"'
)
args = shlex.split(args) if platform.system() != 'Windows' else args
try:
with Popen(args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
) as proc:
output, error = proc.communicate()
if proc.returncode != 0:
raise FFProbeError(f'ffprobe: {error}')
except (OSError, FileNotFoundError) as excepterr:
raise FFProbeError(excepterr) from excepterr
else:
return json.loads(output)

64
ffcuesplitter/str_utils.py

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""
Name: str_utils.py (module)
Porpose: module for cosmetic output console in ANSI sequences
Writer: Gianluca Pernigoto <jeanlucperni@gmail.com>
Copyright: (c) 2022 Gianluca Pernigoto <jeanlucperni@gmail.com>
license: GPL3
Rev: Jan 10 2022
Code checker: flake8, pylint
"""
def msgdebug(head='', info=None, warn=None, err=None, tail=''):
"""
Print debug messages:
``head`` can be used for additionals custom string to use
at the beginning of the string.
``tail`` can be used for additionals custom string to use
at the end of the string.
``info`` print in blue color, ``warn`` print in yellow color
``err`` print in red color.
"""
if info:
print(f"{head}\033[32;1mINFO:\033[0m {info}{tail}")
elif warn:
print(f"{head}\033[33;1mWARNING:\033[0m {warn}{tail}")
elif err:
print(f"{head}\033[31;1mERROR:\033[0m {err}{tail}")
def msgcolor(head='', orange=None, green=None, azure=None, tail=''):
"""
Print information messages by explicitly
choosing the name of the color to be displayed:
``head`` can be used for additionals custom string to use
at the beginning of the string.
``tail`` can be used for additionals custom string to use
at the end of the string.
"""
if orange:
print(f"{head}\033[33;1m{orange}\033[0m{tail}")
elif green:
print(f"{head}\033[32;1m{green}\033[0m{tail}")
elif azure:
print(f"{head}\033[34;1m{azure}\033[0m{tail}")
def msgend(done=None, abort=None):
"""
Print status messages at the end of the tasks
"""
if done:
print("\033[1m..Finished!\033[0m\n")
elif abort:
print("\033[1m..Abort!\033[0m\n")
def msg(message):
"""
Print any string messages
"""
print(message)

86
ffcuesplitter/utils.py

@ -0,0 +1,86 @@
"""
Name: utils.py
Porpose: utils used by FFcuesplitter
Platform: MacOs, Gnu/Linux, FreeBSD
Writer: jeanslack <jeanlucperni@gmail.com>
license: GPL3
Rev: January 16 2022
Code checker: flake8 and pylint
####################################################################
This file is part of FFcuesplitter.
FFcuesplitter is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FFcuesplitter is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FFcuesplitter. If not, see <http://www.gnu.org/licenses/>.
"""
import subprocess
import platform
import datetime
def pairwise(iterable):
"""
Return a zip object from iterable.
This function is used by run method.
----
USE:
after splitting ffmpeg's progress strings such as:
output = "frame= 1178 fps=155 q=29.0 size= 2072kB time=00:00:39.02
bitrate= 435.0kbits/s speed=5.15x "
in a list as:
iterable = ['frame', '1178', 'fps', '155', 'q', '29.0', 'size', '2072kB',
'time', '00:00:39.02', 'bitrate', '435.0kbits/s', speed',
'5.15x']
for x, y in pairwise(iterable):
(x,y)
<https://stackoverflow.com/questions/5389507/iterating-over-every-
two-elements-in-a-list>
"""
itobj = iter(iterable) # list_iterator object
return zip(itobj, itobj) # zip object pairs from list iterable object
# ------------------------------------------------------------------------
def frames_to_seconds(frames):
"""
Converts frames (10407600) to seconds (236.0) and then
converts them to a time format string (0:03:56) using datetime.
"""
secs = frames / 44100
return str(datetime.timedelta(seconds=secs))
# ------------------------------------------------------------------------
class Popen(subprocess.Popen):
"""
Inherit subprocess.Popen class to set _startupinfo.
This avoids displaying a console window on MS-Windows
using GUI's .
"""
if platform.system() == 'Windows':
_startupinfo = subprocess.STARTUPINFO()
_startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
else:
_startupinfo = None
def __init__(self, *args, **kwargs):
"""Constructor
"""
super().__init__(*args, **kwargs, startupinfo=self._startupinfo)
# def communicate_or_kill(self, *args, **kwargs):
# return process_communicate_or_kill(self, *args, **kwargs)
# ------------------------------------------------------------------------
Loading…
Cancel
Save