/* create a new subscriber */ static struct subscriber * newSubscriber(const char * eventurl, const char * callback, int callbacklen) { struct subscriber * tmp; if(!eventurl || !callback || !callbacklen) return NULL; tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1); if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0) tmp->service = EContentDirectory; else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0) tmp->service = EConnectionManager; else if(strcmp(eventurl, X_MS_MEDIARECEIVERREGISTRAR_EVENTURL)==0) tmp->service = EMSMediaReceiverRegistrar; else if (strcmp(eventurl, RENDERTRNSPORT_EVENTLURL) == 0) tmp->service = ERenderTransport; else if (strcmp(eventurl, RENDERCONTROL_PATH) == 0) tmp->service = ERenderControl; else { free(tmp); return NULL; } memcpy(tmp->callback, callback, callbacklen); tmp->callback[callbacklen] = '\0'; /* make a dummy uuid */ strncpyt(tmp->uuid, uuidvalue, sizeof(tmp->uuid)); if( get_uuid_string(tmp->uuid+5) != 0 ) { tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff); } return tmp; }
static char * save_resized_album_art(image_s *imsrc, const char *path) { int dstw, dsth; image_s *imdst; char *cache_file; char cache_dir[MAXPATHLEN]; if( !imsrc ) return NULL; if( art_cache_exists(path, &cache_file) ) return cache_file; strncpyt(cache_dir, cache_file, sizeof(cache_dir)); make_dir(dirname(cache_dir), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); if( imsrc->width > imsrc->height ) { dstw = 160; dsth = (imsrc->height<<8) / ((imsrc->width<<8)/160); } else { dstw = (imsrc->width<<8) / ((imsrc->height<<8)/160); dsth = 160; } imdst = image_resize(imsrc, dstw, dsth); if( !imdst ) goto error; if( image_save_to_jpeg_file(imdst, cache_file) == 0 ) { image_free(imdst); return cache_file; } else image_free(imdst); error: free(cache_file); return NULL; }
void check_for_captions(const char *path, int64_t detailID) { char file[MAXPATHLEN]; char *p; int ret; strncpyt(file, path, sizeof(file)); p = strip_ext(file); if (!p) p = strrchr(file, '\0'); /* If we weren't given a detail ID, look for one. */ if (!detailID) { detailID = sql_get_int64_field(db, "SELECT ID from DETAILS where (PATH > '%q.' and PATH <= '%q.z')" " and MIME glob 'video/*' limit 1", file, file); if (detailID <= 0) { //DPRINTF(E_MAXDEBUG, L_METADATA, "No file found for caption %s.\n", path); return; } } strcpy(p, ".srt"); ret = access(file, R_OK); if (ret != 0) { strcpy(p, ".smi"); ret = access(file, R_OK); } if (ret == 0) { sql_exec(db, "INSERT into CAPTIONS" " (ID, PATH) " "VALUES" " (%lld, %Q)", detailID, file); } }
/* init phase : * 1) read configuration file * 2) read command line arguments * 3) daemonize * 4) check and write pid file * 5) set startup time stamp * 6) compute presentation URL * 7) set signal handlers */ static int init(int argc, char * * argv) { int i; int pid; int debug_flag = 0; int verbose_flag = 0; int options_flag = 0; struct sigaction sa; const char * presurl = NULL; const char * optionsfile = "/etc/minidlna.conf"; char mac_str[13]; char * string, * word; enum media_types type; char * path; char buf[PATH_MAX]; char ip_addr[INET_ADDRSTRLEN + 3] = {'\0'}; char log_str[72] = "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn"; char *log_level = NULL; /* first check if "-f" option is used */ for(i=2; i<argc; i++) { if(0 == strcmp(argv[i-1], "-f")) { optionsfile = argv[i]; options_flag = 1; break; } } /* set up uuid based on mac address */ if( getsyshwaddr(mac_str, sizeof(mac_str)) < 0 ) { DPRINTF(E_OFF, L_GENERAL, "No MAC address found. Falling back to generic UUID.\n"); strcpy(mac_str, "554e4b4e4f57"); } strcpy(uuidvalue+5, "4d696e69-444c-164e-9d41-"); strncat(uuidvalue, mac_str, 12); getfriendlyname(friendly_name, FRIENDLYNAME_MAX_LEN); runtime_vars.port = -1; runtime_vars.notify_interval = 895; /* seconds between SSDP announces */ runtime_vars.root_container = NULL; /* read options file first since * command line arguments have final say */ if(readoptionsfile(optionsfile) < 0) { /* only error if file exists or using -f */ if(access(optionsfile, F_OK) == 0 || options_flag) DPRINTF(E_ERROR, L_GENERAL, "Error reading configuration file %s\n", optionsfile); } else { for(i=0; i<num_options; i++) { switch(ary_options[i].id) { case UPNPIFNAME: for( string = ary_options[i].value; (word = strtok(string, ",")); string = NULL ) { if(n_lan_addr < MAX_LAN_ADDR) { if(getifaddr(word, ip_addr, sizeof(ip_addr)) >= 0) { if( *ip_addr && parselanaddr(&lan_addr[n_lan_addr], ip_addr) == 0 ) if(n_lan_addr < MAX_LAN_ADDR) n_lan_addr++; } } else { DPRINTF(E_ERROR, L_GENERAL, "Too many listening ips (max: %d), ignoring %s\n", MAX_LAN_ADDR, word); } } break; case UPNPLISTENING_IP: if(n_lan_addr < MAX_LAN_ADDR) { if(parselanaddr(&lan_addr[n_lan_addr], ary_options[i].value) == 0) n_lan_addr++; } else { DPRINTF(E_ERROR, L_GENERAL, "Too many listening ips (max: %d), ignoring %s\n", MAX_LAN_ADDR, ary_options[i].value); } break; case UPNPPORT: runtime_vars.port = atoi(ary_options[i].value); break; case UPNPPRESENTATIONURL: presurl = ary_options[i].value; break; case UPNPNOTIFY_INTERVAL: runtime_vars.notify_interval = atoi(ary_options[i].value); break; case UPNPSERIAL: strncpyt(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN); break; case UPNPMODEL_NAME: strncpyt(modelname, ary_options[i].value, MODELNAME_MAX_LEN); break; case UPNPMODEL_NUMBER: strncpyt(modelnumber, ary_options[i].value, MODELNUMBER_MAX_LEN); break; case UPNPFRIENDLYNAME: strncpyt(friendly_name, ary_options[i].value, FRIENDLYNAME_MAX_LEN); break; case UPNPMEDIADIR: type = ALL_MEDIA; char * myval = NULL; switch( ary_options[i].value[0] ) { case 'A': case 'a': if( ary_options[i].value[0] == 'A' || ary_options[i].value[0] == 'a' ) type = AUDIO_ONLY; case 'V': case 'v': if( ary_options[i].value[0] == 'V' || ary_options[i].value[0] == 'v' ) type = VIDEO_ONLY; case 'P': case 'p': if( ary_options[i].value[0] == 'P' || ary_options[i].value[0] == 'p' ) type = IMAGES_ONLY; myval = index(ary_options[i].value, '/'); case '/': path = realpath(myval ? myval:ary_options[i].value, buf); if( !path ) path = (myval ? myval:ary_options[i].value); if( access(path, F_OK) != 0 ) { DPRINTF(E_ERROR, L_GENERAL, "Media directory \"%s\" not accessible! [%s]\n", path, strerror(errno)); break; } struct media_dir_s * this_dir = calloc(1, sizeof(struct media_dir_s)); this_dir->path = strdup(path); this_dir->type = type; if( !media_dirs ) { media_dirs = this_dir; } else { struct media_dir_s * all_dirs = media_dirs; while( all_dirs->next ) all_dirs = all_dirs->next; all_dirs->next = this_dir; } break; default: DPRINTF(E_ERROR, L_GENERAL, "Media directory entry not understood! [%s]\n", ary_options[i].value); break; } break; case UPNPALBUMART_NAMES: for( string = ary_options[i].value; (word = strtok(string, "/")); string = NULL ) { struct album_art_name_s * this_name = calloc(1, sizeof(struct album_art_name_s)); int len = strlen(word); if( word[len-1] == '*' ) { word[len-1] = '\0'; this_name->wildcard = 1; } this_name->name = strdup(word); if( !album_art_names ) { album_art_names = this_name; } else { struct album_art_name_s * all_names = album_art_names; while( all_names->next ) all_names = all_names->next; all_names->next = this_name; } } break; case UPNPDBDIR: path = realpath(ary_options[i].value, buf); if( !path ) path = (ary_options[i].value); make_dir(path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); if( access(path, F_OK) != 0 ) { DPRINTF(E_FATAL, L_GENERAL, "Database path not accessible! [%s]\n", path); break; } strncpyt(db_path, path, PATH_MAX); break; case UPNPLOGDIR: path = realpath(ary_options[i].value, buf); if( !path ) path = (ary_options[i].value); make_dir(path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); if( access(path, F_OK) != 0 ) { DPRINTF(E_FATAL, L_GENERAL, "Log path not accessible! [%s]\n", path); break; } strncpyt(log_path, path, PATH_MAX); break; case UPNPLOGLEVEL: log_level = ary_options[i].value; break; case UPNPINOTIFY: if( (strcmp(ary_options[i].value, "yes") != 0) && !atoi(ary_options[i].value) ) CLEARFLAG(INOTIFY_MASK); break; case ENABLE_TIVO: if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) ) SETFLAG(TIVO_MASK); break; case ENABLE_DLNA_STRICT: if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) ) SETFLAG(DLNA_STRICT_MASK); break; case ROOT_CONTAINER: switch( ary_options[i].value[0] ) { case '.': runtime_vars.root_container = NULL; break; case 'B': case 'b': runtime_vars.root_container = BROWSEDIR_ID; break; case 'M': case 'm': runtime_vars.root_container = MUSIC_ID; break; case 'V': case 'v': runtime_vars.root_container = VIDEO_ID; break; case 'P': case 'p': runtime_vars.root_container = IMAGE_ID; break; default: DPRINTF(E_ERROR, L_GENERAL, "Invalid root container! [%s]\n", ary_options[i].value); break; } break; case UPNPMINISSDPDSOCKET: minissdpdsocketpath = ary_options[i].value; break; default: DPRINTF(E_ERROR, L_GENERAL, "Unknown option in file %s\n", optionsfile); } } } if( log_path[0] == '\0' ) { if( db_path[0] == '\0' ) strncpyt(log_path, DEFAULT_LOG_PATH, PATH_MAX); else strncpyt(log_path, db_path, PATH_MAX); } if( db_path[0] == '\0' ) strncpyt(db_path, DEFAULT_DB_PATH, PATH_MAX); /* command line arguments processing */ for(i=1; i<argc; i++) { if(argv[i][0]!='-') { DPRINTF(E_ERROR, L_GENERAL, "Unknown option: %s\n", argv[i]); } else if(strcmp(argv[i], "--help")==0) { runtime_vars.port = 0; break; } else switch(argv[i][1]) { case 't': if(i+1 < argc) runtime_vars.notify_interval = atoi(argv[++i]); else DPRINTF(E_ERROR, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 's': if(i+1 < argc) strncpyt(serialnumber, argv[++i], SERIALNUMBER_MAX_LEN); else DPRINTF(E_ERROR, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'm': if(i+1 < argc) strncpyt(modelnumber, argv[++i], MODELNUMBER_MAX_LEN); else DPRINTF(E_ERROR, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'p': if(i+1 < argc) runtime_vars.port = atoi(argv[++i]); else DPRINTF(E_ERROR, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'P': if(i+1 < argc) pidfilename = argv[++i]; else DPRINTF(E_ERROR, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'd': debug_flag = 1; case 'v': verbose_flag = 1; break; case 'L': SETFLAG(NO_PLAYLIST_MASK); break; case 'w': if(i+1 < argc) presurl = argv[++i]; else DPRINTF(E_ERROR, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'a': if(i+1 < argc) { int address_already_there = 0; int j; i++; for(j=0; j<n_lan_addr; j++) { struct lan_addr_s tmpaddr; parselanaddr(&tmpaddr, argv[i]); if(0 == strcmp(lan_addr[j].str, tmpaddr.str)) address_already_there = 1; } if(address_already_there) break; if(n_lan_addr < MAX_LAN_ADDR) { if(parselanaddr(&lan_addr[n_lan_addr], argv[i]) == 0) n_lan_addr++; } else { DPRINTF(E_ERROR, L_GENERAL, "Too many listening ips (max: %d), ignoring %s\n", MAX_LAN_ADDR, argv[i]); } } else DPRINTF(E_ERROR, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'i': if(i+1 < argc) { int address_already_there = 0; int j; i++; if( getifaddr(argv[i], ip_addr, sizeof(ip_addr)) < 0 ) { DPRINTF(E_FATAL, L_GENERAL, "Required network interface '%s' not found.\n", argv[i]); } for(j=0; j<n_lan_addr; j++) { struct lan_addr_s tmpaddr; parselanaddr(&tmpaddr, ip_addr); if(0 == strcmp(lan_addr[j].str, tmpaddr.str)) address_already_there = 1; } if(address_already_there) break; if(n_lan_addr < MAX_LAN_ADDR) { if(parselanaddr(&lan_addr[n_lan_addr], ip_addr) == 0) n_lan_addr++; } else { DPRINTF(E_ERROR, L_GENERAL, "Too many listening ips (max: %d), ignoring %s\n", MAX_LAN_ADDR, argv[i]); } } else DPRINTF(E_ERROR, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'f': i++; /* discarding, the config file is already read */ break; case 'h': runtime_vars.port = 0; // triggers help display break; case 'R': snprintf(buf, sizeof(buf), "rm -rf %s/files.db %s/art_cache", db_path, db_path); if( system(buf) != 0 ) DPRINTF(E_WARN, L_GENERAL, "Failed to clean old file cache.\n"); break; case 'V': printf("Version " MINIDLNA_VERSION "\n"); exit(0); break; default: DPRINTF(E_ERROR, L_GENERAL, "Unknown option: %s\n", argv[i]); } } /* If no IP was specified, try to detect one */ if( n_lan_addr < 1 ) { if( (getsysaddr(ip_addr, sizeof(ip_addr)) < 0) && (getifaddr("eth0", ip_addr, sizeof(ip_addr)) < 0) && (getifaddr("eth1", ip_addr, sizeof(ip_addr)) < 0) ) { DPRINTF(E_OFF, L_GENERAL, "No IP address automatically detected!\n"); } if( *ip_addr && parselanaddr(&lan_addr[n_lan_addr], ip_addr) == 0 ) { n_lan_addr++; } } if( (n_lan_addr==0) || (runtime_vars.port<=0) ) { DPRINTF(E_ERROR, L_GENERAL, "Usage:\n\t" "%s [-d] [-v] [-f config_file]\n" "\t\t[-a listening_ip] [-p port]\n" /*"[-l logfile] " not functionnal */ "\t\t[-s serial] [-m model_number] \n" "\t\t[-t notify_interval] [-P pid_filename]\n" "\t\t[-w url] [-R] [-V] [-h]\n" "\nNotes:\n\tNotify interval is in seconds. Default is 895 seconds.\n" "\tDefault pid file is %s.\n" "\tWith -d minidlna will run in debug mode (not daemonize).\n" "\t-w sets the presentation url. Default is http address on port 80\n" "\t-h displays this text\n" "\t-R forces a full rescan\n" "\t-L do note create playlists\n" "\t-V print the version number\n", argv[0], pidfilename); return 1; } if( verbose_flag ) { strcpy(log_str+65, "debug"); log_level = log_str; } else if( !log_level ) { log_level = log_str; } if(debug_flag) { pid = getpid(); log_init(NULL, log_level); } else { pid = daemonize(); #ifdef READYNAS log_init("/var/log/upnp-av.log", log_level); #else if( access(db_path, F_OK) != 0 ) make_dir(db_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); sprintf(buf, "%s/minidlna.log", log_path); log_init(buf, log_level); #endif } if(checkforrunning(pidfilename) < 0) { DPRINTF(E_ERROR, L_GENERAL, "MiniDLNA is already running. EXITING.\n"); return 1; } set_startup_time(); /* presentation url */ if(presurl) strncpyt(presentationurl, presurl, PRESENTATIONURL_MAX_LEN); else strcpy(presentationurl, "/"); /* set signal handler */ memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sigterm; if (sigaction(SIGTERM, &sa, NULL)) { DPRINTF(E_FATAL, L_GENERAL, "Failed to set SIGTERM handler. EXITING.\n"); } if (sigaction(SIGINT, &sa, NULL)) { DPRINTF(E_FATAL, L_GENERAL, "Failed to set SIGINT handler. EXITING.\n"); } if(signal(SIGPIPE, SIG_IGN) == SIG_ERR) { DPRINTF(E_FATAL, L_GENERAL, "Failed to ignore SIGPIPE signals. EXITING.\n"); } writepidfile(pidfilename, pid); return 0; }
static void getfriendlyname(char * buf, int len) { char * dot = NULL; char * hn = calloc(1, 256); int off; if( gethostname(hn, 256) == 0 ) { strncpyt(buf, hn, len); dot = strchr(buf, '.'); if( dot ) *dot = '\0'; } else { strcpy(buf, "Unknown"); } free(hn); off = strlen(buf); off += snprintf(buf+off, len-off, ": "); #ifdef READYNAS FILE * info; char ibuf[64], *key, *val; snprintf(buf+off, len-off, "ReadyNAS"); info = fopen("/proc/sys/dev/boot/info", "r"); if( !info ) return; while( (val = fgets(ibuf, 64, info)) != NULL ) { key = strsep(&val, ": \t"); val = trim(val); if( strcmp(key, "model") == 0 ) { snprintf(buf+off, len-off, "%s", val); key = strchr(val, ' '); if( key ) { strncpyt(modelnumber, key+1, MODELNUMBER_MAX_LEN); *key = '\0'; } snprintf(modelname, MODELNAME_MAX_LEN, "Windows Media Connect compatible (%s)", val); } else if( strcmp(key, "serial") == 0 ) { strncpyt(serialnumber, val, SERIALNUMBER_MAX_LEN); if( serialnumber[0] == '\0' ) { char mac_str[13]; if( getsyshwaddr(mac_str, sizeof(mac_str)) == 0 ) strcpy(serialnumber, mac_str); else strcpy(serialnumber, "0"); } break; } } fclose(info); memcpy(pnpx_hwid+4, "01F2", 4); if( strcmp(modelnumber, "NVX") == 0 ) memcpy(pnpx_hwid+17, "0101", 4); else if( strcmp(modelnumber, "Pro") == 0 || strcmp(modelnumber, "Pro 6") == 0 || strncmp(modelnumber, "Ultra 6", 7) == 0 ) memcpy(pnpx_hwid+17, "0102", 4); else if( strcmp(modelnumber, "Pro 2") == 0 || strncmp(modelnumber, "Ultra 2", 7) == 0 ) memcpy(pnpx_hwid+17, "0103", 4); else if( strcmp(modelnumber, "Pro 4") == 0 || strncmp(modelnumber, "Ultra 4", 7) == 0 ) memcpy(pnpx_hwid+17, "0104", 4); else if( strcmp(modelnumber+1, "100") == 0 ) memcpy(pnpx_hwid+17, "0105", 4); else if( strcmp(modelnumber+1, "200") == 0 ) memcpy(pnpx_hwid+17, "0106", 4); /* 0107 = Stora */ else if( strcmp(modelnumber, "Duo v2") == 0 ) memcpy(pnpx_hwid+17, "0108", 4); else if( strcmp(modelnumber, "NV+ v2") == 0 ) memcpy(pnpx_hwid+17, "0109", 4); #else char * logname; logname = getenv("LOGNAME"); #ifndef STATIC // Disable for static linking if( !logname ) { struct passwd * pwent; pwent = getpwuid(getuid()); if( pwent ) logname = pwent->pw_name; } #endif snprintf(buf+off, len-off, "%s", logname?logname:"Unknown"); #endif }
static int writepidfile(const char *fname, int pid, uid_t uid) { FILE *pidfile; struct stat st; char path[PATH_MAX], *dir; int ret = 0; if(!fname || *fname == '\0') return -1; /* Create parent directory if it doesn't already exist */ strncpyt(path, fname, sizeof(path)); dir = dirname(path); if (stat(dir, &st) == 0) { if (!S_ISDIR(st.st_mode)) { DPRINTF(E_ERROR, L_GENERAL, "Pidfile path is not a directory: %s\n", fname); return -1; } } else { if (make_dir(dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) != 0) { DPRINTF(E_ERROR, L_GENERAL, "Unable to create pidfile directory: %s\n", fname); return -1; } if (uid >= 0) { if (chown(dir, uid, -1) != 0) DPRINTF(E_WARN, L_GENERAL, "Unable to change pidfile ownership: %s\n", dir, strerror(errno)); } } pidfile = fopen(fname, "w"); if (!pidfile) { DPRINTF(E_ERROR, L_GENERAL, "Unable to open pidfile for writing %s: %s\n", fname, strerror(errno)); return -1; } if (fprintf(pidfile, "%d\n", pid) <= 0) { DPRINTF(E_ERROR, L_GENERAL, "Unable to write to pidfile %s: %s\n", fname); ret = -1; } if (uid >= 0) { if (fchown(fileno(pidfile), uid, -1) != 0) DPRINTF(E_WARN, L_GENERAL, "Unable to change pidfile ownership: %s\n", pidfile, strerror(errno)); } fclose(pidfile); return ret; }
int64_t GetImageMetadata(const char *path, char *name) { ExifData *ed; ExifEntry *e = NULL; ExifLoader *l; struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; FILE *infile; int width=0, height=0, thumb=0; char make[32], model[64] = {'\0'}; char b[1024]; struct stat file; int64_t ret; image_s *imsrc; metadata_t m; uint32_t free_flags = 0xFFFFFFFF; memset(&m, '\0', sizeof(metadata_t)); //DEBUG DPRINTF(E_DEBUG, L_METADATA, "Parsing %s...\n", path); if ( stat(path, &file) != 0 ) return 0; strip_ext(name); //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * size: %jd\n", file.st_size); /* MIME hard-coded to JPEG for now, until we add PNG support */ m.mime = strdup("image/jpeg"); l = exif_loader_new(); exif_loader_write_file(l, path); ed = exif_loader_get_data(l); exif_loader_unref(l); if( !ed ) goto no_exifdata; e = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL); if( e || (e = exif_content_get_entry(ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_DIGITIZED)) ) { m.date = strdup(exif_entry_get_value(e, b, sizeof(b))); if( strlen(m.date) > 10 ) { m.date[4] = '-'; m.date[7] = '-'; m.date[10] = 'T'; } else { free(m.date); m.date = NULL; } } else { /* One last effort to get the date from XMP */ image_get_jpeg_date_xmp(path, &m.date); } //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * date: %s\n", m.date); e = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_MAKE); if( e ) { strncpyt(make, exif_entry_get_value(e, b, sizeof(b)), sizeof(make)); e = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_MODEL); if( e ) { strncpyt(model, exif_entry_get_value(e, b, sizeof(b)), sizeof(model)); if( !strcasestr(model, make) ) snprintf(model, sizeof(model), "%s %s", make, exif_entry_get_value(e, b, sizeof(b))); m.creator = escape_tag(trim(model), 1); } } //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * model: %s\n", model); e = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION); if( e ) { int rotate; switch( exif_get_short(e->data, exif_data_get_byte_order(ed)) ) { case 3: rotate = 180; break; case 6: rotate = 90; break; case 8: rotate = 270; break; default: rotate = 0; break; } if( rotate ) xasprintf(&m.rotation, "%d", rotate); } if( ed->size ) { /* We might need to verify that the thumbnail is 160x160 or smaller */ if( ed->size > 12000 ) { imsrc = image_new_from_jpeg(NULL, 0, (char *)ed->data, ed->size, 1, ROTATE_NONE); if( imsrc ) { if( (imsrc->width <= 160) && (imsrc->height <= 160) ) thumb = 1; image_free(imsrc); } } else { thumb = 1; //- 20130708 Sungmin add if(ed->data && ed->size) { char* art_file; if( !thumb_cache_exists(path, &art_file) ) { char cache_dir[MAXPATHLEN]; strncpyt(cache_dir, art_file, sizeof(cache_dir)); make_dir(dirname(cache_dir), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); FILE *thumb = fopen(art_file, "wb"); //DPRINTF(E_WARN, L_METADATA, " * cache_dir: %s\n", cache_dir); //DPRINTF(E_WARN, L_METADATA, " * thumbnail: %s\n", art_file); if(thumb) { fwrite(ed->data, 1, ed->size, thumb); fclose(thumb); } } free(art_file); } } } //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * thumbnail: %d\n", thumb); exif_data_unref(ed); no_exifdata: /* If SOF parsing fails, then fall through to reading the JPEG data with libjpeg to get the resolution */ if( image_get_jpeg_resolution(path, &width, &height) != 0 || !width || !height ) { infile = fopen(path, "r"); if( infile ) { cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = libjpeg_error_handler; jpeg_create_decompress(&cinfo); if( setjmp(setjmp_buffer) ) goto error; jpeg_stdio_src(&cinfo, infile); jpeg_read_header(&cinfo, TRUE); jpeg_start_decompress(&cinfo); width = cinfo.output_width; height = cinfo.output_height; error: jpeg_destroy_decompress(&cinfo); fclose(infile); } } //DEBUG DPRINTF(E_DEBUG, L_METADATA, " * resolution: %dx%d\n", width, height); if( !width || !height ) { free_metadata(&m, free_flags); return 0; } if( width <= 640 && height <= 480 ) m.dlna_pn = strdup("JPEG_SM"); else if( width <= 1024 && height <= 768 ) m.dlna_pn = strdup("JPEG_MED"); else if( (width <= 4096 && height <= 4096) || !GETFLAG(DLNA_STRICT_MASK) ) m.dlna_pn = strdup("JPEG_LRG"); xasprintf(&m.resolution, "%dx%d", width, height); ret = sql_exec(db, "INSERT into DETAILS" " (PATH, TITLE, SIZE, TIMESTAMP, DATE, RESOLUTION," " ROTATION, THUMBNAIL, CREATOR, DLNA_PN, MIME) " "VALUES" " (%Q, '%q', %lld, %ld, %Q, %Q, %Q, %d, %Q, %Q, %Q);", path, name, (long long)file.st_size, file.st_mtime, m.date, m.resolution, m.rotation, thumb, m.creator, m.dlna_pn, m.mime); if( ret != SQLITE_OK ) { fprintf(stderr, "Error inserting details for '%s'!\n", path); ret = 0; } else { ret = sqlite3_last_insert_rowid(db); } free_metadata(&m, free_flags); return ret; }
int64_t GetAudioMetadata(const char *path, char *name) { char type[4]; static char lang[6] = { '\0' }; struct stat file; int64_t ret; char *esc_tag; int i; int64_t album_art = 0; struct song_metadata song; metadata_t m; uint32_t free_flags = FLAG_MIME|FLAG_DURATION|FLAG_DLNA_PN|FLAG_DATE; memset(&m, '\0', sizeof(metadata_t)); if ( stat(path, &file) != 0 ) return 0; strip_ext(name); if( ends_with(path, ".mp3") ) { strcpy(type, "mp3"); m.mime = strdup("audio/mpeg"); } else if( ends_with(path, ".m4a") || ends_with(path, ".mp4") || ends_with(path, ".aac") || ends_with(path, ".m4p") ) { strcpy(type, "aac"); m.mime = strdup("audio/mp4"); } else if( ends_with(path, ".3gp") ) { strcpy(type, "aac"); m.mime = strdup("audio/3gpp"); } else if( ends_with(path, ".wma") || ends_with(path, ".asf") ) { strcpy(type, "asf"); m.mime = strdup("audio/x-ms-wma"); } else if( ends_with(path, ".flac") || ends_with(path, ".fla") || ends_with(path, ".flc") ) { strcpy(type, "flc"); m.mime = strdup("audio/x-flac"); } else if( ends_with(path, ".wav") ) { strcpy(type, "wav"); m.mime = strdup("audio/x-wav"); } else if( ends_with(path, ".ogg") || ends_with(path, ".oga") ) { strcpy(type, "ogg"); m.mime = strdup("audio/ogg"); } else if( ends_with(path, ".pcm") ) { strcpy(type, "pcm"); m.mime = strdup("audio/L16"); } else { DPRINTF(E_WARN, L_GENERAL, "Unhandled file extension on %s\n", path); return 0; } if( !(*lang) ) { if( !getenv("LANG") ) strcpy(lang, "en_US"); else strncpyt(lang, getenv("LANG"), sizeof(lang)); } if( readtags((char *)path, &song, &file, lang, type) != 0 ) { DPRINTF(E_WARN, L_GENERAL, "Cannot extract tags from %s!\n", path); freetags(&song); free_metadata(&m, free_flags); return 0; } if( song.dlna_pn ) m.dlna_pn = strdup(song.dlna_pn); if( song.year ) xasprintf(&m.date, "%04d-01-01", song.year); xasprintf(&m.duration, "%d:%02d:%02d.%03d", (song.song_length/3600000), (song.song_length/60000%60), (song.song_length/1000%60), (song.song_length%1000)); if( song.title && *song.title ) { m.title = trim(song.title); if( (esc_tag = escape_tag(m.title, 0)) ) { free_flags |= FLAG_TITLE; m.title = esc_tag; } } else { m.title = name; } for( i=ROLE_START; i<N_ROLE; i++ ) { if( song.contributor[i] && *song.contributor[i] ) { m.creator = trim(song.contributor[i]); if( strlen(m.creator) > 48 ) { m.creator = strdup("Various Artists"); free_flags |= FLAG_CREATOR; } else if( (esc_tag = escape_tag(m.creator, 0)) ) { m.creator = esc_tag; free_flags |= FLAG_CREATOR; } m.artist = m.creator; break; } } /* If there is a band associated with the album, use it for virtual containers. */ if( (i != ROLE_BAND) && (i != ROLE_ALBUMARTIST) ) { if( song.contributor[ROLE_BAND] && *song.contributor[ROLE_BAND] ) { i = ROLE_BAND; m.artist = trim(song.contributor[i]); if( strlen(m.artist) > 48 ) { m.artist = strdup("Various Artists"); free_flags |= FLAG_ARTIST; } else if( (esc_tag = escape_tag(m.artist, 0)) ) { m.artist = esc_tag; free_flags |= FLAG_ARTIST; } } } if( song.album && *song.album ) { m.album = trim(song.album); if( (esc_tag = escape_tag(m.album, 0)) ) { free_flags |= FLAG_ALBUM; m.album = esc_tag; } } if( song.genre && *song.genre ) { m.genre = trim(song.genre); if( (esc_tag = escape_tag(m.genre, 0)) ) { free_flags |= FLAG_GENRE; m.genre = esc_tag; } } if( song.comment && *song.comment ) { m.comment = trim(song.comment); if( (esc_tag = escape_tag(m.comment, 0)) ) { free_flags |= FLAG_COMMENT; m.comment = esc_tag; } } album_art = find_album_art(path, song.image, song.image_size); ret = sql_exec(db, "INSERT into DETAILS" " (PATH, SIZE, TIMESTAMP, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE," " TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, DISC, TRACK, DLNA_PN, MIME, ALBUM_ART) " "VALUES" " (%Q, %lld, %ld, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, %d, %Q, '%s', %lld);", path, (long long)file.st_size, file.st_mtime, m.duration, song.channels, song.bitrate, song.samplerate, m.date, m.title, m.creator, m.artist, m.album, m.genre, m.comment, song.disc, song.track, m.dlna_pn, song.mime?song.mime:m.mime, album_art); if( ret != SQLITE_OK ) { fprintf(stderr, "Error inserting details for '%s'!\n", path); ret = 0; } else { ret = sqlite3_last_insert_rowid(db); } freetags(&song); free_metadata(&m, free_flags); return ret; }
int readoptionsfile(const char * fname) { FILE *hfile = NULL; char buffer[1024]; char *equals; char *name; char *value; char *t; int linenum = 0; int i; enum upnpconfigoptions id; if(!fname || *fname == '\0') return -1; memset(buffer, 0, sizeof(buffer)); #ifdef DEBUG printf("Reading configuration from file %s\n", fname); #endif if(!(hfile = fopen(fname, "r"))) return -1; while(fgets(buffer, sizeof(buffer), hfile)) { linenum++; t = strchr(buffer, '\n'); if(t) { *t = '\0'; t--; while((t >= buffer) && isspace(*t)) { *t = '\0'; t--; } } /* skip leading whitespaces */ name = buffer; while(isspace(*name)) name++; /* check for comments or empty lines */ if(name[0] == '#' || name[0] == '\0') continue; if(!(equals = strchr(name, '='))) { fprintf(stderr, "parsing error file %s line %d : %s\n", fname, linenum, name); continue; } /* remove ending whitespaces */ for(t=equals-1; t>name && isspace(*t); t--) *t = '\0'; *equals = '\0'; value = equals+1; /* skip leading whitespaces */ while(isspace(*value)) value++; id = UPNP_INVALID; for(i=0; i<sizeof(optionids)/sizeof(optionids[0]); i++) { /*printf("%2d %2d %s %s\n", i, optionids[i].id, name, optionids[i].name); */ if(0 == strcmp(name, optionids[i].name)) { id = optionids[i].id; break; } } if(id == UPNP_INVALID) { if (strcmp(name, "include") == 0) readoptionsfile(value); else fprintf(stderr, "parsing error file %s line %d : %s=%s\n", fname, linenum, name, value); } else { num_options++; t = realloc(ary_options, num_options * sizeof(struct option)); if(!t) { fprintf(stderr, "memory allocation error: %s=%s\n", name, value); num_options--; continue; } else ary_options = (struct option *)t; ary_options[num_options-1].id = id; strncpyt(ary_options[num_options-1].value, value, MAX_OPTION_VALUE_LEN); } } fclose(hfile); return 0; }
/* init phase : * 1) read configuration file * 2) read command line arguments * 3) daemonize * 4) check and write pid file * 5) set startup time stamp * 6) compute presentation URL * 7) set signal handlers */ static int init(int argc, char **argv) { int i; int pid; int debug_flag = 0; int verbose_flag = 0; int options_flag = 0; struct sigaction sa; const char * presurl = NULL; const char * optionsfile = "/etc/minidlna.conf"; char mac_str[13]; char *string, *word; char *path; char buf[PATH_MAX]; char log_str[75] = "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn"; char *log_level = NULL; struct media_dir_s *media_dir; int ifaces = 0; media_types types; uid_t uid = 0; /* first check if "-f" option is used */ for (i=2; i<argc; i++) { if (strcmp(argv[i-1], "-f") == 0) { optionsfile = argv[i]; options_flag = 1; break; } } /* set up uuid based on mac address */ if (getsyshwaddr(mac_str, sizeof(mac_str)) < 0) { DPRINTF(E_OFF, L_GENERAL, "No MAC address found. Falling back to generic UUID.\n"); strcpy(mac_str, "554e4b4e4f57"); } strcpy(uuidvalue+5, "4d696e69-444c-164e-9d41-"); strncat(uuidvalue, mac_str, 12); getfriendlyname(friendly_name, FRIENDLYNAME_MAX_LEN); runtime_vars.port = 8200; runtime_vars.notify_interval = 895; /* seconds between SSDP announces */ runtime_vars.max_connections = 50; runtime_vars.root_container = NULL; runtime_vars.ifaces[0] = NULL; /* read options file first since * command line arguments have final say */ if (readoptionsfile(optionsfile) < 0) { /* only error if file exists or using -f */ if(access(optionsfile, F_OK) == 0 || options_flag) DPRINTF(E_FATAL, L_GENERAL, "Error reading configuration file %s\n", optionsfile); } for (i=0; i<num_options; i++) { switch (ary_options[i].id) { case UPNPIFNAME: for (string = ary_options[i].value; (word = strtok(string, ",")); string = NULL) { if (ifaces >= MAX_LAN_ADDR) { DPRINTF(E_ERROR, L_GENERAL, "Too many interfaces (max: %d), ignoring %s\n", MAX_LAN_ADDR, word); break; } runtime_vars.ifaces[ifaces++] = word; } break; case UPNPPORT: runtime_vars.port = atoi(ary_options[i].value); break; case UPNPPRESENTATIONURL: presurl = ary_options[i].value; break; case UPNPNOTIFY_INTERVAL: runtime_vars.notify_interval = atoi(ary_options[i].value); break; case UPNPSERIAL: strncpyt(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN); break; case UPNPMODEL_NAME: strncpyt(modelname, ary_options[i].value, MODELNAME_MAX_LEN); break; case UPNPMODEL_NUMBER: strncpyt(modelnumber, ary_options[i].value, MODELNUMBER_MAX_LEN); break; case UPNPFRIENDLYNAME: strncpyt(friendly_name, ary_options[i].value, FRIENDLYNAME_MAX_LEN); break; case UPNPMEDIADIR: types = ALL_MEDIA; path = ary_options[i].value; word = strchr(path, ','); if (word && (access(path, F_OK) != 0)) { types = 0; while (*path) { if (*path == ',') { path++; break; } else if (*path == 'A' || *path == 'a') types |= TYPE_AUDIO; else if (*path == 'V' || *path == 'v') types |= TYPE_VIDEO; else if (*path == 'P' || *path == 'p') types |= TYPE_IMAGES; else DPRINTF(E_FATAL, L_GENERAL, "Media directory entry not understood [%s]\n", ary_options[i].value); path++; } } path = realpath(path, buf); if (!path || access(path, F_OK) != 0) { DPRINTF(E_ERROR, L_GENERAL, "Media directory \"%s\" not accessible [%s]\n", ary_options[i].value, strerror(errno)); break; } media_dir = calloc(1, sizeof(struct media_dir_s)); media_dir->path = strdup(path); media_dir->types = types; if (media_dirs) { struct media_dir_s *all_dirs = media_dirs; while( all_dirs->next ) all_dirs = all_dirs->next; all_dirs->next = media_dir; } else media_dirs = media_dir; break; case UPNPALBUMART_NAMES: for (string = ary_options[i].value; (word = strtok(string, "/")); string = NULL) { struct album_art_name_s * this_name = calloc(1, sizeof(struct album_art_name_s)); int len = strlen(word); if (word[len-1] == '*') { word[len-1] = '\0'; this_name->wildcard = 1; } this_name->name = strdup(word); if (album_art_names) { struct album_art_name_s * all_names = album_art_names; while( all_names->next ) all_names = all_names->next; all_names->next = this_name; } else album_art_names = this_name; } break; case UPNPDBDIR: path = realpath(ary_options[i].value, buf); if (!path) path = (ary_options[i].value); make_dir(path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); if (access(path, F_OK) != 0) DPRINTF(E_FATAL, L_GENERAL, "Database path not accessible! [%s]\n", path); strncpyt(db_path, path, PATH_MAX); break; case UPNPLOGDIR: path = realpath(ary_options[i].value, buf); if (!path) path = (ary_options[i].value); make_dir(path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); if (access(path, F_OK) != 0) DPRINTF(E_FATAL, L_GENERAL, "Log path not accessible! [%s]\n", path); strncpyt(log_path, path, PATH_MAX); break; case UPNPLOGLEVEL: log_level = ary_options[i].value; break; case UPNPINOTIFY: if (!strtobool(ary_options[i].value)) CLEARFLAG(INOTIFY_MASK); break; case ENABLE_TIVO: if (strtobool(ary_options[i].value)) SETFLAG(TIVO_MASK); break; case ENABLE_DLNA_STRICT: if (strtobool(ary_options[i].value)) SETFLAG(DLNA_STRICT_MASK); break; case ROOT_CONTAINER: switch (ary_options[i].value[0]) { case '.': runtime_vars.root_container = NULL; break; case 'B': case 'b': runtime_vars.root_container = BROWSEDIR_ID; break; case 'M': case 'm': runtime_vars.root_container = MUSIC_ID; break; case 'V': case 'v': runtime_vars.root_container = VIDEO_ID; break; case 'P': case 'p': runtime_vars.root_container = IMAGE_ID; break; default: runtime_vars.root_container = ary_options[i].value; DPRINTF(E_WARN, L_GENERAL, "Using arbitrary root container [%s]\n", ary_options[i].value); break; } break; case UPNPMINISSDPDSOCKET: minissdpdsocketpath = ary_options[i].value; break; case UPNPUUID: strcpy(uuidvalue+5, ary_options[i].value); break; case USER_ACCOUNT: uid = strtoul(ary_options[i].value, &string, 0); if (*string) { /* Symbolic username given, not UID. */ struct passwd *entry = getpwnam(ary_options[i].value); if (!entry) DPRINTF(E_FATAL, L_GENERAL, "Bad user '%s'.\n", argv[i]); uid = entry->pw_uid; } break; case FORCE_SORT_CRITERIA: force_sort_criteria = ary_options[i].value; break; case MAX_CONNECTIONS: runtime_vars.max_connections = atoi(ary_options[i].value); break; case MERGE_MEDIA_DIRS: if (strtobool(ary_options[i].value)) SETFLAG(MERGE_MEDIA_DIRS_MASK); break; default: DPRINTF(E_ERROR, L_GENERAL, "Unknown option in file %s\n", optionsfile); } } if (log_path[0] == '\0') { if (db_path[0] == '\0') strncpyt(log_path, DEFAULT_LOG_PATH, PATH_MAX); else strncpyt(log_path, db_path, PATH_MAX); } if (db_path[0] == '\0') strncpyt(db_path, DEFAULT_DB_PATH, PATH_MAX); /* command line arguments processing */ for (i=1; i<argc; i++) { if (argv[i][0] != '-') { DPRINTF(E_FATAL, L_GENERAL, "Unknown option: %s\n", argv[i]); } else if (strcmp(argv[i], "--help") == 0) { runtime_vars.port = -1; break; } else switch(argv[i][1]) { case 't': if (i+1 < argc) runtime_vars.notify_interval = atoi(argv[++i]); else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 's': if (i+1 < argc) strncpyt(serialnumber, argv[++i], SERIALNUMBER_MAX_LEN); else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'm': if (i+1 < argc) strncpyt(modelnumber, argv[++i], MODELNUMBER_MAX_LEN); else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'p': if (i+1 < argc) runtime_vars.port = atoi(argv[++i]); else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'P': if (i+1 < argc) { if (argv[++i][0] != '/') DPRINTF(E_FATAL, L_GENERAL, "Option -%c requires an absolute filename.\n", argv[i-1][1]); else pidfilename = argv[i]; } else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'd': debug_flag = 1; case 'v': verbose_flag = 1; break; case 'L': SETFLAG(NO_PLAYLIST_MASK); break; case 'w': if (i+1 < argc) presurl = argv[++i]; else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'i': if (i+1 < argc) { i++; if (ifaces >= MAX_LAN_ADDR) { DPRINTF(E_ERROR, L_GENERAL, "Too many interfaces (max: %d), ignoring %s\n", MAX_LAN_ADDR, argv[i]); break; } runtime_vars.ifaces[ifaces++] = argv[i]; } else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; case 'f': i++; /* discarding, the config file is already read */ break; case 'h': runtime_vars.port = -1; // triggers help display break; case 'R': snprintf(buf, sizeof(buf), "rm -rf %s/files.db %s/art_cache", db_path, db_path); if (system(buf) != 0) DPRINTF(E_FATAL, L_GENERAL, "Failed to clean old file cache. EXITING\n"); break; case 'u': if (i+1 != argc) { i++; uid = strtoul(argv[i], &string, 0); if (*string) { /* Symbolic username given, not UID. */ struct passwd *entry = getpwnam(argv[i]); if (!entry) DPRINTF(E_FATAL, L_GENERAL, "Bad user '%s'.\n", argv[i]); uid = entry->pw_uid; } } else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; break; #ifdef __linux__ case 'S': SETFLAG(SYSTEMD_MASK); break; #endif case 'V': printf("Version " MINIDLNA_VERSION "\n"); exit(0); break; default: DPRINTF(E_ERROR, L_GENERAL, "Unknown option: %s\n", argv[i]); runtime_vars.port = -1; // triggers help display } } if (runtime_vars.port <= 0) { printf("Usage:\n\t" "%s [-d] [-v] [-f config_file] [-p port]\n" "\t\t[-i network_interface] [-u uid_to_run_as]\n" "\t\t[-t notify_interval] [-P pid_filename]\n" "\t\t[-s serial] [-m model_number]\n" #ifdef __linux__ "\t\t[-w url] [-R] [-L] [-S] [-V] [-h]\n" #else "\t\t[-w url] [-R] [-L] [-V] [-h]\n" #endif "\nNotes:\n\tNotify interval is in seconds. Default is 895 seconds.\n" "\tDefault pid file is %s.\n" "\tWith -d minidlna will run in debug mode (not daemonize).\n" "\t-w sets the presentation url. Default is http address on port 80\n" "\t-v enables verbose output\n" "\t-h displays this text\n" "\t-R forces a full rescan\n" "\t-L do not create playlists\n" #ifdef __linux__ "\t-S changes behaviour for systemd\n" #endif "\t-V print the version number\n", argv[0], pidfilename); return 1; } if (verbose_flag) { strcpy(log_str+65, "debug"); log_level = log_str; } else if (!log_level) log_level = log_str; /* Set the default log file path to NULL (stdout) */ path = NULL; if (debug_flag) { pid = getpid(); strcpy(log_str+65, "maxdebug"); log_level = log_str; } else if (GETFLAG(SYSTEMD_MASK)) { pid = getpid(); } else { pid = process_daemonize(); #ifdef READYNAS unlink("/ramfs/.upnp-av_scan"); path = "/var/log/upnp-av.log"; #else if (access(db_path, F_OK) != 0) make_dir(db_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); snprintf(buf, sizeof(buf), "%s/minidlna.log", log_path); path = buf; #endif } log_init(path, log_level); if (process_check_if_running(pidfilename) < 0) { DPRINTF(E_ERROR, L_GENERAL, SERVER_NAME " is already running. EXITING.\n"); return 1; } set_startup_time(); /* presentation url */ if (presurl) strncpyt(presentationurl, presurl, PRESENTATIONURL_MAX_LEN); else strcpy(presentationurl, "/"); /* set signal handlers */ memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sigterm; if (sigaction(SIGTERM, &sa, NULL)) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGTERM"); if (sigaction(SIGINT, &sa, NULL)) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGINT"); if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGPIPE"); if (signal(SIGHUP, &sighup) == SIG_ERR) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGHUP"); signal(SIGUSR1, &sigusr1); sa.sa_handler = process_handle_child_termination; if (sigaction(SIGCHLD, &sa, NULL)) DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGCHLD"); if (writepidfile(pidfilename, pid, uid) != 0) pidfilename = NULL; if (uid > 0) { struct stat st; if (stat(db_path, &st) == 0 && st.st_uid != uid && chown(db_path, uid, -1) != 0) DPRINTF(E_ERROR, L_GENERAL, "Unable to set db_path [%s] ownership to %d: %s\n", db_path, uid, strerror(errno)); } if (uid > 0 && setuid(uid) == -1) DPRINTF(E_FATAL, L_GENERAL, "Failed to switch to uid '%d'. [%s] EXITING.\n", uid, strerror(errno)); children = calloc(runtime_vars.max_connections, sizeof(struct child)); if (!children) { DPRINTF(E_ERROR, L_GENERAL, "Allocation failed\n"); return 1; } // remove working flag remove_scantag(); return 0; }
/* And our main album art functions */ void update_if_album_art(const char *path) { char *dir; char *match; char file[MAXPATHLEN]; char fpath[MAXPATHLEN]; char dpath[MAXPATHLEN]; int ncmp = 0; int album_art; DIR *dh; struct dirent *dp; enum file_types type = TYPE_UNKNOWN; int64_t art_id = 0; int ret; strncpyt(fpath, path, sizeof(fpath)); match = basename(fpath); /* Check if this file name matches a specific audio or video file */ if( ends_with(match, ".cover.jpg") ) { ncmp = strlen(match)-10; } else { ncmp = strrchr(match, '.') - match; } /* Check if this file name matches one of the default album art names */ album_art = is_album_art(match); strncpyt(dpath, path, sizeof(dpath)); dir = dirname(dpath); dh = opendir(dir); if( !dh ) return; while ((dp = readdir(dh)) != NULL) { switch( dp->d_type ) { case DT_REG: type = TYPE_FILE; break; case DT_LNK: case DT_UNKNOWN: snprintf(file, sizeof(file), "%s/%s", dir, dp->d_name); type = resolve_unknown_type(file, ALL_MEDIA); break; default: type = TYPE_UNKNOWN; break; } if( type != TYPE_FILE ) continue; if( (*(dp->d_name) != '.') && (is_video(dp->d_name) || is_audio(dp->d_name)) && (album_art || strncmp(dp->d_name, match, ncmp) == 0) ) { DPRINTF(E_DEBUG, L_METADATA, "New file %s looks like cover art for %s\n", path, dp->d_name); snprintf(file, sizeof(file), "%s/%s", dir, dp->d_name); art_id = find_album_art(file, NULL, 0); ret = sql_exec(db, "UPDATE DETAILS set ALBUM_ART = %lld where PATH = '%q'", (long long)art_id, file); if( ret != SQLITE_OK ) DPRINTF(E_WARN, L_METADATA, "Error setting %s as cover art for %s\n", match, dp->d_name); } } closedir(dh); }
static char * check_for_album_file(const char *path) { char file[MAXPATHLEN]; char mypath[MAXPATHLEN]; struct album_art_name_s *album_art_name; image_s *imsrc = NULL; int width=0, height=0; char *art_file; const char *dir; struct stat st; int ret; if( stat(path, &st) != 0 ) return NULL; if( S_ISDIR(st.st_mode) ) { dir = path; goto check_dir; } strncpyt(mypath, path, sizeof(mypath)); dir = dirname(mypath); /* First look for file-specific cover art */ snprintf(file, sizeof(file), "%s.cover.jpg", path); ret = access(file, R_OK); if( ret != 0 ) { strncpyt(file, path, sizeof(file)); art_file = strrchr(file, '.'); if( art_file ) { strcpy(art_file, ".jpg"); ret = access(file, R_OK); } if( ret != 0 ) { art_file = strrchr(file, '/'); if( art_file ) { memmove(art_file+2, art_file+1, file+MAXPATHLEN-art_file-2); art_file[1] = '.'; ret = access(file, R_OK); } } } if( ret == 0 ) { if( art_cache_exists(file, &art_file) ) goto existing_file; free(art_file); imsrc = image_new_from_jpeg(file, 1, NULL, 0, 1, ROTATE_NONE); if( imsrc ) goto found_file; } check_dir: /* Then fall back to possible generic cover art file names */ for( album_art_name = album_art_names; album_art_name; album_art_name = album_art_name->next ) { snprintf(file, sizeof(file), "%s/%s", dir, album_art_name->name); if( access(file, R_OK) == 0 ) { if( art_cache_exists(file, &art_file) ) { existing_file: return art_file; } free(art_file); imsrc = image_new_from_jpeg(file, 1, NULL, 0, 1, ROTATE_NONE); if( !imsrc ) continue; found_file: width = imsrc->width; height = imsrc->height; if( width > 160 || height > 160 ) art_file = save_resized_album_art(imsrc, file); else art_file = strdup(file); image_free(imsrc); return(art_file); } } //- 20130708 Sungmin add int i; struct dirent **namelist; int n = scandir(dir, &namelist, filter_image_files, alphasort); if(n>0) { snprintf(file, sizeof(file), "%s/%s", dir, namelist[0]->d_name); for(i=0; i<n; i++) { free(namelist[i]); } if( access(file, R_OK) == 0 ) { if( art_cache_exists(file, &art_file) ) { return art_file; } free(art_file); imsrc = image_new_from_jpeg(file, 1, NULL, 0, 1, ROTATE_NONE); if( !imsrc ) return NULL; width = imsrc->width; height = imsrc->height; //DPRINTF(E_WARN, L_METADATA, "imgsrc->size: %s, %d X %d\n", file, width, height); if( width > 160 || height > 160 ) art_file = save_resized_album_art(imsrc, file); else art_file = strdup(file); image_free(imsrc); return art_file; } } return NULL; }
int64_t GetAudioMetadata(const char *path, char *name) { char type[4]; static char lang[6] = { '\0' }; struct stat file; int64_t ret; char *esc_tag; int i; int64_t album_art = 0; struct song_metadata song; metadata_t m; uint32_t free_flags = FLAG_MIME|FLAG_DURATION|FLAG_DLNA_PN|FLAG_DATE; memset(&m, '\0', sizeof(metadata_t)); if ( stat(path, &file) != 0 ) return 0; strip_ext(name); if( ends_with(path, ".mp3") ) { strcpy(type, "mp3"); m.mime = strdup("audio/mpeg"); } else if( ends_with(path, ".m4a") || ends_with(path, ".mp4") || ends_with(path, ".aac") || ends_with(path, ".m4p") ) { strcpy(type, "aac"); m.mime = strdup("audio/mp4"); } else if( ends_with(path, ".3gp") ) { strcpy(type, "aac"); m.mime = strdup("audio/3gpp"); } else if( ends_with(path, ".wma") || ends_with(path, ".asf") ) { strcpy(type, "asf"); m.mime = strdup("audio/x-ms-wma"); } else if( ends_with(path, ".flac") || ends_with(path, ".fla") || ends_with(path, ".flc") ) { strcpy(type, "flc"); m.mime = strdup("audio/x-flac"); } else if( ends_with(path, ".wav") ) { strcpy(type, "wav"); m.mime = strdup("audio/x-wav"); } else if( ends_with(path,".oga") || ends_with(path,".ogg")) { /* The .ogg/.oga file extensions present something of a problem. * ".ogg" has been deprecated in favor of ".oga" for some time, but * many applications still only recognize ".ogg". * * This examines the file and causes .ogg to be presented for any naked * Vorbis file (MIME type audio/ogg; codecs=vorbis) and .oga * (audio/ogg) to be used for everything else. This is in line with * the official ogg naming conventions and, hopefully, makes for a * resonable compromise. */ uint8_t oggtestbuf[35]; FILE *oggfile = fopen (path, "rb"); if (oggfile == (FILE *)NULL) { DPRINTF(E_ERROR, L_METADATA, "Error opening %s\n", path); return 0; } if (fread (oggtestbuf, 1, 35, oggfile) != 35) { DPRINTF(E_WARN, L_METADATA, "Premature EOF on %s\n", path); fclose (oggfile); return 0; } fclose (oggfile); if (memcmp (&oggtestbuf[28], "\x01vorbis", 7)) m.mime = strdup ("audio/ogg"); else m.mime = strdup ("audio/ogg; codecs=vorbis"); strcpy(type, "ogg"); } else if ( ends_with(path, ".opus") ) { strcpy(type,"ops"); m.mime = strdup("audio/ogg; codecs=opus"); } #if 0 /* Not supported yet, and probably won't be. */ else if( ends_with(path, ".ogx") ) { strcpy(type, "ogx"); m.mime = strdup("application/ogg"); } #endif else if( ends_with(path, ".pcm") ) { strcpy(type, "pcm"); m.mime = strdup("audio/L16"); } else { DPRINTF(E_WARN, L_METADATA, "Unhandled file extension on %s\n", path); return 0; } if( !(*lang) ) { if( !getenv("LANG") ) strcpy(lang, "en_US"); else strncpyt(lang, getenv("LANG"), sizeof(lang)); } if( readtags((char *)path, &song, &file, lang, type) != 0 ) { DPRINTF(E_WARN, L_METADATA, "Cannot extract tags from %s!\n", path); freetags(&song); free_metadata(&m, free_flags); return 0; } if( song.dlna_pn ) m.dlna_pn = strdup(song.dlna_pn); if( song.year ) xasprintf(&m.date, "%04d-01-01", song.year); xasprintf(&m.duration, "%d:%02d:%02d.%03d", (song.song_length/3600000), (song.song_length/60000%60), (song.song_length/1000%60), (song.song_length%1000)); if( song.title && *song.title ) { m.title = trim(song.title); if( (esc_tag = escape_tag(m.title, 0)) ) { free_flags |= FLAG_TITLE; m.title = esc_tag; } } else { m.title = name; } for( i = ROLE_START; i < N_ROLE; i++ ) { if( song.contributor[i] && *song.contributor[i] ) { m.creator = trim(song.contributor[i]); if( strlen(m.creator) > 48 ) { m.creator = strdup("Various Artists"); free_flags |= FLAG_CREATOR; } else if( (esc_tag = escape_tag(m.creator, 0)) ) { m.creator = esc_tag; free_flags |= FLAG_CREATOR; } m.artist = m.creator; break; } } /* If there is a album artist or band associated with the album, use it for virtual containers. */ if( i < ROLE_ALBUMARTIST ) { for( i = ROLE_ALBUMARTIST; i <= ROLE_BAND; i++ ) { if( song.contributor[i] && *song.contributor[i] ) break; } if( i <= ROLE_BAND ) { m.artist = trim(song.contributor[i]); if( strlen(m.artist) > 48 ) { m.artist = strdup("Various Artists"); free_flags |= FLAG_ARTIST; } else if( (esc_tag = escape_tag(m.artist, 0)) ) { m.artist = esc_tag; free_flags |= FLAG_ARTIST; } } } if( song.album && *song.album ) { m.album = trim(song.album); if( (esc_tag = escape_tag(m.album, 0)) ) { free_flags |= FLAG_ALBUM; m.album = esc_tag; } } if( song.genre && *song.genre ) { m.genre = trim(song.genre); if( (esc_tag = escape_tag(m.genre, 0)) ) { free_flags |= FLAG_GENRE; m.genre = esc_tag; } } if( song.comment && *song.comment ) { m.comment = trim(song.comment); if( (esc_tag = escape_tag(m.comment, 0)) ) { free_flags |= FLAG_COMMENT; m.comment = esc_tag; } } album_art = find_album_art(path, song.image, song.image_size); ret = sql_exec(db, "INSERT into DETAILS" " (PATH, SIZE, TIMESTAMP, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE," " TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, DISC, TRACK, DLNA_PN, MIME, ALBUM_ART) " "VALUES" " (%Q, %lld, %lld, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, %d, %Q, '%s', %lld);", path, (long long)file.st_size, (long long)file.st_mtime, m.duration, song.channels, song.bitrate, song.samplerate, m.date, m.title, m.creator, m.artist, m.album, m.genre, m.comment, song.disc, song.track, m.dlna_pn, song.mime?song.mime:m.mime, album_art); if( ret != SQLITE_OK ) { DPRINTF(E_ERROR, L_METADATA, "Error inserting details for '%s'!\n", path); ret = 0; } else { ret = sqlite3_last_insert_rowid(db); } freetags(&song); free_metadata(&m, free_flags); return ret; }