├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── config ├── ngx_http_transcode_module.c ├── ngx_http_transcode_module.h ├── ngx_http_transcode_module_transcode.c ├── ngx_http_transcode_module_transcode.h ├── ngx_http_transcode_module_utility.c ├── ngx_http_transcode_module_utility.h └── tests ├── Makefile ├── README.md ├── origin.list ├── test_nginx.conf ├── test_origin.py └── test_transcode.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # vscode 55 | .vscode 56 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Project author: 2 | 3 | Sun Zhenliang 4 | Chengdu, China 5 | 6 | Contacts: 7 | hisunzhenliang@outlook.com 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 sunzhenliang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nginx-transcode-module 2 | 3 | Nginx module for media transcoding 4 | 5 | ## Description 6 | 7 | This project is a nginx third-party module. 8 | 9 | Accept HTTP requests, find the files according to the configuration, transcode it and return it in the required format. 10 | 11 | ## Install 12 | 13 | 1. Clone project 14 | 15 | `git clone https://github.com/HiSunzhenliang/nginx-transcode-module.git` 16 | 17 | 2. Requirements 18 | - Nginx 19 | - libsox 20 | 21 | 3. Compile Nginx with nginx-transcode-module 22 | 23 | `./configure --add-dynamic-module=/.../nginx-transcode-module` 24 | 25 | `make && make install` 26 | 27 | ## Usage 28 | 29 | In nginx config file: 30 | 31 | ``` 32 | location ~ / { 33 | nginx-transcode-module; 34 | transcode_root "/path/audios"; 35 | transcode_output_format "mp3"; 36 | } 37 | ``` 38 | ``` 39 | $ ls /path/audios 40 | a.wav b.wav c.wav ... 41 | ``` 42 | 43 | Send request like: 44 | ``` 45 | curl -v http://127.0.0.1:8000/a.wav -o a.mp3` 46 | ``` 47 | Get `a.mp3` transcoded from `a.wav`. 48 | 49 | If `transcode_output_format` is not specified, output format will be set to the extension of the uri. 50 | ``` 51 | location ~ / { 52 | nginx-transcode-module; 53 | transcode_root "/path/audios"; 54 | # transcode_output_format "mp3"; 55 | } 56 | ``` 57 | Send request like: 58 | ``` 59 | curl -v http://127.0.0.1:8000/b.mp3 -o b.mp3` 60 | ``` 61 | Get `b.mp3` transcoded from `b.wav`. 62 | 63 | ## Copyright & License 64 | ``` 65 | MIT License 66 | 67 | Copyright (c) 2021 sunzhenliang 68 | 69 | Permission is hereby granted, free of charge, to any person obtaining a copy 70 | of this software and associated documentation files (the "Software"), to deal 71 | in the Software without restriction, including without limitation the rights 72 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 73 | copies of the Software, and to permit persons to whom the Software is 74 | furnished to do so, subject to the following conditions: 75 | 76 | The above copyright notice and this permission notice shall be included in all 77 | copies or substantial portions of the Software. 78 | 79 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 80 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 81 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 82 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 83 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 84 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 85 | SOFTWARE. 86 | ``` 87 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_transcode_module 2 | HTTP_MODULES="$HTTP_MODULES ngx_http_transcode_module" 3 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_transcode_module.c" 4 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/ngx_http_transcode_module.h" 5 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_transcode_module_transcode.c" 6 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/ngx_http_transcode_module_transcode.h" 7 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_transcode_module_utility.c" 8 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/ngx_http_transcode_module_utility.h" 9 | ngx_feature_libs="-lsox" 10 | CORE_LIBS="$CORE_LIBS $ngx_feature_libs" -------------------------------------------------------------------------------- /ngx_http_transcode_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @file ngx_http_transcode_module.c 3 | * @author SunZhenliang 4 | */ 5 | 6 | #include "ngx_http_transcode_module_utility.h" 7 | #include "ngx_http_transcode_module_transcode.h" 8 | #include "ngx_http_transcode_module.h" 9 | 10 | /* ngx interface */ 11 | typedef struct { 12 | ngx_flag_t enabled; 13 | ngx_http_complex_value_t *root; 14 | ngx_http_complex_value_t *output_format; 15 | } ngx_http_transcode_loc_conf_t; 16 | 17 | static char *ngx_http_transcode(ngx_conf_t *, ngx_command_t *, void *); 18 | 19 | static ngx_int_t ngx_http_transcode_handler(ngx_http_request_t *r); 20 | 21 | static void *ngx_http_transcode_create_loc_conf(ngx_conf_t *); 22 | 23 | static char *ngx_http_transcode_merge_loc_conf(ngx_conf_t *, void *, void *); 24 | 25 | static ngx_command_t ngx_http_transcode_commands[] = { 26 | { 27 | ngx_string("nginx-transcode-module"), 28 | NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, 29 | ngx_http_transcode, 30 | NGX_HTTP_LOC_CONF_OFFSET, 31 | 0, 32 | NULL 33 | }, 34 | { 35 | ngx_string("transcode_root"), 36 | NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 37 | ngx_http_set_complex_value_slot, 38 | NGX_HTTP_LOC_CONF_OFFSET, 39 | offsetof(ngx_http_transcode_loc_conf_t, root), 40 | NULL 41 | }, 42 | { 43 | ngx_string("transcode_output_format"), 44 | NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 45 | ngx_http_set_complex_value_slot, 46 | NGX_HTTP_LOC_CONF_OFFSET, 47 | offsetof(ngx_http_transcode_loc_conf_t, output_format), 48 | NULL 49 | }, 50 | ngx_null_command 51 | }; 52 | 53 | static ngx_http_module_t ngx_http_transcode_module_ctx = { 54 | NULL, 55 | NULL, 56 | NULL, 57 | NULL, 58 | NULL, 59 | NULL, 60 | ngx_http_transcode_create_loc_conf, 61 | ngx_http_transcode_merge_loc_conf 62 | }; 63 | 64 | ngx_module_t ngx_http_transcode_module = { 65 | NGX_MODULE_V1, 66 | &ngx_http_transcode_module_ctx, 67 | ngx_http_transcode_commands, 68 | NGX_HTTP_MODULE, 69 | NULL, 70 | NULL, 71 | NULL, 72 | NULL, 73 | NULL, 74 | NULL, 75 | NULL, 76 | NGX_MODULE_V1_PADDING 77 | }; 78 | /* ngx interface end */ 79 | 80 | static ngx_int_t ngx_http_transcode_handler(ngx_http_request_t *r) { 81 | ngx_int_t err; 82 | status_code code; 83 | ngx_log_t *log; 84 | ngx_chain_t out; 85 | ngx_buf_t *buff = NULL; 86 | ngx_http_transcode_loc_conf_t *conf; 87 | ngx_str_t root = ngx_null_string; 88 | ngx_str_t output_format = ngx_null_string; 89 | ngx_str_t output = ngx_null_string; 90 | ngx_str_t path = ngx_null_string; 91 | ngx_str_t matched_path = ngx_null_string; 92 | 93 | conf = ngx_http_get_module_loc_conf(r, ngx_http_transcode_module); 94 | if (conf->root) { 95 | ngx_http_complex_value(r, conf->root, &root); 96 | } 97 | if (conf->output_format) { 98 | ngx_http_complex_value(r, conf->output_format, &output_format); 99 | } else { 100 | output_format = get_output_format(r->pool, r->uri); 101 | } 102 | 103 | if (!output_format.data) { 104 | return NGX_HTTP_BAD_REQUEST; 105 | } 106 | 107 | err = ngx_http_discard_request_body(r); 108 | if (err != NGX_OK) { 109 | return err; 110 | } 111 | 112 | log = r->connection->log; 113 | path = generate_path(r->pool, log, root, r->uri); 114 | if (!path.data) { 115 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 116 | } 117 | 118 | matched_path = match_path(r->pool, log, path); 119 | if (!matched_path.data) { 120 | return NGX_HTTP_NOT_FOUND; 121 | } 122 | 123 | code = transcode(&output, r->pool, log, matched_path, output_format); 124 | 125 | switch (code) { 126 | case NGX_HTTP_TRANSCODE_MODULE_NOT_FOUND: 127 | r->headers_out.status = NGX_HTTP_NOT_FOUND; 128 | break; 129 | case NGX_HTTP_TRANSCODE_MODULE_NO_DECODER: 130 | r->headers_out.status = NGX_HTTP_NOT_IMPLEMENTED; 131 | break; 132 | case NGX_HTTP_TRANSCODE_MODULE_NO_ENCODER: 133 | r->headers_out.status = NGX_HTTP_NOT_IMPLEMENTED; 134 | break; 135 | case NGX_HTTP_TRANSCODE_MODULE_LIBSOX_ERROR: 136 | r->headers_out.status = NGX_HTTP_NOT_IMPLEMENTED; 137 | break; 138 | case NGX_HTTP_TRANSCODE_MODULE_TRANS_ERROR: 139 | r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; 140 | break; 141 | default: 142 | r->headers_out.status = NGX_HTTP_OK; 143 | break; 144 | } 145 | if (r->headers_out.status == NGX_HTTP_OK) { 146 | buff = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); 147 | if (buff == NULL) { 148 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 149 | } 150 | buff->pos = output.data; 151 | buff->last = output.data + (output.len * sizeof(u_char)); 152 | buff->memory = 1; 153 | buff->last_buf = 1; 154 | r->headers_out.content_length_n = output.len; 155 | } else { 156 | r->header_only = 1; 157 | r->headers_out.content_length_n = 0; 158 | } 159 | 160 | err = ngx_http_send_header(r); 161 | if (err == NGX_ERROR || err > NGX_OK || r->header_only) { 162 | return err; 163 | } 164 | 165 | out.buf = buff; 166 | out.next = NULL; 167 | 168 | return ngx_http_output_filter(r, &out); 169 | } 170 | 171 | static char *ngx_http_transcode(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { 172 | ngx_http_core_loc_conf_t *clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); 173 | ngx_http_transcode_loc_conf_t *tlcf = conf; 174 | clcf->handler = ngx_http_transcode_handler; 175 | tlcf->enabled = 1; 176 | return NGX_CONF_OK; 177 | } 178 | 179 | static void *ngx_http_transcode_create_loc_conf(ngx_conf_t *cf) { 180 | ngx_http_transcode_loc_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_transcode_loc_conf_t)); 181 | if (!conf) { 182 | return NGX_CONF_ERROR; 183 | } 184 | conf->enabled = NGX_CONF_UNSET; 185 | conf->root = NULL; 186 | conf->output_format = NULL; 187 | 188 | return conf; 189 | } 190 | 191 | static char *ngx_http_transcode_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { 192 | ngx_http_transcode_loc_conf_t *prev = parent; 193 | ngx_http_transcode_loc_conf_t *conf = child; 194 | 195 | ngx_conf_merge_value(conf->enabled, prev->enabled, 0); 196 | 197 | if (!conf->root) { 198 | conf->root = (ngx_http_complex_value_t *)prev->root; 199 | } 200 | if (!conf->output_format) { 201 | conf->output_format = (ngx_http_complex_value_t *)prev->output_format; 202 | } 203 | 204 | if ((!conf->root) && (conf->enabled)) { 205 | ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "transcode: need audio root"); 206 | return NGX_CONF_ERROR; 207 | } 208 | 209 | return NGX_CONF_OK; 210 | } 211 | 212 | -------------------------------------------------------------------------------- /ngx_http_transcode_module.h: -------------------------------------------------------------------------------- 1 | /* 2 | * @file ngx_http_transcode_module.h 3 | * @author SunZhenliang 4 | */ 5 | #ifndef TRANSCODE_MODULE_H 6 | #define TRANSCODE_MODULE_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | typedef enum { 13 | NGX_HTTP_TRANSCODE_MODULE_FOUND = 0, 14 | NGX_HTTP_TRANSCODE_MODULE_NOT_FOUND, 15 | NGX_HTTP_TRANSCODE_MODULE_NO_DECODER, 16 | NGX_HTTP_TRANSCODE_MODULE_NO_ENCODER, 17 | NGX_HTTP_TRANSCODE_MODULE_TRANS_ERROR, 18 | NGX_HTTP_TRANSCODE_MODULE_LIBSOX_ERROR 19 | } status_code; 20 | 21 | #endif /* TRANSCODE_MODULE_H */ -------------------------------------------------------------------------------- /ngx_http_transcode_module_transcode.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_transcode_module_transcode.c 3 | * @author sunzhenliang (hisunzhenliang@outlook.com) 4 | * @brief implements of transcode 5 | * @date 2021-12-03 6 | * 7 | * @copyright Copyright (c) 2021 8 | * 9 | */ 10 | #include "ngx_http_transcode_module.h" 11 | #include "ngx_http_transcode_module_transcode.h" 12 | 13 | #include 14 | 15 | ngx_int_t transcode(ngx_str_t *output, ngx_pool_t *pool, ngx_log_t *log, ngx_str_t source, ngx_str_t fmt) { 16 | ngx_int_t code; 17 | sox_format_t *in = NULL; 18 | sox_format_t *out = NULL; 19 | char output_format[MAX_FMT_LEN] = {0}; 20 | char *buffer = NULL; 21 | size_t buffer_size; 22 | size_t number_read; 23 | ngx_int_t open_libsox = 0; 24 | sox_sample_t samples[MAX_SAMPLES]; 25 | char source_path[NGX_MAX_PATH] = {0}; 26 | 27 | ngx_snprintf((u_char *)source_path, source.len, "%V", &source); 28 | if (access(source_path, F_OK)) { 29 | ngx_log_error(NGX_LOG_ERR, log, 0, "transcode: input file not found : %s.", source_path); 30 | code = NGX_HTTP_TRANSCODE_MODULE_NOT_FOUND; 31 | goto err; 32 | } 33 | 34 | sox_globals.verbosity = 0; 35 | if (sox_init() != SOX_SUCCESS) { 36 | ngx_log_error(NGX_LOG_ERR, log, 0, "transcode: libsox init fail."); 37 | code = NGX_HTTP_TRANSCODE_MODULE_LIBSOX_ERROR; 38 | goto err; 39 | } 40 | open_libsox = 1; 41 | 42 | in = sox_open_read(source_path, NULL, NULL, NULL); 43 | if (!in) { 44 | ngx_log_error(NGX_LOG_ERR, log, 0, "transcode: encoder not found."); 45 | code = NGX_HTTP_TRANSCODE_MODULE_NO_ENCODER; 46 | goto err; 47 | } 48 | 49 | if (fmt.len >= MAX_FMT_LEN) { 50 | ngx_log_error(NGX_LOG_ERR, log, 0, "transcode: format name too long."); 51 | code = NGX_HTTP_INTERNAL_SERVER_ERROR; 52 | goto err; 53 | } 54 | ngx_snprintf((u_char *)output_format, fmt.len, "%V", &fmt); 55 | out = sox_open_memstream_write(&buffer, &buffer_size, &in->signal, NULL, output_format, NULL); 56 | if (!out) { 57 | ngx_log_error(NGX_LOG_ERR, log, 0, "transcode: decoder not found."); 58 | code = NGX_HTTP_TRANSCODE_MODULE_NO_DECODER; 59 | goto err; 60 | } 61 | 62 | while ((number_read = sox_read(in, samples, MAX_SAMPLES))) { 63 | sox_write(out, samples, number_read); 64 | } 65 | fflush(out->fp); 66 | 67 | sox_close(out); 68 | sox_close(in); 69 | 70 | output->data = ngx_pcalloc(pool, buffer_size * sizeof(u_char)); 71 | output->len = buffer_size; 72 | ngx_memcpy(output->data, buffer, buffer_size); 73 | 74 | free(buffer); 75 | sox_quit(); 76 | return NGX_OK; 77 | err: 78 | if (in) { 79 | sox_close(in); 80 | } 81 | if (out) { 82 | sox_close(out); 83 | } 84 | if (buffer) { 85 | free(buffer); 86 | } 87 | if (open_libsox) { 88 | sox_quit(); 89 | } 90 | return code; 91 | } -------------------------------------------------------------------------------- /ngx_http_transcode_module_transcode.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_transcode_module_transcode.h 3 | * @author sunzhenliang (hisunzhenliang@outlook.com) 4 | * @brief transcode 5 | * @date 2021-12-03 6 | * 7 | * @copyright Copyright (c) 2021 8 | * 9 | */ 10 | #ifndef TRANSCODE_MODULE_TRANSCODE_H 11 | #define TRANSCODE_MODULE_TRANSCODE_H 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | #define MAX_SAMPLES (size_t)2048 19 | #define MAX_FMT_LEN (size_t)16 20 | 21 | /** 22 | * @brief transcode flow 23 | * 24 | * @param output transcoded data 25 | * @param pool mem pool 26 | * @param log log 27 | * @param source source data 28 | * @param fmt target format 29 | * @return ngx_int_t 0 for succ; others for err type 30 | */ 31 | ngx_int_t transcode(ngx_str_t *output, ngx_pool_t *pool, ngx_log_t *log, ngx_str_t source, ngx_str_t fmt); 32 | 33 | #endif /* TRANSCODE_MODULE_TRANSCODE_H */ -------------------------------------------------------------------------------- /ngx_http_transcode_module_utility.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_transcode_module_utility.c 3 | * @author sunzhenliang (hisunzhenliang@outlook.com) 4 | * @brief implements of utilities 5 | * @date 2021-12-03 6 | * 7 | * @copyright Copyright (c) 2021 8 | * 9 | */ 10 | #include "ngx_http_transcode_module_utility.h" 11 | 12 | #include 13 | 14 | ngx_str_t generate_path(ngx_pool_t *pool, ngx_log_t *log, ngx_str_t root, ngx_str_t uri) { 15 | ngx_str_t path = ngx_null_string; 16 | 17 | path.data = ngx_pcalloc(pool, (root.len + uri.len) * sizeof(u_char)); 18 | if (!path.data) { 19 | ngx_log_error(NGX_LOG_ERR, log, 0, "transcode: can not alloc for path"); 20 | ngx_str_null(&path); 21 | return path; 22 | } 23 | ngx_snprintf(path.data, root.len + uri.len, "%V%V", &root, &uri); 24 | path.len = root.len + uri.len; 25 | return path; 26 | } 27 | 28 | ngx_str_t match_path(ngx_pool_t *pool, ngx_log_t *log, ngx_str_t path) { 29 | ngx_str_t matched = ngx_null_string; 30 | ngx_str_t dirpath = ngx_null_string; 31 | ngx_str_t target_namebase = ngx_null_string; 32 | ngx_str_t file_namebase = ngx_null_string; 33 | ngx_int_t r = 0; 34 | ngx_dir_t dir; 35 | 36 | dirpath = get_dir(pool, path); 37 | if (!dirpath.data) { 38 | ngx_log_error(NGX_LOG_ERR, log, 0, "transcode: get dir fail %V", &path); 39 | return matched; 40 | } 41 | target_namebase = get_namebase(pool, path); 42 | if (!target_namebase.data) { 43 | ngx_log_error(NGX_LOG_ERR, log, 0, "transcode: get dir fail %V", &path); 44 | return matched; 45 | } 46 | if (ngx_open_dir(&dirpath, &dir) == NGX_ERROR) { 47 | ngx_log_error(NGX_LOG_ERR, log, 0, "transcode: cant not open dir %V", 48 | &dirpath); 49 | return matched; 50 | } 51 | 52 | while (ngx_read_dir(&dir) == NGX_OK) { 53 | ngx_log_error(NGX_LOG_DEBUG, log, 0, "Traverse {%s,%d}", 54 | ngx_de_name(&dir), ngx_de_namelen(&dir)); 55 | file_namebase = get_namebase( pool, (ngx_str_t){ngx_de_namelen(&dir), ngx_de_name(&dir)}); 56 | if (!file_namebase.data || 57 | !ngx_strncmp(file_namebase.data, "", file_namebase.len)) { 58 | continue; 59 | } 60 | 61 | ngx_log_error(NGX_LOG_DEBUG, log, 0, 62 | "file_namebase {%s,%d} target_namebase {%s,%d}", 63 | file_namebase.data, file_namebase.len, 64 | target_namebase.data, target_namebase.len); 65 | 66 | if (!ngx_filename_cmp(target_namebase.data, file_namebase.data, file_namebase.len)) { 67 | ngx_log_error(NGX_LOG_DEBUG, log, 0, "Got {%s,%d}", ngx_de_name(&dir), ngx_de_namelen(&dir)); 68 | r = 1; 69 | break; 70 | } 71 | } 72 | 73 | if (r) { 74 | matched.data = ngx_pcalloc(pool, dirpath.len + ngx_de_namelen(&dir)); 75 | ngx_sprintf(matched.data, "%V/%s", &dirpath, ngx_de_name(&dir)); 76 | matched.len = dirpath.len + ngx_de_namelen(&dir) + 1; 77 | } 78 | 79 | return matched; 80 | } 81 | 82 | ngx_str_t get_output_format(ngx_pool_t *pool, ngx_str_t uri) { 83 | ngx_str_t fmt = ngx_null_string; 84 | ngx_int_t fmtlen; 85 | 86 | u_char *dot = (u_char *)ngx_strchr(uri.data, '.'); 87 | if (!dot) { 88 | return fmt; 89 | } 90 | fmtlen = uri.data + uri.len - dot - 1; 91 | fmt.data = ngx_pcalloc(pool, fmtlen * sizeof(u_char)); 92 | ngx_memcpy(fmt.data, dot + 1, fmtlen); 93 | fmt.len = fmtlen; 94 | return fmt; 95 | } 96 | 97 | ngx_str_t get_dir(ngx_pool_t *pool, ngx_str_t path) { 98 | ngx_str_t dir = ngx_null_string; 99 | u_char *dirpath = ngx_pcalloc(pool, path.len + 1); 100 | u_char *p; 101 | if (!path.data) { 102 | return dir; 103 | } 104 | ngx_snprintf(dirpath, path.len, "%V", &path); 105 | p = (u_char *)dirname((char *)dirpath); 106 | if (!p) { 107 | return dir; 108 | } 109 | dir.data = p; 110 | dir.len = ngx_strlen(p); 111 | 112 | return dir; 113 | } 114 | 115 | ngx_str_t get_namebase(ngx_pool_t *pool, ngx_str_t path) { 116 | ngx_str_t namebase = ngx_null_string; 117 | u_char *name = ngx_pcalloc(pool, path.len + 1); 118 | u_char *p; 119 | if (!path.data) { 120 | return namebase; 121 | } 122 | ngx_snprintf(name, path.len, "%V", &path); 123 | p = (u_char *)basename((char *)name); 124 | if (!p) { 125 | return namebase; 126 | } 127 | u_char *dot = (u_char *)ngx_strchr((char *)p, '.'); 128 | if (dot) { 129 | *dot = '\0'; 130 | } 131 | namebase.data = p; 132 | namebase.len = ngx_strlen(p); 133 | 134 | return namebase; 135 | } -------------------------------------------------------------------------------- /ngx_http_transcode_module_utility.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_transcode_module_utility.h 3 | * @author sunzhenliang (hisunzhenliang@outlook.com) 4 | * @brief utilities 5 | * @date 2021-12-03 6 | * 7 | * @copyright Copyright (c) 2021 8 | * 9 | */ 10 | #ifndef TRANSCODE_MODULE_UTILITY_H 11 | #define TRANSCODE_MODULE_UTILITY_H 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | /** 18 | * @brief generate full path of target file 19 | * 20 | * @param pool alloc from this mem pool 21 | * @param log log 22 | * @param root root directive 23 | * @param uri uri 24 | * @return ngx_str_t alloced str 25 | */ 26 | ngx_str_t generate_path(ngx_pool_t *pool, ngx_log_t *log, ngx_str_t root, ngx_str_t uri); 27 | 28 | /** 29 | * @brief match file by namebase, discard extension 30 | * 31 | * @param pool mem pool 32 | * @param log log 33 | * @param path have needed namebase and dir to search in 34 | * @return ngx_str_t alloced str 35 | */ 36 | ngx_str_t match_path(ngx_pool_t *pool, ngx_log_t *log, ngx_str_t path); 37 | 38 | /** 39 | * @brief Get the output format object 40 | * 41 | * @param pool mem pool 42 | * @param uri uri 43 | * @return ngx_str_t 44 | */ 45 | ngx_str_t get_output_format(ngx_pool_t *pool, ngx_str_t uri); 46 | 47 | /** 48 | * @brief Get the namebase object 49 | * 50 | * @param pool mem pool 51 | * @param path path 52 | * @return ngx_str_t namebase 53 | */ 54 | ngx_str_t get_namebase(ngx_pool_t *pool, ngx_str_t path); 55 | 56 | /** 57 | * @brief Get the dir object 58 | * 59 | * @param pool mem pool 60 | * @param path path 61 | * @return ngx_str_t dir 62 | */ 63 | ngx_str_t get_dir(ngx_pool_t *pool, ngx_str_t path); 64 | 65 | #endif /* TRANSCODE_MODULE_UTILITY_H */ -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | # GNU Makefile 2 | 3 | origin: 4 | @python3 test_origin.py 5 | 6 | transcode: 7 | @python3 test_transcode.py 8 | 9 | clean: 10 | @rm -rf work_dir 11 | 12 | clean-origin: 13 | @rm -rf work_dir/origin 14 | 15 | clean-target: 16 | @rm -rf work_dir/target -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # nginx-transcode-module test 2 | ## how 3 | 1. launch nginx with test-nginx.conf 4 | 2. make clean 5 | 3. make origin 6 | 4. make transcode 7 | 8 | ## Add test 9 | 10 | For now, it only supports sox's supported audio formats. 11 | 12 | -------------------------------------------------------------------------------- /tests/origin.list: -------------------------------------------------------------------------------- 1 | { 2 | "origin": [ 3 | "http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Samples/Goldwave/addf8-Alaw-GW.wav", 4 | "https://download.samplelib.com/mp3/sample-15s.mp3" 5 | ] 6 | } -------------------------------------------------------------------------------- /tests/test_nginx.conf: -------------------------------------------------------------------------------- 1 | daemon off; # default on 2 | 3 | pid objs/nginx.pid; 4 | # error_log stderr notice; 5 | error_log stderr debug; 6 | 7 | events { 8 | } 9 | 10 | http { 11 | 12 | server { 13 | listen 8080 default_server; 14 | location ~ / { 15 | nginx-transcode-module; 16 | transcode_root "dir/work_dir/origin"; 17 | # transcode_output_format "mp3"; 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /tests/test_origin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # @file test_origin.py 3 | # @author SunZhenliang 4 | 5 | import os 6 | import urllib.request 7 | import json 8 | 9 | WORK_DIR = "work_dir" 10 | ORIGIN_DIR = WORK_DIR + "/" + "origin" 11 | 12 | ORIGIN_LIST = "origin.list" 13 | 14 | def download_origin(): 15 | os.mkdir(ORIGIN_DIR) 16 | origin_list = [] 17 | with open(ORIGIN_LIST,"r") as f: 18 | urls = json.load(f) 19 | for i in urls['origin']: 20 | try: 21 | file_path = ORIGIN_DIR + "/" + os.path.basename(i) 22 | print(f"Downloading {i}") 23 | urllib.request.urlretrieve(i, file_path) 24 | origin_list.append(file_path) 25 | except Exception as e: 26 | print(e,i) 27 | return origin_list 28 | 29 | def main(): 30 | os.mkdir(WORK_DIR) 31 | origin_list = download_origin() 32 | print(f"\nOrigins:") 33 | for i in origin_list: 34 | print(i) 35 | 36 | path = os.getcwd() + "/" + ORIGIN_DIR 37 | print(f"\nAdd this in nginx.conf: \ntranscode_root \"{path}\";\n") 38 | 39 | 40 | if __name__ == '__main__': 41 | main() 42 | -------------------------------------------------------------------------------- /tests/test_transcode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # @file test_transcode.py 3 | # @author SunZhenliang 4 | 5 | import os 6 | import urllib.request 7 | import subprocess 8 | 9 | PORT = 8080 10 | HOST = "127.0.0.1" 11 | 12 | # SOURCE = [(CODING_FORMAT, CONTAINER_FORMAT)] 13 | # TARGET = [(CODING_FORMAT, CONTAINER_FORMAT)] 14 | 15 | WORK_DIR = "work_dir" 16 | ORIGIN_DIR = WORK_DIR + "/" + "origin" 17 | TARGET_DIR = WORK_DIR + "/" + "target" 18 | 19 | SOURCE = [ 20 | ("signed-integer", "wav"), 21 | ("a-law", "wav"), 22 | ("u-law", "wav"), 23 | ("amrnb", "amr"), 24 | ("amrwb", "awb"), 25 | ("MPEG", "mp3") 26 | ] 27 | 28 | TARGET = [ 29 | ("signed-integer", "wav"), 30 | ("MPEG", "mp3") 31 | ] 32 | 33 | 34 | def listdir(path, list_name): 35 | for file in os.listdir(path): 36 | file_path = os.path.join(path, file) 37 | if os.path.isdir(file_path): 38 | listdir(file_path, list_name) 39 | else: 40 | list_name.append(file_path) 41 | 42 | 43 | def download_target(target_list): 44 | os.mkdir(TARGET_DIR) 45 | urls = [f"http://{HOST}:{PORT}/{i}" for i in target_list] 46 | targets = [] 47 | for i in urls: 48 | try: 49 | file_path = TARGET_DIR + "/" + os.path.basename(i) 50 | print(f"Downloading {i}") 51 | urllib.request.urlretrieve(i, file_path) 52 | targets.append(file_path) 53 | except Exception as e: 54 | print(e, i) 55 | return targets 56 | 57 | 58 | def generate_targetlist(origin_list): 59 | namebases = [os.path.splitext(i)[0] for i in origin_list] 60 | target_list = [namebase + "." + exten 61 | for namebase in namebases for _, exten in TARGET] 62 | return target_list 63 | 64 | def callcmd(cmd): 65 | return subprocess.check_output(cmd,shell=True,stderr=subprocess.PIPE,encoding="utf-8",timeout=1) 66 | 67 | def main(): 68 | origin_list = [] 69 | listdir(ORIGIN_DIR, origin_list) 70 | origin_list = [os.path.basename(i) for i in origin_list] 71 | target_list = generate_targetlist(origin_list) 72 | targets = download_target(target_list) 73 | print(f"\nTargets:") 74 | for i in targets: 75 | print(f"{i}\n{callcmd(f'soxi -e {i}')}{callcmd(f'soxi -d {i}')}") 76 | 77 | 78 | if __name__ == '__main__': 79 | main() 80 | --------------------------------------------------------------------------------