batchmp 1.4__py3-none-any.whl

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 (68) hide show
  1. batchmp/__init__.py +0 -0
  2. batchmp/cli/__init__.py +0 -0
  3. batchmp/cli/base/__init__.py +0 -0
  4. batchmp/cli/base/bmp_dispatch.py +60 -0
  5. batchmp/cli/base/bmp_options.py +349 -0
  6. batchmp/cli/base/vchk.py +47 -0
  7. batchmp/cli/bmfp/__init__.py +0 -0
  8. batchmp/cli/bmfp/bmfp_dispatch.py +120 -0
  9. batchmp/cli/bmfp/bmfp_options.py +442 -0
  10. batchmp/cli/renamer/__init__.py +0 -0
  11. batchmp/cli/renamer/renamer_dispatch.py +135 -0
  12. batchmp/cli/renamer/renamer_options.py +355 -0
  13. batchmp/cli/tagger/__init__.py +0 -0
  14. batchmp/cli/tagger/tagger_dispatch.py +143 -0
  15. batchmp/cli/tagger/tagger_options.py +338 -0
  16. batchmp/commons/__init__.py +0 -0
  17. batchmp/commons/chainedhandler.py +102 -0
  18. batchmp/commons/descriptors.py +173 -0
  19. batchmp/commons/progressbar.py +154 -0
  20. batchmp/commons/taskprocessor.py +149 -0
  21. batchmp/commons/utils.py +194 -0
  22. batchmp/ffmptools/__init__.py +0 -0
  23. batchmp/ffmptools/ffcommands/__init__.py +0 -0
  24. batchmp/ffmptools/ffcommands/cmdopt.py +115 -0
  25. batchmp/ffmptools/ffcommands/convert.py +130 -0
  26. batchmp/ffmptools/ffcommands/cuesplit.py +223 -0
  27. batchmp/ffmptools/ffcommands/denoise.py +173 -0
  28. batchmp/ffmptools/ffcommands/fragment.py +121 -0
  29. batchmp/ffmptools/ffcommands/normalize_peak.py +135 -0
  30. batchmp/ffmptools/ffcommands/segment.py +157 -0
  31. batchmp/ffmptools/ffcommands/silencesplit.py +159 -0
  32. batchmp/ffmptools/ffrunner.py +189 -0
  33. batchmp/ffmptools/ffutils.py +300 -0
  34. batchmp/ffmptools/processors/__init__.py +0 -0
  35. batchmp/ffmptools/processors/basefp.py +92 -0
  36. batchmp/ffmptools/processors/ffentry.py +81 -0
  37. batchmp/ffmptools/utils/__init__.py +0 -0
  38. batchmp/ffmptools/utils/cueparse.py +227 -0
  39. batchmp/ffmptools/utils/cuesheet.py +239 -0
  40. batchmp/fstools/__init__.py +0 -0
  41. batchmp/fstools/builders/__init__.py +0 -0
  42. batchmp/fstools/builders/fsb.py +221 -0
  43. batchmp/fstools/builders/fsentry.py +60 -0
  44. batchmp/fstools/builders/fsprms.py +372 -0
  45. batchmp/fstools/dirtools.py +549 -0
  46. batchmp/fstools/fsutils.py +272 -0
  47. batchmp/fstools/rename.py +390 -0
  48. batchmp/fstools/walker.py +79 -0
  49. batchmp/tags/__init__.py +0 -0
  50. batchmp/tags/handlers/__init__.py +0 -0
  51. batchmp/tags/handlers/basehandler.py +99 -0
  52. batchmp/tags/handlers/ffmphandler.py +75 -0
  53. batchmp/tags/handlers/ffmphandlers/__init__.py +0 -0
  54. batchmp/tags/handlers/ffmphandlers/base.py +243 -0
  55. batchmp/tags/handlers/mtghandler.py +56 -0
  56. batchmp/tags/handlers/pmhandler.py +36 -0
  57. batchmp/tags/handlers/tagsholder.py +264 -0
  58. batchmp/tags/output/__init__.py +0 -0
  59. batchmp/tags/output/formatters.py +218 -0
  60. batchmp/tags/processors/__init__.py +0 -0
  61. batchmp/tags/processors/basetp.py +266 -0
  62. batchmp-1.4.dist-info/METADATA +422 -0
  63. batchmp-1.4.dist-info/RECORD +68 -0
  64. batchmp-1.4.dist-info/WHEEL +5 -0
  65. batchmp-1.4.dist-info/entry_points.txt +5 -0
  66. batchmp-1.4.dist-info/licenses/LICENSE +11 -0
  67. batchmp-1.4.dist-info/top_level.txt +1 -0
  68. batchmp-1.4.dist-info/zip-safe +1 -0
