Skip to content

Commit ea16bea

Browse files
committed
+ new command phiola rename - Auto-rename files
1 parent 34548b0 commit ea16bea

File tree

14 files changed

+217
-20
lines changed

14 files changed

+217
-20
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ Other use-cases:
264264
# List all available audio playback and recording devices
265265
phiola device list
266266

267+
# Auto-rename all .opus files according to a pattern
268+
phiola rename *.opus -o "@tracknumber. @artist - @title"
269+
267270
# Start HTTP audio streaming server (Opus, 128kbps)
268271
phiola server "My Music" -inc "*.flac" -shuffle -opus_q 128
269272
```
@@ -280,6 +283,7 @@ Currently supported commands:
280283
| [play](src/exe/play.h) | Play audio |
281284
| [record](src/exe/record.h) | Record audio |
282285
| [remote](src/exe/remote.h) | Send remote command |
286+
| [rename](src/exe/rename.h) | Auto-rename files |
283287
| [server](src/exe/server.h) | Start audio streaming server |
284288
| [tag](src/exe/tag.h) | Edit file tags |
285289

src/core/file-write.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ static void fw_name_var(ffvec *buf, ffstr var, phi_track *t)
192192

193193
data:
194194
ffvec_grow(buf, val.len, 1);
195-
buf->len += ffpath_makefn(ffslice_end(buf, 1), -1, val, '_', _ffpath_charmask_filename);
195+
buf->len += ffpath_makename(ffslice_end(buf, 1), -1, val, '_', _ffpath_charmask_filename);
196196
}
197197

198198
static const char* fw_name(ffstr *sbuf, const char *name, phi_track *t)

src/core/queue-entry.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,3 +409,80 @@ int qe_rename(struct q_entry *e, const char *new, uint flags)
409409
ffmem_free((char*)new);
410410
return rc;
411411
}
412+
413+
/** All printable, plus SPACE, except: ", *, /, :, <, >, ?, \, | */
414+
static const uint _ffpath_charmask_filename[] = {
415+
0,
416+
// ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"!
417+
0x2bff7bfb, // 0010 1011 1111 1111 0111 1011 1111 1011
418+
// _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@
419+
0xefffffff, // 1110 1111 1111 1111 1111 1111 1111 1111
420+
// ~}| {zyx wvut srqp onml kjih gfed cba`
421+
0x6fffffff, // 0110 1111 1111 1111 1111 1111 1111 1111
422+
0xffffffff,
423+
0xffffffff,
424+
0xffffffff,
425+
0xffffffff
426+
};
427+
428+
static void q_rename_first_close(void *f, phi_track *t)
429+
{
430+
struct q_entry *e = t->qent;
431+
ffstr s, val, dir, ext;
432+
ffvec buf = {};
433+
434+
// Extract file meta components to prepare the target file name
435+
ffstr out = FFSTR_INITZ(e->q->rename_pattern);
436+
while (out.len) {
437+
if ('v' == ffstr_var_next(&out, &s, '@')) {
438+
ffstr_shift(&s, 1);
439+
core->metaif->find(&t->meta, s, &val, 0);
440+
ffvec_grow(&buf, val.len, 1);
441+
buf.len += ffpath_makename(ffslice_end(&buf, 1), -1, val, '_', _ffpath_charmask_filename);
442+
443+
} else {
444+
ffvec_addstr(&buf, &s);
445+
}
446+
}
447+
448+
ffpath_split3_str(FFSTR_Z(t->conf.ifile.name), &dir, NULL, &ext);
449+
if (dir.len)
450+
dir.len++; // "dir" -> "dir/"
451+
if (ext.len)
452+
ext.ptr--, ext.len++; // "ext" -> ".ext"
453+
char *fn = ffsz_allocfmt("%S%S%S", &dir, &buf, &ext);
454+
qe_rename(t->qent, fn, PHI_QRN_ACQUIRE);
455+
456+
qe_unref(e);
457+
core->metaif->destroy(&t->meta);
458+
ffvec_free(&buf);
459+
q_rename_next(e->q);
460+
}
461+
462+
static int q_rename_first_process(void *f, phi_track *t)
463+
{
464+
return PHI_DONE;
465+
}
466+
467+
static const phi_filter q_rename_first = {
468+
NULL, q_rename_first_close, q_rename_first_process,
469+
"q-rename-first"
470+
};
471+
472+
void qe_rename_start(struct q_entry *e)
473+
{
474+
struct phi_track_conf c = {
475+
.ifile.name = e->pub.url,
476+
.info_only = 1,
477+
};
478+
phi_track *t = core->track->create(&c);
479+
480+
core->track->filter(t, e->q->conf.first_filter, 0);
481+
core->track->filter(t, &q_rename_first, 0);
482+
core->track->filter(t, core->mod("core.auto-input"), 0);
483+
core->track->filter(t, core->mod("format.detect"), 0);
484+
485+
e->used++;
486+
t->qent = &e->pub;
487+
core->track->start(t);
488+
}

