batchmp 1.0__tar.gz → 1.4.2__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.
Files changed (78) hide show
  1. {batchmp-1.0/batchmp.egg-info → batchmp-1.4.2}/PKG-INFO +108 -16
  2. batchmp-1.0/PKG-INFO → batchmp-1.4.2/README.md +86 -41
  3. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/base/bmp_dispatch.py +2 -2
  4. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/base/bmp_options.py +1 -1
  5. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/bmfp/bmfp_dispatch.py +3 -1
  6. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/bmfp/bmfp_options.py +8 -3
  7. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/renamer/renamer_dispatch.py +15 -4
  8. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/renamer/renamer_options.py +32 -1
  9. {batchmp-1.0 → batchmp-1.4.2}/batchmp/commons/utils.py +10 -0
  10. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/ffcommands/cuesplit.py +3 -3
  11. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/ffcommands/fragment.py +13 -3
  12. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/ffcommands/segment.py +7 -7
  13. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/ffutils.py +5 -5
  14. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/utils/cueparse.py +2 -2
  15. {batchmp-1.0 → batchmp-1.4.2}/batchmp/fstools/builders/fsb.py +60 -3
  16. {batchmp-1.0 → batchmp-1.4.2}/batchmp/fstools/builders/fsprms.py +10 -3
  17. {batchmp-1.0 → batchmp-1.4.2}/batchmp/fstools/dirtools.py +78 -9
  18. {batchmp-1.0 → batchmp-1.4.2}/batchmp/fstools/rename.py +11 -0
  19. batchmp-1.4.2/batchmp/fstools/virtual_organizer.py +301 -0
  20. {batchmp-1.0 → batchmp-1.4.2}/batchmp/fstools/walker.py +2 -2
  21. {batchmp-1.0 → batchmp-1.4.2}/batchmp/tags/handlers/mtghandler.py +1 -2
  22. {batchmp-1.0 → batchmp-1.4.2}/batchmp/tags/processors/basetp.py +1 -1
  23. batchmp-1.0/README.md → batchmp-1.4.2/batchmp.egg-info/PKG-INFO +133 -12
  24. {batchmp-1.0 → batchmp-1.4.2}/batchmp.egg-info/SOURCES.txt +2 -2
  25. batchmp-1.4.2/batchmp.egg-info/requires.txt +8 -0
  26. batchmp-1.4.2/pyproject.toml +3 -0
  27. {batchmp-1.0 → batchmp-1.4.2}/setup.py +9 -5
  28. batchmp-1.0/batchmp/tags/extern/mediafile.py +0 -1889
  29. batchmp-1.0/batchmp/tags/processors/__init__.py +0 -0
  30. batchmp-1.0/batchmp.egg-info/requires.txt +0 -2
  31. {batchmp-1.0 → batchmp-1.4.2}/LICENSE +0 -0
  32. {batchmp-1.0 → batchmp-1.4.2}/batchmp/__init__.py +0 -0
  33. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/__init__.py +0 -0
  34. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/base/__init__.py +0 -0
  35. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/base/vchk.py +0 -0
  36. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/bmfp/__init__.py +0 -0
  37. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/renamer/__init__.py +0 -0
  38. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/tagger/__init__.py +0 -0
  39. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/tagger/tagger_dispatch.py +0 -0
  40. {batchmp-1.0 → batchmp-1.4.2}/batchmp/cli/tagger/tagger_options.py +0 -0
  41. {batchmp-1.0 → batchmp-1.4.2}/batchmp/commons/__init__.py +0 -0
  42. {batchmp-1.0 → batchmp-1.4.2}/batchmp/commons/chainedhandler.py +0 -0
  43. {batchmp-1.0 → batchmp-1.4.2}/batchmp/commons/descriptors.py +0 -0
  44. {batchmp-1.0 → batchmp-1.4.2}/batchmp/commons/progressbar.py +0 -0
  45. {batchmp-1.0 → batchmp-1.4.2}/batchmp/commons/taskprocessor.py +0 -0
  46. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/__init__.py +0 -0
  47. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/ffcommands/__init__.py +0 -0
  48. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/ffcommands/cmdopt.py +0 -0
  49. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/ffcommands/convert.py +0 -0
  50. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/ffcommands/denoise.py +0 -0
  51. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/ffcommands/normalize_peak.py +0 -0
  52. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/ffcommands/silencesplit.py +0 -0
  53. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/ffrunner.py +0 -0
  54. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/processors/__init__.py +0 -0
  55. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/processors/basefp.py +0 -0
  56. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/processors/ffentry.py +0 -0
  57. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/utils/__init__.py +0 -0
  58. {batchmp-1.0 → batchmp-1.4.2}/batchmp/ffmptools/utils/cuesheet.py +0 -0
  59. {batchmp-1.0 → batchmp-1.4.2}/batchmp/fstools/__init__.py +0 -0
  60. {batchmp-1.0 → batchmp-1.4.2}/batchmp/fstools/builders/__init__.py +0 -0
  61. {batchmp-1.0 → batchmp-1.4.2}/batchmp/fstools/builders/fsentry.py +0 -0
  62. {batchmp-1.0 → batchmp-1.4.2}/batchmp/fstools/fsutils.py +0 -0
  63. {batchmp-1.0 → batchmp-1.4.2}/batchmp/tags/__init__.py +0 -0
  64. {batchmp-1.0/batchmp/tags/extern → batchmp-1.4.2/batchmp/tags/handlers}/__init__.py +0 -0
  65. {batchmp-1.0 → batchmp-1.4.2}/batchmp/tags/handlers/basehandler.py +0 -0
  66. {batchmp-1.0 → batchmp-1.4.2}/batchmp/tags/handlers/ffmphandler.py +0 -0
  67. {batchmp-1.0/batchmp/tags/handlers → batchmp-1.4.2/batchmp/tags/handlers/ffmphandlers}/__init__.py +0 -0
  68. {batchmp-1.0 → batchmp-1.4.2}/batchmp/tags/handlers/ffmphandlers/base.py +0 -0
  69. {batchmp-1.0 → batchmp-1.4.2}/batchmp/tags/handlers/pmhandler.py +0 -0
  70. {batchmp-1.0 → batchmp-1.4.2}/batchmp/tags/handlers/tagsholder.py +0 -0
  71. {batchmp-1.0/batchmp/tags/handlers/ffmphandlers → batchmp-1.4.2/batchmp/tags/output}/__init__.py +0 -0
  72. {batchmp-1.0 → batchmp-1.4.2}/batchmp/tags/output/formatters.py +0 -0
  73. {batchmp-1.0/batchmp/tags/output → batchmp-1.4.2/batchmp/tags/processors}/__init__.py +0 -0
  74. {batchmp-1.0 → batchmp-1.4.2}/batchmp.egg-info/dependency_links.txt +0 -0
  75. {batchmp-1.0 → batchmp-1.4.2}/batchmp.egg-info/entry_points.txt +0 -0
  76. {batchmp-1.0 → batchmp-1.4.2}/batchmp.egg-info/top_level.txt +0 -0
  77. {batchmp-1.0 → batchmp-1.4.2}/batchmp.egg-info/zip-safe +0 -0
  78. {batchmp-1.0 → batchmp-1.4.2}/setup.cfg +0 -0
@@ -1,14 +1,13 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: batchmp
3
- Version: 1.0
3
+ Version: 1.4.2
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: GNU General Public License v2 (GPLv2)
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,25 @@ 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
+ Provides-Extra: test
33
+ Requires-Dist: pytest; extra == "test"
34
+ Requires-Dist: pytest-mock; extra == "test"
35
+ Dynamic: author
36
+ Dynamic: author-email
37
+ Dynamic: classifier
38
+ Dynamic: description
39
+ Dynamic: description-content-type
40
+ Dynamic: home-page
41
+ Dynamic: keywords
42
+ Dynamic: license
43
+ Dynamic: license-file
44
+ Dynamic: provides-extra
45
+ Dynamic: requires-dist
46
+ Dynamic: summary
29
47
 
30
48
 
31
49
  **Status:**
@@ -39,10 +57,11 @@ A rainy weekends project under occasional development :)
39
57
  - latest from source repository: `$ pip install git+https://github.com/akpw/batch-mp-tools.git`
40
58
 
41
59
  #### Blogs:
42
- - [Practical BatchMP](http://www.akpdev.com/tags.html#BatchMP+Tools)
43
- - [BatchMP Tools Tutorial](http://www.akpdev.com/articles/2015/04/10/batchmp-tutorial-part-i.html)
44
- - [The BatchMP Tools Project](http://www.akpdev.com/articles/2015/03/21/the-batchmp-project.html)
45
- - [Parallel batch media processing with FFmpeg and Python](http://www.akpdev.com/articles/2014/11/24/batch-media-processing-ffmpeg-python.html)
60
+ - [Practical BatchMP](https://akpw.github.io//tags.html#BatchMP+Tools)
61
+ - [Renamer Organize & Virtual Views](https://akpw.github.io/articles/2025/09/22/Print-and-Organize.html)
62
+ - [BatchMP Tools Tutorial](https://akpw.github.io//articles/2015/04/10/batchmp-tutorial-part-i.html)
63
+ - [The BatchMP Tools Project](https://akpw.github.io//articles/2015/03/21/the-batchmp-project.html)
64
+ - [Parallel batch media processing with FFmpeg and Python](https://akpw.github.io//articles/2014/11/24/batch-media-processing-ffmpeg-python.html)
46
65
 
47
66
  ## Description
48
67
 
@@ -62,7 +81,7 @@ By default the tools always visualize targeted changes (whenever possible) befor
62
81
 
63
82
  A little bit more details on each utility:
64
83
 
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, and cleaning up non-media files.
84
+ [**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
85
  At its simplest, Renamer can be used to print out the content of current directory:
67
86
  ```
68
87
  $ renamer
@@ -91,6 +110,37 @@ For multi-level indexing of all M4A files in all sub-directories of the current
91
110
  ```
92
111
  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
112
 
113
+ For organizing files by media type:
114
+ ```
115
+ $ renamer organize -b type
116
+ ~/Downloads
117
+ |- image/
118
+ |- photo1.jpg
119
+ |- screenshot.png
120
+ |- video/
121
+ |- movie.mp4
122
+ |- audio/
123
+ |- song.mp3
124
+
125
+ Proceed? [y/n]:
126
+ ```
127
+
128
+ Or preview how files would look organized by date without moving them:
129
+ ```
130
+ $ renamer print -b date --date-format "%Y/%m"
131
+ Virtual view by date:
132
+ ~/Downloads
133
+ |- 2025/
134
+ |- 01/
135
+ |- document.pdf
136
+ |- photo.jpg
137
+ |- 02/
138
+ |- video.mp4
139
+ ```
140
+ For more detailed examples and advanced organize / virtual views functionality, see:
141
+ - [Renamer Organize & Virtual Views](https://akpw.github.io/articles/2025/09/22/Print-and-Organize.html)
142
+ - [BatchMP Tools Tutorial, Part II: renaming files with renamer](https://akpw.github.io/articles/2015/04/11/batchmp-tutorial-part-ii.html)
143
+ - Other related posts in [Practical BatchMP](https://akpw.github.io//tags.html#BatchMP+Tools)
94
144
 
95
145
 
96
146
  [**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:
@@ -117,6 +167,11 @@ Sequential indexing is supported as well using the `-sq` switch. An important d
117
167
  ```
118
168
  The commands above show some of the available global options: `-r` for recursion into nested folders and `-in` to select media files. In the example above just one file was selected (for the sake of output brevity), which also could be achived via using `-f` for the file source mode.
119
169
 
170
+ For more practical examples, see:
171
+ - [BatchMP Tools Tutorial Part III: setting tags and artwork with tagger](https://akpw.github.io/articles/2015/04/12/batchmp-tutorial-part-iii.html)
172
+ - Other related posts in [Practical BatchMP](https://akpw.github.io//tags.html#BatchMP+Tools)
173
+
174
+
120
175
 
121
176
  [**BMFP**](https://github.com/akpw/batch-mp-tools/blob/master/README.md#bmfp-requires-ffmpeg) is all about efficient media content processing, such as conversion between various formats, normalizing sound volume, segmenting / fragmenting media files, denoising audio, detaching individual audio / video streams, etc. As processing media files can typically be resource consuming, BMFP is designed to take advantage of multi-core processors. By default, it automatically breaks up jobs into individual tasks that are then run as separate processes on available CPU cores.
122
177
  **BMFP is built on top of [FFmpeg](http://ffmpeg.org/download.html), which needs to be installed and available in the command line**. BMFP can be thought of as a batch FFmpeg runner, intended to make common uses of FFmpeg easy while not restricting its full power.
@@ -164,7 +219,10 @@ To check on the result, lets's just use the [tagger's](https://github.com/akpw/b
164
219
  ```
165
220
  From a brief glance, all looks OK. BMFP used FFmpeg to do the actual conversion, while taking care of all other things like preserving tags / artwork, etc.
166
221
 
167
- I will follow up with more examples and common use-cases in future blogs.
222
+ For more practical examples, see:
223
+ - [BatchMP Tools Tutorial, splitting a long media file with bmfp](https://akpw.github.io/articles/2015/04/10/batchmp-tutorial-part-i.html)
224
+ - Other related posts in [Practical BatchMP](https://akpw.github.io//tags.html#BatchMP+Tools)
225
+
168
226
 
169
227
 
170
228
  ## Brief Description of CLI Commands (use -h to expand on details for individual commands)
@@ -178,7 +236,9 @@ I will follow up with more examples and common use-cases in future blogs.
178
236
  . display sorting:
179
237
  .. by size/date, ascending/descending
180
238
  . action commands:
181
- .. print Prints source directory
239
+ .. print Prints source directory. Enhanced with virtual organization views:
240
+ $ renamer print -b type # Preview organize by media type
241
+ $ renamer print -b date -df "%Y/%m" # Preview organize by date
182
242
  .. flatten Flatten all folders below target level, moving the
183
243
  files up at the target level. By default, deletes all empty flattened folders
184
244
  .. index Adds index to files and directories names
@@ -191,6 +251,10 @@ I will follow up with more examples and common use-cases in future blogs.
191
251
  .. remove Removes n characters from files and directories names
192
252
  .. capitalize Capitalizes words in files / directories names
193
253
  .. delete Delete selected files and directories
254
+ .. organize Organizes files into subdirectories based on their attributes:
255
+ $ renamer organize -b type # By media type (image/, video/, audio/)
256
+ $ renamer organize -b date -df "%Y-%m" # By date (2025-01/, 2025-02/)
257
+ $ renamer organize -b date -df "%Y/%m" -td ~/Sorted # To target directory
194
258
 
195
259
  Usage: renamer [-h] [-d DIR] [-f FILE] [Global Options] {Commands} [Commands Options]
196
260
  Global Options:
@@ -215,7 +279,7 @@ I will follow up with more examples and common use-cases in future blogs.
215
279
  [-q, --quiet] Do not visualise changes / show messages during processing
216
280
 
217
281
  Commands:
218
- {print, index, add_date, add_text, remove, replace, capitalize, flatten, delete, version, info}
282
+ {print, index, add_date, add_text, remove, replace, capitalize, flatten, delete, organize, version, info}
219
283
  $ renamer {command} -h #run this for detailed help on individual commands
220
284
 
221
285
  ### tagger
@@ -324,7 +388,7 @@ Support via FFmpeg: 'AVI', 'FLV', 'MKV', 'MKA'
324
388
  (shows hidden files excluded by default)
325
389
 
326
390
  Target output Directory Target output directory. When omitted, will be
327
- [-td, --target-dir] automatically created at the parent level of
391
+ [-td, --target-dir] automatically created inside the parent level of
328
392
  the input source. For recursive processing,
329
393
  the processed files directory structure there
330
394
  will be the same as for the original files.
@@ -346,10 +410,38 @@ Support via FFmpeg: 'AVI', 'FLV', 'MKV', 'MKA'
346
410
 
347
411
 
348
412
  ## Installing Development version
349
- - Clone the repo, then run: `$ python -m pip install .`
413
+ - Clone the repo, create a virtual environment, and activate it:
414
+ ```bash
415
+ $ python3 -m venv .venv
416
+ $ source .venv/bin/activate
417
+ ```
418
+ - Install the project in editable mode:
419
+ ```bash
420
+ $ pip install -e .
421
+ ```
422
+
423
+ ## Running Tests
424
+
425
+ To run the test suite, first ensure you have installed the development dependencies:
426
+ ```bash
427
+ $ pip install -e ".[test]"
428
+ ```
350
429
 
351
- **Running Tests**
352
- - Run via: `$ python setup.py test`
430
+ **Using pytest (recommended):**
431
+ ```bash
432
+ $ pytest -v --tb=short # Run all tests with verbose output
433
+ $ pytest tests/ # Run all tests
434
+ $ pytest tests/fs/test_fs_organize.py # Test organize functionality
435
+ $ pytest tests/fs/test_fsutils.py # Test core filesystem utilities
436
+ $ pytest tests/cli/test_renamer_cli.py # Test renamer CLI
437
+ $ pytest -k "test_organize" # Run tests matching pattern
438
+ ```
439
+
440
+ **Using unittest (fallback):**
441
+ ```bash
442
+ $ python -m unittest discover tests -v # Run all tests with verbose output
443
+ $ python -m unittest tests.fs.test_fs_organize # Run specific test module
444
+ ```
353
445
 
354
446
 
355
447
 
@@ -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](http://www.akpdev.com/tags.html#BatchMP+Tools)
43
- - [BatchMP Tools Tutorial](http://www.akpdev.com/articles/2015/04/10/batchmp-tutorial-part-i.html)
44
- - [The BatchMP Tools Project](http://www.akpdev.com/articles/2015/03/21/the-batchmp-project.html)
45
- - [Parallel batch media processing with FFmpeg and Python](http://www.akpdev.com/articles/2014/11/24/batch-media-processing-ffmpeg-python.html)
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, and cleaning up non-media files.
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,37 @@ 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
+ For more detailed examples and advanced organize / virtual views functionality, see:
94
+ - [Renamer Organize & Virtual Views](https://akpw.github.io/articles/2025/09/22/Print-and-Organize.html)
95
+ - [BatchMP Tools Tutorial, Part II: renaming files with renamer](https://akpw.github.io/articles/2015/04/11/batchmp-tutorial-part-ii.html)
96
+ - Other related posts in [Practical BatchMP](https://akpw.github.io//tags.html#BatchMP+Tools)
94
97
 
95
98
 
96
99
  [**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:
@@ -117,6 +120,11 @@ Sequential indexing is supported as well using the `-sq` switch. An important d
117
120
  ```
118
121
  The commands above show some of the available global options: `-r` for recursion into nested folders and `-in` to select media files. In the example above just one file was selected (for the sake of output brevity), which also could be achived via using `-f` for the file source mode.
119
122
 
123
+ For more practical examples, see:
124
+ - [BatchMP Tools Tutorial Part III: setting tags and artwork with tagger](https://akpw.github.io/articles/2015/04/12/batchmp-tutorial-part-iii.html)
125
+ - Other related posts in [Practical BatchMP](https://akpw.github.io//tags.html#BatchMP+Tools)
126
+
127
+
120
128
 
121
129
  [**BMFP**](https://github.com/akpw/batch-mp-tools/blob/master/README.md#bmfp-requires-ffmpeg) is all about efficient media content processing, such as conversion between various formats, normalizing sound volume, segmenting / fragmenting media files, denoising audio, detaching individual audio / video streams, etc. As processing media files can typically be resource consuming, BMFP is designed to take advantage of multi-core processors. By default, it automatically breaks up jobs into individual tasks that are then run as separate processes on available CPU cores.
122
130
  **BMFP is built on top of [FFmpeg](http://ffmpeg.org/download.html), which needs to be installed and available in the command line**. BMFP can be thought of as a batch FFmpeg runner, intended to make common uses of FFmpeg easy while not restricting its full power.
@@ -164,7 +172,10 @@ To check on the result, lets's just use the [tagger's](https://github.com/akpw/b
164
172
  ```
165
173
  From a brief glance, all looks OK. BMFP used FFmpeg to do the actual conversion, while taking care of all other things like preserving tags / artwork, etc.
166
174
 
167
- I will follow up with more examples and common use-cases in future blogs.
175
+ For more practical examples, see:
176
+ - [BatchMP Tools Tutorial, splitting a long media file with bmfp](https://akpw.github.io/articles/2015/04/10/batchmp-tutorial-part-i.html)
177
+ - Other related posts in [Practical BatchMP](https://akpw.github.io//tags.html#BatchMP+Tools)
178
+
168
179
 
169
180
 
170
181
  ## Brief Description of CLI Commands (use -h to expand on details for individual commands)
@@ -178,7 +189,9 @@ I will follow up with more examples and common use-cases in future blogs.
178
189
  . display sorting:
179
190
  .. by size/date, ascending/descending
180
191
  . action commands:
181
- .. print Prints source directory
192
+ .. print Prints source directory. Enhanced with virtual organization views:
193
+ $ renamer print -b type # Preview organize by media type
194
+ $ renamer print -b date -df "%Y/%m" # Preview organize by date
182
195
  .. flatten Flatten all folders below target level, moving the
183
196
  files up at the target level. By default, deletes all empty flattened folders
184
197
  .. index Adds index to files and directories names
@@ -191,6 +204,10 @@ I will follow up with more examples and common use-cases in future blogs.
191
204
  .. remove Removes n characters from files and directories names
192
205
  .. capitalize Capitalizes words in files / directories names
193
206
  .. delete Delete selected files and directories
207
+ .. organize Organizes files into subdirectories based on their attributes:
208
+ $ renamer organize -b type # By media type (image/, video/, audio/)
209
+ $ renamer organize -b date -df "%Y-%m" # By date (2025-01/, 2025-02/)
210
+ $ renamer organize -b date -df "%Y/%m" -td ~/Sorted # To target directory
194
211
 
195
212
  Usage: renamer [-h] [-d DIR] [-f FILE] [Global Options] {Commands} [Commands Options]
196
213
  Global Options:
@@ -215,7 +232,7 @@ I will follow up with more examples and common use-cases in future blogs.
215
232
  [-q, --quiet] Do not visualise changes / show messages during processing
216
233
 
217
234
  Commands:
218
- {print, index, add_date, add_text, remove, replace, capitalize, flatten, delete, version, info}
235
+ {print, index, add_date, add_text, remove, replace, capitalize, flatten, delete, organize, version, info}
219
236
  $ renamer {command} -h #run this for detailed help on individual commands
220
237
 
221
238
  ### tagger
@@ -324,7 +341,7 @@ Support via FFmpeg: 'AVI', 'FLV', 'MKV', 'MKA'
324
341
  (shows hidden files excluded by default)
325
342
 
326
343
  Target output Directory Target output directory. When omitted, will be
327
- [-td, --target-dir] automatically created at the parent level of
344
+ [-td, --target-dir] automatically created inside the parent level of
328
345
  the input source. For recursive processing,
329
346
  the processed files directory structure there
330
347
  will be the same as for the original files.
@@ -346,10 +363,38 @@ Support via FFmpeg: 'AVI', 'FLV', 'MKV', 'MKA'
346
363
 
347
364
 
348
365
  ## Installing Development version
349
- - Clone the repo, then run: `$ python -m pip install .`
366
+ - Clone the repo, create a virtual environment, and activate it:
367
+ ```bash
368
+ $ python3 -m venv .venv
369
+ $ source .venv/bin/activate
370
+ ```
371
+ - Install the project in editable mode:
372
+ ```bash
373
+ $ pip install -e .
374
+ ```
375
+
376
+ ## Running Tests
377
+
378
+ To run the test suite, first ensure you have installed the development dependencies:
379
+ ```bash
380
+ $ pip install -e ".[test]"
381
+ ```
350
382
 
351
- **Running Tests**
352
- - Run via: `$ python setup.py test`
383
+ **Using pytest (recommended):**
384
+ ```bash
385
+ $ pytest -v --tb=short # Run all tests with verbose output
386
+ $ pytest tests/ # Run all tests
387
+ $ pytest tests/fs/test_fs_organize.py # Test organize functionality
388
+ $ pytest tests/fs/test_fsutils.py # Test core filesystem utilities
389
+ $ pytest tests/cli/test_renamer_cli.py # Test renamer CLI
390
+ $ pytest -k "test_organize" # Run tests matching pattern
391
+ ```
392
+
393
+ **Using unittest (fallback):**
394
+ ```bash
395
+ $ python -m unittest discover tests -v # Run all tests with verbose output
396
+ $ python -m unittest tests.fs.test_fs_organize # Run specific test module
397
+ ```
353
398
 
354
399
 
355
400
 
@@ -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 pkg_resources
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 = pkg_resources.require("batchmp")[0].version
46
+ version = metadata.version("batchmp")
47
47
  print('BatchMP tools version {}'.format(version))
48
48
 
49
49
  def print_info(self):
@@ -31,7 +31,7 @@
31
31
 
32
32
  import os, sys, string
33
33
  from argparse import ArgumentParser, HelpFormatter
34
- from distutils.util import strtobool
34
+ from batchmp.commons.utils import strtobool
35
35
  from urllib.parse import urlparse
36
36
  from batchmp.commons.utils import MiscHelpers
37
37
  from batchmp.fstools.fsutils import FSH
@@ -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 at the parent level of the input source. "
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', '--starttime', dest='fragment_starttime',
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
- fs_entry_params = FSEntryParamsBase(args)
73
- DHandler.print_dir(fs_entry_params)
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
@@ -61,6 +61,16 @@ def run_cmd(cmd, shell = False):
61
61
  return output
62
62
 
63
63
 
64
+ def strtobool(val):
65
+ val = str(val).lower()
66
+ if val in ('y', 'yes', 't', 'true', 'on', '1'):
67
+ return 1
68
+ elif val in ('n', 'no', 'f', 'false', 'off', '0'):
69
+ return 0
70
+ else:
71
+ raise ValueError("invalid truth value %r" % (val,))
72
+
73
+
64
74
  class MiscHelpers:
65
75
  @staticmethod
66
76
  def int_num_digits(num):
@@ -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)