@@ -0,0 +1,266 @@
1
+ # coding=utf8
2
+ ## Copyright (c) 2014 Arseniy Kuznetsov
3
+ ##
4
+ ## This program is free software; you can redistribute it and/or
5
+ ## modify it under the terms of the GNU General Public License
6
+ ## as published by the Free Software Foundation; either version 2
7
+ ## of the License, or (at your option) any later version.
8
+ ##
9
+ ## This program is distributed in the hope that it will be useful,
10
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ ## GNU General Public License for more details.
13
+
14
+
15
+ import sys, os, re, string
16
+ from batchmp.fstools.dirtools import DHandler
17
+ from batchmp.fstools.walker import DWalker
18
+ from batchmp.fstools.rename import DirCounters
19
+ from batchmp.tags.output.formatters import TagOutputFormatter, OutputFormatType
20
+ from batchmp.tags.handlers.mtghandler import MutagenTagHandler
21
+ from batchmp.tags.handlers.ffmphandler import FFmpegTagHandler
22
+ from batchmp.tags.handlers.tagsholder import TagHolder
23
+ from batchmp.commons.progressbar import progress_bar, CmdProgressBarRefreshRate
24
+ from functools import partial
25
+
26
+ class BaseTagProcessor:
27
+ ''' Base Tag Processing
28
+ '''
29
+ def __init__(self):
30
+ self._handler = MutagenTagHandler() + FFmpegTagHandler()
31
+
32
+ @property
33
+ def handler(self):
34
+ return self._handler
35
+
36
+ def print_dir(self, fs_entry_params, format = None, show_stats = False):
37
+
38
+ ''' Prints tags in selected media files
39
+ '''
40
+ formatter = partial(TagOutputFormatter.tags_formatter,
41
+ format = format if format else OutputFormatType.COMPACT,
42
+ handler = self.handler,
43
+ show_stats = show_stats)
44
+
45
+ DHandler.print_dir(fs_entry_params,
46
+ formatter = formatter,
47
+ selected_files_description = 'media file')
48
+
49
+ def set_tags(self, fs_entry_params, total_files, total_dirs, tag_holder = None, tag_holder_builder = None):
50
+
51
+ ''' Set tags from tag_holder attributes
52
+ '''
53
+ if not tag_holder and not tag_holder_builder:
54
+ return
55
+
56
+ fcnt = 0
57
+ pass_filter = lambda fpath: self.handler.can_handle(fpath)
58
+
59
+ with progress_bar(refresh_rate = CmdProgressBarRefreshRate.FAST) as p_bar:
60
+ p_bar.info_msg = 'Setting tags in {} media files'.format(total_files)
61
+ for entry in DWalker.file_entries(fs_entry_params, pass_filter = pass_filter):
62
+ if tag_holder_builder:
63
+ tag_holder = tag_holder_builder(entry)
64
+
65
+ self.handler.copy_tags(tag_holder)
66
+ self.handler.save()
67
+ fcnt += 1
68
+
69
+ p_bar.progress += 100 / total_files
70
+
71
+
72
+ # print summary
73
+ if not fs_entry_params.quiet:
74
+ print('Set tags in {0} media file{1}'.format(fcnt, '' if fcnt == 1 else 's'))
75
+
76
+ def set_tags_visual(self, fs_entry_params,
77
+ diff_tags_only = False,
78
+ tag_holder = None,
79
+ tag_holder_builder = None,
80
+ reset_tag_holder_builder = None):
81
+
82
+ ''' Set tags from tag_holder attributes
83
+ Visualises changes before proceeding
84
+ '''
85
+ if not tag_holder and not tag_holder_builder:
86
+ return
87
+
88
+ if fs_entry_params.quiet:
89
+ proceed = True
90
+ total_files, total_dirs, _ = DHandler.dir_stats(fs_entry_params)
91
+ else:
92
+ # visualise changes to tags and proceed if confirmed
93
+ preformatter = partial(TagOutputFormatter.tags_formatter,
94
+ handler = self.handler,
95
+ show_stats = False,
96
+ tag_holder = tag_holder,
97
+ tag_holder_builder = tag_holder_builder,
98
+ diff_tags_only = diff_tags_only)
99
+
100
+ formatter = partial(TagOutputFormatter.tags_formatter,
101
+ handler = self.handler,
102
+ show_stats = False,
103
+ tag_holder = tag_holder,
104
+ tag_holder_builder = tag_holder_builder,
105
+ show_tag_holder_values = True,
106
+ diff_tags_only = diff_tags_only)
107
+
108
+ proceed, total_files, total_dirs = DHandler.visualise_changes(fs_entry_params,
109
+ preformatter = preformatter, formatter = formatter,
110
+ reset_formatters = reset_tag_holder_builder,
111
+ selected_files_description = 'media file')
112
+ if proceed:
113
+ if reset_tag_holder_builder:
114
+ reset_tag_holder_builder()
115
+
116
+ self.set_tags(fs_entry_params, total_files, total_dirs,
117
+ tag_holder = tag_holder,
118
+ tag_holder_builder = tag_holder_builder)
119
+
120
+ def copy_tags(self, fs_entry_params, diff_tags_only = False, tag_holder_path = None):
121
+
122
+ ''' Copies metadata (including artwork) from a tag_holder file
123
+ then applies to all selected media files
124
+ Visualises changes before proceeding
125
+ '''
126
+ if self.handler.can_handle(tag_holder_path):
127
+ tag_holder = TagHolder()
128
+ tag_holder.copy_tags(self.handler.tag_holder)
129
+ self.set_tags_visual(fs_entry_params,
130
+ diff_tags_only = diff_tags_only,
131
+ tag_holder = tag_holder)
132
+ else:
133
+ print('Can not handle tags holder: {}'.format(tag_holder_path))
134
+
135
+ def index(self, fs_entry_params,
136
+ diff_tags_only = False, start_from = 1):
137
+
138
+ ''' Indexes the tracks / tracktotal tags, per media files' respective directories
139
+ Visualises changes before proceeding
140
+ '''
141
+
142
+ counters = {}
143
+ def reset_counts():
144
+ counters.clear()
145
+
146
+ def tag_holder_builder(entry):
147
+ nonlocal counters
148
+
149
+ parent_dir = os.path.dirname(entry.realpath)
150
+ if not counters.get(parent_dir):
151
+ counters[parent_dir] = DirCounters(start_from, start_from, 0, 0)
152
+
153
+ tag_holder = TagHolder()
154
+ tag_holder.tracktotal = len(fs_entry_params.fnames)
155
+
156
+ tag_holder.track = counters[parent_dir].files_cnt
157
+ counters[parent_dir].files_cnt += 1
158
+
159
+ return tag_holder
160
+
161
+ self.set_tags_visual(fs_entry_params,
162
+ diff_tags_only = diff_tags_only,
163
+ tag_holder_builder = tag_holder_builder,
164
+ reset_tag_holder_builder = reset_counts)
165
+
166
+ def remove_tags(self, fs_entry_params,
167
+ tag_fields = None,
168
+ diff_tags_only = False):
169
+
170
+ ''' Removes metadata info from selected media files
171
+ Can remove all metadata, or metadata from specified fields
172
+ Visualises targeted changes before actual processing
173
+ '''
174
+ if tag_fields is None:
175
+ # remove all tags
176
+ tag_holder = TagHolder(copy_empty_vals = True)
177
+ else:
178
+ # remove specified tags
179
+ tag_holder = TagHolder(nullable_fields = tag_fields)
180
+
181
+ self.set_tags_visual(fs_entry_params,
182
+ diff_tags_only = diff_tags_only,
183
+ tag_holder = tag_holder)
184
+
185
+ def replace_tags(self, fs_entry_params,
186
+ diff_tags_only = False,
187
+ tag_fields = None,
188
+ ignore_case = False,
189
+ ind_str = None, replace_str = None):
190
+
191
+ ''' RegExp-based replace in specified fields
192
+ Visualises changes before proceeding
193
+ '''
194
+
195
+ if not (tag_fields and find_str):
196
+ return
197
+
198
+ flags = re.UNICODE
199
+ if ignore_case:
200
+ flags = flags | re.IGNORECASE
201
+ p = re.compile(find_str, flags)
202
+
203
+ def replace_transform(value):
204
+ match = p.search(value)
205
+ if match:
206
+ if replace_str is not None:
207
+ value = p.sub(replace_str, value)
208
+ else:
209
+ value = match.group()
210
+ return value
211
+
212
+ tag_holder = TagHolder(process_templates = False)
213
+ for tag_field in tag_fields:
214
+ setattr(tag_holder, tag_field, '${}'.format(tag_field))
215
+ tag_holder.template_processor_method = replace_transform
216
+
217
+ self.set_tags_visual(fs_entry_params,
218
+ diff_tags_only = diff_tags_only,
219
+ tag_holder = tag_holder)
220
+
221
+ def capitalize_tags(self, fs_entry_params,
222
+ diff_tags_only = False,
223
+ tag_fields = None):
224
+
225
+ ''' Capitalizes words in specified fields
226
+ Visualises changes before proceeding
227
+ '''
228
+
229
+ if not tag_fields:
230
+ return
231
+
232
+ def capitalize_transform(value):
233
+ return string.capwords(value)
234
+
235
+ tag_holder = TagHolder(process_templates = False)
236
+ for tag_field in tag_fields:
237
+ setattr(tag_holder, tag_field, '${}'.format(tag_field))
238
+ tag_holder.template_processor_method = capitalize_transform
239
+
240
+ self.set_tags_visual(fs_entry_params,
241
+ diff_tags_only = diff_tags_only,
242
+ tag_holder = tag_holder)
243
+
244
+ def detauch_art(self, fs_entry_params, target_dir = None):
245
+
246
+ ''' Detauches art from selected media files
247
+ '''
248
+
249
+ fcnt = 0
250
+ pass_filter = lambda fpath: self.handler.can_handle(fpath)
251
+ for entry in DWalker.file_entries(fs_entry_params, pass_filter = pass_filter):
252
+
253
+ if not target_dir:
254
+ target_dir = src_dir
255
+ else:
256
+ os.makedirs(target_dir, exist_ok = True)
257
+
258
+ if self.handler.tag_holder.has_artwork:
259
+ self.handler.detauch_art(target_dir)
260
+ fcnt += 1
261
+
262
+ # print summary
263
+ if not fs_entry_params.quiet:
264
+ print('Detauched art from {0} media entries'.format(fcnt))
265
+
266
+
@@ -0,0 +1,422 @@
1
+ Metadata-Version: 2.4
2
+ Name: batchmp
3
+ Version: 1.4
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: GPL-2.0-or-later
9
+ Keywords: batch processing media video audio CLI rename tags ID3
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3.6
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: Intended Audience :: Information Technology
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Topic :: Multimedia :: Sound/Audio
20
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
21
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
22
+ Classifier: Topic :: Multimedia :: Video
23
+ Classifier: Topic :: Multimedia :: Video :: Conversion
24
+ Classifier: Topic :: Software Development :: Libraries
25
+ Classifier: Topic :: Utilities
26
+ Description-Content-Type: text/markdown
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
43
+
44
+
45
+ **Status:**
46
+ A rainy weekends project under occasional development :)
47
+
48
+ #### Requirements:
49
+ - [Python 3.4.x](https://www.python.org/download/releases/3.4.1/) or later
50
+
51
+ #### Install:
52
+ - from [PyPI](https://pypi.python.org/pypi/batchmp): `$ pip install batchmp`
53
+ - latest from source repository: `$ pip install git+https://github.com/akpw/batch-mp-tools.git`
54
+
55
+ #### Blogs:
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)
61
+
62
+ ## Description
63
+
64
+ Batch management of media files, from base properties such as file names through tags / artwork metadata to advanced operations on the media content.
65
+
66
+ The project is written in [Python 3.4](https://www.python.org/download/releases/3.4.1/) and currently consists of three main command-line utilities. All three share the core concept of various transformations applied to generated stream of file system entries, and consequently they also share the same set of global options. A quick way to check on that is to run:
67
+ ```
68
+ $ renamer -h
69
+ $ tagger -h
70
+ $ bmfp -h
71
+ ```
72
+ That will show the global options along with specific commands for each tool. Getting more info on the commands level can be done using a similar approach, e.g. to learn more about the `renamer replace` command:
73
+ ```
74
+ $ renamer replace -h
75
+ ```
76
+ By default the tools always visualize targeted changes (whenever possible) before actual processing.
77
+
78
+ A little bit more details on each utility:
79
+
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.
81
+ At its simplest, Renamer can be used to print out the content of current directory:
82
+ ```
83
+ $ renamer
84
+ ```
85
+ Without command arguments, renamer uses `print` as the default command. This is also the case for both `$ tagger` and `$ bmfp`, with each of the tool showing info which is relevant to its intended purpose.
86
+ A little bit more advanced, to see what's lurking at the 7th nested folder level:
87
+ ```
88
+ $ renamer print -sl 7
89
+ ```
90
+ For multi-level indexing of all M4A files in all sub-directories of the current folder:
91
+ ```
92
+ $ renamer -r -in '*.m4a' index
93
+ /Desktop/_test/Gould
94
+ |-/1
95
+ |- 01 Glenn Gould French Suite 1 In D, BWV812 1 Allemande.m4a
96
+ |- 02 Glenn Gould French Suite 1 In D, BWV812 2 Courante.m4a
97
+ |- 03 Glenn Gould French Suite 1 In D, BWV812 3 Sarabande.m4a
98
+ |-/2
99
+ |- 01 Bach, Johann Sebastian French Suite 5 In G Major, BWV816 1 Allemande.m4a
100
+ |- 02 Bach, Johann Sebastian French Suite 5 In G Major, BWV816 2 Courante.m4a
101
+ |- 03 Bach, Johann Sebastian French Suite 5 In G Major, BWV816 3 Sarabande.m4a
102
+ |-/_Art
103
+ 6 files, 3 folders
104
+
105
+ Proceed? [y/n]:
106
+ ```
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.
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
+
137
+
138
+
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:
140
+ ```
141
+ $ tagger -r -in '*BWV816 1*' set --title '$filename, $track of $tracktotal'
142
+ Targeted after processing:
143
+ /Desktop/_test/Gould
144
+ |-/1
145
+ |-/2
146
+ |- Bach, Johann Sebastian French Suite 5 In G Major, BWV816 1 Allemande.m4a
147
+ Title: Bach, Johann Sebastian French Suite 5 In G Major, BWV816 1 Allemande, 1 of 26
148
+ Album: Bach French Suites BWV 812-817 Vol. II; Glenn Gould
149
+ Artist: Glenn Gould
150
+ Album Artist: Glenn Gould
151
+ Genre: Classical
152
+ Composer: Johann Sebastian Bach (1685-1750)
153
+ Year: 1994
154
+ Track: 1/26
155
+ Disk: 2/2
156
+ |-/Art
157
+ 1 files, 3 folders
158
+
159
+ Proceed? [y/n]: n
160
+ ```
161
+ 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.
162
+
163
+
164
+ [**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.
165
+ **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.
166
+
167
+ For example, to convert all files from the above example from M4A to FLAC:
168
+ ```
169
+ $ bmfp -r convert -la -tf FLAC
170
+ ```
171
+ The `-tf` switch specifies the target format, while `-la` explicitly tells BMFP to do a lossless conversion.
172
+
173
+ To check on the result, lets's just use the [tagger's](https://github.com/akpw/batch-mp-tools#tagger) ability to print media files info:
174
+ ```
175
+ $ tagger -r -in '*BWV816 1*' print -st -ss
176
+ /Users/AKPower/Desktop/_test/Gould
177
+ |-/1
178
+ |-/2
179
+ |- 7.4MB Bach, Johann Sebastian French Suite 5 in G major, BWV816 1 Allemande.flac
180
+ Title: French Suite 5 in G major, BWV 816 - 1 Allemande
181
+ Album: Bach French Suites BWV 812-817 Vol. II; Glenn Gould
182
+ Artist: Glenn Gould
183
+ Album Artist: Glenn Gould
184
+ Genre: Classical
185
+ Composer: Johann Sebastian Bach (1685-1750)
186
+ Year: 1994
187
+ Track: 1/26
188
+ Disk: 2/2
189
+ Format: FLAC
190
+ Duration: 0:01:48, Bit rate: 548kb/s, Sample rate: 44100Hz, Bit depth: 16
191
+ |-/_backup_15Mar25_094341
192
+ |- 7.9MB Bach, Johann Sebastian French Suite 5 in G major, BWV816 1 Allemande.m4a
193
+ Title: French Suite 5 in G major, BWV 816 - 1 Allemande
194
+ Album: Bach French Suites BWV 812-817 Vol. II; Glenn Gould
195
+ Artist: Glenn Gould
196
+ Album Artist: Glenn Gould
197
+ Genre: Classical
198
+ Composer: Johann Sebastian Bach (1685-1750)
199
+ Year: 1994
200
+ Track: 1/26
201
+ Disk: 2/2
202
+ Format: ALAC
203
+ Duration: 0:01:48, Bit rate: 579kb/s, Sample rate: 44100Hz, Bit depth: 16
204
+ |-/Art
205
+ 2 files, 4 folders
206
+ Total size: 15.2MB
207
+ ```
208
+ 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.
209
+
210
+ I will follow up with more examples and common use-cases in future blogs.
211
+
212
+
213
+ ## Brief Description of CLI Commands (use -h to expand on details for individual commands)
214
+ ### renamer
215
+ Batch renaming of files and directories
216
+ . source directory or source file modes
217
+ . visualises original / targeted folders structure before actual processing
218
+ . supports recursion, can optionally stop at specified end_level
219
+ . supports flattening folders beyond specified target_level
220
+ . supports include / exclude patterns (Unix style)
221
+ . display sorting:
222
+ .. by size/date, ascending/descending
223
+ . action commands:
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
227
+ .. flatten Flatten all folders below target level, moving the
228
+ files up at the target level. By default, deletes all empty flattened folders
229
+ .. index Adds index to files and directories names
230
+ .. replace RegExp-based replace in files and directories names. Supports expandable templates,
231
+ such as $dirname, $pardirname, $atime, $ctime, etc. For media files, also can process
232
+ tag-based templates such as $title, $album, $artist, $albumartist, $genre, $year, $track,
233
+ etc.
234
+ .. add_date Adds date to files and directories names
235
+ .. add_text Adds text to files and directories names
236
+ .. remove Removes n characters from files and directories names
237
+ .. capitalize Capitalizes words in files / directories names
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
243
+
244
+ Usage: renamer [-h] [-d DIR] [-f FILE] [Global Options] {Commands} [Commands Options]
245
+ Global Options:
246
+ Input source mode:
247
+ [-d, --dir] Source directory (default is the current directory)
248
+ [-f, --file] File to process
249
+
250
+ Recursion mode:
251
+ [-r, --recursive] Recurse into nested folders
252
+ [-el, --end-level] End level for recursion into nested folders
253
+
254
+ Filter files or folders:
255
+ [-in, --include] Include: Unix-style name patterns separated by ';'
256
+ [-ex, --exclude] Exclude: Unix-style name patterns separated by ';'
257
+ (excludes hidden files by default)
258
+ [-fd, --filter-dirs] Enable Include/Exclude patterns on directories
259
+ [-af, --all-files] Disable Include/Exclude patterns on files
260
+ (shows hidden files excluded by default)
261
+ Miscellaneous:
262
+ [-s, --sort]{na|nd|sa|sd} Sort order for files / folders (name | date, asc | desc)
263
+ [-ni, nested-indent] Indent for printing nested directories
264
+ [-q, --quiet] Do not visualise changes / show messages during processing
265
+
266
+ Commands:
267
+ {print, index, add_date, add_text, remove, replace, capitalize, flatten, delete, organize, version, info}
268
+ $ renamer {command} -h #run this for detailed help on individual commands
269
+
270
+ ### tagger
271
+ ###### Supported formats:
272
+ 'MP3', 'MP4', 'M4A', M4V', 'AIFF', 'ASF', 'QuickTime / MOV',
273
+ 'FLAC', 'MonkeysAudio', 'Musepack',
274
+ 'Ogg FLAC', 'Ogg Speex', 'Ogg Theora', 'Ogg Vorbis',
275
+ 'True Audio', 'WavPack', 'OptimFROG' <p>
276
+ Support via FFmpeg: 'AVI', 'FLV', 'MKV', 'MKA'
277
+
278
+ Batch management of media files metadata (tags & artwork)
279
+ . source directory / source file modes
280
+ . visualises original / targeted files metadata structure
281
+ . supports recursion, can optionally stop at specified end_level
282
+ . include / exclude patterns (Unix style)
283
+ . display sorting:
284
+ .. by size/date, ascending/descending
285
+ . action commands:
286
+ .. print Prints media info
287
+ .. set Sets tags in media files, including artwork, e.g:
288
+ $ tagger set --album 'The Album' -art '~/Desktop/art.jpg'
289
+ Supports expandable templates. To specify a template value,
290
+ use the long tag field name preceded by $:
291
+ $ tagger set --title '$title, $track of $tracktotal'
292
+ In addition to tag fields templates, file system names are also supported:
293
+ $ tagger set --title '$filename' --album '$dirname' --artist '$pardirname'...
294
+ .. copy Copies tags from a specified media file
295
+ .. index Indexes Track / Track Total tags
296
+ .. remove Removes tags from media files
297
+ .. replace RegExp-based replace in specified tags
298
+ e.g., to remove the first three characters in title:
299
+ $ tagger replace -tf 'title' -fs '^[\s\S]{0,3}' -rs ''
300
+ .. capitalize Capitalizes words in specified tags
301
+ .. detauch Extracts artwork
302
+
303
+ Usage: tagger [-h] [-d DIR] [-f FILE] [Global Options] {Commands} [Commands Options]
304
+ Global Options:
305
+ Input source mode:
306
+ [-d, --dir] Source directory (default is the current directory)
307
+ [-f, --file] File to process
308
+
309
+ Recursion mode:
310
+ [-r, --recursive] Recurse into nested folders
311
+ [-el, --end-level] End level for recursion into nested folders
312
+
313
+ Filter files or folders:
314
+ [-in, --include] Include: Unix-style name patterns separated by ';'
315
+ [-ex, --exclude] Exclude: Unix-style name patterns separated by ';'
316
+ (excludes hidden files by default)
317
+ [-fd, --filter-dirs] Enable Include/Exclude patterns on directories
318
+ [-af, --all-files] Disable Include/Exclude patterns on files
319
+ (shows hidden files excluded by default)
320
+ Miscellaneous:
321
+ [-s, --sort]{na|nd|sa|sd} Sort order for files / folders (name | date, asc | desc)
322
+ [-ni, nested-indent] Indent for printing nested directories
323
+ [-q, --quiet] Do not visualise changes / show messages during processing
324
+
325
+ Commands:
326
+ {print, set, copy, index, remove, replace, capitalize, detauch, version, info}
327
+ $ tagger {command} -h #run this for detailed help on individual commands
328
+
329
+ ### bmfp (requires [FFmpeg](http://ffmpeg.org/download.html))
330
+ Batch processing of media files
331
+ . Uses multiprocessing to utilize available CPU cores
332
+ . source directory / source file modes
333
+ . recursion to specified end_level
334
+ . include / exclude patterns (Unix style)
335
+ . action commands:
336
+ .. print Prints media files
337
+ .. convert Converts media to specified format
338
+ For example, to convert all files in current directory
339
+ $ bmfp convert -la -tf FLAC
340
+ .. normalize Nomalizes sound volume in media files
341
+ Peak normalization supported, RMS normalizations TBD
342
+ .. fragment Extract a media file fragment
343
+ .. segment Splits media files into segments
344
+ For example, to split media files in segments of 45 mins:
345
+ $ bmfp segment -d 45:00
346
+ .. silencesplit Splits media files into segments via detecting specified silence
347
+ $ bmfp silencesplit
348
+ .. cuesplit Splits media files into parts with specified output format,
349
+ according to their respective cue sheets
350
+ For example, to split all cue files in the current directory
351
+ $ bmfp cuesplit -tf mp3
352
+ .. denoise Reduces background audio noise in media files
353
+
354
+ .. adjust volume TDB: Adjust audio volume
355
+ .. speed up TDB: Uses Time Stretching to increase audio / video speed
356
+ .. slow down TDB: Uses Time Stretching to increase audio / video speed
357
+
358
+ Usage: bmfp [-h] [-d DIR] [-f FILE] [GLobal Options] {Commands}[Commands Options]
359
+ Input source mode:
360
+ [-d, --dir] Source directory (default is the current directory)
361
+ [-f, --file] File to process
362
+
363
+ Recursion mode:
364
+ [-r, --recursive] Recurse into nested folders
365
+ [-el, --end-level] End level for recursion into nested folders
366
+
367
+ Filter files or folders:
368
+ [-in, --include] Include: Unix-style name patterns separated by ';'
369
+ [-ex, --exclude] Exclude: Unix-style name patterns separated by ';'
370
+ (excludes hidden files by default)
371
+ [-fd, --filter-dirs] Enable Include/Exclude patterns on directories
372
+ [-af, --all-files] Disable Include/Exclude patterns on files
373
+ (shows hidden files excluded by default)
374
+
375
+ Target output Directory Target output directory. When omitted, will be
376
+ [-td, --target-dir] automatically created inside the parent level of
377
+ the input source. For recursive processing,
378
+ the processed files directory structure there
379
+ will be the same as for the original files.
380
+ FFmpeg General Output Options:
381
+ [-ma, --map-all] Force including all streams from the input file
382
+ [-cc, --copy-codecs] Copy streams codecs without re-encoding
383
+ [-vn, --no-video] Exclude video streams from the output
384
+ [-an, --no-audio] Exclude audio streams from the output
385
+ [-sn, --no-subs] Exclude subtitles streams from the output
386
+ [-fo, --ffmpeg-options] Additional FFmpeg options
387
+
388
+ FFmpeg Commands Execution:
389
+ [-q, --quiet] Do not visualise changes / show messages during processing
390
+ [-se, --serial-exec] Run all task's commands in a single process
391
+
392
+ Commands:
393
+ {print, convert, normalize, fragment, segment, silencesplit, cuesplit, denoise, version, info}
394
+ $ bmfp {command} -h #run this for detailed help on individual commands
395
+
396
+
397
+ ## Installing Development version
398
+ - Clone the repo, then run: `$ python -m pip install .`
399
+
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
+ ```
417
+
418
+
419
+
420
+
421
+
422
+