-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
KasaneKona
committed
Sep 9, 2020
0 parents
commit 1e12adc
Showing
4 changed files
with
163 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Bad Apple!! for Sansa Clip+ (Rockbox Lua) | ||
|
||
Bad Apple!! video player implemented in Lua for Rockbox, designed to run on the Sansa Clip+ MP3 player. | ||
|
||
Video data assumes a 128x64 display with a 2px gap between visible regions. | ||
|
||
## Files for playing: | ||
- badapple.lua: main script, copy to player storage or SD card | ||
- vid.mp3 (download in Releases): audio data, MP3 format, copy to main script location | ||
- vid.bin (download in Releases): video data, display native format, copy to main script location | ||
|
||
## Additional files: | ||
- vidsrc.mp4 (not provided): 30fps video source, 2:1 aspect ratio | ||
- vidextract.bat: extracts vidsrc.mp4 to frame images and vid.mp3, requires ffmpeg | ||
- writedata.py: converts frame images to display native format, vid.bin | ||
- vidframes: frame image target for vidextract/writedata | ||
|
||
## Theory of operation: | ||
Audio is played natively by the Rockbox MP3 codec. | ||
|
||
Video data is stored at pre-dithered 1-bit-per-pixel, in native display order. It is dithered at 100fps. | ||
The data is read from the file and blitted directly to the display. | ||
Each frame is delayed approximately 1/100th of a second, which achieves rough speed sync, then frames are skipped to maintain time sync. | ||
As the source data is only 30fps before dithering, these skips are not noticeable. | ||
I wanted to do some kind of compression, but both ends of the process use strings to store the binary data, and Lua's string manipulation is too slow for this. | ||
|
||
The Lua script also performs some extra functions like setting the display to maximum brightness, and clearing some settings to ensure the audio plays at the right speed/pitch. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
require("actions") | ||
require("rbsettings") | ||
require("settings") | ||
--mathex=require("math_ex") | ||
do_play=true | ||
|
||
datafile = nil | ||
|
||
function prepare_data(vidfn,audfn) | ||
-- video | ||
root = rb.current_path() | ||
datafile = io.open(root..vidfn,"rb") | ||
rb.sleep(rb.HZ/10) | ||
-- audio | ||
audfile=io.open(root..'_.m3u','w') | ||
audfile:write(root..audfn..'\n') | ||
audfile:close() | ||
rb.playlist('create',root,'_.m3u') | ||
rb.yield() | ||
rb.playlist('start',0,0*1000,0) | ||
rb.audio("pause") -- immediately pause - add silence at the start to avoid click | ||
end | ||
|
||
function play() | ||
if datafile == nil then return end | ||
do_play = true | ||
-- get that data flowing! not sure why this helps. cache? | ||
for i = 1,50 do | ||
datafile:read(1024) | ||
end | ||
datafile:seek("set",0) | ||
rb.lcd_clear_display() -- blank screen | ||
rb.lcd_update() | ||
rb.sleep(rb.HZ/2) | ||
rb.yield() | ||
collectgarbage("collect") | ||
lastframe = -1 | ||
dat = nil | ||
rb.audio("resume") | ||
while do_play do | ||
if rb.get_plugin_action(0)==rb.actions.PLA_CANCEL then break end -- kill on button | ||
if rb.audio("status") == 0 then break end -- kill on stop | ||
playtime = rb.audio("elapsed") | ||
if playtime == nil then break end | ||
timeframe = (playtime / 10) + 20 -- 100fps data | ||
if timeframe >= 0 then | ||
if lastframe > timeframe then break end -- kill on repeat | ||
if lastframe < timeframe then | ||
while lastframe < timeframe-20 do -- pos sync | ||
datafile:seek("cur",1024) | ||
lastframe = lastframe + 1 | ||
end | ||
dat = datafile:read(1024) | ||
lastframe = lastframe + 1 | ||
end | ||
if dat == nil then break end -- kill on data end | ||
rb.lcd_blit_mono(dat,nil,0,0,128,8,128) | ||
rb.sleep(rb.HZ/100) -- rate sync | ||
end | ||
end | ||
datafile:close() | ||
end | ||
|
||
--MAIN PROGRAM | ||
rb.backlight_force_on() -- disable timeout | ||
rb.cpu_boost(true) -- enable overclock | ||
rb.audio('stop') -- clear currently playing | ||
rb.sound_set_pitch(10000) -- reset pitch to +0 | ||
rb.lcd_clear_display() -- blank screen | ||
rb.lcd_update() | ||
rb.lcd_set_contrast(999) -- full brightness | ||
prepare_data("vid.bin","vid.mp3") -- get files ready | ||
play() -- play video | ||
rb.audio('stop') -- stop music | ||
os.remove(rb.current_path()..'_.m3u') -- remove temp playlist | ||
rb.lcd_set_contrast(rb.settings.read("global_settings",rb.system.global_settings.contrast,"system")) -- restore brightness | ||
rb.backlight_use_settings() -- restore timeout | ||
rb.lcd_clear_display() -- blank screen | ||
rb.lcd_update() | ||
rb.sleep(20) -- wait a moment | ||
rb.cpu_boost(false) -- disable overclock |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
@echo off | ||
del /Q vidframes\* | ||
del vid.mp3 | ||
ffmpeg -i vidsrc.mp4 -s 132x66 -sws_flags neighbor vidframes/frame%%04d.png | ||
ffmpeg -i vidsrc.mp4 -q:a 0 -map a vid.mp3 | ||
pause |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from PIL import Image | ||
import os | ||
import sys | ||
import random | ||
|
||
def itoxy(i): | ||
ipage = i&1023 | ||
x = ipage/8 | ||
y = 7-ipage&7 | ||
y += (i//1024)*8 | ||
if y >= 16: | ||
y += 2 | ||
x += 2 | ||
return (x,y) | ||
|
||
f = open('vid.bin', 'w+b') | ||
random.seed("ksk2020") | ||
frame_multiplier = 4 # 30 -> 120 | ||
frame_skip_every = 6 # 120 -> 100 | ||
frame_limit = -1 | ||
fcnt = 0 | ||
scnt = 0 | ||
for filename in os.listdir("vidframes"): | ||
if filename.endswith(".png"): | ||
sys.stdout.write(filename+"\n") | ||
fi = Image.open(os.path.join("vidframes", filename)).convert("RGB") | ||
px = fi.load() | ||
for fm in range(frame_multiplier): | ||
scnt = (scnt + 1) % frame_skip_every | ||
if scnt == 0: | ||
continue | ||
byte_arr = [0]*1024 | ||
for i in range(1024): | ||
byte = 0 | ||
for j in range(8): | ||
k = i*8+j | ||
pv = px[itoxy(k)][1] | ||
pv /= 255 | ||
pv = (pv * 1.04) - 0.02 | ||
#pv = 0.5 | ||
pv = 1 if pv >= random.random() else 0 | ||
byte += byte + pv | ||
byte_arr[i] = byte | ||
binary_format = bytearray(byte_arr) | ||
f.write(binary_format) | ||
fcnt += 1 | ||
if fcnt == frame_limit: | ||
break | ||
f.close() |