src/core/queue.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ struct phi_queue {
3232
fflock lock;
3333
phi_task task;
3434
struct q_entry *cursor;
35+
const char *rename_pattern;
3536
uint cursor_index;
3637
uint active_n, finished_n;
3738
uint track_closed_flags;
@@ -56,6 +57,7 @@ enum Q_TKCL_F {
5657
};
5758
static void q_ent_closed(struct phi_queue *q, uint flags);
5859
static void q_modified(struct phi_queue *q);
60+
static void q_rename_next(struct phi_queue *q);
5961

6062
#include <core/queue-entry.h>
6163

@@ -686,6 +688,27 @@ static void q_read_meta(phi_queue_id q)
686688
qe_read_meta(e);
687689
}
688690

691+
static void q_rename_next(struct phi_queue *q)
692+
{
693+
int i = (q->cursor) ? qe_index(q->cursor) + 1 : 0;
694+
struct q_entry *e = q_get(q, i);
695+
if (!e) {
696+
qm->on_change(q, '.', 0);
697+
return;
698+
}
699+
q->cursor = e;
700+
qe_rename_start(e);
701+
}
702+
703+
static int q_rename_all(phi_queue_id q, const char *pattern, uint flags)
704+
{
705+
if (!q) q = qm_default();
706+
707+
q->rename_pattern = pattern;
708+
q_rename_next(q);
709+
return 0;
710+
}
711+
689712
static void q_remove_multi(phi_queue_id q, uint flags)
690713
{
691714
if (!q) q = qm_default();
@@ -743,6 +766,7 @@ const phi_queue_if phi_queueif = {
743766
q_status,
744767
q_sort,
745768
q_read_meta,
769+
q_rename_all,
746770

747771
q_clear,
748772
q_remove_at,

src/exe/cmd.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ static int wildcard_expand(ffvec *input, ffstr s)
154154

155155
static int cmd_input(ffvec *input, ffstr s)
156156
{
157+
if (s.len && s.ptr[0] == '-')
158+
return _ffargs_err(&x->cmd, 1, "unknown option '%S'. Use '-h' for usage info.", &s);
159+
157160
if (ffstr_eqz(&s, "@names"))
158161
return cmd_input_names(input, ffstdin);
159162

@@ -256,6 +259,7 @@ static int cmd_opus_mode(const char *s)
256259
#include <exe/play.h>
257260
#include <exe/record.h>
258261
#include <exe/remote.h>
262+
#include <exe/rename.h>
259263
#include <exe/server.h>
260264
#include <exe/tag.h>
261265

@@ -281,6 +285,7 @@ Commands:\n\
281285
`play` Play audio [Default command]\n\
282286
`record` Record audio\n\
283287
`remote` Send remote command\n\
288+
`rename` Auto-rename files\n\
284289
`server` Start audio streaming server\n\
285290
`tag` Edit file tags\n\
286291
\n\
@@ -470,6 +475,7 @@ static const struct ffarg cmd_root[] = {
470475
{ "play", '{', cmd_play_init },
471476
{ "record", '{', cmd_rec_init },
472477
{ "remote", '{', cmd_remote_init },
478+
{ "rename", '{', cmd_rename_init },
473479
{ "server", '{', cmd_server_init },
474480
{ "tag", '{', cmd_tag_init },
475481
{ "\0\1", '{', cmd_play_init },

src/exe/convert.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,6 @@ static int conv_tracks(struct cmd_conv *v, ffstr s) { return cmd_tracks(&v->trac
157157

158158
static int conv_input(struct cmd_conv *v, ffstr s)
159159
{
160-
if (s.len && s.ptr[0] == '-')
161-
return _ffargs_err(&x->cmd, 1, "unknown option '%S'. Use '-h' for usage info.", &s);
162-
163160
x->stdin_busy = ffstr_eqz(&s, "@stdin");
164161
return cmd_input(&v->input, s);
165162
}

src/exe/info.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,6 @@ static int info_tracks(struct cmd_info *p, ffstr s) { return cmd_tracks(&p->trac
7171

7272
static int info_input(struct cmd_info *p, ffstr s)
7373
{
74-
if (s.len && s.ptr[0] == '-')
75-
return _ffargs_err(&x->cmd, 1, "unknown option '%S'. Use '-h' for usage info.", &s);
76-
7774
x->stdin_busy = ffstr_eqz(&s, "@stdin");
7875
return cmd_input(&p->input, s);
7976
}

src/exe/play.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,6 @@ static int play_tracks(struct cmd_play *p, ffstr s) { return cmd_tracks(&p->trac
109109

110110
static int play_input(struct cmd_play *p, ffstr s)
111111
{
112-
if (s.len && s.ptr[0] == '-')
113-
return _ffargs_err(&x->cmd, 1, "unknown option '%S'. Use '-h' for usage info.", &s);
114-
115112
x->stdin_busy = ffstr_eqz(&s, "@stdin");
116113
return cmd_input(&p->input, s);
117114
}

src/exe/rename.h

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/** phiola: executor: 'rename' command
2+
2025, Simon Zolin */
3+
4+
static int rename_help()
5+
{
6+
help_info_write("\
7+
Auto-rename files:\n\
8+
`phiola rename` INPUT... -o PATTERN\n\
9+
\n\
10+
INPUT File name, directory or URL\n\
11+
`@names` Read file names from standard input\n\
12+
\n\
13+
Options:\n\
14+
`-o` PATTERN Target file name pattern (without file extension).\n\
15+
Supports runtime variable expansion:\n\
16+
@STRING Expands to file meta data,\n\
17+
e.g. `-o \"@tracknumber. @artist - @title\"`\n\
18+
");
19+
x->exit_code = 0;
20+
return 1;
21+
}
22+
23+
struct cmd_rename {
24+
phi_task task;
25+
ffvec input; // ffstr[]
26+
const char *output;
27+
};
28+
29+
static int rename_action(struct cmd_rename *r)
30+
{
31+
x->queue->on_change(q_on_change);
32+
33+
struct phi_queue_conf qc = {
34+
.first_filter = &phi_guard,
35+
};
36+
x->queue->create(&qc);
37+
38+
ffstr *s;
39+
FFSLICE_WALK(&r->input, s) {
40+
struct phi_queue_entry qe = {
41+
.url = s->ptr,
42+
};
43+
x->queue->add(NULL, &qe);
44+
}
45+
ffvec_free(&r->input);
46+
47+
x->queue->rename_all(NULL, r->output, 0);
48+
return 0;
49+
}
50+
51+
static int rename_input(struct cmd_rename *r, ffstr s)
52+
{
53+
return cmd_input(&r->input, s);
54+
}
55+
56+
static int rename_fin(struct cmd_rename *r)
57+
{
58+
if (!r->input.len)
59+
return _ffargs_err(&x->cmd, 1, "please specify input file");
60+
61+
if (!r->output)
62+
return _ffargs_err(&x->cmd, 1, "please specify output file name pattern with '-o PATTERN'");
63+
64+
return 0;
65+
}
66+
67+
#define O(m) (void*)FF_OFF(struct cmd_rename, m)
68+
static const struct ffarg cmd_rename[] = {
69+
{ "-help", 0, rename_help },
70+
{ "-o", 's', O(output) },
71+
{ "\0\1", 'S', rename_input },
72+
{ "", 0, rename_fin },
73+
};
74+
#undef O
75+
76+
static void cmd_rename_free(struct cmd_rename *r)
77+
{
78+
ffmem_free(r);
79+
}
80+
81+
struct ffarg_ctx cmd_rename_init(void *obj)
82+
{
83+
return SUBCMD_INIT(ffmem_new(struct cmd_rename), cmd_rename_free, rename_action, cmd_rename);
84+
}

src/exe/server.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ static int srv_exclude(struct cmd_srv *s, ffstr ss)
5757

5858
static int srv_input(struct cmd_srv *s, ffstr fn)
5959
{
60-
if (fn.len && fn.ptr[0] == '-')
61-
return _ffargs_err(&x->cmd, 1, "unknown option '%S'. Use '-h' for usage info.", &fn);
62-
6360
return cmd_input(&s->input, fn);
6461
}
6562

0 commit comments

Comments
 (0)