Script that iterates over all photos and movies saved on an SD card and uploads them to an HTTP endpoint. Uploaded files are renamed. A tiny node server receives the files.
Last active
September 9, 2021 15:29
-
-
Save flostellbrink/61fbadd9cad26843d5697a3c851c0a67 to your computer and use it in GitHub Desktop.
FlashAir Media Uploader
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "file_receiver", | |
"version": "0.0.0", | |
"description": "receive files uploaded by flashair", | |
"main": "server.js", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"author": "flo", | |
"license": "ISC", | |
"dependencies": { | |
"formidable": "^1.1.1", | |
"winston": "^2.3.1" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* File Receiver | |
* Handles uploads from flashair. | |
* Install dependencies with npm using the package.json file: https://docs.npmjs.com/cli/install | |
* Start with node: https://nodejs.org/en/ | |
*/ | |
var util = require('util') | |
var spawn = require('child_process').spawn; | |
var http = require('http'); | |
var formidable = require('formidable'); | |
var fs = require('fs'); | |
var winston = require('winston'); | |
var uploadPath = '/media/main/Bilder/FlashAir/'; | |
// Create log file | |
winston.add(winston.transports.File, { filename: '/home/pi/upload/winston.json' }); | |
// Redirect all errors into log file | |
function errorHandler(err) { | |
if(err) { | |
winston.error(err); | |
throw err; | |
} | |
} | |
winston.info('Starting file receiver'); | |
http.createServer(function (req, res) { | |
// Handle upload requests | |
if (req.url == '/upload') { | |
winston.info('Upload request'); | |
var form = new formidable.IncomingForm(); | |
form.parse(req, function (err, fields, files) { | |
// console.log(util.inspect(files, false, null)); | |
if(!files.file) { | |
winston.error('No file in request'); | |
res.writeHead(400); | |
res.end('No file in request'); | |
return; | |
} | |
var readStream = fs.createReadStream(files.file.path); | |
var writeStream = fs.createWriteStream(uploadPath + files.file.name); | |
winston.info('File uploaded to: ' + files.file.path) | |
readStream.on('error', errorHandler); | |
writeStream.on('error', errorHandler); | |
readStream.on('close', function () { | |
winston.info('File moved to destination with name: ' + files.file.name) | |
res.end('Done'); | |
fs.unlink(files.file.path, function(err) { | |
errorHandler(err); | |
if(err) return; | |
winston.info('Removed temporary file'); | |
}); | |
/* I run a backup and sort script at this point. | |
* Feel free to uncomment this and insert your own. | |
winston.info('Running backup and sorting'); | |
spawn('bash', [ '/home/pi/backup/backup.sh' ]); | |
*/ | |
}); | |
readStream.pipe(writeStream); | |
}); | |
} else { | |
// For other requests show a basic html upload form | |
res.writeHead(200, {'Content-Type': 'text/html'}); | |
res.write('<form action="upload" method="post" enctype="multipart/form-data">'); | |
res.write('<input type="file" name="file"><br>'); | |
res.write('<input type="submit">'); | |
res.write('</form>'); | |
return res.end(); | |
} | |
}).listen(3333); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- FlashAir Lua script | |
-- Copy to 'Lua' folder on your SD card | |
-- Set as RUN_SCRIPT/SD_EVENT in your SD_WLAN/CONFIG file: | |
-- LUA_RUN_SCRIPT=/Lua/upload.lua | |
-- LUA_SD_EVENT=/Lua/upload.lua | |
local httpendpoint = 'http://192.168.1.13:3333/upload' | |
local folder = '/DCIM' | |
-- Recursively iterate through directories | |
function walk_directory(folder, extension) | |
for file in lfs.dir(folder) do | |
-- Not sure if this is necessary, but it keeps 'LUA : not enough memory' out of the log | |
collectgarbage() | |
-- Find file path and type | |
local path = folder .. "/" .. file | |
local attr = lfs.attributes(path) | |
if attr.mode == "file" then | |
handle_file(file, path, extension) | |
elseif attr.mode == "directory" then | |
walk_directory(path, extension) | |
end | |
end | |
end | |
-- Check file for upload, upload and rename it | |
function handle_file(file, path, extension) | |
-- Find files not yet marked as "_SAVED" | |
if string.sub(path, -4, -1) ~= extension then return end | |
if string.sub(path, -10, -5) == "_SAVED" then return end | |
-- Upload file to HTTP server | |
local boundary = '--61141483716826' | |
local contenttype = 'multipart/form-data; boundary=' .. boundary | |
-- <!--WLANSDFILE--> will be replaced with file content | |
local body = '--'.. boundary .. '\r\n' | |
..'Content-Disposition: form-data; name=\"file\"; filename=\"'..file ..'\"\r\n' | |
..'Content-Type: text/plain\r\n' | |
..'\r\n' | |
..'<!--WLANSDFILE-->\r\n' | |
..'--' .. boundary .. '--\r\n' | |
-- Exclude size of placeholder | |
local size = lfs.attributes(path, 'size') + string.len(body) - 17 | |
-- Execute request (returns response body, response code, response header) | |
local b, c, h = fa.request { | |
url = httpendpoint, | |
method = 'POST', | |
headers = {['Content-Length'] = tostring(size), | |
['Content-Type'] = contenttype}, | |
file = path, | |
body = body | |
} | |
b, h = nil | |
collectgarbage() | |
if (c ~= 200) then return end | |
-- Mark file as "_SAVED" | |
fa.rename(path, string.sub(path, 0, -5) .. '_SAVED' .. string.sub(path, -4, -1)) | |
end | |
-- Keep checking WLAN status until it is up or 30 seconds passed | |
for i = 1, 300 do | |
-- Allow card to connect with WLAN | |
sleep(100) | |
-- Check if WLAN is up | |
if fa.WlanLink() == 1 then | |
-- Upload pictures first | |
walk_directory(folder, '.JPG') | |
-- Upload movies later, they are much larger and might fail | |
walk_directory(folder, '.MOV') | |
break | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment