batchmp 1.0__tar.gz → 1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {batchmp-1.0/batchmp.egg-info → batchmp-1.4}/PKG-INFO +78 -14
- batchmp-1.0/PKG-INFO → batchmp-1.4/README.md +60 -39
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/base/bmp_dispatch.py +2 -2
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/bmfp/bmfp_dispatch.py +3 -1
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/bmfp/bmfp_options.py +8 -3
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/renamer/renamer_dispatch.py +15 -4
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/renamer/renamer_options.py +32 -1
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/ffcommands/cuesplit.py +3 -3
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/ffcommands/fragment.py +13 -3
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/ffcommands/segment.py +7 -7
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/ffutils.py +5 -5
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/utils/cueparse.py +2 -2
- {batchmp-1.0 → batchmp-1.4}/batchmp/fstools/builders/fsb.py +60 -3
- {batchmp-1.0 → batchmp-1.4}/batchmp/fstools/builders/fsprms.py +10 -3
- {batchmp-1.0 → batchmp-1.4}/batchmp/fstools/dirtools.py +235 -6
- {batchmp-1.0 → batchmp-1.4}/batchmp/fstools/rename.py +11 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/fstools/walker.py +2 -2
- {batchmp-1.0 → batchmp-1.4}/batchmp/tags/handlers/mtghandler.py +1 -2
- {batchmp-1.0 → batchmp-1.4}/batchmp/tags/processors/basetp.py +1 -1
- batchmp-1.0/README.md → batchmp-1.4/batchmp.egg-info/PKG-INFO +103 -10
- {batchmp-1.0 → batchmp-1.4}/batchmp.egg-info/SOURCES.txt +0 -2
- batchmp-1.4/batchmp.egg-info/requires.txt +4 -0
- {batchmp-1.0 → batchmp-1.4}/setup.py +3 -6
- batchmp-1.0/batchmp/tags/extern/mediafile.py +0 -1889
- batchmp-1.0/batchmp/tags/processors/__init__.py +0 -0
- batchmp-1.0/batchmp.egg-info/requires.txt +0 -2
- {batchmp-1.0 → batchmp-1.4}/LICENSE +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/base/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/base/bmp_options.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/base/vchk.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/bmfp/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/renamer/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/tagger/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/tagger/tagger_dispatch.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/cli/tagger/tagger_options.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/commons/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/commons/chainedhandler.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/commons/descriptors.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/commons/progressbar.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/commons/taskprocessor.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/commons/utils.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/ffcommands/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/ffcommands/cmdopt.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/ffcommands/convert.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/ffcommands/denoise.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/ffcommands/normalize_peak.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/ffcommands/silencesplit.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/ffrunner.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/processors/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/processors/basefp.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/processors/ffentry.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/utils/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/ffmptools/utils/cuesheet.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/fstools/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/fstools/builders/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/fstools/builders/fsentry.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/fstools/fsutils.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/tags/__init__.py +0 -0
- {batchmp-1.0/batchmp/tags/extern → batchmp-1.4/batchmp/tags/handlers}/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/tags/handlers/basehandler.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/tags/handlers/ffmphandler.py +0 -0
- {batchmp-1.0/batchmp/tags/handlers → batchmp-1.4/batchmp/tags/handlers/ffmphandlers}/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/tags/handlers/ffmphandlers/base.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/tags/handlers/pmhandler.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/tags/handlers/tagsholder.py +0 -0
- {batchmp-1.0/batchmp/tags/handlers/ffmphandlers → batchmp-1.4/batchmp/tags/output}/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp/tags/output/formatters.py +0 -0
- {batchmp-1.0/batchmp/tags/output → batchmp-1.4/batchmp/tags/processors}/__init__.py +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp.egg-info/dependency_links.txt +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp.egg-info/entry_points.txt +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp.egg-info/top_level.txt +0 -0
- {batchmp-1.0 → batchmp-1.4}/batchmp.egg-info/zip-safe +0 -0
- {batchmp-1.0 → batchmp-1.4}/setup.cfg +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: batchmp
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4
|
|
4
4
|
Summary: Command-line tools for batch media processing
|
|
5
5
|
Home-page: https://github.com/akpw/batch-mp-tools
|
|
6
6
|
Author: Arseniy Kuznetsov
|
|
7
7
|
Author-email: k.arseniy@gmail.com
|
|
8
|
-
License:
|
|
8
|
+
License: GPL-2.0-or-later
|
|
9
9
|
Keywords: batch processing media video audio CLI rename tags ID3
|
|
10
10
|
Classifier: Development Status :: 4 - Beta
|
|
11
|
-
Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)
|
|
12
11
|
Classifier: Programming Language :: Python
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.6
|
|
14
13
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
@@ -26,6 +25,21 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
26
25
|
Classifier: Topic :: Utilities
|
|
27
26
|
Description-Content-Type: text/markdown
|
|
28
27
|
License-File: LICENSE
|
|
28
|
+
Requires-Dist: mutagen>=1.27
|
|
29
|
+
Requires-Dist: pygtrie>=2.3.2
|
|
30
|
+
Requires-Dist: filetype>=1.0.7
|
|
31
|
+
Requires-Dist: mediafile>=0.13.0
|
|
32
|
+
Dynamic: author
|
|
33
|
+
Dynamic: author-email
|
|
34
|
+
Dynamic: classifier
|
|
35
|
+
Dynamic: description
|
|
36
|
+
Dynamic: description-content-type
|
|
37
|
+
Dynamic: home-page
|
|
38
|
+
Dynamic: keywords
|
|
39
|
+
Dynamic: license
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
Dynamic: requires-dist
|
|
42
|
+
Dynamic: summary
|
|
29
43
|
|
|
30
44
|
|
|
31
45
|
**Status:**
|
|
@@ -39,10 +53,11 @@ A rainy weekends project under occasional development :)
|
|
|
39
53
|
- latest from source repository: `$ pip install git+https://github.com/akpw/batch-mp-tools.git`
|
|
40
54
|
|
|
41
55
|
#### Blogs:
|
|
42
|
-
- [Practical BatchMP](
|
|
43
|
-
- [
|
|
44
|
-
- [
|
|
45
|
-
- [
|
|
56
|
+
- [Practical BatchMP](https://akpw.github.io//tags.html#BatchMP+Tools)
|
|
57
|
+
- [Renamer Organize & Virtual Views](https://akpw.github.io/articles/2025/09/22/Print-and-Organize.html)
|
|
58
|
+
- [BatchMP Tools Tutorial](https://akpw.github.io//articles/2015/04/10/batchmp-tutorial-part-i.html)
|
|
59
|
+
- [The BatchMP Tools Project](https://akpw.github.io//articles/2015/03/21/the-batchmp-project.html)
|
|
60
|
+
- [Parallel batch media processing with FFmpeg and Python](https://akpw.github.io//articles/2014/11/24/batch-media-processing-ffmpeg-python.html)
|
|
46
61
|
|
|
47
62
|
## Description
|
|
48
63
|
|
|
@@ -62,7 +77,7 @@ By default the tools always visualize targeted changes (whenever possible) befor
|
|
|
62
77
|
|
|
63
78
|
A little bit more details on each utility:
|
|
64
79
|
|
|
65
|
-
[**Renamer**](https://github.com/akpw/batch-mp-tools#renamer) is a multi-platform batch rename tool. In addition to common operations such as regexp-based replace, adding text / dates, etc. it also supports advanced operations such as expandable template processing during replace, multi-level indexing across nested directories, flattening folders,
|
|
80
|
+
[**Renamer**](https://github.com/akpw/batch-mp-tools#renamer) is a multi-platform batch rename tool. In addition to common operations such as regexp-based replace, adding text / dates, etc. it also supports advanced operations such as expandable template processing during replace, multi-level indexing across nested directories, flattening folders, organizing files by type or date, etc. The enhanced print command now supports virtual views to preview organization without moving files.
|
|
66
81
|
At its simplest, Renamer can be used to print out the content of current directory:
|
|
67
82
|
```
|
|
68
83
|
$ renamer
|
|
@@ -91,6 +106,34 @@ For multi-level indexing of all M4A files in all sub-directories of the current
|
|
|
91
106
|
```
|
|
92
107
|
Sequential indexing is supported as well using the `-sq` switch. An important detail here, by default Renamer is visualizing the targeted changes and asking for permission to proceed before actually doing anything.
|
|
93
108
|
|
|
109
|
+
For organizing files by media type:
|
|
110
|
+
```
|
|
111
|
+
$ renamer organize -b type
|
|
112
|
+
~/Downloads
|
|
113
|
+
|- image/
|
|
114
|
+
|- photo1.jpg
|
|
115
|
+
|- screenshot.png
|
|
116
|
+
|- video/
|
|
117
|
+
|- movie.mp4
|
|
118
|
+
|- audio/
|
|
119
|
+
|- song.mp3
|
|
120
|
+
|
|
121
|
+
Proceed? [y/n]:
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Or preview how files would look organized by date without moving them:
|
|
125
|
+
```
|
|
126
|
+
$ renamer print -b date --date-format "%Y/%m"
|
|
127
|
+
Virtual view by date:
|
|
128
|
+
~/Downloads
|
|
129
|
+
|- 2025/
|
|
130
|
+
|- 01/
|
|
131
|
+
|- document.pdf
|
|
132
|
+
|- photo.jpg
|
|
133
|
+
|- 02/
|
|
134
|
+
|- video.mp4
|
|
135
|
+
```
|
|
136
|
+
|
|
94
137
|
|
|
95
138
|
|
|
96
139
|
[**Tagger**](https://github.com/akpw/batch-mp-tools#tagger) manages media metadata, such as tags and artwork. Setting those in selected media file over multiple nested directories now becomes a breeze, with just a few simple commands working uniformly over almost any practically imaginable audio / video media formats. While easy to use, Tagger supports advanced metadata manipulation such as regexp-based replace, expandable template processing, etc. For example, to set the title tag to respective file names followed by the values of track and tracktotal tags:
|
|
@@ -178,7 +221,9 @@ I will follow up with more examples and common use-cases in future blogs.
|
|
|
178
221
|
. display sorting:
|
|
179
222
|
.. by size/date, ascending/descending
|
|
180
223
|
. action commands:
|
|
181
|
-
.. print Prints source directory
|
|
224
|
+
.. print Prints source directory. Enhanced with virtual organization views:
|
|
225
|
+
$ renamer print -b type # Preview organize by media type
|
|
226
|
+
$ renamer print -b date -df "%Y/%m" # Preview organize by date
|
|
182
227
|
.. flatten Flatten all folders below target level, moving the
|
|
183
228
|
files up at the target level. By default, deletes all empty flattened folders
|
|
184
229
|
.. index Adds index to files and directories names
|
|
@@ -191,6 +236,10 @@ I will follow up with more examples and common use-cases in future blogs.
|
|
|
191
236
|
.. remove Removes n characters from files and directories names
|
|
192
237
|
.. capitalize Capitalizes words in files / directories names
|
|
193
238
|
.. delete Delete selected files and directories
|
|
239
|
+
.. organize Organizes files into subdirectories based on their attributes:
|
|
240
|
+
$ renamer organize -b type # By media type (image/, video/, audio/)
|
|
241
|
+
$ renamer organize -b date -df "%Y-%m" # By date (2025-01/, 2025-02/)
|
|
242
|
+
$ renamer organize -b date -df "%Y/%m" -td ~/Sorted # To target directory
|
|
194
243
|
|
|
195
244
|
Usage: renamer [-h] [-d DIR] [-f FILE] [Global Options] {Commands} [Commands Options]
|
|
196
245
|
Global Options:
|
|
@@ -215,7 +264,7 @@ I will follow up with more examples and common use-cases in future blogs.
|
|
|
215
264
|
[-q, --quiet] Do not visualise changes / show messages during processing
|
|
216
265
|
|
|
217
266
|
Commands:
|
|
218
|
-
{print, index, add_date, add_text, remove, replace, capitalize, flatten, delete, version, info}
|
|
267
|
+
{print, index, add_date, add_text, remove, replace, capitalize, flatten, delete, organize, version, info}
|
|
219
268
|
$ renamer {command} -h #run this for detailed help on individual commands
|
|
220
269
|
|
|
221
270
|
### tagger
|
|
@@ -324,7 +373,7 @@ Support via FFmpeg: 'AVI', 'FLV', 'MKV', 'MKA'
|
|
|
324
373
|
(shows hidden files excluded by default)
|
|
325
374
|
|
|
326
375
|
Target output Directory Target output directory. When omitted, will be
|
|
327
|
-
[-td, --target-dir] automatically created
|
|
376
|
+
[-td, --target-dir] automatically created inside the parent level of
|
|
328
377
|
the input source. For recursive processing,
|
|
329
378
|
the processed files directory structure there
|
|
330
379
|
will be the same as for the original files.
|
|
@@ -348,8 +397,23 @@ Support via FFmpeg: 'AVI', 'FLV', 'MKV', 'MKA'
|
|
|
348
397
|
## Installing Development version
|
|
349
398
|
- Clone the repo, then run: `$ python -m pip install .`
|
|
350
399
|
|
|
351
|
-
|
|
352
|
-
|
|
400
|
+
## Running Tests
|
|
401
|
+
|
|
402
|
+
**Using pytest (recommended):**
|
|
403
|
+
```bash
|
|
404
|
+
$ pytest -v --tb=short # Run all tests with verbose output
|
|
405
|
+
$ pytest tests/ # Run all tests
|
|
406
|
+
$ pytest tests/fs/test_fs_organize.py # Test organize functionality
|
|
407
|
+
$ pytest tests/fs/test_fsutils.py # Test core filesystem utilities
|
|
408
|
+
$ pytest tests/cli/test_renamer_cli.py # Test renamer CLI
|
|
409
|
+
$ pytest -k "test_organize" # Run tests matching pattern
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Using unittest (fallback):**
|
|
413
|
+
```bash
|
|
414
|
+
$ python -m unittest discover tests -v # Run all tests with verbose output
|
|
415
|
+
$ python -m unittest tests.fs.test_fs_organize # Run specific test module
|
|
416
|
+
```
|
|
353
417
|
|
|
354
418
|
|
|
355
419
|
|
|
@@ -1,32 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: batchmp
|
|
3
|
-
Version: 1.0
|
|
4
|
-
Summary: Command-line tools for batch media processing
|
|
5
|
-
Home-page: https://github.com/akpw/batch-mp-tools
|
|
6
|
-
Author: Arseniy Kuznetsov
|
|
7
|
-
Author-email: k.arseniy@gmail.com
|
|
8
|
-
License: GNU General Public License v2 (GPLv2)
|
|
9
|
-
Keywords: batch processing media video audio CLI rename tags ID3
|
|
10
|
-
Classifier: Development Status :: 4 - Beta
|
|
11
|
-
Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)
|
|
12
|
-
Classifier: Programming Language :: Python
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
14
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
-
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
-
Classifier: Intended Audience :: Developers
|
|
17
|
-
Classifier: Intended Audience :: System Administrators
|
|
18
|
-
Classifier: Intended Audience :: Information Technology
|
|
19
|
-
Classifier: Operating System :: OS Independent
|
|
20
|
-
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
21
|
-
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
22
|
-
Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
|
|
23
|
-
Classifier: Topic :: Multimedia :: Video
|
|
24
|
-
Classifier: Topic :: Multimedia :: Video :: Conversion
|
|
25
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
-
Classifier: Topic :: Utilities
|
|
27
|
-
Description-Content-Type: text/markdown
|
|
28
|
-
License-File: LICENSE
|
|
29
|
-
|
|
30
1
|
|
|
31
2
|
**Status:**
|
|
32
3
|
A rainy weekends project under occasional development :)
|
|
@@ -39,10 +10,11 @@ A rainy weekends project under occasional development :)
|
|
|
39
10
|
- latest from source repository: `$ pip install git+https://github.com/akpw/batch-mp-tools.git`
|
|
40
11
|
|
|
41
12
|
#### Blogs:
|
|
42
|
-
- [Practical BatchMP](
|
|
43
|
-
- [
|
|
44
|
-
- [
|
|
45
|
-
- [
|
|
13
|
+
- [Practical BatchMP](https://akpw.github.io//tags.html#BatchMP+Tools)
|
|
14
|
+
- [Renamer Organize & Virtual Views](https://akpw.github.io/articles/2025/09/22/Print-and-Organize.html)
|
|
15
|
+
- [BatchMP Tools Tutorial](https://akpw.github.io//articles/2015/04/10/batchmp-tutorial-part-i.html)
|
|
16
|
+
- [The BatchMP Tools Project](https://akpw.github.io//articles/2015/03/21/the-batchmp-project.html)
|
|
17
|
+
- [Parallel batch media processing with FFmpeg and Python](https://akpw.github.io//articles/2014/11/24/batch-media-processing-ffmpeg-python.html)
|
|
46
18
|
|
|
47
19
|
## Description
|
|
48
20
|
|
|
@@ -62,7 +34,7 @@ By default the tools always visualize targeted changes (whenever possible) befor
|
|
|
62
34
|
|
|
63
35
|
A little bit more details on each utility:
|
|
64
36
|
|
|
65
|
-
[**Renamer**](https://github.com/akpw/batch-mp-tools#renamer) is a multi-platform batch rename tool. In addition to common operations such as regexp-based replace, adding text / dates, etc. it also supports advanced operations such as expandable template processing during replace, multi-level indexing across nested directories, flattening folders,
|
|
37
|
+
[**Renamer**](https://github.com/akpw/batch-mp-tools#renamer) is a multi-platform batch rename tool. In addition to common operations such as regexp-based replace, adding text / dates, etc. it also supports advanced operations such as expandable template processing during replace, multi-level indexing across nested directories, flattening folders, organizing files by type or date, etc. The enhanced print command now supports virtual views to preview organization without moving files.
|
|
66
38
|
At its simplest, Renamer can be used to print out the content of current directory:
|
|
67
39
|
```
|
|
68
40
|
$ renamer
|
|
@@ -91,6 +63,34 @@ For multi-level indexing of all M4A files in all sub-directories of the current
|
|
|
91
63
|
```
|
|
92
64
|
Sequential indexing is supported as well using the `-sq` switch. An important detail here, by default Renamer is visualizing the targeted changes and asking for permission to proceed before actually doing anything.
|
|
93
65
|
|
|
66
|
+
For organizing files by media type:
|
|
67
|
+
```
|
|
68
|
+
$ renamer organize -b type
|
|
69
|
+
~/Downloads
|
|
70
|
+
|- image/
|
|
71
|
+
|- photo1.jpg
|
|
72
|
+
|- screenshot.png
|
|
73
|
+
|- video/
|
|
74
|
+
|- movie.mp4
|
|
75
|
+
|- audio/
|
|
76
|
+
|- song.mp3
|
|
77
|
+
|
|
78
|
+
Proceed? [y/n]:
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Or preview how files would look organized by date without moving them:
|
|
82
|
+
```
|
|
83
|
+
$ renamer print -b date --date-format "%Y/%m"
|
|
84
|
+
Virtual view by date:
|
|
85
|
+
~/Downloads
|
|
86
|
+
|- 2025/
|
|
87
|
+
|- 01/
|
|
88
|
+
|- document.pdf
|
|
89
|
+
|- photo.jpg
|
|
90
|
+
|- 02/
|
|
91
|
+
|- video.mp4
|
|
92
|
+
```
|
|
93
|
+
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
[**Tagger**](https://github.com/akpw/batch-mp-tools#tagger) manages media metadata, such as tags and artwork. Setting those in selected media file over multiple nested directories now becomes a breeze, with just a few simple commands working uniformly over almost any practically imaginable audio / video media formats. While easy to use, Tagger supports advanced metadata manipulation such as regexp-based replace, expandable template processing, etc. For example, to set the title tag to respective file names followed by the values of track and tracktotal tags:
|
|
@@ -178,7 +178,9 @@ I will follow up with more examples and common use-cases in future blogs.
|
|
|
178
178
|
. display sorting:
|
|
179
179
|
.. by size/date, ascending/descending
|
|
180
180
|
. action commands:
|
|
181
|
-
.. print Prints source directory
|
|
181
|
+
.. print Prints source directory. Enhanced with virtual organization views:
|
|
182
|
+
$ renamer print -b type # Preview organize by media type
|
|
183
|
+
$ renamer print -b date -df "%Y/%m" # Preview organize by date
|
|
182
184
|
.. flatten Flatten all folders below target level, moving the
|
|
183
185
|
files up at the target level. By default, deletes all empty flattened folders
|
|
184
186
|
.. index Adds index to files and directories names
|
|
@@ -191,6 +193,10 @@ I will follow up with more examples and common use-cases in future blogs.
|
|
|
191
193
|
.. remove Removes n characters from files and directories names
|
|
192
194
|
.. capitalize Capitalizes words in files / directories names
|
|
193
195
|
.. delete Delete selected files and directories
|
|
196
|
+
.. organize Organizes files into subdirectories based on their attributes:
|
|
197
|
+
$ renamer organize -b type # By media type (image/, video/, audio/)
|
|
198
|
+
$ renamer organize -b date -df "%Y-%m" # By date (2025-01/, 2025-02/)
|
|
199
|
+
$ renamer organize -b date -df "%Y/%m" -td ~/Sorted # To target directory
|
|
194
200
|
|
|
195
201
|
Usage: renamer [-h] [-d DIR] [-f FILE] [Global Options] {Commands} [Commands Options]
|
|
196
202
|
Global Options:
|
|
@@ -215,7 +221,7 @@ I will follow up with more examples and common use-cases in future blogs.
|
|
|
215
221
|
[-q, --quiet] Do not visualise changes / show messages during processing
|
|
216
222
|
|
|
217
223
|
Commands:
|
|
218
|
-
{print, index, add_date, add_text, remove, replace, capitalize, flatten, delete, version, info}
|
|
224
|
+
{print, index, add_date, add_text, remove, replace, capitalize, flatten, delete, organize, version, info}
|
|
219
225
|
$ renamer {command} -h #run this for detailed help on individual commands
|
|
220
226
|
|
|
221
227
|
### tagger
|
|
@@ -324,7 +330,7 @@ Support via FFmpeg: 'AVI', 'FLV', 'MKV', 'MKA'
|
|
|
324
330
|
(shows hidden files excluded by default)
|
|
325
331
|
|
|
326
332
|
Target output Directory Target output directory. When omitted, will be
|
|
327
|
-
[-td, --target-dir] automatically created
|
|
333
|
+
[-td, --target-dir] automatically created inside the parent level of
|
|
328
334
|
the input source. For recursive processing,
|
|
329
335
|
the processed files directory structure there
|
|
330
336
|
will be the same as for the original files.
|
|
@@ -348,8 +354,23 @@ Support via FFmpeg: 'AVI', 'FLV', 'MKV', 'MKA'
|
|
|
348
354
|
## Installing Development version
|
|
349
355
|
- Clone the repo, then run: `$ python -m pip install .`
|
|
350
356
|
|
|
351
|
-
|
|
352
|
-
|
|
357
|
+
## Running Tests
|
|
358
|
+
|
|
359
|
+
**Using pytest (recommended):**
|
|
360
|
+
```bash
|
|
361
|
+
$ pytest -v --tb=short # Run all tests with verbose output
|
|
362
|
+
$ pytest tests/ # Run all tests
|
|
363
|
+
$ pytest tests/fs/test_fs_organize.py # Test organize functionality
|
|
364
|
+
$ pytest tests/fs/test_fsutils.py # Test core filesystem utilities
|
|
365
|
+
$ pytest tests/cli/test_renamer_cli.py # Test renamer CLI
|
|
366
|
+
$ pytest -k "test_organize" # Run tests matching pattern
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Using unittest (fallback):**
|
|
370
|
+
```bash
|
|
371
|
+
$ python -m unittest discover tests -v # Run all tests with verbose output
|
|
372
|
+
$ python -m unittest tests.fs.test_fs_organize # Run specific test module
|
|
373
|
+
```
|
|
353
374
|
|
|
354
375
|
|
|
355
376
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
13
|
## GNU General Public License for more details.
|
|
14
14
|
|
|
15
|
-
import
|
|
15
|
+
from importlib import metadata
|
|
16
16
|
import batchmp.cli.base.vchk
|
|
17
17
|
from batchmp.cli.base.bmp_options import BatchMPArgParser, BatchMPBaseCommands
|
|
18
18
|
|
|
@@ -43,7 +43,7 @@ class BatchMPDispatcher:
|
|
|
43
43
|
def print_version(self):
|
|
44
44
|
''' Prints BatchMP version info
|
|
45
45
|
'''
|
|
46
|
-
version =
|
|
46
|
+
version = metadata.version("batchmp")
|
|
47
47
|
print('BatchMP tools version {}'.format(version))
|
|
48
48
|
|
|
49
49
|
def print_info(self):
|
|
@@ -94,7 +94,9 @@ class BMFPDispatcher(BatchMPDispatcher):
|
|
|
94
94
|
ff_entry_params = FFEntryParamsExt(args)
|
|
95
95
|
Fragmenter().fragment(ff_entry_params,
|
|
96
96
|
fragment_starttime = args['fragment_starttime'].total_seconds(),
|
|
97
|
-
fragment_duration = args['fragment_duration'].total_seconds()
|
|
97
|
+
fragment_duration = args['fragment_duration'].total_seconds(),
|
|
98
|
+
fragment_trim = args['fragment_trim'].total_seconds(),
|
|
99
|
+
)
|
|
98
100
|
|
|
99
101
|
def segment(self, args):
|
|
100
102
|
ff_entry_params = FFEntryParamsExt(args)
|
|
@@ -146,8 +146,9 @@ class BMFPArgParser(BatchMPArgParser):
|
|
|
146
146
|
target_output_group = parser.add_argument_group('Target Output Directory')
|
|
147
147
|
target_output_group.add_argument("-td", "--target-dir", dest = "target_dir",
|
|
148
148
|
type = lambda d: self._is_valid_dir_path(parser, d),
|
|
149
|
+
default = '.',
|
|
149
150
|
help = "Target output directory. When omitted, will be automatically "
|
|
150
|
-
"created
|
|
151
|
+
"created inside the parent level of the input source. "
|
|
151
152
|
"For recursive processing, the processed files directory structure there "
|
|
152
153
|
"will be the same as for the original files.")
|
|
153
154
|
|
|
@@ -246,14 +247,18 @@ class BMFPArgParser(BatchMPArgParser):
|
|
|
246
247
|
description = 'Extracts a fragment via specified start time & duration',
|
|
247
248
|
formatter_class = BatchMPHelpFormatter)
|
|
248
249
|
group = fragment_parser.add_argument_group('Fragment parameters')
|
|
249
|
-
group.add_argument('-fs', '--
|
|
250
|
+
group.add_argument('-fs', '--start', dest='fragment_starttime',
|
|
250
251
|
help = 'Fragment start time, in seconds or in the "hh:mm:ss[.xxx]" format',
|
|
251
252
|
type = lambda f: self._is_timedelta(parser, f),
|
|
252
253
|
required = True)
|
|
253
254
|
group.add_argument('-fd', '--duration', dest='fragment_duration',
|
|
254
|
-
help = 'Fragment duration, in seconds or in the "hh:mm:ss[.xxx]" format',
|
|
255
|
+
help = 'Fragment duration (default is full media length), in seconds or in the "hh:mm:ss[.xxx]" format',
|
|
255
256
|
type = lambda f: self._is_timedelta(parser, f),
|
|
256
257
|
default = timedelta(days = 380))
|
|
258
|
+
group.add_argument('-ft', '--trim', dest='fragment_trim',
|
|
259
|
+
help = 'Fragment trimming at the end (optional), in seconds or in the "hh:mm:ss[.xxx]" format',
|
|
260
|
+
type = lambda f: self._is_timedelta(parser, f),
|
|
261
|
+
default = timedelta(0))
|
|
257
262
|
|
|
258
263
|
# Segment
|
|
259
264
|
segment_parser = subparsers.add_parser(BMFPCommands.SEGMENT,
|
|
@@ -16,7 +16,7 @@ from batchmp.cli.base.bmp_dispatch import BatchMPDispatcher
|
|
|
16
16
|
from batchmp.cli.renamer.renamer_options import RenameArgParser, RenamerCommands
|
|
17
17
|
from batchmp.fstools.dirtools import DHandler
|
|
18
18
|
from batchmp.fstools.rename import Renamer
|
|
19
|
-
from batchmp.fstools.builders.fsprms import FSEntryParamsBase, FSEntryParamsExt, FSEntryParamsFlatten
|
|
19
|
+
from batchmp.fstools.builders.fsprms import FSEntryParamsBase, FSEntryParamsExt, FSEntryParamsFlatten, FSEntryParamsOrganize
|
|
20
20
|
from batchmp.fstools.builders.fsb import FSEntryBuilderBase
|
|
21
21
|
|
|
22
22
|
class RenameDispatcher(BatchMPDispatcher):
|
|
@@ -61,6 +61,9 @@ class RenameDispatcher(BatchMPDispatcher):
|
|
|
61
61
|
elif args['sub_cmd'] == RenamerCommands.STATS:
|
|
62
62
|
self.stats(args)
|
|
63
63
|
|
|
64
|
+
elif args['sub_cmd'] == RenamerCommands.ORGANIZE:
|
|
65
|
+
self.organize(args)
|
|
66
|
+
|
|
64
67
|
else:
|
|
65
68
|
print('Nothing to dispatch')
|
|
66
69
|
return False
|
|
@@ -69,8 +72,13 @@ class RenameDispatcher(BatchMPDispatcher):
|
|
|
69
72
|
|
|
70
73
|
# Dispatched Methods
|
|
71
74
|
def print_dir(self, args):
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
# Check if organize view is requested
|
|
76
|
+
if args.get('by'):
|
|
77
|
+
fs_entry_params = FSEntryParamsOrganize(args)
|
|
78
|
+
DHandler.print_organized_view(fs_entry_params)
|
|
79
|
+
else:
|
|
80
|
+
fs_entry_params = FSEntryParamsBase(args)
|
|
81
|
+
DHandler.print_dir(fs_entry_params)
|
|
74
82
|
|
|
75
83
|
def stats(self, args):
|
|
76
84
|
fs_entry_params = FSEntryParamsBase(args)
|
|
@@ -117,7 +125,10 @@ class RenameDispatcher(BatchMPDispatcher):
|
|
|
117
125
|
fs_entry_params = FSEntryParamsExt(args)
|
|
118
126
|
Renamer.delete(fs_entry_params)
|
|
119
127
|
|
|
120
|
-
|
|
128
|
+
def organize(self, args):
|
|
129
|
+
fs_entry_params = FSEntryParamsOrganize(args)
|
|
130
|
+
DHandler.organize(fs_entry_params)
|
|
131
|
+
|
|
121
132
|
def main():
|
|
122
133
|
''' Renamer entry point
|
|
123
134
|
'''
|
|
@@ -74,6 +74,7 @@ class RenamerCommands(BatchMPBaseCommands):
|
|
|
74
74
|
FLATTEN = 'flatten'
|
|
75
75
|
DELETE = 'delete'
|
|
76
76
|
STATS = 'stats'
|
|
77
|
+
ORGANIZE = 'organize'
|
|
77
78
|
|
|
78
79
|
@classmethod
|
|
79
80
|
def commands_meta(cls):
|
|
@@ -89,7 +90,8 @@ class RenamerCommands(BatchMPBaseCommands):
|
|
|
89
90
|
'{}, '.format(cls.DELETE),
|
|
90
91
|
'{}, '.format(cls.STATS),
|
|
91
92
|
'{}, '.format(cls.INFO),
|
|
92
|
-
'{}'.format(cls.VERSION),
|
|
93
|
+
'{}, '.format(cls.VERSION),
|
|
94
|
+
'{}' .format(cls.ORGANIZE),
|
|
93
95
|
'}'))
|
|
94
96
|
|
|
95
97
|
|
|
@@ -142,6 +144,14 @@ class RenameArgParser(BatchMPArgParser):
|
|
|
142
144
|
print_parser.add_argument('-ss', '--show-size', dest = 'show_size',
|
|
143
145
|
help ='Show files size',
|
|
144
146
|
action = 'store_true')
|
|
147
|
+
print_parser.add_argument('-b', '--by', dest = 'by',
|
|
148
|
+
help = 'Show organized virtual view by type or date',
|
|
149
|
+
type = str,
|
|
150
|
+
choices = ['type', 'date'])
|
|
151
|
+
print_parser.add_argument('-df', '--date-format', dest = 'date_format',
|
|
152
|
+
help = 'Date format for subdirectories when using -b date (e.g., %%Y/%%m)',
|
|
153
|
+
type = str,
|
|
154
|
+
default = '%Y-%m-%d')
|
|
145
155
|
|
|
146
156
|
# Stats
|
|
147
157
|
stats_parser = subparsers.add_parser(RenamerCommands.STATS,
|
|
@@ -299,6 +309,27 @@ class RenameArgParser(BatchMPArgParser):
|
|
|
299
309
|
self._add_arg_display_curent_state_mode(delete_parser)
|
|
300
310
|
|
|
301
311
|
|
|
312
|
+
# Organize
|
|
313
|
+
organize_parser = subparsers.add_parser(RenamerCommands.ORGANIZE,
|
|
314
|
+
description='Organize selected files into directories by specified attributes',
|
|
315
|
+
formatter_class=BatchMPHelpFormatter)
|
|
316
|
+
organize_parser.add_argument('-b', '--by', dest='by',
|
|
317
|
+
help='Organization strategy: by type or by date',
|
|
318
|
+
type=str,
|
|
319
|
+
choices=['type', 'date'],
|
|
320
|
+
required=True)
|
|
321
|
+
organize_parser.add_argument('-df', '--date-format', dest='date_format',
|
|
322
|
+
help='Date format for subdirectories (e.g., %%Y/%%m)',
|
|
323
|
+
type=str,
|
|
324
|
+
default='%Y-%m-%d')
|
|
325
|
+
organize_parser.add_argument('-td', '--target-dir', dest='target_dir',
|
|
326
|
+
help='Target directory to organize files into',
|
|
327
|
+
type=str)
|
|
328
|
+
_add_include_mode_group(organize_parser)
|
|
329
|
+
self._add_arg_display_curent_state_mode(organize_parser)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|
|
302
333
|
# Args Checking
|
|
303
334
|
def default_command(self, args, parser):
|
|
304
335
|
args['sub_cmd'] = RenamerCommands.PRINT
|
|
@@ -99,7 +99,7 @@ class CueSplitterTask(ConvertorTask):
|
|
|
99
99
|
with temp_dir() as tmp_dir:
|
|
100
100
|
# prepare the tmp output path
|
|
101
101
|
conv_fname = '{0:02d} {1}'.format(self.track_number, self.track_title)
|
|
102
|
-
conv_fname = re.sub('[^\w\-_\. ]', '_', conv_fname)
|
|
102
|
+
conv_fname = re.sub(r'[^\w\-_\. ]', '_', conv_fname)
|
|
103
103
|
conv_fname = ''.join((conv_fname, self.target_format))
|
|
104
104
|
conv_fpath = os.path.join(tmp_dir, conv_fname)
|
|
105
105
|
|
|
@@ -198,12 +198,12 @@ class CueSplitter(FFMPRunner):
|
|
|
198
198
|
if cue_sheet.rem:
|
|
199
199
|
for rem_item in cue_sheet.rem:
|
|
200
200
|
if not tag_holder.year:
|
|
201
|
-
match = re.match('DATE.+(\d{4})', rem_item)
|
|
201
|
+
match = re.match(r'DATE.+(\d{4})', rem_item)
|
|
202
202
|
if match:
|
|
203
203
|
tag_holder.year = match.group(1)
|
|
204
204
|
continue
|
|
205
205
|
if not tag_holder.genre:
|
|
206
|
-
match = re.match('GENRE\s+(.+)$', rem_item)
|
|
206
|
+
match = re.match(r'GENRE\s+(.+)$', rem_item)
|
|
207
207
|
if match:
|
|
208
208
|
tag_holder.genre = match.group(1)
|
|
209
209
|
tag_holder.comments = ', '.join(cue_sheet.rem)
|
|
@@ -19,6 +19,7 @@ from batchmp.commons.utils import temp_dir
|
|
|
19
19
|
from batchmp.ffmptools.ffrunner import FFMPRunner, FFMPRunnerTask, LogLevel
|
|
20
20
|
from batchmp.commons.taskprocessor import TaskResult
|
|
21
21
|
from batchmp.ffmptools.ffcommands.cmdopt import FFmpegCommands, FFmpegBitMaskOptions
|
|
22
|
+
from batchmp.ffmptools.ffcommands.segment import Segmenter
|
|
22
23
|
from batchmp.commons.utils import (
|
|
23
24
|
timed,
|
|
24
25
|
run_cmd,
|
|
@@ -30,10 +31,11 @@ class FragmenterTask(FFMPRunnerTask):
|
|
|
30
31
|
'''
|
|
31
32
|
def __init__(self, fpath, target_dir, log_level,
|
|
32
33
|
ff_general_options, ff_other_options, preserve_metadata,
|
|
33
|
-
fragment_starttime, fragment_duration):
|
|
34
|
+
fragment_starttime, fragment_duration, fragment_trim):
|
|
34
35
|
|
|
35
36
|
self.fragment_starttime = fragment_starttime
|
|
36
37
|
self.fragment_duration = fragment_duration
|
|
38
|
+
self.fragment_trim = fragment_trim
|
|
37
39
|
|
|
38
40
|
super().__init__(fpath, target_dir, log_level,
|
|
39
41
|
ff_general_options, ff_other_options, preserve_metadata)
|
|
@@ -42,6 +44,9 @@ class FragmenterTask(FFMPRunnerTask):
|
|
|
42
44
|
def ff_cmd(self):
|
|
43
45
|
''' Fragment command builder
|
|
44
46
|
'''
|
|
47
|
+
if self.fragment_trim:
|
|
48
|
+
media_duration = Segmenter._media_duration(self.fpath)
|
|
49
|
+
self.fragment_duration = media_duration - self.fragment_trim - self.fragment_starttime
|
|
45
50
|
return ''.join((super().ff_cmd,
|
|
46
51
|
' -ss {}'.format(self.fragment_starttime),
|
|
47
52
|
' -t {}'.format(self.fragment_duration)
|
|
@@ -65,6 +70,11 @@ class FragmenterTask(FFMPRunnerTask):
|
|
|
65
70
|
|
|
66
71
|
# run ffmpeg command as a subprocess
|
|
67
72
|
try:
|
|
73
|
+
if self.fragment_duration < 0:
|
|
74
|
+
raise CmdProcessingError('A problem while processing media file {0}: \
|
|
75
|
+
Negative media duration {1}s, check your input parameters to add up correctly'\
|
|
76
|
+
.format(self.fpath, int(self.fragment_duration)))
|
|
77
|
+
|
|
68
78
|
_, task_elapsed = run_cmd(p_in)
|
|
69
79
|
task_result.add_task_step_duration(task_elapsed)
|
|
70
80
|
except CmdProcessingError as e:
|
|
@@ -87,7 +97,7 @@ class FragmenterTask(FFMPRunnerTask):
|
|
|
87
97
|
|
|
88
98
|
class Fragmenter(FFMPRunner):
|
|
89
99
|
def fragment(self, ff_entry_params,
|
|
90
|
-
fragment_starttime = None, fragment_duration = None):
|
|
100
|
+
fragment_starttime = None, fragment_duration = None, fragment_trim = None):
|
|
91
101
|
|
|
92
102
|
''' Fragment media file by specified starttime & duration
|
|
93
103
|
'''
|
|
@@ -99,7 +109,7 @@ class Fragmenter(FFMPRunner):
|
|
|
99
109
|
# build tasks
|
|
100
110
|
tasks_params = [(media_file, target_dir_path, ff_entry_params.log_level,
|
|
101
111
|
ff_entry_params.ff_general_options, ff_entry_params.ff_other_options, ff_entry_params.preserve_metadata,
|
|
102
|
-
fragment_starttime, fragment_duration)
|
|
112
|
+
fragment_starttime, fragment_duration, fragment_trim)
|
|
103
113
|
for media_file, target_dir_path in zip(media_files, target_dirs)]
|
|
104
114
|
for task_param in tasks_params:
|
|
105
115
|
task = FragmenterTask(*task_param)
|
|
@@ -120,15 +120,15 @@ class Segmenter(FFMPRunner):
|
|
|
120
120
|
'''
|
|
121
121
|
tasks = []
|
|
122
122
|
if segment_size_MB or segment_length_secs:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
# if segment_length_secs:
|
|
124
|
+
# # here need to determine media length
|
|
125
|
+
# pass_filter = lambda fpath: self._media_duration(fpath) > segment_length_secs
|
|
126
|
+
# elif segment_size_MB:
|
|
127
|
+
# # simple media selection by size
|
|
128
|
+
# pass_filter = lambda fpath: FFH.ffmpeg_supported_media(fpath) and (self._media_size_MB(fpath) > segment_size_MB)
|
|
129
129
|
|
|
130
130
|
ff_entry_params.target_dir_prefix = 'segmented'
|
|
131
|
-
media_files, target_dirs = self._prepare_files(ff_entry_params
|
|
131
|
+
media_files, target_dirs = self._prepare_files(ff_entry_params)
|
|
132
132
|
|
|
133
133
|
# build tasks
|
|
134
134
|
tasks_params = [(media_file, target_dir_path, ff_entry_params.log_level,
|
|
@@ -239,8 +239,8 @@ class FFH:
|
|
|
239
239
|
except CmdProcessingError as e:
|
|
240
240
|
return None
|
|
241
241
|
else:
|
|
242
|
-
silence_starts = re.findall('(?<=silence_start:)(?:\D*)(\d*\.?\d+)', output)
|
|
243
|
-
silence_ends = re.findall('(?<=silence_end:)(?:\D*)(\d*\.?\d+)', output)
|
|
242
|
+
silence_starts = re.findall(r'(?<=silence_start:)(?:\D*)(\d*\.?\d+)', output)
|
|
243
|
+
silence_ends = re.findall(r'(?<=silence_end:)(?:\D*)(\d*\.?\d+)', output)
|
|
244
244
|
|
|
245
245
|
SilenceEntry = namedtuple('SilenceEntry', ['silence_start', 'silence_end'])
|
|
246
246
|
silence_entries = []
|
|
@@ -250,7 +250,7 @@ class FFH:
|
|
|
250
250
|
if len(silence_entries) < len(silence_starts):
|
|
251
251
|
# matched non-balanced silence at the end
|
|
252
252
|
# try to parse output audio duration and use it as the silence_end value
|
|
253
|
-
found = re.findall('(?<=Duration:)(?:\D*)([\d:\.]*)', output)
|
|
253
|
+
found = re.findall(r'(?<=Duration:)(?:\D*)([\d:\.]*)', output)
|
|
254
254
|
if found:
|
|
255
255
|
duration = MiscHelpers.time_delta(found[0]).total_seconds()
|
|
256
256
|
else:
|
|
@@ -282,12 +282,12 @@ class FFH:
|
|
|
282
282
|
mean_volume = max_volume = 0
|
|
283
283
|
|
|
284
284
|
# mean volume
|
|
285
|
-
found = re.findall('(?<=mean_volume:)(?:\D*)(\d*\.?\d+)', output)
|
|
285
|
+
found = re.findall(r'(?<=mean_volume:)(?:\D*)(\d*\.?\d+)', output)
|
|
286
286
|
if found:
|
|
287
287
|
mean_volume = float(found[0])
|
|
288
288
|
|
|
289
289
|
# max volume
|
|
290
|
-
found = re.findall('(?<=max_volume:)(?:\D*)(\d*\.?\d+)', output)
|
|
290
|
+
found = re.findall(r'(?<=max_volume:)(?:\D*)(\d*\.?\d+)', output)
|
|
291
291
|
if found:
|
|
292
292
|
max_volume = float(found[0])
|
|
293
293
|
|